Files
peptide-repo-core/includes/class-pr-core.php
peptiderepo 850279e756 feat(cpt): consolidate peptide CPT with PSA; drop pr_peptide (v0.2.0)
Resolves the rewrite slug collision that was 404'ing all 89 production
peptide detail pages. PR Core v0.1.1 registered an empty pr_peptide CPT
claiming /peptides/%postname%/; WP's rewrite resolver picked it over PSA's
populated peptide CPT. This release consolidates both registrations onto a
single peptide CPT owned by PR Core. PSA v4.5.0 drops its registration in
favor of this one.

## Contract (per CTO msg 02)
- POST_TYPE: pr_peptide -> peptide
- TAX_CATEGORY: pr_peptide_category -> peptide_category
- TAX_FAMILY const removed (pr_peptide_family was never populated)
- CPT + taxonomy registration guarded with post_type_exists() / taxonomy_exists()
  so deploy order between PR Core and PSA is forgiving
- Args harmonized as superset of PSA + prior PR Core (thumbnail added to
  supports; capability_type + map_meta_cap preserved)
- Activator: flush_rewrite_rules(false) on activation; new
  maybe_flush_on_version_change() handler hooked at init:999 for in-place
  updates that bypass the activation hook (e.g. SCP deploys)
- uninstall.php: blanket DELETE replaced with JOIN on _pr_core_authored so
  PSA-authored peptide posts (the 89 canonical monographs) are never destroyed;
  peptide_category terms preserved (shared ownership)
- Scrub scope: narrow — CPT-slug-derived identifiers only; class names,
  file names, PR_CORE_* constants, pr_core_* options, and pr-core/v1 REST
  namespace remain

## Docs
- ARCHITECTURE.md: new Key Decision #7 (v0.2.0 consolidation) and §2.9
  Uninstall specification
- CONVENTIONS.md: CPT Ownership rule + _pr_core_authored flag contract

## Tests
- Lightweight harness (tests/bootstrap.php) that stubs the handful of WP
  functions exercised by the CPT class
- tests/unit/test-peptide-cpt.php asserts: constants match contract,
  TAX_FAMILY is removed, guard no-ops when post_type_exists/taxonomy_exists
  returns true, args payload shape (thumbnail in supports, rewrite slug,
  REST namespace), hooks wired
- CI: new 'Unit tests (lightweight, no PHPUnit)' step in lint-php job,
  runs across PHP 8.1/8.2/8.3. Full PHPUnit + wp-env harness is a
  follow-up

Refs: convo/prcore/threads/2026-04-peptides-cpt-collision/02-cto-correction.md
2026-04-22 08:32:00 +00:00

132 lines
4.3 KiB
PHP

<?php
declare(strict_types=1);
/**
* Main orchestrator for the Peptide Repo Core plugin.
*
* What: Registers all hooks, runs migrations, boots subsystems.
* Who calls it: peptide-repo-core.php on plugins_loaded.
* Dependencies: PR_Core_Migration_Runner, PR_Core_Peptide_CPT, PR_Core_Admin,
* PR_Core_Disclaimer, PR_Core_Jsonld, PR_Core_Rest_Controller.
*
* @see peptide-repo-core.php — Bootstrap that instantiates this class.
* @see ARCHITECTURE.md — Full data flow diagram.
*/
class PR_Core {
/**
* Initialize all plugin subsystems.
*
* Side effects: registers WordPress hooks, runs pending migrations.
*
* @return void
*/
public function init(): void {
// Run migrations before anything else.
$runner = new PR_Core_Migration_Runner();
$runner->run_pending();
// Register CPT + taxonomies (fires on init).
$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();
$admin->register_hooks();
}
// Frontend: disclaimer shortcode + JSON-LD.
$disclaimer = new PR_Core_Disclaimer();
$disclaimer->register_hooks();
$jsonld = new PR_Core_Jsonld();
$jsonld->register_hooks();
// REST API.
$rest = new PR_Core_Rest_Controller();
$rest->register_hooks();
// Expose public API filters.
$this->register_public_filters();
}
/**
* Register the public filter hooks that consumer plugins rely on.
*
* Filters:
* - pr_core_get_indexable_corpus: Returns indexable content for search plugins.
* - pr_core_disclaimer_for_surface: Returns disclaimer text for a given surface.
* - pr_core_evidence_strength_label: Maps enum to human-readable label.
*
* @return void
*/
private function register_public_filters(): void {
add_filter( 'pr_core_get_indexable_corpus', [ $this, 'filter_indexable_corpus' ] );
add_filter( 'pr_core_disclaimer_for_surface', [ $this, 'filter_disclaimer_for_surface' ], 10, 2 );
add_filter( 'pr_core_evidence_strength_label', [ $this, 'filter_evidence_label' ], 10, 2 );
}
/**
* Return indexable corpus entries for search plugins (e.g., Peptide Search AI).
*
* Each entry: { id, url, title, body, type }.
*
* @param array<int, array<string, mixed>> $entries Existing entries from other filters.
* @return array<int, array<string, mixed>>
*/
public function filter_indexable_corpus( array $entries ): array {
$repo = new PR_Core_Peptide_Repository();
$peptides = $repo->find_all( [ 'status' => 'publish' ] );
foreach ( $peptides as $dto ) {
$entries[] = [
'id' => $dto->id,
'url' => get_permalink( $dto->id ),
'title' => $dto->title,
'body' => $dto->excerpt . "\n" . $dto->content,
'type' => 'peptide_monograph',
];
}
return $entries;
}
/**
* Return disclaimer text for a given surface identifier.
*
* @param string $text Existing text (empty string default).
* @param string $surface Surface identifier (dosing, legal, reconstitution, ai-answer).
* @return string Disclaimer HTML.
*/
public function filter_disclaimer_for_surface( string $text, string $surface ): string {
return PR_Core_Disclaimer::get_disclaimer_text( $surface );
}
/**
* Map evidence_strength enum value to human-readable label.
*
* @param string $label Existing label (empty string default).
* @param string $strength Enum value.
* @return string Localized label.
*/
public function filter_evidence_label( string $label, string $strength ): string {
$map = [
'preclinical' => __( 'Preclinical', 'peptide-repo-core' ),
'case-series' => __( 'Case Series', 'peptide-repo-core' ),
'observational' => __( 'Observational', 'peptide-repo-core' ),
'rct-small' => __( 'Small RCT', 'peptide-repo-core' ),
'rct-large' => __( 'Large RCT', 'peptide-repo-core' ),
'meta-analysis' => __( 'Meta-Analysis', 'peptide-repo-core' ),
];
return $map[ $strength ] ?? $strength;
}
}