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 | 1434 +++++++++++++++++++++++++++++++++--------------------------
1 files changed, 805 insertions(+), 629 deletions(-)
diff --git a/inc/managers/UploadManager.php b/inc/managers/UploadManager.php
index 6c9655a..4889dbc 100644
--- a/inc/managers/UploadManager.php
+++ b/inc/managers/UploadManager.php
@@ -1,338 +1,452 @@
<?php
namespace JVBase\managers;
-use JVBase\JVB;
-use JVBase\meta\MetaManager;
+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')) {
- exit; // Exit if accessed directly
+ exit;
}
+
/**
- * Handles file uploads for edmonton.ink dashboard
- * Includes image processing, validation, optimization, and SEO-friendly naming
+ * 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
{
- /**
- * @var array Default configuration
- */
- protected array $config = [
- 'allowed_types' => [
- 'image/jpeg',
- 'image/png',
- 'image/gif',
- 'image/webp',
- ],
- 'max_size' => 5242880, // 5MB
- 'convert_to_webp' => true,
- 'webp_quality' => 80,
- 'optimize_images' => true,
- 'create_thumbnails' => true,
- 'original_retention' => 2592000, // 30 days in seconds
- 'use_imagick' => null // Will be set in constructor
+ use Instance;
+ protected array $allowedTypes = [
+ // Images
+ 'image/jpeg' => 'image',
+ 'image/png' => 'image',
+ 'image/gif' => 'image',
+ 'image/webp' => 'image',
+ // Videos
+ 'video/mp4' => 'video',
+ 'video/webm' => 'video',
+ 'video/ogg' => 'video',
+ 'video/ogv' => 'video',
+ 'video/quicktime' => 'video',
+ 'video/x-msvideo' => 'video',
+ // Documents
+ 'application/pdf' => 'document',
+ 'application/msword' => 'document',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'document',
+ 'application/vnd.ms-excel' => 'document',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'document',
+ 'text/plain' => 'document',
+ 'text/csv' => 'document',
+ 'application/rtf' => 'document'
];
- protected string $post_type;
- protected int $user_id;
- protected int $term_id;
- protected string $upload_dir;
- protected string $upload_url;
- protected object $cache;
- protected object $notifications;
- protected int $max_retries = 3;
- protected int $retry_delay = 300; // 5 minutes
- public function __construct($post_type, $user_id, $config = [])
- {
- $this->post_type = $post_type;
- $this->user_id = $user_id;
- $this->config = wp_parse_args($config, $this->config);
- // Check for Imagick availability
- $this->config['use_imagick'] = extension_loaded('imagick');
+ protected array $supportedFormats = [
+ 'webp' => 'image/webp',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'png' => 'image/png',
+ 'gif' => 'image/gif'
+ ];
- $this->setupUploadDirs();
+ protected array $defaultConfig = [
+ 'allowed_types' => null, // null = all types allowed
+ 'max_size' => [
+ 'image' => 5242880, // 5MB
+ 'video' => 104857600, // 100MB
+ 'document' => 10485760 // 10MB
+ ],
+ // Image settings
+ 'convert' => 'webp',
+ 'quality' => 80,
+ 'optimize_images' => true,
+ 'create_thumbnails' => true,
+ 'use_imagick' => null,
+ // Video settings
+ 'extract_video_thumbnail' => true,
+ 'video_thumbnail_time' => 0,
+ // Document settings
+ 'extract_document_preview' => false,
+ // Directory structure
+ 'directory_pattern' => '{user_id}/{post_type}',
+ // General
+ 'original_retention' => 2592000, // 30 days
+ ];
-
- // Schedule cleanup of original files
-// if (!wp_next_scheduled('jvb_cleanup_original_uploads')) {
-// wp_schedule_event(time(), 'daily', 'jvb_cleanup_original_uploads');
-// }
-// add_action('jvb_cleanup_original_uploads', [$this, 'cleanupOriginalFiles']);
-
-
- // Track upload statistics
- add_action('jvb_upload_complete', [$this, 'trackUploadStats']);
- add_action('jvb_upload_failed', [$this, 'track_failed_upload']);
- }
-
- /**
- * @param array $file_data
- *
- * @return array
- * @throws Exception
- */
- public function secureUploadedFile(array $file_data): array
+ /******************************************************************
+ * 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;
+ protected string $dir_pattern;
+ /*****************************************************************
+ * FEATURE FLAG
+ *****************************************************************/
+ protected bool $has_imagick = false;
+ private function __construct()
{
- // Create a persistent temporary directory if it doesn't exist
- $temp_dir = "{$this->upload_dir}/jvb_temp_uploads/{$this->user_id}";
- wp_mkdir_p($temp_dir);
+ $upload = wp_upload_dir();
+ $this->upload_dir = $upload['basedir'];
+ $this->upload_url = $upload['baseurl'];
- // Generate a unique filename for temporary storage
- $temp_filename = uniqid() . '_' . sanitize_file_name($file_data['name']);
- $temp_path = "{$temp_dir}/{$temp_filename}";
+ 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;
- // Move the uploaded file to our temporary storage
- if (!move_uploaded_file($file_data['tmp_name'], $temp_path)) {
- // Fallback to copy if needed
- if (!copy($file_data['tmp_name'], $temp_path)) {
- throw new Exception('Failed to store uploaded file');
+ 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');
}
- // Return metadata about the stored file
- return [
- 'temp_path' => $temp_path,
- 'original_name' => $file_data['name'],
- 'mime_type' => $file_data['type'],
- 'file_size' => $file_data['size'],
- 'stored_at' => current_time('mysql')
- ];
- }
-
- /**
- * @param string $temp_path
- * @param string $post_type
- * @param int $post_id
- * @param int $term_id
- * @param string $original_filename
- *
- * @return array
- * @throws Exception
- */
- public function processImageFromStorage(string $temp_path, string $post_type, int $post_id = 0, int $term_id = 0, string $original_filename = ''): array
- {
- if (!file_exists($temp_path)) {
- throw new Exception('Stored file no longer exists: ' . $temp_path);
+ $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);
}
- $this->term_id = $term_id;
- $this->post_type = $post_type;
-
- // Get base upload directory based on content type
- $rel_path = $this->getUploadDirectory();
-
- // Ensure the directory exists
- $full_upload_path = $this->upload_dir . '/' . $rel_path;
- if (!wp_mkdir_p($full_upload_path)) {
- throw new Exception("Failed to create upload directory: {$full_upload_path}");
+ // 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);
}
- // Generate filename WITHOUT extension (generateFilename should not include extension)
- $base_filename = $this->generateFilename($original_filename, get_userdata($this->user_id));
-
- // Process the image directly from our temporary storage
- return $this->processImage($temp_path, $rel_path, $base_filename, $post_id);
- }
-
- /**
- * @return void
- * @throws Exception
- */
- protected function setupUploadDirs():void
- {
- $upload_info = wp_upload_dir();
- if ($upload_info['error']) {
- throw new Exception($upload_info['error']);
- }
-
- $this->upload_dir = $upload_info['basedir'];
- $this->upload_url = $upload_info['baseurl'];
- }
-
- /**
- * @return string
- */
- public function getUploadDirectory():string
- {
- // Default WordPress organization: year/month
- $default_path = date('Y/m');
-
- /**
- * Filter the upload directory structure
- *
- * @param string $path The default upload path
- * @param string $post_type The post type being uploaded
- * @param int $user_id The user ID
- * @param int $term_id The term ID (if applicable)
- */
- return apply_filters('jvb_upload_directory', $default_path, $this->post_type, $this->user_id, $this->term_id);
- }
-
- /**
- * Generate filename with extensible filtering
- * Default: generic SEO-friendly format
- *
- * @param string $original_name
- * @param object $user_data
- * @return string
- */
- public function generateFilename(string $original_name, object $user_data): string
- {
- // Default generic filename: {post_type}-{user_id}-{counter}
- $filename = sprintf(
- '%s-%s',
- sanitize_title($this->post_type),
- $this->user_id
- );
-
- /**
- * Filter the generated filename (without extension)
- *
- * @param string $filename The generated filename
- * @param string $original_name The original filename
- * @param object $user_data WordPress user data object
- * @param string $post_type The post type
- * @param int $user_id The user ID
- * @param int $term_id The term ID (if applicable)
- */
- return apply_filters('jvb_upload_filename', $filename, $original_name, $user_data, $this->post_type, $this->user_id, $this->term_id).'-'.$this->getNextFileCounter();
- }
-
- /**
- * @return string
- */
- protected function getNextFileCounter(): string
- {
- // Get counter key for this post type
- $counter_key = 'upload_counter_' . str_replace(['/', '\\'], '_', $this->post_type);
-
- // Get current counter value, default to 0
- $counter = (int)get_user_meta($this->user_id, $counter_key, true) ?: 0;
-
- // Increment counter
- $counter++;
-
- // Update counter in user meta
- update_user_meta($this->user_id, $counter_key, $counter);
-
- // Return formatted counter
- return sprintf('%08d', $counter);
- }
-
-
- /**
- * Generate alt text with filtering for customization
- * Default: basic or empty alt text
- *
- * @param string $file
- * @param object $user_data
- * @param int|null $post_id
- * @return string
- */
- protected function generateAltText(string $file, object $user_data, int|null $post_id = null): string
- {
- // Default: basic alt text or empty
- $alt_text = '';
-
- /**
- * Filter the generated alt text
- *
- * @param string $alt_text The generated alt text
- * @param string $file The file path
- * @param object $user_data WordPress user data object
- * @param int|null $post_id The post ID (if applicable)
- * @param string $post_type The post type
- * @param int $user_id The user ID
- * @param int $term_id The term ID (if applicable)
- */
- return apply_filters('jvb_upload_alt_text', $alt_text, $file, $user_data, $post_id, $this->post_type, $this->user_id, $this->term_id);
+ return $file_type;
}
/**
- * Generate image title with filtering for customization
- * Default: WordPress default behavior (filename-based)
- *
- * @param string $file
- * @param object $user_data
- * @param int|null $post_id
- * @return string
+ * Initial, basic processing of upload data for later processing through OperationQueue.php
+ * @param array $file_data
+ * @param array $context
+ * @return array
*/
- protected function generateImageTitle(string $file, object $user_data, int|null $post_id = null): string
- {
- // Default: Use filename without extension (WordPress default behavior)
- $title = pathinfo($file, PATHINFO_FILENAME);
-
- /**
- * Filter the generated image title
- *
- * @param string $title The generated title
- * @param string $file The file path
- * @param object $user_data WordPress user data object
- * @param int|null $post_id The post ID (if applicable)
- * @param string $post_type The post type
- * @param int $user_id The user ID
- * @param int $term_id The term ID (if applicable)
- */
- return apply_filters('jvb_upload_image_title', $title, $file, $user_data, $post_id, $this->post_type, $this->user_id, $this->term_id);
- }
-
- /**
- * @param array $file_data
- * @param array $options
- *
- * @return array|WP_Error
- */
- public function handleContentUpload(array $file_data, array $options = []): array|WP_Error
+ public function secureUploadedFile(array $file_data, array $context): array
{
try {
- if (!isset($file_data['tmp_name']) || !is_uploaded_file($file_data['tmp_name'])) {
- throw new Exception('No valid file uploaded.');
+ $file_type = $this->testFile($file_data, $context);
+
+ $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']);
+ $temp_path = "{$this->upload_dir}/{$temp_dir}/{$temp_filename}";
+
+ // Move the uploaded file to our temporary storage
+ if (!move_uploaded_file($file_data['tmp_name'], $temp_path)) {
+ // Fallback to copy if needed
+ if (!copy($file_data['tmp_name'], $temp_path)) {
+ throw new Exception('Failed to store uploaded file');
+ }
}
- $user_data = get_userdata($this->user_id);
- $post_id = $options['post_id'] ?? 0;
- $this->term_id = $options['term_id'] ?? 0;
+ // Return metadata about the stored file
+ return [
+ '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')
+ ];
+ } catch (Exception $e) {
+ Errors::getInstance()->log('UploadManager','Failed to store uploaded file: ' . print_r($e->getMessage(), true));
+ return [];
+ }
+ }
- // Get base upload directory based on content type
- $base_dir = $this->getUploadDirectory();
- $original_dir = 'originals';
+ /**
+ * Process an uploaded file with full context
+ *
+ * @param string $temp_path
+ * @param array $context Upload context and configuration
+ * @return array|WP_Error Result with attachment_id, url, file path
+ */
+ public function processUpload(string $temp_path, array $context): array|WP_Error
+ {
+ try {
+ $file_type = $this->testFile($temp_path, $context);
- $rel_path = $base_dir;
- $this->ensureUploadDirs($rel_path, $original_dir);
+ // Extract context
+ $user_id = $context['user_id'] ?? get_current_user_id();
+ $post_id = $context['post_id'] ?? 0;
- $filename = $this->generateFilename($file_data['name'], $user_data);
+ // Validate file size
+ $this->validateFileSize($temp_path, $file_type, $context);
- // Store original file
- $original_file = $this->storeOriginalFile(
- $file_data['tmp_name'],
- $base_dir,
- $original_dir,
- $filename
- );
+ // Generate directory structure for final storage
+ $directory = $this->generateDirectory($file_type, $context);
- // Process immediately
- return $this->processImage($original_file, $rel_path, $filename, $post_id);
+ // Generate filename
+ $original_name = $context['original_name'] ?? basename($temp_path);
+ $filename = $this->generateFilename($original_name, $context);
+
+ // Process based on file type
+ return match($file_type) {
+ '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) {
+ Errors::getInstance()->log(
+ '[UploadManager]:processUpload',
+ $e->getMessage(),
+ ['temp_path' => $temp_path, 'context' => $context]
+ );
+
return new WP_Error('upload_failed', $e->getMessage());
}
}
- /**
- * @param string $rel_path
- * @param string $original_dir
- *
- * @return void
- * @throws Exception
- */
- protected function ensureUploadDirs($rel_path, $original_dir): void
+
+ /**
+ * Clean up empty temporary directories
+ */
+ public function cleanupEmptyTempDirs(int $user_id): void
+ {
+ $temp_base = "{$this->upload_dir}/{$user_id}/temp";
+
+ if (!is_dir($temp_base)) {
+ return;
+ }
+
+ // Get all subdirectories
+ $dirs = glob($temp_base . '/*', GLOB_ONLYDIR);
+
+ foreach ($dirs as $dir) {
+ // Check if directory is empty
+ $files = scandir($dir);
+ $files = array_diff($files, ['.', '..']);
+
+ if (empty($files)) {
+ @rmdir($dir);
+ }
+ }
+
+ // Try to remove temp base if empty
+ $files = scandir($temp_base);
+ $files = array_diff($files, ['.', '..']);
+ if (empty($files)) {
+ @rmdir($temp_base);
+ }
+ }
+
+ /**
+ * Generate directory structure based on pattern
+ * @param string $file_type
+ * @param array $config
+ * @return string
+ * @throws Exception
+ */
+ public function generateDirectory(string $file_type, array $config): string
+ {
+ $pattern = array_key_exists('pattern', $config) && is_string($config['pattern']) ? $config['pattern'] : $this->dir_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
+ $directory = apply_filters('jvb_upload_directory', $directory, $file_type, $config);
+
+ // Ensure directories exist
+ $this->ensureDirectories($directory);
+ return $directory;
+ }
+
+ /**
+ * Ensure directory structure exists
+ */
+ protected function ensureDirectories(string $rel_path): void
{
$dirs = [
- "{$this->upload_dir}/{$rel_path}",
- "{$this->upload_dir}/{$rel_path}/{$original_dir}"
+ "{$this->upload_dir}/{$rel_path}"
];
foreach ($dirs as $dir) {
@@ -344,295 +458,464 @@
/**
- * @param string $tmp_file
- * @param string $base_dir
- * @param string $original_dir
- * @param string $filename
- * @return string
+ * 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 storeOriginalFile(string $tmp_file, string $base_dir, string $original_dir, string $filename): string
+ protected function validateFileSize(string $file_path, string $file_type, array $config): void
{
- $ext = pathinfo($tmp_file, PATHINFO_EXTENSION);
- $original_path = "{$this->upload_dir}/{$base_dir}/{$original_dir}/{$filename}.{$ext}";
-
- if (!move_uploaded_file($tmp_file, $original_path)) {
- throw new Exception('Failed to store original file');
+ $size = filesize($file_path);
+ $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];
+ }
}
- return $original_path;
+ if ($size > $max_size) {
+ $max_mb = round($max_size / 1048576, 2);
+ $actual_mb = round($size / 1048576, 2);
+ throw new Exception("File too large: {$actual_mb}MB (max: {$max_mb}MB)");
+ }
+ }
+
+ protected function validateMimeType(string $file_path, string $declared_type): bool
+ {
+ // Get actual MIME type
+ $actual_type = mime_content_type($file_path);
+
+ // Check if actual type is allowed
+ if (!array_key_exists($actual_type, $this->allowedTypes)) {
+ return false;
+ }
+
+ // For images, verify it's actually an image
+ if (str_starts_with($actual_type, 'image/')) {
+ $image_info = @getimagesize($file_path);
+ if ($image_info === false) {
+ return false;
+ }
+ }
+
+ return true;
}
/**
- * Process image with better error handling
+ * Process image file
+ * @throws Exception
*/
- protected function processImage(string $original_file, string $rel_path, string $filename, int $post_id): array
+ protected function processImage(string $original_path, string $directory, string $filename, int $post_id, array $context): array
{
- // Validate the original file still exists
- if (!file_exists($original_file)) {
- throw new Exception('Original file no longer exists: ' . $original_file);
+ $full_dir = "{$this->upload_dir}/{$directory}";
+ $original_mime = mime_content_type($original_path);
+
+ // Determine if conversion is needed
+ $should_convert = false;
+ $target_format = false;
+
+ 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);
+ }
+ }
}
- // Verify file type before processing
- $mime_type = mime_content_type($original_file);
- if (!in_array($mime_type, $this->config['allowed_types'])) {
- throw new Exception('Invalid file type detected during processing: ' . $mime_type);
- }
+ // Convert or copy based on configuration
+ if ($should_convert) {
+ $final_path = "{$full_dir}/{$filename}.{$target_format}";
- // Ensure the upload directory exists
- $full_upload_dir = $this->upload_dir . '/' . $rel_path;
- if (!wp_mkdir_p($full_upload_dir)) {
- throw new Exception("Failed to create upload directory: {$full_upload_dir}");
- }
+ try {
- // Convert to WebP if enabled
- if ($this->config['convert_to_webp'] && $mime_type !== 'image/webp') {
- $final_path = "{$full_upload_dir}/{$filename}.webp";
+ $this->convertImage(
+ $original_path,
+ $final_path,
+ $target_format,
+ array_key_exists('quality', $context) && is_int($context['quality']) && $context['quality'] <= 100 ?
+ $context['quality'] :
+ $this->convertQuality,
+ );
- if ($this->config['use_imagick']) {
- $this->convertWithImagick($original_file, $final_path);
- } else {
- $this->convertWithGd($original_file, $final_path);
+ } catch (Exception $e) {
+ 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}";
+ copy($original_path, $final_path);
}
} else {
- // Just copy the original with its extension
- $original_ext = pathinfo($original_file, PATHINFO_EXTENSION);
- $final_path = "{$full_upload_dir}/{$filename}.{$original_ext}";
-
- if (!copy($original_file, $final_path)) {
- throw new Exception("Failed to copy file from {$original_file} to {$final_path}");
- }
+ // Keep original format
+ $ext = pathinfo($original_path, PATHINFO_EXTENSION);
+ $final_path = "{$full_dir}/{$filename}.{$ext}";
+ copy($original_path, $final_path);
}
- // Verify the final file was created
- if (!file_exists($final_path)) {
- throw new Exception("Final processed file was not created: {$final_path}");
+ // Create attachment
+ $attachment_id = $this->createAttachment($final_path, $filename, $context, $post_id);
+
+ // Set alt text if provided
+ $alt_text = apply_filters('jvb_upload_alt_text', '', $context);
+ if ($alt_text !== '') {
+ update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt_text);
}
- // Generate title text
- $title = $this->generateImageTitle(
- $final_path,
- get_userdata($this->user_id),
- $post_id
- );
-
- // Create attachment with title
- $attachment_id = $this->createAttachment($final_path, $title, $post_id);
-
// Generate thumbnails
- if ($this->config['create_thumbnails']) {
+ $hasOption = array_key_exists('create_thumbnails', $context);
+ if (($hasOption && $context['create_thumbnails']) || (!$hasOption && $this->createThumbnails)) {
$this->generateThumbnails($attachment_id);
}
- // Update post attachments with new file info
- $this->updatePostAttachments($attachment_id, $final_path);
-
return [
- 'success' => true,
+ 'success' => true,
'attachment_id' => $attachment_id,
- 'url' => wp_get_attachment_url($attachment_id),
- 'file' => $final_path
+ 'url' => wp_get_attachment_url($attachment_id),
+ 'file' => $final_path,
+ 'type' => 'image',
+ 'format' => $target_format ?: pathinfo($final_path, PATHINFO_EXTENSION),
+ 'context' => $context
];
}
- /**
- * @param string $source
- * @param string $destination
- *
- * @return void
- * @throws Exception
- */
- protected function convertWithImagick(string $source, string $destination, string $toType = 'webp'): void
+ /**
+ * Process video file
+ * @throws Exception
+ */
+ protected function processVideo(string $original_path, string $directory, string $filename, int $post_id, array $context): array
{
- $allowed = ['webp', 'jpeg', 'jpg', 'png'];
- if (!in_array($toType, $allowed)) {
- return;
+ $full_dir = "{$this->upload_dir}/{$directory}";
+ $ext = pathinfo($original_path, PATHINFO_EXTENSION);
+ $final_path = "{$full_dir}/{$filename}.{$ext}";
+
+ // Copy video to final location
+ copy($original_path, $final_path);
+
+ // Create attachment
+ try {
+ $attachment_id = $this->createAttachment($final_path, $filename, $context, $post_id);
+ } catch (Exception $e) {
+ Errors::getInstance()->log('Uploader', 'Attachment creation failed: '.print_r($e->getMessage(), true));
+ return ['success' => false];
}
+
+
+ // Extract thumbnail if enabled
+ $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,
+ 'attachment_id' => $attachment_id,
+ '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 $context): array
+ {
+ $full_dir = "{$this->upload_dir}/{$directory}";
+ $ext = pathinfo($original_path, PATHINFO_EXTENSION);
+ $final_path = "{$full_dir}/{$filename}.{$ext}";
+
+ // Copy document to final location
+ copy($original_path, $final_path);
+
+ // Create attachment
+ try {
+ $attachment_id = $this->createAttachment($final_path, $filename, $context, $post_id);
+ } catch (Exception $e) {
+ Errors::getInstance()->log('Uploader','Conversion failed: '.print_r($e->getMessage(), true));
+ return ['success' => false];
+ }
+
+ return [
+ 'success' => true,
+ 'attachment_id' => $attachment_id,
+ 'url' => wp_get_attachment_url($attachment_id),
+ 'file' => $final_path,
+ 'type' => 'document',
+ 'context' => $context
+ ];
+ }
+
+ /**
+ * Generate SEO-friendly filename
+ */
+ public function generateFilename(string $original_name, array $context): string
+ {
+ $name_parts = pathinfo($original_name);
+ $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 $this->sanitizeFileName(apply_filters(
+ 'jvb_upload_filename',
+ "{$base_name}-{$timestamp}",
+ $context
+ ));
+ }
+
+ protected function sanitizeFileName(string $filename): string
+ {
+ // Remove path info
+ $filename = basename($filename);
+
+ // Remove special characters
+ $filename = preg_replace('/[^a-zA-Z0-9._-]/', '_', $filename);
+
+ // Remove multiple underscores
+ $filename = preg_replace('/_+/', '_', $filename);
+
+ // Ensure it's not too long (255 char limit in most filesystems)
+ if (strlen($filename) > 200) {
+ $ext = pathinfo($filename, PATHINFO_EXTENSION);
+ $name = pathinfo($filename, PATHINFO_FILENAME);
+ $name = substr($name, 0, 200 - strlen($ext) - 1);
+ $filename = $name . '.' . $ext;
+ }
+
+ return $filename;
+ }
+
+ /**
+ * Create WordPress attachment
+ */
+ protected function createAttachment(string $file_path, string $title, array $context, int $post_id = 0): int
+ {
+ $file_url = str_replace($this->upload_dir, $this->upload_url, $file_path);
+ $mime_type = mime_content_type($file_path);
+
+ $title = apply_filters('jvb_upload_title', $title, $file_path, $context);
+
+ $attachment_id = wp_insert_attachment([
+ 'guid' => $file_url,
+ 'post_mime_type' => $mime_type,
+ 'post_title' => $title,
+ 'post_status' => 'inherit'
+ ], $file_path, $post_id);
+
+ if (is_wp_error($attachment_id)) {
+ throw new Exception('Failed to create attachment');
+ }
+
+ require_once(ABSPATH . 'wp-admin/includes/image.php');
+ $metadata = wp_generate_attachment_metadata($attachment_id, $file_path);
+ wp_update_attachment_metadata($attachment_id, $metadata);
+
+ return $attachment_id;
+ }
+
+
+ /**
+ * Convert an existing attachment to a different format
+ *
+ * @param int $attachment_id WordPress attachment ID
+ * @param string $format Target format: 'webp', 'jpeg', 'png', 'gif'
+ * @param int $quality Conversion quality (1-100)
+ * @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 = false): array|WP_Error
+ {
+ try {
+ // Validate format
+ $format = strtolower($format);
+ if (!isset($this->supportedFormats[$format])) {
+ throw new Exception("Unsupported format: {$format}");
+ }
+
+ // Get original file
+ $original_path = get_attached_file($attachment_id);
+ if (!$original_path || !file_exists($original_path)) {
+ throw new Exception('Original file not found');
+ }
+
+ // Check if it's an image
+ $mime_type = get_post_mime_type($attachment_id);
+ if (!str_starts_with($mime_type, 'image/')) {
+ throw new Exception('Attachment is not an image');
+ }
+
+ // Don't convert if already in target format
+ $current_mime = $this->supportedFormats[$format];
+ if ($mime_type === $current_mime) {
+ return [
+ 'success' => true,
+ 'message' => 'Already in target format',
+ 'attachment_id' => $attachment_id,
+ 'url' => wp_get_attachment_url($attachment_id)
+ ];
+ }
+
+ // Generate new filename
+ $path_info = pathinfo($original_path);
+ $new_filename = $path_info['filename'] . '.' . $format;
+ $new_path = $path_info['dirname'] . '/' . $new_filename;
+
+ // Perform conversion
+ $this->convertImage($original_path, $new_path, $format, $quality);
+
+ if ($replace) {
+ // Replace the original file
+ $this->replaceAttachmentFile($attachment_id, $new_path, $current_mime);
+ $result_attachment_id = $attachment_id;
+ } else {
+ // Create new attachment
+ $post_id = wp_get_post_parent_id($attachment_id);
+ $result_attachment_id = $this->createAttachment(
+ $new_path,
+ get_the_title($attachment_id),
+ ['user_id' => get_post_field('post_author', $attachment_id)],
+ $post_id
+ );
+ }
+
+ // Regenerate thumbnails
+ $this->generateThumbnails($result_attachment_id);
+
+ return [
+ 'success' => true,
+ 'attachment_id' => $result_attachment_id,
+ 'url' => wp_get_attachment_url($result_attachment_id),
+ 'format' => $format,
+ 'file' => $new_path
+ ];
+
+ } catch (Exception $e) {
+ return new WP_Error('conversion_failed', $e->getMessage());
+ }
+ }
+
+ /**
+ * Generic image conversion method
+ */
+ /**
+ * @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 ($this->has_imagick) {
+ $this->convertWithImagick($source, $destination, $format, $quality);
+ } else {
+ $this->convertWithGd($source, $destination, $format, $quality);
+ }
+ }
+
+ /**
+ * 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
+ {
try {
$image = new Imagick($source);
- $image->setImageFormat('webp');
- $image->setImageCompressionQuality($this->config['webp_quality']);
+
+ // Set format
+ $image->setImageFormat($format);
+
+ // Set quality
+ $image->setImageCompressionQuality($quality);
+
+ // Format-specific optimizations
+ if ($format === 'webp') {
+ $image->setOption('webp:method', '6'); // Better compression
+ } elseif ($format === 'jpeg' || $format === 'jpg') {
+ $image->setImageCompression(Imagick::COMPRESSION_JPEG);
+ } elseif ($format === 'png') {
+ $image->setImageCompression(Imagick::COMPRESSION_ZIP);
+ }
+
$image->writeImage($destination);
$image->clear();
} catch (Exception $e) {
- throw new Exception('WebP conversion with Imagick failed: ' . $e->getMessage());
+ throw new Exception("Imagick conversion to {$format} failed: " . $e->getMessage());
}
}
- /**
- * Fixed convertWithGd method with better error handling
- */
- protected function convertWithGd(string $source, string $destination, string $toType = 'webp'): void
+ /**
+ * 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
{
$mime_type = mime_content_type($source);
- // Ensure destination directory exists
- $dest_dir = dirname($destination);
- if (!wp_mkdir_p($dest_dir)) {
- throw new Exception("Failed to create destination directory: {$dest_dir}");
+ // Create image resource from source
+ $image = match($mime_type) {
+ 'image/jpeg' => imagecreatefromjpeg($source),
+ 'image/png' => imagecreatefrompng($source),
+ 'image/gif' => imagecreatefromgif($source),
+ 'image/webp' => imagecreatefromwebp($source),
+ default => throw new Exception('Unsupported source image type for GD conversion')
+ };
+
+ if (!$image) {
+ throw new Exception('Failed to create image resource');
}
- switch ($mime_type) {
- case 'image/webp':
- $image = imagecreatefromwebp($source);
- break;
- case 'image/jpeg':
- $image = imagecreatefromjpeg($source);
- break;
- case 'image/png':
- $image = imagecreatefrompng($source);
- if ($image !== false) {
- imagepalettetotruecolor($image);
- imagealphablending($image, true);
- imagesavealpha($image, true);
- }
- break;
- case 'image/gif':
- $image = imagecreatefromgif($source);
- if ($image !== false) {
- imagepalettetotruecolor($image);
- }
- break;
- default:
- throw new Exception('Unsupported image type for GD conversion: ' . $mime_type);
- }
+ // Convert to target format
+ $result = match($format) {
+ 'webp' => imagewebp($image, $destination, $quality),
+ 'jpeg', 'jpg' => imagejpeg($image, $destination, $quality),
+ 'png' => imagepng($image, $destination, $this->qualityToPngCompression($quality)),
+ 'gif' => imagegif($image, $destination),
+ default => throw new Exception("Unsupported target format for GD: {$format}")
+ };
- if ($image === false) {
- throw new Exception('Failed to create image resource from source file: ' . $source);
- }
-
-
- // Convert to WebP
- $result = imagewebp($image, $destination, $this->config['webp_quality']);
-
- // Clean up memory
imagedestroy($image);
if (!$result) {
- throw new Exception('WebP conversion with GD failed - imagewebp returned false');
- }
-
- // Verify the file was actually created
- if (!file_exists($destination)) {
- throw new Exception('WebP file was not created despite imagewebp returning true');
+ throw new Exception("GD conversion to {$format} failed");
}
}
/**
- * @return void
+ * Convert quality (1-100) to PNG compression level (0-9)
*/
- public function cleanupOriginalFiles(): void
+ protected function qualityToPngCompression(int $quality): int
{
- $cutoff = time() - $this->config['original_retention'];
-
- // Get upload directory and find original directories
- $pattern = "{$this->upload_dir}/**/originals";
- $original_dirs = glob($pattern, GLOB_ONLYDIR);
-
- foreach ($original_dirs as $original_dir) {
- $files = glob("{$original_dir}/*");
- foreach ($files as $file) {
- if (filemtime($file) < $cutoff) {
- unlink($file);
- }
- }
- }
+ // PNG compression is inverted: 0 = no compression, 9 = max compression
+ // Quality: 100 = best quality, 1 = worst quality
+ // So: quality 100 -> compression 0, quality 1 -> compression 9
+ return (int) round((100 - $quality) / 11.11);
}
/**
- * @param string|null $temp_dir
- * @return void
- */
- protected function cleanupTempFiles(string|null $temp_dir = null): void
- {
- if (is_null($temp_dir)) {
- $temp_dir = $this->upload_dir . '/tmp';
- }
-
- if (is_dir($temp_dir)) {
- $files = glob($temp_dir . '/*');
- foreach ($files as $file) {
- if (is_file($file)) {
- @unlink($file);
- }
- }
- @rmdir($temp_dir);
- }
- }
-
-
- /**
- * @param int $retention_days
- * @return void
- */
- public function cleanupUserFiles(int $retention_days = 30): void
- {
- $cutoff = time() - ($retention_days * DAY_IN_SECONDS);
- $user_dir = $this->getUploadDirectory();
-
- if (!is_dir($user_dir)) {
- return;
- }
-
- $this->cleanupDirectory($user_dir, $cutoff);
- }
-
- /**
- * @param string $dir
- * @param int $cutoff
- * @return void
- */
- protected function cleanupDirectory(string $dir, int $cutoff): void
- {
- $files = glob($dir . '/*');
- foreach ($files as $file) {
- if (is_dir($file)) {
- $this->cleanupDirectory($file, $cutoff);
- } elseif (filemtime($file) < $cutoff) {
- @unlink($file);
- }
- }
- }
-
- /**
- * @param string $file
- * @param string $title
- * @param int $post_id
- * @return int|WP_Error
- * @throws Exception
- */
- protected function createAttachment(string $file, string $title, int $post_id): int|WP_Error
- {
- $file_url = str_replace($this->upload_dir, $this->upload_url, $file);
-
- $attachment = [
- 'post_mime_type' => mime_content_type($file),
- 'post_title' => $title,
- 'post_name' => sanitize_title($title),
- 'post_content' => '',
- 'post_status' => 'inherit',
- 'guid' => $file_url
- ];
-
- $attach_id = wp_insert_attachment($attachment, $file, $post_id);
- if (is_wp_error($attach_id)) {
- throw new Exception($attach_id->get_error_message());
- }
-
- // Generate and set alt text
- $alt_text = $this->generateAltText($file, get_userdata($this->user_id), $post_id);
- update_post_meta($attach_id, '_wp_attachment_image_alt', $alt_text);
-
- return $attach_id;
- }
-
- /**
- * Generate thumbnails using WordPress's built-in image size system
- * This will create all registered image sizes (thumbnail, medium, large, and any custom sizes)
- * Sites can register custom image sizes using add_image_size()
- *
- * @param int $attachment_id
- * @return void
+ * Generate image thumbnails
*/
protected function generateThumbnails(int $attachment_id): void
{
@@ -642,119 +925,12 @@
}
/**
- * @param $result
- * @return void
+ * Extract video thumbnail (placeholder - requires ffmpeg)
*/
- protected function trackUploadStats($result)
+ protected function extractVideoThumbnail(int $attachment_id, string $video_path, int $time): void
{
- $stats_key = "upload_stats_{$this->user_id}";
- $stats = wp_cache_get($stats_key) ?: [
- 'total_uploads' => 0,
- 'successful_uploads' => 0,
- 'failed_uploads' => 0,
- 'total_size' => 0,
- 'last_upload' => null
- ];
-
- if ($result['success']) {
- $stats['successful_uploads']++;
- $stats['total_size'] += filesize($result['file']);
- } else {
- $stats['failed_uploads']++;
- }
-
- $stats['total_uploads']++;
- $stats['last_upload'] = current_time('mysql');
-
- wp_cache_set($stats_key, $stats, '', DAY_IN_SECONDS);
- }
-
- /**
- * @param int $attachment_id
- * @param string $new_file_path
- * @return void
- */
- protected function updatePostAttachments(int $attachment_id, string $new_file_path): void
- {
- // Update attachment post
- $file_url = str_replace($this->upload_dir, $this->upload_url, $new_file_path);
- $filename = basename($new_file_path);
-
- wp_update_post([
- 'ID' => $attachment_id,
- 'guid' => $file_url,
- 'post_mime_type' => mime_content_type($new_file_path),
- 'post_title' => $filename
- ]);
-
- // Update attachment metadata
- update_post_meta($attachment_id, '_wp_attached_file', str_replace($this->upload_dir . '/', '', $new_file_path));
-
- // Update attachment metadata including sizes
- $metadata = wp_get_attachment_metadata($attachment_id);
- if ($metadata) {
- $metadata['file'] = str_replace($this->upload_dir . '/', '', $new_file_path);
-
- // Update thumbnail paths if they exist
- if (!empty($metadata['sizes'])) {
- foreach ($metadata['sizes'] as $size => $info) {
- $old_file = $info['file'];
- $new_file = preg_replace(
- '/\.(jpe?g|png|gif)$/i',
- '.webp',
- $old_file
- );
- $metadata['sizes'][$size]['file'] = $new_file;
- $metadata['sizes'][$size]['mime-type'] = 'image/webp';
- }
- }
-
- wp_update_attachment_metadata($attachment_id, $metadata);
- }
-
- // If this is a profile/featured image, update those references
- $post_id = wp_get_post_parent_id($attachment_id);
- if ($post_id) {
- $featured_image_id = get_post_thumbnail_id($post_id);
- if ($featured_image_id === $attachment_id) {
- // Re-set the featured image to trigger any necessary updates
- set_post_thumbnail($post_id, $attachment_id);
- }
- }
-
- // Clear any caches
- clean_attachment_cache($attachment_id);
- clean_post_cache($post_id);
- }
-
- /**
- * Clean up empty temporary directories
- *
- * @param int $user_id
- * @return void
- */
- public function cleanupEmptyTempDirs(int $user_id): void
- {
- $temp_dir = "{$this->upload_dir}/jvb_temp_uploads/{$user_id}";
-
- if (is_dir($temp_dir)) {
- // Check if directory is empty
- $files = scandir($temp_dir);
- $is_empty = (count($files) <= 2); // Only . and .. entries
-
- if ($is_empty) {
- // Try to remove the empty directory
- @rmdir($temp_dir);
-
- // Also check if parent temp directory is empty
- $parent_temp_dir = "{$this->upload_dir}/jvb_temp_uploads";
- $parent_files = scandir($parent_temp_dir);
- $parent_is_empty = (count($parent_files) <= 2); // Only . and .. entries
-
- if ($parent_is_empty) {
- @rmdir($parent_temp_dir);
- }
- }
- }
+ // This would require ffmpeg or similar
+ // Placeholder for future implementation
+ apply_filters('jvb_extract_video_thumbnail', null, $attachment_id, $video_path, $time);
}
}
--
Gitblit v1.10.0