plugin = $plugin; add_action( 'admin_menu', array( $this, 'admin_menu' ) ); add_action( 'admin_init', array( $this, 'handle_actions' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); } public function admin_menu() { add_menu_page( __( 'Ansico CPT & Taxonomies', 'ansico-cpt-and-taxonomies' ), __( 'Ansico CPT', 'ansico-cpt-and-taxonomies' ), 'manage_options', 'ansico-cptax', array( $this, 'render_cpts_page' ), 'dashicons-database-add', 58 ); add_submenu_page( 'ansico-cptax', __( 'Custom Post Types', 'ansico-cpt-and-taxonomies' ), __( 'Custom Post Types', 'ansico-cpt-and-taxonomies' ), 'manage_options', 'ansico-cptax', array( $this, 'render_cpts_page' ) ); add_submenu_page( 'ansico-cptax', __( 'Taxonomies', 'ansico-cpt-and-taxonomies' ), __( 'Taxonomies', 'ansico-cpt-and-taxonomies' ), 'manage_options', 'ansico-cptax-taxonomies', array( $this, 'render_taxonomies_page' ) ); add_submenu_page( 'ansico-cptax', __( 'Tools', 'ansico-cpt-and-taxonomies' ), __( 'Tools', 'ansico-cpt-and-taxonomies' ), 'manage_options', 'ansico-cptax-tools', array( $this, 'render_tools_page' ) ); } public function enqueue_assets( $hook_suffix ) { if ( false === strpos( $hook_suffix, 'ansico-cptax' ) ) { return; } wp_enqueue_style( 'ansico-cptax-admin', ANSICO_CPTAX_URL . 'assets/admin.css', array(), ANSICO_CPTAX_VERSION ); wp_enqueue_script( 'ansico-cptax-admin', ANSICO_CPTAX_URL . 'assets/admin.js', array(), ANSICO_CPTAX_VERSION, true ); } public function handle_actions() { if ( ! current_user_can( 'manage_options' ) ) { return; } if ( empty( $_POST['ansico_cptax_action'] ) ) { return; } $action = sanitize_text_field( wp_unslash( $_POST['ansico_cptax_action'] ) ); if ( 'export_json' === $action ) { check_admin_referer( 'ansico_cptax_export' ); $this->download_export(); } check_admin_referer( 'ansico_cptax_save' ); if ( 'save_cpt' === $action ) { $record = $this->sanitize_cpt_request(); if ( ! empty( $record['is_override'] ) ) { $records = $this->plugin->get_cpt_overrides(); $records = $this->upsert_by_key( $records, sanitize_key( wp_unslash( $_POST['current_key'] ?? '' ) ), $record, 'key' ); $this->plugin->save_cpt_overrides( $records ); } else { $records = $this->plugin->get_cpts(); $records = $this->upsert_by_key( $records, sanitize_key( wp_unslash( $_POST['current_key'] ?? '' ) ), $record, 'key' ); $this->plugin->save_cpts( $records ); } flush_rewrite_rules(); wp_safe_redirect( admin_url( 'admin.php?page=ansico-cptax&updated=1' ) ); exit; } if ( 'delete_cpt' === $action ) { $key = sanitize_key( wp_unslash( $_POST['key'] ?? '' ) ); $is_override = ! empty( $_POST['is_override'] ); $records = $is_override ? $this->plugin->get_cpt_overrides() : $this->plugin->get_cpts(); $records = array_values( array_filter( $records, function( $cpt ) use ( $key ) { return ( $cpt['key'] ?? '' ) !== $key; } ) ); if ( $is_override ) { $this->plugin->save_cpt_overrides( $records ); } else { $this->plugin->save_cpts( $records ); } flush_rewrite_rules(); wp_safe_redirect( admin_url( 'admin.php?page=ansico-cptax&deleted=1' ) ); exit; } if ( 'save_taxonomy' === $action ) { $record = $this->sanitize_taxonomy_request(); if ( ! empty( $record['is_override'] ) ) { $records = $this->plugin->get_taxonomy_overrides(); $records = $this->upsert_by_key( $records, sanitize_key( wp_unslash( $_POST['current_key'] ?? '' ) ), $record, 'key' ); $this->plugin->save_taxonomy_overrides( $records ); } else { $records = $this->plugin->get_taxonomies(); $records = $this->upsert_by_key( $records, sanitize_key( wp_unslash( $_POST['current_key'] ?? '' ) ), $record, 'key' ); $this->plugin->save_taxonomies( $records ); } flush_rewrite_rules(); wp_safe_redirect( admin_url( 'admin.php?page=ansico-cptax-taxonomies&updated=1' ) ); exit; } if ( 'delete_taxonomy' === $action ) { $key = sanitize_key( wp_unslash( $_POST['key'] ?? '' ) ); $is_override = ! empty( $_POST['is_override'] ); $records = $is_override ? $this->plugin->get_taxonomy_overrides() : $this->plugin->get_taxonomies(); $records = array_values( array_filter( $records, function( $taxonomy ) use ( $key ) { return ( $taxonomy['key'] ?? '' ) !== $key; } ) ); if ( $is_override ) { $this->plugin->save_taxonomy_overrides( $records ); } else { $this->plugin->save_taxonomies( $records ); } flush_rewrite_rules(); wp_safe_redirect( admin_url( 'admin.php?page=ansico-cptax-taxonomies&deleted=1' ) ); exit; } if ( 'import_json' === $action ) { $replace_existing = ! empty( $_POST['replace_existing'] ); $result = $this->handle_import_json( $replace_existing ); $status = ! empty( $result['error'] ) ? 'import_error=1' : 'imported=1'; $message = ! empty( $result['message'] ) ? rawurlencode( $result['message'] ) : ''; wp_safe_redirect( admin_url( 'admin.php?page=ansico-cptax-tools&' . $status . '&message=' . $message ) ); exit; } } private function download_export() { $filename = 'ansico-cptax-export-' . gmdate( 'Y-m-d-His' ) . '.json'; $payload = $this->plugin->get_export_payload(); nocache_headers(); header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) ); header( 'Content-Disposition: attachment; filename=' . $filename ); echo wp_json_encode( $payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ); exit; } private function handle_import_json( $replace_existing ) { if ( empty( $_FILES['import_file']['tmp_name'] ) ) { return array( 'error' => true, 'message' => __( 'No JSON file was uploaded.', 'ansico-cpt-and-taxonomies' ), ); } $raw = file_get_contents( $_FILES['import_file']['tmp_name'] ); // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown if ( false === $raw || '' === $raw ) { return array( 'error' => true, 'message' => __( 'The uploaded file could not be read.', 'ansico-cpt-and-taxonomies' ), ); } $payload = json_decode( $raw, true ); if ( ! is_array( $payload ) ) { return array( 'error' => true, 'message' => __( 'The uploaded file is not valid JSON.', 'ansico-cpt-and-taxonomies' ), ); } $cpts = $this->normalize_import_records( $payload['cpts'] ?? array(), 'cpt' ); $taxonomies = $this->normalize_import_records( $payload['taxonomies'] ?? array(), 'taxonomy' ); $cpt_overrides = $this->normalize_import_records( $payload['cpt_overrides'] ?? array(), 'cpt' ); $taxonomy_overrides = $this->normalize_import_records( $payload['taxonomy_overrides'] ?? array(), 'taxonomy' ); if ( $replace_existing ) { $this->plugin->save_cpts( $cpts ); $this->plugin->save_taxonomies( $taxonomies ); $this->plugin->save_cpt_overrides( $cpt_overrides ); $this->plugin->save_taxonomy_overrides( $taxonomy_overrides ); } else { $this->plugin->save_cpts( $this->merge_by_key( $this->plugin->get_cpts(), $cpts ) ); $this->plugin->save_taxonomies( $this->merge_by_key( $this->plugin->get_taxonomies(), $taxonomies ) ); $this->plugin->save_cpt_overrides( $this->merge_by_key( $this->plugin->get_cpt_overrides(), $cpt_overrides ) ); $this->plugin->save_taxonomy_overrides( $this->merge_by_key( $this->plugin->get_taxonomy_overrides(), $taxonomy_overrides ) ); } flush_rewrite_rules(); return array( 'error' => false, 'message' => __( 'JSON import completed.', 'ansico-cpt-and-taxonomies' ), ); } private function normalize_import_records( $records, $type ) { $normalized = array(); if ( ! is_array( $records ) ) { return $normalized; } foreach ( $records as $record ) { if ( 'taxonomy' === $type ) { $record = $this->plugin->normalize_taxonomy_record( $record ); } else { $record = $this->plugin->normalize_cpt_record( $record ); } if ( empty( $record['key'] ) ) { continue; } $normalized[] = $record; } return $normalized; } private function merge_by_key( $existing, $incoming ) { foreach ( $incoming as $record ) { $existing = $this->upsert_by_key( $existing, $record['key'] ?? '', $record, 'key' ); } return array_values( $existing ); } private function upsert_by_key( $records, $current_key, $record, $key_field ) { $updated = false; foreach ( $records as $index => $existing ) { if ( ( $existing[ $key_field ] ?? '' ) === $current_key ) { $records[ $index ] = $record; $updated = true; break; } } if ( ! $updated ) { $records[] = $record; } return array_values( $records ); } private function sanitize_cpt_request() { $supports = isset( $_POST['supports'] ) && is_array( $_POST['supports'] ) ? array_map( 'sanitize_key', wp_unslash( $_POST['supports'] ) ) : array(); $taxonomies = isset( $_POST['taxonomies'] ) && is_array( $_POST['taxonomies'] ) ? array_map( 'sanitize_key', wp_unslash( $_POST['taxonomies'] ) ) : array(); $custom_fields = $this->sanitize_custom_fields(); $template_settings = array( 'single_mode' => sanitize_key( wp_unslash( $_POST['single_mode'] ?? 'plugin' ) ), 'archive_mode' => sanitize_key( wp_unslash( $_POST['archive_mode'] ?? 'plugin' ) ), 'single_show_featured' => ! empty( $_POST['single_show_featured'] ) ? 1 : 0, 'single_show_meta' => ! empty( $_POST['single_show_meta'] ) ? 1 : 0, 'single_show_terms' => ! empty( $_POST['single_show_terms'] ) ? 1 : 0, 'single_show_custom_fields' => ! empty( $_POST['single_show_custom_fields'] ) ? 1 : 0, 'archive_show_featured' => ! empty( $_POST['archive_show_featured'] ) ? 1 : 0, 'archive_show_excerpt' => ! empty( $_POST['archive_show_excerpt'] ) ? 1 : 0, 'archive_show_meta' => ! empty( $_POST['archive_show_meta'] ) ? 1 : 0, 'archive_columns' => absint( wp_unslash( $_POST['archive_columns'] ?? 3 ) ), 'archive_intro' => sanitize_textarea_field( wp_unslash( $_POST['archive_intro'] ?? '' ) ), ); return $this->plugin->normalize_cpt_record( array( 'key' => sanitize_key( wp_unslash( $_POST['key'] ?? '' ) ), 'singular_label' => sanitize_text_field( wp_unslash( $_POST['singular_label'] ?? '' ) ), 'plural_label' => sanitize_text_field( wp_unslash( $_POST['plural_label'] ?? '' ) ), 'slug' => sanitize_title( wp_unslash( $_POST['slug'] ?? '' ) ), 'exclude_from_search' => ! empty( $_POST['exclude_from_search'] ) ? 1 : 0, 'can_export' => ! empty( $_POST['can_export'] ) ? 1 : 0, 'supports' => $supports, 'taxonomies' => $taxonomies, 'custom_fields' => $custom_fields, 'template_settings' => $template_settings, 'is_override' => ! empty( $_POST['is_override'] ) ? 1 : 0, ) ); } private function sanitize_taxonomy_request() { $object_type = isset( $_POST['object_type'] ) && is_array( $_POST['object_type'] ) ? array_map( 'sanitize_key', wp_unslash( $_POST['object_type'] ) ) : array(); $template_settings = array( 'archive_mode' => sanitize_key( wp_unslash( $_POST['archive_mode'] ?? 'plugin' ) ), 'archive_show_featured' => ! empty( $_POST['archive_show_featured'] ) ? 1 : 0, 'archive_show_excerpt' => ! empty( $_POST['archive_show_excerpt'] ) ? 1 : 0, 'archive_show_meta' => ! empty( $_POST['archive_show_meta'] ) ? 1 : 0, 'archive_columns' => absint( wp_unslash( $_POST['archive_columns'] ?? 3 ) ), 'archive_intro' => sanitize_textarea_field( wp_unslash( $_POST['archive_intro'] ?? '' ) ), ); return $this->plugin->normalize_taxonomy_record( array( 'key' => sanitize_key( wp_unslash( $_POST['key'] ?? '' ) ), 'singular_label' => sanitize_text_field( wp_unslash( $_POST['singular_label'] ?? '' ) ), 'plural_label' => sanitize_text_field( wp_unslash( $_POST['plural_label'] ?? '' ) ), 'slug' => sanitize_title( wp_unslash( $_POST['slug'] ?? '' ) ), 'hierarchical' => ! empty( $_POST['hierarchical'] ) ? 1 : 0, 'object_type' => $object_type, 'template_settings' => $template_settings, 'is_override' => ! empty( $_POST['is_override'] ) ? 1 : 0, ) ); } private function sanitize_custom_fields() { $labels = isset( $_POST['field_label'] ) && is_array( $_POST['field_label'] ) ? wp_unslash( $_POST['field_label'] ) : array(); $keys = isset( $_POST['field_key'] ) && is_array( $_POST['field_key'] ) ? wp_unslash( $_POST['field_key'] ) : array(); $types = isset( $_POST['field_type'] ) && is_array( $_POST['field_type'] ) ? wp_unslash( $_POST['field_type'] ) : array(); $descriptions = isset( $_POST['field_description'] ) && is_array( $_POST['field_description'] ) ? wp_unslash( $_POST['field_description'] ) : array(); $options = isset( $_POST['field_options'] ) && is_array( $_POST['field_options'] ) ? wp_unslash( $_POST['field_options'] ) : array(); $custom_fields = array(); foreach ( $keys as $index => $key ) { $key = sanitize_key( $key ); if ( ! $key ) { continue; } $raw_options = preg_split( '/[ ,]+/', (string) ( $options[ $index ] ?? '' ) ); $normalized_options = array(); foreach ( $raw_options as $option ) { $option = sanitize_text_field( $option ); if ( '' !== $option ) { $normalized_options[] = $option; } } $custom_fields[] = array( 'label' => sanitize_text_field( $labels[ $index ] ?? '' ), 'key' => $key, 'type' => sanitize_key( $types[ $index ] ?? 'text' ), 'description' => sanitize_text_field( $descriptions[ $index ] ?? '' ), 'options' => $normalized_options, ); } return $custom_fields; } private function render_page_header( $title, $description, $active_tab ) { ?>

render_notices(); } private function render_notices() { if ( ! empty( $_GET['updated'] ) ) { echo '

' . esc_html__( 'Saved successfully.', 'ansico-cpt-and-taxonomies' ) . '

'; } if ( ! empty( $_GET['deleted'] ) ) { echo '

' . esc_html__( 'Deleted successfully.', 'ansico-cpt-and-taxonomies' ) . '

'; } if ( ! empty( $_GET['imported'] ) ) { $message = sanitize_text_field( wp_unslash( $_GET['message'] ?? __( 'Import completed.', 'ansico-cpt-and-taxonomies' ) ) ); echo '

' . esc_html( $message ) . '

'; } if ( ! empty( $_GET['import_error'] ) ) { $message = sanitize_text_field( wp_unslash( $_GET['message'] ?? __( 'Import failed.', 'ansico-cpt-and-taxonomies' ) ) ); echo '

' . esc_html( $message ) . '

'; } } public function render_cpts_page() { $managed = $this->plugin->get_cpts(); $overrides = $this->plugin->get_cpt_overrides(); $edit_key = sanitize_key( wp_unslash( $_GET['edit'] ?? '' ) ); $import_key = sanitize_key( wp_unslash( $_GET['import'] ?? '' ) ); $editing = null; foreach ( array_merge( $managed, $overrides ) as $record ) { if ( $edit_key && ( $record['key'] ?? '' ) === $edit_key ) { $editing = $record; break; } } if ( ! $editing && $import_key ) { $all_post_types = $this->plugin->get_editable_post_types(); if ( isset( $all_post_types[ $import_key ] ) ) { $editing = $this->map_post_type_object_to_record( $all_post_types[ $import_key ] ); $editing['is_override'] = 1; } } $editing = $editing ?: $this->plugin->normalize_cpt_record( array( 'supports' => array( 'title', 'editor' ) ) ); $all_taxonomies = $this->plugin->get_editable_taxonomies(); $all_post_types = $this->plugin->get_editable_post_types(); $field_types = $this->get_field_types(); $template_row = $this->get_field_row_template( $field_types ); $this->render_page_header( __( 'Ansico CPT & Taxonomies', 'ansico-cpt-and-taxonomies' ), __( 'Create new custom post types or import existing post types for plugin-based overrides.', 'ansico-cpt-and-taxonomies' ), 'cpts' ); ?>

>


'Title', 'editor' => 'Editor', 'thumbnail' => 'Featured image', 'author' => 'Author', 'comments' => 'Comments' ) as $feature => $label ) : ?>

$taxonomy ) : ?>

render_field_row( $field, $field_types ); ?>
plugin->get_taxonomies(); $overrides = $this->plugin->get_taxonomy_overrides(); $edit_key = sanitize_key( wp_unslash( $_GET['edit'] ?? '' ) ); $import_key = sanitize_key( wp_unslash( $_GET['import'] ?? '' ) ); $editing = null; foreach ( array_merge( $managed, $overrides ) as $record ) { if ( $edit_key && ( $record['key'] ?? '' ) === $edit_key ) { $editing = $record; break; } } if ( ! $editing && $import_key ) { $all_taxonomies = $this->plugin->get_editable_taxonomies(); if ( isset( $all_taxonomies[ $import_key ] ) ) { $editing = $this->map_taxonomy_object_to_record( $all_taxonomies[ $import_key ] ); $editing['is_override'] = 1; } } $editing = $editing ?: $this->plugin->normalize_taxonomy_record( array( 'object_type' => array( 'post' ) ) ); $post_types = $this->plugin->get_editable_post_types(); $this->render_page_header( __( 'Taxonomies', 'ansico-cpt-and-taxonomies' ), __( 'Create new taxonomies or import existing ones for plugin-based overrides.', 'ansico-cpt-and-taxonomies' ), 'taxonomies' ); ?>

>

plugin->get_export_payload(); $this->render_page_header( __( 'Tools', 'ansico-cpt-and-taxonomies' ), __( 'Export or import plugin-managed custom post types, taxonomies, overrides, and custom fields as JSON.', 'ansico-cpt-and-taxonomies' ), 'tools' ); ?>

' . esc_html__( 'Name', 'ansico-cpt-and-taxonomies' ) . '' . esc_html__( 'Key', 'ansico-cpt-and-taxonomies' ) . '' . esc_html__( 'Slug', 'ansico-cpt-and-taxonomies' ) . '' . esc_html__( 'Actions', 'ansico-cpt-and-taxonomies' ) . ''; if ( empty( $records ) ) { echo '' . esc_html__( 'No items found.', 'ansico-cpt-and-taxonomies' ) . ''; } else { foreach ( $records as $cpt ) { echo ''; echo '' . esc_html( $cpt['plural_label'] ?? $cpt['key'] ) . ''; echo '' . esc_html( $cpt['key'] ?? '' ) . ''; echo '' . esc_html( $cpt['slug'] ?? '' ) . ''; echo '' . esc_html__( 'Edit', 'ansico-cpt-and-taxonomies' ) . ' '; echo '
'; wp_nonce_field( 'ansico_cptax_save' ); echo ''; echo ''; echo ''; submit_button( __( 'Delete', 'ansico-cpt-and-taxonomies' ), 'delete small', '', false, array( 'onclick' => "return confirm('Are you sure?');" ) ); echo '
'; echo ''; } } echo ''; } private function render_taxonomy_table( $records, $is_override ) { echo ''; if ( empty( $records ) ) { echo ''; } else { foreach ( $records as $taxonomy ) { echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } } echo '
' . esc_html__( 'Name', 'ansico-cpt-and-taxonomies' ) . '' . esc_html__( 'Key', 'ansico-cpt-and-taxonomies' ) . '' . esc_html__( 'Slug', 'ansico-cpt-and-taxonomies' ) . '' . esc_html__( 'Actions', 'ansico-cpt-and-taxonomies' ) . '
' . esc_html__( 'No items found.', 'ansico-cpt-and-taxonomies' ) . '
' . esc_html( $taxonomy['plural_label'] ?? $taxonomy['key'] ) . '' . esc_html( $taxonomy['key'] ?? '' ) . '' . esc_html( $taxonomy['slug'] ?? '' ) . '' . esc_html__( 'Edit', 'ansico-cpt-and-taxonomies' ) . ' '; echo '
'; wp_nonce_field( 'ansico_cptax_save' ); echo ''; echo ''; echo ''; submit_button( __( 'Delete', 'ansico-cpt-and-taxonomies' ), 'delete small', '', false, array( 'onclick' => "return confirm('Are you sure?');" ) ); echo '
'; } private function get_field_types() { return array( 'text' => __( 'Text', 'ansico-cpt-and-taxonomies' ), 'textarea' => __( 'Textarea', 'ansico-cpt-and-taxonomies' ), 'number' => __( 'Number', 'ansico-cpt-and-taxonomies' ), 'url' => __( 'URL', 'ansico-cpt-and-taxonomies' ), 'email' => __( 'Email', 'ansico-cpt-and-taxonomies' ), 'date' => __( 'Date', 'ansico-cpt-and-taxonomies' ), 'checkbox' => __( 'Checkbox', 'ansico-cpt-and-taxonomies' ), 'select' => __( 'Select', 'ansico-cpt-and-taxonomies' ), 'radio' => __( 'Radio', 'ansico-cpt-and-taxonomies' ), ); } private function render_field_row( $field, $field_types ) { echo $this->get_field_row_markup( $field, $field_types ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } private function get_field_row_template( $field_types ) { return str_replace( array( "\n", "\r", "\t" ), '', $this->get_field_row_markup( array(), $field_types ) ); } private function get_field_row_markup( $field, $field_types ) { ob_start(); ?> name, $feature ) ) { $supports[] = $feature; } } $taxonomies = get_object_taxonomies( $obj->name, 'names' ); return $this->plugin->normalize_cpt_record( array( 'key' => $obj->name, 'singular_label' => $obj->labels->singular_name ?? $obj->name, 'plural_label' => $obj->labels->name ?? $obj->name, 'slug' => is_array( $obj->rewrite ) && ! empty( $obj->rewrite['slug'] ) ? $obj->rewrite['slug'] : $obj->name, 'exclude_from_search' => ! empty( $obj->exclude_from_search ) ? 1 : 0, 'can_export' => ! empty( $obj->can_export ) ? 1 : 0, 'supports' => $supports, 'taxonomies' => is_array( $taxonomies ) ? $taxonomies : array(), 'custom_fields' => array(), ) ); } private function map_taxonomy_object_to_record( $obj ) { return $this->plugin->normalize_taxonomy_record( array( 'key' => $obj->name, 'singular_label' => $obj->labels->singular_name ?? $obj->name, 'plural_label' => $obj->labels->name ?? $obj->name, 'slug' => is_array( $obj->rewrite ) && ! empty( $obj->rewrite['slug'] ) ? $obj->rewrite['slug'] : $obj->name, 'hierarchical' => ! empty( $obj->hierarchical ) ? 1 : 0, 'object_type' => is_array( $obj->object_type ) ? $obj->object_type : array(), ) ); } }