feat: TaxonomyTools, OptionTools, PluginTools, ThemeTools, MenuTools, CommentTools, SeoTools, SiteTools
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
namespace AskTamMCP\Tools;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class TaxonomyTools {
|
||||
public static function listTerms(array $args): array {
|
||||
$terms = get_terms(['taxonomy' => $args['taxonomy'], 'hide_empty' => false, 'number' => (int) ($args['per_page'] ?? 50), 'parent' => $args['parent'] ?? '', 'search' => $args['search'] ?? '']);
|
||||
if (is_wp_error($terms)) throw new \RuntimeException($terms->get_error_message());
|
||||
return ['terms' => array_map(fn($t) => ['id' => $t->term_id, 'name' => $t->name, 'slug' => $t->slug, 'count' => $t->count, 'parent' => $t->parent, 'description' => $t->description], $terms)];
|
||||
}
|
||||
|
||||
public static function createTerm(array $args): array {
|
||||
$result = wp_insert_term($args['name'], $args['taxonomy'], ['slug' => $args['slug'] ?? '', 'description' => $args['description'] ?? '', 'parent' => (int) ($args['parent'] ?? 0)]);
|
||||
if (is_wp_error($result)) throw new \RuntimeException($result->get_error_message());
|
||||
return ['id' => $result['term_id'], 'name' => $args['name']];
|
||||
}
|
||||
|
||||
public static function updateTerm(array $args): array {
|
||||
$data = [];
|
||||
if (isset($args['name'])) $data['name'] = $args['name'];
|
||||
if (isset($args['slug'])) $data['slug'] = $args['slug'];
|
||||
if (isset($args['description'])) $data['description'] = $args['description'];
|
||||
if (isset($args['parent'])) $data['parent'] = (int) $args['parent'];
|
||||
$result = wp_update_term((int) $args['term_id'], $args['taxonomy'], $data);
|
||||
if (is_wp_error($result)) throw new \RuntimeException($result->get_error_message());
|
||||
return ['id' => (int) $args['term_id'], 'updated' => true];
|
||||
}
|
||||
|
||||
public static function deleteTerm(array $args): array {
|
||||
$result = wp_delete_term((int) $args['term_id'], $args['taxonomy']);
|
||||
if (is_wp_error($result) || $result === false) throw new \RuntimeException("Failed to delete term");
|
||||
return ['id' => (int) $args['term_id'], 'deleted' => true];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class OptionTools {
|
||||
// Options that should never be exposed
|
||||
private const BLOCKED = ['auth_key', 'secure_auth_key', 'logged_in_key', 'nonce_key', 'auth_salt', 'secure_auth_salt', 'logged_in_salt', 'nonce_salt', 'admin_email'];
|
||||
|
||||
public static function getOption(array $args): array {
|
||||
$name = $args['option_name'];
|
||||
if (in_array($name, self::BLOCKED, true)) throw new \RuntimeException("Access to option '{$name}' is blocked.");
|
||||
$value = get_option($name, null);
|
||||
return ['option_name' => $name, 'value' => $value, 'exists' => $value !== null];
|
||||
}
|
||||
|
||||
public static function updateOption(array $args): array {
|
||||
$name = $args['option_name'];
|
||||
if (in_array($name, self::BLOCKED, true)) throw new \RuntimeException("Modifying option '{$name}' is blocked.");
|
||||
$result = update_option($name, $args['option_value']);
|
||||
return ['option_name' => $name, 'updated' => $result];
|
||||
}
|
||||
|
||||
public static function listOptions(array $args): array {
|
||||
global $wpdb;
|
||||
$prefix = $args['prefix'] ?? '';
|
||||
$like = $wpdb->esc_like($prefix) . '%';
|
||||
$rows = $wpdb->get_results($wpdb->prepare("SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s AND autoload = 'yes' LIMIT 100", $like));
|
||||
$names = array_map(fn($r) => $r->option_name, $rows);
|
||||
$names = array_values(array_filter($names, fn($n) => !in_array($n, self::BLOCKED, true)));
|
||||
return ['options' => $names, 'count' => count($names)];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PluginTools {
|
||||
public static function listPlugins(array $args): array {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
$all = get_plugins();
|
||||
$active = get_option('active_plugins', []);
|
||||
$status = $args['status'] ?? 'all';
|
||||
$plugins = [];
|
||||
foreach ($all as $file => $data) {
|
||||
$isActive = in_array($file, $active, true);
|
||||
if ($status === 'active' && !$isActive) continue;
|
||||
if ($status === 'inactive' && $isActive) continue;
|
||||
$plugins[] = ['file' => $file, 'name' => $data['Name'], 'version' => $data['Version'], 'active' => $isActive, 'author' => $data['Author']];
|
||||
}
|
||||
return ['plugins' => $plugins, 'count' => count($plugins)];
|
||||
}
|
||||
|
||||
public static function activatePlugin(array $args): array {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
$result = activate_plugin($args['plugin']);
|
||||
if (is_wp_error($result)) throw new \RuntimeException($result->get_error_message());
|
||||
return ['plugin' => $args['plugin'], 'activated' => true];
|
||||
}
|
||||
|
||||
public static function deactivatePlugin(array $args): array {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
deactivate_plugins($args['plugin']);
|
||||
return ['plugin' => $args['plugin'], 'deactivated' => true];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ThemeTools {
|
||||
public static function listThemes(array $args): array {
|
||||
$themes = wp_get_themes();
|
||||
$active = get_stylesheet();
|
||||
$list = [];
|
||||
foreach ($themes as $slug => $theme) {
|
||||
$list[] = ['slug' => $slug, 'name' => $theme->get('Name'), 'version' => $theme->get('Version'), 'active' => $slug === $active];
|
||||
}
|
||||
return ['themes' => $list];
|
||||
}
|
||||
|
||||
public static function getActiveTheme(array $args): array {
|
||||
$theme = wp_get_theme();
|
||||
return ['name' => $theme->get('Name'), 'slug' => get_stylesheet(), 'version' => $theme->get('Version'), 'author' => $theme->get('Author'), 'description' => $theme->get('Description'), 'template' => $theme->get_template()];
|
||||
}
|
||||
|
||||
public static function switchTheme(array $args): array {
|
||||
switch_theme($args['stylesheet']);
|
||||
return ['stylesheet' => $args['stylesheet'], 'switched' => true];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MenuTools {
|
||||
public static function listMenus(array $args): array {
|
||||
$menus = wp_get_nav_menus();
|
||||
return ['menus' => array_map(fn($m) => ['id' => $m->term_id, 'name' => $m->name, 'slug' => $m->slug, 'count' => $m->count], $menus)];
|
||||
}
|
||||
|
||||
public static function getMenu(array $args): array {
|
||||
$items = wp_get_nav_menu_items($args['menu']);
|
||||
if ($items === false) throw new \RuntimeException("Menu not found: {$args['menu']}");
|
||||
return ['items' => array_map(fn($i) => ['id' => $i->ID, 'title' => $i->title, 'url' => $i->url, 'parent' => (int) $i->menu_item_parent, 'order' => (int) $i->menu_order, 'type' => $i->type, 'object' => $i->object, 'object_id' => (int) $i->object_id], $items)];
|
||||
}
|
||||
|
||||
public static function updateMenuItem(array $args): array {
|
||||
$itemId = wp_update_nav_menu_item($args['menu'], (int) ($args['item_id'] ?? 0), ['menu-item-title' => $args['title'], 'menu-item-url' => $args['url'], 'menu-item-status' => 'publish', 'menu-item-parent-id' => $args['parent'] ?? 0, 'menu-item-position' => $args['position'] ?? 0]);
|
||||
if (is_wp_error($itemId)) throw new \RuntimeException($itemId->get_error_message());
|
||||
return ['item_id' => $itemId, 'saved' => true];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CommentTools {
|
||||
public static function listComments(array $args): array {
|
||||
$comments = get_comments(['post_id' => $args['post_id'] ?? 0, 'status' => $args['status'] ?? 'approve', 'number' => (int) ($args['per_page'] ?? 20), 'offset' => ((int) ($args['page'] ?? 1) - 1) * (int) ($args['per_page'] ?? 20)]);
|
||||
return ['comments' => array_map(fn($c) => ['id' => $c->comment_ID, 'post_id' => $c->comment_post_ID, 'author' => $c->comment_author, 'email' => $c->comment_author_email, 'content' => $c->comment_content, 'status' => $c->comment_approved, 'date' => $c->comment_date], $comments)];
|
||||
}
|
||||
|
||||
public static function approveComment(array $args): array {
|
||||
wp_set_comment_status((int) $args['comment_id'], 'approve');
|
||||
return ['id' => (int) $args['comment_id'], 'approved' => true];
|
||||
}
|
||||
|
||||
public static function spamComment(array $args): array {
|
||||
wp_spam_comment((int) $args['comment_id']);
|
||||
return ['id' => (int) $args['comment_id'], 'spammed' => true];
|
||||
}
|
||||
|
||||
public static function deleteComment(array $args): array {
|
||||
$result = wp_delete_comment((int) $args['comment_id'], (bool) ($args['force'] ?? false));
|
||||
if (!$result) throw new \RuntimeException("Failed to delete comment");
|
||||
return ['id' => (int) $args['comment_id'], 'deleted' => true];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SeoTools {
|
||||
// Supports Yoast SEO and RankMath
|
||||
private static function detectPlugin(): string {
|
||||
if (defined('WPSEO_VERSION')) return 'yoast';
|
||||
if (defined('RANK_MATH_VERSION')) return 'rankmath';
|
||||
return 'none';
|
||||
}
|
||||
|
||||
public static function getSeoMeta(array $args): array {
|
||||
$postId = (int) $args['post_id'];
|
||||
$plugin = self::detectPlugin();
|
||||
$meta = [];
|
||||
|
||||
if ($plugin === 'yoast') {
|
||||
$meta = ['title' => get_post_meta($postId, '_yoast_wpseo_title', true), 'description' => get_post_meta($postId, '_yoast_wpseo_metadesc', true), 'focus_keyword' => get_post_meta($postId, '_yoast_wpseo_focuskw', true), 'robots' => get_post_meta($postId, '_yoast_wpseo_meta-robots-noindex', true) ? 'noindex' : 'index'];
|
||||
} elseif ($plugin === 'rankmath') {
|
||||
$meta = ['title' => get_post_meta($postId, 'rank_math_title', true), 'description' => get_post_meta($postId, 'rank_math_description', true), 'focus_keyword' => get_post_meta($postId, 'rank_math_focus_keyword', true), 'robots' => get_post_meta($postId, 'rank_math_robots', true)];
|
||||
}
|
||||
|
||||
return ['post_id' => $postId, 'plugin' => $plugin, 'meta' => $meta];
|
||||
}
|
||||
|
||||
public static function updateSeoMeta(array $args): array {
|
||||
$postId = (int) $args['post_id'];
|
||||
$plugin = self::detectPlugin();
|
||||
|
||||
if ($plugin === 'yoast') {
|
||||
if (isset($args['title'])) update_post_meta($postId, '_yoast_wpseo_title', $args['title']);
|
||||
if (isset($args['description'])) update_post_meta($postId, '_yoast_wpseo_metadesc', $args['description']);
|
||||
if (isset($args['focus_keyword'])) update_post_meta($postId, '_yoast_wpseo_focuskw', $args['focus_keyword']);
|
||||
} elseif ($plugin === 'rankmath') {
|
||||
if (isset($args['title'])) update_post_meta($postId, 'rank_math_title', $args['title']);
|
||||
if (isset($args['description'])) update_post_meta($postId, 'rank_math_description', $args['description']);
|
||||
if (isset($args['focus_keyword'])) update_post_meta($postId, 'rank_math_focus_keyword', $args['focus_keyword']);
|
||||
if (isset($args['robots'])) update_post_meta($postId, 'rank_math_robots', explode(',', $args['robots']));
|
||||
} else {
|
||||
throw new \RuntimeException("No supported SEO plugin detected (Yoast or RankMath required).");
|
||||
}
|
||||
|
||||
return ['post_id' => $postId, 'updated' => true, 'plugin' => $plugin];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SiteTools {
|
||||
public static function getSiteInfo(array $args): array {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
$active = count(get_option('active_plugins', []));
|
||||
return ['site_name' => get_bloginfo('name'), 'site_url' => get_bloginfo('url'), 'wp_version' => get_bloginfo('version'), 'php_version' => PHP_VERSION, 'language' => get_bloginfo('language'), 'charset' => get_bloginfo('charset'), 'timezone' => get_option('timezone_string') ?: 'UTC', 'active_plugins' => $active, 'active_theme' => wp_get_theme()->get('Name'), 'is_multisite' => is_multisite()];
|
||||
}
|
||||
|
||||
public static function getHealth(array $args): array {
|
||||
if (!class_exists('WP_Site_Health')) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
|
||||
}
|
||||
$health = \WP_Site_Health::get_instance();
|
||||
$tests = $health->get_tests();
|
||||
$results = [];
|
||||
|
||||
foreach ($tests['direct'] as $id => $test) {
|
||||
if (is_callable($test['test'])) {
|
||||
$result = call_user_func($test['test']);
|
||||
$results[] = ['id' => $id, 'label' => $result['label'] ?? $id, 'status' => $result['status'] ?? 'unknown', 'description' => wp_strip_all_tags($result['description'] ?? '')];
|
||||
}
|
||||
}
|
||||
|
||||
return ['health_checks' => $results, 'count' => count($results)];
|
||||
}
|
||||
|
||||
public static function runCron(array $args): array {
|
||||
define('DOING_CRON', true);
|
||||
$crons = _get_cron_array();
|
||||
$ran = [];
|
||||
$now = time();
|
||||
|
||||
foreach ($crons as $timestamp => $hooks) {
|
||||
if ($timestamp > $now) continue;
|
||||
foreach ($hooks as $hook => $events) {
|
||||
foreach ($events as $key => $event) {
|
||||
do_action_ref_array($hook, $event['args']);
|
||||
$ran[] = $hook;
|
||||
wp_unschedule_event($timestamp, $hook, $event['args']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ['executed_hooks' => $ran, 'count' => count($ran)];
|
||||
}
|
||||
|
||||
public static function searchContent(array $args): array {
|
||||
$query = new \WP_Query(['s' => $args['query'], 'post_type' => $args['post_types'] ?? ['post', 'page'], 'posts_per_page' => (int) ($args['per_page'] ?? 20), 'post_status' => 'publish']);
|
||||
$results = [];
|
||||
foreach ($query->posts as $p) {
|
||||
$results[] = ['id' => $p->ID, 'type' => $p->post_type, 'title' => $p->post_title, 'excerpt' => wp_trim_excerpt('', $p), 'link' => get_permalink($p->ID), 'date' => $p->post_date];
|
||||
}
|
||||
return ['results' => $results, 'total' => $query->found_posts];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user