[ 'type', 'default', 'sanitize_callback', 'show_in_rest' ]. * * @return array> */ public static function get_meta_fields(): array { return [ 'display_name' => [ 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ], 'aliases' => [ 'type' => 'string', 'default' => '[]', 'sanitize' => [ PR_Core_Schema_Sanitizers::class, 'sanitize_json_array' ], ], 'molecular_formula' => [ 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ], 'molecular_weight' => [ 'type' => 'number', 'default' => 0, 'sanitize' => 'floatval', ], 'cas_number' => [ 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ], 'drugbank_id' => [ 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ], 'chembl_id' => [ 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ], 'evidence_strength' => [ 'type' => 'string', 'default' => 'preclinical', 'sanitize' => [ __CLASS__, 'sanitize_evidence_strength' ], ], 'editorial_review_status' => [ 'type' => 'string', 'default' => 'draft', 'sanitize' => [ __CLASS__, 'sanitize_review_status' ], ], 'last_editorial_review_at' => [ 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ], 'medical_editor_id' => [ 'type' => 'integer', 'default' => 0, 'sanitize' => 'absint', ], '_pr_last_source_verified' => [ 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ], '_pr_last_reviewed' => [ 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ], '_pr_next_review_by' => [ 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ], '_pr_verification_velocity' => [ 'type' => 'string', 'default' => 'medium', 'sanitize' => [ PR_Core_Verification_Sanitizers::class, 'sanitize_velocity' ], ], '_pr_verification_notes' => [ 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_textarea_field', ], '_pr_verification_status' => [ 'type' => 'string', 'default' => 'current', 'sanitize' => [ PR_Core_Verification_Sanitizers::class, 'sanitize_status' ], ], // Schema-input meta (v0.6.0): molecular data sourced from PSA psa_* keys via migration 0004. '_pr_molecular_formula' => [ 'type' => 'string', 'default' => '', 'sanitize' => [ PR_Core_Schema_Sanitizers::class, 'sanitize_molecular_formula' ], ], '_pr_molecular_weight' => [ 'type' => 'string', 'default' => '', 'sanitize' => [ PR_Core_Schema_Sanitizers::class, 'sanitize_molecular_weight_string' ], ], '_pr_aliases' => [ 'type' => 'string', 'default' => '[]', 'sanitize' => [ PR_Core_Schema_Sanitizers::class, 'sanitize_json_array' ], ], // FAQ items (v0.6.0): [{question, answer}] JSON for FAQPage schema. Populated by CMO content sprint. '_pr_faq_items' => [ 'type' => 'string', 'default' => '[]', 'sanitize' => [ PR_Core_Schema_Sanitizers::class, 'sanitize_faq_items' ], ], ]; } /** * Register WordPress hooks for CPT and taxonomy registration. * * @return void */ public function register_hooks(): void { add_action( 'init', [ __CLASS__, 'register_peptide_post_type' ] ); add_action( 'init', [ __CLASS__, 'register_taxonomies' ] ); add_action( 'init', [ __CLASS__, 'register_meta_fields' ] ); } /** * Register the `peptide` CPT. Guarded with post_type_exists() so deploy order is irrelevant. * * Side effects: registers CPT with WordPress. * * @return void */ public static function register_peptide_post_type(): void { if ( post_type_exists( self::POST_TYPE ) ) { return; } $labels = [ 'name' => __( 'Peptides', 'peptide-repo-core' ), 'singular_name' => __( 'Peptide', 'peptide-repo-core' ), 'add_new_item' => __( 'Add New Peptide', 'peptide-repo-core' ), 'edit_item' => __( 'Edit Peptide', 'peptide-repo-core' ), 'new_item' => __( 'New Peptide', 'peptide-repo-core' ), 'view_item' => __( 'View Peptide', 'peptide-repo-core' ), 'search_items' => __( 'Search Peptides', 'peptide-repo-core' ), 'not_found' => __( 'No peptides found', 'peptide-repo-core' ), 'not_found_in_trash' => __( 'No peptides found in trash', 'peptide-repo-core' ), 'all_items' => __( 'All Peptides', 'peptide-repo-core' ), 'menu_name' => __( 'Peptides', 'peptide-repo-core' ), ]; // Args harmonized superset of PR Core + PSA for 89 existing posts. // Supports union, capability/role perms preserved, slugs unchanged. $args = [ 'labels' => $labels, 'public' => true, 'publicly_queryable' => true, 'show_ui' => true, 'show_in_menu' => true, 'show_in_nav_menus' => true, 'show_in_rest' => true, 'rest_base' => 'peptides', // Note: 'rest_namespace' is deliberately omitted. Custom namespaces prevent // Gutenberg's block editor from loading posts for editing (it fetches the // hardcoded wp/v2 REST route). WordPress defaults to wp/v2 — appropriate for // this CPT — and keeps the REST endpoint at /wp-json/wp/v2/peptides/. 'menu_position' => 25, 'menu_icon' => 'dashicons-database', 'capability_type' => 'post', 'map_meta_cap' => true, 'hierarchical' => false, 'has_archive' => true, 'rewrite' => [ 'slug' => 'peptides', 'with_front' => false ], 'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt', 'revisions', 'custom-fields' ], ]; register_post_type( self::POST_TYPE, $args ); } /** * Register the `peptide_category` taxonomy. Guarded with taxonomy_exists(). * * Side effects: registers taxonomy with WordPress. * * @return void */ public static function register_taxonomies(): void { if ( taxonomy_exists( self::TAX_CATEGORY ) ) { return; } register_taxonomy( self::TAX_CATEGORY, self::POST_TYPE, [ 'labels' => [ 'name' => __( 'Peptide Categories', 'peptide-repo-core' ), 'singular_name' => __( 'Peptide Category', 'peptide-repo-core' ), ], 'public' => true, 'publicly_queryable' => true, 'show_in_rest' => true, 'show_ui' => true, 'show_admin_column' => true, 'hierarchical' => true, 'rewrite' => [ 'slug' => 'peptide-category', 'with_front' => false ], ] ); } /** * Register post-meta fields. Auth gated on manage_peptide_content. * * @return void */ public static function register_meta_fields(): void { foreach ( self::get_meta_fields() as $key => $config ) { register_post_meta( self::POST_TYPE, $key, [ 'type' => $config['type'], 'single' => true, 'default' => $config['default'], 'show_in_rest' => true, 'sanitize_callback' => $config['sanitize'], 'auth_callback' => static function () { return current_user_can( self::CAPABILITY ); }, ] ); } } /** Sanitize evidence_strength to allowed enum values. */ public static function sanitize_evidence_strength( $value ): string { $value = sanitize_text_field( (string) $value ); return in_array( $value, self::EVIDENCE_STRENGTHS, true ) ? $value : 'preclinical'; } /** Sanitize editorial_review_status to allowed enum values. */ public static function sanitize_review_status( $value ): string { $value = sanitize_text_field( (string) $value ); return in_array( $value, self::REVIEW_STATUSES, true ) ? $value : 'draft'; } }