Backfill psa_*->_pr_* meta (migration 0004 + PubChem fallback); enrich Drug node with {permalink}#drug @id + molecularFormula/Weight/alternateName; rewrite emitter into Yoast schema graph (MedicalWebPage retype + FAQPage, no duplicate nodes).
QA: APPROVE (independent) — qa-reviews/peptide-repo-core/2026-06-13-817690c1b6ec2118ddcf3b8cfe8bc5ea7f3933b3.md
Agent-Session: peptide-schema-backfill v0.6.0 merge
6.7 KiB
Peptide Repo Core — Conventions
Naming
- Class prefix:
PR_Core_— all classes use this prefix. - Hook prefix:
pr_core_— all actions and filters start with this. - Option prefix:
pr_core_— all wp_options keys start with this. - Table prefix:
pr_— custom tables use{wpdb->prefix}pr_*. - CSS prefix:
.pr-core-— admin and frontend styles. - Text domain:
peptide-repo-core.
File Naming
Autoloader convention: PR_Core_Foo_Bar maps to class-pr-core-foo-bar.php.
Files live in includes/ or one level of subdirectory under includes/.
How To: Add a New Post-Meta Field to Peptide Posts
- Add the field to
PR_Core_Peptide_CPT::get_meta_fields()incpt/class-pr-core-peptide-cpt.php. - Add the field to
PR_Core_Peptide_DTOconstructor andto_array()indto/class-pr-core-peptide-dto.php. - Add the field to
PR_Core_Peptide_Repository::post_to_dto()inrepositories/class-pr-core-peptide-repository.php. - Add a form input in
PR_Core_Peptide_Metaboxes::render_identifiers_box()inadmin/class-pr-core-peptide-metaboxes.php. - The
save_meta()method reads from the same field list — no changes needed unless custom save logic applies. - Schema-input meta fields (
_pr_*keys introduced in v0.6.0) usePR_Core_Schema_Sanitizersas the sanitize_callback. Add new sanitizers tocpt/class-pr-core-schema-sanitizers.php, not toPR_Core_Peptide_CPT(keeps CPT under 300 lines). - If the field is purged on uninstall, add it to the
$schema_meta_keysarray inuninstall.php.
How To: Add a New Post-Meta Field to Repo Daily Posts
- Define the meta key (start with
_repo_daily_) and sanitization logic (usesanitize_text_field()for strings, cast to bool for checkboxes). - Create a new metabox or extend
PR_Core_Repo_Daily_Metaboxes::render_meta_box()inadmin/class-pr-core-repo-daily-metaboxes.php. - In
PR_Core_Repo_Daily_Metaboxes::save_meta(), handle the field: read from$_POST, sanitize, and callupdate_post_meta(). - Example: The
_repo_daily_clinical_review_requiredcheckbox checks forisset( $_POST['key'] )and stores'1'or'';get_post_meta()is type-safe because the value is always one of two strings, never a bool.
How To: Add a New Migration
- Create
includes/migrations/class-pr-core-migration-NNNN-description.php. - The class must be named
PR_Core_Migration_NNNN_Descriptionwith a publicup()method. - Add the class name to
PR_Core_Migration_Runner::MIGRATIONSarray (index = version - 1). - Bump
PR_CORE_TARGET_SCHEMA_VERSIONinpeptide-repo-core.php. - All migrations must be idempotent (safe to run twice).
- Idempotency caveat (all-or-nothing skip): migration 0004's skip guard requires all populated keys; a post with legitimately empty aliases will re-run on manual re-invoke — this is safe (write_meta only overwrites with non-empty values) but expected.
How To: Add a New REST Endpoint
- Add a
register_rest_route()call inPR_Core_Rest_Controller::register_routes(). - Implement the callback method in the same class.
- Use
check_write_permission()for any write endpoints. - Return typed DTOs via
->to_array()for consistent response shape.
How To: Add a New Disclaimer Surface
- Add a default text entry in
PR_Core_Disclaimer::DEFAULTS. - Use
[pr_disclaimer surface="new-surface"]in templates. - Custom copy can be set via
PR_Core_Disclaimer::update_disclaimer().
How To: Extend the JSON-LD Drug Schema
v0.6.0 uses four focused classes (see ARCHITECTURE.md §#6). To add a new field to the Drug node:
- Add the logic to
PR_Core_Jsonld_Drug::build()inincludes/frontend/class-pr-core-jsonld-drug.php. - Read data from the appropriate
_pr_*post-meta key (not from PSApsa_*keys directly). - Always omit the field when the meta is empty — never emit blank/null/zero values.
- To add a new schema piece (not Drug), create a new
PR_Core_Jsonld_<Piece>class infrontend/and inject it fromPR_Core_Jsonld::inject_graph_pieces(). - The
pr_core_jsonld_peptidefilter on the Drug node array is preserved for consumer plugins.
Error Handling
- Repository methods return
nullor0on failure, never throw. - REST endpoints return
WP_Errorwith appropriate HTTP status codes. - All database queries use
$wpdb->prepare()for parameterization. - Admin actions verify nonces and capabilities before processing.
Evidence Strength Enum
Ordered weakest to strongest: preclinical, case-series, observational, rct-small, rct-large, meta-analysis. Validated by PR_Core_Peptide_CPT::sanitize_evidence_strength().
Legal Status Enum
Values: prescription, ruo, otc, restricted, banned, unclear. Validated by PR_Core_Legal_Repository::sanitize_row().
Supersede Pattern (Soft Delete)
Dosing rows and legal cells use a superseded_by_id column instead of hard deletes. When a correction is needed, the old row gets superseded_by_id set to the new row's ID. Queries for "active" data filter on superseded_by_id IS NULL. This preserves full audit history.
CPT Ownership
- One plugin owns each CPT. PR Core owns the
peptideCPT and thepeptide_categorytaxonomy (as of v0.2.0). No other plugin in this ecosystem registers these post types or taxonomies directly. - All registrations must guard with existence checks. CPT registrations call
if ( post_type_exists( $slug ) ) return;beforeregister_post_type(), and taxonomy registrations callif ( taxonomy_exists( $slug ) ) return;beforeregister_taxonomy(). This makes deploy order between plugins irrelevant — whichever runs first wins, the other no-ops — and prevents rewrite-slug collisions when two plugins temporarily overlap during a transition. - Consumers read, do not register. Plugins that need to query or display
peptideposts (PSA, PR Theme, PRAutoBlogger, Peptide News, Tier 1 tools) callget_posts( [ 'post_type' => 'peptide' ] )/WP_Querywithout registering the CPT themselves. If a consumer plugin needs its own custom post type for a different domain (e.g.,reconstitution_protocol), it registers its own slug and does not touchpeptide. - Meta keys are plugin-namespaced, not CPT-namespaced. PR Core writes
_pr_core_*or bare keys registered viaregister_post_meta(). PSA writespsa_*. The two namespaces coexist on the samepeptidepost because they don't collide.
_pr_core_authored Flag
Any peptide post created via PR Core's own UI (not authored via PSA's meta boxes) must be tagged with post-meta key _pr_core_authored = 1. This is the flag uninstall.php uses to scope teardown to plugin-owned posts only. Posts originating from PSA (the 89 canonical peptide monographs on production) do not carry this flag and are never deleted on PR Core uninstall.