| |
| <?php
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| if (!defined('ABSPATH')) {
|
| exit;
|
| }
|
|
|
|
|
|
|
|
|
| add_action('rest_api_init', function () {
|
| $namespace = 'cortex/v1';
|
|
|
|
|
|
|
|
|
|
|
|
|
| register_rest_route($namespace, '/schema/types', [
|
| 'methods' => 'GET',
|
| 'callback' => 'cortex_get_schema_types',
|
| 'permission_callback' => 'cortex_api_permissions',
|
| ]);
|
|
|
|
|
| register_rest_route($namespace, '/schema/all/page', [
|
| 'methods' => 'GET',
|
| 'callback' => 'cortex_get_all_page_schemas',
|
| 'permission_callback' => 'cortex_api_permissions',
|
| ]);
|
|
|
|
|
| register_rest_route($namespace, '/schema/live/(?P<id>\d+)', [
|
| 'methods' => 'GET',
|
| 'callback' => 'cortex_get_live_schema',
|
| 'permission_callback' => 'cortex_api_permissions',
|
| 'args' => [
|
| 'id' => ['required' => true, 'type' => 'integer'],
|
| ],
|
| ]);
|
|
|
|
|
|
|
|
|
|
|
|
|
| register_rest_route($namespace, '/featured-image/(?P<id>\d+)', [
|
| 'methods' => 'GET',
|
| 'callback' => 'cortex_get_featured_image',
|
| 'permission_callback' => 'cortex_api_permissions',
|
| 'args' => [
|
| 'id' => ['required' => true, 'type' => 'integer'],
|
| ],
|
| ]);
|
|
|
|
|
| register_rest_route($namespace, '/featured-image/all', [
|
| 'methods' => 'GET',
|
| 'callback' => 'cortex_get_all_featured_images',
|
| 'permission_callback' => 'cortex_api_permissions',
|
| ]);
|
|
|
|
|
| register_rest_route($namespace, '/featured-image/all/(?P<type>page|post)', [
|
| 'methods' => 'GET',
|
| 'callback' => 'cortex_get_all_featured_images',
|
| 'permission_callback' => 'cortex_api_permissions',
|
| 'args' => [
|
| 'type' => ['required' => true, 'type' => 'string'],
|
| ],
|
| ]);
|
| });
|
|
|
| function cortex_api_permissions() {
|
| return current_user_can('edit_posts');
|
| }
|
|
|
|
|
|
|
|
|
|
|
| function cortex_fetch_live_schemas($post_id) {
|
| $url = get_permalink($post_id);
|
| $response = wp_remote_get($url, ['timeout' => 15, 'sslverify' => false]);
|
|
|
| if (is_wp_error($response)) {
|
| return ['error' => $response->get_error_message(), 'schemas' => []];
|
| }
|
|
|
| $body = wp_remote_retrieve_body($response);
|
| $schemas = [];
|
|
|
| if (preg_match_all('/<script[^>]*type=["\']application\/ld\+json["\'][^>]*>(.*?)<\/script>/si', $body, $matches)) {
|
| foreach ($matches[1] as $json) {
|
| $decoded = json_decode(trim($json), true);
|
| if ($decoded) {
|
| $schemas[] = $decoded;
|
| }
|
| }
|
| }
|
|
|
| return ['error' => null, 'schemas' => $schemas];
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| function cortex_filter_schemas_by_post_type($schemas, $post_type) {
|
|
|
| if ($post_type !== 'page') {
|
| return $schemas;
|
| }
|
|
|
|
|
| $filtered_types = ['FAQPage', 'VideoObject', 'HowTo', 'BreadcrumbList'];
|
|
|
| $filtered_schemas = [];
|
|
|
| foreach ($schemas as $schema) {
|
|
|
| if (isset($schema['@graph']) && is_array($schema['@graph'])) {
|
| $filtered_graph = [];
|
|
|
| foreach ($schema['@graph'] as $item) {
|
| if (!cortex_should_filter_schema_item($item, $filtered_types)) {
|
| $filtered_graph[] = $item;
|
| }
|
| }
|
|
|
|
|
| if (!empty($filtered_graph)) {
|
| $schema['@graph'] = $filtered_graph;
|
| $filtered_schemas[] = $schema;
|
| }
|
| }
|
|
|
| else {
|
| if (!cortex_should_filter_schema_item($schema, $filtered_types)) {
|
| $filtered_schemas[] = $schema;
|
| }
|
| }
|
| }
|
|
|
| return $filtered_schemas;
|
| }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| function cortex_should_filter_schema_item($item, $filtered_types) {
|
| if (!isset($item['@type'])) {
|
| return false;
|
| }
|
|
|
| $type = $item['@type'];
|
|
|
|
|
| if (is_array($type)) {
|
| foreach ($type as $t) {
|
| if (in_array($t, $filtered_types)) {
|
| return true;
|
| }
|
| }
|
| } else {
|
|
|
| if (in_array($type, $filtered_types)) {
|
| return true;
|
| }
|
|
|
|
|
| if ($type === 'ImageObject') {
|
| if (isset($item['@id']) && strpos($item['@id'], '/wp-content/uploads/') !== false) {
|
| return true;
|
| }
|
| if (isset($item['contentUrl']) && strpos($item['contentUrl'], '/wp-content/uploads/') !== false) {
|
|
|
|
|
| if (isset($item['@id'])) {
|
| $id = $item['@id'];
|
| if (strpos($id, '#logo') === false &&
|
| strpos($id, '#primaryimage') === false &&
|
| strpos($id, '/#') === false) {
|
| return true;
|
| }
|
| }
|
| }
|
| }
|
| }
|
|
|
| return false;
|
| }
|
|
|
|
|
|
|
|
|
|
|
| function cortex_extract_types_from_live_schemas($live_schemas) {
|
| $types = [];
|
|
|
| foreach ($live_schemas as $schema) {
|
|
|
| if (isset($schema['@graph']) && is_array($schema['@graph'])) {
|
| foreach ($schema['@graph'] as $item) {
|
| if (isset($item['@type'])) {
|
| $type = is_array($item['@type']) ? implode('/', $item['@type']) : $item['@type'];
|
| if (!in_array($type, $types)) {
|
| $types[] = $type;
|
| }
|
| }
|
| }
|
| }
|
|
|
| elseif (isset($schema['@type'])) {
|
| $type = is_array($schema['@type']) ? implode('/', $schema['@type']) : $schema['@type'];
|
| if (!in_array($type, $types)) {
|
| $types[] = $type;
|
| }
|
| }
|
| }
|
|
|
| return $types;
|
| }
|
|
|
|
|
|
|
|
|
|
|
| function cortex_get_schema_types($request) {
|
| return rest_ensure_response([
|
| 'success' => true,
|
| 'api_version' => '3.1',
|
| 'api_mode' => 'audit_only',
|
| 'note' => 'Streamlined schema audit API with server-side filtering. Pages filter out auto-generated schemas (FAQPage, VideoObject, HowTo, BreadcrumbList, standalone ImageObject). Posts retain full data.',
|
| 'supported_schema_types' => [
|
| 'LocalBusiness' => 'Homepage - main business entity',
|
| 'Organization' => 'Homepage - brand entity (separate from LocalBusiness)',
|
| 'WebSite' => 'Homepage - site identity with SearchAction',
|
| 'WebPage' => 'All pages - page-level context with speakable',
|
| 'Service' => 'Service pages',
|
| 'JobPosting' => 'Careers/Jobs pages',
|
| 'BlogPosting' => 'Blog posts',
|
| 'FAQPage' => 'FAQ pages (filtered on pages, kept on posts)',
|
| 'HowTo' => 'Tutorial/Guide pages (filtered on pages, kept on posts)',
|
| 'Person' => 'Founder/Team pages',
|
| ],
|
| 'local_business_types' => [
|
| 'Electrician', 'Plumber', 'HVACBusiness', 'RoofingContractor',
|
| 'GeneralContractor', 'HomeAndConstructionBusiness', 'LocksmithService',
|
| 'MovingCompany', 'PestControlService', 'ProfessionalService',
|
| ],
|
| 'page_filtering' => [
|
| 'enabled' => true,
|
| 'filtered_types' => ['FAQPage', 'VideoObject', 'HowTo', 'BreadcrumbList', 'ImageObject (standalone)'],
|
| 'note' => 'Pages filter out auto-generated schemas to focus on manually configured schemas. Posts retain all schemas.',
|
| ],
|
| 'endpoints' => [
|
| 'GET /cortex/v1/schema/types' => 'API info + supported types',
|
| 'GET /cortex/v1/schema/all/page' => 'All live schemas from pages (filtered)',
|
| 'GET /cortex/v1/schema/live/{id}' => 'Live schema for single page/post (pages filtered, posts unfiltered)',
|
| 'GET /cortex/v1/featured-image/{id}' => 'Featured image for post',
|
| 'GET /cortex/v1/featured-image/all' => 'All featured images',
|
| 'GET /cortex/v1/featured-image/all/page' => 'Featured images for pages',
|
| 'GET /cortex/v1/featured-image/all/post' => 'Featured images for posts',
|
| ],
|
| ]);
|
| }
|
|
|
|
|
|
|
|
|
|
|
| function cortex_get_all_page_schemas($request) {
|
| $pages = get_posts([
|
| 'post_type' => 'page',
|
| 'post_status' => 'publish',
|
| 'posts_per_page' => -1,
|
| 'orderby' => 'title',
|
| 'order' => 'ASC',
|
| ]);
|
|
|
| $results = [];
|
| $with_schema = 0;
|
| $without_schema = 0;
|
| $schema_type_counts = [];
|
| $missing_schema = [];
|
| $fetch_errors = [];
|
| $total_filtered = 0;
|
|
|
| foreach ($pages as $page) {
|
|
|
| $live_result = cortex_fetch_live_schemas($page->ID);
|
| $raw_schemas = $live_result['schemas'];
|
| $fetch_error = $live_result['error'];
|
|
|
|
|
| $raw_count = cortex_count_schema_items($raw_schemas);
|
|
|
|
|
| $filtered_schemas = cortex_filter_schemas_by_post_type($raw_schemas, 'page');
|
|
|
|
|
| $filtered_count = cortex_count_schema_items($filtered_schemas);
|
| $items_filtered = $raw_count - $filtered_count;
|
| $total_filtered += $items_filtered;
|
|
|
|
|
| $schema_types = cortex_extract_types_from_live_schemas($filtered_schemas);
|
| $has_schema = !empty($filtered_schemas);
|
|
|
| if ($fetch_error) {
|
| $fetch_errors[] = [
|
| 'id' => $page->ID,
|
| 'title' => $page->post_title,
|
| 'error' => $fetch_error,
|
| ];
|
| }
|
|
|
| if ($has_schema) {
|
| $with_schema++;
|
| foreach ($schema_types as $type) {
|
| $schema_type_counts[$type] = isset($schema_type_counts[$type]) ? $schema_type_counts[$type] + 1 : 1;
|
| }
|
| } else {
|
| $without_schema++;
|
| $missing_schema[] = [
|
| 'id' => $page->ID,
|
| 'title' => $page->post_title,
|
| 'url' => get_permalink($page->ID),
|
| ];
|
| }
|
|
|
| $results[] = [
|
| 'id' => $page->ID,
|
| 'title' => $page->post_title,
|
| 'url' => get_permalink($page->ID),
|
| 'has_schema' => $has_schema,
|
| 'schema_types' => $schema_types,
|
| 'schema_count' => $filtered_count,
|
| 'raw_schema_count' => $raw_count,
|
| 'filtered_out' => $items_filtered,
|
| 'live_schemas' => $filtered_schemas,
|
| 'test_url' => 'https://search.google.com/test/rich-results?url=' . urlencode(get_permalink($page->ID)),
|
| ];
|
| }
|
|
|
| return rest_ensure_response([
|
| 'success' => true,
|
| 'api_version' => '3.1',
|
| 'data_source' => 'live_rendered',
|
| 'filtering' => [
|
| 'enabled' => true,
|
| 'post_type' => 'page',
|
| 'filtered_types' => ['FAQPage', 'VideoObject', 'HowTo', 'BreadcrumbList', 'ImageObject (standalone)'],
|
| 'total_items_filtered' => $total_filtered,
|
| ],
|
| 'summary' => [
|
| 'total' => count($results),
|
| 'with_schema' => $with_schema,
|
| 'without_schema' => $without_schema,
|
| 'schema_type_counts' => $schema_type_counts,
|
| ],
|
| 'missing_schema' => $missing_schema,
|
| 'fetch_errors' => $fetch_errors,
|
| 'results' => $results,
|
| ]);
|
| }
|
|
|
|
|
|
|
|
|
| function cortex_count_schema_items($schemas) {
|
| $count = 0;
|
| foreach ($schemas as $schema) {
|
| if (isset($schema['@graph']) && is_array($schema['@graph'])) {
|
| $count += count($schema['@graph']);
|
| } else {
|
| $count++;
|
| }
|
| }
|
| return $count;
|
| }
|
|
|
|
|
|
|
|
|
|
|
| function cortex_get_live_schema($request) {
|
| $post_id = $request->get_param('id');
|
| $post = get_post($post_id);
|
|
|
| if (!$post) {
|
| return new WP_Error('not_found', 'Post not found', ['status' => 404]);
|
| }
|
|
|
| $url = get_permalink($post_id);
|
| $response = wp_remote_get($url, ['timeout' => 15, 'sslverify' => false]);
|
|
|
| if (is_wp_error($response)) {
|
| return new WP_Error('fetch_failed', 'Could not fetch page: ' . $response->get_error_message(), ['status' => 500]);
|
| }
|
|
|
| $body = wp_remote_retrieve_body($response);
|
| $raw_schemas = [];
|
|
|
| if (preg_match_all('/<script[^>]*type=["\']application\/ld\+json["\'][^>]*>(.*?)<\/script>/si', $body, $matches)) {
|
| foreach ($matches[1] as $json) {
|
| $decoded = json_decode(trim($json), true);
|
| if ($decoded) {
|
| $raw_schemas[] = $decoded;
|
| }
|
| }
|
| }
|
|
|
|
|
| $raw_count = cortex_count_schema_items($raw_schemas);
|
|
|
|
|
| $filtered_schemas = cortex_filter_schemas_by_post_type($raw_schemas, $post->post_type);
|
|
|
|
|
| $filtered_count = cortex_count_schema_items($filtered_schemas);
|
| $items_filtered = $raw_count - $filtered_count;
|
|
|
|
|
| $schema_types = cortex_extract_types_from_live_schemas($filtered_schemas);
|
|
|
|
|
| $filtering_applied = ($post->post_type === 'page');
|
|
|
| return rest_ensure_response([
|
| 'success' => true,
|
| 'post_id' => $post_id,
|
| 'post_type' => $post->post_type,
|
| 'post_title' => $post->post_title,
|
| 'url' => $url,
|
| 'has_schema' => !empty($filtered_schemas),
|
| 'schema_types' => $schema_types,
|
| 'schema_count' => $filtered_count,
|
| 'filtering' => [
|
| 'applied' => $filtering_applied,
|
| 'raw_count' => $raw_count,
|
| 'filtered_out' => $items_filtered,
|
| 'note' => $filtering_applied
|
| ? 'Auto-generated schemas filtered (FAQPage, VideoObject, HowTo, BreadcrumbList, standalone ImageObject)'
|
| : 'No filtering applied (posts retain full schema data)',
|
| ],
|
| 'live_schemas' => $filtered_schemas,
|
| 'test_url' => 'https://search.google.com/test/rich-results?url=' . urlencode($url),
|
| ]);
|
| }
|
|
|
|
|
|
|
|
|
|
|
| function cortex_get_featured_image($request) {
|
| $post_id = $request->get_param('id');
|
| $post = get_post($post_id);
|
|
|
| if (!$post) {
|
| return new WP_Error('not_found', 'Post not found', ['status' => 404]);
|
| }
|
|
|
| $thumbnail_id = get_post_thumbnail_id($post_id);
|
|
|
| if (!$thumbnail_id) {
|
| return rest_ensure_response([
|
| 'success' => true,
|
| 'post_id' => $post_id,
|
| 'post_title' => $post->post_title,
|
| 'has_featured_image' => false,
|
| 'featured_image' => null,
|
| ]);
|
| }
|
|
|
| $image_url = wp_get_attachment_image_url($thumbnail_id, 'full');
|
| $image_meta = wp_get_attachment_metadata($thumbnail_id);
|
| $image_alt = get_post_meta($thumbnail_id, '_wp_attachment_image_alt', true);
|
|
|
| return rest_ensure_response([
|
| 'success' => true,
|
| 'post_id' => $post_id,
|
| 'post_title' => $post->post_title,
|
| 'has_featured_image' => true,
|
| 'featured_image' => [
|
| 'id' => $thumbnail_id,
|
| 'url' => $image_url,
|
| 'width' => isset($image_meta['width']) ? $image_meta['width'] : null,
|
| 'height' => isset($image_meta['height']) ? $image_meta['height'] : null,
|
| 'alt' => $image_alt,
|
| ],
|
| ]);
|
| }
|
|
|
| function cortex_get_all_featured_images($request) {
|
| $post_type = $request->get_param('type');
|
| $post_types = empty($post_type) ? ['page', 'post'] : [$post_type];
|
|
|
| $posts = get_posts([
|
| 'post_type' => $post_types,
|
| 'post_status' => 'publish',
|
| 'posts_per_page' => -1,
|
| 'orderby' => 'title',
|
| 'order' => 'ASC',
|
| ]);
|
|
|
| $results = [];
|
| $with_image = 0;
|
| $without_image = 0;
|
| $missing_images = [];
|
|
|
| foreach ($posts as $post) {
|
| $thumbnail_id = get_post_thumbnail_id($post->ID);
|
| $has_image = !empty($thumbnail_id);
|
|
|
| if ($has_image) {
|
| $with_image++;
|
| $image_url = wp_get_attachment_image_url($thumbnail_id, 'full');
|
| $image_meta = wp_get_attachment_metadata($thumbnail_id);
|
| $image_alt = get_post_meta($thumbnail_id, '_wp_attachment_image_alt', true);
|
|
|
| $results[] = [
|
| 'id' => $post->ID,
|
| 'post_type' => $post->post_type,
|
| 'title' => $post->post_title,
|
| 'url' => get_permalink($post->ID),
|
| 'has_featured_image' => true,
|
| 'featured_image' => [
|
| 'id' => $thumbnail_id,
|
| 'url' => $image_url,
|
| 'width' => isset($image_meta['width']) ? $image_meta['width'] : null,
|
| 'height' => isset($image_meta['height']) ? $image_meta['height'] : null,
|
| 'alt' => $image_alt,
|
| ],
|
| ];
|
| } else {
|
| $without_image++;
|
| $missing_images[] = [
|
| 'id' => $post->ID,
|
| 'post_type' => $post->post_type,
|
| 'title' => $post->post_title,
|
| 'url' => get_permalink($post->ID),
|
| ];
|
|
|
| $results[] = [
|
| 'id' => $post->ID,
|
| 'post_type' => $post->post_type,
|
| 'title' => $post->post_title,
|
| 'url' => get_permalink($post->ID),
|
| 'has_featured_image' => false,
|
| 'featured_image' => null,
|
| ];
|
| }
|
| }
|
|
|
| return rest_ensure_response([
|
| 'success' => true,
|
| 'filter' => empty($post_type) ? 'all' : $post_type,
|
| 'summary' => [
|
| 'total' => count($results),
|
| 'with_image' => $with_image,
|
| 'without_image' => $without_image,
|
| ],
|
| 'missing_images' => $missing_images,
|
| 'results' => $results,
|
| ]);
|
| }
|
| |
| |