| | |
| | | namespace JVBase\managers\queue\executors; |
| | | |
| | | use JVBase\managers\queue\{Executor, Operation, Progress, Result}; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\UploadManager; |
| | | use JVBase\meta\MetaManager; |
| | | use JVBase\meta\Meta; |
| | | use Exception; |
| | | use JVBase\utility\Features; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\base\Site; |
| | | use WP_Error; |
| | | |
| | | 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 |
| | | * update_image_meta, temporary_cleanup, attach_upload_to_content, process_upload_groups |
| | | */ |
| | | final class UploadExecutor implements Executor |
| | | { |
| | |
| | | 'image_upload', |
| | | 'video_upload', |
| | | 'document_upload', |
| | | 'update_metadata', |
| | | 'update_image_meta', |
| | | 'temporary_cleanup', |
| | | 'attach_upload_to_content', |
| | | 'process_upload_groups' |
| | |
| | | '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), |
| | | 'update_image_meta' => $this->processMetaUpdate($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), |
| | |
| | | $uploader = new UploadManager(); |
| | | $processedResults = []; |
| | | $errors = []; |
| | | $uploadIds = []; |
| | | |
| | | $securedFiles = $data['secured_files'] ?? []; |
| | | |
| | | // Phase 1: File I/O — no DB, no transactions |
| | | $prepared = []; |
| | | foreach ($securedFiles as $securedFile) { |
| | | $uploadIds[] = $securedFile['upload_id']; |
| | | try { |
| | | $result = $uploader->processUpload( |
| | | $prepared[$securedFile['upload_id']] = $uploader->prepareFile( |
| | | $securedFile['temp_path'], |
| | | [ |
| | | 'file_type' => $fileType, |
| | |
| | | '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()); |
| | | $progress->failItem($securedFile['upload_id'], $e->getMessage()); |
| | | $errors[] = $e->getMessage(); |
| | | } |
| | | } |
| | | |
| | | // Handle destination (meta, post, post_group) |
| | | $this->handleUploadDestination($data, $processedResults); |
| | | // Phase 2: DB registration — quick per-file writes |
| | | foreach ($prepared as $uploadId => $fileInfo) { |
| | | try { |
| | | $result = $uploader->registerFile($fileInfo); |
| | | |
| | | // Cleanup temp files |
| | | if (!$result['success']) { |
| | | $progress->failItem($uploadId, 'Registration failed'); |
| | | $errors[] = "Failed to register {$uploadId}"; |
| | | continue; |
| | | } |
| | | |
| | | $processedResults[$uploadId] = [ |
| | | 'upload_id' => $uploadId, |
| | | 'attachment_id' => $result['attachment_id'] ?? 0, |
| | | 'url' => $result['url'] ?? '', |
| | | 'sizes' => $result['sizes'] ?? [], |
| | | ]; |
| | | |
| | | // Apply frontend metadata if provided |
| | | $securedFile = $this->findSecuredFile($securedFiles, $uploadId); |
| | | if ($securedFile && !empty($securedFile['metadata'])) { |
| | | $this->applyMeta($result['attachment_id'], $securedFile['metadata']); |
| | | } |
| | | |
| | | $progress->advance(); |
| | | |
| | | } catch (Exception $e) { |
| | | $progress->failItem($uploadId, $e->getMessage()); |
| | | $errors[] = $e->getMessage(); |
| | | } |
| | | } |
| | | |
| | | // Destination + cleanup unchanged |
| | | $this->handleUploadDestination($data, $processedResults); |
| | | $this->cleanupTempFiles($securedFiles, $operation->userId); |
| | | |
| | | $outcome = 'success'; |
| | | if (!empty($operation->failedItems)) { |
| | | $outcome = count($operation->failedItems) === count($securedFiles) ? 'failed' : 'partial'; |
| | | $outcome = count($processedResults) > 0 ? 'success' : 'failed'; |
| | | if (count($processedResults) > 0 && !empty($errors)) { |
| | | $outcome = 'partial'; |
| | | } |
| | | |
| | | return new Result( |
| | | outcome: $outcome, |
| | | result: $processedResults |
| | | result: [ |
| | | 'upload_ids' => $uploadIds, |
| | | 'uploads' => $processedResults, |
| | | 'processed_count' => count($processedResults), |
| | | 'total_count' => count($uploadIds), |
| | | 'errors' => $errors, |
| | | ] |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * Find a secured file entry by upload_id |
| | | */ |
| | | private function findSecuredFile(array $securedFiles, string $uploadId): ?array |
| | | { |
| | | foreach ($securedFiles as $file) { |
| | | if (($file['upload_id'] ?? null) === $uploadId) { |
| | | return $file; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * Attach upload results to content |
| | | */ |
| | | private function processAttachToContent(Operation $operation, array $data, Progress $progress): Result |
| | | { |
| | | $uploadOpId = $data['upload'] ?? null; |
| | | error_log('processing attached to content: '.print_r($data, true)); |
| | | 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') { |
| | | if (!$uploadOp || !in_array($uploadOp->outcome, ['success', 'partial'])) { |
| | | throw new Exception("Upload operation {$uploadOpId} not completed successfully"); |
| | | } |
| | | |
| | | $uploadResults = $uploadOp->result ?? []; |
| | | $uploadResults = $uploadOp->result['uploads'] ?? []; |
| | | if (empty($uploadResults)) { |
| | | throw new Exception('No upload results found'); |
| | | } |
| | | |
| | | // Attach to content via field |
| | | // Resolve post_id from content_update dependency for new posts |
| | | 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; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (empty($data['post_id'])) { |
| | | throw new Exception('Could not resolve post_id from dependencies'); |
| | | } |
| | | } |
| | | |
| | | if (!empty($data['field_name'])) { |
| | | $this->updateFieldValue($data, $uploadResults); |
| | | if ($data['field_name'] === 'timeline_gallery') { |
| | | $registrar = Registrar::getInstance($data['content']); |
| | | if ($registrar) { |
| | | switch ($registrar->getType()) { |
| | | case 'post': |
| | | $meta = Meta::forPost($data['item_id']); |
| | | break; |
| | | case 'term': |
| | | $meta = Meta::forTerm($data['item_id']); |
| | | break; |
| | | case 'user': |
| | | $meta = Meta::forUser($data['item_id']); |
| | | break; |
| | | default: |
| | | $meta = false; |
| | | |
| | | } |
| | | if ($meta) { |
| | | $title = $meta->get('post_title'); |
| | | $current = $meta->get('number'); |
| | | $i = empty($current) ? 1 : $current + 1; |
| | | foreach ($data['upload_ids'] as $uploadID) { |
| | | if (!array_key_exists($uploadID, $uploadResults)) { |
| | | continue; |
| | | } |
| | | $imgID = $uploadResults[$uploadID]['attachment_id'] ?? false; |
| | | if (!$imgID) { |
| | | continue; |
| | | } |
| | | $this->createTimelinePoint($imgID, $data['item_id'], $data['user'], $data['content'], $title, $i); |
| | | $i++; |
| | | } |
| | | } |
| | | } |
| | | } else { |
| | | $this->saveToMeta($data, $uploadResults); |
| | | } |
| | | } |
| | | |
| | | $progress->advance(1); |
| | | |
| | | return new Result( |
| | | outcome: 'success', |
| | | result: ['attached' => count($uploadResults)] |
| | | result: ['attached' => count($uploadResults), 'post_id' => $data['post_id']] |
| | | ); |
| | | } |
| | | |
| | | protected function createTimelinePoint(int $imgID, int $parentID, int $user, string $postType, string $baseTitle, int $index):int|WP_Error |
| | | { |
| | | $title = $baseTitle.' - Treatment #'.$index; |
| | | $args = [ |
| | | 'post_type' => jvbCheckBase($postType), |
| | | 'post_author' => $user, |
| | | 'post_status' => 'draft', |
| | | 'post_parent' => $parentID, |
| | | 'post_title' => $title, |
| | | 'post_name' => sanitize_title($title) |
| | | ]; |
| | | |
| | | $child = wp_insert_post($args); |
| | | if ($child && !is_wp_error($child)) { |
| | | set_post_thumbnail($child, $imgID); |
| | | } |
| | | return $child; |
| | | } |
| | | |
| | | /** |
| | | * Process metadata updates for attachments |
| | | */ |
| | | private function processMetadataUpdate(Operation $operation, array $data, Progress $progress): Result |
| | | private function processMetaUpdate(Operation $operation, array $data, Progress $progress): Result |
| | | { |
| | | $updatedCount = 0; |
| | | $errors = []; |
| | | |
| | | $postsAttachedTo =[]; |
| | | error_log('Processing Meta Update with data: '.print_r($data, true)); |
| | | foreach ($data as $uploadId => $info) { |
| | | if (!is_array($info) || empty($info['depends_on'])) { |
| | | if (!is_array($info)) { |
| | | continue; |
| | | } |
| | | |
| | | $success = true; |
| | | 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"; |
| | | if (array_key_exists('depends_on', $info)) { |
| | | // 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; |
| | | } |
| | | } else { |
| | | $attachmentId = $info['attachmentId']??false; |
| | | } |
| | | |
| | | if (!$attachmentId) { |
| | | $errors[] = "No attachment found for: ".print_r($info, true); |
| | | 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) { |
| | | $success = false; |
| | | $progress->failItem($uploadId, $e->getMessage()); |
| | | $errors[] = $e->getMessage(); |
| | | } |
| | | |
| | | if ($success) { |
| | | $postID = wp_get_post_parent_id($attachmentId); |
| | | if ($postID && !in_array($postID, $postsAttachedTo)){ |
| | | $postsAttachedTo[] = $postID; |
| | | } |
| | | } |
| | | |
| | | } |
| | | if (!empty ($postsAttachedTo)) { |
| | | foreach ($postsAttachedTo as $postId) { |
| | | Cache::invalidateItem('post', $postId); |
| | | } |
| | | } |
| | | |
| | | $outcome = $updatedCount > 0 ? 'success' : 'failed'; |
| | |
| | | } |
| | | |
| | | $uploads = []; |
| | | $uploadIds = []; |
| | | foreach ($dependencies as $dependency) { |
| | | $res = JVB()->queue()->getOperationValue($dependency, 'result'); |
| | | if (empty($res)) { |
| | | continue; |
| | | } |
| | | |
| | | // Results are stored at root level, keyed by upload_id |
| | | // Check if dependency result has upload_ids |
| | | if (isset($res['upload_ids'])) { |
| | | $uploadIds = array_merge($uploadIds, $res['upload_ids']); |
| | | } |
| | | // Results are stored in 'uploads', keyed by upload_id |
| | | // Filter to only include actual upload results (arrays with attachment_id) |
| | | foreach ($res as $key => $value) { |
| | | foreach ($res['uploads'] as $key => $value) { |
| | | if (is_array($value) && isset($value['attachment_id'])) { |
| | | $uploads[$key] = $value; |
| | | |
| | | // If we didn't get upload_ids from result, track them from keys |
| | | if (!isset($res['upload_ids']) && !in_array($key, $uploadIds)) { |
| | | $uploadIds[] = $key; |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | $content = jvbCheckBase($data['content']); |
| | | if (Features::forContent($data['content'])->has('is_timeline')) { |
| | | $registrar = Registrar::getInstance($data['content']); |
| | | if ($registrar && $registrar->hasFeature('is_timeline')) { |
| | | return $this->processTimelineUploads($operation, $data, $progress, $all_uploads); |
| | | } |
| | | |
| | | $user = (int)$operation->userId; |
| | | $user = $operation->userId; |
| | | $createdPosts = []; |
| | | $errors = []; |
| | | $groupMappings = []; |
| | | $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); |
| | | try { |
| | | $groupId = $post['groupId'] ?? null; |
| | | // Create post for this group |
| | | $created = $this->createPostFromGroup($post, $index+1, $content, $uploads, $operation); |
| | | |
| | | $post_excerpt = array_key_exists('post_excerpt', $post['fields']) |
| | | ? sanitize_textarea_field($post['fields']['post_excerpt']) |
| | | : ''; |
| | | if ($created) { |
| | | $postId = $created['ID']; |
| | | $createdPosts[] = [ |
| | | 'post_id' => $postId, |
| | | 'group_id' => $groupId, |
| | | ]; |
| | | |
| | | $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 ($groupId) { |
| | | $groupMappings[$groupId] = $postId; |
| | | } |
| | | } |
| | | 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; |
| | | } |
| | | } |
| | | $usedUploads = array_merge($usedUploads, $created['usedUploads']); |
| | | $progress->advance(1); |
| | | } |
| | | } catch (Exception $e) { |
| | | $errors[] = $e->getMessage(); |
| | | $progress->failItem($index ?? 'unknown', $e->getMessage()); |
| | | } |
| | | } |
| | | $outcome = !empty($createdPosts) ? 'success' : 'failed'; |
| | | if (!empty($createdPosts) && !empty($errors)) { |
| | | $outcome = 'partial'; |
| | | } |
| | | return new Result( |
| | | outcome: $outcome, |
| | | result: [ |
| | | 'upload_ids' => $usedUploads, |
| | | 'created_posts' => $createdPosts, |
| | | 'group_mappings' => $groupMappings, |
| | | 'post_count' => count($createdPosts), |
| | | 'processed_uploads' => count($uploads), |
| | | 'errors' => $errors, |
| | | ] |
| | | ); |
| | | } |
| | | |
| | | protected function createPostFromGroup(array $post, int $index, string $content, array $uploads, Operation $op):array|false |
| | | { |
| | | $registrar = Registrar::getInstance($content); |
| | | |
| | | $post_title = array_key_exists('post_title', $post['fields']) |
| | | ? sanitize_text_field($post['fields']['post_title']) |
| | | : ($registrar ? 'New '. $registrar->getSingular().' '.($index + 1) : 'New Item '.($index + 1)); |
| | | |
| | | $post_excerpt = array_key_exists('post_excerpt', $post['fields']) |
| | | ? sanitize_textarea_field($post['fields']['post_excerpt']) |
| | | : ''; |
| | | |
| | | $ID = wp_insert_post([ |
| | | 'post_type' => $content, |
| | | 'post_author' => $op->userId, |
| | | 'post_status' => 'draft', |
| | | 'post_title' => $post_title, |
| | | 'post_excerpt' => $post_excerpt, |
| | | ]); |
| | | if (!$ID || is_wp_error($ID)) { |
| | | throw new Exception('Could not create post: '.$ID?->get_error_message()); |
| | | } |
| | | |
| | | $uploadIds = []; |
| | | $featured_upload_id = $post['fields']['featured']??null; |
| | | $featured_attachment_id = null; |
| | | $gallery = []; |
| | | |
| | | foreach ($post['images'] as $img) { |
| | | $uploadId = $img['upload_id']; |
| | | if (array_key_exists($uploadId, $uploads)){ |
| | | $imgID = $uploads[$uploadId]['attachment_id']; |
| | | if ($uploadId === $featured_upload_id) { |
| | | $featured_attachment_id = $imgID; |
| | | } else { |
| | | $gallery[] = $imgID; |
| | | } |
| | | $uploadIds[] = $uploadId; |
| | | } |
| | | } |
| | | |
| | | return new Result( |
| | | outcome: !empty($createdPosts) ? 'success' : 'failed', |
| | | result: ['posts' => $createdPosts] |
| | | ); |
| | | if ($featured_attachment_id) { |
| | | set_post_thumbnail($ID, $featured_attachment_id); |
| | | } elseif (!empty($gallery)) { |
| | | set_post_thumbnail($ID, $gallery[0]); |
| | | array_shift($gallery); |
| | | } |
| | | |
| | | if (!empty($gallery)) { |
| | | $meta = Meta::forPost($ID); |
| | | $fields = Registrar::getFieldsFor($content); |
| | | //add images to first found gallery field |
| | | $found = false; |
| | | foreach ($fields as $name =>$config) { |
| | | if ($config['type'] === 'gallery' || ($config['type'] === 'upload' && (array_key_exists('multiple', $config) && $config['multiple'] === true))) { |
| | | $found = true; |
| | | $meta->set($name, implode(',', $gallery)); |
| | | break; |
| | | } |
| | | } |
| | | if (!$found) { |
| | | error_log('Could not find a gallery upload field for post '.$ID); |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | return [ |
| | | 'ID' => $ID, |
| | | 'usedUploads' => $uploadIds |
| | | ]; |
| | | } |
| | | |
| | | private function processTimelineUploads(Operation $operation, array $data, Progress $progress, array $uploads):Result |
| | |
| | | $user = $operation->userId; |
| | | $createdPosts = []; |
| | | $usedUploads = []; |
| | | $errors = []; |
| | | |
| | | $content = jvbCheckBase($data['content']); |
| | | $config = Features::getConfig($content); |
| | | $registrar = Registrar::getInstance($data['content']); |
| | | |
| | | $defaultTitle = 'New '.$config['singular']. ' '; |
| | | |
| | | $defaultTitle = ($registrar) ? 'New '.$registrar->getSingular(). ' ' : 'New Item '; |
| | | foreach($data['posts'] as $index => $post) { |
| | | $title = array_key_exists('post_title', $post['fields']) |
| | | ? sanitize_text_field($post['fields']['post_title']) |
| | | : $defaultTitle . ($index + 1); |
| | | try { |
| | | $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']) |
| | | : ''; |
| | | $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 |
| | | ]; |
| | | $args = [ |
| | | 'post_type' => $content, |
| | | 'post_author' => $user, |
| | | 'post_status' => 'draft', |
| | | 'post_title' => $title, |
| | | 'post_name' => sanitize_title($title), |
| | | 'post_excerpt' => $excerpt |
| | | ]; |
| | | |
| | | $parent = wp_insert_post($args); |
| | | $progress->advance(); |
| | | if ($parent && !is_wp_error($parent)) { |
| | | $parent = wp_insert_post($args); |
| | | |
| | | if (!$parent || is_wp_error($parent)) { |
| | | throw new Exception('Could not create post: '.$parent->get_error_message()); |
| | | } |
| | | |
| | | |
| | | $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 ($featuredID) { |
| | | $usedUploads[] = $featuredID; |
| | | set_post_thumbnail($parent, $featuredID); |
| | | } elseif (!empty($childPosts)) { |
| | | set_post_thumbnail($parent, (int)$childPosts[0]); |
| | | $usedUploads[] = (int)$childPosts[0]; |
| | | array_shift($childPosts); |
| | | } |
| | | |
| | | $createdChildren = []; |
| | | 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); |
| | | $child = $this->createTimelinePoint($imgID, $parent, $args['post_author'], $args['post_type'], $title, $treatment); |
| | | if ($child && !is_wp_error($child) && $child> 0 ) { |
| | | $createdChildren[] = $child; |
| | | $usedUploads[] = $imgID; |
| | | } |
| | | } |
| | | } |
| | | $createdPosts[] = [ |
| | | 'parent' => $parent, |
| | | 'children' => $createdChildren |
| | | ]; |
| | | |
| | | $this->updateTimelineMetadata($parent); |
| | | $progress->advance(); |
| | | } catch (Exception $e) { |
| | | $errors[] = $e->getMessage(); |
| | | $progress->failItem($index ?? 'unknown', $e->getMessage()); |
| | | } |
| | | } |
| | | |
| | | $outcome = !empty($createdPosts) ? 'success' : 'failed'; |
| | | if (!empty($createdPosts) && !empty($errors)) { |
| | | $outcome = 'partial'; |
| | | } |
| | | return new Result( |
| | | outcome: !empty($createdPosts) ? 'success' : 'failed', |
| | | result: ['posts' => $createdPosts] |
| | | outcome: $outcome, |
| | | result: [ |
| | | 'upload_ids' => $usedUploads, |
| | | 'created_posts' => $createdPosts, |
| | | 'post_count' => count($createdPosts), |
| | | 'processed_uploads' => count($uploads), |
| | | 'errors' => $errors |
| | | ] |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * Update timeline parent post with count and latest date |
| | | * @param int $parentId Parent timeline post ID |
| | | */ |
| | | private function updateTimelineMetadata(int $parentId): void |
| | | { |
| | | // Get all child posts |
| | | $children = get_children([ |
| | | 'post_parent' => $parentId, |
| | | 'post_type' => get_post_type($parentId), |
| | | 'post_status' => ['publish', 'draft'], |
| | | 'orderby' => 'date', |
| | | 'order' => 'DESC', |
| | | 'fields' => 'ids' |
| | | ]); |
| | | |
| | | // Count includes parent + children |
| | | $number = count($children) + 1; |
| | | |
| | | // Update both meta fields |
| | | update_post_meta($parentId, BASE . 'number', $number); |
| | | update_post_meta($parentId, BASE . 'latest_date', time()); |
| | | } |
| | | |
| | | // ───────────────────────────────────────────────────────────── |
| | | // 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 (array_key_exists('image-alt-text', $metadata)) { |
| | | update_post_meta($attachmentId, '_wp_attachment_image_alt', sanitize_text_field($metadata['image-alt-text'])); |
| | | } |
| | | |
| | | if (!empty($metadata['alt'])) { |
| | | update_post_meta($attachmentId, '_wp_attachment_image_alt', sanitize_text_field($metadata['alt'])); |
| | | $postUpdates = []; |
| | | |
| | | if (array_key_exists('image-title', $metadata)) { |
| | | $postUpdates['post_title'] = sanitize_text_field($metadata['image-title']); |
| | | } |
| | | |
| | | if (!empty($metadata['caption'])) { |
| | | wp_update_post([ |
| | | 'ID' => $attachmentId, |
| | | 'post_excerpt' => sanitize_textarea_field($metadata['caption']), |
| | | ]); |
| | | if (array_key_exists('image-caption', $metadata)) { |
| | | $postUpdates['post_excerpt'] = sanitize_textarea_field($metadata['image-caption']); |
| | | } |
| | | if (array_key_exists('image-description', $metadata)) { |
| | | $postUpdates['post_excerpt']= sanitize_textarea_field($metadata['image-caption']); |
| | | } |
| | | if (!empty($postUpdates)){ |
| | | $postUpdates['ID'] = $attachmentId; |
| | | wp_update_post($postUpdates); |
| | | } |
| | | } |
| | | |
| | |
| | | return; |
| | | } |
| | | |
| | | $existing = $meta->getValue($data['field_name']); |
| | | $existingIds = !empty($existing) ? explode(',', $existing) : []; |
| | | $allIds = array_unique(array_merge($existingIds, $attachmentIds)); |
| | | $fieldType = $data['field_type'] ?? 'single'; |
| | | |
| | | $meta->updateValue($data['field_name'], implode(',', $allIds)); |
| | | if ($fieldType === 'single') { |
| | | // Single field: replace with latest upload |
| | | $meta->set($data['field_name'], end($attachmentIds)); |
| | | } else { |
| | | // Multi field: merge with existing |
| | | $existing = $meta->get($data['field_name']); |
| | | $existingIds = !empty($existing) ? explode(',', $existing) : []; |
| | | $allIds = array_unique(array_merge($existingIds, $attachmentIds)); |
| | | $meta->set($data['field_name'], implode(',', $allIds)); |
| | | } |
| | | |
| | | } |
| | | |
| | | private function updateFieldValue(array $data, array $results): void |
| | |
| | | return; |
| | | } |
| | | |
| | | $existing = $meta->getValue($data['field_name']); |
| | | $existing = $meta->get($data['field_name']); |
| | | $existingIds = !empty($existing) ? explode(',', $existing) : []; |
| | | $allIds = array_unique(array_merge($existingIds, $attachmentIds)); |
| | | |
| | | $meta->updateValue($data['field_name'], implode(',', $allIds)); |
| | | $meta->set($data['field_name'], implode(',', $allIds)); |
| | | } |
| | | |
| | | private function getMetaManager(array $data): ?MetaManager |
| | | private function getMetaManager(array $data): ?Meta |
| | | { |
| | | if (!empty($data['post_id'])) { |
| | | return new MetaManager($data['post_id'], 'post'); |
| | | return Meta::forPost($data['post_id']); |
| | | } |
| | | if (!empty($data['term_id'])) { |
| | | return new MetaManager($data['term_id'], 'term'); |
| | | return Meta::forTerm($data['term_id']); |
| | | } |
| | | if (!empty($data['user'])) { |
| | | $link = (int)get_user_meta($data['user'], BASE . 'link', true); |
| | | if ($link) { |
| | | return new MetaManager($link, 'post'); |
| | | return Meta::forPost($link); |
| | | } |
| | | } |
| | | return null; |
| | |
| | | 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); |
| | | $meta = Meta::forPost($postId); |
| | | $meta->set('video', $attachmentId); |
| | | } else { |
| | | $meta = new MetaManager($postId, 'post'); |
| | | $existing = $meta->getValue('documents'); |
| | | $meta = Meta::forPost($postId); |
| | | $existing = $meta->get('documents'); |
| | | $existingIds = !empty($existing) ? explode(',', $existing) : []; |
| | | $existingIds[] = $attachmentId; |
| | | $meta->updateValue('documents', implode(',', $existingIds)); |
| | | $meta->set('documents', implode(',', $existingIds)); |
| | | } |
| | | } |
| | | |