registrar = $registrar; $this->registerHooks(); } protected function registerHooks():void { switch ($this->registrar->getType()) { case 'post': add_action('save_post', [$this, 'checkPostFields'], 10, 2); add_action('enqueue_block_editor_assets', [$this, 'enqueue']); break; case 'term': add_action('saved_term', [$this, 'checkTermFields']); break; case 'user': add_action('profile_update', [$this, 'checkUserFields']); break; } add_action('init', [$this, 'registerMeta']); } public function enqueue():void { if ($this->registrar->getType() !== 'post') { return; } $screen = get_current_screen(); if (!$screen || $screen->post_type !== $this->registrar->getBased()) { return; } $asset = include JVB_DIR.'/build/_meta/fields.asset.php'; wp_enqueue_script( 'jvb-editor-fields', JVB_URL.'build/_meta/fields.js', $asset['dependencies'], $asset['version'], true ); wp_add_inline_script( 'jvb-editor-fields', 'window.jvbFieldConfig = '.wp_json_encode([ 'fields' => array_map(function($f) { $f['name'] = jvbCheckBase($f['name']); $f['id'] = jvbCheckBase($f['name']); if ($f['type'] === 'selector') { $f['taxonomy'] = jvbCheckBase($f['taxonomy']); } if (array_key_exists('condition', $f)) { $f['condition']['field'] = jvbCheckBase($f['condition']['field']); } return $f; },array_values($this->registrar->getFields(false))), 'sections' => array_map(function ($s) { $s['id'] = jvbCheckBase($s['slug']); $s['fields'] = array_map(function ($f) { return jvbCheckBase($f); }, $s['fields']); return $s; },$this->registrar->getSections()), ]).';', 'before' ); } public function registerMeta():void { foreach ($this->registrar->getFields(false) as $name => $config) { $metaType = $this->getSchemaType($config); $rawSchema = in_array($metaType, ['array', 'object'], true) ? $this->determineSchema($config) : true; //Allow for null if ($metaType === 'object') { // $rawSchema['type'] = ['object', 'null']; } $schema = is_array($rawSchema) ? $this->schema($rawSchema) : $rawSchema; $default = $config['type'] === 'repeater' ? $this->determineDefault($schema['schema']['properties']??[]) : $config['default'] ?? MetaTypeManager::getType($metaType)['default'] ?? ''; if ($metaType === 'boolean' && $default === '') { $default = 0; } if ($name === 'lineItems') { //TODO: We need to get the actual type. Removing the boolean and number fields fixed the error, but obviously that's not fixing it $unset = ['makeCustom', 'quantity', 'price', 'taxAmount','discountAmount']; foreach ($unset as $u) { unset($default[$u]); unset($schema['schema']['properties'][$u]); } } register_post_meta($this->registrar->getBased(), jvbCheckBase($name), [ 'single' => true, 'type' => $metaType, 'default' => $default, 'sanitize_callback' => fn($value) => (Sanitizer::sanitize($value, $config)), 'auth_callback' => fn($allowed, $key, $postID) => current_user_can('edit_post', $postID), 'show_in_rest' => $schema, ]); } } protected function getSchemaType(array $config):string { $type = MetaTypeManager::getMetaType($config['type']); if ($config['type'] === 'text' && array_key_exists('subtype', $config) && $config['subtype'] === 'number'){ $type = 'number'; } return $type; } protected function determineSchema(array $config):array|bool { return match ($config['type']) { 'url' => $this->schemaText('uri'), 'email' => $this->schemaText('email'), 'text' => $this->checkText($config), 'select','set','radio','checkbox' => $this->schemaOptions($config), 'repeater' => $this->schemaRepeater($config), 'taglist' => $this->schemaTagList($config), 'group' => $this->schemaGroup($config), 'location' => $this->schemaLocation($config), default => true }; } protected function schema(array $additional = []):array { return [ 'schema' => $additional ]; } protected function checkText(array $config):array|true { if (!array_key_exists('subtype', $config)) { return true; } return match ($config['subtype']) { 'url' => $this->schemaText('url'), 'email' => $this->schemaText('email'), default => true }; } protected function schemaText(string $type):array|true { if (!in_array($type, ['uri', 'email', 'date-time', 'hex-color', 'uuid', 'ip'])) { error_log('Attempted to set unknown schema type for Meta: '.$type); return true; } return $this->schema(['type' => 'string', 'format' => $type]); } protected function schemaOptions(array $config):array|true { if (!array_key_exists('options', $config) || empty($config['options'])) { return true; } return $this->schema([ 'type' => 'string', 'enum' => $config['options'] ]); } protected function schemaRepeater(array $config):array|true { $schema = [ 'type' => 'object', ]; $properties = []; $required = []; foreach ($config['fields']??[] as $name => $c) { $args = [ 'type' => $this->getSchemaType($c) ]; if (array_key_exists('required', $c) && $c['required']) { $required[] = $name; } $tmp = $this->determineSchema($c); if (is_array($tmp)) { if (array_key_exists('default', $tmp)) { $args['default'] = $tmp['default']; } if (array_key_exists('enum', $tmp)) { $args['enum'] = $tmp['enum']; } if (array_key_exists('format', $tmp)) { $args['format'] = $tmp['format']; } } if (array_key_exists('default', $c)) { //TODO: We should check for any stored default for the SEO things $args['default'] = $c['default']; } $properties[$name] = $args; } $schema['properties'] = $properties; if (!empty($required)) { $schema['required'] = $required; } return $schema; } protected function schemaTagList(array $config):array|true { //Likely just the same as repeater return $this->schemaRepeater($config); } protected function schemaGroup(array $config):array|true { //Likely just the same as repeater return $this->schemaRepeater($config); } protected function schemaLocation(array $config):array|true { return [ 'type' => ['object', 'null'], 'properties' => [ 'address' => [ 'type' => 'string' ], 'lat' => [ 'type' => 'number', ], 'lng' => [ 'type' => 'number', ], 'street' => [ 'type' => 'string' ], 'city' => [ 'type' => 'string' ], 'province' => [ 'type' => 'string' ], 'postal_code' => [ 'type' => 'string' ], 'country' => [ 'type' => 'string' ], ] ]; } public function checkPostFields(int $ID, WP_Post $post):void { if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return; if (wp_is_post_revision($ID) || wp_is_post_autosave($ID)) return; if ($post->post_type !== $this->registrar->getBased()) { return; } $taxFields = array_filter($this->registrar->getFields(false), function ($field) { return array_key_exists('type', $field) && $field['type'] === 'selector' && (!array_key_exists('isReference', $field) || !$field['isReference']); }); if (empty($taxFields)) { return; } foreach ($taxFields as $name => $config) { $tax = jvbCheckBase($config['taxonomy']); $value = get_post_meta($ID, BASE.$name, true); $termIDs = array_map('intval', array_filter(explode(',', $value))); $result = wp_set_object_terms($ID, $termIDs, $tax); } } public function checkTermFields(int $ID, int $ttID, string $taxonomy):void { } public function checkUserFields(int $ID, WP_User $oldUser, array $userData):void { } protected function determineDefault(array $properties):array { $out = []; foreach ($properties as $prop => $value) { $out[$prop] = match ($value) { 'object' => [], default => '' }; } return $out; } }