From 235ce5716edc2f7cbe80fdccf26eac7269587839 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Mon, 08 Jun 2026 04:38:18 +0000
Subject: [PATCH] =FavouritesManager.php and FavouritesRoutes.php fixes. Moving all logic to FavouritesManager.php. Still some left to do
---
inc/rest/routes/UploadRoutes.php | 2381 ++++++++++++++++++++++++++---------------------------------
1 files changed, 1,041 insertions(+), 1,340 deletions(-)
diff --git a/inc/rest/routes/UploadRoutes.php b/inc/rest/routes/UploadRoutes.php
index 3049d25..5222e4d 100644
--- a/inc/rest/routes/UploadRoutes.php
+++ b/inc/rest/routes/UploadRoutes.php
@@ -1,10 +1,15 @@
<?php
namespace JVBase\rest\routes;
-use JVBase\JVB;
-use JVBase\rest\RestRouteManager;
-use JVBase\meta\MetaManager;
+use JVBase\managers\queue\executors\UploadExecutor;
+use JVBase\managers\queue\mergers\UploadMerger;
+use JVBase\managers\queue\TypeConfig;
+use JVBase\registrar\Registrar;
+use JVBase\rest\PermissionHandler;
+use JVBase\rest\Rest;
+use JVBase\meta\Meta;
use JVBase\managers\UploadManager;
+use JVBase\rest\Route;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
@@ -13,134 +18,257 @@
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
-class UploadRoutes extends RestRouteManager
+class UploadRoutes extends Rest
{
public function __construct()
{
- $this->action = 'dash-';
parent::__construct();
- add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
+
+ add_action('init', [$this, 'registerUploadExecutors'], 5);
}
+ /**
+ * Register upload operation types with the queue's TypeRegistry
+ */
+ public function registerUploadExecutors(): void
+ {
+ $registry = JVB()->queue()->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
+ ));
+ }
+
/**
* Registers upload routes
* @return void
*/
public function registerRoutes():void
{
- register_rest_route($this->namespace, '/uploads', [
- 'methods' => 'POST',
- 'callback' => [$this, 'handleUploadRequest'],
- 'permission_callback' => [$this, 'checkPermission'],
- 'args' => [
- 'content' => [
- 'required' => true,
- 'type' => 'string'
- ],
- 'user' => [
- 'required' => true,
- 'type' => 'int'
- ],
- 'post_id' => [
- 'required' => false,
- 'type' => 'int'
- ],
- 'term_id' => [
- 'required' => false,
- 'type' => 'int'
- ],
- 'field_name' => [
- 'required' => false,
- 'type' => 'string'
- ],
- 'id' => [
- 'required' => false,
- 'type' => 'string'
- ],
- 'mode' => [
- 'required' => false,
- 'type' => 'string',
- 'default' => 'direct'
- ]
- ]
- ]);
+ Route::for('uploads')
+ ->post([$this, 'handleUpload'])
+ ->auth(PermissionHandler::combine(['nonce']))
+ ->rateLimit(30)
+ ->register();
- register_rest_route($this->namespace, '/uploads/groups', [
- 'methods' => 'POST',
- 'callback' => [$this, 'handleGroupingRequest'],
- 'permission_callback' => [$this, 'checkPermission'],
- 'args' => [
- 'id' => [
- 'required' => true,
- 'type' => 'string',
- 'description' => 'Original upload operation ID'
- ],
- 'content' => [
- 'required' => true,
- 'type' => 'string'
- ],
- 'user' => [
- 'required' => true,
- 'type' => 'integer'
- ],
- ]
- ]);
+ Route::for('uploads/groups')
+ ->post([$this, 'handleGroupingRequest'])
+ ->auth(PermissionHandler::combine(['nonce']))
+ ->rateLimit(30)
+ ->args([
+ 'id' => 'string|required',
+ 'content' => 'string|required',
+ 'user' => 'int|required'
+ ])
+ ->register();
- register_rest_route($this->namespace, '/uploads/meta', [
- 'methods' => 'POST',
- 'callback' => [$this, 'handleMetadataUpdate'],
- 'permission_callback' => [$this, 'checkPermission'],
- 'args' => [
- 'id' => [
- 'type' => 'string',
- 'required' => false,
- 'description' => 'Original upload operation ID (for updates during processing)'
- ],
- 'attachment_ids' => [
- 'type' => 'array',
- 'required' => false,
- 'description' => 'Direct attachment IDs (for updates after completion)',
- 'items' => ['type' => 'integer']
- ],
- 'upload_ids' => [
- 'type' => 'array',
- 'required' => false,
- 'description' => 'Frontend upload IDs to map to attachments',
- 'items' => ['type' => 'string']
- ],
- 'metadata' => [
- 'type' => 'object',
- 'required' => true,
- 'description' => 'Metadata changes to apply'
- ],
- 'user' => [
- 'type' => 'integer',
- 'required' => true
- ]
- ]
- ]);
+ Route::for('uploads/meta')
+ ->post([$this, 'handleMetadataUpdate'])
+ ->auth(PermissionHandler::combine(['nonce']))
+ ->rateLimit(30)
+ ->args([
+ 'user' => 'int|required',
+ 'items' => 'array|required',
+ 'id' => 'string'
+ ])
+ ->register();
}
+ /**
+ * Build the main $context for UploadManager from the $request params
+ * @param WP_REST_Request $request
+ * @return array
+ */
protected function buildUploadArgs(WP_REST_Request $request):array
{
$data = $request->get_params();
- error_log('Upload Args: '.print_r($data, true));
- $args = [
- 'content' => (array_key_exists('content', $data) && (array_key_exists(jvbGetValidType($data['content']), JVB_CONTENT) || $data['content'] === 'options')) ? jvbGetValidType($data['content']) : false,
- 'user' => (array_key_exists('user', $data) && $this->userCheck($data['user'])) ? (int)$data['user'] : false,
- 'id' => (array_key_exists('id', $data) && is_string($data['id'])) ? sanitize_text_field(str_replace('_upload', '', $data['id'])) : false,
- 'post_id' => (array_key_exists('post_id', $data) && is_numeric($data['post_id'])) ? absint($data['post_id']) : false,
- 'term_id' => (array_key_exists('term_id', $data) && is_numeric($data['term_id'])) ? absint($data['term_id']) : false,
- 'field_name' => (array_key_exists('field_name', $data) && is_string($data['field_name'])) ? sanitize_text_field($data['field_name']) : false,
- 'mode' => (array_key_exists('mode', $data) && in_array($data['mode'], ['direct', 'selection'])) ? sanitize_text_field($data['mode']) : false,
- ];
+ $args = [];
+ $registrar = Registrar::getInstance($data['content']??'');
- if ((!$args['post_id'] && !$args['term_id']) && contentIsJVBUserType($args['content'])) {
- $args['post_id'] = (int)get_user_meta($args['user'], BASE.'link', true);
- }
+ foreach ($data as $key => $value) {
+ switch ($key) {
+ case 'depends_on':
+ if (is_string($value) && !empty($value)) {
+ $args['depends_on'] = sanitize_text_field($value);
+ }
+ break;
+ case 'item_id':
+ if (is_numeric($value)) {
+ $args['item_id'] = absint($value);
+ if ($registrar) {
+ switch ($registrar->getType()) {
+ case 'post':
+ $args['post_id'] = absint($value);
+ break;
+ case 'term':
+ $args['term_id'] = absint($value);
+ break;
+ case 'user':
+ $args['user_id'] = absint($value);
+ break;
+ }
+ }
+ }
+ break;
+ // Post Type/Taxonomy
+ case 'content':
+ $value = str_replace('-', '_', $value);
+ if ($value === 'options' || $registrar) {
+ $args['content'] = $value;
+ }
+ break;
+ case 'destination':
+ if (in_array($value, ['meta', 'post', 'post_group'])) {
+ $args['destination'] = sanitize_text_field($value);
+ if (in_array($value, ['post', 'post_group']) && empty($data['content'])) {
+ throw new Exception("Content type required for destination: {$value}");
+ }
+ }
+ break;
+ // User ID
+ case 'user':
+ if ($this->userCheck($value)) {
+ $args['user'] = (int) $value;
+ if (!array_key_exists('post_id', $args) &&
+ !array_key_exists('post_id', $data) &&
+ !array_key_exists('term_id', $data) &&
+ !array_key_exists('item_id', $data)) {
+ $args['post_id'] = (int)get_user_meta((int) $value, BASE.'profile_link', true);
+ }
+ }
+ break;
+ // Operation ID
+ case 'id':
+ if (is_string($value)) {
+ $value = sanitize_text_field($value);
+ $args['id'] = $value;
+ $args['upload'] = $value.'_upload';
+ }
+ break;
+ // Post ID
+ case 'post_id':
+ if (is_numeric($value)) {
+ $args['post_id'] = absint($value);
+ }
+ break;
+ // Term ID
+ case 'term_id':
+ if (is_numeric($value)) {
+ $args['term_id'] = absint($value);
+ }
+ break;
+ // Field Name, as defined for Meta.php
+ case 'field_name':
+ if (is_string($value)) {
+ $args['field_name'] = sanitize_text_field($value);
+ }
+ break;
+ // Upload Mode
+ case 'mode':
+ if (in_array($value, ['direct', 'selection'])) {
+ $args['mode'] = sanitize_text_field($value);
+ }
+ break;
+ case 'upload_ids':
+ if (is_string($value)) {
+ // Parse JSON array
+ $decoded = json_decode($value, true);
+ if (is_array($decoded)) {
+ $args['upload_ids'] = $decoded;
+ }
+ } elseif (is_array($value)) {
+ // Already an array (shouldn't happen with FormData JSON, but handle it)
+ $args['upload_ids'] = $value;
+ }
+ break;
+ case 'metadata':
+ if (!empty($value)) {
+ $metadata = is_string($value) ? json_decode($value, true) : $value;
+ if (is_array($metadata)) {
+ foreach ($metadata as $k => $v) {
+ if (in_array($k, ['title', 'caption', 'alt', 'depends_on'])) {
+ $args['metadata'][$k] = sanitize_text_field($v);
+ }
+ }
+ }
+ }
+ break;
+ case 'posts':
+ if (is_string($value)) {
+ $decoded = json_decode($value, true);
+ if (is_array($decoded)) {
+ $args['posts'] = $decoded;
+ }
+ }
+ break;
- $args['upload'] = ($args['id']) ? $args['id'].'_upload' : false;
+ case 'group_titles':
+ if (is_string($value)) {
+ $decoded = json_decode($value, true);
+ if (is_array($decoded)) {
+ $args['group_titles'] = array_map('sanitize_text_field', $decoded);
+ }
+ }
+ break;
+ // Other field info
+ case 'field_key':
+ case 'field_type':
+ case 'subtype':
+ case 'item_id':
+ case 'context':
+ if (is_string($value)) {
+ $args[$key] = sanitize_text_field($value);
+ }
+ break;
+ }
+ }
return $args;
}
@@ -152,111 +280,32 @@
*
* @return WP_REST_Response
*/
- public function handleUploadRequest(WP_REST_Request $request): WP_REST_Response
+ public function handleUpload(WP_REST_Request $request): WP_REST_Response
{
try {
- if (!isset($_FILES['files'])) {
- return $this->createStandardResponse(
- false,
- [],
- __('No files uploaded.', 'jvb')
- );
- }
-
-
+ $files = $request->get_file_params();
$args = $this->buildUploadArgs($request);
- error_log('Validating upload args: '.print_r($args, true));
- // Validation...
- $validation_errors = [];
- if (!$args['content'] && !$args['term_id'] && !$args['post_id']) $validation_errors[] = 'content or term or post id required';
- if (!$args['user']) $validation_errors[] = 'user';
- if (!$args['id']) $validation_errors[] = 'operation_id';
- if (!empty($validation_errors)) {
- error_log('Validation Errors: '.print_r($validation_errors, true));
- return $this->createStandardResponse(
- false,
- ['missing_fields' => $validation_errors],
- 'Required fields missing: ' . implode(', ', $validation_errors)
- );
+
+
+ if (!$args['user']) {
+ return $this->unauthorized();
+ }
+ if (!$args['content']) {
+ return $this->validationError(['message' => 'Missing content']);
}
- $secured_files = $this->processFileUploads($_FILES['files'], $args);
+ // Step 1: Secure all uploaded files
+ $secured_files = $this->secureFiles($files, $args);
+
if (empty($secured_files)) {
- error_log('Images not processed');
- return $this->createStandardResponse(
- false,
- [],
- 'No valid files could be processed'
- );
+ $this->logError('No valid files to upload');
+ return $this->error('No valid files to upload');
}
+ // Step 2: Queue for processing via OperationQueue
+ $operation_id = $this->queueProcessing($secured_files, $args);
-
- $queue = JVB()->queue();
- error_log('Queuing Operation...');
- $queue->queueOperation(
- 'image_upload',
- $args['user'],
- [
- 'secured_files' => $secured_files,
- 'upload_map' => explode(',',$request->get_param('upload_map')),
- 'content' => $args['content'],
- 'post_id' => $args['post_id'],
- 'term_id' => $args['term_id'],
- 'field_name' => $args['field_name'],
- 'mode' => $args['mode'],
- 'operation_id' => $args['id']
- ],
- [
- 'operation_id' => $args['upload'],
- 'chunk_key' => 'secured_files',
- 'chunk_size' => 5,
- 'priority' => 'high',
- 'notification' => true,
- ]
- );
-
- error_log('Adding dependent operations...');
-
-
- if ($args['mode'] !== 'selection') {
- $queue->queueOperation(
- 'attach_upload_to_content',
- $args['user'],
- $args,
- [
- 'operation_id' => $args['id'],
- 'priority' => 'high',
- 'depends_on' => $args['upload']
- ]
- );
- }
-
- $queue->queueOperation(
- 'temporary_cleanup',
- $args['user'],
- [
- 'files' => $secured_files,
- 'content' => $args['content'],
- 'user' => $args['user']
- ],
- [
- 'priority' => 'low',
- 'chunk_size' => 5,
- 'chunk_key' => 'files',
- 'depends_on' => $args['upload'],
- ]
- );
-
- return $this->createStandardResponse(
- true,
- [
- 'files_count' => count($secured_files),
- 'operation_id' => $args['id'],
- 'upload_operation_id' => $args['upload']
- ],
- 'Files secured and queued for processing'
- );
+ return $this->queued($operation_id, 'Files secured and queued for processing');
} catch (Exception $e) {
// Error handling...
@@ -269,195 +318,482 @@
'trace' => $e->getTraceAsString()
]
);
-
- return $this->createStandardResponse(
- false,
- ['error_code' => 'upload_failed'],
- 'Upload processing failed: ' . $e->getMessage()
- );
+ return $this->error($e->getMessage());
}
}
- protected function processFileUploads(array $files, array $args): array
+ /**
+ * Secure uploaded files to temporary storage
+ */
+ protected function secureFiles(array $files, array $args): array
{
- $uploader = new UploadManager($args['content'], $args['user']);
+ $uploader = new UploadManager();
$secured_files = [];
+ $errors = [];
- // Handle different file array structures
-// if ($args['mode'] === 'selection') {
-// // Selection mode: files[group][index]
-// foreach ($files['tmp_name'] as $group => $images) {
-// if (is_array($images)) {
-// foreach ($images as $index => $tmp_name) {
-// if ($files['error'][$group][$index] === UPLOAD_ERR_OK) {
-// $file = [
-// 'name' => $files['name'][$group][$index],
-// 'type' => $files['type'][$group][$index],
-// 'tmp_name' => $tmp_name,
-// 'error' => $files['error'][$group][$index],
-// 'size' => $files['size'][$group][$index]
-// ];
-// $secured_files[$group][] = $uploader->secureUploadedFile($file);
-// }
-// }
-// }
-// }
-// } else {
- // Direct mode: files[index] or files[0][index]
- $tmp_names = isset($files['tmp_name'][0]) && is_array($files['tmp_name'][0])
- ? $files['tmp_name'][0]
- : $files['tmp_name'];
+ $context = $args;
+ unset($context['upload_ids']);
- foreach ($tmp_names as $index => $tmp_name) {
- $file_data = isset($files['tmp_name'][0]) && is_array($files['tmp_name'][0]) ? [
- 'name' => $files['name'][0][$index],
- 'type' => $files['type'][0][$index],
- 'tmp_name' => $tmp_name,
- 'error' => $files['error'][0][$index],
- 'size' => $files['size'][0][$index]
- ] : [
- 'name' => $files['name'][$index],
- 'type' => $files['type'][$index],
- 'tmp_name' => $tmp_name,
- 'error' => $files['error'][$index],
- 'size' => $files['size'][$index]
- ];
+ $file_array = $files['files'] ?? $files;
+ if (!is_array($file_array['tmp_name'])) {
+ $file_array = [
+ 'name' => [$file_array['name']],
+ 'type' => [$file_array['type']],
+ 'tmp_name' => [$file_array['tmp_name']],
+ 'error' => [$file_array['error']],
+ 'size' => [$file_array['size']]
+ ];
+ }
+ $tmp_names = $file_array['tmp_name'] ?? [];
- if ($file_data['error'] === UPLOAD_ERR_OK) {
- $secured_files[] = $uploader->secureUploadedFile($file_data);
- }
+
+
+ foreach ($tmp_names as $index => $tmp_name) {
+ $file_data = [
+ 'name' => $file_array['name'][$index],
+ 'type' => $file_array['type'][$index],
+ 'tmp_name' => $tmp_name,
+ 'error' => $file_array['error'][$index],
+ 'size' => $file_array['size'][$index]
+ ];
+
+ if ($file_data['error'] !== UPLOAD_ERR_OK) {
+ $errors[$index] = $this->getUploadErrorMessage($file_data['error']);
+ continue;
}
-// }
- return $secured_files;
+ try {
+ $secured = $uploader->secureUploadedFile($file_data, $context);
+
+ if (empty($secured)) {
+ throw new Exception('Failed to secure file');
+ }
+
+ // Embed upload_id directly in the secured file data
+ $secured['upload_id'] = $args['upload_ids'][$index] ?? 'upload_' . $index;
+
+ $secured_files[] = $secured;
+ } catch (Exception $e) {
+ $errors[$index] = $e->getMessage();
+ }
+ }
+
+ return [
+ 'files' => $secured_files,
+ 'errors' => $errors
+ ];
}
- /**
- * Process upload operation in the background
- */
- /**
- * @param WP_Error|array $result
- * @param object $operation
- * @param array $data
- *
- * @return array|WP_Error
- */
- public function processOperation(WP_Error|array $result, object $operation, array $data):WP_Error|array
- {
- switch ($operation->type) {
- case 'image_upload':
- return $this->processImageUpload($result, $operation, $data);
- case 'process_upload_groups':
- return $this->processUploadGroups($result, $operation, $data);
- case 'update_metadata':
- return $this->processMetadataUpdate($result, $operation, $data);
- case 'temporary_cleanup':
- return $this->processTemporaryCleanup($result, $operation, $data);
- case 'attach_upload_to_content':
- return $this->attachUploadToContent($result, $operation, $data);
- default:
- return $result;
- }
- }
+ protected function getUploadErrorMessage(int $error_code): string
+ {
+ return match($error_code) {
+ UPLOAD_ERR_INI_SIZE => 'File exceeds maximum upload size',
+ UPLOAD_ERR_FORM_SIZE => 'File exceeds form maximum size',
+ UPLOAD_ERR_PARTIAL => 'File was only partially uploaded',
+ UPLOAD_ERR_NO_FILE => 'No file was uploaded',
+ UPLOAD_ERR_NO_TMP_DIR => 'Missing temporary folder',
+ UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk',
+ UPLOAD_ERR_EXTENSION => 'Upload stopped by extension',
+ default => 'Unknown upload error'
+ };
+ }
+
/**
- * Process attachment metadata updates
+ * Queue files for processing
*/
- protected function processAttachmentMetadata(WP_Error|array $result, object $operation, array $data): WP_Error|array
+ protected function queueProcessing(array $secured_data, array $args): string
+ {
+ $operation_type = $this->determineOperationType($secured_data['files'][0] ?? []);
+
+ $chunkSize = 5;
+ if ($operation_type === 'video') {
+ $chunkSize = 1;
+ } elseif ($operation_type === 'document') {
+ $chunkSize = 10;
+ }
+
+ error_log('Queueing Operation: '.print_r($operation_type, true));
+ error_log('With ID: '.print_r($args['upload'], true));
+ $queuedProcessing = JVB()->queue()->queueOperation(
+ $operation_type,
+ $args['user'],
+ array_merge(
+ ['secured_files' => $secured_data['files']],
+ $args
+ ),
+ [
+ 'operation_id' => $args['upload'],
+ 'chunk_key' => 'secured_files',
+ 'chunk_size' => $chunkSize
+ ]
+ );
+
+ error_log('queuedProcessing operation: '.print_r($queuedProcessing, true));
+
+ $uploadOpId = $queuedProcessing['operation_id'];
+
+ if ($args['mode'] !== 'selection') {
+
+ // Only create attach_upload_to_content if the upload was NOT merged.
+ // When merged, the original upload's attach_upload_to_content
+ // will handle all files after the merged image_upload completes.
+ if (!$queuedProcessing['updated_existing']) {
+ JVB()->queue()->queueOperation(
+ 'attach_upload_to_content',
+ $args['user'],
+ $args,
+ [
+ 'priority' => 'high',
+ 'operation_id' => $args['id'],
+ 'depends_on' => [$uploadOpId]
+ ]
+ );
+ }
+ }
+
+ JVB()->queue()->queueOperation(
+ 'temporary_cleanup',
+ $args['user'],
+ [
+ 'files' => $secured_data['files'],
+ 'context' => $args,
+ ],
+ [
+ 'priority' => 'low',
+ 'chunk_size' => 5,
+ 'chunk_key' => 'files',
+ 'depends_on' => [$uploadOpId]
+ ]
+ );
+
+ return $args['id'];
+ }
+
+ /**
+ * Determine operation type from file data
+ */
+ protected function determineOperationType(array $file): string
+ {
+ $file_type = $file['file_type'] ?? 'image';
+
+ return match($file_type) {
+ 'video' => 'video_upload',
+ 'document' => 'document_upload',
+ default => 'image_upload'
+ };
+ }
+
+ /**
+ * Step 3: Process operation from queue
+ * Called by OperationQueue
+ * @param WP_Error|array $result
+ * @param object $operation
+ * @param array $data
+ *
+ * @return array|WP_Error
+ */
+ public function processOperation(array $result, object $operation, array $data): array
+ {
+ // Only handle our operation types
+ $handled_types = [
+ 'image_upload',
+ 'video_upload',
+ 'document_upload',
+ 'update_metadata',
+ 'temporary_cleanup',
+ 'attach_upload_to_content',
+ 'process_upload_groups'
+ ];
+
+ if (!in_array($operation->type, $handled_types)) {
+ return $result; // Not our operation, pass through
+ }
+
+ try {
+ // Route to appropriate handler
+ $handler_result = match($operation->type) {
+ 'image_upload' => $this->processImageUpload($operation, $data),
+ 'video_upload' => $this->processVideoUpload($operation, $data),
+ 'document_upload' => $this->processDocumentUpload($operation, $data),
+ 'update_metadata' => $this->processUploadMeta($result, $operation, $data),
+ 'temporary_cleanup' => $this->processTemporaryCleanup($result, $operation, $data),
+ 'attach_upload_to_content' => $this->processAttachToContent($operation, $data),
+ 'process_upload_groups' => $this->processUploadGroups($result, $operation, $data),
+ default => new WP_Error('unknown_type', 'Unknown operation type')
+ };
+
+ // Handle WP_Error
+ if (is_wp_error($handler_result)) {
+ return [
+ 'success' => false,
+ 'result' => $handler_result->get_error_message()
+ ];
+ }
+
+ return $handler_result;
+
+ } catch (Exception $e) {
+ JVB()->error()->log(
+ '[UploadRoutes]:processOperation',
+ $e->getMessage(),
+ [
+ 'operation_id' => $operation->id,
+ 'operation_type' => $operation->type
+ ]
+ );
+
+ return [
+ 'success' => false,
+ 'result' => $e->getMessage()
+ ];
+ }
+ }
+ /**
+ * Standardize processing result
+ */
+ protected function standardizeResult(array $result): array
+ {
+ return [
+ 'attachment_id' => $result['attachment_id'],
+ 'url' => $result['url'],
+ 'file' => $result['file'],
+ 'upload_id' => $result['upload_id'] ?? null
+ ];
+ }
+
+ protected function processAttachToContent(object $operation, array $data): array
{
try {
- $metadata_updates = $data['metadata_updates'];
- $updated_count = 0;
- $failed_count = 0;
- $results = [];
+ // Get the results from the upload operation
+ $upload_results = JVB()->queue()->getOperationValue($data['upload'], 'result', true);
- foreach ($metadata_updates as $update) {
- try {
- $attachment_id = (int)$update['attachment_id'];
- $upload_id = $update['upload_id'];
+ if (empty($upload_results)) {
+ throw new Exception('No upload results found for operation: ' . $data['upload']);
+ }
- // Verify attachment exists and user has permission
- if (!$this->verifyAttachmentAccess($attachment_id, $operation->user_id)) {
- $failed_count++;
- $results[] = [
- 'upload_id' => $upload_id,
- 'attachment_id' => $attachment_id,
- 'success' => false,
- 'error' => 'Attachment not found or no permission'
- ];
- continue;
+ if (empty($data['post_id']) || str_starts_with((string)($data['item_id'] ?? ''), 'new')) {
+ foreach ($operation->dependencies as $depId) {
+ $dep = JVB()->queue()->get($depId);
+ if ($dep && $dep->type === 'content_update' && !empty($dep->result['new_posts'])) {
+ $itemId = $data['item_id'] ?? null;
+ if ($itemId && isset($dep->result['new_posts'][$itemId])) {
+ $data['post_id'] = $dep->result['new_posts'][$itemId];
+ break;
+ }
}
-
- // Apply metadata updates
- $updated_fields = [];
-
- // Update alt text
- if (isset($update['alt'])) {
- $alt_text = sanitize_text_field($update['alt']);
- update_post_meta($attachment_id, '_wp_attachment_image_alt', $alt_text);
- $updated_fields[] = 'alt';
- }
-
- // Update title
- if (isset($update['title'])) {
- $title = sanitize_text_field($update['title']);
- wp_update_post([
- 'ID' => $attachment_id,
- 'post_title' => $title
- ]);
- $updated_fields[] = 'title';
- }
-
- // Update caption
- if (isset($update['caption'])) {
- $caption = sanitize_textarea_field($update['caption']);
- wp_update_post([
- 'ID' => $attachment_id,
- 'post_excerpt' => $caption
- ]);
- $updated_fields[] = 'caption';
- }
-
- // Update description
- if (isset($update['description'])) {
- $description = sanitize_textarea_field($update['description']);
- wp_update_post([
- 'ID' => $attachment_id,
- 'post_content' => $description
- ]);
- $updated_fields[] = 'description';
- }
-
- $updated_count++;
- $results[] = [
- 'upload_id' => $upload_id,
- 'attachment_id' => $attachment_id,
- 'success' => true,
- 'updated_fields' => $updated_fields
- ];
-
- } catch (Exception $e) {
- $failed_count++;
- $results[] = [
- 'upload_id' => $update['upload_id'] ?? 'unknown',
- 'attachment_id' => $update['attachment_id'] ?? 0,
- 'success' => false,
- 'error' => $e->getMessage()
- ];
}
+
+ if (empty($data['post_id'])) {
+ throw new Exception('Could not resolve post_id from dependencies');
+ }
+ }
+
+ // Now attach to the specified content
+ if (!empty($data['field_name'])) {
+ $this->updateFieldValue($data, $upload_results);
}
return [
'success' => true,
- 'data' => $results,
- 'updated_count' => $updated_count,
- 'failed_count' => $failed_count,
- 'message' => "Metadata updated: {$updated_count} succeeded, {$failed_count} failed"
+ 'result' => 'Attachments linked to content'
+ ];
+
+ } catch (Exception $e) {
+ return [
+ 'success' => false,
+ 'result' => $e->getMessage()
+ ];
+ }
+ }
+
+ /**
+ * Cleanup temporary files after processing
+ */
+ protected function cleanupTempFiles(array $secured_files, int $user_id): void
+ {
+ $uploader = new UploadManager();
+
+ foreach ($secured_files as $secured) {
+ if (!empty($secured['temp_path']) && file_exists($secured['temp_path'])) {
+ @unlink($secured['temp_path']);
+ }
+ }
+
+ // Clean up empty directories
+ $uploader->cleanupEmptyTempDirs($user_id);
+ }
+
+ /**
+ * Update field value with new attachment IDs
+ */
+ protected function updateFieldValue(array $data, array $results): void
+ {
+ if ((!array_key_exists('post_id', $data) && !array_key_exists('term_id', $data) && !array_key_exists('user', $data)) && !array_key_exists('field_name', $data)) {
+ return;
+ }
+
+ $attachment_ids = array_column($results, 'attachment_id');
+ if (array_key_exists('post_id', $data)) {
+ $meta = Meta::forPost($data['post_id']);
+ } elseif (array_key_exists('term_id', $data)) {
+ $meta = Meta::forTerm($data['term_id']);
+ } else {
+ $link = (int)get_user_meta($data['user'], BASE.'profile_link');
+ $meta = Meta::forPost($link);
+ }
+
+ // Get existing value
+ $existing = $meta->get($data['field_name']);
+ $existing_ids = !empty($existing) ? explode(',', $existing) : [];
+
+ // Merge with new IDs
+ $all_ids = array_unique(array_merge($existing_ids, $attachment_ids));
+
+ // Update with comma-separated string
+ $meta->set($data['field_name'], implode(',', $all_ids));
+ }
+
+ /**
+ * Generic file upload processor - works for images, videos, documents
+ */
+ protected function processFileUpload(object $operation, array $data, string $file_type): WP_Error|array
+ {
+ try {
+ $uploader = new UploadManager();
+ $processed_results = [];
+ $config = $this->getFieldConfig($data);
+
+ $args = $data;
+ unset($args['secured_files']);
+
+ foreach ($data['secured_files'] as $secured_file) {
+ $result = $uploader->processUpload(
+ $secured_file['temp_path'],
+ array_merge(
+ $config,
+ [
+ 'file_type' => $file_type,
+ 'user_id' => $operation->user_id,
+ 'post_id' => (int)($data['post_id'] ?? 0),
+ 'term_id' => (int)($data['term_id'] ?? 0),
+ 'original_name' => $secured_file['original_name'],
+ 'mime_type' => $secured_file['mime_type'],
+ 'content' => $data['content'] ?? '',
+ ]
+ )
+ );
+
+ if (!is_wp_error($result)) {
+ $standardized = $this->standardizeResult($result);
+
+ // Use embedded upload_id from the secured file itself
+ $standardized['upload_id'] = $secured_file['upload_id'] ?? null;
+
+ if ($standardized['upload_id']) {
+ $processed_results[$standardized['upload_id']] = $standardized;
+ } else {
+ $processed_results[] = $standardized;
+ }
+
+ // Apply frontend metadata if provided
+ if (!empty($secured_file['metadata'])) {
+ $this->applyMeta($standardized['attachment_id'], $secured_file['metadata']);
+ }
+ }
+ }
+
+ $this->handleUploadDestination($data, $processed_results);
+
+ // Cleanup temporary files
+ $this->cleanupTempFiles($data['secured_files'], $operation->user_id);
+
+ return [
+ 'success' => true,
+ 'result' => $processed_results
];
} catch (Exception $e) {
JVB()->error()->log(
- '[UploadRoutes]:processAttachmentMetadata',
+ '[UploadRoutes]:processFileUpload',
+ $e->getMessage(),
+ [
+ 'operation_id' => $operation->id,
+ 'file_type' => $file_type,
+ 'user_id' => $operation->user_id
+ ]
+ );
+ return [
+ 'success' => false,
+ 'result' => $e->getMessage()
+ ];
+ }
+ }
+
+ /**
+ * Process image uploads
+ */
+ protected function processImageUpload(object $operation, array $data): WP_Error|array
+ {
+ return $this->processFileUpload($operation, $data, 'image');
+ }
+
+ /**
+ * Process video uploads
+ */
+ protected function processVideoUpload(object $operation, array $data): WP_Error|array
+ {
+ return $this->processFileUpload($operation, $data, 'video');
+ }
+
+ /**
+ * Process document uploads
+ */
+ protected function processDocumentUpload(object $operation, array $data): WP_Error|array
+ {
+ return $this->processFileUpload($operation, $data, 'document');
+ }
+
+ /**
+ * Process temporary cleanup operation
+ * Called by OperationQueue for 'temporary_cleanup' operations
+ *
+ * @param array $result
+ * @param object $operation
+ * @param array $data
+ * @return array
+ */
+ protected function processTemporaryCleanup(array $result, object $operation, array $data): array
+ {
+ try {
+ // Cleanup temporary files if they exist
+ if (!empty($data['secured_files'])) {
+ $this->cleanupTempFiles($data['secured_files'], $operation->user_id);
+ }
+
+ // If specific temp paths provided
+ if (!empty($data['temp_paths']) && is_array($data['temp_paths'])) {
+ foreach ($data['temp_paths'] as $temp_path) {
+ if (file_exists($temp_path)) {
+ @unlink($temp_path);
+ }
+ }
+ }
+
+ // Cleanup empty temp directories for this user
+ if (!empty($operation->user_id)) {
+ $uploader = new UploadManager();
+ $uploader->cleanupEmptyTempDirs($operation->user_id);
+ }
+
+ return [
+ 'success' => true,
+ 'result' => 'Temporary files cleaned up successfully'
+ ];
+
+ } catch (Exception $e) {
+ JVB()->error()->log(
+ '[UploadRoutes]:processTemporaryCleanup',
$e->getMessage(),
[
'operation_id' => $operation->id,
@@ -465,17 +801,263 @@
]
);
- return new WP_Error(
- 'metadata_processing_failed',
+ return [
+ 'success' => false,
+ 'result' => $e->getMessage()
+ ];
+ }
+ }
+
+ /**
+ * Handle metadata update requests
+ */
+ public function handleMetadataUpdate(WP_REST_Request $request): WP_REST_Response
+ {
+ try {
+ $data = $request->get_params();
+
+ error_log('Received data for meta change: '.print_r($data, true));
+
+
+ $items = $data['items']??false;
+ if (!$items) {
+ return $this->sendResponse(
+ true,
+ [
+ ],
+ 'No items to update'
+ );
+ }
+ $pending = [];
+ $attachments = array_filter($items, function ($item) {
+ return array_key_exists('attachmentId', $item) || array_key_exists('uploadId', $item);
+ });
+
+
+ if (!empty($attachments)) {
+ error_log('Attachments: '.print_r($attachments, true));
+ return $this->queueMetaUpdate($attachments, absint($data['user']), sanitize_text_field($data['id']??''));
+ }
+
+
+ return $this->sendResponse(
+ false,
+ ['error_code' => 'missing_identifiers'],
+ 'Must provide either attachment_ids or operation_id'
+ );
+
+ } catch (Exception $e) {
+ JVB()->error()->log(
+ '[UploadRoutes]:handleMetadataUpdate',
+ $e->getMessage(),
+ [
+ 'request_data' => $request->get_params(),
+ 'trace' => $e->getTraceAsString()
+ ]
+ );
+
+ return $this->sendResponse(
+ false,
+ ['error_code' => 'metadata_update_failed'],
+ 'Metadata update failed: ' . $e->getMessage()
+ );
+ }
+ }
+ /**
+ * Update metadata directly on completed attachments
+ */
+ protected function updateMeta(array $data, int $user): WP_REST_Response
+ {
+ $updated_count = 0;
+ $errors = [];
+ $ids = [];
+ foreach ($data as $info) {
+ try {
+ $attachment_id = $info['attachmentId'];
+ error_log('Updating attachment ID:'.print_r($attachment_id,true));
+ $ids[] = $attachment_id;
+ unset($info['attachmentId']);
+ // Verify attachment exists and user has permission
+ if (!$this->verifyAttachmentAccess($attachment_id, $user)) {
+ $errors[] = "No permission to edit attachment {$attachment_id}";
+ continue;
+ }
+
+ $this->applyMeta($attachment_id, $info);
+ $updated_count++;
+
+ } catch (Exception $e) {
+ $errors[] = "Failed to update attachment {$attachment_id}: " . $e->getMessage();
+ }
+ }
+
+ return $this->sendResponse(
+ $updated_count > 0,
+ [
+ 'updated_count' => $updated_count,
+ 'errors' => $errors,
+ 'attachment_ids' => $ids
+ ],
+ $updated_count > 0 ?
+ "Updated metadata for {$updated_count} attachment(s)" :
+ 'No attachments were updated'
+ );
+ }
+ /**
+ * Queue metadata update with dependency on upload operation
+ */
+ protected function queueMetaUpdate(array $data, int $user, ?string $operationId = null): WP_REST_Response
+ {
+ $queue = JVB()->queue();
+ $depends_on = [];
+ $errors = [];
+ $original = count($data);
+ foreach ($data as $uploadID => $info) {
+ if (array_key_exists('depends_on', $info) && !in_array($info['depends_on'], $depends_on)) {
+ $depends_on[] = $info['depends_on'];
+ }
+ }
+ $queueData = [
+ 'depends_on' => $depends_on,
+ ];
+ if ($operationId) {
+ $queueData['operation_id'] = $operationId;
+ }
+ $operationID = $queue->queueOperation(
+ 'update_image_meta',
+ $user,
+ $data,
+ $queueData
+ );
+
+ return $this->sendResponse(
+ true,
+ [
+ 'operation_id' => $operationID['operation_id']??$operationId,
+ 'message' => "Successfully queued ".count($data)." of {$original} meta updates"
+ ],
+ 'Metadata update queued - will apply after upload completes'
+ );
+ }
+
+ /**
+ * Process metadata update operation (called by queue processor)
+ */
+ public function processUploadMeta(WP_Error|array $result, object $operation, array $data): WP_Error|array
+ {
+ try {
+ if (!is_array($operation->depends_on)) {
+ $operation->depends_on = [$operation->depends_on];
+ }
+ $updated_count = 0;
+ $errors = [];
+ foreach ($operation->depends_on as $dependency) {
+ $operationData = JVB()->queue()->getOperation($dependency);
+ if (!$operationData || $operationData->state !== 'completed') {
+ throw new Exception('Original upload operation not found or not completed');
+ }
+
+ $uploadResults = json_decode($operationData->result, true);
+ if (!$uploadResults || !$uploadResults['success']) {
+ throw new Exception('Original upload operation failed');
+ }
+ $attachmentsToUpdate = array_filter($data, function ($item) use ($dependency) {
+ return $item['depends_on'] === $dependency;
+ });
+
+ $uploadIDToAttachment = $this->buildUploadToAttachmentMapping(
+ array_keys($attachmentsToUpdate),
+ $uploadResults['data']
+ );
+
+ if (empty($uploadIDToAttachment)) {
+ throw new Exception('No valid upload ID to attachment ID mappings found');
+ }
+
+ foreach ($uploadIDToAttachment as $uploadId => $attachmentId) {
+ try {
+ $this->applyMeta($attachmentId, $attachmentsToUpdate[$uploadId]);
+ $updated_count++;
+ } catch (Exception $e) {
+ $errors[] = "Upload {$uploadId} (Attachment {$attachmentId}): " . $e->getMessage();
+ }
+ }
+ }
+
+ return [
+ 'success' => $updated_count > 0,
+ 'result' => [
+ 'updated_count' => $updated_count,
+ 'mappings' => $uploadIDToAttachment,
+ 'errors' => $errors,
+ 'message' => "Applied metadata to {$updated_count} attachment(s)"
+ ]
+ ];
+
+ } catch (Exception $e) {
+ JVB()->error()->log(
+ '[UploadRoutes]:processUploadMeta',
$e->getMessage(),
[
'operation_id' => $operation->id,
- 'error_code' => 'metadata_update_error'
+ 'original_operation_id' => $data['original_operation_id'] ?? 'unknown'
]
);
+
+ return [
+ 'success' => false,
+ 'result' => $e->getMessage()
+ ];
}
}
+ protected function applyMeta(int $attachment_id, array $metadata): void
+ {
+ // Update alt text
+ if (!empty($metadata['image-alt-text'])) {
+ update_post_meta($attachment_id, '_wp_attachment_image_alt', sanitize_text_field($metadata['image-alt-text']));
+ }
+ $postUpdates = [];
+ // Update title
+ if (!empty($metadata['image-title'])) {
+ $postUpdates['post_title'] = $metadata['image-title'];
+ }
+
+ // Update caption
+ if (!empty($metadata['image-caption'])) {
+ $postUpdates['post_excerpt'] = sanitize_textarea_field($metadata['image-caption']);
+ }
+
+ if (!empty($postUpdates)) {
+ $postUpdates['ID'] = $attachment_id;
+ wp_update_post($postUpdates);
+ }
+ }
+
+ /**
+ * Build mapping from frontend upload IDs to WordPress attachment IDs
+ */
+ protected function buildUploadToAttachmentMapping(array $upload_ids, array $results): array
+ {
+ $mapping = [];
+
+ foreach ($results as $result) {
+ if (!isset($result['upload_id']) || !isset($result['attachment_id'])) {
+ continue;
+ }
+
+ $upload_id = $result['upload_id'];
+ $attachment_id = $result['attachment_id'];
+
+ // Validate that this upload_id was requested
+ if (in_array($upload_id, $upload_ids)) {
+ $mapping[$upload_id] = $attachment_id;
+ }
+ }
+
+ return $mapping;
+ }
+
/**
* Verify user has access to attachment
*/
@@ -491,177 +1073,58 @@
return ($attachment->post_author == $user_id) || current_user_can('manage_options');
}
- protected function processImageUpload(WP_Error|array $result, object $operation, array $data): WP_Error|array
- {
- try {
- $uploader = new UploadManager($data['content'], $operation->user_id);
- $processed_results = [];
-
- // Extract frontend metadata if available
- $frontend_metadata = $this->extractFrontendMetadata($data);
-
-
- foreach ($data['secured_files'] as $index => $secured_file) {
- $result = $uploader->processImageFromStorage(
- $secured_file['temp_path'],
- $data['content'],
- (int)($data['post_id'] ?? 0),
- (int)($data['term_id'] ?? 0),
- $secured_file['original_name'] ?? ''
- );
-
- if (!is_wp_error($result)) {
- $standardized = $this->standardizeImageResult($result);
-
- // FIX: Proper mapping using consistent naming
- if (isset($data['upload_map'][$index])) {
- $standardized['upload_id'] = $data['upload_map'][$index];
- } else {
- error_log("Warning: No upload_map found for index {$index}");
- $standardized['upload_id'] = 'unknown_' . $index;
- }
-
- $file_metadata = $frontend_metadata[$index] ?? null;
- if ($file_metadata) {
- $this->applyFrontendMetadata($standardized['attachment_id'], $file_metadata);
- }
-
- $processed_results[] = $standardized;
- }
- }
-
- // Update field metadata if specified
- if ($data['field_name']) {
- $this->updateFieldMetadata($data, $processed_results);
- }
-
- return [
- 'success' => true,
- 'result' => $processed_results
- ];
-
- } catch (Exception $e) {
- JVB()->error()->log(
- '[UploadRoutes]:processImageUpload',
- $e->getMessage(),
- [
- 'operation_id' => $operation->id,
- 'user_id' => $operation->user_id,
- 'content' => $data['content'],
- 'upload_map' => $data['upload_map'] ?? 'missing'
- ]
- );
-
- return [
- 'success' => false,
- 'result' => $e->getMessage()
- ];
- }
- }
- protected function extractFrontendMetadata(array $data): array
- {
- $metadata = [];
-
- // Check if we have metadata in the request
- if (isset($_POST['metadata'])) {
- foreach ($_POST['metadata'] as $index => $meta_json) {
- $decoded = json_decode($meta_json, true);
- if ($decoded) {
- $metadata[$index] = $decoded;
- }
- }
- }
-
- return $metadata;
- }
-
- protected function applyFrontendMetadata(int $attachment_id, array $metadata): void
- {
- // Update alt text
- if (!empty($metadata['alt'])) {
- update_post_meta($attachment_id, '_wp_attachment_image_alt', sanitize_text_field($metadata['alt']));
- }
-
- // Update title
- if (!empty($metadata['title'])) {
- wp_update_post([
- 'ID' => $attachment_id,
- 'post_title' => sanitize_text_field($metadata['title'])
- ]);
- }
-
- // Update caption
- if (!empty($metadata['caption'])) {
- wp_update_post([
- 'ID' => $attachment_id,
- 'post_excerpt' => sanitize_textarea_field($metadata['caption'])
- ]);
- }
- }
- protected function countProcessedFiles(array $processed_results): int
- {
- $count = 0;
- foreach ($processed_results as $result) {
- if (is_array($result) && isset($result[0])) {
- // Grouped results
- $count += count($result);
- } else {
- // Single result
- $count++;
- }
- }
- return $count;
- }
- protected function updateFieldMetadata(array $data, array $processed_results): void
- {
- if (!$data['field_name']) return;
-
- $type = ($data['post_id']) ? 'post' : (($data['term_id']) ? 'term' : false);
- if (!$type) return;
-
- $ID = ($type == 'post') ? $data['post_id'] : $data['term_id'];
- $meta = new MetaManager($ID, $type);
-
- // Get attachment IDs from processed results
- $attachment_ids = [];
- if ($data['mode'] == 'selection') {
- // Selection mode: grouped results
- foreach ($processed_results as $group => $files) {
- foreach ($files as $file_result) {
- $attachment_ids[] = $file_result['attachment_id'];
- }
- }
- } else {
- // Direct mode: flat array
- foreach ($processed_results as $file_result) {
- $attachment_ids[] = $file_result['attachment_id'];
- }
- }
-
- if (!empty($attachment_ids)) {
- $value = implode(',', array_map('absint', $attachment_ids));
- $meta->updateValue($data['field_name'], $value);
- }
- }
-
/**
- * Standardize image processing results for frontend
+ * Get field configuration for upload processing
*/
- protected function standardizeImageResult(array $result): array
+ protected function getFieldConfig(array $data): array
{
- return [
- 'attachment_id' => $result['attachment_id'],
- 'url' => $result['url'],
- 'file_path' => $result['file'] ?? '',
- 'success' => $result['success'] ?? true,
- 'metadata' => [
- 'alt_text' => get_post_meta($result['attachment_id'], '_wp_attachment_image_alt', true),
- 'title' => get_the_title($result['attachment_id']),
- 'mime_type' => get_post_mime_type($result['attachment_id'])
- ]
+ static $config_cache = [];
+
+ $cache_key = md5(json_encode([
+ $args['content'] ?? '',
+ $args['field_name'] ?? ''
+ ]));
+
+ if (isset($config_cache[$cache_key])) {
+ return $config_cache[$cache_key];
+ }
+
+ $config = [
+ 'allowed_types' => null,
+ 'max_size' => null,
+ 'convert' => 'webp',
+ 'quality' => 80,
+ 'create_thumbnails' => true,
];
+
+ // Get field definition from registry if available
+ if (!empty($args['content']) && !empty($args['field_name'])) {
+ $content_type = $args['content'];
+ $field_name = $args['field_name'];
+ $registrar = Registrar::getInstance($content_type);
+ if ($registrar) {
+ $content_fields = $registrar->getFields();
+ if (array_key_exists($field_name, $content_fields)) {
+ $field_def = $content_fields[$field_name];
+
+ // Extract relevant config from field definition
+ $config = array_merge($config, [
+ 'allowed_types' => $field_def['accepted_types'] ?? null,
+ 'max_size' => $field_def['max_size'] ?? null,
+ 'convert' => $field_def['convert'] ?? 'webp',
+ 'quality' => $field_def['quality'] ?? 80,
+ 'create_thumbnails' => $field_def['create_thumbnails'] ?? true,
+ ]);
+ }
+ }
+ }
+
+ $config_cache[$cache_key] = $config;
+ return $config;
}
+
+
/**
* Get files info for error logging
*/
@@ -674,164 +1137,7 @@
];
}
- /**
- * @param WP_Error $result
- * @param object $operation
- * @param array $data
- *
- * @return array|WP_Error
- */
- public function processTemporaryCleanup(WP_Error|array $result, object $operation, array $data):WP_Error|array
- {
- error_log('Processing Temporary Cleanup...');
- $JVB = JVB();
- $logger = $JVB->error();
-
- $uploader = new UploadManager($data['content'], $data['user']);
-
- // Check if we have files to clean up
- if (empty($data['files']) || !is_array($data['files'])) {
- return ['success' => true, 'result' => 'No files to clean up'];
- }
-
- $success_count = 0;
- $error_count = 0;
- $skipped_count = 0;
-
- foreach ($data['files'] as $file_data) {
- // Skip if we don't have a temp path
- if (empty($file_data['temp_path'])) {
- $skipped_count++;
- continue;
- }
-
- $temp_path = $file_data['temp_path'];
-
- try {
- // Only remove if the file exists
- if (file_exists($temp_path)) {
- if (@unlink($temp_path)) {
- $success_count++;
- } else {
- $error_count++;
- $logger->log(
- 'temp_cleanup',
- 'Failed to delete temporary file',
- ['file' => $temp_path],
- 'warning'
- );
- }
- } else {
- // File already gone, count as success
- $skipped_count++;
- }
- } catch (Exception $e) {
- $error_count++;
- $logger->log(
- '[UploadManager]:processTempCleanupOperation',
- 'Error during temp file cleanup: ' . $e->getMessage(),
- ['file' => $temp_path],
- 'warning'
- );
- }
- }
-
- // After cleaning individual files, check if directory is empty and can be removed
- $uploader->cleanupEmptyTempDirs($operation->user_id);
-
- return [
- 'success' => true,
- 'result' => [
- 'removed' => $success_count,
- 'errors' => $error_count,
- 'skipped' => $skipped_count,
- 'message' => "Temporary files cleanup completed: {$success_count} removed, {$error_count} errors, {$skipped_count} skipped"
- ]
- ];
- }
-
- protected function attachUploadToContent($result, $operation, $args):array|false
- {
- error_log('STARTING ATTACHING UPLOAD');
- error_log('Upload ID: '.print_r($args['upload'], true));
- $uploadOperation = JVB()->queue()->getOperation($args['upload'], true);
- error_log('Upload Operation from Attach Image to Content: '.print_r($uploadOperation, true));
- error_log('Args: '.print_r($args, true));
-
-
- $field = ($args['field_name']??false) ? $args['field_name'] : 'post_thumbnail';
- $ID = $args['post_id']??$args['term_id'];
- $return = [
- 'success' => false,
- 'results' => []
- ];
- $images = array_map('absint', array_column(json_decode($uploadOperation->result, true),'attachment_id'));
- error_log('Images: '.print_r($images, true));
- error_log('Parsed ID: '.print_r($ID, true));
- if (array_key_exists('content', $args) && $args['content'] === 'options') {
- $meta = new MetaManager(null, 'options');
- if (str_contains($args['field_name'], ':')) {
- $result = $meta->updateRepeaterRowField($args['field_name'], (count($images) === 1) ? $images[0] : $images);
- } else {
- $result = $meta->updateValue($args['field_name'], (count($images) === 1) ? $images[0] : $images);
- }
- $return = [
- 'success' => $result,
- ];
- } elseif (str_starts_with($field, 'new_') && !$ID) {
- //Create post
- $success = [
- 'created' => [],
- 'errors' => []
- ];
- $i = 1;
- foreach ($images as $img) {
- $ID = wp_insert_post([
- 'post_type' => jvbCheckBase($args['content']),
- 'post_author' => $operation->user_id,
- 'post_title' => 'New '.JVB_CONTENT[$args['content']]['singular'].' '.$i,
- 'post_content' => ' ',
- 'post_excerpt' => ' '
- ], true);
-
- error_log('Created Post: '.print_r($ID, true));
-
- if ($ID && !is_wp_error($ID)) {
- $success['created'][] = $ID;
- set_post_thumbnail($ID, $img);
- }else {
- $success['errors'][] = $ID;
- }
- $i++;
- }
- $return = [
- 'success' => true,
- 'result' => $success
- ];
- } else {
- if ($args['post_id']) {
- $type = 'post';
- } elseif ($args['term_id']) {
- $type = 'term';
- }
- error_log('ID: '.print_r($ID, true));
- error_log('Type: '.print_r($type, true));
- $meta = new MetaManager($ID, $type);
-
- $images = implode(',', $images);
- error_log('Processed Image IDs: '.$images);
- $success = $meta->updateValue($field, $images);
-
- $return = [
- 'success' => $success,
- 'result' => 'Field '.$field.' updated successfully'
- ];
- }
-
- return $return;
- }
-
- protected function createStandardResponse(bool $success, array $data = [], string $message = '', string $operation_id = ''): WP_REST_Response
+ protected function sendResponse(bool $success, array $data = [], string $message = '', string $operation_id = ''): WP_REST_Response
{
$response = [
'success' => $success,
@@ -844,353 +1150,68 @@
$response['operation_id'] = $operation_id;
}
- return new WP_REST_Response($response);
- }
-
- /**
- * Handle metadata update requests
- */
- public function handleMetadataUpdate(WP_REST_Request $request): WP_REST_Response
- {
- try {
- $data = $request->get_params();
-
- // Validate user permissions
- if (!$this->userCheck($data['user'])) {
- return $this->createStandardResponse(
- false,
- ['error_code' => 'invalid_user'],
- 'Invalid user specified'
- );
- }
-
- if (!empty($data['attachment_ids'])) {
- // Phase 2B: Direct attachment update (images already processed)
- return $this->updateAttachmentMetadataDirect($data);
- }
- elseif (!empty($data['operation_id'])) {
- // Phase 2A: Queue metadata update with dependency on upload operation
- return $this->queueMetadataUpdate($data);
- }
-
- return $this->createStandardResponse(
- false,
- ['error_code' => 'missing_identifiers'],
- 'Must provide either attachment_ids or operation_id'
- );
-
- } catch (Exception $e) {
- JVB()->error()->log(
- '[UploadRoutes]:handleMetadataUpdate',
- $e->getMessage(),
- [
- 'request_data' => $request->get_params(),
- 'trace' => $e->getTraceAsString()
- ]
- );
-
- return $this->createStandardResponse(
- false,
- ['error_code' => 'metadata_update_failed'],
- 'Metadata update failed: ' . $e->getMessage()
- );
- }
- }
- /**
- * Update metadata directly on completed attachments
- */
- protected function updateAttachmentMetadataDirect(array $data): WP_REST_Response
- {
- $updated_count = 0;
- $errors = [];
-
- foreach ($data['attachment_ids'] as $attachment_id) {
- try {
- // Verify attachment exists and user has permission
- if (!$this->canUserEditAttachment($data['user'], $attachment_id)) {
- $errors[] = "No permission to edit attachment {$attachment_id}";
- continue;
- }
-
- $this->applyMetadataToAttachment($attachment_id, $data['metadata']);
- $updated_count++;
-
- } catch (Exception $e) {
- $errors[] = "Failed to update attachment {$attachment_id}: " . $e->getMessage();
- }
- }
-
- return $this->createStandardResponse(
- $updated_count > 0,
- [
- 'updated_count' => $updated_count,
- 'errors' => $errors,
- 'attachment_ids' => $data['attachment_ids']
- ],
- $updated_count > 0 ?
- "Updated metadata for {$updated_count} attachment(s)" :
- 'No attachments were updated'
- );
- }
- /**
- * Queue metadata update with dependency on upload operation
- */
- protected function queueMetadataUpdate(array $data): WP_REST_Response
- {
- $queue = JVB()->queue();
-
- // Generate unique operation ID for this metadata update
- $metadata_operation_id = 'meta_' . uniqid();
-
- $queue->queueOperation(
- 'update_metadata',
- $data['user'],
- [
- 'original_operation_id' => $data['operation_id'],
- 'upload_ids' => $data['upload_ids'] ?? [],
- 'metadata' => $data['metadata']
- ],
- [
- 'operation_id' => $metadata_operation_id,
- 'depends_on' => $data['operation_id'], // Wait for upload completion
- 'priority' => 'medium',
- 'notification' => false // Don't spam notifications for metadata updates
- ]
- );
-
- return $this->createStandardResponse(
- true,
- [
- 'metadata_operation_id' => $metadata_operation_id,
- 'depends_on' => $data['operation_id'],
- 'upload_ids' => $data['upload_ids'] ?? []
- ],
- 'Metadata update queued - will apply after upload completes'
- );
- }
-
- /**
- * Process metadata update operation (called by queue processor)
- */
- public function processMetadataUpdate(WP_Error|array $result, object $operation, array $data): WP_Error|array
- {
- try {
- // Get the completed upload operation to map upload IDs to attachment IDs
- $uploadOperation = JVB()->queue()->getOperation($data['original_operation_id']);
-
- if (!$uploadOperation || $uploadOperation->status !== 'completed') {
- throw new Exception('Original upload operation not found or not completed');
- }
-
- $uploadResults = json_decode($uploadOperation->result, true);
- if (!$uploadResults || !$uploadResults['success']) {
- throw new Exception('Original upload operation failed');
- }
-
- // Build mapping from upload IDs to attachment IDs
- $uploadIdToAttachment = $this->buildUploadToAttachmentMapping(
- $data['upload_ids'],
- $uploadResults['data']
- );
-
- if (empty($uploadIdToAttachment)) {
- throw new Exception('No valid upload ID to attachment ID mappings found');
- }
-
- // Apply metadata to each mapped attachment
- $updated_count = 0;
- $errors = [];
-
- foreach ($uploadIdToAttachment as $uploadId => $attachmentId) {
- try {
- $this->applyMetadataToAttachment($attachmentId, $data['metadata']);
- $updated_count++;
- } catch (Exception $e) {
- $errors[] = "Upload {$uploadId} (Attachment {$attachmentId}): " . $e->getMessage();
- }
- }
-
- return [
- 'success' => $updated_count > 0,
- 'result' => [
- 'updated_count' => $updated_count,
- 'mappings' => $uploadIdToAttachment,
- 'errors' => $errors,
- 'message' => "Applied metadata to {$updated_count} attachment(s)"
- ]
- ];
-
- } catch (Exception $e) {
- JVB()->error()->log(
- '[UploadRoutes]:processMetadataUpdate',
- $e->getMessage(),
- [
- 'operation_id' => $operation->id,
- 'original_operation_id' => $data['original_operation_id'] ?? 'unknown'
- ]
- );
-
- return [
- 'success' => false,
- 'result' => $e->getMessage()
- ];
- }
- }
-
- /**
- * Build mapping from frontend upload IDs to WordPress attachment IDs
- */
- protected function buildUploadToAttachmentMapping(array $uploadIds, array $uploadResults): array
- {
- $mapping = [];
-
- // Handle both flat array and grouped results
- if (isset($uploadResults[0]) && is_array($uploadResults[0]) && isset($uploadResults[0]['attachment_id'])) {
- // Flat array of results
- foreach ($uploadResults as $index => $result) {
- if (isset($uploadIds[$index]) && isset($result['attachment_id'])) {
- $mapping[$uploadIds[$index]] = $result['attachment_id'];
- }
- }
- } else {
- // Grouped results (selection mode)
- $resultIndex = 0;
- foreach ($uploadResults as $group) {
- if (is_array($group)) {
- foreach ($group as $result) {
- if (isset($uploadIds[$resultIndex]) && isset($result['attachment_id'])) {
- $mapping[$uploadIds[$resultIndex]] = $result['attachment_id'];
- }
- $resultIndex++;
- }
- }
- }
- }
-
- return $mapping;
- }
-
- /**
- * Apply metadata to a WordPress attachment
- */
- protected function applyMetadataToAttachment(int $attachment_id, array $metadata): void
- {
- $updates = [];
-
- // Handle title update
- if (isset($metadata['title']) && !empty($metadata['title'])) {
- $updates['post_title'] = sanitize_text_field($metadata['title']);
- }
-
- // Handle caption update
- if (isset($metadata['caption'])) {
- $updates['post_excerpt'] = sanitize_textarea_field($metadata['caption']);
- }
-
- // Update post if we have title or caption changes
- if (!empty($updates)) {
- $updates['ID'] = $attachment_id;
- $result = wp_update_post($updates);
-
- if (is_wp_error($result)) {
- throw new Exception('Failed to update attachment post: ' . $result->get_error_message());
- }
- }
-
- // Handle alt text update (stored as post meta)
- if (isset($metadata['alt'])) {
- $alt_result = update_post_meta(
- $attachment_id,
- '_wp_attachment_image_alt',
- sanitize_text_field($metadata['alt'])
- );
-
- if ($alt_result === false) {
- throw new Exception('Failed to update alt text meta');
- }
- }
-
- // Handle any custom metadata fields
- if (isset($metadata['custom']) && is_array($metadata['custom'])) {
- foreach ($metadata['custom'] as $key => $value) {
- $meta_key = 'jvb_' . sanitize_key($key);
- update_post_meta($attachment_id, $meta_key, sanitize_text_field($value));
- }
- }
-
- // Clear any attachment caches
- clean_attachment_cache($attachment_id);
- }
-
- /**
- * Check if user can edit attachment
- */
- protected function canUserEditAttachment(int $user_id, int $attachment_id): bool
- {
- // Basic permission check - attachment must exist
- $attachment = get_post($attachment_id);
- if (!$attachment || $attachment->post_type !== 'attachment') {
- return false;
- }
-
- // User must be the author or have edit capabilities
- if ($attachment->post_author == $user_id) {
- return true;
- }
-
- // Check if user has edit_posts capability
- $user = get_user_by('id', $user_id);
- return $user && user_can($user, 'skip_moderation');
+ return $this->success($response);
}
public function handleGroupingRequest(WP_REST_Request $request): WP_REST_Response
{
- error_log('Handling Group Request from UploadRoutes.php');
try {
- $data = $request->get_params();
- error_log('Processing Data: '.print_r($data, true));
+ $files = $request->get_file_params();
+ $args = $this->buildUploadArgs($request);
- if (!array_key_exists($data['content'], JVB_CONTENT)) {
- return $this->createStandardResponse(
- false,
- ['error_code' => 'invalid_content'],
- 'Invalid content type specified'
- );
+ if (!array_key_exists('user', $args) || $args['user'] === 0){
+ return $this->unauthorized();
+ }
+ if (!array_key_exists('content', $args) || empty($args['content'])) {
+ return $this->validationError(['message'=>'Missing required content']);
+ }
+ if (!array_key_exists('posts', $args) || empty($args['posts'])) {
+ return $this->validationError(['message' => 'Missing posts required']);
}
+ // Secure files to temporary storage
+ $secured_files = $this->secureFiles($files, $args);
- //Get the operationIDs of the image uploads for the depends on
- $operationIDs = [];
- foreach ($data['posts'] as $index => $post) {
- foreach ($post['images'] as $img) {
- if (array_key_exists('operationId', $img)) {
- $operationIDs[] = $img['operationId'];
- }
- }
+ if (empty($secured_files['files'])) {
+ return $this->error('No valid files to upload');
}
- $operationIDs = array_unique($operationIDs);
+ // Queue file upload operation
+ $operation_type = $this->determineOperationType($secured_files['files'][0] ?? []);
+ $chunkSize = 5;
+ if ($operation_type === 'video') {
+ $chunkSize = 1;
+ } elseif ($operation_type === 'document') {
+ $chunkSize = 10;
+ }
- $queue = JVB()->queue();
- $queue->queueOperation(
- 'process_upload_groups',
- $data['user'],
- $data,
+ JVB()->queue()->queueOperation(
+ $operation_type,
+ $args['user'],
+ array_merge(
+ ['secured_files' => $secured_files['files']],
+ $args
+ ),
[
- 'operation_id' => $data['id'],
- 'depends_on' => $operationIDs
+ 'operation_id' => $args['upload'],
+ 'chunk_key' => 'secured_files',
+ 'chunk_size' => $chunkSize
]
);
- return $this->createStandardResponse(
- true,
+ $ID = JVB()->queue()->queueOperation(
+ 'process_upload_groups',
+ $args['user'],
+ $args,
[
- 'operation_id' => $data['id'],
- 'count' => count($data['posts'] ?? [])
- ],
- 'Operation queued successfully'
+ 'operation_id' => $args['id'],
+ 'depends_on' => [$args['upload']],
+ 'priority' => 'high',
+ 'chunk_key' => 'posts'
+ ]
);
+ return $this->queued($ID['operation_id'], 'Files uploaded and posts queued for creation');
} catch (Exception $e) {
JVB()->error()->log(
'[UploadRoutes]:handleGroupingRequest',
@@ -1200,327 +1221,7 @@
'trace' => $e->getTraceAsString()
]
);
-
- return $this->createStandardResponse(
- false,
- ['error_code' => 'grouping_failed'],
- 'Grouping operation failed: ' . $e->getMessage()
- );
+ return $this->error('Grouping operation failed: '.$e->getMessage());
}
}
-
- protected function processUploadGroups(WP_Error|array $result, object $operation, array $data): WP_Error|array
- {
- try {
- error_log('Processing Upload Groups - Data: ' . print_r($data, true));
-
- $queue = JVB()->queue();
- $all_uploaded_images = [];
- $used_upload_ids = [];
-
- // Collect all uploaded images from dependencies
- foreach(json_decode($operation->dependencies, true) as $operationID) {
- $o = $queue->getOperation($operationID);
- $r = json_decode($o->result, true);
-
- error_log('Saved operation'.print_r($r, true));
- if ($r['success'] && isset($r['data'])) {
- foreach ($r['data'] as $img) {
- $uploadKey = $img['upload_id'];
- $all_uploaded_images[$uploadKey] = [
- 'upload_id' => $img['upload_id'],
- 'attachment_id' => (int)$img['attachment_id'],
- 'operation_id' => $operationID
- ];
- }
- }
- }
-
-
- $content = jvbCheckBase($data['content']);
- $user = (int)$data['user'];
- $created_posts = [];
-
- foreach ($data['posts'] as $index => $post) {
- $new_post_id = wp_insert_post([
- 'post_type' => $content,
- 'post_author' => $user,
- 'post_status' => 'draft',
- 'post_title' => array_key_exists('post_title', $post['fields'])
- ? sanitize_text_field($post['fields']['post_title'])
- : 'New ' . JVB_CONTENT[$data['content']]['singular'],
- 'post_excerpt' => array_key_exists('post_excerpt', $post['fields'])
- ? sanitize_text_field($post['fields']['post_excerpt'])
- : '',
- ]);
-
- if ($new_post_id && !is_wp_error($new_post_id)) {
- $created_posts[] = $new_post_id;
-
- // Extract featured image
- $featured_upload_id = array_key_exists('featured', $post['fields'])
- ? $post['fields']['featured']
- : $post['images'][0]['upload_id'] ?? null;
-
- $featured_attachment_id = null;
- $gallery_attachment_ids = [];
-
- // Process all images for this post
- foreach ($post['images'] as $img) {
- $upload_id = $img['upload_id'];
- $used_upload_ids[] = $upload_id;
-
- if (isset($all_uploaded_images[$upload_id])) {
- $attachment_id = $all_uploaded_images[$upload_id]['attachment_id'];
-
- if ($upload_id === $featured_upload_id) {
- $featured_attachment_id = $attachment_id;
- } else {
- $gallery_attachment_ids[] = $attachment_id;
- }
- } else {
- error_log("Warning: Could not find attachment for upload_id: {$upload_id}");
- }
- }
-
- // Set featured image
- if ($featured_attachment_id) {
- set_post_thumbnail($new_post_id, (int)$featured_attachment_id);
- }
-
- // Set gallery images
- if (!empty($gallery_attachment_ids)) {
- $meta = new MetaManager($new_post_id, 'post');
- $fields = jvbGetFields($data['content'], 'post');
-
- foreach ($fields as $name => $config) {
- if ($config['type'] === 'gallery') {
- $meta->updateValue($name, implode(',', $gallery_attachment_ids));
- break;
- }
- }
- }
- }
- }
-
- // Cleanup unused uploaded images
- $unused_images = array_diff_key($all_uploaded_images, array_flip($used_upload_ids));
- $cleanup_result = $this->cleanupUnusedImages($unused_images);
-
- return [
- 'success' => true,
- 'result' => [
- 'created_posts' => $created_posts,
- 'total_posts' => count($created_posts),
- 'used_images' => count($used_upload_ids),
- 'cleaned_up_images' => $cleanup_result['cleaned_count'],
- 'cleanup_errors' => $cleanup_result['errors'],
- 'message' => "Created " . count($created_posts) . " post(s) from uploads"
- ]
- ];
-
- } catch (Exception $e) {
- JVB()->error()->log(
- '[UploadRoutes]:processUploadGroups',
- $e->getMessage(),
- [
- 'operation_id' => $operation->id,
- 'user_id' => $operation->user_id,
- 'posts_count' => count($data['posts'] ?? [])
- ]
- );
-
- return [
- 'success' => false,
- 'result' => $e->getMessage()
- ];
- }
- }
-
- protected function cleanupUnusedImages(array $unused_images): array
- {
- $cleaned_count = 0;
- $errors = [];
-
- foreach ($unused_images as $upload_id => $image_data) {
- try {
- $attachment_id = $image_data['attachment_id'];
-
- // Verify this attachment exists and wasn't already deleted
- if (get_post($attachment_id)) {
- // Delete the attachment and its files
- $deleted = wp_delete_attachment($attachment_id, true);
-
- if ($deleted) {
- $cleaned_count++;
- error_log("Cleaned up unused image: attachment_id {$attachment_id}, upload_id {$upload_id}");
- } else {
- $errors[] = "Failed to delete attachment {$attachment_id} for upload {$upload_id}";
- }
- } else {
- // Attachment already doesn't exist, count as cleaned
- $cleaned_count++;
- }
-
- } catch (Exception $e) {
- $errors[] = "Error cleaning up upload {$upload_id}: " . $e->getMessage();
- }
- }
-
- return [
- 'cleaned_count' => $cleaned_count,
- 'errors' => $errors
- ];
- }
-
- function getAttachmentID(array $image, array $storedResults): int|false
- {
- error_log('Looking for upload_id: ' . ($image['upload_id'] ?? 'NOT SET'));
- error_log('Available results: ' . print_r(array_keys($storedResults), true));
-
- foreach ($storedResults as $operationID => $uploads) {
- foreach ($uploads as $upload) {
- // FIX: Handle both case variations
- $stored_upload_id = $upload['upload_id'];
- $search_upload_id = $image['upload_id'];
-
- if ($stored_upload_id === $search_upload_id) {
- error_log("Found match! upload_id: {$search_upload_id} -> attachment_id: {$upload['attachment_id']}");
- return (int)$upload['attachment_id'];
- }
- }
- }
-
- error_log("No match found for upload_id: " . ($image['upload_id'] ?? 'MISSING'));
- return false;
- }
-
- function extractFeaturedItem(array &$items, string $meta_key = 'featured'): array
- {
- // Handle empty array
- if (empty($items)) {
- return [
- 'featured' => null,
- 'remaining' => []
- ];
- }
-
- $featured_index = null;
-
- // First pass: look for featured item
- foreach ($items as $index => $item) {
- if (isset($item['meta'][$meta_key])) {
- $featured_index = $index;
- break;
- }
- }
-
- // If no featured item found, use first item (index 0)
- if ($featured_index === null) {
- $featured_index = 0;
- }
-
- // Extract the featured/first item
- $featured = $items[$featured_index];
-
- // Remove the item from the original array
- unset($items[$featured_index]);
-
- // Re-index the array to maintain sequential indices
- $remaining = array_values($items);
-
- return [
- 'featured' => $featured,
- 'remaining' => $remaining
- ];
- }
-
- protected function mapUploadIdsToAttachments(array $uploadIds, array $uploadedImages): array
- {
- $mappedImages = [];
-
- foreach ($uploadIds as $uploadId) {
- $imageData = $this->findImageByUploadId($uploadId, $uploadedImages);
- if ($imageData) {
- $mappedImages[] = $imageData;
- }
- }
-
- return $mappedImages;
- }
-
- protected function findImageByUploadId(string $uploadId, array $uploadedImages): ?array
- {
- // Handle both flat array and grouped results
- foreach ($uploadedImages as $image) {
- if (is_array($image)) {
- // If it's a grouped result, check each image in the group
- if (isset($image[0]) && is_array($image[0])) {
- foreach ($image as $groupImage) {
- if (isset($groupImage['upload_id']) && $groupImage['upload_id'] === $uploadId) {
- return $groupImage;
- }
- }
- } else {
- // Single image result
- if (isset($image['upload_id']) && $image['upload_id'] === $uploadId) {
- return $image;
- }
- }
- }
- }
-
- return null;
- }
-
- protected function determineFeaturedImage(array $group, array $groupImages): ?int
- {
- // Check if user has starred a specific image
- if (!empty($group['featured_upload_id'])) {
- foreach ($groupImages as $image) {
- if (isset($image['upload_id']) && $image['upload_id'] === $group['featured_upload_id']) {
- return $image['attachment_id'];
- }
- }
- }
-
- // Default to first image
- return !empty($groupImages) ? $groupImages[0]['attachment_id'] : null;
- }
-
- protected function sanitizeGroupMetadata(array $metadata): array
- {
- $sanitized = [];
-
- foreach ($metadata as $key => $value) {
- $sanitizedKey = sanitize_key($key);
-
- if (is_string($value)) {
- $sanitized[$sanitizedKey] = sanitize_text_field($value);
- } elseif (is_array($value)) {
- $sanitized[$sanitizedKey] = array_map('sanitize_text_field', $value);
- } else {
- $sanitized[$sanitizedKey] = $value;
- }
- }
-
- return $sanitized;
- }
-
- protected function generatePostTitle(string $content, int $userId): string
- {
- $username = get_user_meta($userId, 'first_name', true) ?: get_user_meta($userId, 'display_name', true);
- $link = get_user_meta($userId, BASE.'link', true);
- $city = function_exists('jvbArtistCity') ? jvbArtistCity($link) : '';
-
- $title = ucfirst($content);
- if ($username) {
- $title .= ' by ' . $username;
- }
- if ($city) {
- $title .= ' from ' . $city;
- }
-
- return $title;
- }
}
--
Gitblit v1.10.0