From f8ffc3dc1748b9af63123eb281696c0c30448715 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 30 Aug 2026 03:43:48 +0000
Subject: [PATCH] =Meta overhaul, attempting to simplify. Maybe it did?
---
inc/meta/Meta.php | 957 ++++++++++++++++++++++++++++++++---------------------------
1 files changed, 519 insertions(+), 438 deletions(-)
diff --git a/inc/meta/Meta.php b/inc/meta/Meta.php
index fcf262a..0482cbc 100644
--- a/inc/meta/Meta.php
+++ b/inc/meta/Meta.php
@@ -1,423 +1,521 @@
<?php
namespace JVBase\meta;
+use JVBase\managers\Cache;
use JVBase\registrar\Registrar;
-use WP_Post;
-use WP_Term;
-use WP_User;
if (!defined('ABSPATH')) {
exit;
}
-class Meta
-{
+class Meta {
/**
* @var string post, term, user, or options
*/
- protected string $type;
+ protected string $objectType;
/**
- * @var ?string the full slug, with BASE
+ * @var string|null post type, user role, or taxonomy
*/
- 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 ?string $content;
+ /**
+ * @var int|string <int>post, term, or user ID OR {string} option key base
+ */
protected int|string $ID;
- protected MetaTypeManager $typeManager;
- protected static array $instances = ['post' => [],'term' => [], 'user'=>[],'options'=>[]];
-
- protected array $defaults = ['post_thumbnail'];
/**
- * Create Meta instance for a post
+ * @var array of Registrar's defined fields, if any
*/
- public static function forPost(int $id): self
- {
- 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;
- }
-
+ protected static array $registrars = [];
/**
- * Create Meta instance for a term
+ * @var array Field values, stored in memory
*/
- public static function forTerm(int $id): self
- {
- if (array_key_exists($id, self::$instances['term'])) {
- return self::$instances['term'][$id];
+ protected array $fields;
+
+ protected ?\WC_Product $product = null;
+
+ protected static Validator $validator;
+ protected static Sanitizer $sanitizer;
+
+ 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' => []
+ ];
+
+ protected static array $instances = [
+ 'post' => [],
+ 'term' => [],
+ 'user' => [],
+ 'options'=> [],
+ ];
+
+ private function __construct(string $objectType, int|string $ID) {
+ $this->objectType = $objectType;
+ $this->ID = $ID;
+ $this->content = $this->resolveBasedType();
+
+ if ($this->content && !array_key_exists($this->content, self::$registrars)) {
+ $registrar = Registrar::getInstance($this->content);
+ if ($registrar) {
+ self::$registrars[$this->content] = $registrar->getFields();
+ }
}
- $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
- {
- if (array_key_exists($id, self::$instances['user'])) {
- return self::$instances['user'][$id];
+ 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;
}
- $new = new self($id, 'user');
- self::$instances['user'][$id] = $new;
- return $new;
- }
-
- /**
- * Create Meta instance for options
- */
- public static function forOptions(?string $baseKey = 'ajv'): self
- {
- if (array_key_exists($baseKey, self::$instances['options'])) {
- return self::$instances['options'][$baseKey];
+ $this->buildData();
+ if (!isset(self::$sanitizer)) {
+ self::$sanitizer = new Sanitizer();
}
- $new = new self($baseKey, 'options');
- self::$instances['options'][$baseKey] = $new;
- return $new;
+ if (!isset(self::$validator)) {
+ self::$validator = new Validator();
+ }
}
- /***************************************************************
- * Constructor
- ***************************************************************/
+ 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
+ );
- public function __construct(int|string|null $id, string $type)
+ //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??'';
+ }
+ }
+ }
+
+ public static function forPost(int $ID):self
{
- $this->validator = new Validator();
- $this->sanitizer = new Sanitizer();
- $this->typeManager = new MetaTypeManager();
- $this->type = $type;
- $this->ID = $id;
- $this->buildData($id, $type);
+ 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];
}
- protected function buildData(int|string|null $id, string $type):void
+ public static function forTerm(int $ID):self
{
- $this->wpObject = match($type) {
- 'post' => get_post($id),
- 'term' => get_term($id),
- 'user', 'integrations' => get_userdata($id),
- default => false
- };
+ 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];
+ }
- $this->slug = match($type) {
- 'post' => $this->wpObject->post_type,
- 'term' => $this->wpObject->taxonomy,
- 'user' => jvbUserRole($id),
+ public static function forUser(int $ID):self
+ {
+ 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 static function forOptions(string $base = 'default'):self
+ {
+ 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];
+ }
+
+ 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
};
+ }
- $registrar = !is_null($this->slug) ? Registrar::getInstance($this->slug) : false;
- $fields = $registrar ? $registrar->getFields() : [];
- $meta = match($type) {
- 'post' => get_post_meta($id),
- 'term' => get_term_meta($id),
- 'user' => get_user_meta($id),
- default => []
+
+
+ /***************************************************************************
+ * GETTING FIELDS
+ ***************************************************************************/
+ public function get(string $name): mixed {
+ if (array_key_exists($name, $this->fields)) {
+ return $this->fields[$name];
+ }
+
+ 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;
+ }
+
+ public function getAll(?array $names = null): array {
+ $names = $names ?? $this->getAllFieldNames();
+ $out = [];
+ foreach ($names as $name) {
+ $out[$name] = $this->get($name);
+ }
+ return $out;
+ }
+
+ 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,
};
- $meta = array_map(fn($value) => maybe_unserialize($value[0]), $meta);
+ }
- foreach ($fields as $fieldName => $config) {
- $fieldName = jvbNoBase($fieldName);
- if ($this->wpObject && property_exists($this->wpObject, $fieldName)) {
- $config['wp'] = true;
- $value = $this->wpObject->$fieldName;
- } 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 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);
}
- $this->fields[$fieldName] = new Field($fieldName, $value, $config);
- }
- }
- // ─────────────────────────────────────────────────────────────
- // Magic Methods for Fluent Access
- // ─────────────────────────────────────────────────────────────
-
- public function __get(string $name): mixed
- {
- return $this->get($name);
- }
-
- public function __set(string $name, mixed $value): void
- {
- $this->set($name, $value);
- }
-
- public function __isset(string $name): bool
- {
- return $this->item->hasField($name);
- }
-
- // ─────────────────────────────────────────────────────────────
- // Core API
- // ─────────────────────────────────────────────────────────────
-
- /**
- * Get a field value
- */
- public function get(string $name): mixed
- {
- // Handle repeater subfield path
- if (str_contains($name, ':')) {
- return $this->getByPath($name);
- }
-
- return $this->fields[$name]->get();
- }
-
- /**
- * Set a field value (validates & sanitizes by default)
- */
- 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);
- }
-
- $field = $this->fields[$name]??false;
- if (!$field) {
- error_log('No config found for field: '.$name);
- return $this;
- }
-
- // Validate
- if (!$this->validator->validate($value, $field->config)) {
- $field->addError("Validation failed for {$name}");
- return $this;
- }
-
- // Sanitize
- $value = $this->sanitizer->sanitize($value, $field->config);
- $field->set($value);
- if ($autosave && $field->isDirty) {
- $this->save();
- }
-
- return $this;
- }
-
- /**
- * Get multiple fields
- */
- public function getAll(array $fields = []): array
- {
- if (empty($fields) || $fields === ['all']) {
- $fields = array_keys($this->fields);
- }
- $fields = array_filter($this->fields, function ($field) use ($fields) {
- return in_array($field, $fields);
- }, ARRAY_FILTER_USE_KEY);
-
- return array_map(function ($field) {
- return $field->value;
- }, $fields);
- }
-
- /**
- * Set multiple fields
- */
- public function setAll(array $data):bool
- {
- foreach ($data as $name => $value) {
- $this->set($name, $value, false);
- }
- return $this->save();
- }
-
- /**
- * Save all dirty fields to database
- */
- public function save(): bool
- {
- $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;
- });
-
- $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':
- $result = wp_update_term($this->ID, $this->slug, $defaults);
- break;
- case 'user':
- $data = array_merge([
- 'ID' => $this->ID
- ], $defaults);
- $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',
- 'options', 'option' => 'update_option',
- default => false,
- };
- if (!$function) {
- error_log('[Meta]::save() Invalid type, cannot save: '.$this->type);
+ if (!self::$validator->validate($value, $field)) {
return false;
}
- foreach ($custom as $field) {
- $result = $function($this->ID, BASE.$field->name, $field->value);
- if (!$result) {
- error_log('Problem saving field: '.$field->name.' with value: '.print_r($field->value, true));
- }
- }
- 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'));
- }
+
+ $value = self::$sanitizer->sanitize($value, $field);
}
-
- return $success;
+ 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);
}
- /**
- * Delete a field value
- */
- public function delete(string $name): bool
- {
- $field = $this->fields[$name]??false;
- if (!$field) {
- error_log('[Meta]::delete Could not delete field '.$name.': not registered');
- return true;
- }
-
- 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),
- 'options' => delete_option(BASE.$name),
- default => false
- };
- }
-
-
-
- /**
- * Delete multiple field values
- */
- public function deleteAll(array $names): array
- {
+ 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 = [];
- foreach ($names as $name) {
- $results[$name] = $this->delete($name);
+ 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;
}
- /*****************************************************************
- * Repeater Access
- *****************************************************************/
+ protected function setAllCoreFields(array $data):array
+ {
+ $results = [];
- /**
- * Get repeater accessor for fluent repeater operations
- */
- public function repeater(string $name): Repeater
+ $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));
+ }
+ }
+ $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)
+ };
+ }
+ public function deleteAll(array $fieldNames):array
+ {
+ $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);
+ }
+
+ 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;
+ }
+
+ protected function deleteCoreField(string $name):bool
+ {
+ 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): self
+ protected function setByPath(string $path, mixed $value):bool
{
- error_log('Setting by path: '.$path.', with value: '.print_r($value, true));
$parts = explode(':', $path, 3);
if (count($parts) !== 3) {
- return $this;
+ $this->log('Could not set value by path');
+ return false;
}
-
[$repeaterName, $rowIndex, $subField] = $parts;
- $this->repeater($repeaterName)->setField((int) $rowIndex, $subField, $value);
-
- return $this;
+ $this->clearCache();
+ return $this->repeater($repeaterName)->setField((int) $rowIndex, $subField, $value);
}
- protected function getByPath(string $path): mixed
+ protected function getByPath(string $path):mixed
{
$parts = explode(':', $path, 3);
if (count($parts) !== 3) {
@@ -427,102 +525,85 @@
[$repeaterName, $rowIndex, $subField] = $parts;
return $this->repeater($repeaterName)->field((int) $rowIndex, $subField);
}
+ /****************************************************************************************
+ * GROUP helpers
+ ****************************************************************************************/
- // ─────────────────────────────────────────────────────────────
- // Utility Methods
- // ─────────────────────────────────────────────────────────────
-
-
- /**
- * Discard all unsaved changes
- */
- public function reset(): self
+ public function group(string $name): Group
{
- $this->item->resetAll();
- return $this;
+ return new Group($this, $name);
}
-
- /**
- * Get field configuration
- */
- public function config(string $name): ?array
+ protected function setGroup(string $name, mixed $value):bool
{
- return $this->fields[$name]->config??null;
+ [$parent, $child] = explode('|', $name, 2);
+ $this->clearCache();
+ return $this->group($parent)->set($child, $value);
}
-
- /**
- * Get item ID
- */
- public function id(): int|string|null
+ /***************************************************************************************
+ * UTILITY
+ ***************************************************************************************/
+ protected function clearCache():void
{
- return $this->ID;
+ 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');
}
- /**
- * Get object type (post, term, user, options)
- */
- public function objectType(): string
- {
- return $this->type;
- }
-
- /**
- * Get content type (tattoo, artist, etc)
- */
- public function contentType(): ?string
- {
- return $this->contentType;
- }
-
-
- // ─────────────────────────────────────────────────────────────
- // Protected Helpers
- // ─────────────────────────────────────────────────────────────
-
- /**
- * Check for field update overrides
- */
- public 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;
- }
+ protected function getAllFieldNames(): array {
+ $names = static::$coreFields[$this->objectType];
+ if ($this->hasRegistrar()) {
+ $names = array_merge($names, array_keys(self::$registrars[$this->content]));
}
-
- return false;
+ if ($this->product) {
+ $names = array_merge($names, $this->getAllProductFieldNames());
+ }
+ return array_unique($names);
}
- /**
- * Get default value for a field type
- */
- protected function getDefaultValue(string $name): mixed
+ protected function hasRegistrar():bool
{
- $config = $this->item->getFieldConfig($name);
- $type = $config['type'] ?? 'text';
+ return array_key_exists($this->content, self::$registrars);
+ }
- return match ($this->typeManager->getMetaType($type)) {
- 'object', 'array' => [],
- 'boolean' => false,
- 'integer' => 0,
- default => '',
+ 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);
+ }
+
+ 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