From 0e9f92c80893368a680b2e827b3eaba4f6899b35 Mon Sep 17 00:00:00 2001 From: colorlink Date: Fri, 16 Oct 2026 01:16:40 +0000 Subject: [PATCH] =?UTF-8?q?feat(batch7):=20WPBakery=20(js=5Fcomposer)=20to?= =?UTF-8?q?ols=20=E2=80=94=20detect,=20list=20elements,=20get=20content,?= =?UTF-8?q?=20add=20element,=20replace=20text=20(guarded,=20inert=20unless?= =?UTF-8?q?=20active)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- includes/Extensions/Batch7.php | 115 +++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 includes/Extensions/Batch7.php diff --git a/includes/Extensions/Batch7.php b/includes/Extensions/Batch7.php new file mode 100644 index 0000000..20eff96 --- /dev/null +++ b/includes/Extensions/Batch7.php @@ -0,0 +1,115 @@ + 'object', 'properties' => $props]; + if ($required) $schema['required'] = $required; + return ['name' => $name, 'description' => $desc, 'scope' => $scope, + 'handler' => 'AskTamMCP\\Extensions\\Batch7::' . $method, 'inputSchema' => $schema]; + }; + + $registry->register($def('wpbakery_detect', 'Report whether WPBakery (js_composer) is active and its version.', [], [], 'read', 'detect')); + + $registry->register($def('wpbakery_list_elements', 'List registered WPBakery elements (shortcode base, name, category) from the WPBMap registry.', + ['category' => ['type' => 'string'], 'per_page' => ['type' => 'integer', 'default' => 300]], [], 'read', 'listElements')); + + $registry->register($def('wpbakery_get_content', 'Get a post\'s WPBakery content: the raw shortcode markup plus the list of top-level element tags in order.', + ['post_id' => ['type' => 'integer']], ['post_id'], 'read', 'getContent')); + + $registry->register($def('wpbakery_add_element', 'Append a WPBakery element to a post. Provide raw shortcode in "shortcode", or build one from tag + attrs + content. mode=append (default) or replace.', + ['post_id' => ['type' => 'integer'], 'shortcode' => ['type' => 'string'], 'tag' => ['type' => 'string'], + 'attrs' => ['type' => 'object'], 'content' => ['type' => 'string'], + 'mode' => ['type' => 'string', 'enum' => ['append', 'replace'], 'default' => 'append']], + ['post_id'], 'write', 'addElement')); + + $registry->register($def('wpbakery_replace_text', 'Find and replace a literal string within a post\'s WPBakery content.', + ['post_id' => ['type' => 'integer'], 'search' => ['type' => 'string'], 'replace' => ['type' => 'string']], + ['post_id', 'search', 'replace'], 'write', 'replaceText')); + } + + public static function detect(array $args): array { + return ['active' => defined('WPB_VC_VERSION'), 'version' => defined('WPB_VC_VERSION') ? WPB_VC_VERSION : null]; + } + + public static function listElements(array $args): array { + if (!class_exists('WPBMap')) throw new \RuntimeException('WPBMap registry unavailable.'); + $cat = isset($args['category']) ? (string) $args['category'] : ''; + $per = (int) ($args['per_page'] ?? 300); + $all = \WPBMap::getAllShortCodes(); + $rows = []; + foreach ((array) $all as $base => $meta) { + $c = ''; + if (is_array($meta)) $c = is_array($meta['category'] ?? null) ? implode(',', $meta['category']) : (string) ($meta['category'] ?? ''); + if ($cat !== '' && stripos($c, $cat) === false) continue; + $rows[] = ['base' => $base, 'name' => is_array($meta) ? (string) ($meta['name'] ?? $base) : $base, 'category' => $c]; + } + return ['count' => count($rows), 'elements' => array_slice($rows, 0, $per)]; + } + + public static function getContent(array $args): array { + $id = (int) ($args['post_id'] ?? 0); + $post = $id ? get_post($id) : null; + if (!$post) throw new \RuntimeException("Post not found: {$id}"); + $raw = (string) $post->post_content; + preg_match_all('/\[(vc_[a-z0-9_]+)/i', $raw, $m); + return ['post_id' => $id, 'raw' => $raw, 'top_level_tags' => $m[1] ?? [], 'tag_count' => count($m[1] ?? [])]; + } + + private static function buildShortcode(array $args): string { + if (!empty($args['shortcode'])) return (string) $args['shortcode']; + $tag = sanitize_key((string) ($args['tag'] ?? '')); + if ($tag === '') throw new \RuntimeException('Provide "shortcode" or "tag".'); + $attrs = ''; + foreach ((array) ($args['attrs'] ?? []) as $k => $v) { + $attrs .= ' ' . sanitize_key((string) $k) . '="' . esc_attr((string) $v) . '"'; + } + $content = (string) ($args['content'] ?? ''); + return "[{$tag}{$attrs}]" . $content . "[/{$tag}]"; + } + + public static function addElement(array $args): array { + $id = (int) ($args['post_id'] ?? 0); + $post = $id ? get_post($id) : null; + if (!$post) throw new \RuntimeException("Post not found: {$id}"); + $sc = self::buildShortcode($args); + $mode = (($args['mode'] ?? 'append') === 'replace') ? 'replace' : 'append'; + $new = $mode === 'replace' ? $sc : rtrim((string) $post->post_content) . "\n" . $sc; + $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, 'mode' => $mode, 'added' => $sc]; + } + + public static function replaceText(array $args): array { + $id = (int) ($args['post_id'] ?? 0); + $post = $id ? get_post($id) : null; + if (!$post) throw new \RuntimeException("Post not found: {$id}"); + $search = (string) ($args['search'] ?? ''); + if ($search === '') throw new \RuntimeException('search is required.'); + $replace = (string) ($args['replace'] ?? ''); + $content = (string) $post->post_content; + $count = 0; + $new = str_replace($search, $replace, $content, $count); + if ($count === 0) return ['post_id' => $id, 'replacements' => 0, 'note' => 'Search string not found.']; + $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, 'replacements' => $count]; + } +}