| | |
| | | { |
| | | $this->cache_name = 'user_content_'.get_current_user_id(); |
| | | parent::__construct(); |
| | | |
| | | $this->cache->clear(); |
| | | $this->action = 'dash-'; |
| | | $this->operation_type = 'content_update'; |
| | | add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3); |
| | |
| | | $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){ |
| | | $this->timelineSharedFields = $this->getTimelineSharedFields($content); |
| | | array_unshift($this->timelineSharedFields, 'post_thumbnail'); |
| | | array_unshift($this->timelineSharedFields, 'post_title'); |
| | | array_unshift($this->timelineSharedFields, 'post_status'); |
| | | |
| | | $this->timelineUniqueFields = $this->getTimelineUniqueFields($content); |
| | | } |
| | | public function getTimelineUniqueFields(string $content):array |
| | | { |
| | | $content = jvbNoBase($content); |
| | | if (!Features::forContent($content)->has('is_timeline')){ |
| | | return []; |
| | | } |
| | | $config = Features::getConfig($content); |
| | | $allFields = $config['fields']; |
| | | |
| | | return array_keys(array_filter($allFields, function ($field) { |
| | | if (array_key_exists('for_all', $field) && $field['for_all'] === true) { |
| | | 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) { |
| | | public function getTimelineSharedFields(string $content):array |
| | | { |
| | | $content = jvbNoBase($content); |
| | | if (!Features::forContent($content)->has('is_timeline')){ |
| | | return []; |
| | | } |
| | | $config = Features::getConfig($content); |
| | | if (!$config || empty($config)) { |
| | | return []; |
| | | } |
| | | $allFields = $config['fields']??[]; |
| | | |
| | | return array_keys(array_filter($allFields, function ($field) { |
| | | if (!array_key_exists('for_all', $field) || $field['for_all'] === false){ |
| | | return true; |
| | | } |
| | | return false; |
| | |
| | | public function handleContentRequest(WP_REST_Request $request):WP_REST_Response |
| | | { |
| | | $params = $request->get_params(); |
| | | error_log('handleContentRequest params: '.print_r($params, true)); |
| | | |
| | | error_log('Fetching content. Params: '.print_r($params, true)); |
| | | $user_id = $params['user']; |
| | | if (!$this->userCheck($user_id)) { |
| | | return new WP_REST_Response([ |
| | |
| | | if (Features::forContent($post_type)->has('is_calendar')) { |
| | | $args = $this->applyCalendarFilters($args, $params); |
| | | } |
| | | $taxonomies = array_filter($params, function($param) { |
| | | return str_starts_with($param, 'tax_'); |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | if (!empty($taxonomies)) { |
| | | $params['taxonomies'] = []; |
| | | foreach ($taxonomies as $taxonomy => $terms) { |
| | | $taxonomy = str_replace('tax_', '', $taxonomy); |
| | | $params['taxonomies'][$taxonomy] = $terms; |
| | | } |
| | | } |
| | | if (array_key_exists('taxonomies', $params)) { |
| | | $args = $this->applyTaxonomyFilters($args, $params); |
| | | } |
| | |
| | | $args['s'] = sanitize_text_field($params['search']); |
| | | } |
| | | |
| | | |
| | | |
| | | error_log('Content Routes final args: '.print_r($args, true)); |
| | | |
| | | $key = $this->cache->generateKey($args); |
| | | // Check HTTP cache headers with the specific content type |
| | | $content_type = $params['content'] ?? $params['type']; |
| | |
| | | |
| | | |
| | | $cache = $this->cache->get($key); |
| | | $cache = false; |
| | | if ($cache) { |
| | | $response = new WP_REST_Response($cache); |
| | | return $this->addCacheHeaders($response); |
| | |
| | | $results = []; |
| | | |
| | | foreach ($posts as $ID => $post_data) { |
| | | if (Features::forContent($post_data['content'])->has('is_timeline')) { |
| | | if (Features::forContent($post_data['content'])->has('is_timeline') && array_key_exists('timeline', $post_data)) { |
| | | $results[$ID] =$this->processTimelinePost($ID, $post_data); |
| | | continue; |
| | | } |
| | |
| | | error_log('Allowed Fields: '.print_r($allowedFields, true)); |
| | | $meta = new MetaManager($ID, 'post'); |
| | | $success = $meta->setAll($allowedFields); |
| | | error_log('Should be set?'); |
| | | $results[$ID] = [ |
| | | 'success' => $success |
| | | ]; |
| | |
| | | return ['success' => false, 'message' => 'No permission']; |
| | | } |
| | | |
| | | $ignore = ['content', 'user']; |
| | | $this->fields = jvbGetFields($post_data['content']); |
| | | $this->initTimelineFields($post_data['content']); |
| | | error_log('Received Data: '.print_r($post_data, true)); |
| | | |
| | | // First, process the main fields that will apply to all posts |
| | | $sharedData = array_filter($post_data, function ($key) { |
| | | return in_array($key, $this->timelineSharedFields); |
| | | // Get parent post details |
| | | $parent_post = get_post($parent_id); |
| | | $parent_title = $parent_post->post_title; |
| | | $parent_is_published = ($parent_post->post_status === 'publish'); |
| | | |
| | | // Extract shared data from top level (excluding post_thumbnail which is unique per post) |
| | | $sharedData = array_filter($post_data, function ($key) use ($ignore) { |
| | | return in_array($key, $this->timelineSharedFields) |
| | | && !in_array($key, $ignore) |
| | | && $key !== 'post_thumbnail'; |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | |
| | | //Next, process any individual posts, including any menu order changes |
| | | // If no shared post_title at top level, extract from first timeline entry |
| | | if (!isset($sharedData['post_title']) && isset($post_data['timeline'][0]['post_title'])) { |
| | | $sharedData['post_title'] = $post_data['timeline'][0]['post_title']; |
| | | } |
| | | $clearParent = false; |
| | | if (array_key_exists('timeline', $post_data) && is_array($post_data['timeline'])) { |
| | | $sharedTaxonomies = $sharedData; |
| | | unset($sharedTaxonomies['post_title']); |
| | | // Remove post_title and post_thumbnail from shared taxonomies |
| | | $sharedTaxonomies = array_filter($sharedData, function($key) { |
| | | return $key !== 'post_title' && $key !== 'post_thumbnail'; |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | |
| | | //Ensure the parent post exists and is still first in the array |
| | | // 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' |
| | | '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); |
| | | } |
| | | |
| | | if ($index !== 0) { |
| | | $new_parent_id = $post_data['timeline'][0]['id']; |
| | | |
| | | if (is_numeric($new_parent_id) && (int)$new_parent_id > 0) { |
| | | $new_parent_id = (int)$new_parent_id; |
| | | wp_update_post([ |
| | | 'ID' => $new_parent_id, |
| | | 'post_parent' => 0 |
| | | ]); |
| | | |
| | | wp_update_post([ |
| | | 'ID' => $parent_id, |
| | | 'post_parent' => $new_parent_id |
| | | ]); |
| | | |
| | | $existing_children = get_children([ |
| | | 'post_parent' => $parent_id, |
| | | 'fields' => 'ids' |
| | | ]); |
| | | |
| | | foreach ($existing_children as $child_id) { |
| | | if ($child_id !== $new_parent_id) { |
| | | wp_update_post([ |
| | | 'ID' => $child_id, |
| | | 'post_parent' => $new_parent_id |
| | | ]); |
| | | } |
| | | } |
| | | |
| | | // Update parent references |
| | | $parent_id = $new_parent_id; |
| | | $parent_post = get_post($parent_id); |
| | | $parent_title = $parent_post->post_title; |
| | | $parent_is_published = ($parent_post->post_status === 'publish'); |
| | | } else { |
| | | $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' |
| | | 'orderby' => 'menu_order', |
| | | 'post_status' => ['publish', 'draft'], |
| | | 'fields'=> 'ids' |
| | | ]); |
| | | //Iterate through the timeline posts |
| | | |
| | | $prevDate = null; |
| | | |
| | | foreach($post_data['timeline'] as $order => $timeline) { |
| | | $allowedFields = array_filter($timeline, function($key) { |
| | | return in_array($key, $this->timelineUniqueFields); |
| | | // Get unique fields for this specific timeline entry |
| | | $allowedFields = array_filter($timeline, function($key) use ($ignore) { |
| | | return in_array($key, $this->timelineUniqueFields) && !in_array($key, $ignore); |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | |
| | | $allowedFields['post_title'] = $allowedFields['post_title'] ?? $sharedData['post_title'].' - Treatment #'.$order; |
| | | $allowedFields = array_merge($allowedFields, $sharedTaxonomies); |
| | | // Determine the post title |
| | | $is_parent = ((int)$timeline['id'] === $parent_id); |
| | | $provided_title = $timeline['post_title'] ?? ''; |
| | | $auto_generated_pattern = '/^.+Treatment #?\d+$/'; // Matches "Title - Treatment #1" or "Title - Treatment 1" |
| | | |
| | | if ($is_parent) { |
| | | // Parent keeps its own title or uses shared title |
| | | $allowedFields['post_title'] = $provided_title ?: ($sharedData['post_title'] ?? $parent_title); |
| | | } else { |
| | | // For child posts, auto-generate if: |
| | | // 1. No title provided, OR |
| | | // 2. Title matches auto-generated pattern (meaning it wasn't customized) |
| | | if (empty($provided_title) || preg_match($auto_generated_pattern, $provided_title)) { |
| | | $allowedFields['post_title'] = 'Treatment ' . $order; |
| | | } else { |
| | | // Keep custom title |
| | | $allowedFields['post_title'] = $provided_title; |
| | | } |
| | | } |
| | | |
| | | // Merge with shared taxonomies AFTER setting unique fields |
| | | $allowedFields = array_merge($sharedTaxonomies, $allowedFields); |
| | | |
| | | // Handle post creation if needed |
| | | 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 |
| | | 'post_author' => $this->user_id, |
| | | 'post_type' => jvbCheckBase($post_data['content']), |
| | | 'post_title' => $allowedFields['post_title'], |
| | | 'post_parent' => $parent_id, |
| | | 'menu_order' => $order, |
| | | 'post_status' => $parent_is_published ? 'publish' : 'draft' |
| | | ]); |
| | | if (!$newChild || is_wp_error($newChild)) { |
| | | $errors[] = [ |
| | | 'message' => 'Could not create child post', |
| | | 'data' => $timeline |
| | | 'message' => 'Could not create child post', |
| | | 'data' => $timeline |
| | | ]; |
| | | continue; |
| | | } |
| | |
| | | unset($existing_children[array_search((int)$timeline['id'], $existing_children)]); |
| | | } |
| | | |
| | | //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 |
| | | ]; |
| | | // Update post status and menu order |
| | | $post_updates = ['ID' => $timeline['id']]; |
| | | |
| | | if (!$is_parent) { |
| | | $post_updates['menu_order'] = $order; |
| | | |
| | | // Auto-publish child if parent is published |
| | | if ($parent_is_published) { |
| | | $current_post = get_post($timeline['id']); |
| | | if ($current_post && $current_post->post_status !== 'publish') { |
| | | $post_updates['post_status'] = 'publish'; |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (count($post_updates) > 1) { |
| | | $result = wp_update_post($post_updates); |
| | | error_log('Updated post '.$timeline['id'].' with: '.print_r($post_updates, true).' Result: '.$result); |
| | | $clearParent = true; |
| | | } |
| | | |
| | | // Update metadata |
| | | $meta = new MetaManager($timeline['id'], 'post'); |
| | | $oldValues = $meta->getAll(array_keys($allowedFields)); |
| | | |
| | | // Set number taxonomy to menu_order (always update for reordering) |
| | | if (!$is_parent) { |
| | | $number_value = $order; |
| | | $term = get_term_by('name', (string)$number_value, BASE.'number'); |
| | | if (!$term) { |
| | | $result = wp_insert_term((string)$number_value, BASE.'number'); |
| | | if ($result && !is_wp_error($result)) { |
| | | $term = $result['term_id']; |
| | | } |
| | | } else { |
| | | $term = $term->term_id; |
| | | } |
| | | $allowedFields['number'] = $term; |
| | | } |
| | | |
| | | // Auto-timeline logic |
| | | if ($prevDate) { |
| | | $newDate = array_key_exists('date', $oldValues) ? $oldValues['date'] : ((array_key_exists('date', $allowedFields)) ? $allowedFields['date'] : null); |
| | | if ($newDate) { |
| | | $date1 = new \DateTime($prevDate); |
| | | $date2 = new \DateTime($newDate); |
| | | $weeks = floor($date1->diff($date2)->days / 7); |
| | | if ($weeks > 0) { |
| | | $termToCheck = $weeks.' Weeks'; |
| | | $term = get_term_by('name', $termToCheck, BASE.'timeline'); |
| | | if (!$term) { |
| | | $result = wp_insert_term($termToCheck, BASE.'timeline'); |
| | | if ($result && !is_wp_error($result)) { |
| | | $term = $result['term_id']; |
| | | } |
| | | } else { |
| | | $term = $term->term_id; |
| | | } |
| | | $allowedFields['timeline'] = $term; |
| | | } |
| | | } |
| | | } |
| | | $prevDate = array_key_exists('date', $oldValues) ? $oldValues['date'] : ((array_key_exists('date', $allowedFields)) ? $allowedFields['date'] : $prevDate); |
| | | |
| | | $updateValues = array_filter($allowedFields, function($value, $key) use ($oldValues) { |
| | | return (!array_key_exists($key, $oldValues) || $value !== $oldValues[$key]); |
| | | }, ARRAY_FILTER_USE_BOTH); |
| | | error_log('Setting values for '.$timeline['id'].': '.print_r($updateValues, true)); |
| | | |
| | | $meta->setAll($updateValues); |
| | | $timeline['id'] = (int) $timeline['id']; |
| | | |
| | | $success[] = $timeline['id']; |
| | | } |
| | | } |
| | | //Delete any remaining children that no longer exist |
| | | |
| | | // Delete any remaining children that no longer exist |
| | | if (!empty($existing_children)) { |
| | | foreach ($existing_children as $ID) { |
| | | wp_delete_post($ID); |
| | | } |
| | | } |
| | | |
| | | if ($clearParent) { |
| | | $this->cache->clear(); |
| | | CacheManager::onPostSave($parent_id, $parent_post); |
| | | } |
| | | |
| | | |
| | | return ['success' => true, 'data' => [ |
| | | 'success' => $success, |
| | | 'errors' => $errors |
| | | 'success' => $success, |
| | | 'errors' => $errors |
| | | ]]; |
| | | } |
| | | |
| | |
| | | $this->meta = new MetaManager($post->ID, 'post'); |
| | | $data = [ |
| | | 'id' => $post->ID, |
| | | 'title' => $post->post_title, |
| | | 'status' => $post->post_status, |
| | | 'date' => $post->post_date, |
| | | 'modified' => $post->post_modified, |
| | |
| | | return $images; |
| | | } |
| | | |
| | | protected function formatTimeline(WP_Post $post):array |
| | | public function formatTimeline(WP_Post $post):array |
| | | { |
| | | $item = $this->prepareItem($post, true, false); |
| | | //Step 1: Get the fields that apply to all posts |
| | |
| | | $item['fields'] = $mainMeta->getAll($this->timelineSharedFields); |
| | | |
| | | //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']); |
| | | $children = get_children(['post_parent' => $post->ID, 'orderby' => 'date', 'order' => 'ASC', 'post_status' => ['publish', 'draft'], 'fields'=> 'ids']); |
| | | array_unshift($children, $post->ID); |
| | | |
| | | $subFields = []; |
| | |
| | | $meta = new MetaManager($child, 'post'); |
| | | $f = $meta->getAll($this->timelineUniqueFields); |
| | | $f = ['id' => $child] + $f; |
| | | $subFields[$f['post_thumbnail']] = $f; |
| | | $subFields[] = $f; |
| | | |
| | | $images[$f['post_thumbnail']] = jvbImageData((int) $f['post_thumbnail']); |
| | | } |