From d38d825e3484d822ea3c1f0fb1df37ecf386b18a Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 04 Jan 2026 19:54:16 +0000
Subject: [PATCH] =TaxonomyCreator.js debugging

---
 inc/rest/routes/ContentRoutes.php |  322 +++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 248 insertions(+), 74 deletions(-)

diff --git a/inc/rest/routes/ContentRoutes.php b/inc/rest/routes/ContentRoutes.php
index dfa9cb6..3d455d5 100644
--- a/inc/rest/routes/ContentRoutes.php
+++ b/inc/rest/routes/ContentRoutes.php
@@ -36,7 +36,7 @@
     {
         $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);
@@ -88,17 +88,44 @@
 		$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;
@@ -198,9 +225,6 @@
     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([
@@ -237,6 +261,16 @@
 		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);
 		}
@@ -251,10 +285,6 @@
 			$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'];
@@ -267,7 +297,6 @@
 
 
         $cache = $this->cache->get($key);
-		$cache = false;
         if ($cache) {
             $response = new WP_REST_Response($cache);
 			return $this->addCacheHeaders($response);
@@ -339,8 +368,21 @@
         $results = [];
 
         foreach ($posts as $ID => $post_data) {
-			if (Features::forContent($post_data['content'])->has('is_timeline')) {
-				$results[$ID] =$this->processTimelinePost($ID, $post_data);
+			if (Features::forContent($post_data['content'])->has('is_timeline') && array_key_exists('timeline', $post_data)) {
+				// Handle timeline posts - ensure we have a valid integer ID
+				$parent_id = (int)$ID;
+
+				// Skip if ID is invalid (0, 'null', etc would become 0)
+				if ($parent_id === 0) {
+					error_log('Invalid timeline parent ID: ' . $ID);
+					$results[$ID] = [
+						'success' => false,
+						'message' => 'Invalid parent post ID for timeline'
+					];
+					continue;
+				}
+
+				$results[$ID] = $this->processTimelinePost($parent_id, $post_data);
 				continue;
 			}
 			if (str_starts_with($ID, 'new')) {
@@ -416,7 +458,6 @@
 				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
 				];
@@ -458,62 +499,138 @@
 			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;
 					}
@@ -524,42 +641,98 @@
 					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
 		]];
 	}
 
@@ -650,6 +823,7 @@
         $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,
@@ -711,7 +885,7 @@
 		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
@@ -719,7 +893,7 @@
 		$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 = [];
@@ -728,7 +902,7 @@
 			$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']);
 		}

--
Gitblit v1.10.0