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 |  709 ++++++++++++++++++++++++++++++++++++++++++----------------
 1 files changed, 507 insertions(+), 202 deletions(-)

diff --git a/inc/meta/Meta.php b/inc/meta/Meta.php
index 05810e6..1d34549 100644
--- a/inc/meta/Meta.php
+++ b/inc/meta/Meta.php
@@ -1,105 +1,295 @@
 <?php
 namespace JVBase\meta;
 
-use Exception;
+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
- */
 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 MetaValidator $validator;
-	protected MetaSanitizer $sanitizer;
+	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;
-
-	// ─────────────────────────────────────────────────────────────
-	// 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;
 	}
 
+	/**
+	 * Create Meta instance for a term
+	 */
 	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;
 	}
 
+	/**
+	 * Create Meta instance for a user
+	 */
 	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;
 	}
 
-	public static function forOptions(?string $baseKey = null): self
+	/**
+	 * Create Meta instance for options
+	 */
+	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
+	***************************************************************/
 
-	// ─────────────────────────────────────────────────────────────
-	// Constructor
-	// ─────────────────────────────────────────────────────────────
-
-	public function __construct(int|string|null $id, string $type)
+	public function __construct(int|string|null $id, string $type, array $fields = [])
 	{
-		$this->storage = new Storage();
-		$this->validator = new MetaValidator();
-		$this->sanitizer = new MetaSanitizer();
+		$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 jvbGetFields($contentType ?? 'options', $objectType);
 	}
 
 	// ─────────────────────────────────────────────────────────────
@@ -130,59 +320,58 @@
 	 */
 	public function get(string $name): mixed
 	{
-		// Return from loaded field if exists
-		if ($field = $this->item->getField($name)) {
-			return $field->get();
+		// Handle repeater subfield path
+		if (str_contains($name, ':')) {
+			return $this->getByPath($name);
 		}
-
-		// 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;
+		// Group shortcut path
+		if (str_contains($name, '|')) {
+			[$parent, $child] = explode('|', $name, 2);
+			return $this->group($parent)->get($child);
+		}
+		if (!array_key_exists($name, $this->fields)) {
+			error_log('[Meta]::get Attempted to get unregistered field: '.$name);
+			return '';
+		}
+		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)
+	 * 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
 	{
-		$config = $this->item->getFieldConfig($name);
-
-		if (!$config) {
-			// Allow setting unknown fields with minimal config
-			$config = ['type' => 'text', 'name' => $name];
+		// Handle repeater subfield path (e.g., "services:2:image")
+		if (str_contains($name, ':')) {
+			return $this->setByPath($name, $value);
 		}
 
-		$config['name'] = $name;
+		// Group shortcut
+		if (str_contains($name, '|')) {
+			return $this->setGroup($name, $value, $autosave);
+		}
+
+		$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);
-
-		if ($field) {
-			$field->set($value);
-		} else {
-			// Need to load original to track dirty state
-			$original = $this->storage->get($this->item, $name);
-			$field = new Field($name, $original, $config);
-			$field->set($value);
-			$this->item->setField($field);
+		$value = $this->sanitizer->sanitize($value, $field->config);
+		$field->set($value);
+		if ($autosave && $field->isDirty) {
+			$this->save();
 		}
 
 		return $this;
@@ -194,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;
 	}
 
 	/**
@@ -249,117 +512,155 @@
 	 */
 	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
+	 */
+	public function deleteAll(array $names): array
+	{
+		$results = [];
+		foreach ($names as $name) {
+			$results[$name] = $this->delete($name);
+		}
+		return $results;
+	}
+
+	/*****************************************************************
+	 * Repeater Access
+	*****************************************************************/
+
+	/**
+	 * Get repeater accessor for fluent repeater operations
+	 */
+	public function repeater(string $name): Repeater
+	{
+		return new Repeater($this, $name);
+	}
+
+
+	protected function setByPath(string $path, mixed $value): self
+	{
+		$parts = explode(':', $path, 3);
+		if (count($parts) !== 3) {
+			return $this;
+		}
+
+		[$repeaterName, $rowIndex, $subField] = $parts;
+		$this->repeater($repeaterName)->setField((int) $rowIndex, $subField, $value);
+
+		return $this;
+	}
+
+	protected function getByPath(string $path): mixed
+	{
+		$parts = explode(':', $path, 3);
+		if (count($parts) !== 3) {
+			return null;
+		}
+
+		[$repeaterName, $rowIndex, $subField] = $parts;
+		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) fields
-	 */
-	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
 	 */
 	public function reset(): self
 	{
-		foreach ($this->item->fields as $field) {
-			$field->reset();
-		}
+		$this->item->resetAll();
 		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;
-	}
-
-	/**
-	 * Get the underlying item (for rendering, etc)
-	 */
-	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 item ID
+	 */
+	public function id(): int|string|null
+	{
+		return $this->ID;
 	}
 
 	/**
-	 * Get all field configurations
+	 * Get object type (post, term, user, options)
 	 */
-	public function configs(): array
+	public function objectType(): string
 	{
-		return $this->item->fieldConfigs;
+		return $this->type;
 	}
 
+	/**
+	 * Get content type (tattoo, artist, etc)
+	 */
+	public function contentType(): ?string
+	{
+		return $this->contentType;
+	}
+
+	public function item():Item
+	{
+		return $this->item;
+	}
+
+
 	// ─────────────────────────────────────────────────────────────
 	// Protected Helpers
 	// ─────────────────────────────────────────────────────────────
 
-	protected function checkOverrides(Field $field): bool
+	/**
+	 * Check for field update overrides
+	 */
+	public function checkOverrides(Field $field): bool
 	{
 		$name = $field->name;
 		$type = $field->type();
@@ -384,6 +685,9 @@
 		return false;
 	}
 
+	/**
+	 * Get default value for a field type
+	 */
 	protected function getDefaultValue(string $name): mixed
 	{
 		$config = $this->item->getFieldConfig($name);
@@ -396,4 +700,5 @@
 			default => '',
 		};
 	}
+
 }

--
Gitblit v1.10.0