140 lines
7.6 KiB
PHP
140 lines
7.6 KiB
PHP
<?php
|
|
/**
|
|
* Batch 6 — Gutenberg block / reusable-pattern generator (Pro-equivalent, net-new).
|
|
*
|
|
* Independent implementation of block-generation capabilities: list block types,
|
|
* insert core blocks into a post from a simple spec, create & list reusable blocks
|
|
* (wp_block), and render a shortcode. GPL-2.0-or-later. See NOTICE.md.
|
|
*
|
|
* Registered via the asktam_mcp_register_tools hook (glob auto-loader).
|
|
*/
|
|
|
|
namespace AskTamMCP\Extensions;
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class Batch6 {
|
|
|
|
public static function register($registry): void {
|
|
$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\\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 "<!-- wp:heading{$attr} -->\n<h{$lvl}>" . wp_kses_post($content) . "</h{$lvl}>\n<!-- /wp:heading -->";
|
|
case 'list':
|
|
$items = (array) ($b['items'] ?? []);
|
|
$lis = '';
|
|
foreach ($items as $it) $lis .= '<li>' . wp_kses_post((string) $it) . '</li>';
|
|
if ($lis === '' && $content !== '') $lis = '<li>' . wp_kses_post($content) . '</li>';
|
|
return "<!-- wp:list -->\n<ul class=\"wp-block-list\">{$lis}</ul>\n<!-- /wp:list -->";
|
|
case 'image':
|
|
$url = esc_url((string) ($b['url'] ?? ''));
|
|
$alt = esc_attr((string) ($b['alt'] ?? ''));
|
|
$id = (int) ($b['id'] ?? 0);
|
|
$attr = $id ? ' {"id":' . $id . '}' : '';
|
|
return "<!-- wp:image{$attr} -->\n<figure class=\"wp-block-image\"><img src=\"{$url}\" alt=\"{$alt}\"/></figure>\n<!-- /wp:image -->";
|
|
case 'quote':
|
|
return "<!-- wp:quote -->\n<blockquote class=\"wp-block-quote\"><p>" . wp_kses_post($content) . "</p></blockquote>\n<!-- /wp:quote -->";
|
|
case 'html':
|
|
return "<!-- wp:html -->\n" . $content . "\n<!-- /wp:html -->";
|
|
case 'paragraph':
|
|
default:
|
|
return "<!-- wp:paragraph -->\n<p>" . wp_kses_post($content) . "</p>\n<!-- /wp:paragraph -->";
|
|
}
|
|
}
|
|
|
|
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)];
|
|
}
|
|
}
|