$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]; } }