Files
advanced-mcp-plugin/includes/Extensions/Batch14.php
T

139 lines
7.0 KiB
PHP

<?php
/**
* Batch 14 — Yoast SEO deep (Pro-equivalent, net-new).
*
* Yoast-specific surface beyond the generic seo_* family (b2): cornerstone,
* primary category, SEO/readability scores, redirects (Premium), sitemap state.
* Guarded by WPSEO_VERSION — inert unless Yoast is active.
* GPL-2.0-or-later. See NOTICE.md.
*/
namespace AskTamMCP\Extensions;
defined('ABSPATH') || exit;
class Batch14 {
private const MAP = [
'title' => '_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];
}
}