From 3d25ad7de4fdbb3eba2cde2a62422e6be5d79a0a Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 25 Aug 2026 22:57:22 +0000
Subject: [PATCH] =It's been a minute. I went through the Routes and associated Managers and ensured they played together well. Reworked the Cache.php, and I think I caught all the references. Moving away from JVBase() root class, to individuall classes that are Instanced. Double checked for the ABSPATH check in every file, and added index.php to every directory that didn't already have it. Also still working on the MenuBlock to work with WooCommerce for now for Northeh

---
 inc/meta/Meta.php |  802 ++++++++++++++++++++++++++++----------------------------
 1 files changed, 405 insertions(+), 397 deletions(-)

diff --git a/inc/meta/Meta.php b/inc/meta/Meta.php
index 8984345..1d34549 100644
--- a/inc/meta/Meta.php
+++ b/inc/meta/Meta.php
@@ -1,50 +1,50 @@
 <?php
 namespace JVBase\meta;
 
+use JVBase\base\Options;
 use JVBase\registrar\Registrar;
+use WP_Post;
+use WP_Term;
+use WP_User;
 
 if (!defined('ABSPATH')) {
 	exit;
 }
 
-/**
- * Main facade for meta operations
- * Fluent API for getting/setting meta values with validation & sanitization
- *
- * Usage:
- *   $meta = Meta::forPost($id);
- *   $meta->price = 150;
- *   $meta->save();
- *
- *   Meta::forPost($id)->set('price', 150)->set('style', 'traditional')->save();
- */
 class Meta
 {
+	/**
+	 * @var string post, term, user, or options
+	 */
+	protected string $type;
+	/**
+	 * @var ?string the full slug, with BASE
+	 */
+	protected ?string $slug;
+
+	protected string $contentType;
 	protected Item $item;
 	protected Storage $storage;
 	protected Validator $validator;
 	protected Sanitizer $sanitizer;
+	protected array $fields;
+	protected WP_Post|WP_Term|WP_User|false|null $wpObject;
+	protected int|string $ID;
 	protected MetaTypeManager $typeManager;
+	protected static array $instances = ['post' => [],'term' => [], 'user'=>[],'option'=>[]];
 
-	protected bool $autoValidate = true;
-	protected bool $autoSanitize = true;
-
-	/** @var array<string, callable[]> */
-	protected array $onChangeCallbacks = [];
-
-	/** @var array<string, callable> */
-	protected array $computed = [];
-
-	// ─────────────────────────────────────────────────────────────
-	// Factory Methods
-	// ─────────────────────────────────────────────────────────────
-
+	protected array $defaults = ['post_thumbnail'];
 	/**
 	 * Create Meta instance for a post
 	 */
 	public static function forPost(int $id): self
 	{
-		return new self($id, 'post');
+		if (array_key_exists($id, self::$instances['post'])) {
+			return self::$instances['post'][$id];
+		}
+		$new = new self($id, 'post');
+		self::$instances['post'][$id] = $new;
+		return $new;
 	}
 
 	/**
@@ -52,7 +52,12 @@
 	 */
 	public static function forTerm(int $id): self
 	{
-		return new self($id, 'term');
+		if (array_key_exists($id, self::$instances['term'])) {
+			return self::$instances['term'][$id];
+		}
+		$new = new self($id, 'term');
+		self::$instances['term'][$id] = $new;
+		return $new;
 	}
 
 	/**
@@ -60,194 +65,231 @@
 	 */
 	public static function forUser(int $id): self
 	{
-		return new self($id, 'user');
+		if (array_key_exists($id, self::$instances['user'])) {
+			return self::$instances['user'][$id];
+		}
+		$new = new self($id, 'user');
+		self::$instances['user'][$id] = $new;
+		return $new;
 	}
 
 	/**
 	 * Create Meta instance for options
 	 */
-	public static function forOptions(?string $baseKey = null): self
+	public static function forOptions(?string $baseKey = BASE): self
 	{
-		$instance = new self($baseKey, 'options');
-		$instance->item->baseKey = $baseKey;
-		return $instance;
+		if (array_key_exists($baseKey, self::$instances['option'])) {
+			return self::$instances['option'][$baseKey];
+		}
+		$new = new self($baseKey, 'option');
+		self::$instances['option'][$baseKey] = $new;
+		return $new;
 	}
+	/***************************************************************
+	 * Constructor
+	***************************************************************/
 
-	/**
-	 * Bulk load multiple posts with optional field preloading
-	 * @return array<int, Meta>
-	 */
-	public static function bulkForPosts(array $ids, array $preloadFields = []): array
+	public function __construct(int|string|null $id, string $type, array $fields = [])
 	{
-		return self::bulkFor($ids, 'post', $preloadFields);
-	}
-
-	/**
-	 * Bulk load multiple terms with optional field preloading
-	 * @return array<int, Meta>
-	 */
-	public static function bulkForTerms(array $ids, array $preloadFields = []): array
-	{
-		return self::bulkFor($ids, 'term', $preloadFields);
-	}
-
-	/**
-	 * Bulk load multiple users with optional field preloading
-	 * @return array<int, Meta>
-	 */
-	public static function bulkForUsers(array $ids, array $preloadFields = []): array
-	{
-		return self::bulkFor($ids, 'user', $preloadFields);
-	}
-
-	/**
-	 * Generic bulk loader
-	 * @return array<int, Meta>
-	 */
-	protected static function bulkFor(array $ids, string $type, array $preloadFields = []): array
-	{
-		if (empty($ids)) {
-			return [];
-		}
-
-		$metas = [];
-
-		// Create instances
-		foreach ($ids as $id) {
-			$metas[$id] = new self($id, $type);
-		}
-
-		// Preload fields if specified
-		if (!empty($preloadFields)) {
-			self::bulkPreload($metas, $type, $preloadFields);
-		}
-
-		return $metas;
-	}
-
-	/**
-	 * Bulk preload fields for multiple Meta instances
-	 * @param Meta[] $metas
-	 */
-	protected static function bulkPreload(array $metas, string $objectType, array $fields): void
-	{
-		if (empty($metas) || empty($fields)) {
-			return;
-		}
-
-		$ids = array_keys($metas);
-		$values = Storage::getBulkValues($ids, $objectType, $fields);
-
-		// Distribute results to Meta instances
-		foreach ($values as $id => $fieldValues) {
-			if (!isset($metas[$id])) {
-				continue;
-			}
-
-			$meta = $metas[$id];
-			foreach ($fieldValues as $name => $value) {
-				$config = $meta->config($name) ?? ['type' => 'text'];
-				$field = new Field($name, $value, $config);
-				$meta->item()->setField($field);
-			}
-		}
-	}
-
-	/**
-	 * Save multiple Meta instances efficiently
-	 * @param Meta[] $metas
-	 * @return array<int, bool>
-	 */
-	public static function saveBulk(array $metas, bool $updateTimestamp = true): array
-	{
-		// Validate all first
-		$invalid = [];
-		foreach ($metas as $id => $meta) {
-			if (!$meta->isValid()) {
-				$invalid[$id] = $meta->getErrors();
-			}
-		}
-
-		if (!empty($invalid)) {
-			JVB()->error()->log('meta', 'Bulk save has validation errors', [
-				'invalid_items' => $invalid
-			], 'warning');
-		}
-
-		// Filter to only valid metas
-		$validMetas = array_filter($metas, fn($m) => $m->isValid());
-
-		// Check overrides before bulk save
-		foreach ($validMetas as $meta) {
-			foreach ($meta->item()->getDirtyFields() as $field) {
-				if ($meta->checkOverrides($field)) {
-					$field->markClean();
-				}
-			}
-		}
-
-		$results = Storage::saveBulk($validMetas, $updateTimestamp);
-
-		// Mark invalid ones as failed
-		foreach ($invalid as $id => $errors) {
-			$results[$id] = false;
-		}
-
-		return $results;
-	}
-
-	// ─────────────────────────────────────────────────────────────
-	// Constructor
-	// ─────────────────────────────────────────────────────────────
-
-	public function __construct(int|string|null $id, string $type)
-	{
-		$this->storage = new Storage();
 		$this->validator = new Validator();
 		$this->sanitizer = new Sanitizer();
 		$this->typeManager = new MetaTypeManager();
-
-		$this->item = $this->buildItem($id, $type);
+		$this->type = $type;
+		$this->ID = $id;
+		$this->buildData($id, $type, $fields);
 	}
 
-	protected function buildItem(int|string|null $id, string $type): Item
+	protected function buildData(int|string|null $id, string $type, array $fields):void
 	{
-		$contentType = null;
-		$wpObject = null;
+		$this->wpObject = match($type) {
+			'post' 	=> get_post($id),
+			'term'	=> get_term($id),
+			'user', 'integrations' => get_userdata($id),
+			default => false
+		};
 
-		if ($id && $type !== 'options') {
-			[$wpObject, $contentType] = match ($type) {
-				'post' => [get_post($id), jvbNoBase(get_post_type($id))],
-				'term' => [get_term($id), jvbNoBase(get_term($id)->taxonomy)],
-				'user', 'integrations' => [get_user_by('id', $id), jvbUserRole($id)],
-				default => [null, null]
-			};
+		$this->slug = match($type) {
+			'post'	=> $this->wpObject->post_type,
+			'term'	=> $this->wpObject->taxonomy,
+			'user'	=> jvbUserRole($id),
+			default => null
+		};
+
+		$registrar = !is_null($this->slug) ? Registrar::getInstance($this->slug) : false;
+		$fields = $registrar ? $registrar->getFields() : $fields;
+
+		if ($this->type == 'option') {
+			$options = Options::getInstance();
+			$fields = $options->getFields();
 		}
+		$meta = match($type) {
+			'post'	=> get_post_meta($id),
+			'term'	=> get_term_meta($id),
+			'user'	=> get_user_meta($id),
+			default => []
+		};
+		if (!$meta) {
+			$meta = [];
+		}
+		$meta = array_map(fn($value) => maybe_unserialize($value[0]), $meta);
 
-		$item = new Item($id, $type, $contentType);
-		$item->wpObject = $wpObject;
-		$item->fieldConfigs = $this->loadFieldConfigs($contentType, $type);
 
-		// Mark WP defaults in configs
-		$defaults = Item::WP_DEFAULTS[$type] ?? [];
-		foreach ($defaults as $name) {
-			if (!isset($item->fieldConfigs[$name])) {
-				$item->fieldConfigs[$name] = ['type' => 'text', '_wp_default' => true];
-			} else {
-				$item->fieldConfigs[$name]['_wp_default'] = true;
+		$this->fields = [];
+
+		if (class_exists('WooCommerce') && $this->type === 'post' && $this->wpObject->post_type??'' === 'product') {
+			$fields['post_title'] = [
+				'type'	=> 'text',
+				'label'	=> 'Title',
+			];
+			$fields['post_content'] = [
+				'type'	=> 'text',
+				'label'	=> 'Content',
+			];
+			$fields['post_excerpt'] = [
+				'type'	=> 'text',
+				'label'	=> 'Short Description',
+			];
+			$fields['post_id'] = [
+				'type'	=> 'text',
+				'label'	=> 'ID',
+			];
+
+
+			$fields['_stock'] = [
+				'type' => 'number',
+				'label' => 'Stock',
+				'default'	=> null
+			];
+			$fields['_stock_status'] = [
+				'type'	=> 'select',
+				'label'	=> 'Stock Status',
+				'default'	=> 'instock',
+				'options'	=> [
+					'instock'	=> 'In Stock',
+					'outofstock'	=> 'Out of Stock',
+					'onbackorder'		=> 'On Backorder'
+				]
+			];
+			$fields['_price'] = [
+				'type'		=> 'text',
+				'subtype'	=> 'number',
+				'label'		=>	'Price'
+			];
+
+
+			$product = wc_get_product($id);
+			$fields['sku'] = [
+				'type'	=> 'text',
+				'label'	=> 'SKU',
+			];
+
+			$out = [];
+			if ($product && $product->is_type('variable')) {
+				$var = $product->get_children();
+				$var = array_filter(array_map(function($v) {
+					return wc_get_product($v);
+				}, $var));
+
+				$title= $product->get_title();
+				foreach ($var as $variation) {
+					$out[] = [
+						'id'	=> $variation->get_id(),
+						'sku'	=> $variation->get_sku(),
+						'title'	=> str_replace($title.' - ', '', $variation->get_name()),
+						'price'	=> $variation->get_price(),
+						'description'=> $variation->get_short_description(),
+						'sale'	=> $variation->get_sale_price(),
+						'img'	=> $variation->get_image_id(),
+						'stock'	=> $variation->get_stock_quantity(),
+						'stock_status'	=> $variation->get_stock_status()
+					];
+				}
 			}
+			if (!empty($out)) {
+				$this->fields['variations'] = new Field('variations', $out, [
+					'type'	=> 'repeater',
+					'label'	=> 'Variations',
+					'fields'	=> [
+						'id'	=> [
+							'type'	=> 'text',
+							'subtype'	=> 'number',
+							'label'	=> 'Variation ID',
+						],
+						'sku'	=> [
+							'type'	=> 'text',
+							'label'	=> 'Stock Keeping Unit (SKU)'
+						],
+						'title'	=> [
+							'type'	=> 'text',
+							'label'	=> 'Variation Title'
+						],
+						'price'	=> [
+							'type'	=> 'text',
+							'subtype'	=> 'number',
+							'label'	=> 'Variation Price'
+						],
+						'sale'	=> [
+							'type'	=> 'text',
+							'subtype'	=> 'number',
+							'label'	=> 'Sale Price',
+						],
+						'image'	=> [
+							'type'	=> 'text',
+							'subtype'	=> 'number',
+							'label'	=> 'Variation Image ID'
+						],
+						'stock'	=> [
+							'type'	=> 'text',
+							'label'	=> 'Stock Quantity'
+						],
+						'stock_status'	=> [
+							'type'	=> 'text',
+							'label'	=> 'Stock Status'
+						]
+					]
+				]);
+			}
+
+
 		}
+		foreach ($fields as $fieldName => $config) {
+			$fieldName = jvbNoBase($fieldName);
+			$value = '';
+			if ($this->type === 'option') {
+				$value = get_option(BASE . $fieldName, $config['default'] ?? '');
+			} else if (
+				($this->wpObject && property_exists($this->wpObject, $fieldName))
+				|| ($type === 'user' && $this->wpObject && property_exists($this->wpObject->data, $fieldName))) {
+				$config['wp'] = true;
+				$value = $this->wpObject->$fieldName;
 
-		return $item;
-	}
+			}  else if (in_array($fieldName, $this->defaults)) {
+				$config['wp'] = true;
+				switch ($fieldName) {
+					case 'post_thumbnail':
+						$value = get_post_thumbnail_id($this->ID);
+						break;
+				}
+			} else {
+				$value = array_key_exists(BASE.$fieldName, $meta) ? $meta[BASE.$fieldName] : $config['default']??'';
+				switch ($config['type']) {
+					case 'taxonomy':
+						if (!$config['isReference']??true) {
+							$config['wp'] = true;
+							$value = implode(',', wp_get_post_terms($this->ID, jvbCheckBase($config['taxonomy']), ['fields' => 'ids']));
+						}
+						break;
+					case 'selector':
+						if (array_key_exists('subtype', $config) && $config['subtype'] === 'taxonomy' && !$config['isReference']??true) {
+							$config['wp'] = true;
+							$value = implode(',',wp_get_post_terms($this->ID, jvbCheckBase($config['taxonomy']), ['fields' => 'ids']));
+						}
+						break;
+				}
 
-	protected function loadFieldConfigs(?string $contentType, string $objectType): array
-	{
-		if (!$contentType && $objectType !== 'options') {
-			return [];
+			}
+			$this->fields[$fieldName] = new Field($fieldName, $value, $config);
 		}
-
-		return Registrar::getFieldsFor($contentType??'options');
 	}
 
 	// ─────────────────────────────────────────────────────────────
@@ -266,7 +308,7 @@
 
 	public function __isset(string $name): bool
 	{
-		return $this->item->hasField($name) || isset($this->computed[$name]);
+		return $this->item->hasField($name);
 	}
 
 	// ─────────────────────────────────────────────────────────────
@@ -282,78 +324,54 @@
 		if (str_contains($name, ':')) {
 			return $this->getByPath($name);
 		}
-
-		// Check computed fields first
-		if (isset($this->computed[$name])) {
-			return ($this->computed[$name])($this);
+		// Group shortcut path
+		if (str_contains($name, '|')) {
+			[$parent, $child] = explode('|', $name, 2);
+			return $this->group($parent)->get($child);
 		}
-
-		// Return from loaded field if exists
-		if ($field = $this->item->getField($name)) {
-			return $field->get();
+		if (!array_key_exists($name, $this->fields)) {
+			error_log('[Meta]::get Attempted to get unregistered field: '.$name);
+			return '';
 		}
-
-		// Load from storage
-		$value = $this->storage->get($this->item, $name);
-		$config = $this->item->getFieldConfig($name) ?? ['type' => 'text'];
-
-		$field = new Field($name, $value, $config);
-		$this->item->setField($field);
-
-		return $value;
+		if (is_null($this->fields[$name])) {
+			error_log('[Meta]::get Field does not seem to be setup yet: '.$name);
+			return '';
+		}
+		return $this->fields[$name]->get()??'';
 	}
 
 	/**
 	 * Set a field value (validates & sanitizes by default)
 	 */
-	public function set(string $name, mixed $value): self
+	public function set(string $name, mixed $value, $autosave = true): self
 	{
 		// Handle repeater subfield path (e.g., "services:2:image")
 		if (str_contains($name, ':')) {
 			return $this->setByPath($name, $value);
 		}
 
-		$config = $this->item->getFieldConfig($name);
+		// Group shortcut
+		if (str_contains($name, '|')) {
+			return $this->setGroup($name, $value, $autosave);
+		}
 
-		if (!$config) {
-			// Allow setting unknown fields with minimal config
-			$config = ['type' => 'text', 'name' => $name];
+		$field = $this->fields[$name]??false;
+		if (!$field) {
+			error_log('No config found for field: '.$name);
+			return $this;
 		}
 
 		// Validate
-		if ($this->autoValidate && !$this->validator->validate($value, $config)) {
-			$field = $this->item->getField($name) ?? new Field($name, $value, $config);
+		if (!$this->validator->validate($value, $field->config)) {
 			$field->addError("Validation failed for {$name}");
-			$this->item->setField($field);
 			return $this;
 		}
 
 		// Sanitize
-		if ($this->autoSanitize) {
-			$value = $this->sanitizer->sanitize($value, $config);
-		}
-
-		// Get or create field
-		$field = $this->item->getField($name);
-		$oldValue = null;
-
-		if ($field) {
-			$oldValue = $field->value;
-			$field->set($value);
-		} else {
-			// Load original to track dirty state
-			$original = $this->storage->get($this->item, $name);
-			$oldValue = $original;
-			$field = new Field($name, $original, $config);
-			$field->set($value);
-			$this->item->setField($field);
-		}
-
-		// Fire change callbacks
-		if (isset($this->onChangeCallbacks[$name]) && $oldValue !== $value) {
-			foreach ($this->onChangeCallbacks[$name] as $callback) {
-				$callback($value, $oldValue, $this);
-			}
+		$value = $this->sanitizer->sanitize($value, $field->config);
+		$field->set($value);
+		if ($autosave && $field->isDirty) {
+			$this->save();
 		}
 
 		return $this;
@@ -365,54 +383,128 @@
 	public function getAll(array $fields = []): array
 	{
 		if (empty($fields) || $fields === ['all']) {
-			$fields = array_keys($this->item->fieldConfigs);
+			$fields = array_keys($this->fields);
 		}
+		$fields = array_filter($this->fields, function ($field) use ($fields) {
+			return in_array($field, $fields);
+		}, ARRAY_FILTER_USE_KEY);
 
-		// Load all from storage
-		$values = $this->storage->getAll($this->item, $fields);
-
-		// Create Field instances
-		foreach ($values as $name => $value) {
-			if (!$this->item->getField($name)) {
-				$config = $this->item->getFieldConfig($name) ?? ['type' => 'text'];
-				$this->item->setField(new Field($name, $value, $config));
-			}
-		}
-
-		return $values;
+		return array_map(function ($field) {
+			return $field->value;
+		}, $fields);
 	}
 
 	/**
 	 * Set multiple fields
 	 */
-	public function setAll(array $data): self
+	public function setAll(array $data):bool
 	{
 		foreach ($data as $name => $value) {
-			$this->set($name, $value);
+			$this->set($name, $value, false);
 		}
-		return $this;
+		return $this->save();
 	}
 
 	/**
 	 * Save all dirty fields to database
 	 */
-	public function save(bool $updateTimestamp = true): bool
+	public function save(): bool
 	{
-		if (!$this->item->isValid()) {
-			JVB()->error()->log('meta', 'Cannot save: validation errors exist', [
-				'fields' => array_keys($this->item->getInvalidFields())
-			], 'warning');
-			return false;
-		}
+		$dirtyFields = array_filter($this->fields, function ($field) {
+			return $field->isDirty;
+		});
+		$defaults = array_filter($dirtyFields, function ($field) {
+			return $field->isDefault;
+		});
+		$custom = array_filter($dirtyFields, function($field) {
+			return !$field->isDefault;
+		});
 
-		// Check for field overrides before saving
-		foreach ($this->item->getDirtyFields() as $field) {
-			if ($this->checkOverrides($field)) {
-				$field->markClean();
+		$success = true;
+
+		if (!empty($defaults)) {
+			$result = false;
+			switch ($this->type) {
+				case 'post':
+					//Deal with field exceptions, first, that cannot be set via wp_update_post
+					foreach ($defaults as $fieldName => $field) {
+						switch ($fieldName) {
+							case 'post_thumbnail':
+								$result = set_post_thumbnail($this->ID, $field->value);
+								unset($defaults[$fieldName]);
+								break;
+						}
+						//If it's a taxonomy, we use wp_set_object_terms
+						if (
+							($field->config['type'] === 'selector' && $field->config['subtype'] === 'taxonomy')
+							|| $field->config['type'] === 'taxonomy') {
+
+							$result = wp_set_object_terms($this->ID, array_map('absint', explode(',', $field->value)), jvbCheckBase($field->config['taxonomy']));
+							unset($defaults[$fieldName]);
+						}
+					}
+
+
+
+					if (!empty($defaults)) {
+						error_log('Remaining fields: '.print_r($defaults, true));
+						$defaults = array_map(function ($field) {
+							return $field->value;
+						}, $defaults);
+						error_log('Remaining values to save: '.print_r($defaults, true));
+						$data = array_merge([
+							'post_type'	=> $this->slug,
+							'ID'		=> $this->ID
+						], $defaults);
+						error_log('Updating post: '.print_r($data, true));
+						$result = wp_update_post($data);
+					}
+					break;
+				case 'term':
+					$termDefaults = array_map(fn($field) => $field->value, $defaults);
+					$result = wp_update_term($this->ID, $this->slug, $termDefaults);
+					break;
+				case 'user':
+					$userDefaults = array_map(fn($field) => $field->value, $defaults);
+					$data = array_merge(['ID' => $this->ID], $userDefaults);
+					$result = wp_update_user($data);
+					break;
+			}
+			if (!$result || is_wp_error($result)) {
+				$success = false;
 			}
 		}
+		if (!empty($custom)) {
+			$function = match ($this->type) {
+				'post' => 'update_post_meta',
+				'term' => 'update_term_meta',
+				'user' => 'update_user_meta',
+				'option' => 'update_option',
+				default => false,
+			};
+			if (!$function) {
+				error_log('[Meta]::save() Invalid type, cannot save: '.$this->type);
+				return false;
+			}
+			foreach ($custom as $field) {
+				if ($this->type === 'option') {
+					$result = update_option(BASE.$field->name, $field->value);
+				} else {
+					$result = $function($this->ID, BASE.$field->name, $field->value);
+				}
 
-		return $this->storage->save($this->item, $updateTimestamp);
+				if (!$result) {
+					error_log('Problem saving field: '.$field->name.' with value: '.print_r($field->value, true));
+				}
+			}
+			//Now handled directly from Registrar
+//			if ($this->type === 'term' && Registrar::getInstance($this->slug)->hasFeature('is_content')) {
+//				update_term_meta($this->ID, BASE.'date_modified', date('Y-m-d H:i:s'));
+//			}
+		}
+
+
+		return $success;
 	}
 
 	/**
@@ -420,16 +512,28 @@
 	 */
 	public function delete(string $name): bool
 	{
-		$result = $this->storage->delete($this->item, $name);
-
-		if ($result && $field = $this->item->getField($name)) {
-			$field->set($this->getDefaultValue($name));
-			$field->markClean();
+		$field = $this->fields[$name]??false;
+		if (!$field) {
+			error_log('[Meta]::delete Could not delete field '.$name.': not registered');
+			return true;
 		}
 
-		return $result;
+		if ($field->isTaxonomy()) {
+			wp_set_object_terms($this->ID, [], $this->slug);
+			return true;
+		}
+
+		return match ($this->type) {
+			'post'	=> delete_post_meta($this->ID, BASE.$name),
+			'term'	=> delete_term_meta($this->ID, BASE.$name),
+			'user', 'integrations' => delete_user_meta($this->ID, BASE.$name),
+			'option'	=> delete_option(BASE.$name),
+			default => false
+		};
 	}
 
+
+
 	/**
 	 * Delete multiple field values
 	 */
@@ -442,9 +546,9 @@
 		return $results;
 	}
 
-	// ─────────────────────────────────────────────────────────────
-	// Repeater Access
-	// ─────────────────────────────────────────────────────────────
+	/*****************************************************************
+	 * Repeater Access
+	*****************************************************************/
 
 	/**
 	 * Get repeater accessor for fluent repeater operations
@@ -454,6 +558,7 @@
 		return new Repeater($this, $name);
 	}
 
+
 	protected function setByPath(string $path, mixed $value): self
 	{
 		$parts = explode(':', $path, 3);
@@ -478,28 +583,26 @@
 		return $this->repeater($repeaterName)->field((int) $rowIndex, $subField);
 	}
 
+
+
+	public function group(string $name): Group
+	{
+		return new Group($this, $name);
+	}
+
+	protected function setGroup(string $name, mixed $value, bool $autosave = true): self
+	{
+		$parent = strtok($name, '|');
+		$child = str_replace($parent . '|', '', $name);
+
+		$this->group($parent)->set($child, $value, $autosave);
+		return $this;
+	}
+
 	// ─────────────────────────────────────────────────────────────
 	// Utility Methods
 	// ─────────────────────────────────────────────────────────────
 
-	/**
-	 * Get all dirty (changed) field values
-	 */
-	public function getDirty(): array
-	{
-		return array_map(
-			fn(Field $f) => $f->value,
-			$this->item->getDirtyFields()
-		);
-	}
-
-	/**
-	 * Check if any fields have changed
-	 */
-	public function isDirty(): bool
-	{
-		return $this->item->hasDirtyFields();
-	}
 
 	/**
 	 * Discard all unsaved changes
@@ -510,84 +613,22 @@
 		return $this;
 	}
 
-	/**
-	 * Get validation errors
-	 */
-	public function getErrors(): array
-	{
-		$errors = [];
-		foreach ($this->item->getInvalidFields() as $name => $field) {
-			$errors[$name] = $field->errors;
-		}
-		return $errors;
-	}
-
-	/**
-	 * Check if valid (no validation errors)
-	 */
-	public function isValid(): bool
-	{
-		return $this->item->isValid();
-	}
-
-	/**
-	 * Disable auto-validation for bulk operations
-	 */
-	public function withoutValidation(): self
-	{
-		$this->autoValidate = false;
-		return $this;
-	}
-
-	/**
-	 * Disable auto-sanitization
-	 */
-	public function withoutSanitization(): self
-	{
-		$this->autoSanitize = false;
-		return $this;
-	}
-
-	/**
-	 * Re-enable validation and sanitization
-	 */
-	public function withDefaults(): self
-	{
-		$this->autoValidate = true;
-		$this->autoSanitize = true;
-		return $this;
-	}
-
-	/**
-	 * Get the underlying Item
-	 */
-	public function item(): Item
-	{
-		return $this->item;
-	}
 
 	/**
 	 * Get field configuration
 	 */
 	public function config(string $name): ?array
 	{
-		return $this->item->getFieldConfig($name);
+		return $this->fields[$name]->config??null;
 	}
 
-	/**
-	 * Get all field configurations
-	 */
-	public function configs(): array
-	{
-		return $this->item->fieldConfigs;
-	}
 
 	/**
 	 * Get item ID
 	 */
 	public function id(): int|string|null
 	{
-		return $this->item->id;
+		return $this->ID;
 	}
 
 	/**
@@ -595,7 +636,7 @@
 	 */
 	public function objectType(): string
 	{
-		return $this->item->objectType;
+		return $this->type;
 	}
 
 	/**
@@ -603,47 +644,14 @@
 	 */
 	public function contentType(): ?string
 	{
-		return $this->item->contentType;
+		return $this->contentType;
 	}
 
-	/**
-	 * Eager load all fields
-	 */
-	public function eager(): self
+	public function item():Item
 	{
-		$this->getAll();
-		return $this;
+		return $this->item;
 	}
 
-	/**
-	 * Convert loaded fields to array
-	 */
-	public function toArray(): array
-	{
-		return $this->item->toArray();
-	}
-
-	// ─────────────────────────────────────────────────────────────
-	// Event Callbacks
-	// ─────────────────────────────────────────────────────────────
-
-	/**
-	 * Register callback for field changes
-	 */
-	public function onChange(string $field, callable $callback): self
-	{
-		$this->onChangeCallbacks[$field][] = $callback;
-		return $this;
-	}
-
-	/**
-	 * Register computed/virtual field
-	 */
-	public function computed(string $name, callable $getter): self
-	{
-		$this->computed[$name] = $getter;
-		return $this;
-	}
 
 	// ─────────────────────────────────────────────────────────────
 	// Protected Helpers

--
Gitblit v1.10.0