| | |
| | | protected string $post_type = ''; |
| | | protected string $user_id = ''; |
| | | |
| | | //For Timeline-specific posts |
| | | protected array $timelineSharedFields = []; |
| | | protected array $timelineUniqueFields = []; |
| | | |
| | | //TODO: Ensure we are handling the bulk operations for all processes |
| | | //TODO: be sure to clear cache ($this->>cache->invalidateGroup($this->>cache_name)) on content update/create |
| | | //TODO: Also invalidate feed caches on updates!! |
| | | |
| | | public function __construct() |
| | | { |
| | | $this->cache_name = 'user_content_'.get_current_user_id(); |
| | | parent::__construct(); |
| | | |
| | | $this->action = 'dash-'; |
| | | $this->operation_type = 'content_update'; |
| | | add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3); |
| | |
| | | ]); |
| | | } |
| | | |
| | | protected function initTimelineFields(string $content):void |
| | | { |
| | | $content = jvbNoBase($content); |
| | | if (!Features::forContent($content)->has('is_timeline')){ |
| | | return; |
| | | } |
| | | $config = Features::getConfig($content); |
| | | $this->fields = $config['fields']; |
| | | |
| | | $this->timelineSharedFields = array_keys(array_filter($this->fields, function ($field) { |
| | | if (!array_key_exists('for_all', $field) || $field['for_all'] === false){ |
| | | return true; |
| | | } |
| | | return false; |
| | | })); |
| | | array_unshift($this->timelineSharedFields, 'post_thumbnail'); |
| | | array_unshift($this->timelineSharedFields, 'post_title'); |
| | | |
| | | $this->timelineUniqueFields = array_keys(array_filter($this->fields, function ($field) { |
| | | if (array_key_exists('for_all', $field) && $field['for_all'] === true) { |
| | | return true; |
| | | } |
| | | return false; |
| | | })); |
| | | } |
| | | |
| | | /** |
| | | * Handle content update/creation |
| | | * @param WP_REST_Request $request |
| | |
| | | } |
| | | $post_type = str_replace('-', '_',jvbCheckBase($params['content'])); |
| | | |
| | | $config = Features::getConfig($params['content']); |
| | | |
| | | |
| | | |
| | | // Build query args |
| | | $args = [ |
| | |
| | | |
| | | } |
| | | |
| | | CacheManager::invalidateGroup($post_data['content']); |
| | | CacheManager::for($post_data['content'])->clear(); |
| | | if (jvbSiteUsesFeedBlock()) { |
| | | CacheManager::invalidateGroup($post_data['feed']); |
| | | CacheManager::for('feed')->clear(); |
| | | } |
| | | } |
| | | |
| | | |
| | | CacheManager::invalidateGroup('user_content'); |
| | | if (jvbSiteHasNotifications()) { |
| | | $this->notifications = JVB()->notification(); |
| | | $this->notifications->addNotification( |
| | |
| | | return ['success' => false, 'message' => 'No permission']; |
| | | } |
| | | |
| | | $rows = $post_data['fields'] ?? []; |
| | | if (empty($rows)) { |
| | | return ['success' => false, 'message' => 'No data']; |
| | | } |
| | | $this->fields = jvbGetFields($post_data['content']); |
| | | $this->initTimelineFields($post_data['content']); |
| | | error_log('Received Data: '.print_r($post_data, true)); |
| | | |
| | | $fields = jvbGetFields($post_data['content']); |
| | | |
| | | // First row = parent post |
| | | $parent_row = array_shift($rows); |
| | | if (($parent_row['id'] ?? null) != $parent_id) { |
| | | return ['success' => false, 'message' => 'Parent ID mismatch']; |
| | | } |
| | | |
| | | $allowedFields = array_filter($parent_row, function($key) use ($fields) { |
| | | return array_key_exists($key, $fields); |
| | | // First, process the main fields that will apply to all posts |
| | | $sharedData = array_filter($post_data, function ($key) { |
| | | return in_array($key, $this->timelineSharedFields); |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | |
| | | $parentMeta = new MetaManager($parent_id, 'post'); |
| | | $parentMeta->setAll($allowedFields); |
| | | //Next, process any individual posts, including any menu order changes |
| | | if (array_key_exists('timeline', $post_data) && is_array($post_data['timeline'])) { |
| | | $sharedTaxonomies = $sharedData; |
| | | unset($sharedTaxonomies['post_title']); |
| | | |
| | | //Ensure the parent post exists and is still first in the array |
| | | $index = array_search((string)$parent_id, array_column($post_data['timeline'], 'id')); |
| | | error_log('Found index: '.print_r($index, true)); |
| | | if ($index === false) { |
| | | return [ |
| | | 'success' => false, |
| | | 'message' => 'Missing parent id. This should not have happened' |
| | | ]; |
| | | } elseif ($index !== 0) { |
| | | // Move that element to the start of the array |
| | | $item = $post_data['timeline'][$index]; |
| | | unset($post_data['timeline'][$index]); |
| | | array_unshift($post_data['timeline'], $item); |
| | | } |
| | | $errors = []; |
| | | $success = []; |
| | | // Get existing children to track deletions |
| | | $existing_children = get_children([ |
| | | 'post_parent' => $parent_id, |
| | | 'post_type' => jvbCheckBase($post_data['content']), |
| | | 'fields' => 'ids' |
| | | ]); |
| | | |
| | | $processed_ids = []; |
| | | |
| | | // Process remaining rows as children |
| | | foreach ($rows as $index => $row_data) { |
| | | $row_id = $row_data['id'] ?? null; |
| | | |
| | | // New child post |
| | | if (!$row_id || str_starts_with($row_id, 'new')) { |
| | | $child_id = wp_insert_post([ |
| | | 'post_type' => jvbCheckBase($post_data['content']), |
| | | 'post_parent' => $parent_id, |
| | | 'post_author' => $this->user_id, |
| | | 'post_status' => $post_data['status'] ?? 'draft', |
| | | 'menu_order' => $index |
| | | ]); |
| | | } |
| | | // Existing child post |
| | | else { |
| | | $child_id = (int) $row_id; |
| | | |
| | | // Verify ownership via parent |
| | | if (!in_array($child_id, $existing_children)) { |
| | | continue; // Skip if not actually a child of this parent |
| | | } |
| | | |
| | | // Update menu_order (position may have changed) |
| | | wp_update_post([ |
| | | 'ID' => $child_id, |
| | | 'menu_order' => $index |
| | | ]); |
| | | } |
| | | |
| | | // Update child meta |
| | | $allowedChildFields = array_filter($row_data, function($key) use ($fields) { |
| | | return array_key_exists($key, $fields) && $key !== 'id' && $key !== 'draggable'; |
| | | //Iterate through the timeline posts |
| | | foreach($post_data['timeline'] as $order => $timeline) { |
| | | $allowedFields = array_filter($timeline, function($key) { |
| | | return in_array($key, $this->timelineUniqueFields); |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | |
| | | $childMeta = new MetaManager($child_id, 'post'); |
| | | $childMeta->setAll($allowedChildFields); |
| | | |
| | | $processed_ids[] = $child_id; |
| | | $allowedFields['post_title'] = $allowedFields['post_title'] ?? $sharedData['post_title'].' - Treatment #'.$order; |
| | | $allowedFields = array_merge($allowedFields, $sharedTaxonomies); |
| | | if (!array_key_exists('id', $timeline) || !is_numeric($timeline['id'])) { |
| | | $newChild = wp_insert_post([ |
| | | 'post_author' => $this->user_id, |
| | | 'post_type' => jvbCheckBase($post_data['content']), |
| | | 'post_title' => $allowedFields['post_title'], |
| | | 'post_parent' => $parent_id, |
| | | 'menu_order' => $order |
| | | ]); |
| | | if (!$newChild || is_wp_error($newChild)) { |
| | | $errors[] = [ |
| | | 'message' => 'Could not create child post', |
| | | 'data' => $timeline |
| | | ]; |
| | | continue; |
| | | } |
| | | $timeline['id'] = $newChild; |
| | | } |
| | | |
| | | // Delete removed children |
| | | $deleted_ids = array_diff($existing_children, $processed_ids); |
| | | foreach ($deleted_ids as $delete_id) { |
| | | wp_delete_post($delete_id, true); |
| | | if (in_array((int)$timeline['id'], $existing_children)) { |
| | | unset($existing_children[array_search((int)$timeline['id'], $existing_children)]); |
| | | } |
| | | |
| | | return ['success' => true, 'processed' => $processed_ids]; |
| | | //Determine which fields to update |
| | | $meta = new MetaManager($timeline['id'], 'post'); |
| | | $oldValues = $meta->getAll(array_keys($allowedFields)); |
| | | $updateValues = array_filter($allowedFields, function($value, $key) use ($oldValues) { |
| | | return (!array_key_exists($key, $oldValues) || $value !== $oldValues[$key]); |
| | | }, ARRAY_FILTER_USE_BOTH); |
| | | $meta->setAll($updateValues); |
| | | //Update Menu Order, if applicable |
| | | if ((int) $timeline['id'] !== $parent_id) { |
| | | $post = get_post((int) $timeline['id']); |
| | | if ($post && $post->menu_order !== $order) { |
| | | $updated = wp_update_post([ |
| | | 'ID' => $post->ID, |
| | | 'menu_order' => $order, |
| | | ]); |
| | | if (!$updated || is_wp_error($updated)) { |
| | | $errors[] = [ |
| | | 'message' => 'Could not update timeline order for post', |
| | | 'data' => $timeline |
| | | ]; |
| | | } |
| | | } |
| | | } |
| | | $success[] = $timeline['id']; |
| | | } |
| | | } |
| | | //Delete any remaining children that no longer exist |
| | | if (!empty($existing_children)) { |
| | | foreach ($existing_children as $ID) { |
| | | wp_delete_post($ID); |
| | | } |
| | | } |
| | | |
| | | return ['success' => true, 'data' => [ |
| | | 'success' => $success, |
| | | 'errors' => $errors |
| | | ]]; |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @return array |
| | | */ |
| | | protected function prepareItem(WP_Post $post, $skip = false):array |
| | | protected function prepareItem(WP_Post $post, bool $skip = false, bool $fields = true):array |
| | | { |
| | | if (!$skip && Features::forContent($post->post_type)->has('is_timeline')) { |
| | | $this->initTimelineFields($post->post_type); |
| | | return $this->formatTimeline($post); |
| | | } |
| | | $this->meta = new MetaManager($post->ID, 'post'); |
| | |
| | | 'alt' => get_post_meta(get_post_thumbnail_id(), '_wp_attachment_image_alt', true), |
| | | 'icon' => $this->post_type, |
| | | 'taxonomies'=> [], |
| | | 'fields' => $this->meta->getAll(), |
| | | 'fields' => ($fields) ? $this->meta->getAll() : [], |
| | | 'images' => [], |
| | | ]; |
| | | |
| | |
| | | |
| | | return $data; |
| | | } |
| | | protected function extractImages():array |
| | | protected function extractImages(array $fields = []):array |
| | | { |
| | | //Extract images |
| | | $images = []; |
| | | $get = []; |
| | | foreach ($this->fields as $field => $config) { |
| | | $fields = (empty($fields)) ? $this->fields : $fields; |
| | | foreach ($fields as $field => $config) { |
| | | if ($config['type'] === 'gallery' || $config['type'] === 'image' || $field === 'post_thumbnail') { |
| | | $get[] = $field; |
| | | } |
| | |
| | | |
| | | protected function formatTimeline(WP_Post $post):array |
| | | { |
| | | $data = $this->prepareItem($post, true); |
| | | $firstRow = $data['fields']; |
| | | $firstRow['id'] = $post->ID; |
| | | $firstRow['draggable'] = false; |
| | | $fields = [$firstRow]; |
| | | $item = $this->prepareItem($post, true, false); |
| | | //Step 1: Get the fields that apply to all posts |
| | | $mainMeta = new MetaManager($post->ID, 'post'); |
| | | $item['fields'] = $mainMeta->getAll($this->timelineSharedFields); |
| | | |
| | | $children = get_children(['post_parent' => $post->ID, 'orderby' => 'menu_order']); |
| | | $allImages = []; |
| | | //Step 2: Get the fields for each individual posts |
| | | $children = get_children(['post_parent' => $post->ID, 'orderby' => 'menu_order', 'post_status' => ['publish', 'draft'], 'fields'=> 'ids']); |
| | | array_unshift($children, $post->ID); |
| | | |
| | | $subFields = []; |
| | | $images = []; |
| | | foreach ($children as $child) { |
| | | $this->meta = new MetaManager($child->ID, 'post'); |
| | | $row = $this->meta->getAll(); // Store in variable first |
| | | $row['id'] = $child->ID; // Add ID to the row |
| | | $row['draggable'] = true; // Mark as draggable |
| | | $fields[] = $row; // Then append to fields |
| | | $meta = new MetaManager($child, 'post'); |
| | | $f = $meta->getAll($this->timelineUniqueFields); |
| | | $f = ['id' => $child] + $f; |
| | | $subFields[$f['post_thumbnail']] = $f; |
| | | |
| | | $images = $this->extractImages(); |
| | | if (!empty($images)) { |
| | | $allImages = $allImages + $images; |
| | | $images[$f['post_thumbnail']] = jvbImageData((int) $f['post_thumbnail']); |
| | | } |
| | | } |
| | | $item['fields']['timeline'] = $subFields; |
| | | $item['images'] = $item['images'] + $images; |
| | | |
| | | if (!empty($allImages)) { |
| | | if (!array_key_exists('images', $data)) { |
| | | $data['images'] = []; |
| | | } |
| | | $data['images'] = $data['images'] + $allImages; |
| | | } |
| | | $data['fields']['timeline'] = $fields; |
| | | return $data; |
| | | return $item; |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | //Clear cache |
| | | CacheManager::invalidateGroup($data['content']); |
| | | CacheManager::invalidateGroup('feed'); |
| | | CacheManager::invalidateGroup('user_content'); |
| | | CacheManager::for($data['content'])->clear(); |
| | | CacheManager::for('feed')->clear(); |
| | | } |
| | | |
| | | return [ |