| | |
| | | namespace JVBase\managers\queue\executors; |
| | | |
| | | use JVBase\managers\queue\{Executor, Operation, Progress, Result}; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\UploadManager; |
| | | 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; |
| | |
| | | |
| | | $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)) { |
| | | $progress->failItem($securedFile['upload_id'], $result->get_error_message()); |
| | | $errors[] = $result->get_error_message(); |
| | | continue; |
| | | } |
| | | |
| | | $processedResults[$securedFile['upload_id']] = [ |
| | | 'upload_id' => $securedFile['upload_id'], |
| | | 'attachment_id' => $result['attachment_id'] ?? 0, |
| | | 'url' => $result['url'] ?? '', |
| | | 'sizes' => $result['sizes'] ?? [], |
| | | ]; |
| | | if (!empty($securedFile['metadata'])) { |
| | | $this->applyMeta($securedFile['attachment_id'], $securedFile['metadata']); |
| | | } |
| | | |
| | | $progress->advance(); |
| | | |
| | | } catch (Exception $e) { |
| | | $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 = count($processedResults) > 0 ? 'success' : 'failed'; |
| | |
| | | return new Result( |
| | | outcome: $outcome, |
| | | result: [ |
| | | 'upload_ids' => $uploadIds, |
| | | 'uploads' => $processedResults, |
| | | 'processed_count'=> count($processedResults), |
| | | 'total_count' => count($uploadIds), |
| | | 'errors' => $errors |
| | | '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 |
| | | */ |
| | |
| | | $updatedCount = 0; |
| | | $errors = []; |
| | | |
| | | $postsAttachedTo =[]; |
| | | error_log('Processing Meta Update with data: '.print_r($data, true)); |
| | | foreach ($data as $uploadId => $info) { |
| | | if (!is_array($info)) { |
| | | continue; |
| | | } |
| | | |
| | | $success = true; |
| | | try { |
| | | if (array_key_exists('depends_on', $info)) { |
| | | // Get the dependency operation to find attachment ID |
| | |
| | | $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'; |
| | |
| | | } |
| | | |
| | | $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); |
| | | } |
| | | |
| | |
| | | |
| | | protected function createPostFromGroup(array $post, int $index, string $content, array $uploads, Operation $op):array|false |
| | | { |
| | | $config = JVB_CONTENT[jvbNoBase($content)]??false; |
| | | if (!$config) { |
| | | throw new Exception('No config found for content: '.$content.'.'); |
| | | } |
| | | $registrar = Registrar::getInstance($content); |
| | | |
| | | $post_title = array_key_exists('post_title', $post['fields']) |
| | | ? sanitize_text_field($post['fields']['post_title']) |
| | | : 'New '. $config['singular'].' '.($index + 1); |
| | | : ($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']) |
| | |
| | | |
| | | if (!empty($gallery)) { |
| | | $meta = Meta::forPost($ID); |
| | | $fields = jvbGetFields($content, 'post'); |
| | | $fields = Registrar::getFieldsFor($content); |
| | | //add images to first found gallery field |
| | | $found = false; |
| | | foreach ($fields as $name =>$config) { |
| | | if ($config['type'] === 'upload' && (array_key_exists('multiple', $config) && $config['multiple'] === true)) { |
| | | 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 |
| | |
| | | $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) { |
| | | try { |
| | | $title = array_key_exists('post_title', $post['fields']) |
| | |
| | | 'post_author' => $user, |
| | | 'post_status' => 'draft', |
| | | 'post_title' => $title, |
| | | 'post_slug' => sanitize_title($title), |
| | | 'post_name' => sanitize_title($title), |
| | | 'post_excerpt' => $excerpt |
| | | ]; |
| | | |
| | |
| | | |
| | | 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)) { |
| | | $createdChildren = $child; |
| | | $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; |
| | | set_post_thumbnail($child, $imgID); |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | private function applyMeta(int $attachmentId, array $metadata): void |
| | | { |
| | | if (!empty($metadata['image-title'])) { |
| | | wp_update_post([ |
| | | 'ID' => $attachmentId, |
| | | 'post_title' => sanitize_text_field($metadata['image-title']), |
| | | ]); |
| | | } |
| | | |
| | | if (!empty($metadata['image-alt-text'])) { |
| | | 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['image-caption'])) { |
| | | wp_update_post([ |
| | | 'ID' => $attachmentId, |
| | | 'post_excerpt' => sanitize_textarea_field($metadata['image-caption']), |
| | | ]); |
| | | $postUpdates = []; |
| | | |
| | | if (array_key_exists('image-title', $metadata)) { |
| | | $postUpdates['post_title'] = sanitize_text_field($metadata['image-title']); |
| | | } |
| | | |
| | | 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->get($data['field_name']); |
| | | $existingIds = !empty($existing) ? explode(',', $existing) : []; |
| | | $allIds = array_unique(array_merge($existingIds, $attachmentIds)); |
| | | $fieldType = $data['field_type'] ?? 'single'; |
| | | |
| | | $meta->set($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 |