From 7153a9fe27b6bc2defeb588b6039f1caf9040d8e Mon Sep 17 00:00:00 2001 From: colorlink Date: Fri, 16 Oct 2026 02:09:05 +0000 Subject: [PATCH] =?UTF-8?q?feat(batch14):=20Yoast=20SEO=20deep=20=E2=80=94?= =?UTF-8?q?=20cornerstone,=20primary=20category,=20scores,=20redirects,=20?= =?UTF-8?q?sitemap=20(guarded)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Q8t9GEyyLZbUQu26na1JQg --- includes/Extensions/Batch14.php | 138 ++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 includes/Extensions/Batch14.php diff --git a/includes/Extensions/Batch14.php b/includes/Extensions/Batch14.php new file mode 100644 index 0000000..3f99d2f --- /dev/null +++ b/includes/Extensions/Batch14.php @@ -0,0 +1,138 @@ + '_yoast_wpseo_title', + 'metadesc' => '_yoast_wpseo_metadesc', + 'focuskw' => '_yoast_wpseo_focuskw', + 'canonical' => '_yoast_wpseo_canonical', + 'og_title' => '_yoast_wpseo_opengraph-title', + 'og_description' => '_yoast_wpseo_opengraph-description', + 'og_image' => '_yoast_wpseo_opengraph-image', + 'twitter_title' => '_yoast_wpseo_twitter-title', + 'twitter_description' => '_yoast_wpseo_twitter-description', + 'cornerstone' => '_yoast_wpseo_is_cornerstone', + 'primary_category' => '_yoast_wpseo_primary_category', + 'noindex' => '_yoast_wpseo_meta-robots-noindex', // '1' noindex, '2' index, '' default + 'nofollow' => '_yoast_wpseo_meta-robots-nofollow', // '1' nofollow, '' default + 'seo_score' => '_yoast_wpseo_linkdex', + 'readability' => '_yoast_wpseo_content_score', + ]; + + public static function register($registry): void { + if (!defined('WPSEO_VERSION')) return; + + $def = function (string $name, string $desc, array $props, array $required, string $scope, string $method) { + $schema = ['type' => 'object', 'properties' => $props]; + if ($required) $schema['required'] = $required; + return ['name' => $name, 'description' => $desc, 'scope' => $scope, + 'handler' => 'AskTamMCP\\Extensions\\Batch14::' . $method, 'inputSchema' => $schema]; + }; + + $registry->register($def('yoast_detect', 'Report Yoast SEO version, whether Premium is active, and whether the XML sitemap is enabled.', + [], [], 'read', 'detect')); + + $registry->register($def('yoast_get_meta', 'Get all Yoast meta for a post: title, metadesc, focus keyword, canonical, OG/Twitter, cornerstone flag, primary category, robots, and SEO/readability scores.', + ['post_id' => ['type' => 'integer']], ['post_id'], 'read', 'getMeta')); + + $registry->register($def('yoast_set_meta', 'Set Yoast meta on a post. Only supplied fields change. cornerstone is boolean; noindex/nofollow accept true/false.', + ['post_id' => ['type' => 'integer'], 'title' => ['type' => 'string'], 'metadesc' => ['type' => 'string'], + 'focuskw' => ['type' => 'string'], 'canonical' => ['type' => 'string'], + 'og_title' => ['type' => 'string'], 'og_description' => ['type' => 'string'], 'og_image' => ['type' => 'string'], + 'twitter_title' => ['type' => 'string'], 'twitter_description' => ['type' => 'string'], + 'cornerstone' => ['type' => 'boolean'], 'primary_category' => ['type' => 'integer'], + 'noindex' => ['type' => 'boolean'], 'nofollow' => ['type' => 'boolean']], + ['post_id'], 'write', 'setMeta')); + + $registry->register($def('yoast_bulk_audit', 'Scan posts/pages for Yoast problems: missing meta description, missing focus keyword, or low SEO score (<40). Returns the offenders.', + ['post_types' => ['type' => 'array'], 'per_page' => ['type' => 'integer', 'default' => 100]], [], 'read', 'bulkAudit')); + + $registry->register($def('yoast_list_redirects', 'List Yoast Premium redirects (origin -> target, type). Requires Yoast Premium; reports supported=false otherwise.', + [], [], 'read', 'listRedirects')); + } + + public static function detect(array $args): array { + $opts = get_option('wpseo', []); + return [ + 'version' => WPSEO_VERSION, + 'premium' => defined('WPSEO_PREMIUM_VERSION') ? WPSEO_PREMIUM_VERSION : false, + 'sitemap_enabled' => !empty($opts['enable_xml_sitemap']), + 'sitemap_url' => home_url('/sitemap_index.xml'), + ]; + } + + public static function getMeta(array $args): array { + $id = (int) ($args['post_id'] ?? 0); + if (!get_post($id)) throw new \RuntimeException('Post not found: ' . $id); + $out = ['post_id' => $id]; + foreach (self::MAP as $k => $mk) { + $v = get_post_meta($id, $mk, true); + if ($k === 'cornerstone') $v = ($v === '1'); + $out[$k] = $v === '' ? null : $v; + } + return $out; + } + + public static function setMeta(array $args): array { + $id = (int) ($args['post_id'] ?? 0); + if (!get_post($id)) throw new \RuntimeException('Post not found: ' . $id); + $changed = []; + foreach (self::MAP as $k => $mk) { + if (!array_key_exists($k, $args)) continue; + if (in_array($k, ['seo_score', 'readability'], true)) continue; // computed, not user-set + $val = $args[$k]; + if ($k === 'cornerstone') $val = !empty($val) ? '1' : ''; + elseif ($k === 'noindex') $val = !empty($val) ? '1' : '2'; + elseif ($k === 'nofollow') $val = !empty($val) ? '1' : ''; + else $val = (string) $val; + update_post_meta($id, $mk, $val); + $changed[] = $k; + } + return ['post_id' => $id, 'changed' => $changed]; + } + + public static function bulkAudit(array $args): array { + $types = !empty($args['post_types']) && is_array($args['post_types']) ? $args['post_types'] : ['post', 'page']; + $posts = get_posts(['post_type' => $types, 'post_status' => 'publish', + 'numberposts' => (int) ($args['per_page'] ?? 100), 'fields' => 'ids']); + $issues = []; + foreach ($posts as $pid) { + $md = get_post_meta($pid, '_yoast_wpseo_metadesc', true); + $fk = get_post_meta($pid, '_yoast_wpseo_focuskw', true); + $sc = (int) get_post_meta($pid, '_yoast_wpseo_linkdex', true); + $probs = []; + if ($md === '') $probs[] = 'no_metadesc'; + if ($fk === '') $probs[] = 'no_focus_keyword'; + if ($sc > 0 && $sc < 40) $probs[] = 'low_seo_score(' . $sc . ')'; + if ($probs) $issues[] = ['id' => $pid, 'title' => get_the_title($pid), 'issues' => $probs]; + } + return ['scanned' => count($posts), 'with_issues' => count($issues), 'offenders' => $issues]; + } + + public static function listRedirects(array $args): array { + if (!defined('WPSEO_PREMIUM_VERSION')) { + return ['supported' => false, 'note' => 'Yoast Premium not active; redirect manager unavailable.', 'redirects' => []]; + } + $opt = get_option('wpseo-premium-redirects-base', []); + $out = []; + if (is_array($opt)) { + foreach ($opt as $origin => $r) { + $out[] = ['origin' => $origin, 'target' => $r['url'] ?? '', 'type' => $r['type'] ?? '']; + } + } + return ['supported' => true, 'count' => count($out), 'redirects' => $out]; + } +}