slug = $slug; $this->config = $config; $this->taxonomy = BASE . $slug; $this->fieldRegistry = FieldRegistry::getInstance(); $this->fields = $this->fieldRegistry->getFields($slug, 'term'); // Set up custom table hooks if this is a content taxonomy if ($this->config['is_content'] ?? false) { $this->setupContentTaxonomyHooks(); } } public function register(): void { $singular = $this->config['singular'] ?? ucfirst($this->slug); $plural = $this->config['plural'] ?? $singular . 's'; $args = [ 'labels' => $this->buildLabels($singular, $plural), 'hierarchical' => $this->config['hierarchical'] ?? true, 'public' => $this->config['public'] ?? true, 'show_ui' => $this->config['show_ui'] ?? true, 'show_admin_column' => $this->config['show_admin_column'] ?? true, 'show_in_rest' => $this->config['show_in_rest'] ?? true, 'rewrite' => $this->config['rewrite'] ?? [ 'slug' => $this->slug, 'with_front' => false ] ]; $post_types = array_map(fn($type) => BASE . $type, $this->config['for_content'] ?? []); register_taxonomy($this->taxonomy, $post_types, $args); $this->maybeAddRewriteRule($args['rewrite']); if (!empty($this->fields)) { $meta_registry = new MetaRegistry($this->fields, $this->slug, 'term'); $meta_registry->registerMetaFields(); } } /** * Add custom rewrite rule for hierarchical taxonomy slugs (e.g., post-type/by/taxonomy-slug) */ protected function maybeAddRewriteRule(array $rewrite_config): void { $slug = $rewrite_config['slug'] ?? $this->slug; // Only add custom rule if slug contains slashes (hierarchical path) if (!str_contains($slug, '/')) { return; } add_rewrite_rule( "^{$slug}/([^/]+)/?$", 'index.php?' . $this->taxonomy . '=$matches[1]', 'top' ); } private function buildLabels(string $singular, string $plural): array { return [ 'name' => $plural, 'singular_name' => $singular, 'search_items' => "Search {$plural}", 'all_items' => "All {$plural}", 'edit_item' => "Edit {$singular}", 'update_item' => "Update {$singular}", 'add_new_item' => "Add New {$singular}", 'new_item_name' => "New {$singular} Name", 'menu_name' => $singular ]; } /** * Set up hooks for content taxonomies to maintain custom table data */ protected function setupContentTaxonomyHooks(): void { // Hook into term creation/updates add_action('created_term', [$this, 'handleTermSave'], 10, 3); add_action('edited_term', [$this, 'handleTermSave'], 10, 3); // Hook into term meta changes add_action('added_term_meta', [$this, 'handleTermMetaChange'], 10, 4); add_action('updated_term_meta', [$this, 'handleTermMetaChange'], 10, 4); add_action('deleted_term_meta', [$this, 'handleTermMetaChange'], 10, 4); add_action('add_term_relationship', [$this, 'addUserToContentType'], 10, 3); add_action('delete_term_relationships', [$this, 'removeUserFromContentType'], 10, 3); } public function addUserToContentType($postID, $termIDs, $taxonomy):void { if ($taxonomy !== $this->taxonomy) { return; } if (empty(jvbGetUserContentTypes($postID))) { return; } $userID = get_post_meta($postID, BASE . 'link', true); if (!is_numeric($userID)) { return; } foreach ($termIDs as $termID) { $users = get_term_meta($termID, BASE.'users', true); $users = ($users === '') ? [] : explode(',', $users); $users[] = $userID; update_term_meta($termID, BASE.'users', implode(',', $users)); } } public function removeUserFromContentType($postID, $termIDs, $taxonomy):void { if ($taxonomy !== $this->taxonomy) { return; } if (empty(jvbGetUserContentTypes($postID))) { return; } $userID = get_post_meta($postID, BASE . 'link', true); if (!is_numeric($userID)) { return; } foreach ($termIDs as $termID) { $users = get_term_meta($termID, BASE.'users', true); $users = ($users === '') ? [] : explode(',', $users); unset($users[array_search($userID, $users)]); update_term_meta($termID, BASE.'users', implode(',', $users)); } } /** * Handle term save (creation and updates) * @param int $term_id The term ID * @param int $taxonomy_id The taxonomy term ID * @param string $taxonomy The taxonomy name */ public function handleTermSave(int $term_id, int $taxonomy_id, string $taxonomy): void { if ($taxonomy !== $this->taxonomy) { return; } $this->syncTermToCustomTable($term_id); } /** * Handle term meta changes * @param int $meta_id The meta ID * @param int $term_id The term ID * @param string $meta_key The meta key * @param mixed $meta_value The meta value */ public function handleTermMetaChange(int $meta_id, int $term_id, string $meta_key, $meta_value): void { // Only handle meta for our taxonomy $term = get_term($term_id); if (!$term || is_wp_error($term) || $term->taxonomy !== $this->taxonomy) { return; } $this->syncTermToCustomTable($term_id); } /** * Sync a single term to the custom table * @param int $term_id The term ID to sync */ public function syncTermToCustomTable(int $term_id): void { global $wpdb; $table = $wpdb->prefix . BASE . 'content_' . $this->slug; $term = get_term($term_id, $this->taxonomy); if (!$term || is_wp_error($term)) { return; } // Get custom table fields $custom_fields = $this->getCustomTableFields(); if (empty($custom_fields)) { return; } // Prepare data for insertion/update $data = [ 'term_id' => $term_id, 'name' => html_entity_decode($term->name), 'slug' => $term->slug, 'updated_at' => current_time('mysql') ]; // Get meta manager for this term $meta = new MetaManager($term_id, 'term'); $values = $meta->getAll(array_keys($custom_fields)); // Process each custom field foreach ($custom_fields as $field_name => $field_config) { $value = $values[$field_name]; // Handle different field types $data = $this->processFieldForCustomTable($data, $field_name, $value, $field_config); } foreach ($data as $k => $v) { switch ($v) { case 0: case '': case false: $data[$k] = null; break; } } // Check if record exists $exists = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE term_id = %d", $term_id )); if ($exists) { // Update existing record $wpdb->update($table, $data, ['term_id' => $term_id]); } else { // Insert new record $wpdb->insert($table, $data); } } /** * Process a field value for storage in custom table * @param array $data Current data array * @param string $field_name Field name * @param mixed $value Field value * @param array $field_config Field configuration * @return array Updated data array */ protected function processFieldForCustomTable(array $data, string $field_name, $value, array $field_config): array { $field_type = $field_config['type'] ?? 'text'; switch ($field_type) { case 'location': // Handle location fields - they create multiple columns if (is_array($value)) { $data["{$field_name}_address"] = $value['address'] ?? ''; $data["{$field_name}_lat"] = (float)($value['lat'] ?? 0); $data["{$field_name}_lng"] = (float)($value['lng'] ?? 0); $data["{$field_name}_street"] = $value['street'] ?? ''; $data["{$field_name}_city"] = $value['city'] ?? ''; $data["{$field_name}_province"] = $value['province'] ?? ''; $data["{$field_name}_postal_code"] = $value['postal_code'] ?? ''; $data["{$field_name}_country"] = $value['country'] ?? ''; } break; case 'taxonomy': // Handle taxonomy references $limit = $field_config['limit'] ?? null; if ($limit === 1) { // Single selection - store as integer $data[$field_name] = is_array($value) ? (int)($value[0] ?? 0) : (int)$value; } else { // Multiple selections - store as JSON $data[$field_name] = is_array($value) ? json_encode(array_map('intval', $value)) : $value; } break; case 'user': // Handle user references $limit = $field_config['limit'] ?? null; if ($limit === 1) { $data[$field_name] = is_array($value) ? (int)($value[0] ?? 0) : (int)$value; } else { $data[$field_name] = is_array($value) ? json_encode(array_map('intval', $value)) : (int)$value; } break; case 'repeater': case 'gallery': case 'set': case 'checkbox': // Store complex data as JSON $data[$field_name] = is_array($value) ? json_encode($value) : $value; break; case 'image': case 'file': // Store attachment ID $data[$field_name] = (int)$value; break; case 'number': $data[$field_name] = (int)$value; break; case 'true_false': $data[$field_name] = (bool)$value; break; default: // Store as string for text, textarea, email, url, etc. $data[$field_name] = (string)$value; break; } return $data; } /** * Get custom table fields for this taxonomy * @return array Field definitions */ protected function getCustomTableFields():array { return jvbContentTaxonomiesTableFields($this->slug)['fields'] ?? []; } }