<?php
|
namespace JVBase\managers\queue\executors;
|
|
use JVBase\managers\queue\{Executor, Operation, Progress, Result};
|
use JVBase\managers\UploadManager;
|
use JVBase\meta\MetaManager;
|
use Exception;
|
use JVBase\utility\Features;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
|
/**
|
* Executor for upload-related queue operations.
|
* Handles: image_upload, video_upload, document_upload,
|
* update_metadata, temporary_cleanup, attach_upload_to_content, process_upload_groups
|
*/
|
final class UploadExecutor implements Executor
|
{
|
private const HANDLED_TYPES = [
|
'image_upload',
|
'video_upload',
|
'document_upload',
|
'update_metadata',
|
'temporary_cleanup',
|
'attach_upload_to_content',
|
'process_upload_groups'
|
];
|
|
public function execute(Operation $operation, Progress $progress): Result
|
{
|
if (!in_array($operation->type, self::HANDLED_TYPES)) {
|
throw new Exception("UploadExecutor cannot handle type: {$operation->type}");
|
}
|
|
try {
|
$data = $operation->requestData;
|
|
return match($operation->type) {
|
'image_upload' => $this->processFileUpload($operation, $data, 'image', $progress),
|
'video_upload' => $this->processFileUpload($operation, $data, 'video', $progress),
|
'document_upload' => $this->processFileUpload($operation, $data, 'document', $progress),
|
'update_metadata' => $this->processMetadataUpdate($operation, $data, $progress),
|
'temporary_cleanup' => $this->processTemporaryCleanup($operation, $data, $progress),
|
'attach_upload_to_content'=> $this->processAttachToContent($operation, $data, $progress),
|
'process_upload_groups' => $this->processUploadGroups($operation, $data, $progress),
|
default => throw new Exception("Unknown type: {$operation->type}")
|
};
|
|
} catch (Exception $e) {
|
JVB()->error()->log(
|
'[UploadExecutor]:execute',
|
$e->getMessage(),
|
[
|
'operation_id' => $operation->id,
|
'operation_type' => $operation->type,
|
'user_id' => $operation->userId,
|
]
|
);
|
|
return new Result(
|
outcome: 'failed',
|
result: ['error' => $e->getMessage()]
|
);
|
}
|
}
|
|
/**
|
* Process file uploads (images, videos, documents)
|
*/
|
private function processFileUpload(Operation $operation, array $data, string $fileType, Progress $progress): Result
|
{
|
$uploader = new UploadManager();
|
$processedResults = [];
|
$errors = [];
|
|
$securedFiles = $data['secured_files'] ?? [];
|
|
foreach ($securedFiles as $securedFile) {
|
try {
|
$result = $uploader->processUpload(
|
$securedFile['temp_path'],
|
[
|
'file_type' => $fileType,
|
'user_id' => $operation->userId,
|
'post_id' => (int)($data['post_id'] ?? 0),
|
'term_id' => (int)($data['term_id'] ?? 0),
|
'original_name' => $securedFile['original_name'],
|
'mime_type' => $securedFile['mime_type'],
|
'content' => $data['content'] ?? '',
|
]
|
);
|
|
if (!is_wp_error($result)) {
|
$standardized = [
|
'attachment_id' => $result['attachment_id'],
|
'url' => $result['url'],
|
'file' => $result['file'],
|
'upload_id' => $securedFile['upload_id'] ?? null,
|
];
|
|
if ($standardized['upload_id']) {
|
$processedResults[$standardized['upload_id']] = $standardized;
|
} else {
|
$processedResults[] = $standardized;
|
}
|
|
// Apply frontend metadata if provided
|
if (!empty($securedFile['metadata'])) {
|
$this->applyMeta($standardized['attachment_id'], $securedFile['metadata']);
|
}
|
|
$progress->advance(1);
|
} else {
|
$progress->failItem($securedFile, $result->get_error_message());
|
}
|
|
} catch (Exception $e) {
|
$progress->failItem($securedFile, $e->getMessage());
|
$errors[] = $e->getMessage();
|
}
|
}
|
|
// Handle destination (meta, post, post_group)
|
$this->handleUploadDestination($data, $processedResults);
|
|
// Cleanup temp files
|
$this->cleanupTempFiles($securedFiles, $operation->userId);
|
|
$outcome = 'success';
|
if (!empty($operation->failedItems)) {
|
$outcome = count($operation->failedItems) === count($securedFiles) ? 'failed' : 'partial';
|
}
|
|
return new Result(
|
outcome: $outcome,
|
result: $processedResults
|
);
|
}
|
|
/**
|
* Attach upload results to content
|
*/
|
private function processAttachToContent(Operation $operation, array $data, Progress $progress): Result
|
{
|
$uploadOpId = $data['upload'] ?? null;
|
if (!$uploadOpId) {
|
throw new Exception('No upload operation ID provided');
|
}
|
|
// Get results from the dependency
|
$uploadOp = JVB()->queue()->get($uploadOpId);
|
if (!$uploadOp || $uploadOp->outcome !== 'success') {
|
throw new Exception("Upload operation {$uploadOpId} not completed successfully");
|
}
|
|
$uploadResults = $uploadOp->result ?? [];
|
if (empty($uploadResults)) {
|
throw new Exception('No upload results found');
|
}
|
|
// Attach to content via field
|
if (!empty($data['field_name'])) {
|
$this->updateFieldValue($data, $uploadResults);
|
}
|
|
$progress->advance(1);
|
|
return new Result(
|
outcome: 'success',
|
result: ['attached' => count($uploadResults)]
|
);
|
}
|
|
/**
|
* Process metadata updates for attachments
|
*/
|
private function processMetadataUpdate(Operation $operation, array $data, Progress $progress): Result
|
{
|
$updatedCount = 0;
|
$errors = [];
|
|
foreach ($data as $uploadId => $info) {
|
if (!is_array($info) || empty($info['depends_on'])) {
|
continue;
|
}
|
|
try {
|
// Get the dependency operation to find attachment ID
|
$depOp = JVB()->queue()->get($info['depends_on']);
|
if (!$depOp || !$depOp->result) {
|
$errors[] = "Dependency {$info['depends_on']} not found or has no result";
|
continue;
|
}
|
|
$attachmentId = $this->findAttachmentByUploadId($uploadId, $depOp->result);
|
if (!$attachmentId) {
|
$errors[] = "No attachment found for upload ID: {$uploadId}";
|
continue;
|
}
|
|
$this->applyMeta($attachmentId, $info);
|
$updatedCount++;
|
$progress->advance(1);
|
|
} catch (Exception $e) {
|
$progress->failItem($uploadId, $e->getMessage());
|
$errors[] = $e->getMessage();
|
}
|
}
|
|
$outcome = $updatedCount > 0 ? 'success' : 'failed';
|
if ($updatedCount > 0 && !empty($errors)) {
|
$outcome = 'partial';
|
}
|
|
return new Result(
|
outcome: $outcome,
|
result: [
|
'updated' => $updatedCount,
|
'errors' => $errors,
|
]
|
);
|
}
|
|
/**
|
* Cleanup temporary upload files
|
*/
|
private function processTemporaryCleanup(Operation $operation, array $data, Progress $progress): Result
|
{
|
$uploader = new UploadManager();
|
|
// Cleanup secured files
|
if (!empty($data['files'])) {
|
foreach ($data['files'] as $file) {
|
if (!empty($file['temp_path']) && file_exists($file['temp_path'])) {
|
@unlink($file['temp_path']);
|
}
|
$progress->advance(1);
|
}
|
}
|
|
// Cleanup specific temp paths
|
if (!empty($data['temp_paths']) && is_array($data['temp_paths'])) {
|
foreach ($data['temp_paths'] as $tempPath) {
|
if (file_exists($tempPath)) {
|
@unlink($tempPath);
|
}
|
}
|
}
|
|
// Cleanup empty directories
|
$uploader->cleanupEmptyTempDirs($operation->userId);
|
|
return new Result(
|
outcome: 'success',
|
result: ['cleaned' => true]
|
);
|
}
|
|
/**
|
* Process grouped uploads into posts
|
*/
|
private function processUploadGroups(Operation $operation, array $data, Progress $progress): Result
|
{
|
|
$dependencies = $operation->dependencies;
|
if (empty($dependencies)) {
|
throw new Exception('No dependencies found for group uploads.');
|
}
|
|
$uploads = [];
|
foreach ($dependencies as $dependency) {
|
$res = JVB()->queue()->getOperationValue($dependency, 'result');
|
if (empty($res)) {
|
continue;
|
}
|
|
// Results are stored at root level, keyed by upload_id
|
// Filter to only include actual upload results (arrays with attachment_id)
|
foreach ($res as $key => $value) {
|
if (is_array($value) && isset($value['attachment_id'])) {
|
$uploads[$key] = $value;
|
}
|
}
|
}
|
|
if (empty($uploads)) {
|
throw new Exception('No uploads found for group uploads.');
|
}
|
|
$all_uploads = [];
|
foreach($uploads as $upload_id => $img) {
|
if (!array_key_exists('attachment_id', $img) || (int)$img['attachment_id'] === 0){
|
continue;
|
}
|
$all_uploads[$upload_id] = [
|
'upload_id' => $upload_id,
|
'attachment_id'=> (int)$img['attachment_id']
|
];
|
}
|
|
$content = jvbCheckBase($data['content']);
|
if (Features::forContent($data['content'])->has('is_timeline')) {
|
return $this->processTimelineUploads($operation, $data, $progress, $all_uploads);
|
}
|
|
$user = (int)$operation->userId;
|
$createdPosts = [];
|
$usedUploads = [];
|
|
foreach($data['posts'] as $index => $post) {
|
$progress->advance();
|
$post_title = array_key_exists('post_title', $post['fields'])
|
? sanitize_text_field($post['fields']['post_title'])
|
: 'New '. JVB_CONTENT[$data['content']]['singular'].' '.($index + 1);
|
|
$post_excerpt = array_key_exists('post_excerpt', $post['fields'])
|
? sanitize_textarea_field($post['fields']['post_excerpt'])
|
: '';
|
|
$args = [
|
'post_type' => $content,
|
'post_author' => $user,
|
'post_status' => 'draft',
|
'post_title' => $post_title,
|
'post_excerpt' => $post_excerpt
|
];
|
$newPostID = wp_insert_post($args);
|
if ($newPostID && !is_wp_error($newPostID)) {
|
$createdPosts[] = $newPostID;
|
|
$featured_upload_id = $post['fields']['featured']??null;
|
$featured_attachment_id = null;
|
$gallery_attachment_ids = [];
|
|
foreach ($post['images'] as $img) {
|
$uploadId = $img['upload_id'];
|
$usedUploads[] = $uploadId;
|
if (array_key_exists($uploadId, $all_uploads)) {
|
$attachmentId = $all_uploads[$uploadId]['attachment_id'];
|
|
if ($uploadId === $featured_upload_id) {
|
$featured_attachment_id = $attachmentId;
|
} else {
|
$gallery_attachment_ids[] = $attachmentId;
|
}
|
}
|
}
|
if ($featured_attachment_id) {
|
set_post_thumbnail($newPostID, $featured_attachment_id);
|
} elseif (!empty($gallery_attachment_ids)) {
|
set_post_thumbnail($newPostID, $gallery_attachment_ids[0]);
|
array_shift($gallery_attachment_ids);
|
}
|
|
if (!empty($gallery_attachment_ids)) {
|
$meta = new MetaManager($newPostID, 'post');
|
$fields = jvbGetFields($content, 'post');
|
foreach($fields as $name => $config) {
|
if ($config['type'] === 'gallery') {
|
$meta->updateValue($name, implode(',', $gallery_attachment_ids));
|
break;
|
}
|
}
|
}
|
}
|
}
|
|
return new Result(
|
outcome: !empty($createdPosts) ? 'success' : 'failed',
|
result: ['posts' => $createdPosts]
|
);
|
}
|
|
private function processTimelineUploads(Operation $operation, array $data, Progress $progress, array $uploads):Result
|
{
|
$user = $operation->userId;
|
$createdPosts = [];
|
$usedUploads = [];
|
|
$content = jvbCheckBase($data['content']);
|
$config = Features::getConfig($content);
|
|
$defaultTitle = 'New '.$config['singular']. ' ';
|
foreach($data['posts'] as $index => $post) {
|
$title = array_key_exists('post_title', $post['fields'])
|
? sanitize_text_field($post['fields']['post_title'])
|
: $defaultTitle . ($index + 1);
|
|
$excerpt = array_key_exists('post_excerpt', $post['fields'])
|
? sanitize_textarea_field($post['fields']['post_excerpt'])
|
: '';
|
|
$args = [
|
'post_type' => $content,
|
'post_author' => $user,
|
'post_status' => 'draft',
|
'post_title' => $title,
|
'post_excerpt' => $excerpt
|
];
|
|
$parent = wp_insert_post($args);
|
$progress->advance();
|
if ($parent && !is_wp_error($parent)) {
|
$childPosts = [];
|
$featured = $post['fields']['featured']??null;
|
$featuredID = null;
|
|
foreach ($post['images'] as $img) {
|
$uploadId = $img['upload_id'];
|
$usedUploads[] = $uploadId;
|
|
if (array_key_exists($uploadId, $uploads)) {
|
$attachmentId = (int)$uploads[$uploadId]['attachment_id'];
|
if ($uploadId === $featured) {
|
$featuredID = $attachmentId;;
|
} else {
|
$childPosts[] = $attachmentId;
|
}
|
}
|
}
|
if ($featuredID) {
|
set_post_thumbnail($parent, $featuredID);
|
} elseif (!empty($childPosts)) {
|
set_post_thumbnail($parent, (int)$childPosts[0]);
|
array_shift($childPosts);
|
}
|
if (!empty($childPosts)) {
|
$args['post_parent'] = $parent;
|
$args['post_excerpt'] = '';
|
$createdPosts[$parent] = [];
|
foreach($childPosts as $i => $imgID) {
|
$treatment = $i + 1;
|
$args ['post_title'] = $title.' - Treatment #'.$treatment;
|
$child = wp_insert_post($args);
|
if ($child && !is_wp_error($child)) {
|
$createdPosts[$parent][] = $child;
|
set_post_thumbnail($child, $imgID);
|
}
|
}
|
}
|
}
|
}
|
return new Result(
|
outcome: !empty($createdPosts) ? 'success' : 'failed',
|
result: ['posts' => $createdPosts]
|
);
|
}
|
|
// ─────────────────────────────────────────────────────────────
|
// Helper methods
|
// ─────────────────────────────────────────────────────────────
|
|
private function applyMeta(int $attachmentId, array $metadata): void
|
{
|
if (!empty($metadata['title'])) {
|
wp_update_post([
|
'ID' => $attachmentId,
|
'post_title' => sanitize_text_field($metadata['title']),
|
]);
|
}
|
|
if (!empty($metadata['alt'])) {
|
update_post_meta($attachmentId, '_wp_attachment_image_alt', sanitize_text_field($metadata['alt']));
|
}
|
|
if (!empty($metadata['caption'])) {
|
wp_update_post([
|
'ID' => $attachmentId,
|
'post_excerpt' => sanitize_textarea_field($metadata['caption']),
|
]);
|
}
|
}
|
|
private function handleUploadDestination(array $data, array $results): void
|
{
|
$destination = $data['destination'] ?? 'meta';
|
|
switch ($destination) {
|
case 'meta':
|
$this->saveToMeta($data, $results);
|
break;
|
case 'post':
|
$this->createIndividualPosts($data, $results);
|
break;
|
case 'post_group':
|
// Handled by process_upload_groups
|
break;
|
}
|
}
|
|
private function saveToMeta(array $data, array $results): void
|
{
|
if (empty($data['field_name'])) {
|
return;
|
}
|
|
$attachmentIds = array_column($results, 'attachment_id');
|
$meta = $this->getMetaManager($data);
|
if (!$meta) {
|
return;
|
}
|
|
$existing = $meta->getValue($data['field_name']);
|
$existingIds = !empty($existing) ? explode(',', $existing) : [];
|
$allIds = array_unique(array_merge($existingIds, $attachmentIds));
|
|
$meta->updateValue($data['field_name'], implode(',', $allIds));
|
}
|
|
private function updateFieldValue(array $data, array $results): void
|
{
|
if (empty($data['field_name'])) {
|
return;
|
}
|
|
$attachmentIds = array_column($results, 'attachment_id');
|
$meta = $this->getMetaManager($data);
|
if (!$meta) {
|
return;
|
}
|
|
$existing = $meta->getValue($data['field_name']);
|
$existingIds = !empty($existing) ? explode(',', $existing) : [];
|
$allIds = array_unique(array_merge($existingIds, $attachmentIds));
|
|
$meta->updateValue($data['field_name'], implode(',', $allIds));
|
}
|
|
private function getMetaManager(array $data): ?MetaManager
|
{
|
if (!empty($data['post_id'])) {
|
return new MetaManager($data['post_id'], 'post');
|
}
|
if (!empty($data['term_id'])) {
|
return new MetaManager($data['term_id'], 'term');
|
}
|
if (!empty($data['user'])) {
|
$link = (int)get_user_meta($data['user'], BASE . 'link', true);
|
if ($link) {
|
return new MetaManager($link, 'post');
|
}
|
}
|
return null;
|
}
|
|
private function createIndividualPosts(array $data, array $results): array
|
{
|
if (empty($data['content'])) {
|
return [];
|
}
|
|
$createdPosts = [];
|
|
foreach ($results as $result) {
|
$attachmentId = $result['attachment_id'];
|
$attachment = get_post($attachmentId);
|
|
$postData = [
|
'post_type' => jvbCheckBase($data['content']),
|
'post_title' => $attachment->post_title,
|
'post_status' => 'draft',
|
'post_author' => $data['user'] ?? get_current_user_id(),
|
];
|
|
$postId = wp_insert_post($postData);
|
|
if (!is_wp_error($postId)) {
|
$this->attachFileToPost($postId, $attachmentId, $data);
|
$createdPosts[] = [
|
'post_id' => $postId,
|
'attachment_id' => $attachmentId,
|
];
|
}
|
}
|
|
return $createdPosts;
|
}
|
|
private function attachFileToPost(int $postId, int $attachmentId, array $data): void
|
{
|
$attachment = get_post($attachmentId);
|
$mimeType = $attachment->post_mime_type;
|
|
if (str_starts_with($mimeType, 'image/')) {
|
set_post_thumbnail($postId, $attachmentId);
|
} elseif (str_starts_with($mimeType, 'video/')) {
|
$meta = new MetaManager($postId, 'post');
|
$meta->updateValue('video', $attachmentId);
|
} else {
|
$meta = new MetaManager($postId, 'post');
|
$existing = $meta->getValue('documents');
|
$existingIds = !empty($existing) ? explode(',', $existing) : [];
|
$existingIds[] = $attachmentId;
|
$meta->updateValue('documents', implode(',', $existingIds));
|
}
|
}
|
|
private function cleanupTempFiles(array $securedFiles, int $userId): void
|
{
|
$uploader = new UploadManager();
|
|
foreach ($securedFiles as $secured) {
|
if (!empty($secured['temp_path']) && file_exists($secured['temp_path'])) {
|
@unlink($secured['temp_path']);
|
}
|
}
|
|
$uploader->cleanupEmptyTempDirs($userId);
|
}
|
|
private function findAttachmentByUploadId(string $uploadId, array $results): ?int
|
{
|
if (isset($results[$uploadId]['attachment_id'])) {
|
return (int)$results[$uploadId]['attachment_id'];
|
}
|
|
foreach ($results as $result) {
|
if (isset($result['upload_id']) && $result['upload_id'] === $uploadId) {
|
return (int)$result['attachment_id'];
|
}
|
}
|
|
return null;
|
}
|
}
|