[ 'type', 'default', 'sanitize_callback', 'show_in_rest' ]. * * @return array> */ public static function get_meta_fields(): array { return array( 'display_name' => array( 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ), 'aliases' => array( 'type' => 'string', 'default' => '[]', 'sanitize' => array( PR_Core_Schema_Sanitizers::class, 'sanitize_json_array' ), ), 'molecular_formula' => array( 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ), 'molecular_weight' => array( 'type' => 'number', 'default' => 0, 'sanitize' => 'floatval', ), 'cas_number' => array( 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ), 'drugbank_id' => array( 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ), 'chembl_id' => array( 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ), 'evidence_strength' => array( 'type' => 'string', 'default' => 'preclinical', 'sanitize' => array( __CLASS__, 'sanitize_evidence_strength' ), ), 'editorial_review_status' => array( 'type' => 'string', 'default' => 'draft', 'sanitize' => array( __CLASS__, 'sanitize_review_status' ), ), 'last_editorial_review_at' => array( 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ), 'medical_editor_id' => array( 'type' => 'integer', 'default' => 0, 'sanitize' => 'absint', ), '_pr_last_source_verified' => array( 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ), '_pr_last_reviewed' => array( 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ), '_pr_next_review_by' => array( 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_text_field', ), '_pr_verification_velocity' => array( 'type' => 'string', 'default' => 'medium', 'sanitize' => array( PR_Core_Verification_Sanitizers::class, 'sanitize_velocity' ), ), '_pr_verification_notes' => array( 'type' => 'string', 'default' => '', 'sanitize' => 'sanitize_textarea_field', ), '_pr_verification_status' => array( 'type' => 'string', 'default' => 'current', 'sanitize' => array( 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' => array( 'type' => 'string', 'default' => '', 'sanitize' => array( PR_Core_Schema_Sanitizers::class, 'sanitize_molecular_formula' ), ), '_pr_molecular_weight' => array( 'type' => 'string', 'default' => '', 'sanitize' => array( PR_Core_Schema_Sanitizers::class, 'sanitize_molecular_weight_string' ), ), '_pr_aliases' => array( 'type' => 'string', 'default' => '[]', 'sanitize' => array( 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' => array( 'type' => 'string', 'default' => '[]', 'sanitize' => array( 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', array( __CLASS__, 'register_peptide_post_type' ) ); add_action( 'init', array( __CLASS__, 'register_taxonomies' ) ); add_action( 'init', array( __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 = array( '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 = array( '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' => array( 'slug' => 'peptides', 'with_front' => false, ), 'supports' => array( '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, array( 'labels' => array( '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' => array( '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, array( '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'; } }