__( 'Codex Blog Admin', 'codex-blog-abilities' ), 'description' => __( 'Administrative blog operations exposed to trusted MCP clients.', 'codex-blog-abilities' ), ) ); } ); add_action( 'wp_abilities_api_init', static function() { if ( ! function_exists( 'wp_register_ability' ) ) { return; } codex_blog_register_ability( 'get-site-info', 'Get Site Info', 'Read basic WordPress site, theme, and current authenticated user information.', null, 'codex_blog_get_site_info', static function() { return current_user_can( 'read' ); }, true ); codex_blog_register_ability( 'list-post-types', 'List Post Types', 'List registered post types and their key capabilities.', codex_blog_schema( array( 'include_non_public' => array( 'type' => 'boolean', 'description' => 'Whether to include non-public post types.', ), ) ), 'codex_blog_list_post_types', static function() { return current_user_can( 'edit_posts' ); }, true ); codex_blog_register_ability( 'list-posts', 'List Posts', 'Query posts, pages, or custom post types with pagination and basic filters.', codex_blog_schema( array( 'post_type' => array( 'type' => 'string', 'description' => 'Post type slug. Defaults to post.', ), 'status' => array( 'type' => 'string', 'description' => 'Post status such as publish, draft, pending, private, trash, or any.', ), 'search' => array( 'type' => 'string', 'description' => 'Search phrase.', ), 'page' => array( 'type' => 'integer', 'minimum' => 1, 'description' => 'Page number. Defaults to 1.', ), 'per_page' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 100, 'description' => 'Results per page. Defaults to 10.', ), 'orderby' => array( 'type' => 'string', 'description' => 'Order by field. Common values: date, modified, title, ID, menu_order.', ), 'order' => array( 'type' => 'string', 'enum' => array( 'ASC', 'DESC', 'asc', 'desc' ), 'description' => 'Sort order.', ), ) ), 'codex_blog_list_posts', static function( $input ) { $args = codex_blog_input( $input ); $post_type = codex_blog_post_type_from_input( $args ); $type_obj = get_post_type_object( $post_type ); return $type_obj && current_user_can( $type_obj->cap->edit_posts ); }, true ); codex_blog_register_ability( 'get-post', 'Get Post', 'Read one post, page, attachment, or custom post type item by ID.', codex_blog_schema( array( 'id' => array( 'type' => 'integer', 'minimum' => 1, 'description' => 'Post ID.', ), ), array( 'id' ) ), 'codex_blog_get_post', static function( $input ) { $args = codex_blog_input( $input ); $post = get_post( (int) $args['id'] ); return $post && current_user_can( 'edit_post', $post->ID ); }, true ); codex_blog_register_ability( 'create-post', 'Create Post', 'Create a post, page, or custom post type item. Publishing requires the relevant publish capability.', codex_blog_schema( array( 'post_type' => array( 'type' => 'string' ), 'title' => array( 'type' => 'string' ), 'content' => array( 'type' => 'string' ), 'excerpt' => array( 'type' => 'string' ), 'status' => array( 'type' => 'string', 'enum' => array( 'draft', 'pending', 'publish', 'private' ), ), 'slug' => array( 'type' => 'string' ), 'parent_id' => array( 'type' => 'integer', 'minimum' => 0, ), 'terms' => array( 'type' => 'object', 'description' => 'Object keyed by taxonomy slug. Values are arrays of term IDs, slugs, or names.', 'additionalProperties' => array( 'type' => 'array', 'items' => array( 'type' => array( 'integer', 'string' ), ), ), ), ), array( 'title' ) ), 'codex_blog_create_post', 'codex_blog_can_create_post', false ); codex_blog_register_ability( 'update-post', 'Update Post', 'Update fields and taxonomy terms for an existing post, page, or custom post type item.', codex_blog_schema( array( 'id' => array( 'type' => 'integer', 'minimum' => 1, ), 'title' => array( 'type' => 'string' ), 'content' => array( 'type' => 'string' ), 'excerpt' => array( 'type' => 'string' ), 'status' => array( 'type' => 'string', 'enum' => array( 'draft', 'pending', 'publish', 'private', 'trash' ), ), 'slug' => array( 'type' => 'string' ), 'parent_id' => array( 'type' => 'integer', 'minimum' => 0, ), 'terms' => array( 'type' => 'object', 'additionalProperties' => array( 'type' => 'array', 'items' => array( 'type' => array( 'integer', 'string' ), ), ), ), ), array( 'id' ) ), 'codex_blog_update_post', 'codex_blog_can_update_post', false ); codex_blog_register_ability( 'delete-post', 'Delete Post', 'Trash or permanently delete a post, page, attachment, or custom post type item.', codex_blog_schema( array( 'id' => array( 'type' => 'integer', 'minimum' => 1, ), 'force' => array( 'type' => 'boolean', 'description' => 'If true, permanently delete. Otherwise move to trash when possible.', ), ), array( 'id' ) ), 'codex_blog_delete_post', static function( $input ) { $args = codex_blog_input( $input ); return ! empty( $args['id'] ) && current_user_can( 'delete_post', (int) $args['id'] ); }, false, true ); codex_blog_register_ability( 'list-terms', 'List Terms', 'List taxonomy terms.', codex_blog_schema( array( 'taxonomy' => array( 'type' => 'string' ), 'hide_empty' => array( 'type' => 'boolean' ), 'search' => array( 'type' => 'string' ), 'page' => array( 'type' => 'integer', 'minimum' => 1, ), 'per_page' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 100, ), ), array( 'taxonomy' ) ), 'codex_blog_list_terms', static function() { return current_user_can( 'edit_posts' ); }, true ); codex_blog_register_ability( 'create-term', 'Create Term', 'Create a taxonomy term.', codex_blog_schema( array( 'taxonomy' => array( 'type' => 'string' ), 'name' => array( 'type' => 'string' ), 'slug' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ), 'parent_id' => array( 'type' => 'integer', 'minimum' => 0, ), ), array( 'taxonomy', 'name' ) ), 'codex_blog_create_term', 'codex_blog_can_manage_terms', false ); codex_blog_register_ability( 'update-term', 'Update Term', 'Update a taxonomy term.', codex_blog_schema( array( 'taxonomy' => array( 'type' => 'string' ), 'term_id' => array( 'type' => 'integer', 'minimum' => 1, ), 'name' => array( 'type' => 'string' ), 'slug' => array( 'type' => 'string' ), 'description' => array( 'type' => 'string' ), 'parent_id' => array( 'type' => 'integer', 'minimum' => 0, ), ), array( 'taxonomy', 'term_id' ) ), 'codex_blog_update_term', 'codex_blog_can_manage_terms', false ); codex_blog_register_ability( 'delete-term', 'Delete Term', 'Delete a taxonomy term.', codex_blog_schema( array( 'taxonomy' => array( 'type' => 'string' ), 'term_id' => array( 'type' => 'integer', 'minimum' => 1, ), ), array( 'taxonomy', 'term_id' ) ), 'codex_blog_delete_term', 'codex_blog_can_manage_terms', false, true ); codex_blog_register_ability( 'list-comments', 'List Comments', 'List comments with moderation status filters.', codex_blog_schema( array( 'status' => array( 'type' => 'string', 'description' => 'Comment status: all, approve, hold, spam, trash.', ), 'post_id' => array( 'type' => 'integer', 'minimum' => 1, ), 'page' => array( 'type' => 'integer', 'minimum' => 1, ), 'per_page' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 100, ), ) ), 'codex_blog_list_comments', static function() { return current_user_can( 'moderate_comments' ); }, true ); codex_blog_register_ability( 'update-comment', 'Update Comment', 'Update a comment moderation status or content.', codex_blog_schema( array( 'id' => array( 'type' => 'integer', 'minimum' => 1, ), 'status' => array( 'type' => 'string', 'enum' => array( 'approve', 'hold', 'spam', 'trash' ), ), 'content' => array( 'type' => 'string' ), ), array( 'id' ) ), 'codex_blog_update_comment', static function() { return current_user_can( 'moderate_comments' ); }, false ); codex_blog_register_ability( 'delete-comment', 'Delete Comment', 'Trash or permanently delete a comment.', codex_blog_schema( array( 'id' => array( 'type' => 'integer', 'minimum' => 1, ), 'force' => array( 'type' => 'boolean' ), ), array( 'id' ) ), 'codex_blog_delete_comment', static function() { return current_user_can( 'moderate_comments' ); }, false, true ); codex_blog_register_ability( 'list-media', 'List Media', 'List media library attachments.', codex_blog_schema( array( 'search' => array( 'type' => 'string' ), 'mime_type' => array( 'type' => 'string' ), 'page' => array( 'type' => 'integer', 'minimum' => 1, ), 'per_page' => array( 'type' => 'integer', 'minimum' => 1, 'maximum' => 100, ), ) ), 'codex_blog_list_media', static function() { return current_user_can( 'upload_files' ); }, true ); codex_blog_register_ability( 'list-plugins', 'List Plugins', 'List installed plugins and activation status.', null, 'codex_blog_list_plugins', static function() { return current_user_can( 'activate_plugins' ); }, true ); codex_blog_register_ability( 'activate-plugin', 'Activate Plugin', 'Activate an installed plugin by plugin file path.', codex_blog_schema( array( 'plugin_file' => array( 'type' => 'string', 'description' => 'Plugin file path, for example akismet/akismet.php.', ), ), array( 'plugin_file' ) ), 'codex_blog_activate_plugin', static function() { return current_user_can( 'activate_plugins' ); }, false ); codex_blog_register_ability( 'deactivate-plugin', 'Deactivate Plugin', 'Deactivate an installed plugin by plugin file path. Blocks deactivating MCP-critical plugins unless allow_critical is true.', codex_blog_schema( array( 'plugin_file' => array( 'type' => 'string' ), 'allow_critical' => array( 'type' => 'boolean', 'description' => 'Allow deactivating mcp-adapter or codex-blog-abilities.', ), ), array( 'plugin_file' ) ), 'codex_blog_deactivate_plugin', static function() { return current_user_can( 'activate_plugins' ); }, false ); codex_blog_register_ability( 'get-options', 'Get Options', 'Read selected safe WordPress options. If names is omitted, reads a common admin allowlist.', codex_blog_schema( array( 'names' => array( 'type' => 'array', 'items' => array( 'type' => 'string' ), ), ) ), 'codex_blog_get_options', static function() { return current_user_can( 'manage_options' ); }, true ); codex_blog_register_ability( 'update-options', 'Update Options', 'Update selected safe WordPress options from a strict allowlist.', codex_blog_schema( array( 'updates' => array( 'type' => 'object', 'additionalProperties' => true, ), ), array( 'updates' ) ), 'codex_blog_update_options', static function() { return current_user_can( 'manage_options' ); }, false ); } ); function codex_blog_register_ability( $slug, $label, $description, $input_schema, $callback, $permission_callback, $readonly, $destructive = false ) { $args = array( 'label' => __( $label, 'codex-blog-abilities' ), 'description' => __( $description, 'codex-blog-abilities' ), 'category' => 'codex-blog-admin', 'output_schema' => codex_blog_output_schema(), 'execute_callback' => $callback, 'permission_callback' => $permission_callback, 'meta' => array( 'mcp' => array( 'public' => true, ), 'annotations' => array( 'readonly' => (bool) $readonly, 'destructive' => (bool) $destructive, 'idempotent' => false, ), ), ); if ( null !== $input_schema ) { $args['input_schema'] = $input_schema; } wp_register_ability( 'codex-blog/' . $slug, $args ); } function codex_blog_schema( $properties, $required = array() ) { return array( 'type' => 'object', 'properties' => $properties, 'required' => $required, 'additionalProperties' => false, ); } function codex_blog_output_schema() { return array( 'type' => 'object', 'additionalProperties' => true, ); } function codex_blog_input( $input ) { if ( is_object( $input ) ) { $input = get_object_vars( $input ); } if ( ! is_array( $input ) ) { return array(); } foreach ( $input as $key => $value ) { if ( is_object( $value ) ) { $input[ $key ] = codex_blog_input( $value ); } elseif ( is_array( $value ) ) { $input[ $key ] = codex_blog_deep_normalize( $value ); } } return $input; } function codex_blog_deep_normalize( $value ) { if ( is_object( $value ) ) { return codex_blog_input( $value ); } if ( ! is_array( $value ) ) { return $value; } foreach ( $value as $key => $child ) { $value[ $key ] = codex_blog_deep_normalize( $child ); } return $value; } function codex_blog_limit( $value, $default, $min, $max ) { $value = (int) $value; if ( $value < $min ) { $value = $default; } return min( $max, max( $min, $value ) ); } function codex_blog_post_type_from_input( $args ) { $post_type = isset( $args['post_type'] ) ? sanitize_key( $args['post_type'] ) : 'post'; return $post_type ? $post_type : 'post'; } function codex_blog_post_to_array( WP_Post $post, $include_content = false ) { $data = array( 'id' => (int) $post->ID, 'post_type' => $post->post_type, 'status' => $post->post_status, 'title' => get_the_title( $post ), 'slug' => $post->post_name, 'author_id' => (int) $post->post_author, 'date' => get_post_time( DATE_ATOM, false, $post ), 'modified' => get_post_modified_time( DATE_ATOM, false, $post ), 'link' => get_permalink( $post ), 'parent_id' => (int) $post->post_parent, 'comment_count' => (int) $post->comment_count, ); if ( $include_content ) { $data['content'] = $post->post_content; $data['excerpt'] = $post->post_excerpt; $data['terms'] = codex_blog_get_post_terms( $post->ID ); } return $data; } function codex_blog_get_post_terms( $post_id ) { $result = array(); $taxonomies = get_object_taxonomies( get_post_type( $post_id ) ); foreach ( $taxonomies as $taxonomy ) { $terms = get_the_terms( $post_id, $taxonomy ); if ( is_wp_error( $terms ) || empty( $terms ) ) { $result[ $taxonomy ] = array(); continue; } $result[ $taxonomy ] = array_map( static function( WP_Term $term ) { return array( 'id' => (int) $term->term_id, 'name' => $term->name, 'slug' => $term->slug, ); }, $terms ); } return $result; } function codex_blog_get_site_info() { $current_user = wp_get_current_user(); $theme = wp_get_theme(); return array( 'name' => get_bloginfo( 'name' ), 'description' => get_bloginfo( 'description' ), 'url' => site_url(), 'home' => home_url(), 'wp_version' => get_bloginfo( 'version' ), 'active_theme' => array( 'name' => $theme->get( 'Name' ), 'version' => $theme->get( 'Version' ), ), 'current_user' => array( 'id' => (int) $current_user->ID, 'login' => $current_user->user_login, 'display' => $current_user->display_name, 'roles' => $current_user->roles, 'capability' => array( 'edit_posts' => current_user_can( 'edit_posts' ), 'manage_options' => current_user_can( 'manage_options' ), 'activate_plugins' => current_user_can( 'activate_plugins' ), ), ), ); } function codex_blog_list_post_types( $input ) { $args = codex_blog_input( $input ); $include_non_public = ! empty( $args['include_non_public'] ); $post_types = get_post_types( array(), 'objects' ); $result = array(); foreach ( $post_types as $post_type => $type_obj ) { if ( ! $include_non_public && ! $type_obj->public ) { continue; } $result[] = array( 'name' => $post_type, 'label' => $type_obj->label, 'public' => (bool) $type_obj->public, 'show_ui' => (bool) $type_obj->show_ui, 'hierarchical' => (bool) $type_obj->hierarchical, 'capabilities' => array( 'edit_posts' => $type_obj->cap->edit_posts, 'publish_posts' => $type_obj->cap->publish_posts, 'delete_posts' => $type_obj->cap->delete_posts, ), ); } return array( 'post_types' => $result ); } function codex_blog_list_posts( $input ) { $args = codex_blog_input( $input ); $post_type = codex_blog_post_type_from_input( $args ); $status = isset( $args['status'] ) ? sanitize_key( $args['status'] ) : 'any'; $page = codex_blog_limit( $args['page'] ?? 1, 1, 1, 100000 ); $per_page = codex_blog_limit( $args['per_page'] ?? 10, 10, 1, 100 ); $query_args = array( 'post_type' => $post_type, 'post_status' => $status ? $status : 'any', 'paged' => $page, 'posts_per_page' => $per_page, 'orderby' => isset( $args['orderby'] ) ? sanitize_key( $args['orderby'] ) : 'date', 'order' => isset( $args['order'] ) && 'ASC' === strtoupper( $args['order'] ) ? 'ASC' : 'DESC', ); if ( ! empty( $args['search'] ) ) { $query_args['s'] = sanitize_text_field( $args['search'] ); } $query = new WP_Query( $query_args ); return array( 'page' => $page, 'per_page' => $per_page, 'total' => (int) $query->found_posts, 'total_pages' => (int) $query->max_num_pages, 'posts' => array_map( static function( WP_Post $post ) { return codex_blog_post_to_array( $post, false ); }, $query->posts ), ); } function codex_blog_get_post( $input ) { $args = codex_blog_input( $input ); $post = get_post( (int) $args['id'] ); if ( ! $post ) { return new WP_Error( 'codex_blog_not_found', 'Post not found.' ); } return array( 'post' => codex_blog_post_to_array( $post, true ) ); } function codex_blog_can_create_post( $input ) { $args = codex_blog_input( $input ); $post_type = codex_blog_post_type_from_input( $args ); $type_obj = get_post_type_object( $post_type ); if ( ! $type_obj || ! current_user_can( $type_obj->cap->edit_posts ) ) { return false; } $status = isset( $args['status'] ) ? sanitize_key( $args['status'] ) : 'draft'; if ( in_array( $status, array( 'publish', 'private' ), true ) && ! current_user_can( $type_obj->cap->publish_posts ) ) { return false; } return true; } function codex_blog_create_post( $input ) { $args = codex_blog_input( $input ); $post_type = codex_blog_post_type_from_input( $args ); $status = isset( $args['status'] ) ? sanitize_key( $args['status'] ) : 'draft'; $postarr = array( 'post_type' => $post_type, 'post_status' => $status, 'post_title' => sanitize_text_field( $args['title'] ?? '' ), 'post_content' => isset( $args['content'] ) ? wp_kses_post( $args['content'] ) : '', 'post_excerpt' => isset( $args['excerpt'] ) ? wp_kses_post( $args['excerpt'] ) : '', ); if ( isset( $args['slug'] ) ) { $postarr['post_name'] = sanitize_title( $args['slug'] ); } if ( isset( $args['parent_id'] ) ) { $postarr['post_parent'] = (int) $args['parent_id']; } $post_id = wp_insert_post( $postarr, true ); if ( is_wp_error( $post_id ) ) { return $post_id; } $terms_result = codex_blog_apply_terms( $post_id, $args['terms'] ?? array() ); if ( is_wp_error( $terms_result ) ) { return $terms_result; } return array( 'success' => true, 'post' => codex_blog_post_to_array( get_post( $post_id ), true ), ); } function codex_blog_can_update_post( $input ) { $args = codex_blog_input( $input ); if ( empty( $args['id'] ) || ! current_user_can( 'edit_post', (int) $args['id'] ) ) { return false; } if ( ! empty( $args['status'] ) && in_array( sanitize_key( $args['status'] ), array( 'publish', 'private' ), true ) ) { $post = get_post( (int) $args['id'] ); $type_obj = $post ? get_post_type_object( $post->post_type ) : null; return $type_obj && current_user_can( $type_obj->cap->publish_posts ); } return true; } function codex_blog_update_post( $input ) { $args = codex_blog_input( $input ); $post = get_post( (int) $args['id'] ); if ( ! $post ) { return new WP_Error( 'codex_blog_not_found', 'Post not found.' ); } $postarr = array( 'ID' => $post->ID ); if ( array_key_exists( 'title', $args ) ) { $postarr['post_title'] = sanitize_text_field( $args['title'] ); } if ( array_key_exists( 'content', $args ) ) { $postarr['post_content'] = wp_kses_post( $args['content'] ); } if ( array_key_exists( 'excerpt', $args ) ) { $postarr['post_excerpt'] = wp_kses_post( $args['excerpt'] ); } if ( array_key_exists( 'status', $args ) ) { $postarr['post_status'] = sanitize_key( $args['status'] ); } if ( array_key_exists( 'slug', $args ) ) { $postarr['post_name'] = sanitize_title( $args['slug'] ); } if ( array_key_exists( 'parent_id', $args ) ) { $postarr['post_parent'] = (int) $args['parent_id']; } $result = wp_update_post( $postarr, true ); if ( is_wp_error( $result ) ) { return $result; } $terms_result = codex_blog_apply_terms( $post->ID, $args['terms'] ?? array() ); if ( is_wp_error( $terms_result ) ) { return $terms_result; } return array( 'success' => true, 'post' => codex_blog_post_to_array( get_post( $post->ID ), true ), ); } function codex_blog_delete_post( $input ) { $args = codex_blog_input( $input ); $id = (int) $args['id']; $force = ! empty( $args['force'] ); $post = get_post( $id ); if ( ! $post ) { return new WP_Error( 'codex_blog_not_found', 'Post not found.' ); } $result = $force ? wp_delete_post( $id, true ) : wp_trash_post( $id ); if ( ! $result ) { return new WP_Error( 'codex_blog_delete_failed', 'The post could not be deleted.' ); } return array( 'success' => true, 'id' => $id, 'previous_status' => $post->post_status, 'force' => $force, ); } function codex_blog_apply_terms( $post_id, $terms_by_taxonomy ) { if ( empty( $terms_by_taxonomy ) ) { return true; } if ( ! is_array( $terms_by_taxonomy ) ) { return new WP_Error( 'codex_blog_invalid_terms', 'Terms must be an object keyed by taxonomy.' ); } foreach ( $terms_by_taxonomy as $taxonomy => $terms ) { $taxonomy = sanitize_key( $taxonomy ); if ( ! taxonomy_exists( $taxonomy ) ) { return new WP_Error( 'codex_blog_invalid_taxonomy', 'Invalid taxonomy: ' . $taxonomy ); } $tax_obj = get_taxonomy( $taxonomy ); if ( ! current_user_can( $tax_obj->cap->assign_terms ) ) { return new WP_Error( 'codex_blog_forbidden_terms', 'Current user cannot assign terms for taxonomy: ' . $taxonomy ); } $result = wp_set_object_terms( $post_id, $terms, $taxonomy, false ); if ( is_wp_error( $result ) ) { return $result; } } return true; } function codex_blog_list_terms( $input ) { $args = codex_blog_input( $input ); $taxonomy = sanitize_key( $args['taxonomy'] ); if ( ! taxonomy_exists( $taxonomy ) ) { return new WP_Error( 'codex_blog_invalid_taxonomy', 'Invalid taxonomy.' ); } $page = codex_blog_limit( $args['page'] ?? 1, 1, 1, 100000 ); $per_page = codex_blog_limit( $args['per_page'] ?? 50, 50, 1, 100 ); $query = array( 'taxonomy' => $taxonomy, 'hide_empty' => ! empty( $args['hide_empty'] ), 'number' => $per_page, 'offset' => ( $page - 1 ) * $per_page, ); if ( ! empty( $args['search'] ) ) { $query['search'] = sanitize_text_field( $args['search'] ); } $terms = get_terms( $query ); if ( is_wp_error( $terms ) ) { return $terms; } return array( 'terms' => array_map( static function( WP_Term $term ) { return array( 'id' => (int) $term->term_id, 'taxonomy' => $term->taxonomy, 'name' => $term->name, 'slug' => $term->slug, 'description' => $term->description, 'parent_id' => (int) $term->parent, 'count' => (int) $term->count, ); }, $terms ), ); } function codex_blog_can_manage_terms( $input ) { $args = codex_blog_input( $input ); $taxonomy = isset( $args['taxonomy'] ) ? sanitize_key( $args['taxonomy'] ) : ''; $tax_obj = $taxonomy ? get_taxonomy( $taxonomy ) : null; return $tax_obj && current_user_can( $tax_obj->cap->manage_terms ); } function codex_blog_create_term( $input ) { $args = codex_blog_input( $input ); $taxonomy = sanitize_key( $args['taxonomy'] ); $termargs = array(); foreach ( array( 'slug', 'description' ) as $field ) { if ( isset( $args[ $field ] ) ) { $termargs[ $field ] = 'slug' === $field ? sanitize_title( $args[ $field ] ) : sanitize_text_field( $args[ $field ] ); } } if ( isset( $args['parent_id'] ) ) { $termargs['parent'] = (int) $args['parent_id']; } $result = wp_insert_term( sanitize_text_field( $args['name'] ), $taxonomy, $termargs ); if ( is_wp_error( $result ) ) { return $result; } return array( 'success' => true, 'term_id' => (int) $result['term_id'], ); } function codex_blog_update_term( $input ) { $args = codex_blog_input( $input ); $taxonomy = sanitize_key( $args['taxonomy'] ); $termargs = array(); foreach ( array( 'name', 'slug', 'description' ) as $field ) { if ( isset( $args[ $field ] ) ) { $termargs[ $field ] = 'slug' === $field ? sanitize_title( $args[ $field ] ) : sanitize_text_field( $args[ $field ] ); } } if ( isset( $args['parent_id'] ) ) { $termargs['parent'] = (int) $args['parent_id']; } $result = wp_update_term( (int) $args['term_id'], $taxonomy, $termargs ); if ( is_wp_error( $result ) ) { return $result; } return array( 'success' => true, 'term_id' => (int) $result['term_id'], ); } function codex_blog_delete_term( $input ) { $args = codex_blog_input( $input ); $result = wp_delete_term( (int) $args['term_id'], sanitize_key( $args['taxonomy'] ) ); if ( is_wp_error( $result ) ) { return $result; } return array( 'success' => (bool) $result, 'deleted' => (bool) $result, ); } function codex_blog_list_comments( $input ) { $args = codex_blog_input( $input ); $page = codex_blog_limit( $args['page'] ?? 1, 1, 1, 100000 ); $per_page = codex_blog_limit( $args['per_page'] ?? 20, 20, 1, 100 ); $query = array( 'status' => isset( $args['status'] ) ? sanitize_key( $args['status'] ) : 'all', 'number' => $per_page, 'offset' => ( $page - 1 ) * $per_page, ); if ( ! empty( $args['post_id'] ) ) { $query['post_id'] = (int) $args['post_id']; } $comments = get_comments( $query ); return array( 'comments' => array_map( static function( WP_Comment $comment ) { return array( 'id' => (int) $comment->comment_ID, 'post_id' => (int) $comment->comment_post_ID, 'author' => $comment->comment_author, 'author_url' => $comment->comment_author_url, 'date' => mysql2date( DATE_ATOM, $comment->comment_date_gmt, false ), 'status' => wp_get_comment_status( $comment ), 'content' => $comment->comment_content, ); }, $comments ), ); } function codex_blog_update_comment( $input ) { $args = codex_blog_input( $input ); $id = (int) $args['id']; if ( isset( $args['status'] ) ) { wp_set_comment_status( $id, sanitize_key( $args['status'] ) ); } if ( isset( $args['content'] ) ) { $result = wp_update_comment( array( 'comment_ID' => $id, 'comment_content' => wp_kses_post( $args['content'] ), ) ); if ( false === $result ) { return new WP_Error( 'codex_blog_comment_update_failed', 'Comment update failed.' ); } } return array( 'success' => true, 'comment' => get_comment( $id, ARRAY_A ), ); } function codex_blog_delete_comment( $input ) { $args = codex_blog_input( $input ); $result = wp_delete_comment( (int) $args['id'], ! empty( $args['force'] ) ); return array( 'success' => (bool) $result, 'deleted' => (bool) $result, ); } function codex_blog_list_media( $input ) { $args = codex_blog_input( $input ); $page = codex_blog_limit( $args['page'] ?? 1, 1, 1, 100000 ); $per_page = codex_blog_limit( $args['per_page'] ?? 20, 20, 1, 100 ); $query_args = array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'paged' => $page, 'posts_per_page' => $per_page, ); if ( ! empty( $args['search'] ) ) { $query_args['s'] = sanitize_text_field( $args['search'] ); } if ( ! empty( $args['mime_type'] ) ) { $query_args['post_mime_type'] = sanitize_mime_type( $args['mime_type'] ); } $query = new WP_Query( $query_args ); return array( 'total' => (int) $query->found_posts, 'total_pages' => (int) $query->max_num_pages, 'media' => array_map( static function( WP_Post $post ) { return array( 'id' => (int) $post->ID, 'title' => get_the_title( $post ), 'mime_type' => $post->post_mime_type, 'url' => wp_get_attachment_url( $post->ID ), 'date' => get_post_time( DATE_ATOM, false, $post ), 'alt_text' => get_post_meta( $post->ID, '_wp_attachment_image_alt', true ), 'parent_id' => (int) $post->post_parent, ); }, $query->posts ), ); } function codex_blog_list_plugins() { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugins = get_plugins(); $result = array(); foreach ( $plugins as $plugin_file => $data ) { $result[] = array( 'plugin_file' => $plugin_file, 'name' => $data['Name'] ?? $plugin_file, 'version' => $data['Version'] ?? '', 'description' => wp_strip_all_tags( $data['Description'] ?? '' ), 'active' => is_plugin_active( $plugin_file ), ); } return array( 'plugins' => $result ); } function codex_blog_activate_plugin( $input ) { $args = codex_blog_input( $input ); $plugin_file = plugin_basename( sanitize_text_field( $args['plugin_file'] ) ); if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } if ( ! array_key_exists( $plugin_file, get_plugins() ) ) { return new WP_Error( 'codex_blog_plugin_not_found', 'Plugin not found.' ); } $result = activate_plugin( $plugin_file ); if ( is_wp_error( $result ) ) { return $result; } return array( 'success' => true, 'plugin_file' => $plugin_file, 'active' => is_plugin_active( $plugin_file ), ); } function codex_blog_deactivate_plugin( $input ) { $args = codex_blog_input( $input ); $plugin_file = plugin_basename( sanitize_text_field( $args['plugin_file'] ) ); $critical = array( CODEX_BLOG_ABILITIES_PLUGIN_FILE, 'mcp-adapter/mcp-adapter.php', ); if ( ! function_exists( 'deactivate_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } if ( in_array( $plugin_file, $critical, true ) && empty( $args['allow_critical'] ) ) { return new WP_Error( 'codex_blog_critical_plugin', 'Refusing to deactivate an MCP-critical plugin unless allow_critical is true.' ); } deactivate_plugins( $plugin_file ); return array( 'success' => true, 'plugin_file' => $plugin_file, 'active' => is_plugin_active( $plugin_file ), ); } function codex_blog_option_allowlist() { return array( 'blogname', 'blogdescription', 'admin_email', 'start_of_week', 'timezone_string', 'date_format', 'time_format', 'posts_per_page', 'default_category', 'default_post_format', 'default_comment_status', 'default_ping_status', 'comment_moderation', 'comment_previously_approved', 'permalink_structure', ); } function codex_blog_get_options( $input ) { $args = codex_blog_input( $input ); $allowlist = codex_blog_option_allowlist(); $names = ! empty( $args['names'] ) && is_array( $args['names'] ) ? $args['names'] : $allowlist; $result = array(); foreach ( $names as $name ) { $name = sanitize_key( $name ); if ( ! in_array( $name, $allowlist, true ) ) { continue; } $result[ $name ] = get_option( $name ); } return array( 'options' => $result, 'allowlist' => $allowlist, ); } function codex_blog_update_options( $input ) { $args = codex_blog_input( $input ); $updates = isset( $args['updates'] ) && is_array( $args['updates'] ) ? $args['updates'] : array(); $allowlist = codex_blog_option_allowlist(); $result = array(); foreach ( $updates as $name => $value ) { $name = sanitize_key( $name ); if ( ! in_array( $name, $allowlist, true ) ) { $result[ $name ] = array( 'updated' => false, 'error' => 'Option is not in the allowlist.', ); continue; } $old_value = get_option( $name ); $new_value = is_string( $value ) ? sanitize_text_field( $value ) : $value; $updated = update_option( $name, $new_value ); if ( 'permalink_structure' === $name ) { flush_rewrite_rules(); } $result[ $name ] = array( 'updated' => (bool) $updated, 'previous_value' => $old_value, 'new_value' => get_option( $name ), ); } return array( 'options' => $result ); }