$plural,
'singular_name' => $singular,
'search_items' => 'Search ' . $plural,
'all_items' => 'All ' . $plural,
'parent_item' => 'Parent ' . $singular,
'parent_item_colon' => 'Parent ' . $singular . ':',
'edit_item' => 'Edit ' . $singular,
'update_item' => 'Update ' . $singular,
'add_new_item' => 'Add New ' . $singular,
'new_item_name' => 'New ' . $singular . ' Name',
'menu_name' => $plural,
);
$args = array(
'labels' => $labels,
'hierarchical' => ! empty( $tax['hierarchical'] ),
'public' => ! empty( $tax['public'] ),
'show_ui' => ! empty( $tax['public'] ),
'show_admin_column' => ! empty( $tax['show_admin_column'] ),
'show_in_rest' => ! empty( $tax['show_in_rest'] ),
'query_var' => true,
'rewrite' => array( 'slug' => $slug ),
);
$post_types = isset( $tax['post_types'] ) && is_array( $tax['post_types'] ) ? $tax['post_types'] : array( 'post' );
register_taxonomy( $slug, $post_types, $args );
}
}
public function admin_menu() {
$hook = add_submenu_page(
'metaforge',
'Taxonomies',
'Taxonomies',
'manage_metaforge',
'metaforge_taxonomies',
array( $this, 'admin_page' )
);
add_action( "load-{$hook}", array( $this, 'load_screen' ) );
add_submenu_page(
null,
'Add Taxonomy',
'Add Taxonomy',
'manage_metaforge',
'metaforge_taxonomies_add',
'__return_null'
);
}
public function load_screen() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : '';
if ( $action !== 'add' && $action !== 'edit' ) {
require_once METAFORGE_PATH . 'admin/TaxonomyListTable.php';
$this->list_table = new MetaForge_TaxonomyListTable();
$screen = get_current_screen();
if ( $screen ) {
register_column_headers( $screen, $this->list_table->get_columns() );
}
}
}
public function admin_page() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only UI action parameter.
$action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : '';
if ( $action === 'add' || $action === 'edit' ) {
$this->render_form();
return;
}
$this->render_list();
}
private function render_list() {
if ( isset( $this->list_table ) ) {
$list_table = $this->list_table;
} else {
require_once METAFORGE_PATH . 'admin/TaxonomyListTable.php';
$list_table = new MetaForge_TaxonomyListTable();
}
$list_table->prepare_items();
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$search_val = isset( $_REQUEST['s'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['s'] ) ) : '';
$is_empty = empty( $list_table->items ) && empty( $search_val );
?>
$item ) {
if ( ( $item['slug'] ?? '' ) === $search_slug ) {
$index = $i;
break;
}
}
} elseif ( isset( $_GET['index'] ) ) {
$index = absint( wp_unslash( $_GET['index'] ) );
}
$new_tax = array(
'plural_label' => sanitize_text_field( wp_unslash( $_POST['plural_label'] ?? '' ) ),
'singular_label' => sanitize_text_field( wp_unslash( $_POST['singular_label'] ?? '' ) ),
'slug' => sanitize_key( wp_unslash( $_POST['slug'] ?? '' ) ),
'hierarchical' => isset( $_POST['hierarchical'] ) ? 1 : 0,
'public' => isset( $_POST['public'] ) ? 1 : 0,
'show_in_rest' => isset( $_POST['show_in_rest'] ) ? 1 : 0,
'show_admin_column' => isset( $_POST['show_admin_column'] ) ? 1 : 0,
'post_types' => isset( $_POST['post_types'] ) && is_array( $_POST['post_types'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['post_types'] ) ) : array(),
);
$existing_slugs = array_column( $taxs, 'slug' );
if ( $index !== null && isset( $taxs[ $index ] ) ) {
$current_slug = $taxs[ $index ]['slug'];
$existing_slugs = array_diff( $existing_slugs, array( $current_slug ) );
} else {
$current_slug = '';
}
if ( empty( $new_tax['plural_label'] ) ) {
$this->form_error = '⚠️ Plural label cannot be empty.';
} elseif ( empty( $new_tax['singular_label'] ) ) {
$this->form_error = '⚠️ Singular label cannot be empty.';
} elseif ( empty( $new_tax['slug'] ) ) {
$this->form_error = '⚠️ Slug cannot be empty.';
} elseif ( in_array( $new_tax['slug'], $existing_slugs ) || ( $new_tax['slug'] !== $current_slug && taxonomy_exists( $new_tax['slug'] ) ) ) {
$this->form_error = '⚠️ A Taxonomy with this slug already exists. Please use a unique slug to prevent conflicts.';
}
if ( ! $this->form_error ) {
if ( $index !== null && isset( $taxs[ $index ] ) ) {
$taxs[ $index ] = $new_tax;
} else {
$taxs[] = $new_tax;
$index = array_key_last( $taxs );
}
update_option( 'metaforge_taxonomies', $taxs );
// Flush caches so field groups using taxonomy fields pick up changes immediately
delete_transient( 'metaforge_all_field_groups_cache' );
flush_rewrite_rules();
wp_safe_redirect( admin_url( 'admin.php?page=metaforge_taxonomies&action=edit&slug=' . rawurlencode( $new_tax['slug'] ) . '&saved=true' ) );
exit;
}
}
}
private function render_form() {
$taxs = get_option( 'metaforge_taxonomies', array() );
$index = null;
$saved = false;
$error = $this->form_error;
// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only UI parameters.
if ( isset( $_GET['slug'] ) && ! empty( $_GET['slug'] ) ) {
$search_slug = sanitize_key( wp_unslash( $_GET['slug'] ) );
foreach ( $taxs as $i => $item ) {
if ( ( $item['slug'] ?? '' ) === $search_slug ) {
$index = $i;
break;
}
}
} elseif ( isset( $_GET['index'] ) ) {
$index = intval( wp_unslash( $_GET['index'] ) );
}
$tax = ( $index !== null && isset( $taxs[ $index ] ) ) ? $taxs[ $index ] : array();
// phpcs:enable WordPress.Security.NonceVerification.Recommended
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only notice query parameter.
if ( isset( $_GET['saved'] ) && sanitize_text_field( wp_unslash( $_GET['saved'] ) ) === 'true' ) {
$saved = true;
}
// phpcs:disable WordPress.Security.NonceVerification.Missing -- Nonce verified prior to setting form_error if submitted.
if ( $error && isset( $_POST['metaforge_save_tax'] ) ) {
$plural = sanitize_text_field( wp_unslash( $_POST['plural_label'] ?? '' ) );
$singular = sanitize_text_field( wp_unslash( $_POST['singular_label'] ?? '' ) );
$slug = sanitize_key( wp_unslash( $_POST['slug'] ?? '' ) );
$hierarchical = isset( $_POST['hierarchical'] ) ? 1 : 0;
$public = isset( $_POST['public'] ) ? 1 : 0;
$rest = isset( $_POST['show_in_rest'] ) ? 1 : 0;
$show_admin_column = isset( $_POST['show_admin_column'] ) ? 1 : 0;
$pts = isset( $_POST['post_types'] ) && is_array( $_POST['post_types'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['post_types'] ) ) : array();
} else {
// phpcs:enable WordPress.Security.NonceVerification.Missing
$plural = $tax['plural_label'] ?? '';
$singular = $tax['singular_label'] ?? '';
$slug = $tax['slug'] ?? '';
$hierarchical = $tax['hierarchical'] ?? 1;
$public = $tax['public'] ?? 1;
$rest = $tax['show_in_rest'] ?? 1;
$show_admin_column = $tax['show_admin_column'] ?? 1;
$pts = $tax['post_types'] ?? array( 'post' );
}
// Get all post types to allow assigning taxonomy to them
$all_post_types = get_post_types( array( 'public' => true ), 'objects' );
if ( isset( $all_post_types['attachment'] ) ) {
unset( $all_post_types['attachment'] );
}
$is_edit = $index !== null;
?>
'Unauthorized' ) );
}
$prompt = sanitize_text_field( wp_unslash( $_POST['prompt'] ?? '' ) );
if ( empty( $prompt ) ) {
wp_send_json_error( array( 'message' => 'Please provide a prompt.' ) );
}
$client = new MetaForge_APIClient();
if ( ! $client->is_ready() ) {
wp_send_json_error( array( 'message' => 'AI is not configured. Please set your API key in MetaForge -> AI Settings.' ) );
}
$all_post_types = get_post_types( array( 'public' => true ), 'objects' );
$pt_list = array();
foreach ( $all_post_types as $pt ) {
$pt_list[] = $pt->name;
}
$system_prompt = "You are a WordPress developer assistant.
Your task is to generate custom taxonomy registration configuration based on the user's prompt.
Respond ONLY with a valid raw JSON object. Do not include markdown blocks.
JSON format:
{
\"plural_label\": \"Genres\",
\"singular_label\": \"Genre\",
\"slug\": \"genre\",
\"hierarchical\": 1,
\"public\": 1,
\"show_admin_column\": 1,
\"show_in_rest\": 1,
\"post_types\": [\"post\"]
}
In post_types, select relevant values from: " . implode( ', ', $pt_list );
$response = $client->generate( $system_prompt, 'Generate a taxonomy for: ' . $prompt, true );
if ( is_wp_error( $response ) ) {
wp_send_json_error( array( 'message' => $response->get_error_message() ) );
}
$response = trim( $response );
if ( strpos( $response, '```json' ) === 0 ) {
$response = trim( substr( $response, 7, -3 ) );
} elseif ( strpos( $response, '```' ) === 0 ) {
$response = trim( substr( $response, 3, -3 ) );
}
$decoded = json_decode( $response, true );
if ( ! $decoded || empty( $decoded['slug'] ) ) {
wp_send_json_error( array( 'message' => 'Failed to parse AI response.' ) );
}
$decoded = map_deep( $decoded, 'sanitize_text_field' );
wp_send_json_success( $decoded );
}
public function delete_taxonomy_completely( $slug ) {
$slug = sanitize_key( $slug );
if ( empty( $slug ) ) {
return false;
}
// 1. Remove from metaforge_taxonomies option
$taxs = get_option( 'metaforge_taxonomies', array() );
$found = false;
foreach ( $taxs as $index => $tax ) {
if ( isset( $tax['slug'] ) && $tax['slug'] === $slug ) {
unset( $taxs[ $index ] );
$found = true;
break;
}
}
if ( $found ) {
update_option( 'metaforge_taxonomies', array_values( $taxs ) );
}
// 2. Unregister taxonomy from WP runtime
if ( taxonomy_exists( $slug ) ) {
unregister_taxonomy( $slug );
}
// 3. Remove taxonomy from metaforge_taxonomy_order_settings
$order_settings = get_option( 'metaforge_taxonomy_order_settings', array() );
if ( isset( $order_settings['enabled_taxonomies'] ) && is_array( $order_settings['enabled_taxonomies'] ) ) {
if ( in_array( $slug, $order_settings['enabled_taxonomies'], true ) ) {
$order_settings['enabled_taxonomies'] = array_values( array_diff( $order_settings['enabled_taxonomies'], array( $slug ) ) );
update_option( 'metaforge_taxonomy_order_settings', $order_settings );
}
}
// 4. Clean up Field Group location rules referencing this taxonomy
$field_groups = get_posts(
array(
'post_type' => 'metaforge_group',
'posts_per_page' => -1,
'post_status' => 'any',
)
);
foreach ( $field_groups as $fg ) {
$config = get_post_meta( $fg->ID, '_metaforge_group_config', true );
if ( is_array( $config ) && ! empty( $config['location_rules'] ) ) {
$rule_groups_changed = false;
$new_location_rules = array();
foreach ( $config['location_rules'] as $rule_group ) {
$new_rule_group = array();
foreach ( $rule_group as $rule ) {
if ( ( $rule['param'] ?? '' ) === 'taxonomy' && ( $rule['value'] ?? '' ) === $slug ) {
$rule_groups_changed = true;
} else {
$new_rule_group[] = $rule;
}
}
if ( ! empty( $new_rule_group ) ) {
$new_location_rules[] = $new_rule_group;
}
}
if ( $rule_groups_changed ) {
$config['location_rules'] = $new_location_rules;
update_post_meta( $fg->ID, '_metaforge_group_config', $config );
}
}
}
// 5. Clear field groups cache transient
delete_transient( 'metaforge_all_field_groups_cache' );
// 6. Flush rewrite rules
flush_rewrite_rules();
return $found;
}
public function ajax_delete() {
if ( ! check_ajax_referer( 'metaforge_delete_tax_nonce', 'nonce', false ) && ! check_ajax_referer( 'metaforge_taxonomy_generation_nonce', 'nonce', false ) ) {
wp_send_json_error( array( 'message' => 'Security check failed. Please refresh the page and try again.' ) );
}
if ( ! current_user_can( 'manage_metaforge' ) ) {
wp_send_json_error( array( 'message' => 'Unauthorized' ) );
}
$slug = isset( $_POST['slug'] ) ? sanitize_key( $_POST['slug'] ) : '';
if ( empty( $slug ) ) {
wp_send_json_error( array( 'message' => 'Invalid taxonomy slug.' ) );
}
$success = $this->delete_taxonomy_completely( $slug );
if ( $success ) {
wp_send_json_success();
}
wp_send_json_error( array( 'message' => 'Taxonomy not found or could not be deleted.' ) );
}
}