diff --git a/includes/Extensions/Batch6.php b/includes/Extensions/Batch6.php new file mode 100644 index 0000000..2e76cf9 --- /dev/null +++ b/includes/Extensions/Batch6.php @@ -0,0 +1,139 @@ + 'object', 'properties' => $props]; + if ($required) $schema['required'] = $required; + return ['name' => $name, 'description' => $desc, 'scope' => $scope, + 'handler' => 'AskTamMCP\\Extensions\\Batch6::' . $method, 'inputSchema' => $schema]; + }; + + $registry->register($def('list_block_types', 'List registered Gutenberg block types (name, title, category). Optional category filter.', + ['category' => ['type' => 'string'], 'per_page' => ['type' => 'integer', 'default' => 200]], [], 'read', 'listBlockTypes')); + + $registry->register($def('insert_blocks', 'Insert Gutenberg blocks into a post from a simple spec. Each block: {type, content, level?, items?, url?, alt?, id?}. Types: paragraph, heading, list, image, quote, html. mode=append (default) or replace.', + ['post_id' => ['type' => 'integer'], + 'blocks' => ['type' => 'array', 'items' => ['type' => 'object']], + 'mode' => ['type' => 'string', 'enum' => ['append', 'replace'], 'default' => 'append']], + ['post_id', 'blocks'], 'write', 'insertBlocks')); + + $registry->register($def('create_reusable_block', 'Create a reusable block (wp_block) from a block spec or raw block markup.', + ['title' => ['type' => 'string'], 'blocks' => ['type' => 'array', 'items' => ['type' => 'object']], 'markup' => ['type' => 'string']], + ['title'], 'write', 'createReusable')); + + $registry->register($def('list_reusable_blocks', 'List reusable blocks (wp_block posts).', + ['per_page' => ['type' => 'integer', 'default' => 100]], [], 'read', 'listReusable')); + + $registry->register($def('render_shortcode', 'Render a shortcode string to HTML via do_shortcode, optionally in the context of a post.', + ['shortcode' => ['type' => 'string'], 'post_id' => ['type' => 'integer']], ['shortcode'], 'read', 'renderShortcode')); + } + + private static function block(array $b): string { + $type = $b['type'] ?? 'paragraph'; + $content = (string) ($b['content'] ?? ''); + switch ($type) { + case 'heading': + $lvl = max(1, min(6, (int) ($b['level'] ?? 2))); + $attr = $lvl !== 2 ? ' {"level":' . $lvl . '}' : ''; + return "\n" . wp_kses_post($content) . "\n"; + case 'list': + $items = (array) ($b['items'] ?? []); + $lis = ''; + foreach ($items as $it) $lis .= '
  • ' . wp_kses_post((string) $it) . '
  • '; + if ($lis === '' && $content !== '') $lis = '
  • ' . wp_kses_post($content) . '
  • '; + return "\n\n"; + case 'image': + $url = esc_url((string) ($b['url'] ?? '')); + $alt = esc_attr((string) ($b['alt'] ?? '')); + $id = (int) ($b['id'] ?? 0); + $attr = $id ? ' {"id":' . $id . '}' : ''; + return "\n
    \"{$alt}\"/
    \n"; + case 'quote': + return "\n

    " . wp_kses_post($content) . "

    \n"; + case 'html': + return "\n" . $content . "\n"; + case 'paragraph': + default: + return "\n

    " . wp_kses_post($content) . "

    \n"; + } + } + + private static function buildMarkup(array $specs): string { + $out = []; + foreach ($specs as $b) if (is_array($b)) $out[] = self::block($b); + return implode("\n\n", $out); + } + + public static function listBlockTypes(array $args): array { + if (!class_exists('WP_Block_Type_Registry')) throw new \RuntimeException('Block registry unavailable.'); + $cat = isset($args['category']) ? (string) $args['category'] : ''; + $per = (int) ($args['per_page'] ?? 200); + $all = \WP_Block_Type_Registry::get_instance()->get_all_registered(); + $rows = []; + foreach ($all as $name => $bt) { + $c = (string) ($bt->category ?? ''); + if ($cat !== '' && $c !== $cat) continue; + $rows[] = ['name' => $name, 'title' => (string) ($bt->title ?? $name), 'category' => $c]; + } + return ['count' => count($rows), 'block_types' => array_slice($rows, 0, $per)]; + } + + public static function insertBlocks(array $args): array { + $id = (int) ($args['post_id'] ?? 0); + $post = $id ? get_post($id) : null; + if (!$post) throw new \RuntimeException("Post not found: {$id}"); + $specs = $args['blocks'] ?? []; + if (!is_array($specs) || !$specs) throw new \RuntimeException('blocks (array) is required.'); + $markup = self::buildMarkup($specs); + $mode = (($args['mode'] ?? 'append') === 'replace') ? 'replace' : 'append'; + $new = $mode === 'replace' ? $markup : rtrim((string) $post->post_content) . "\n\n" . $markup; + $res = wp_update_post(['ID' => $id, 'post_content' => $new], true); + if (is_wp_error($res)) throw new \RuntimeException($res->get_error_message()); + return ['post_id' => $id, 'blocks_added' => count($specs), 'mode' => $mode]; + } + + public static function createReusable(array $args): array { + $title = sanitize_text_field((string) ($args['title'] ?? 'Reusable block')); + $markup = !empty($args['markup']) ? (string) $args['markup'] : self::buildMarkup((array) ($args['blocks'] ?? [])); + if (trim($markup) === '') throw new \RuntimeException('Provide blocks or markup.'); + $res = wp_insert_post(['post_type' => 'wp_block', 'post_status' => 'publish', 'post_title' => $title, 'post_content' => $markup], true); + if (is_wp_error($res)) throw new \RuntimeException($res->get_error_message()); + return ['success' => true, 'block_id' => (int) $res, 'title' => $title]; + } + + public static function listReusable(array $args): array { + $per = (int) ($args['per_page'] ?? 100); + $posts = get_posts(['post_type' => 'wp_block', 'post_status' => 'any', 'posts_per_page' => $per]); + $rows = array_map(fn($p) => ['id' => $p->ID, 'title' => $p->post_title, 'status' => $p->post_status], $posts); + return ['count' => count($rows), 'reusable_blocks' => $rows]; + } + + public static function renderShortcode(array $args): array { + $sc = (string) ($args['shortcode'] ?? ''); + if (trim($sc) === '') throw new \RuntimeException('shortcode is required.'); + $pid = (int) ($args['post_id'] ?? 0); + if ($pid && ($p = get_post($pid))) { + global $post; + $post = $p; + setup_postdata($post); + } + $html = do_shortcode($sc); + wp_reset_postdata(); + return ['shortcode' => $sc, 'rendered' => $html, 'length' => strlen($html)]; + } +}