From a58a2e7ca4700d3e23341fde767060a4c2c704a3 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 30 Aug 2026 21:04:37 +0000
Subject: [PATCH] =Minor tweaks to MenuBlock.php
---
inc/meta/Meta.php | 889 +++++++++++++++++++++++++++++++++++++----------------------
1 files changed, 556 insertions(+), 333 deletions(-)
diff --git a/inc/meta/Meta.php b/inc/meta/Meta.php
index 05810e6..c0454a6 100644
--- a/inc/meta/Meta.php
+++ b/inc/meta/Meta.php
@@ -1,399 +1,622 @@
<?php
namespace JVBase\meta;
-use Exception;
+use JVBase\managers\Cache;
+use JVBase\registrar\Registrar;
if (!defined('ABSPATH')) {
exit;
}
-/**
- * Main facade for meta operations
- * Fluent API for getting/setting meta values with validation & sanitization
- */
-class Meta
-{
- protected Item $item;
- protected Storage $storage;
- protected MetaValidator $validator;
- protected MetaSanitizer $sanitizer;
- protected MetaTypeManager $typeManager;
+class Meta {
+ /**
+ * @var string post, term, user, or options
+ */
+ protected string $objectType;
+ /**
+ * @var string|null post type, user role, or taxonomy
+ */
+ protected ?string $content;
+ /**
+ * @var int|string <int>post, term, or user ID OR {string} option key base
+ */
+ protected int|string $ID;
+ /**
+ * @var array of Registrar's defined fields, if any
+ */
+ protected static array $registrars = [];
+ /**
+ * @var array Field values, stored in memory
+ */
+ protected array $fields;
- protected bool $autoValidate = true;
- protected bool $autoSanitize = true;
+ protected ?\WC_Product $product = null;
- // ─────────────────────────────────────────────────────────────
- // Factory Methods
- // ─────────────────────────────────────────────────────────────
+ protected static Validator $validator;
+ protected static Sanitizer $sanitizer;
- public static function forPost(int $id): self
- {
- return new self($id, 'post');
- }
+ protected static array $coreFields = [
+ 'post' => ['post_title', 'post_content', 'post_excerpt', 'post_status', 'post_date', 'post_modified', 'post_thumbnail', 'post_name'],
+ 'term' => ['name', 'description', 'parent'],
+ 'user' => ['first_name', 'last_name', 'user_email', 'display_name', 'user_login'],
+ 'options' => []
+ ];
- public static function forTerm(int $id): self
- {
- return new self($id, 'term');
- }
+ protected static array $instances = [
+ 'post' => [],
+ 'term' => [],
+ 'user' => [],
+ 'options'=> [],
+ ];
- public static function forUser(int $id): self
- {
- return new self($id, 'user');
- }
+ private function __construct(string $objectType, int|string $ID) {
+ $this->objectType = $objectType;
+ $this->ID = $ID;
+ $this->content = $this->resolveBasedType();
- public static function forOptions(?string $baseKey = null): self
- {
- $instance = new self($baseKey, 'options');
- $instance->item->baseKey = $baseKey;
- return $instance;
- }
-
- // ─────────────────────────────────────────────────────────────
- // Constructor
- // ─────────────────────────────────────────────────────────────
-
- public function __construct(int|string|null $id, string $type)
- {
- $this->storage = new Storage();
- $this->validator = new MetaValidator();
- $this->sanitizer = new MetaSanitizer();
- $this->typeManager = new MetaTypeManager();
-
- $this->item = $this->buildItem($id, $type);
- }
-
- protected function buildItem(int|string|null $id, string $type): Item
- {
- $contentType = null;
- $wpObject = null;
-
- 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]
- };
- }
-
- $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;
+ if ($this->content && !array_key_exists($this->content, self::$registrars)) {
+ $registrar = Registrar::getInstance($this->content);
+ if ($registrar) {
+ self::$registrars[$this->content] = $registrar->getFields();
}
}
- return $item;
+ if ($this->objectType === 'post' && function_exists('wc_get_product') && in_array($this->content, ['product', 'product_variation'], true)) {
+ $this->product = wc_get_product($this->ID) ?: null;
+ }
+ $this->buildData();
+ if (!isset(self::$sanitizer)) {
+ self::$sanitizer = new Sanitizer();
+ }
+ if (!isset(self::$validator)) {
+ self::$validator = new Validator();
+ }
}
+ protected function buildData():void
+ {
+ //Get all Item meta
+ $this->fields = match ($this->objectType) {
+ 'post' => get_post_meta($this->ID),
+ 'term' => get_term_meta($this->ID),
+ 'user' => get_user_meta($this->ID),
+ default => []
+ };
+ $this->fields = array_combine(
+ array_map('jvbNoBase', array_keys($this->fields)),
+ $this->fields
+ );
- protected function loadFieldConfigs(?string $contentType, string $objectType): array
- {
- if (!$contentType && $objectType !== 'options') {
- return [];
+ //Add in core fields
+ $obj = match ($this->objectType) {
+ 'post' => get_post($this->ID),
+ 'term' => get_term($this->ID, $this->content),
+ 'user' => get_userdata($this->ID),
+ default => false
+ };
+ if ($obj) {
+ foreach (self::$coreFields[$this->objectType] as $name) {
+ $value = match ($name) {
+ 'post_thumbnail' => get_post_thumbnail_id($this->ID),
+ default => $obj->$name??false
+ };
+// if (!$value) {
+// $this->log('Could not find value for '.$name.', of '.$this->content);
+// }
+ $this->fields[$name] = $value??'';
+ }
+ }
}
- return jvbGetFields($contentType ?? 'options', $objectType);
+ public static function forPost(int $ID):self
+ {
+ if (array_key_exists($ID, self::$instances['post'])) {
+ return self::$instances['post'][$ID];
+ }
+ self::$instances['post'][$ID] = new self('post', $ID);
+ return self::$instances['post'][$ID];
}
- // ─────────────────────────────────────────────────────────────
- // Magic Methods for Fluent Access
- // ─────────────────────────────────────────────────────────────
-
- public function __get(string $name): mixed
+ public static function forTerm(int $ID):self
{
- return $this->get($name);
+ if (array_key_exists($ID, self::$instances['term'])) {
+ return self::$instances['term'][$ID];
+ }
+ self::$instances['term'][$ID] = new self('term', $ID);
+ return self::$instances['term'][$ID];
}
- public function __set(string $name, mixed $value): void
+ public static function forUser(int $ID):self
{
- $this->set($name, $value);
+ if (array_key_exists($ID, self::$instances['user'])) {
+ return self::$instances['user'][$ID];
+ }
+ self::$instances['user'][$ID] = new self('user', $ID);
+ return self::$instances['user'][$ID];
}
- public function __isset(string $name): bool
+ public static function forOptions(string $base = 'default'):self
{
- return $this->item->hasField($name);
+ if (array_key_exists($base, self::$instances['options'])) {
+ return self::$instances['options'][$base];
+ }
+ self::$instances['options'][$base] = new self('options', $base);
+ return self::$instances['options'][$base];
}
- // ─────────────────────────────────────────────────────────────
- // Core API
- // ─────────────────────────────────────────────────────────────
+ protected function resolveBasedType(): ?string {
+ return match ($this->objectType) {
+ 'post' => get_post_type($this->ID) ?: null,
+ 'term' => ($term = get_term($this->ID)) && !is_wp_error($term) ? $term->taxonomy : null,
+ 'user' => jvbUserRole($this->ID),
+ default => null
+ };
+ }
- /**
- * Get a field value
- */
- public function get(string $name): mixed
- {
- // Return from loaded field if exists
- if ($field = $this->item->getField($name)) {
- return $field->get();
+
+
+
+
+ /***************************************************************************
+ * GETTING FIELDS
+ ***************************************************************************/
+ public function get(string $name): mixed {
+ if (array_key_exists($name, $this->fields)) {
+ return $this->fields[$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);
-
+ if ($this->product && $this->isProductField($name)) {
+ $value = $this->getProductField($name);
+ } else if (in_array($name, static::$coreFields[$this->objectType], true)) {
+ $value = $this->getCoreField($name);
+ } else {
+ $value = $this->getMetaField($name);
+ }
+ $this->fields[$name] = $value;
return $value;
}
- /**
- * Set a field value (validates & sanitizes)
- */
- public function set(string $name, mixed $value): self
- {
- $config = $this->item->getFieldConfig($name);
-
- if (!$config) {
- // Allow setting unknown fields with minimal config
- $config = ['type' => 'text', 'name' => $name];
+ public function getAll(?array $names = null): array {
+ $names = $names ?? $this->getAllFieldNames();
+ $out = [];
+ foreach ($names as $name) {
+ $out[$name] = $this->get($name);
}
-
- $config['name'] = $name;
-
- // Validate
- if ($this->autoValidate && !$this->validator->validate($value, $config)) {
- $field = $this->item->getField($name) ?? new Field($name, $value, $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);
- }
-
- return $this;
+ return $out;
}
- /**
- * Get multiple fields
- */
- public function getAll(array $fields = []): array
- {
- if (empty($fields) || $fields === ['all']) {
- $fields = array_keys($this->item->fieldConfigs);
+ protected function getCoreField(string $name): mixed {
+ return match ($this->objectType) {
+ 'post' => $name === 'post_thumbnail'
+ ? get_post_thumbnail_id($this->ID)
+ : (($p = get_post($this->ID)) ? ($p->$name ?? null) : null),
+ 'term' => ($t = get_term($this->ID, $this->content)) && !is_wp_error($t) ? ($t->$name ?? null) : null,
+ 'user' => ($u = get_userdata($this->ID)) ? ($u->$name ?? null) : null,
+ };
+ }
+
+ protected function getMetaField(string $name): mixed {
+ $key = $this->metaKey($name);
+ return match ($this->objectType) {
+ 'post' => get_post_meta($this->ID, $key, true),
+ 'term' => get_term_meta($this->ID, $key, true),
+ 'user' => get_user_meta($this->ID, $key, true),
+ 'options' => get_option($key)
+ };
+ }
+
+ /***************************************************************************
+ * SETTING FIELDS
+ ***************************************************************************/
+ public function set(string $name, mixed $value): bool {
+ $field = $this->getFieldConfig($name);
+ if ($field) {
+ if ($field['type']??'' === 'repeater' && str_contains($name, ':')) {
+ return $this->setByPath($name, $value);
+ } elseif ($field['type']??'' === 'group' && str_contains($name, '|')) {
+ return $this->setGroup($name, $value);
+ }
+
+ if (!self::$validator->validate($value, $field)) {
+ return false;
+ }
+
+ $value = self::$sanitizer->sanitize($value, $field);
}
- // Load all from storage
- $values = $this->storage->getAll($this->item, $fields);
+ if ($this->product && $this->isProductField($name)) {
+ return $this->setProductField($name, $value);
+ }
+ if (in_array($name, static::$coreFields[$this->objectType], true)) {
+ return $this->setCoreField($name, $value);
+ }
+ return $this->setMetaField($name, $value);
+ }
- // 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));
+ public function setAll(array $data): array {
+ $core = array_filter($data, function ($name) {
+ return in_array($name, self::$coreFields[$this->objectType]);
+ }, ARRAY_FILTER_USE_KEY);
+ $results = [];
+ if (!empty($core)) {
+ $data = array_filter($data, function ($name) {
+ return !in_array($name, self::$coreFields[$this->objectType]);
+ }, ARRAY_FILTER_USE_KEY);
+ $results = $this->setAllCoreFields($core);
+ }
+
+ foreach ($data as $name => $value) {
+ $results[$name] = $this->set($name, $value);
+ }
+ return $results;
+ }
+
+ protected function setAllCoreFields(array $data):array
+ {
+ $results = [];
+
+ $core = array_filter($data, function ($name) {
+ return in_array($name, self::$coreFields[$this->objectType]);
+ }, ARRAY_FILTER_USE_KEY);
+
+
+ if (count($core) !== count($data)) {
+ $notCore = array_filter($data, function ($name) {
+ return !in_array($name, self::$coreFields[$this->objectType]);
+ }, ARRAY_FILTER_USE_KEY);
+ $results = $this->setAll($notCore);
+ }
+
+ if (array_key_exists('post_thumbnail', $core)) {
+ $results['post_thumbnail'] = $this->setCoreField('post_thumbnail', $core['post_thumbnail']);
+ unset ($core['post_thumbnail']);
+ }
+
+ $return = match ($this->objectType) {
+ 'post' => !is_wp_error(wp_update_post(array_merge(['ID' => $this->ID], $core))),
+ 'term' => !is_wp_error(wp_update_term($this->ID, $this->content, $core)),
+ 'user' => !is_wp_error(wp_update_user(array_merge(['ID' => $this->ID], $core))),
+ };
+ foreach ($core as $name => $value) {
+ $results[$name] = $return;
+ }
+ return $results;
+ }
+
+ protected function setCoreField(string $name, mixed $value): bool {
+ $success = match ($this->objectType) {
+ 'post' => $name === 'post_thumbnail'
+ ? (!empty($value) ? set_post_thumbnail($this->ID, (int)$value) : delete_post_thumbnail($this->ID))
+ : !is_wp_error(wp_update_post(['ID' => $this->ID, $name => $value])),
+ 'term' => !is_wp_error(wp_update_term($this->ID, $this->content, [$name => $value])),
+ 'user' => !is_wp_error(wp_update_user(['ID' => $this->ID, $name => $value])),
+ default => false
+ };
+ if ($success) {
+ $this->clearCache();
+ $this->fields[$name] = $value;
+ }
+ return $success;
+ }
+
+ protected function setMetaField(string $name, mixed $value): bool {
+ $key = $this->metaKey($name);
+ $result = match ($this->objectType) {
+ 'post' => update_post_meta($this->ID, $key, $value),
+ 'term' => update_term_meta($this->ID, $key, $value),
+ 'user' => update_user_meta($this->ID, $key, $value),
+ 'options' => update_option($key, $value)
+ };
+ if ($result) {
+ $this->fields[$name] = $value;
+ $this->clearCache();
+ }
+ return $result !== false;
+ }
+ /********************************************************************************
+ * DELETING FIELDS
+ ********************************************************************************/
+ public function delete(string $name):bool
+ {
+ if (in_array($name, static::$coreFields[$this->objectType])) {
+ return $this->deleteCoreField($name);
+ }
+
+ $field = $this->getFieldConfig($name);
+ if ($field) {
+ if ($this->isTaxonomy($field)) {
+ return !is_wp_error(wp_set_object_terms($this->ID, [], $this->content));
}
}
-
- return $values;
+ $key = $this->metaKey($name);
+ return match ($this->objectType) {
+ 'post' => delete_post_meta($this->ID, $key),
+ 'term' => delete_term_meta($this->ID, $key),
+ 'user' => delete_user_meta($this->ID, $key),
+ 'option'=> delete_option($key)
+ };
}
-
- /**
- * Set multiple fields
- */
- public function setAll(array $data): self
+ public function deleteAll(array $fieldNames):array
{
- foreach ($data as $name => $value) {
- $this->set($name, $value);
+ $core = array_filter($fieldNames, function ($name) {
+ return in_array($name, self::$coreFields[$this->objectType]);
+ }, ARRAY_FILTER_USE_KEY);
+
+ $results = [];
+ if (!empty($core)) {
+ $fieldNames = array_filter($fieldNames, function ($name) {
+ return !in_array($name, self::$coreFields[$this->objectType]);
+ });
+ $results = $this->deleteAllCoreFields($core);
}
- return $this;
+
+ foreach ($fieldNames as $name) {
+ $results[$name] = $this->delete($name);
+ }
+
+ return $results;
+ }
+ protected function deleteAllCoreFields(array $fieldNames):array
+ {
+ $results = [];
+ $core = array_filter($fieldNames, function ($name) {
+ return in_array($name, self::$coreFields[$this->objectType]);
+ }, ARRAY_FILTER_USE_KEY);
+
+
+ if (count($core) !== count($fieldNames)) {
+ $notCore = array_filter($fieldNames, function ($name) {
+ return !in_array($name, self::$coreFields[$this->objectType]);
+ }, ARRAY_FILTER_USE_KEY);
+ $results = $this->deleteAll($notCore);
+ }
+
+ if (array_key_exists('post_thumbnail', $fieldNames)) {
+ $results['post_thumbnail'] = delete_post_thumbnail($this->ID);
+ unset($fieldNames['post_thumbnail']);
+ unset($core['post_thumbnail']);
+ }
+
+ $updated = [];
+ foreach ($core as $name) {
+ $updated[$name] = $this->getCoreDefault($name);
+ }
+
+ $success = match ($this->objectType) {
+ 'post' => !is_wp_error(wp_update_post(array_merge(['ID' => $this->ID], $updated))),
+ 'term' => !is_wp_error(wp_update_term($this->ID, $this->content, $updated)),
+ 'user' => !is_wp_error(wp_update_user(array_merge(['ID' => $this->ID], $updated)))
+ };
+
+ foreach ($core as $name) {
+ $results[$name] = $success;
+ }
+ return $results;
}
- /**
- * Save all dirty fields to database
- */
- public function save(bool $updateTimestamp = true): bool
+ protected function deleteCoreField(string $name):bool
{
- if (!$this->item->isValid()) {
- JVB()->error()->log('meta', 'Cannot save: validation errors exist', [
- 'fields' => array_keys($this->item->getInvalidFields())
- ], 'warning');
+ if ($name === 'post_thumbnail') {
+ return delete_post_thumbnail($this->ID);
+ }
+ $default = $this->getCoreDefault($name);
+ return $this->setCoreField($name, $default);
+ }
+
+
+ /**************************************************************
+ * WOOCOMMERCE PRODUCT SPECIFIC
+ **************************************************************/
+ protected function isProductField(string $name): bool {
+ return method_exists($this->product, 'get_'.$name) || method_exists($this->product, 'set_'.$name);
+ }
+
+ protected function getProductField(string $name): mixed {
+ $method = 'get_'.$name;
+ if ($name === 'variations') {
+ return $this->getVariations();
+ }
+ return method_exists($this->product, $method) ? $this->product->$method() : null;
+ }
+ protected function getVariations():array
+ {
+ $variations = $this->product->get_children();
+ $out = [];
+ foreach ($variations as $v) {
+ $out[] = self::forPost($v)->getAll();
+ }
+ return $out;
+ }
+
+ protected function setProductField(string $name, mixed $value): bool {
+ $method = 'set_'.$name;
+ if (!method_exists($this->product, $method)) return false;
+ $this->product->$method($value);
+ $this->product->save();
+ $this->fields[$name] = $value;
+ $this->clearCache();
+ return true;
+ }
+
+ protected function getAllProductFieldNames(): array
+ {
+ return [
+ 'price',
+ 'sku',
+ 'sale',
+ 'short_description',
+ 'image_id',
+ 'gallery_image_ids',
+ 'stock',
+ 'stock_quantity',
+ 'stock_status',
+ 'variations'
+ ];
+ }
+
+ /*** Sanitization & Validation ***/
+// protected function sanitize(mixed $value, array $field): mixed {
+// return match ($field['type']) {
+// 'text', 'tel' => sanitize_text_field($value),
+// 'email' => sanitize_email($value),
+// 'url' => esc_url_raw($value),
+// 'textarea' => !empty($field['quill']) ? wp_kses_post($value) : sanitize_textarea_field($value),
+// 'number' => is_numeric($value) ? $value + 0 : 0,
+// 'true_false' => (bool)$value,
+// 'datetime' => sanitize_text_field($value),
+// 'upload', 'image', 'gallery' => !empty($field['multiple']) ? array_map('intval', (array)$value) : (int)$value,
+// 'checkbox', 'set' => array_map('sanitize_text_field', (array)$value),
+// 'radio', 'select' => sanitize_text_field($value),
+// 'selector', 'taxonomy', 'user', 'post' => array_map('intval', (array)$value),
+// 'repeater', 'tagList' => $this->sanitizeRepeater($value, $field),
+// 'group' => $this->sanitizeGroup($value, $field),
+// default => is_array($value) ? array_map('sanitize_text_field', $value) : sanitize_text_field($value),
+// };
+// }
+
+// protected function validate(string $name, mixed $value, array $field): bool|string {
+// if (!empty($field['required']) && ($value === '' || $value === null)) {
+// return $name.' is required';
+// }
+// if (isset($field['maxLength']) && is_string($value) && strlen($value) > $field['maxLength']) {
+// return $name.' exceeds max length of '.$field['maxLength'];
+// }
+// if (isset($field['min']) && is_numeric($value) && $value < $field['min']) {
+// return $name.' is below minimum of '.$field['min'];
+// }
+// if (isset($field['max']) && is_numeric($value) && $value > $field['max']) {
+// return $name.' exceeds maximum of '.$field['max'];
+// }
+// if (in_array($field['type'], ['checkbox', 'set', 'radio', 'select'], true) && !empty($field['options'])) {
+// foreach ((array)$value as $v) {
+// if (!array_key_exists($v, $field['options'])) {
+// return $name.': invalid option '.$v;
+// }
+// }
+// }
+// return true;
+// }
+
+ /************************************************************************
+ * REPEATER Helpers
+ ************************************************************************/
+ public function repeater(string $name):Repeater
+ {
+ return new Repeater($this, $name);
+ }
+ protected function setByPath(string $path, mixed $value):bool
+ {
+ $parts = explode(':', $path, 3);
+ if (count($parts) !== 3) {
+ $this->log('Could not set value by path');
return false;
}
+ [$repeaterName, $rowIndex, $subField] = $parts;
+ $this->clearCache();
+ return $this->repeater($repeaterName)->setField((int) $rowIndex, $subField, $value);
+ }
- // Check for field overrides before saving
- foreach ($this->item->getDirtyFields() as $field) {
- if ($this->checkOverrides($field)) {
- $field->markClean();
- }
+ protected function getByPath(string $path):mixed
+ {
+ $parts = explode(':', $path, 3);
+ if (count($parts) !== 3) {
+ return null;
}
- return $this->storage->save($this->item, $updateTimestamp);
+ [$repeaterName, $rowIndex, $subField] = $parts;
+ return $this->repeater($repeaterName)->field((int) $rowIndex, $subField);
+ }
+ /****************************************************************************************
+ * GROUP helpers
+ ****************************************************************************************/
+
+ public function group(string $name): Group
+ {
+ return new Group($this, $name);
}
- /**
- * Delete a field value
- */
- public function delete(string $name): bool
+ protected function setGroup(string $name, mixed $value):bool
{
- $result = $this->storage->delete($this->item, $name);
-
- if ($result && $field = $this->item->getField($name)) {
- $field->set($this->getDefaultValue($name));
- $field->markClean();
- }
-
- return $result;
+ [$parent, $child] = explode('|', $name, 2);
+ $this->clearCache();
+ return $this->group($parent)->set($child, $value);
}
- // ─────────────────────────────────────────────────────────────
- // Utility Methods
- // ─────────────────────────────────────────────────────────────
-
- /**
- * Get all dirty (changed) fields
- */
- public function getDirty(): array
+ /***************************************************************************************
+ * UTILITY
+ ***************************************************************************************/
+ protected function clearCache():void
{
- 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();
- }
- 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);
- }
-
- /**
- * Get all field configurations
- */
- public function configs(): array
- {
- return $this->item->fieldConfigs;
- }
-
- // ─────────────────────────────────────────────────────────────
- // Protected Helpers
- // ─────────────────────────────────────────────────────────────
-
- protected function checkOverrides(Field $field): bool
- {
- $name = $field->name;
- $type = $field->type();
- $value = $field->value;
-
- do_action('jvb_meta_update', $name, $value, $this->item->objectType);
-
- $overrides = [
- BASE . 'update_' . $name,
- BASE . 'update_' . $type,
- 'jvb_update_' . $name,
- 'jvb_update_' . $type,
- ];
-
- foreach ($overrides as $override) {
- if (function_exists($override)) {
- $override($this->item->id, $value);
- return true;
- }
- }
-
- return false;
- }
-
- protected function getDefaultValue(string $name): mixed
- {
- $config = $this->item->getFieldConfig($name);
- $type = $config['type'] ?? 'text';
-
- return match ($this->typeManager->getMetaType($type)) {
- 'object', 'array' => [],
- 'boolean' => false,
- 'integer' => 0,
- default => '',
+ match ($this->objectType) {
+ 'post' => Cache::clearPost($this->ID),
+ 'term' => Cache::clearTerm($this->ID),
+ 'user' => Cache::clearUser($this->ID),
};
}
+ protected function isTaxonomy(array $field):bool
+ {
+ $type = $field['type']??'';
+ return $type === 'taxonomy' || ($type === 'selector' &&$field['subtype']??'' === 'taxonomy');
+ }
+
+ protected function getAllFieldNames(): array {
+ $names = static::$coreFields[$this->objectType];
+ if ($this->hasRegistrar()) {
+ $names = array_merge($names, array_keys(self::$registrars[$this->content]));
+ }
+ if ($this->product) {
+ $names = array_merge($names, $this->getAllProductFieldNames());
+ }
+ return array_unique($names);
+ }
+
+ protected function hasRegistrar():bool
+ {
+ return array_key_exists($this->content, self::$registrars);
+ }
+
+ public function getFieldConfig(string $name): ?array {
+ if (!$this->hasRegistrar()) return null;
+ return self::$registrars[$this->content][$name] ?? null;
+ }
+
+ protected function getCoreDefault(string $name):string
+ {
+ return match($name) {
+ 'post_status' => 'draft',
+ 'post_date','post_modified' => date('mysql'),
+ 'post_name' => sanitize_title($this->get('post_title')),
+ default => ''
+ };
+ }
+
+ protected function metaKey(string $name): string {
+ if ($this->product) {
+ if (in_array($name, $this->getAllProductFieldNames())) {
+ return $name;
+ }
+ }
+ return jvbCheckBase($name);
+ }
+
+ /**
+ * Get item ID
+ */
+ public function id(): int|string|null
+ {
+ return $this->ID;
+ }
+
+ public function config(string $name):?array
+ {
+ return $this->getFieldConfig($name);
+ }
+
+ protected function log(string $message, array $context = []):void
+ {
+ $caller = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'] ?? '';
+ error_log('[Meta] '.$caller.' '.$message);
+ if (!empty($context)) {
+ error_log(' CONTEXT: '.print_r($context, true));
+ }
+ }
}
--
Gitblit v1.10.0