rule.
if ( preg_match_all( '/]*>(.*?)<\/ol>/is', $post->post_content, $ol ) ) {
return null;
}
if ( ! preg_match_all( '/
__( 'Forbidden', 'agentgarrison' ) ), 403 );
}
$founding = sanitize_text_field( wp_unslash( $_POST['founding_date'] ?? '' ) );
if ( $founding && ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $founding ) ) {
$founding = '';
}
$settings = array(
'article' => ! empty( $_POST['article'] ),
'product' => ! empty( $_POST['product'] ),
'faq' => ! empty( $_POST['faq'] ),
'howto' => ! empty( $_POST['howto'] ),
'breadcrumb' => ! empty( $_POST['breadcrumb'] ),
'author' => ! empty( $_POST['author'] ),
'organization' => sanitize_text_field( wp_unslash( $_POST['organization'] ?? '' ) ),
'org_description' => sanitize_textarea_field( wp_unslash( $_POST['org_description'] ?? '' ) ),
'org_logo' => esc_url_raw( wp_unslash( $_POST['org_logo'] ?? '' ) ),
// Trust & Editorial signals.
'publishing_principles' => esc_url_raw( wp_unslash( $_POST['publishing_principles'] ?? '' ) ),
'ethics_policy' => esc_url_raw( wp_unslash( $_POST['ethics_policy'] ?? '' ) ),
'corrections_policy' => esc_url_raw( wp_unslash( $_POST['corrections_policy'] ?? '' ) ),
'ownership_funding' => sanitize_textarea_field( wp_unslash( $_POST['ownership_funding'] ?? '' ) ),
'founding_date' => $founding,
'same_as' => sanitize_textarea_field( wp_unslash( $_POST['same_as'] ?? '' ) ),
'default_reviewer' => sanitize_text_field( wp_unslash( $_POST['default_reviewer'] ?? '' ) ),
// Detection & more types (S3).
'website' => ! empty( $_POST['website'] ),
'speakable' => ! empty( $_POST['speakable'] ),
'local_business' => ! empty( $_POST['local_business'] ),
'lb_type' => sanitize_text_field( wp_unslash( $_POST['lb_type'] ?? 'LocalBusiness' ) ),
'lb_phone' => sanitize_text_field( wp_unslash( $_POST['lb_phone'] ?? '' ) ),
'lb_price' => sanitize_text_field( wp_unslash( $_POST['lb_price'] ?? '' ) ),
'lb_street' => sanitize_text_field( wp_unslash( $_POST['lb_street'] ?? '' ) ),
'lb_city' => sanitize_text_field( wp_unslash( $_POST['lb_city'] ?? '' ) ),
'lb_region' => sanitize_text_field( wp_unslash( $_POST['lb_region'] ?? '' ) ),
'lb_postal' => sanitize_text_field( wp_unslash( $_POST['lb_postal'] ?? '' ) ),
'lb_country' => sanitize_text_field( wp_unslash( $_POST['lb_country'] ?? '' ) ),
'lb_lat' => sanitize_text_field( wp_unslash( $_POST['lb_lat'] ?? '' ) ),
'lb_lng' => sanitize_text_field( wp_unslash( $_POST['lb_lng'] ?? '' ) ),
'lb_hours' => sanitize_textarea_field( wp_unslash( $_POST['lb_hours'] ?? '' ) ),
);
update_option( 'rayetun_ag_schema_settings', $settings );
$this->settings = $settings;
Rayetun_AG_Visibility_Score::invalidate();
wp_send_json_success( array( 'message' => __( 'Schema settings saved.', 'agentgarrison' ) ) );
}
/**
* Build the full @graph for a given post (or the latest published post when
* post_id is 0) and return it pretty-printed. Powers both the editor metabox
* preview and the Schema tab's full-graph preview.
*/
public function handle_preview_post_schema() {
check_ajax_referer( 'rayetun_ag_ajax', 'nonce' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( array( 'message' => __( 'Forbidden', 'agentgarrison' ) ), 403 );
}
$post_id = absint( $_POST['post_id'] ?? 0 );
if ( ! $post_id ) {
$recent = get_posts( array( 'numberposts' => 1, 'post_status' => 'publish' ) );
if ( $recent ) {
$post_id = $recent[0]->ID;
}
}
$post = $post_id ? get_post( $post_id ) : null;
if ( ! $post ) {
wp_send_json_error( array( 'message' => __( 'No post available to preview.', 'agentgarrison' ) ) );
}
$schema = array(
'@context' => 'https://schema.org',
'@graph' => $this->build_full_graph( $post ),
);
wp_send_json_success( array(
'json' => wp_json_encode( $schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT ),
'title' => get_the_title( $post ),
) );
}
// -------------------------------------------------------------------------
// Validator
// -------------------------------------------------------------------------
/**
* Recommended properties per @type (Google-aligned). Missing ones are flagged
* as advisories — they don't break the markup but improve rich-result eligibility.
*/
private function recommended_props() {
return array(
'Organization' => array( 'logo', 'sameAs' ),
'WebSite' => array( 'potentialAction' ),
'WebPage' => array( 'url', 'datePublished' ),
'Article' => array( 'image', 'author', 'datePublished', 'headline' ),
'Product' => array( 'offers', 'image' ),
'FAQPage' => array( 'mainEntity' ),
'HowTo' => array( 'step' ),
'BreadcrumbList' => array( 'itemListElement' ),
'Person' => array( 'name' ),
'LocalBusiness' => array( 'address', 'telephone' ),
);
}
public function handle_validate_schema() {
check_ajax_referer( 'rayetun_ag_ajax', 'nonce' );
if ( ! current_user_can( 'edit_posts' ) ) {
wp_send_json_error( array( 'message' => __( 'Forbidden', 'agentgarrison' ) ), 403 );
}
$post_id = absint( $_POST['post_id'] ?? 0 );
if ( ! $post_id ) {
$recent = get_posts( array( 'numberposts' => 1, 'post_status' => 'publish' ) );
if ( $recent ) {
$post_id = $recent[0]->ID;
}
}
$post = $post_id ? get_post( $post_id ) : null;
if ( ! $post ) {
wp_send_json_error( array( 'message' => __( 'No post available to validate.', 'agentgarrison' ) ) );
}
$graph = $this->build_full_graph( $post );
$recommended = $this->recommended_props();
$types_found = array();
$recommendations = array();
foreach ( $graph as $node ) {
$type = $node['@type'] ?? '';
if ( is_array( $type ) ) {
$type = reset( $type );
}
if ( ! $type ) {
continue;
}
$types_found[] = $type;
if ( isset( $recommended[ $type ] ) ) {
$missing = array();
foreach ( $recommended[ $type ] as $prop ) {
if ( empty( $node[ $prop ] ) ) {
$missing[] = $prop;
}
}
if ( $missing ) {
$recommendations[] = array(
'type' => $type,
'missing' => $missing,
);
}
}
}
$types_found = array_values( array_unique( $types_found ) );
// Conflict report.
$conflict = array( 'active' => false, 'plugin' => '', 'deferred' => array() );
if ( $this->has_seo_plugin_schema() ) {
$conflict['active'] = true;
$conflict['plugin'] = defined( 'WPSEO_VERSION' ) ? 'Yoast SEO' : 'Rank Math';
$conflict['deferred'] = array( 'Article' ); // AgentGarrison defers Article when an SEO plugin is active.
}
wp_send_json_success( array(
'title' => get_the_title( $post ),
'node_count' => count( $graph ),
'types' => $types_found,
'recommendations' => $recommendations,
'conflict' => $conflict,
'rich_results_url' => 'https://search.google.com/test/rich-results?url=' . rawurlencode( get_permalink( $post ) ),
) );
}
public function get_settings() {
return $this->settings;
}
}