wpdb = $wpdb; } /** * Load a single field value from database */ public function get(Item $item, string $name): mixed { if ($item->isWpDefault($name)) { return $this->getWpDefault($item, $name); } $metaKey = BASE . $name; return match ($item->objectType) { 'post' => get_post_meta($item->id, $metaKey, true), 'term' => get_term_meta($item->id, $metaKey, true), 'user', 'integrations' => get_user_meta($item->id, $metaKey, true), 'options' => $this->getOption($item, $name), default => '' }; } /** * Load multiple field values in a single query */ public function getAll(Item $item, array $fieldNames): array { if (empty($fieldNames) || !$item->id) { return []; } $defaults = Item::WP_DEFAULTS[$item->objectType] ?? []; $wpFields = array_intersect($defaults, $fieldNames); $metaFields = array_diff($fieldNames, $wpFields); $values = []; // Get meta fields in bulk if (!empty($metaFields)) { $values = $this->bulkGetMeta($item, $metaFields); } // Get WP default fields foreach ($wpFields as $name) { $values[$name] = $this->getWpDefault($item, $name); } return $values; } /** * Save a single field */ public function saveField(Item $item, Field $field): bool { if ($field->isWpDefault()) { return $this->saveWpDefault($item, $field); } if ($field->isTaxonomy()) { return $this->saveTaxonomyField($item, $field); } $metaKey = BASE . $field->name; return match ($item->objectType) { 'post' => update_post_meta($item->id, $metaKey, $field->value) !== false, 'term' => update_term_meta($item->id, $metaKey, $field->value) !== false, 'user', 'integrations' => update_user_meta($item->id, $metaKey, $field->value) !== false, 'options' => $this->saveOption($item, $field), default => false }; } /** * Save all dirty fields on an item */ public function save(Item $item, bool $updateTimestamp = true): bool { $dirty = $item->getDirtyFields(); if (empty($dirty)) { return true; } $this->wpdb->query('START TRANSACTION'); try { foreach ($dirty as $field) { if (!$this->saveField($item, $field)) { throw new Exception("Failed to save field: {$field->name}"); } $field->markClean(); } $this->wpdb->query('COMMIT'); // Update post modified timestamp if ($updateTimestamp && $item->objectType === 'post' && $item->id) { wp_update_post(['ID' => $item->id]); } $this->clearCache($item); return true; } catch (Exception $e) { $this->wpdb->query('ROLLBACK'); JVB()->error()->log('meta_storage', $e->getMessage(), [ 'item_id' => $item->id, 'object_type' => $item->objectType, 'fields' => array_keys($dirty) ], 'error'); return false; } } /** * Delete a field value */ public function delete(Item $item, string $name): bool { $metaKey = BASE . $name; return match ($item->objectType) { 'post' => delete_post_meta($item->id, $metaKey), 'term' => delete_term_meta($item->id, $metaKey), 'user', 'integrations' => delete_user_meta($item->id, $metaKey), 'options' => delete_option($this->optionKey($item, $name)), default => false }; } // ───────────────────────────────────────────────────────────── // Protected helpers // ───────────────────────────────────────────────────────────── protected function getWpDefault(Item $item, string $name): mixed { if (in_array($name, ['featured_image', 'post_thumbnail'])) { return get_post_thumbnail_id($item->id); } return match ($item->objectType) { 'post' => $this->getPostField($item, $name), 'term' => $this->getTermField($item, $name), 'user' => $this->getUserField($item, $name), default => '' }; } protected function getPostField(Item $item, string $name): mixed { return match ($name) { 'post_title' => get_the_title($item->id), 'post_excerpt' => get_the_excerpt($item->id), 'post_content' => get_post_field('post_content', $item->id), default => $item->wpObject->$name ?? '' }; } protected function getTermField(Item $item, string $name): mixed { return match ($name) { 'term_name' => get_term_field('name', $item->id), 'description' => get_term_field('description', $item->id), default => '' }; } protected function getUserField(Item $item, string $name): mixed { return match ($name) { 'display_name' => get_the_author_meta('display_name', $item->id), 'user_email' => get_the_author_meta('user_email', $item->id), 'first_name' => get_the_author_meta('first_name', $item->id), 'last_name' => get_the_author_meta('last_name', $item->id), default => $item->wpObject->$name ?? '' }; } protected function saveWpDefault(Item $item, Field $field): bool { $name = $field->name; $value = $field->value; if (in_array($name, ['featured_image', 'post_thumbnail'])) { return set_post_thumbnail($item->id, $value) !== false; } return match ($item->objectType) { 'post' => wp_update_post(['ID' => $item->id, $name => $value]) !== 0, 'term' => !is_wp_error(wp_update_term($item->id, $item->wpObject->taxonomy, [ $name => $value, 'slug' => $name === 'term_name' ? sanitize_title($value) : null ])), 'user' => wp_update_user(['ID' => $item->id, $name => $value]) !== 0, default => false }; } protected function saveTaxonomyField(Item $item, Field $field): bool { $taxonomy = jvbCheckBase($field->config['taxonomy']); $value = $field->value; if (empty(trim($value))) { wp_set_object_terms($item->id, [], $taxonomy, false); return true; } $termIds = array_map('intval', array_filter(explode(',', $value))); $result = wp_set_object_terms($item->id, $termIds, $taxonomy, false); return !is_wp_error($result); } protected function bulkGetMeta(Item $item, array $fields): array { [$table, $idColumn] = $this->getTableInfo($item->objectType); if (!$table) { return []; } $metaKeys = array_map(fn($f) => BASE . $f, $fields); $placeholders = implode(',', array_fill(0, count($metaKeys), '%s')); $query = $this->wpdb->prepare( "SELECT meta_key, meta_value FROM {$table} WHERE {$idColumn} = %d AND meta_key IN ({$placeholders})", array_merge([$item->id], $metaKeys) ); $results = $this->wpdb->get_results($query, ARRAY_A); $values = array_fill_keys($fields, ''); foreach ($results as $row) { $key = str_replace(BASE, '', $row['meta_key']); $values[$key] = maybe_unserialize($row['meta_value']); } return $values; } protected function getTableInfo(string $objectType): array { return match ($objectType) { 'post' => [$this->wpdb->postmeta, 'post_id'], 'term' => [$this->wpdb->termmeta, 'term_id'], 'user', 'integrations' => [$this->wpdb->usermeta, 'user_id'], default => [null, null] }; } protected function getOption(Item $item, string $name): mixed { return get_option($this->optionKey($item, $name)); } protected function saveOption(Item $item, Field $field): bool { return update_option($this->optionKey($item, $field->name), $field->value); } protected function optionKey(Item $item, string $name): string { return $item->baseKey ? BASE . $item->baseKey . '_' . $name : BASE . $name; } protected function clearCache(Item $item): void { match ($item->objectType) { 'post' => clean_post_cache($item->id), 'term' => clean_term_cache($item->id), 'user', 'integrations' => clean_user_cache($item->id), default => null }; } }