feat(cpt): consolidate peptide CPT with PSA; drop pr_peptide (v0.2.0) (#1)
Consolidates the `peptide` CPT under PR Core ownership, dropping the `pr_peptide` slug-collision that was 404ing all 89 peptide detail pages. - POST_TYPE: pr_peptide -> peptide - TAX_CATEGORY: pr_peptide_category -> peptide_category - TAX_FAMILY + pr_peptide_family taxonomy removed - Defensive post_type_exists/taxonomy_exists guards on both registrations - Args harmonized as superset of PSA + prior PR Core (adds thumbnail support) - Activation hook + init:999 version-change flush handler - Uninstall scoped to _pr_core_authored flag (PSA-authored posts survive) - Plugin version 0.1.1 -> 0.2.0, CHANGELOG in same commit - ARCHITECTURE.md + CONVENTIONS.md updated with CPT ownership rule QA gate verdict: PASS (qa-reviews/peptide-repo-core/2026-04-22-874f93b.md) Sibling release: peptiderepo/peptide-search-ai#15 (PSA v4.5.0) Known follow-up: manage_peptide_content capability is not granted to any role as of v0.2.0. No current UI/theme reads or writes PR Core-defined meta keys, so no regression. Follow-up thread to decide role assignment.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Custom admin list-table columns for the pr_peptide CPT.
|
||||
* Custom admin list-table columns for the peptide CPT.
|
||||
*
|
||||
* What: Adds evidence strength, editorial status, and dosing count columns.
|
||||
* Who calls it: PR_Core_Admin via manage_posts_columns and custom_column hooks.
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
/**
|
||||
* Meta boxes for the peptide edit screen: scientific identifiers, dosing, and legal.
|
||||
*
|
||||
* What: Renders and saves three meta boxes on the pr_peptide edit screen.
|
||||
* What: Renders and saves three meta boxes on the peptide edit screen.
|
||||
* Who calls it: PR_Core_Admin via add_meta_boxes and save_post hooks.
|
||||
* Dependencies: PR_Core_Dosing_Repository, PR_Core_Legal_Repository.
|
||||
*
|
||||
|
||||
@@ -5,17 +5,26 @@ declare(strict_types=1);
|
||||
* Plugin activation handler.
|
||||
*
|
||||
* What: Runs migrations, adds capabilities, flushes rewrite rules.
|
||||
* Who calls it: register_activation_hook() in peptide-repo-core.php.
|
||||
* Dependencies: PR_Core_Migration_Runner.
|
||||
* Also detects version changes on subsequent init cycles and re-flushes
|
||||
* rewrite rules once so in-place plugin updates (without a deactivate +
|
||||
* reactivate cycle) still pick up CPT/taxonomy slug changes.
|
||||
* Who calls it: register_activation_hook() in peptide-repo-core.php; and
|
||||
* PR_Core::init() registers maybe_flush_on_version_change on init:999.
|
||||
* Dependencies: PR_Core_Migration_Runner, PR_Core_Peptide_CPT.
|
||||
*
|
||||
* @see peptide-repo-core.php — Registers this class on activation.
|
||||
* @see includes/class-pr-core.php — Wires the init:999 version-change handler.
|
||||
*/
|
||||
class PR_Core_Activator {
|
||||
|
||||
/** @var string Option key storing the last activated/seen plugin version. */
|
||||
private const VERSION_OPTION = 'pr_core_version';
|
||||
|
||||
/**
|
||||
* Run on plugin activation.
|
||||
*
|
||||
* Side effects: database table creation, capability grants, rewrite flush.
|
||||
* Side effects: database table creation, capability grants, rewrite flush,
|
||||
* pr_core_version option write.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -32,7 +41,39 @@ class PR_Core_Activator {
|
||||
self::add_capabilities();
|
||||
|
||||
// Flush rewrite rules for the new CPT.
|
||||
flush_rewrite_rules();
|
||||
flush_rewrite_rules( false );
|
||||
|
||||
// Record the activated version so the init:999 drift handler can
|
||||
// detect future in-place updates and re-flush without a manual
|
||||
// deactivate/reactivate cycle.
|
||||
update_option( self::VERSION_OPTION, PR_CORE_VERSION, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect in-place plugin version changes and flush rewrite rules once.
|
||||
*
|
||||
* Wired to init:999 so all CPTs + taxonomies (ours and anyone else's)
|
||||
* are registered before the flush. When the option is absent (first
|
||||
* install that bypassed the activation hook — e.g., manual SCP deploy)
|
||||
* or is older than the current constant, we flush once and update.
|
||||
*
|
||||
* Side effects: may call flush_rewrite_rules( false ); updates option.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function maybe_flush_on_version_change(): void {
|
||||
if ( ! defined( 'PR_CORE_VERSION' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recorded = get_option( self::VERSION_OPTION, '' );
|
||||
|
||||
if ( PR_CORE_VERSION === $recorded ) {
|
||||
return;
|
||||
}
|
||||
|
||||
flush_rewrite_rules( false );
|
||||
update_option( self::VERSION_OPTION, PR_CORE_VERSION, false );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,12 @@ class PR_Core {
|
||||
$cpt = new PR_Core_Peptide_CPT();
|
||||
$cpt->register_hooks();
|
||||
|
||||
// One-shot rewrite flush on in-place version bumps. Runs at the very
|
||||
// end of init (priority 999) so all CPTs/taxonomies — ours and
|
||||
// anyone else's — are registered first. Handles updates deployed
|
||||
// without a deactivate/reactivate cycle (e.g., SCP/rsync pushes).
|
||||
add_action( 'init', [ PR_Core_Activator::class, 'maybe_flush_on_version_change' ], 999 );
|
||||
|
||||
// Admin UI (fires on admin_init, admin_menu).
|
||||
if ( is_admin() ) {
|
||||
$admin = new PR_Core_Admin();
|
||||
|
||||
@@ -2,25 +2,29 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Registers the pr_peptide custom post type and associated taxonomies.
|
||||
* Registers the peptide custom post type and associated taxonomy.
|
||||
*
|
||||
* What: Defines the peptide CPT with REST support, archive, and monograph meta fields.
|
||||
* Who calls it: PR_Core::init() on plugins_loaded.
|
||||
* Dependencies: None.
|
||||
*
|
||||
* Ownership: As of v0.2.0, PR Core is the canonical owner of the `peptide` CPT
|
||||
* and `peptide_category` taxonomy (previously registered by Peptide Search AI
|
||||
* pre-schema-sprint; PSA v4.5.0 drops its registrations in favor of this one).
|
||||
* Registration is guarded by `post_type_exists()` / `taxonomy_exists()` so
|
||||
* deploy order between the two plugins does not matter — whichever runs first
|
||||
* wins, the other no-ops.
|
||||
*
|
||||
* @see ARCHITECTURE.md — CPT specification and post-meta field definitions.
|
||||
* @see cpt/class-pr-core-peptide-taxonomies.php — Not used; taxonomies registered here for simplicity.
|
||||
* @see CONVENTIONS.md — CPT ownership rule (no plugin registers a CPT another plugin owns).
|
||||
*/
|
||||
class PR_Core_Peptide_CPT {
|
||||
|
||||
/** @var string Post type slug. */
|
||||
public const POST_TYPE = 'pr_peptide';
|
||||
/** @var string Post type slug (owned by PR Core since v0.2.0; PSA previously registered this). */
|
||||
public const POST_TYPE = 'peptide';
|
||||
|
||||
/** @var string Taxonomy: category (e.g., GLP-1 agonist). */
|
||||
public const TAX_CATEGORY = 'pr_peptide_category';
|
||||
|
||||
/** @var string Taxonomy: family grouping. */
|
||||
public const TAX_FAMILY = 'pr_peptide_family';
|
||||
public const TAX_CATEGORY = 'peptide_category';
|
||||
|
||||
/** @var string Capability required for editing peptide data. */
|
||||
public const CAPABILITY = 'manage_peptide_content';
|
||||
@@ -129,13 +133,22 @@ class PR_Core_Peptide_CPT {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the pr_peptide custom post type.
|
||||
* Register the `peptide` custom post type.
|
||||
*
|
||||
* Guarded with `post_type_exists()`: if another plugin (historically PSA)
|
||||
* has already registered the `peptide` post type, this call no-ops. This
|
||||
* makes deploy order between PR Core and PSA irrelevant during the
|
||||
* PSA v4.5.0 consolidation transition.
|
||||
*
|
||||
* 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' ),
|
||||
@@ -150,60 +163,77 @@ class PR_Core_Peptide_CPT {
|
||||
'menu_name' => __( 'Peptides', 'peptide-repo-core' ),
|
||||
];
|
||||
|
||||
/*
|
||||
* Args are a harmonized superset of PR Core's prior `pr_peptide` args
|
||||
* and PSA's `peptide` args, so the 89 existing posts (authored under
|
||||
* PSA's registration) keep every capability they relied on:
|
||||
* - supports: union of both — thumbnail comes from PSA, revisions +
|
||||
* custom-fields come from PR Core.
|
||||
* - capability_type + map_meta_cap: preserve PSA's per-role perms.
|
||||
* - rewrite slug `peptides`: unchanged from both plugins, so
|
||||
* /peptides/%postname%/ URLs keep working and theme templates
|
||||
* single-peptide.php / archive-peptide.php continue to match.
|
||||
*/
|
||||
$args = [
|
||||
'labels' => $labels,
|
||||
'public' => true,
|
||||
'has_archive' => true,
|
||||
'rewrite' => [ 'slug' => 'peptides', 'with_front' => false ],
|
||||
'publicly_queryable' => true,
|
||||
'show_ui' => true,
|
||||
'show_in_menu' => true,
|
||||
'show_in_nav_menus' => true,
|
||||
'show_in_rest' => true,
|
||||
'rest_base' => 'peptides',
|
||||
'rest_namespace' => 'pr-core/v1',
|
||||
'supports' => [ 'title', 'editor', 'excerpt', 'revisions', 'custom-fields' ],
|
||||
'menu_position' => 25,
|
||||
'menu_icon' => 'dashicons-database',
|
||||
'capability_type' => 'post',
|
||||
'map_meta_cap' => true,
|
||||
'show_in_menu' => true,
|
||||
'menu_position' => 25,
|
||||
'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 pr_peptide_category and pr_peptide_family taxonomies.
|
||||
* Register the `peptide_category` taxonomy.
|
||||
*
|
||||
* Side effects: registers taxonomies with WordPress.
|
||||
* Guarded with `taxonomy_exists()` for the same reason CPT registration
|
||||
* is guarded. The 8 existing terms + term_relationships stay intact —
|
||||
* they key on taxonomy name `peptide_category` in wp_term_taxonomy,
|
||||
* which is exactly what we register here.
|
||||
*
|
||||
* v0.2.0: `pr_peptide_family` taxonomy removed — never populated, never
|
||||
* surfaced in UI.
|
||||
*
|
||||
* 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' => [
|
||||
'labels' => [
|
||||
'name' => __( 'Peptide Categories', 'peptide-repo-core' ),
|
||||
'singular_name' => __( 'Peptide Category', 'peptide-repo-core' ),
|
||||
],
|
||||
'hierarchical' => true,
|
||||
'show_in_rest' => true,
|
||||
'show_admin_column' => true,
|
||||
'rewrite' => [ 'slug' => 'peptide-category' ],
|
||||
] );
|
||||
|
||||
register_taxonomy( self::TAX_FAMILY, self::POST_TYPE, [
|
||||
'labels' => [
|
||||
'name' => __( 'Peptide Families', 'peptide-repo-core' ),
|
||||
'singular_name' => __( 'Peptide Family', 'peptide-repo-core' ),
|
||||
],
|
||||
'hierarchical' => false,
|
||||
'show_in_rest' => true,
|
||||
'show_admin_column' => true,
|
||||
'rewrite' => [ 'slug' => 'peptide-family' ],
|
||||
'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 with the REST API.
|
||||
*
|
||||
* Side effects: registers meta with WordPress.
|
||||
* Register post-meta fields. Auth gated on manage_peptide_content.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
/**
|
||||
* Typed data-transfer object for a peptide record.
|
||||
*
|
||||
* What: Immutable value object wrapping a pr_peptide post + its meta fields.
|
||||
* What: Immutable value object wrapping a peptide post + its meta fields.
|
||||
* Who calls it: PR_Core_Peptide_Repository returns these; consumers read them.
|
||||
* Dependencies: None.
|
||||
*
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
/**
|
||||
* Repository for peptide CPT records.
|
||||
*
|
||||
* What: CRUD operations over pr_peptide posts, returning typed DTOs.
|
||||
* What: CRUD operations over peptide posts, returning typed DTOs.
|
||||
* Who calls it: PR_Core (public API), REST controller, admin UI, consumer plugins.
|
||||
* Dependencies: WordPress WP_Query, PR_Core_Peptide_DTO.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user