From 2127b1bdd73ecd2423e443992da4b442f5a3c1a3 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Wed, 04 Feb 2026 21:19:25 +0000
Subject: [PATCH] =Major overhaul of MetaManager.php -> Meta.php and RestRouteManager.php -> Rest.php. Seems to work for JakeVan

---
 inc/meta/MetaManager.php |  397 ++++++++++++++++++++++++++++++++------------------------
 1 files changed, 229 insertions(+), 168 deletions(-)

diff --git a/inc/meta/MetaManager.php b/inc/meta/MetaManager.php
index a1d7dbc..4fe121f 100644
--- a/inc/meta/MetaManager.php
+++ b/inc/meta/MetaManager.php
@@ -1,7 +1,10 @@
 <?php
 namespace JVBase\meta;
 
+use DateTime;
 use Exception;
+use JVBase\utility\Features;
+use WP_Post;
 
 if (!defined('ABSPATH')) {
     exit; // Exit if accessed directly
@@ -10,13 +13,16 @@
 /**
  * Core meta management class
  */
+
+/**
+ * @deprecated Use Meta() now
+ */
 class MetaManager
 {
 	public MetaTypeManager $type_manager;
-	public MetaValidator $validator;
-	public MetaSanitizer $sanitizer;
-	public MetaRenderer $renderer;
-	public MetaForm $form;
+	public Validator $validator;
+	public Sanitizer $sanitizer;
+	public Render $renderer;
 	protected int|null $object_id;
 	public object|null $data;
 	protected array $fields =[];
@@ -25,61 +31,71 @@
 	protected string|null $object_type;
 	protected int $max_file_size = 5242880;
 	protected ?string $content = null;
+	protected bool $isTimeline = false;
+
+	protected ?string $baseKey = null;
 	protected \wpdb $wpdb;
-	protected array $postFields = [
-		'post_title',
-		'post_excerpt',
-		'post_content',
-		'post_date',
-		'post_status',
-		'post_modified',
-		'post_thumbnail'
+
+	protected array $wpDefaults = [
+		'post'	=> [
+			'post_title',
+			'post_excerpt',
+			'post_content',
+			'post_date',
+			'post_status',
+			'post_modified',
+			'post_thumbnail',
+			'menu_order'
+		],
+		'user'	=> [
+			'first_name',
+			'last_name',
+			'display_name',
+			'description',
+			'user_email',
+		],
+		'term'	=> [
+			'term_name',
+			'description'
+		]
 	];
 
-	protected array $userFields = [
-		'first_name',
-		'last_name',
-		'description',
-		'display_name',
-		'user_email',
-	];
-
-	protected array $termFields = [
-		'term_name',
-		'description'
-	];
-
-	public function __construct(?int $ID = null, ?string $type = null, ?string $content = null)
+	public function __construct(int|string|null $ID = null, ?string $type = null, ?string $content = null)
 	{
 		global $wpdb;
 		$this->wpdb = $wpdb;
-		$this->object_id = $ID;
-
+		$this->object_id = is_int($ID) ? $ID : null;
 		$this->object_type = $type;
 		if ($ID) {
 			switch ($type) {
 				case 'post':
 					$this->data = get_post((string)$ID);
+					$this->content = jvbNoBase($this->data->post_type);
+					$this->isTimeline = Features::forContent($this->content)->has('is_timeline');
 					break;
 				case 'term':
 					$this->data = get_term($ID);
+					$this->content = jvbNoBase($this->data->taxonomy);
 					break;
 				case 'user':
 				case 'integrations':
 					$this->data = get_user($ID);
+					$this->content = jvbUserRole($ID);
+					break;
+				case 'options':
+					$this->baseKey = $ID;
+					$this->data = null;
 					break;
 				default:
 					$this->data = null;
 					break;
 			}
 		}
-		$this->content = $content;
 
 		$this->type_manager = new MetaTypeManager();
-		$this->validator = new MetaValidator();
-		$this->sanitizer = new MetaSanitizer();
-		$this->renderer = new MetaRenderer();
-		$this->form = new MetaForm();
+		$this->validator = new Validator();
+		$this->sanitizer = new Sanitizer();
+		$this->renderer = new Render();
 	}
 
 	/**
@@ -110,32 +126,22 @@
 	public function getValue(string $name): mixed
 	{
 		//Get standard post fields first
-		switch ($name) {
-			case 'post_title':
-				return $this->data->post_title ?? '';
-			case 'post_excerpt':
-				return $this->data->post_excerpt ?? '';
-			case 'post_content':
-				return $this->data->post_content ?? '';
-			case 'featured_image':
-			case 'post_thumbnail':
-				return get_post_thumbnail_id($this->object_id);
-			case 'display_name':
-				if (is_null($this->data) || !$this->data->display_name) {
-					$user = get_userdata((int)get_post_meta($this->object_id, BASE . 'link', true));
-					return $user->display_name;
+		if (array_key_exists($this->object_type, $this->wpDefaults)) {
+			$defaults = $this->wpDefaults[$this->object_type];
+			if (in_array($name, $defaults)) {
+				if (in_array($name, ['featured_image', 'post_thumbnail'])) {
+					return get_post_thumbnail_id($this->object_id);
 				}
-				return $this->data->display_name ?? '';
-			case 'user_email':
-				if (is_null($this->data) || !$this->data->display_name) {
-					$user = get_userdata(get_post_meta($this->object_id, BASE . 'link', true));
-					return $user->user_email;
-				}
-				return $this->data->user_email ?? '';
-			case 'term_name':
-				return htmlspecialchars_decode($this->data->name);
+				return match ($this->object_type) {
+					'term' => $this->getTermField($name),
+					'post' => $this->getPostField($name),
+					'user' => $this->getUserField($name),
+					default => ''
+				};
+			}
 		}
 
+
 		$meta_key = BASE . $name;
 		switch ($this->object_type) {
 			case 'post':
@@ -146,12 +152,46 @@
 			case 'integrations':
 				return get_user_meta($this->object_id, $meta_key, true);
 			case 'options':
-				return get_option($meta_key);
+				$key = $this->baseKey
+					? BASE . $this->baseKey . '_' . $name
+					: BASE . $name;
+				return get_option($key);
 			default:
 				return '';
 		}
 	}
 
+	protected function getTermField(string $name): mixed
+	{
+		// WordPress handles entity decoding and filters
+		return match ($name) {
+			'term_name' => get_term_field('name', $this->object_id),
+			'description' => get_term_field('description', $this->object_id),
+			default => ''
+		};
+	}
+
+	protected function getPostField(string $name): mixed
+	{
+		return match ($name) {
+			'post_title' => get_the_title($this->object_id),
+			'post_excerpt' => get_the_excerpt($this->object_id),
+			'post_content' => get_post_field('post_content', $this->object_id),
+			default => $this->data->$name ?? ''
+		};
+	}
+
+	protected function getUserField(string $name): mixed
+	{
+		return match ($name) {
+			'display_name' => get_the_author_meta('display_name', $this->object_id),
+			'user_email' => get_the_author_meta('user_email', $this->object_id),
+			'first_name' => get_the_author_meta('first_name', $this->object_id),
+			'last_name' => get_the_author_meta('last_name', $this->object_id),
+			default => $this->data->$name ?? ''
+		};
+	}
+
 	/**
 	 * @param string $name
 	 *
@@ -223,7 +263,7 @@
 	 *
 	 * @return bool
 	 */
-	public function updateValue(string $name, mixed $value): bool
+	public function updateValue(string $name, mixed $value, bool $updatePost = true): bool
 	{
 		try {
 			// Get field definition
@@ -237,7 +277,6 @@
 			$field_config['name'] = $name;
 			// Validate value
 			if (!$this->validator->validate($value, $field_config)) {
-				error_log('Validation unsuccessful');
 				throw new Exception("Validation failed for {$name}");
 			}
 
@@ -247,93 +286,69 @@
 				return true;
 			}
 
-			switch ($name) {
-				case 'post_title':
+			if (array_key_exists($this->object_type, $this->wpDefaults)) {
+				$check = $this->wpDefaults[$this->object_type];
+				if (in_array($name, $check)) {
 					$ID = true;
-					if ($this->data->post_title !== $sanitized) {
-						$ID = wp_update_post([
-							'ID' => $this->object_id,
-							'post_title' => $sanitized
-						]);
+					if (in_array($name, ['featured_image', 'post_thumbnail'])) {
+						$old = get_post_thumbnail_id($this->object_id);
+						if ($old !== $sanitized) {
+							$ID = set_post_thumbnail($this->object_id, $sanitized);
+						}
+						return $ID !== false;
 					}
-					return ($ID !== 0);
-				case 'post_excerpt':
-					$ID = true;
-					if ($this->data->post_excerpt !== $sanitized) {
-						$ID = wp_update_post([
-							'ID' => $this->object_id,
-							'post_excerpt' => $sanitized
-						]);
-					}
-					return ($ID !== 0);
-				case 'post_content':
-					$ID = true;
-					if ($this->data->post_content !== $sanitized) {
-						$ID = wp_update_post([
-							'ID' => $this->object_id,
-							'post_content' => $sanitized
-						]);
-					}
-					return ($ID !== 0);
-				case 'featured_image':
-				case 'post_thumbnail':
-					$ID = true;
-					$old = get_post_thumbnail_id($this->object_id);
+					$old = $this->data->$name;
 					if ($old !== $sanitized) {
-						$ID = set_post_thumbnail($this->object_id, $sanitized);
-					}
-					return ($ID !== false);
-				case 'display_name':
-					$ID = true;
-					$object_id = $this->object_id;
-					$displayName = $this->data->display_name;
-					if (!$this->data->display_name) {
-						$user = get_userdata(get_post_meta($this->object_id, BASE . 'link', true));
-						$object_id = $user->ID;
-						$displayName = $user->display_name;
-					}
+						switch ($this->object_type) {
+							case 'post':
 
-					if ($displayName !== $sanitized) {
-						$ID = wp_update_user([
-							'ID' => $object_id,
-							'display_name' => $sanitized
-						]);
-						$link = get_user_meta($object_id, BASE . 'link', true);
-						wp_update_post([
-							'ID' => $link,
-							'post_title' => $sanitized,
-						]);
-					}
-					return (!is_wp_error($ID));
-				case 'user_email':
-					$ID = true;
+								$ID = jvb_update_post([
+									'ID' => $this->object_id,
+									$name => $sanitized
+								]);
+								break;
+							case 'term':
+								$data = [$name => $sanitized];
+								if ($name === 'term_name') {
+									$data['slug'] = sanitize_title($sanitized);
+								}
+								$ID = wp_update_term(
+									$this->data->term_id,
+									$this->data->taxonomy,
+									$data
+								);
+								break;
+							case 'user':
+								$ID = wp_update_user([
+									'ID'	=> $this->object_id,
+									$name => $sanitized
+								]);
+								if ($name === 'display_name') {
+									$link = get_user_meta($this->object_id, BASE.'link', true);
+									if ($link !== '') {
+										jvb_update_post([
+											'ID'	=> $link,
+											'post_title'	=> $sanitized
+										]);
+									}
+								}
 
-					$object_id = $this->object_id;
-					$email = $this->data->user_email;
-					if (!$this->data->display_name) {
-						$user = get_userdata(get_post_meta($this->object_id, BASE . 'link', true));
-						$object_id = $user->ID;
-						$email = $user->user_email;
+								break;
+						}
 					}
-					if ($email !== $sanitized) {
-						$ID = wp_update_user([
-							'ID' => $object_id,
-							'user_email' => $sanitized
-						]);
-					}
-					return (!is_wp_error($ID));
-				case 'term_name':
-					$ID = true;
-					$name = $this->data->name;
-					if ($name !== $sanitized) {
-						$ID = wp_update_term($this->data->term_id, $this->data->taxonomy, [
-							'name' => $sanitized,
-							'slug' => sanitize_title($sanitized)
-						]);
-					}
+					return $ID !== false;
+				}
 			}
+
+
 			if ($field_config['type'] == 'taxonomy' && (!array_key_exists('taxonomy_type', $field_config))) {
-				$set = wp_set_post_terms($this->object_id, $sanitized, jvbCheckBase($field_config['taxonomy']), false);
+				if (empty(trim($sanitized))) {
+					// Clear all terms when value is empty
+					wp_set_object_terms($this->object_id, [], jvbCheckBase($field_config['taxonomy']), false);
+				} else {
+					$term_ids = array_map('intval', array_filter(explode(',', $sanitized)));
+					wp_set_object_terms($this->object_id, $term_ids, jvbCheckBase($field_config['taxonomy']), false);
+				}
 			}
 			if ($field_config['type'] === 'location' && empty($sanitized)) {
 				$this->addMeta('has_map', false);
@@ -354,13 +369,22 @@
 					$result = update_user_meta($this->object_id, $meta_key, $sanitized);
 					break;
 				case 'options':
-					$result = update_option($meta_key, $sanitized);
+					$key = $this->baseKey
+						? BASE . $this->baseKey . '_' . $name
+						: BASE . $name;
+					return update_option($key, $sanitized);
 			}
 
 			if ($result === false) {
 				throw new Exception("Failed to update meta value for {$name}");
 			}
 
+			if ($updatePost && $this->object_type === 'post') {
+				//Flush the cache for this post.
+				jvb_update_post([
+					'ID'	=> $this->object_id,
+				]);
+			}
 			return true;
 
 		} catch (Exception $e) {
@@ -520,7 +544,8 @@
 			return [];
 		}
 
-		return jvbGetFields($type, $this->object_type);
+		$this->fields = jvbGetFields($type, $this->object_type);
+		return $this->fields;
 	}
 
 	protected function getObjectType(): string|false
@@ -667,11 +692,11 @@
 		$out = '';
         switch ($type) {
             case 'form':
-                $out =  $this->form->render($name, $value, $config, $showHidden, true);
+                $out =  Form::render($name, $value, $config);
 				$out = apply_filters('jvbRenderFormMeta', $out, $name, $config, $value, $this->getObjectType());
 				break;
             case 'render':
-                $out = $this->renderer->render($name, $value, $config, true);
+                $out = $this->renderer->render($name, $value, $config);
 				if (empty($out) && !$hideEmpty) {
 					$out = $this->getEmptyTemplate($config['type'], $name);
 				}
@@ -812,9 +837,7 @@
 				return '';
 		}
 
-		$out = apply_filters('jvbMetaTypeTemplate', $out, $type);
-
-		return $out;
+		return apply_filters('jvbMetaTypeTemplate', $out, $type);
 	}
 
 	public function getDefaultValue(string $type):mixed {
@@ -838,19 +861,7 @@
 			return [];
 		}
 
-		switch ($this->object_type) {
-			case 'user':
-				$check = $this->userFields;
-				break;
-			case 'term':
-				$check = $this->termFields;
-				break;
-			case 'post':
-				$check = $this->postFields;
-				break;
-			default:
-				$check = [];
-		}
+		$check = array_key_exists($this->object_type, $this->wpDefaults) ? $this->wpDefaults[$this->object_type] : [];
 
 		$setFields = array_intersect($check, $fields);
 		foreach ($setFields as $f) {
@@ -941,7 +952,7 @@
 		return false;
 	}
 
-	public function setAll(array $fields):bool
+	public function setAll(array $fields, bool $updatePost = true):bool
 	{
 		if (empty($fields) || !$this->object_type) {
 			return false;
@@ -951,23 +962,22 @@
 			return false;
 		}
 
+
 		// Determine table based on object type
+		$check = array_key_exists($this->object_type, $this->wpDefaults) ? $this->wpDefaults[$this->object_type] : [];
 		switch ($this->object_type) {
 			case 'post':
 				$table = $this->wpdb->postmeta;
 				$id_column = 'post_id';
-				$check = $this->postFields;
 				break;
 			case 'term':
 				$table = $this->wpdb->termmeta;
 				$id_column = 'term_id';
-				$check = $this->termFields;
 				break;
 			case 'user':
 			case 'integrations':
 				$table = $this->wpdb->usermeta;
 				$id_column = 'user_id';
-				$check = $this->userFields;
 				break;
 			case 'options':
 				try {
@@ -996,7 +1006,7 @@
 		$temp = [];
 		foreach ($setFields as $f) {
 			$temp[$f] = $fields[$f];
-			unset($fields[array_search($f, $fields)]);
+			unset($fields[$f]);
 		}
 		$setFields = $temp;
 
@@ -1012,12 +1022,18 @@
 					// Sanitize value
 					$sanitized = $this->sanitizer->sanitize($value, $field_config);
 					if ($this->checkOverrides($field, $sanitized, $field_config)) {
-						return true;
+						continue;
 					}
 
-					if ($field_config['type'] === 'taxonomy' && !array_key_exists('taxonomy_type', $field_config)){
-						$term_ids = array_map('intval', explode(',', trim($sanitized)));
-						$set = wp_set_post_terms($this->object_id, $term_ids, jvbCheckBase($field_config['taxonomy']), false);
+
+					if ($field_config['type'] == 'taxonomy' && (!array_key_exists('taxonomy_type', $field_config))) {
+						if (empty(trim($sanitized))) {
+							// Clear all terms when value is empty
+							wp_set_object_terms($this->object_id, [], jvbCheckBase($field_config['taxonomy']), false);
+						} else {
+							$term_ids = array_map('intval', array_filter(explode(',', $sanitized)));
+							wp_set_object_terms($this->object_id, $term_ids, jvbCheckBase($field_config['taxonomy']), false);
+						}
 					}
 
 					if ($field_config['type'] === 'location' && empty($sanitized)) {
@@ -1075,6 +1091,25 @@
 			}
 
 			if (!empty($setFields)) {
+				foreach ($setFields as $field => $value) {
+					$field_config = $this->getFieldConfig($field);
+					if ($field_config) {
+						$setFields[$field] = $this->sanitizer->sanitize($value, $field_config);
+					}
+
+					if ($field === 'post_date') {
+						$datetime = strtotime($setFields[$field]);
+						if ($datetime !== false) {
+							$setFields[$field] = date('Y-m-d H:i:s', $datetime);
+						} else {
+							$setFields[$field] = date('Y-m-d H:i:s', time());
+						}
+
+						$setFields['post_date_gmt'] = get_gmt_from_date($setFields[$field]);
+						$setFields['edit_date'] = true;
+					}
+				}
+
 				switch ($this->object_type) {
 					case 'post':
 						if (array_key_exists('post_thumbnail', $setFields)) {
@@ -1083,7 +1118,7 @@
 						}
 
 						if (!empty($setFields)) {
-							$result = wp_update_post(array_merge(['ID' => $this->object_id], $setFields), true);
+							$result = jvb_update_post(array_merge(['ID' => $this->object_id], $setFields));
 						}
 						break;
 					case 'user':
@@ -1094,6 +1129,9 @@
 						wp_update_term($this->object_id, $this->data->taxonomy, $setFields);
 						break;
 				}
+			} elseif ($updatePost && $this->object_type === 'post' && !empty($this->object_id)) {
+				//Update the 'post modified' date with meta updates, for filtering
+				jvb_update_post(['ID' => $this->object_id]);
 			}
 
 		} catch (Exception $e) {
@@ -1387,4 +1425,27 @@
 			}
 		}
 	}
+
+
+
+	private function getOrCreateTerm(string $termName, string $taxonomy):?int
+	{
+		$taxonomy = jvbCheckBase($taxonomy);
+		$term = get_term_by('name', $termName, $taxonomy);
+
+		if (!$term) {
+			$result = wp_insert_term($termName, $taxonomy);
+			if (is_wp_error($result)) {
+				return null;
+			}
+			$termID = $result['term_id'];
+		} else {
+			$termID = $term->term_id;
+		}
+
+		if ($termID) {
+			return $termID;
+		}
+		return null;
+	}
 }

--
Gitblit v1.10.0