build_organization( $settings ); // WebSite with SearchAction, on the homepage. if ( is_front_page() || is_home() ) { $schemas[] = $this->build_website( $settings ); } // BreadcrumbList, on inner pages. if ( ! is_front_page() && ! is_home() ) { $breadcrumb = $this->build_breadcrumb(); if ( $breadcrumb ) { $schemas[] = $breadcrumb; } } } // FAQ — on pages with FAQ blocks or shortcodes if ( is_singular() ) { $faq = $this->build_faq(); if ( $faq ) { $schemas[] = $faq; } } // Filter for extensibility $schemas = apply_filters( 'airewp_json_ld_schemas', $schemas, $settings ); // Output foreach ( $schemas as $schema ) { if ( ! empty( $schema ) ) { echo '' . "\n"; } } } /** * Build Organization or LocalBusiness schema. */ private function build_organization( array $settings ): array { $type = $settings['schema_type'] === 'LocalBusiness' ? 'LocalBusiness' : 'Organization'; $schema = [ '@context' => 'https://schema.org', '@type' => $type, 'name' => $settings['company_name'], 'url' => home_url(), ]; if ( ! empty( $settings['company_desc'] ) ) { $schema['description'] = $settings['company_desc']; } if ( ! empty( $settings['company_logo'] ) ) { $schema['logo'] = [ '@type' => 'ImageObject', 'url' => $settings['company_logo'], ]; $schema['image'] = $settings['company_logo']; } if ( ! empty( $settings['company_email'] ) ) { $schema['email'] = $settings['company_email']; } if ( ! empty( $settings['company_phone'] ) ) { $schema['telephone'] = $settings['company_phone']; } // Address (only if we have data) if ( ! empty( $settings['company_address'] ) || ! empty( $settings['company_city'] ) ) { $address = [ '@type' => 'PostalAddress', ]; if ( ! empty( $settings['company_address'] ) ) { $address['streetAddress'] = $settings['company_address']; } if ( ! empty( $settings['company_city'] ) ) { $address['addressLocality'] = $settings['company_city']; } if ( ! empty( $settings['company_zip'] ) ) { $address['postalCode'] = $settings['company_zip']; } if ( ! empty( $settings['company_country'] ) ) { $address['addressCountry'] = $settings['company_country']; } $schema['address'] = $address; } // Social profiles $same_as = []; $social_fields = [ 'social_x', 'social_linkedin', 'social_instagram', 'social_facebook', 'social_github' ]; foreach ( $social_fields as $field ) { if ( ! empty( $settings[ $field ] ) ) { $same_as[] = $settings[ $field ]; } } if ( ! empty( $same_as ) ) { $schema['sameAs'] = $same_as; } return $schema; } /** * Build WebSite schema with SearchAction. */ private function build_website( array $settings ): array { return [ '@context' => 'https://schema.org', '@type' => 'WebSite', 'name' => $settings['company_name'], 'url' => home_url(), 'potentialAction' => [ '@type' => 'SearchAction', 'target' => [ '@type' => 'EntryPoint', 'urlTemplate' => home_url( '/?s={search_term_string}' ), ], 'query-input' => 'required name=search_term_string', ], ]; } /** * Build BreadcrumbList from current page hierarchy. */ private function build_breadcrumb(): ?array { global $post; if ( ! $post ) { return null; } $items = []; $position = 1; // Home $items[] = [ '@type' => 'ListItem', 'position' => $position++, 'name' => __( 'Home', 'ai-ready-wp' ), 'item' => home_url(), ]; // Category (for posts) if ( is_single() ) { $categories = get_the_category( $post->ID ); if ( ! empty( $categories ) ) { $cat = $categories[0]; $items[] = [ '@type' => 'ListItem', 'position' => $position++, 'name' => $cat->name, 'item' => get_category_link( $cat->term_id ), ]; } } // Parent pages if ( is_page() && $post->post_parent ) { $ancestors = get_post_ancestors( $post ); $ancestors = array_reverse( $ancestors ); foreach ( $ancestors as $ancestor_id ) { $ancestor = get_post( $ancestor_id ); if ( $ancestor ) { $items[] = [ '@type' => 'ListItem', 'position' => $position++, // get_the_title() applies the "Protected:" prefix for // locked ancestors; the raw post_title dropped it. 'name' => get_the_title( $ancestor ), 'item' => get_permalink( $ancestor ), ]; } } } // Current page $items[] = [ '@type' => 'ListItem', 'position' => $position, 'name' => get_the_title( $post ), 'item' => get_permalink( $post ), ]; return [ '@context' => 'https://schema.org', '@type' => 'BreadcrumbList', 'itemListElement' => $items, ]; } /** * Build FAQ schema from page content. * Detects FAQ patterns:

/

followed by

content. */ private function build_faq(): ?array { global $post; if ( ! $post ) { return null; } // The FAQ schema is built from $post->post_content and printed into the // page source. On a password-protected post that published question and // answer text of locked content to every visitor and crawler. if ( post_password_required( $post ) ) { return null; } $content = $post->post_content; // Look for FAQ-style content: heading tags followed by paragraph content // Match patterns like

Question?

$faq_items = []; // Pattern: h2 or h3 containing a question (ends with ?) immediately followed by a paragraph // Strip WordPress block comments first for cleaner matching $clean = preg_replace( '//s', '', $content ); if ( preg_match_all( '/]*>\s*([^<]*\?)\s*<\/h[23]>\s*]*>(.*?)<\/p>/si', $clean, $matches, PREG_SET_ORDER ) ) { foreach ( $matches as $match ) { // Same reason as everywhere else: an answer paragraph can // carry shortcodes, and this string is published as a fact in // structured data. $question = AI_Ready_WP::to_plain_text( $match[1] ); $answer = AI_Ready_WP::to_plain_text( $match[2] ); if ( strlen( $question ) > 5 && strlen( $answer ) > 10 ) { $faq_items[] = [ '@type' => 'Question', 'name' => $question, 'acceptedAnswer' => [ '@type' => 'Answer', 'text' => $answer, ], ]; } } } if ( empty( $faq_items ) ) { return null; } return [ '@context' => 'https://schema.org', '@type' => 'FAQPage', 'mainEntity' => $faq_items, ]; } }