| | |
| | | |
| | | // Update with comma-separated string |
| | | $meta->set($data['field_name'], implode(',', $all_ids)); |
| | | $meta->save(); |
| | | } |
| | | |
| | | /** |
| | |
| | | return $this->success($response); |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | public function handleGroupingRequest(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | try { |
| | |
| | | return $this->error('Grouping operation failed: '.$e->getMessage()); |
| | | } |
| | | } |
| | | |
| | | protected function processUploadGroups(WP_Error|array $result, object $operation, array $data): WP_Error|array |
| | | { |
| | | try { |
| | | $queue = JVB()->queue(); |
| | | $all_uploaded_images = []; |
| | | |
| | | // Get the upload operation ID from dependencies |
| | | $dependencies = json_decode($operation->dependencies, true); |
| | | |
| | | if (empty($dependencies)) { |
| | | return [ |
| | | 'success' => false, |
| | | 'result' => 'No dependencies found' |
| | | ]; |
| | | } |
| | | |
| | | // Collect uploads from all dependency operations |
| | | $uploads = []; |
| | | foreach ($dependencies as $dependency) { |
| | | // Use getOperationValue like ContentRoutes does |
| | | $res = $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)) { |
| | | return [ |
| | | 'success' => false, |
| | | 'result' => 'No uploads to process' |
| | | ]; |
| | | } |
| | | |
| | | // Build map of upload_id => attachment_id |
| | | foreach ($uploads as $upload_id => $img) { |
| | | $all_uploaded_images[$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($data, $uploads, $all_uploaded_images, $operation); |
| | | } |
| | | |
| | | $user = (int)$data['user']; |
| | | $created_posts = []; |
| | | $used_upload_ids = []; |
| | | |
| | | // Create posts from groups |
| | | foreach ($data['posts'] as $index => $post) { |
| | | $post_title = !empty($post['fields']['post_title']) |
| | | ? sanitize_text_field($post['fields']['post_title']) |
| | | : 'New ' . JVB_CONTENT[$data['content']]['singular'] . ' ' . ($index + 1); |
| | | |
| | | $post_excerpt = !empty($post['fields']['post_excerpt']) |
| | | ? 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, |
| | | ]; |
| | | |
| | | $new_post_id = wp_insert_post($args); |
| | | |
| | | if ($new_post_id && !is_wp_error($new_post_id)) { |
| | | $created_posts[] = $new_post_id; |
| | | |
| | | // Get featured image upload_id - string, not int! |
| | | $featured_upload_id = $post['fields']['featured'] ?? 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; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Set featured image |
| | | if ($featured_attachment_id) { |
| | | set_post_thumbnail($new_post_id, $featured_attachment_id); |
| | | } elseif (!empty($gallery_attachment_ids)) { |
| | | set_post_thumbnail($new_post_id, $gallery_attachment_ids[0]); |
| | | array_shift($gallery_attachment_ids); |
| | | } |
| | | |
| | | // Set gallery images |
| | | if (!empty($gallery_attachment_ids)) { |
| | | $meta = Meta::forPost($new_post_id); |
| | | $fields = jvbGetFields($content, 'post'); |
| | | |
| | | foreach ($fields as $name => $config) { |
| | | if ($config['type'] === 'gallery') { |
| | | $meta->set($name, implode(',', $gallery_attachment_ids)); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | return [ |
| | | 'success' => true, |
| | | 'result' => [ |
| | | 'created_posts' => $created_posts, |
| | | 'total_posts' => count($created_posts), |
| | | 'used_images' => count($used_upload_ids), |
| | | '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 |
| | | ] |
| | | ); |
| | | |
| | | return [ |
| | | 'success' => false, |
| | | 'result' => $e->getMessage() |
| | | ]; |
| | | } |
| | | } |
| | | |
| | | protected function processTimelineUploads(array $data, array $uploads, array $uploadMap, object $operation):array |
| | | { |
| | | try { |
| | | $user = (int)$data['user']; |
| | | $created_posts = []; |
| | | $used_upload_ids = []; |
| | | |
| | | $content = jvbCheckBase($data['content']); |
| | | |
| | | $config = Features::getConfig($content); |
| | | |
| | | $defaultTitle = 'New '.$config['singular']. ' '; |
| | | foreach ($data['posts'] as $index=> $post) { |
| | | $title = !empty($post['fields']['post_title']) |
| | | ? sanitize_text_field($post['fields']['post_title']) |
| | | : $defaultTitle.($index + 1); |
| | | $excerpt = !empty($post['fields']['post_excerpt']) |
| | | ? 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); |
| | | |
| | | if ($parent && !is_wp_error($parent)) { |
| | | //Get the attachment IDs first |
| | | $childPosts = []; |
| | | $featured = $post['fields']['featured']??null; |
| | | $featuredID = null; |
| | | foreach ($post['images'] as $key => $img) { |
| | | $upload_id = $img['upload_id']; |
| | | $used_upload_ids[] = $upload_id; |
| | | |
| | | if (isset($uploadMap[$upload_id])) { |
| | | $attachment_id = (int)$uploadMap[$upload_id]['attachment_id']; |
| | | if ($upload_id === $featured) { |
| | | $featuredID = $attachment_id; |
| | | } else { |
| | | $childPosts[] = $attachment_id; |
| | | } |
| | | } |
| | | } |
| | | // Set the featured image for the parent |
| | | if ($featuredID) { |
| | | set_post_thumbnail($parent, $featuredID); |
| | | } elseif (!empty($childPosts)) { |
| | | //use first image if no set featured |
| | | set_post_thumbnail($parent, (int)$childPosts[0]); |
| | | array_shift($childPosts); |
| | | } |
| | | |
| | | //Create Child Posts |
| | | if (!empty($childPosts)) { |
| | | $args['post_parent'] = $parent; |
| | | $created_posts[$parent] = []; |
| | | foreach ($childPosts as $i => $imgID) { |
| | | $treatment = $i + 1; |
| | | $childTitle = $title.' - Treatment '.$treatment; |
| | | $childDesc = ''; |
| | | $args['post_title'] = $childTitle; |
| | | $args['post_excerpt'] = $childDesc; |
| | | $child = wp_insert_post($args); |
| | | if ($child && !is_wp_error($child)) { |
| | | $created_posts[$parent][] = $child; |
| | | set_post_thumbnail($child, $imgID); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | return [ |
| | | 'success' => true, |
| | | 'result' => [ |
| | | 'created_posts' => $created_posts, |
| | | 'used_images' => $used_upload_ids |
| | | ] |
| | | ]; |
| | | } catch (Exception $e) { |
| | | JVB()->error()->log( |
| | | '[UploadRoutes]:processTimelineUploads', |
| | | $e->getMessage(), |
| | | [ |
| | | 'operation_id' => $operation->id, |
| | | 'user_id' => $operation->user_id |
| | | ] |
| | | ); |
| | | |
| | | 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++; |
| | | } 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 |
| | | { |
| | | 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) { |
| | | return (int)$upload['attachment_id']; |
| | | } |
| | | } |
| | | } |
| | | 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; |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | /** |
| | | * Determine how to save uploaded files based on configuration |
| | | */ |
| | | protected function handleUploadDestination(array $data, array $results): void |
| | | { |
| | | // Determine destination from config |
| | | $destination = $data['destination'] ?? 'meta'; |
| | | |
| | | switch ($destination) { |
| | | case 'meta': |
| | | // Save to post/term/user meta |
| | | $this->saveToMeta($data, $results); |
| | | break; |
| | | |
| | | case 'post': |
| | | // Create individual posts for each file |
| | | $this->createIndividualPosts($data, $results); |
| | | break; |
| | | |
| | | case 'post_group': |
| | | // Create posts with grouped files |
| | | $this->createGroupedPosts($data, $results); |
| | | break; |
| | | |
| | | default: |
| | | // No destination specified - files processed but not attached |
| | | break; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Infer destination from existing data (backward compatibility) |
| | | */ |
| | | protected function inferDestination(array $data): string |
| | | { |
| | | // If field_name exists → saving to meta |
| | | if (!empty($data['field_name'])) { |
| | | return 'meta'; |
| | | } |
| | | |
| | | // If post_type exists without field_name → creating posts |
| | | if (!empty($data['content'])) { |
| | | return 'post'; |
| | | } |
| | | |
| | | // No destination |
| | | return 'none'; |
| | | } |
| | | |
| | | private function getMetaManager(array $data): ?Meta |
| | | { |
| | | if (!empty($data['post_id'])) { |
| | | return Meta::forPost($data['post_id']); |
| | | } |
| | | if (!empty($data['term_id'])) { |
| | | return Meta::forTerm($data['term_id']); |
| | | } |
| | | if (!empty($data['user'])) { |
| | | $link = (int)get_user_meta($data['user'], BASE . 'link', true); |
| | | if ($link) { |
| | | return Meta::forPost($link); |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | /** |
| | | * Save attachment IDs to meta field |
| | | */ |
| | | 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; |
| | | } |
| | | |
| | | $fieldType = $data['field_type'] ?? 'single'; |
| | | |
| | | 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)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Create individual posts from uploads |
| | | */ |
| | | protected function createIndividualPosts(array $data, array $results): array |
| | | { |
| | | if (empty($data['content'])) { |
| | | return []; |
| | | } |
| | | |
| | | $created_posts = []; |
| | | |
| | | foreach ($results as $result) { |
| | | $attachment_id = $result['attachment_id']; |
| | | $attachment = get_post($attachment_id); |
| | | |
| | | // Create post |
| | | $post_data = [ |
| | | 'post_type' => jvbCheckBase($data['content']), |
| | | 'post_title' => $attachment->post_title, |
| | | 'post_status' => 'draft', |
| | | 'post_author' => $data['user'] ?? get_current_user_id(), |
| | | ]; |
| | | |
| | | $post_id = wp_insert_post($post_data); |
| | | |
| | | if (!is_wp_error($post_id)) { |
| | | // Set as featured image or attach to gallery |
| | | $this->attachFileToPost($post_id, $attachment_id, $data); |
| | | |
| | | $created_posts[] = [ |
| | | 'post_id' => $post_id, |
| | | 'attachment_id' => $attachment_id, |
| | | ]; |
| | | } |
| | | } |
| | | |
| | | return $created_posts; |
| | | } |
| | | |
| | | /** |
| | | * Create posts with grouped uploads |
| | | */ |
| | | protected function createGroupedPosts(array $data, array $results): array |
| | | { |
| | | if (empty($data['content'])) { |
| | | return []; |
| | | } |
| | | |
| | | $id_map = []; |
| | | foreach ($results as $result) { |
| | | if (isset($result['upload_id'], $result['attachment_id'])) { |
| | | $id_map[$result['upload_id']] = $result['attachment_id']; |
| | | } |
| | | } |
| | | |
| | | // Groups come from frontend as metadata |
| | | $groups = $data['groups'] ?? [array_column($results, 'attachment_id')]; |
| | | $created_posts = []; |
| | | |
| | | foreach ($groups as $group_index => $group_upload_ids) { |
| | | $group_attachment_ids = array_filter( |
| | | array_map(fn($uid) => $id_map[$uid] ?? null, $group_upload_ids) |
| | | ); |
| | | |
| | | if (empty($group_attachment_ids)) continue; |
| | | // Create post for this group |
| | | $first_attachment = get_post($group_attachment_ids[0]); |
| | | |
| | | $post_data = [ |
| | | 'post_type' => jvbCheckBase($data['content']), |
| | | 'post_title' => $data['group_titles'][$group_index] ?? $first_attachment->post_title, |
| | | 'post_status' => $data['post_status'] ?? 'draft', |
| | | 'post_author' => $data['user'] ?? get_current_user_id(), |
| | | ]; |
| | | |
| | | $post_id = wp_insert_post($post_data); |
| | | |
| | | if (!is_wp_error($post_id)) { |
| | | // Attach all files in group |
| | | foreach ($group_attachment_ids as $index => $attachment_id) { |
| | | if ($index === 0) { |
| | | // First is featured |
| | | set_post_thumbnail($post_id, $attachment_id); |
| | | } else { |
| | | // Others go to gallery |
| | | $meta = Meta::forPost($post_id); |
| | | $existing = $meta->get('gallery'); |
| | | $existing_ids = !empty($existing) ? explode(',', $existing) : []; |
| | | $existing_ids[] = $attachment_id; |
| | | $meta->set('gallery', implode(',', $existing_ids)); |
| | | } |
| | | } |
| | | |
| | | $created_posts[] = [ |
| | | 'post_id' => $post_id, |
| | | 'attachment_ids' => $group_attachment_ids, |
| | | ]; |
| | | } |
| | | } |
| | | |
| | | return $created_posts; |
| | | } |
| | | |
| | | /** |
| | | * Attach file to post based on file type |
| | | */ |
| | | protected function attachFileToPost(int $post_id, int $attachment_id, array $data): void |
| | | { |
| | | $attachment = get_post($attachment_id); |
| | | $mime_type = $attachment->post_mime_type; |
| | | |
| | | // Determine file type |
| | | if (str_starts_with($mime_type, 'image/')) { |
| | | // Set as featured image |
| | | set_post_thumbnail($post_id, $attachment_id); |
| | | } elseif (str_starts_with($mime_type, 'video/')) { |
| | | // Save to video field |
| | | $meta = Meta::forPost($post_id); |
| | | $meta->set('video', $attachment_id); |
| | | } else { |
| | | // Documents - save to documents field |
| | | $meta = Meta::forPost($post_id); |
| | | $existing = $meta->get('documents'); |
| | | $existing_ids = !empty($existing) ? explode(',', $existing) : []; |
| | | $existing_ids[] = $attachment_id; |
| | | $meta->set('documents', implode(',', $existing_ids)); |
| | | } |
| | | } |
| | | } |