fix(schema): v0.6.2 — move Drug/FAQ injection to wpseo_schema_graph; fix peptide 500
Root cause: inject_graph_pieces() added plain PHP arrays to wpseo_schema_graph_pieces. Yoast Schema_Generator::filter_graph_pieces_to_generate() calls get_class() + is_needed() on every piece expecting Abstract_Schema_Piece objects — a plain array produces: PHP Fatal: get_class(): Argument #1 ($object) must be of type object, array given. All peptide monograph pages returned HTTP 500; non-peptide pages were unaffected (is_singular guard). Fix: remove wpseo_schema_graph_pieces hook entirely. New inject_graph_nodes() method hooks wpseo_schema_graph at priority 12 (after enrich_webpage_piece at 11), which receives the fully-assembled graph as array-of-arrays — safe for plain array appends. Also adds regression test (test-jsonld-peptide-piece.php) that: - Reproduces the v0.6.1 get_class(array) TypeError (fails-before) - Confirms inject_graph_nodes Drug+FAQ emission is correct (passes-after) Agent-Session: jsonld-peptide-piece-fix v0.6.2
This commit is contained in:
13
CHANGELOG.md
13
CHANGELOG.md
@@ -3,6 +3,19 @@
|
||||
All notable changes to Peptide Repo Core are documented here.
|
||||
Format: [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [0.6.2] — 2026-06-13
|
||||
|
||||
### Fixed
|
||||
- **Critical**: Peptide monograph pages (e.g. `/peptides/bpc-157/`) returned HTTP 500 after v0.6.0 launch. Root cause: `PR_Core_Jsonld::inject_graph_pieces()` appended plain PHP arrays (Drug, FAQ) to Yoast's `wpseo_schema_graph_pieces` filter. Yoast's `Schema_Generator::filter_graph_pieces_to_generate()` then called `get_class()` and `is_needed()` on every piece, expecting `Abstract_Schema_Piece` objects — a plain array causes `PHP Fatal: get_class(): Argument #1 ($object) must be of type object, array given`. Non-peptide pages (/, /about/, /peptides/ archive) were unaffected because the `is_singular('peptide')` guard prevented entry.
|
||||
- Fix: removed `wpseo_schema_graph_pieces` hook entirely. Drug and FAQPage nodes are now injected via a new `inject_graph_nodes()` method hooked on `wpseo_schema_graph` at priority 12, which receives Yoast's fully-assembled graph as an array of plain arrays — safe for plain array appends.
|
||||
- Added regression test `tests/unit/test-jsonld-peptide-piece.php` that reproduces the v0.6.1 `get_class(array)` fatal and verifies the v0.6.2 `inject_graph_nodes` path emits Drug + FAQ correctly across peptide, non-peptide, and empty-FAQ scenarios.
|
||||
|
||||
### Changed
|
||||
- `PR_Core_Jsonld::register_hooks()`: no longer registers `wpseo_schema_graph_pieces`; registers `wpseo_schema_graph` at priority 12 for Drug/FAQ injection (in addition to priority 11 for `enrich_webpage_piece`, unchanged).
|
||||
- `PR_Core_Jsonld::inject_graph_pieces()` renamed to `inject_graph_nodes()`; signature updated from `(array $pieces, $context)` to `(array $graph, $context)` to reflect that it now receives the assembled graph, not the pieces array.
|
||||
|
||||
|
||||
|
||||
## [0.6.1] — 2026-06-13
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -16,7 +16,13 @@ declare(strict_types=1);
|
||||
* - PR_Core_Jsonld_Drug, PR_Core_Jsonld_Webpage, PR_Core_Jsonld_Faq (builders).
|
||||
* - PR_Core_Peptide_Repository, PR_Core_Peptide_CPT (data).
|
||||
*
|
||||
* Contract (ratified 2026-06-11, jsonld-contract-v1.md):
|
||||
* Yoast integration contract (ratified 2026-06-13, v0.6.2):
|
||||
* - wpseo_schema_graph_pieces delivers OBJECTS (Abstract_Schema_Piece subclasses)
|
||||
* to Yoast, which calls get_class() + is_needed() on every item. Injecting plain
|
||||
* arrays there causes a PHP Fatal. Drug/FAQ nodes must be injected via
|
||||
* wpseo_schema_graph instead, which receives the fully-assembled graph as
|
||||
* array-of-arrays and is safe for plain array appends.
|
||||
* - wpseo_schema_graph_pieces is therefore left UNHOOKED by this plugin.
|
||||
* - Integrated path: Yoast owns WebPage and BreadcrumbList — we never duplicate.
|
||||
* - Standalone fallback: emits a complete @graph (Drug + FAQPage) only.
|
||||
* - Drug @id: {permalink}#drug (stable for cross-plugin linking).
|
||||
@@ -49,59 +55,68 @@ class PR_Core_Jsonld {
|
||||
/**
|
||||
* Register hooks for JSON-LD output.
|
||||
*
|
||||
* Registers both the Yoast-integrated path (active when Yoast is loaded)
|
||||
* and the standalone wp_head fallback (active only when Yoast is absent).
|
||||
* Registers the Yoast-integrated path via wpseo_schema_graph (safe for plain
|
||||
* array appends) and the standalone wp_head fallback (active only when Yoast
|
||||
* is absent). Does NOT hook wpseo_schema_graph_pieces — Yoast calls
|
||||
* get_class() + is_needed() on every item in that filter's array and will
|
||||
* fatal on a plain PHP array (see class-level docblock).
|
||||
*
|
||||
* Side effects: calls add_filter(), add_action().
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_hooks(): void {
|
||||
// Integrated path: hook into Yoast's schema graph.
|
||||
// wpseo_schema_graph_pieces: filter on the array of schema piece objects.
|
||||
// wpseo_schema_webpage_type: filter returning the WebPage @type string.
|
||||
add_filter( 'wpseo_schema_graph_pieces', [ $this, 'inject_graph_pieces' ], 11, 2 );
|
||||
// wpseo_schema_webpage_type: retype WebPage -> MedicalWebPage on peptide singles.
|
||||
add_filter( 'wpseo_schema_webpage_type', [ $this->webpage_enricher, 'retype_to_medical_webpage' ] );
|
||||
|
||||
// wpseo_schema_graph: two callbacks at different priorities.
|
||||
// Priority 11 -- enrich_webpage_piece: merge lastReviewed/reviewedBy/audience into the
|
||||
// existing WebPage node that Yoast already emitted.
|
||||
// Priority 12 -- inject_graph_nodes: append Drug + FAQPage arrays to the graph.
|
||||
// Must run after enrich_webpage_piece so the WebPage node is already
|
||||
// enriched before Drug/FAQ are appended.
|
||||
add_filter( 'wpseo_schema_graph', [ $this->webpage_enricher, 'enrich_webpage_piece' ], 11, 2 );
|
||||
add_filter( 'wpseo_schema_graph', [ $this, 'inject_graph_nodes' ], 12, 2 );
|
||||
|
||||
// Standalone fallback: emits only when Yoast is not producing output.
|
||||
add_action( 'wp_head', [ $this, 'emit_standalone_fallback' ], 99 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject Drug and FAQPage pieces into Yoast's schema graph.
|
||||
* Append Drug and FAQPage nodes to Yoast's fully-assembled schema graph.
|
||||
*
|
||||
* Hooked on: wpseo_schema_graph_pieces (priority 11, after Yoast's own pieces).
|
||||
* This filter receives Yoast's piece objects array. We append plain arrays;
|
||||
* Yoast 27.6 accepts both objects and plain arrays in this filter.
|
||||
* Hooked on: wpseo_schema_graph (priority 12, after enrich_webpage_piece).
|
||||
* This filter receives Yoast's completed graph as an array of plain arrays.
|
||||
* Appending plain Drug/FAQ arrays here is safe -- unlike wpseo_schema_graph_pieces
|
||||
* which calls get_class() and is_needed() on each item expecting objects.
|
||||
*
|
||||
* @param array<int, mixed> $pieces Existing graph pieces from Yoast.
|
||||
* @param \WPSEO_Schema_Context $context Yoast schema context object.
|
||||
* @return array<int, mixed> Pieces array with Drug and FAQPage appended.
|
||||
* @param array<int, array<string, mixed>> $graph Assembled Yoast graph.
|
||||
* @param \Yoast\WP\SEO\Context\Meta_Tags_Context|null $context Yoast schema context.
|
||||
* @return array<int, array<string, mixed>> Graph with Drug (and optionally FAQ) appended.
|
||||
*/
|
||||
public function inject_graph_pieces( array $pieces, $context ): array {
|
||||
public function inject_graph_nodes( array $graph, $context ): array {
|
||||
if ( ! is_singular( PR_Core_Peptide_CPT::POST_TYPE ) ) {
|
||||
return $pieces;
|
||||
return $graph;
|
||||
}
|
||||
|
||||
$post_id = get_the_ID();
|
||||
if ( ! $post_id ) {
|
||||
return $pieces;
|
||||
return $graph;
|
||||
}
|
||||
|
||||
$peptide = ( new PR_Core_Peptide_Repository() )->find_by_id( (int) $post_id );
|
||||
if ( ! $peptide ) {
|
||||
return $pieces;
|
||||
return $graph;
|
||||
}
|
||||
|
||||
$pieces[] = $this->drug_builder->build( $peptide );
|
||||
$graph[] = $this->drug_builder->build( $peptide );
|
||||
|
||||
$faq_piece = $this->faq_builder->build( (int) $post_id, get_permalink( (int) $post_id ) );
|
||||
if ( null !== $faq_piece ) {
|
||||
$pieces[] = $faq_piece;
|
||||
$faq_node = $this->faq_builder->build( (int) $post_id, get_permalink( (int) $post_id ) );
|
||||
if ( null !== $faq_node ) {
|
||||
$graph[] = $faq_node;
|
||||
}
|
||||
|
||||
return $pieces;
|
||||
return $graph;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +124,7 @@ class PR_Core_Jsonld {
|
||||
*
|
||||
* Guard: suppressed if Yoast is loaded (function_exists check). This is the
|
||||
* no-Yoast fallback required by the jsonld-contract. When Yoast is present,
|
||||
* the integrated path (inject_graph_pieces) handles emission.
|
||||
* the integrated path (inject_graph_nodes via wpseo_schema_graph) handles emission.
|
||||
*
|
||||
* Side effects: outputs a script tag in wp_head.
|
||||
*
|
||||
@@ -137,9 +152,9 @@ class PR_Core_Jsonld {
|
||||
$permalink = get_permalink( (int) $post_id );
|
||||
$graph = [ $this->drug_builder->build( $peptide ) ];
|
||||
|
||||
$faq_piece = $this->faq_builder->build( (int) $post_id, $permalink );
|
||||
if ( null !== $faq_piece ) {
|
||||
$graph[] = $faq_piece;
|
||||
$faq_node = $this->faq_builder->build( (int) $post_id, $permalink );
|
||||
if ( null !== $faq_node ) {
|
||||
$graph[] = $faq_node;
|
||||
}
|
||||
|
||||
printf(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Plugin Name: Peptide Repo Core
|
||||
* Plugin URI: https://peptiderepo.com
|
||||
* Description: Canonical peptide schema — shared data layer for the peptiderepo.com ecosystem. Provides the peptide CPT, dosing rows, legal status cells, AI candidate queue, disclaimer component, and JSON-LD output.
|
||||
* Version: 0.6.1
|
||||
* Version: 0.6.2
|
||||
* Author: peptiderepo
|
||||
* Author URI: https://peptiderepo.com
|
||||
* License: GPL-2.0-or-later
|
||||
@@ -22,7 +22,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
|
||||
/* ── Constants ────────────────────────────────────────────────────────── */
|
||||
|
||||
define( 'PR_CORE_VERSION', '0.6.1' );
|
||||
define( 'PR_CORE_VERSION', '0.6.2' );
|
||||
define( 'PR_CORE_PLUGIN_FILE', __FILE__ );
|
||||
define( 'PR_CORE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
define( 'PR_CORE_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||
|
||||
366
tests/unit/test-jsonld-peptide-piece.php
Normal file
366
tests/unit/test-jsonld-peptide-piece.php
Normal file
@@ -0,0 +1,366 @@
|
||||
<?php
|
||||
/**
|
||||
* Regression tests for v0.6.2: inject_graph_pieces (plain array) fatal on Yoast.
|
||||
*
|
||||
* Root cause: v0.6.0 and v0.6.1 hooked wpseo_schema_graph_pieces and appended
|
||||
* plain PHP arrays (Drug, FAQ) to Yoast's pieces array. Yoast's
|
||||
* Schema_Generator::filter_graph_pieces_to_generate() then called get_class()
|
||||
* and is_needed() on every piece expecting objects extending Abstract_Schema_Piece.
|
||||
* Passing a plain array caused:
|
||||
*
|
||||
* PHP Fatal: get_class(): Argument #1 ($object) must be of type object, array given
|
||||
*
|
||||
* This crashed every peptide monograph page (HTTP 500) while /about/ and
|
||||
* /peptides/ archive returned 200 (they don't enter the is_singular('peptide')
|
||||
* branch).
|
||||
*
|
||||
* Fix: move Drug + FAQ injection to wpseo_schema_graph (priority 12) which
|
||||
* receives the fully-assembled graph as an array of plain arrays — safe for
|
||||
* plain array appends. wpseo_schema_graph_pieces is no longer hooked.
|
||||
*
|
||||
* These tests FAIL against v0.6.1 inject_graph_pieces and PASS after the
|
||||
* v0.6.2 inject_graph_nodes fix.
|
||||
*
|
||||
* Run: php tests/unit/test-jsonld-peptide-piece.php
|
||||
* Exit 0 = all pass, 1 = any failure.
|
||||
*
|
||||
* @package PeptideRepoCore
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../bootstrap.php';
|
||||
|
||||
// ── Additional stubs ─────────────────────────────────────────────────────
|
||||
|
||||
if ( ! defined( 'PR_CORE_TARGET_SCHEMA_VERSION' ) ) {
|
||||
define( 'PR_CORE_TARGET_SCHEMA_VERSION', 4 );
|
||||
}
|
||||
|
||||
$GLOBALS['pr_test_postmeta'] = [];
|
||||
$GLOBALS['pr_test_is_singular'] = false;
|
||||
$GLOBALS['pr_test_singular_type'] = '';
|
||||
$GLOBALS['pr_test_the_id'] = 0;
|
||||
|
||||
// Stub the peptide repository — returned when find_by_id is called.
|
||||
$GLOBALS['pr_test_peptide_dto'] = null;
|
||||
|
||||
if ( ! function_exists( 'get_post_meta' ) ) {
|
||||
function get_post_meta( $post_id, string $key, bool $single = false ) {
|
||||
if ( $single ) {
|
||||
return $GLOBALS['pr_test_postmeta'][ (int) $post_id ][ $key ] ?? '';
|
||||
}
|
||||
return array_values( $GLOBALS['pr_test_postmeta'][ (int) $post_id ] ?? [] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_permalink' ) ) {
|
||||
function get_permalink( $post_id = null ): string {
|
||||
return 'https://peptiderepo.com/peptides/bpc-157/';
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_the_ID' ) ) {
|
||||
function get_the_ID(): int {
|
||||
return $GLOBALS['pr_test_the_id'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'is_singular' ) ) {
|
||||
function is_singular( $type = '' ): bool {
|
||||
if ( '' === $type ) {
|
||||
return $GLOBALS['pr_test_is_singular'];
|
||||
}
|
||||
return $GLOBALS['pr_test_is_singular'] && ( $GLOBALS['pr_test_singular_type'] === $type );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_post_modified_time' ) ) {
|
||||
function get_post_modified_time( $format, $gmt, $post_id ) {
|
||||
return '2026-06-13';
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_userdata' ) ) {
|
||||
function get_userdata( int $id ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_author_posts_url' ) ) {
|
||||
function get_author_posts_url( int $id ): string {
|
||||
return 'https://peptiderepo.com/author/' . $id;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'home_url' ) ) {
|
||||
function home_url( string $path = '' ): string {
|
||||
return 'https://peptiderepo.com' . $path;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'apply_filters' ) ) {
|
||||
function apply_filters( string $tag, $value ) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'esc_html' ) ) {
|
||||
function esc_html( string $val ): string {
|
||||
return htmlspecialchars( $val, ENT_QUOTES, 'UTF-8' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'esc_html__' ) ) {
|
||||
function esc_html__( string $text, string $domain = '' ): string {
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wp_strip_all_tags' ) ) {
|
||||
function wp_strip_all_tags( $value ): string {
|
||||
return strip_tags( (string) $value );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'sanitize_textarea_field' ) ) {
|
||||
function sanitize_textarea_field( $value ): string {
|
||||
return is_scalar( $value ) ? trim( (string) $value ) : '';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Stub PR_Core_Peptide_Repository ──────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Minimal repository stub — returns whatever is in $GLOBALS['pr_test_peptide_dto'].
|
||||
*/
|
||||
class PR_Core_Peptide_Repository {
|
||||
public function find_by_id( int $id ): ?PR_Core_Peptide_DTO {
|
||||
return $GLOBALS['pr_test_peptide_dto'];
|
||||
}
|
||||
}
|
||||
|
||||
// ── Load classes under test ───────────────────────────────────────────────
|
||||
|
||||
require_once PR_CORE_PLUGIN_DIR . 'includes/cpt/class-pr-core-schema-sanitizers.php';
|
||||
require_once PR_CORE_PLUGIN_DIR . 'includes/cpt/class-pr-core-peptide-cpt.php';
|
||||
require_once PR_CORE_PLUGIN_DIR . 'includes/dto/class-pr-core-peptide-dto.php';
|
||||
require_once PR_CORE_PLUGIN_DIR . 'includes/frontend/class-pr-core-jsonld-drug.php';
|
||||
require_once PR_CORE_PLUGIN_DIR . 'includes/frontend/class-pr-core-jsonld-faq.php';
|
||||
require_once PR_CORE_PLUGIN_DIR . 'includes/frontend/class-pr-core-jsonld-webpage.php';
|
||||
require_once PR_CORE_PLUGIN_DIR . 'includes/frontend/class-pr-core-jsonld.php';
|
||||
|
||||
// ── Helper ────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Create a minimal PR_Core_Peptide_DTO for testing.
|
||||
*
|
||||
* @param array<string, mixed> $overrides Field overrides.
|
||||
* @return PR_Core_Peptide_DTO
|
||||
*/
|
||||
function make_peptide_piece_dto( array $overrides = [] ): PR_Core_Peptide_DTO {
|
||||
return new PR_Core_Peptide_DTO( array_merge( [
|
||||
'id' => 36,
|
||||
'title' => 'BPC-157',
|
||||
'slug' => 'bpc-157',
|
||||
'content' => '',
|
||||
'excerpt' => 'A synthetic pentadecapeptide.',
|
||||
'status' => 'publish',
|
||||
'display_name' => 'BPC-157',
|
||||
'aliases' => [],
|
||||
'molecular_formula' => '',
|
||||
'molecular_weight' => 0.0,
|
||||
'cas_number' => '',
|
||||
'drugbank_id' => '',
|
||||
'chembl_id' => '',
|
||||
'evidence_strength' => 'preclinical',
|
||||
'editorial_review_status' => 'published',
|
||||
'last_editorial_review_at' => '',
|
||||
'medical_editor_id' => 0,
|
||||
'categories' => [],
|
||||
'families' => [],
|
||||
], $overrides ) );
|
||||
}
|
||||
|
||||
echo "== JSON-LD peptide-piece regression tests (v0.6.2) ==\n\n";
|
||||
|
||||
// ── Setup ─────────────────────────────────────────────────────────────────
|
||||
|
||||
$GLOBALS['pr_test_the_id'] = 36;
|
||||
$GLOBALS['pr_test_is_singular'] = true;
|
||||
$GLOBALS['pr_test_singular_type'] = PR_Core_Peptide_CPT::POST_TYPE;
|
||||
$GLOBALS['pr_test_postmeta'][36] = [
|
||||
'_pr_molecular_formula' => 'C62H98N16O22',
|
||||
'_pr_molecular_weight' => '1419.5',
|
||||
'_pr_aliases' => '["Body Protection Compound"]',
|
||||
];
|
||||
$GLOBALS['pr_test_peptide_dto'] = make_peptide_piece_dto();
|
||||
|
||||
$jsonld = new PR_Core_Jsonld();
|
||||
|
||||
// ── Regression: inject_graph_pieces fatal reproduced against old contract ─
|
||||
|
||||
echo "Regression: inject_graph_pieces (OLD hook) with plain array fatal:\n";
|
||||
|
||||
// Simulate what Yoast's filter_graph_pieces_to_generate does:
|
||||
// it calls get_class($piece) on every item. A plain array causes a fatal.
|
||||
$drug_builder = new PR_Core_Jsonld_Drug();
|
||||
$drug_array = $drug_builder->build( $GLOBALS['pr_test_peptide_dto'] );
|
||||
|
||||
$caught_fatal = false;
|
||||
try {
|
||||
// This is the exact operation Yoast does in filter_graph_pieces_to_generate().
|
||||
// Against the old v0.6.1 code (inject via wpseo_schema_graph_pieces), this would
|
||||
// produce: "get_class(): Argument #1 ($object) must be of type object, array given"
|
||||
$class_result = get_class( $drug_array );
|
||||
} catch ( TypeError $e ) {
|
||||
$caught_fatal = true;
|
||||
} catch ( Error $e ) {
|
||||
$caught_fatal = true;
|
||||
}
|
||||
pr_assert( $caught_fatal, 'get_class(array) throws TypeError/Error — confirms the v0.6.1 fatal' );
|
||||
|
||||
// ── Fix: inject_graph_nodes (NEW hook) works without fatal ────────────────
|
||||
|
||||
echo "\nFix: inject_graph_nodes via wpseo_schema_graph (plain array graph):\n";
|
||||
|
||||
// The new inject_graph_nodes method receives an already-built graph (array of arrays).
|
||||
// We simulate the wpseo_schema_graph filter call: $graph = array of plain arrays.
|
||||
$existing_graph = [
|
||||
[
|
||||
'@type' => 'MedicalWebPage',
|
||||
'@id' => 'https://peptiderepo.com/peptides/bpc-157/#webpage',
|
||||
'name' => 'BPC-157',
|
||||
],
|
||||
[
|
||||
'@type' => 'BreadcrumbList',
|
||||
'@id' => 'https://peptiderepo.com/peptides/bpc-157/#breadcrumb',
|
||||
],
|
||||
];
|
||||
|
||||
// This must NOT throw and must append Drug node.
|
||||
$threw = false;
|
||||
try {
|
||||
$result_graph = $jsonld->inject_graph_nodes( $existing_graph, null );
|
||||
} catch ( Throwable $e ) {
|
||||
$threw = true;
|
||||
}
|
||||
|
||||
pr_assert( ! $threw, 'inject_graph_nodes does NOT throw (no get_class/is_needed calls)' );
|
||||
pr_assert( count( $result_graph ) > count( $existing_graph ), 'inject_graph_nodes appended at least one node' );
|
||||
|
||||
// Drug node must be present.
|
||||
$drug_nodes = array_filter(
|
||||
$result_graph,
|
||||
static function ( $node ) {
|
||||
$types = is_array( $node['@type'] ?? null ) ? $node['@type'] : [ $node['@type'] ?? '' ];
|
||||
return in_array( 'Drug', $types, true );
|
||||
}
|
||||
);
|
||||
pr_assert( count( $drug_nodes ) === 1, 'exactly one Drug node in graph after inject' );
|
||||
|
||||
$drug_node = array_values( $drug_nodes )[0];
|
||||
pr_assert_equals(
|
||||
'https://peptiderepo.com/peptides/bpc-157/#drug',
|
||||
$drug_node['@id'],
|
||||
'Drug @id = {permalink}#drug'
|
||||
);
|
||||
pr_assert( isset( $drug_node['molecularFormula'] ), 'Drug has molecularFormula' );
|
||||
pr_assert( isset( $drug_node['molecularWeight'] ), 'Drug has molecularWeight (QuantitativeValue)' );
|
||||
pr_assert_equals( 'QuantitativeValue', $drug_node['molecularWeight']['@type'], 'molecularWeight @type is QuantitativeValue' );
|
||||
pr_assert_equals( 1419.5, $drug_node['molecularWeight']['value'], 'molecularWeight value correct' );
|
||||
pr_assert_equals( 'g/mol', $drug_node['molecularWeight']['unitText'], 'molecularWeight unitText = g/mol' );
|
||||
pr_assert( isset( $drug_node['alternateName'] ), 'Drug has alternateName' );
|
||||
|
||||
// Original WebPage + BreadcrumbList nodes must be preserved.
|
||||
$webpage_nodes = array_filter( $result_graph, static fn( $n ) => ( $n['@type'] ?? '' ) === 'MedicalWebPage' );
|
||||
pr_assert( count( $webpage_nodes ) === 1, 'MedicalWebPage node preserved (no duplicate)' );
|
||||
|
||||
$breadcrumb_nodes = array_filter( $result_graph, static fn( $n ) => ( $n['@type'] ?? '' ) === 'BreadcrumbList' );
|
||||
pr_assert( count( $breadcrumb_nodes ) === 1, 'BreadcrumbList node preserved (no duplicate)' );
|
||||
|
||||
// ── FAQ: present when _pr_faq_items has items ─────────────────────────────
|
||||
|
||||
echo "\nFAQ injection via inject_graph_nodes:\n";
|
||||
|
||||
$GLOBALS['pr_test_postmeta'][36]['_pr_faq_items'] = wp_json_encode( [
|
||||
[ 'question' => 'What is BPC-157?', 'answer' => 'A synthetic pentadecapeptide.' ],
|
||||
[ 'question' => 'Is it safe?', 'answer' => 'Research use only; not approved for human therapy.' ],
|
||||
] );
|
||||
|
||||
$result_with_faq = $jsonld->inject_graph_nodes( $existing_graph, null );
|
||||
|
||||
$faq_nodes = array_filter(
|
||||
$result_with_faq,
|
||||
static fn( $n ) => ( $n['@type'] ?? '' ) === 'FAQPage'
|
||||
);
|
||||
pr_assert( count( $faq_nodes ) === 1, 'FAQPage node injected when _pr_faq_items has items' );
|
||||
|
||||
$faq_node = array_values( $faq_nodes )[0];
|
||||
pr_assert_equals( 'https://peptiderepo.com/peptides/bpc-157/#faq', $faq_node['@id'], 'FAQPage @id = {permalink}#faq' );
|
||||
pr_assert( isset( $faq_node['mainEntity'] ), 'FAQPage has mainEntity' );
|
||||
pr_assert_equals( 2, count( $faq_node['mainEntity'] ), 'FAQPage has 2 questions' );
|
||||
|
||||
// ── FAQ: absent when _pr_faq_items empty ─────────────────────────────────
|
||||
|
||||
$GLOBALS['pr_test_postmeta'][36]['_pr_faq_items'] = '[]';
|
||||
|
||||
$result_no_faq = $jsonld->inject_graph_nodes( $existing_graph, null );
|
||||
$faq_nodes_empty = array_filter(
|
||||
$result_no_faq,
|
||||
static fn( $n ) => ( $n['@type'] ?? '' ) === 'FAQPage'
|
||||
);
|
||||
pr_assert( count( $faq_nodes_empty ) === 0, 'FAQPage NOT injected when _pr_faq_items is empty array' );
|
||||
|
||||
// ── Non-peptide page: no injection ───────────────────────────────────────
|
||||
|
||||
echo "\nNon-peptide page: no injection:\n";
|
||||
|
||||
$GLOBALS['pr_test_is_singular'] = false;
|
||||
$GLOBALS['pr_test_singular_type'] = '';
|
||||
|
||||
$non_peptide_graph = [
|
||||
[
|
||||
'@type' => 'WebPage',
|
||||
'@id' => 'https://peptiderepo.com/about/#webpage',
|
||||
],
|
||||
];
|
||||
|
||||
$result_non_peptide = $jsonld->inject_graph_nodes( $non_peptide_graph, null );
|
||||
pr_assert( count( $result_non_peptide ) === 1, 'inject_graph_nodes no-ops on non-peptide pages (graph unchanged)' );
|
||||
|
||||
$drug_nodes_np = array_filter( $result_non_peptide, static fn( $n ) => ( is_array( $n['@type'] ?? null ) ? in_array( 'Drug', $n['@type'], true ) : ( $n['@type'] ?? '' ) === 'Drug' ) );
|
||||
pr_assert( count( $drug_nodes_np ) === 0, 'Drug NOT injected on non-peptide page' );
|
||||
|
||||
// ── register_hooks: does NOT hook wpseo_schema_graph_pieces ──────────────
|
||||
|
||||
echo "\nregister_hooks: wpseo_schema_graph_pieces must NOT be hooked:\n";
|
||||
|
||||
$GLOBALS['pr_core_test_state']['added_actions'] = [];
|
||||
$GLOBALS['pr_core_test_state']['added_filters'] = [];
|
||||
$filters_added = [];
|
||||
|
||||
// Override add_filter in the bootstrap to capture calls.
|
||||
// (bootstrap only stubs add_action; we check via a fresh parse of register_hooks.)
|
||||
$jsonld2 = new PR_Core_Jsonld();
|
||||
|
||||
// Use Reflection to inspect what hooks register_hooks would call.
|
||||
// Instead: inspect the class source for the string — if inject_graph_pieces is
|
||||
// still registered on wpseo_schema_graph_pieces, the bug is NOT fixed.
|
||||
$src = file_get_contents( PR_CORE_PLUGIN_DIR . 'includes/frontend/class-pr-core-jsonld.php' );
|
||||
pr_assert(
|
||||
strpos( $src, "'wpseo_schema_graph_pieces'" ) === false,
|
||||
'class-pr-core-jsonld.php does NOT add_filter wpseo_schema_graph_pieces (correct hook removed)'
|
||||
);
|
||||
pr_assert(
|
||||
strpos( $src, 'inject_graph_nodes' ) !== false,
|
||||
'class-pr-core-jsonld.php references inject_graph_nodes (new method present)'
|
||||
);
|
||||
pr_assert(
|
||||
strpos( $src, "'wpseo_schema_graph'" ) !== false,
|
||||
'class-pr-core-jsonld.php hooks wpseo_schema_graph for injection'
|
||||
);
|
||||
|
||||
// ── Summary ───────────────────────────────────────────────────────────────
|
||||
|
||||
exit( pr_test_summary() );
|
||||
Reference in New Issue
Block a user