<?php
|
namespace JVBase\registry;
|
|
use JVBase\meta\MetaManager;
|
use JVBase\meta\MetaRegistry;
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
class TaxonomyRegistrar
|
{
|
private string $slug;
|
private array $config;
|
private array $fields;
|
private string $taxonomy;
|
private FieldRegistry $fieldRegistry;
|
|
public function __construct(string $slug, array $config)
|
{
|
$this->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'] ?? [];
|
}
|
}
|