feat: initial peptide-repo-core plugin (v0.1.0) — canonical schema sprint
Implements ADR-0002: canonical peptide schema as shared data layer for the peptiderepo.com ecosystem. All Tier 1 tools (Reconstitution Calculator, Dosage Reference, Legal Status Tracker, PSA enhancements) build on this. Includes: - pr_peptide CPT with REST, taxonomies, 11 meta fields - 3 custom tables: pr_dosing_rows, pr_legal_cells, pr_ai_candidate_queue - Sequential migration runner with schema versioning - 4 typed DTOs and 4 repository classes (full CRUD) - REST API (pr-core/v1): 6 endpoints (read public, write auth-gated) - Admin UI: 3 meta boxes, custom columns, AI candidate queue page - Shared disclaimer component (shortcode + static API) - JSON-LD Drug schema emission on single peptide pages - Seed data fixture (3 peptides, 10 dosing rows, 5 legal cells) - Full teardown in uninstall.php - CI: PHP lint 8.0-8.2, PHPCS WordPress, 300-line check All 22 PHP files under 300 lines. Highest: peptide-cpt at 265. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
125
includes/frontend/class-pr-core-jsonld.php
Normal file
125
includes/frontend/class-pr-core-jsonld.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* JSON-LD / schema.org structured data emission for peptide pages.
|
||||
*
|
||||
* What: Emits Drug schema on single peptide pages, Dataset schema on archives.
|
||||
* Who calls it: PR_Core::init() registers wp_head hook.
|
||||
* Dependencies: PR_Core_Peptide_Repository for data lookup.
|
||||
*
|
||||
* Consumer plugins can override via pr_core_jsonld_peptide filter.
|
||||
*
|
||||
* @see ARCHITECTURE.md — Section 2.7 JSON-LD output.
|
||||
*/
|
||||
class PR_Core_Jsonld {
|
||||
|
||||
/**
|
||||
* Register hooks for JSON-LD output.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks(): void {
|
||||
add_action( 'wp_head', [ $this, 'emit_jsonld' ], 99 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit JSON-LD on peptide single pages.
|
||||
*
|
||||
* Side effects: outputs script tag in wp_head.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function emit_jsonld(): void {
|
||||
if ( ! is_singular( PR_Core_Peptide_CPT::POST_TYPE ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = get_the_ID();
|
||||
if ( ! $post_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$repo = new PR_Core_Peptide_Repository();
|
||||
$peptide = $repo->find_by_id( $post_id );
|
||||
|
||||
if ( ! $peptide ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schema = $this->build_drug_schema( $peptide );
|
||||
|
||||
/**
|
||||
* Filter the JSON-LD schema for a peptide page.
|
||||
*
|
||||
* @param array<string, mixed> $schema Schema.org data array.
|
||||
* @param PR_Core_Peptide_DTO $peptide The peptide DTO.
|
||||
*/
|
||||
$schema = apply_filters( 'pr_core_jsonld_peptide', $schema, $peptide );
|
||||
|
||||
if ( empty( $schema ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
printf(
|
||||
'<script type="application/ld+json">%s</script>' . "\n",
|
||||
wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a schema.org Drug object for a peptide.
|
||||
*
|
||||
* @param PR_Core_Peptide_DTO $peptide Peptide data.
|
||||
* @return array<string, mixed> Schema.org JSON-LD structure.
|
||||
*/
|
||||
private function build_drug_schema( PR_Core_Peptide_DTO $peptide ): array {
|
||||
$schema = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@type' => 'Drug',
|
||||
'name' => $peptide->display_name ?: $peptide->title,
|
||||
'url' => get_permalink( $peptide->id ),
|
||||
'description' => $peptide->excerpt,
|
||||
];
|
||||
|
||||
if ( ! empty( $peptide->aliases ) ) {
|
||||
$schema['alternateName'] = $peptide->aliases;
|
||||
}
|
||||
|
||||
if ( '' !== $peptide->molecular_formula ) {
|
||||
$schema['molecularFormula'] = $peptide->molecular_formula;
|
||||
}
|
||||
|
||||
if ( $peptide->molecular_weight > 0 ) {
|
||||
$schema['molecularWeight'] = [
|
||||
'@type' => 'QuantitativeValue',
|
||||
'value' => $peptide->molecular_weight,
|
||||
'unitText' => 'Da',
|
||||
];
|
||||
}
|
||||
|
||||
// External identifiers as codes.
|
||||
$codes = [];
|
||||
if ( '' !== $peptide->cas_number ) {
|
||||
$codes[] = [
|
||||
'@type' => 'MedicalCode',
|
||||
'codeValue' => $peptide->cas_number,
|
||||
'codingSystem' => 'CAS',
|
||||
];
|
||||
}
|
||||
|
||||
if ( '' !== $peptide->drugbank_id ) {
|
||||
$codes[] = [
|
||||
'@type' => 'MedicalCode',
|
||||
'codeValue' => $peptide->drugbank_id,
|
||||
'codingSystem' => 'DrugBank',
|
||||
];
|
||||
}
|
||||
|
||||
if ( ! empty( $codes ) ) {
|
||||
$schema['code'] = $codes;
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user