From 3dfdaffdd76d47fe5f58f18d1e7133c66436f21b Mon Sep 17 00:00:00 2001 From: colorlink Date: Fri, 16 Oct 2026 01:21:02 +0000 Subject: [PATCH] =?UTF-8?q?feat(batch9):=20backup/export=20=E2=80=94=20exp?= =?UTF-8?q?ort/import=20Elementor=20page,=20global=20styles,=20duplicate?= =?UTF-8?q?=20page=20(Elementor-guarded)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- includes/Extensions/Batch9.php | 126 +++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 includes/Extensions/Batch9.php diff --git a/includes/Extensions/Batch9.php b/includes/Extensions/Batch9.php new file mode 100644 index 0000000..b4542fb --- /dev/null +++ b/includes/Extensions/Batch9.php @@ -0,0 +1,126 @@ + 'object', 'properties' => $props]; + if ($required) $schema['required'] = $required; + return ['name' => $name, 'description' => $desc, 'scope' => $scope, + 'handler' => 'AskTamMCP\\Extensions\\Batch9::' . $method, 'inputSchema' => $schema]; + }; + + $registry->register($def('export_elementor_page', 'Export a page\'s Elementor data: parsed elements, page settings, and template. Use as a backup or to clone into another page.', + ['post_id' => ['type' => 'integer']], ['post_id'], 'read', 'exportPage')); + + $registry->register($def('import_elementor_page', 'Import Elementor data into a target post. Provide elementor_data (JSON string or array). regenerate_ids=true gives every element a fresh id.', + ['post_id' => ['type' => 'integer'], 'elementor_data' => ['type' => ['array', 'string']], 'regenerate_ids' => ['type' => 'boolean', 'default' => false]], + ['post_id', 'elementor_data'], 'write', 'importPage')); + + $registry->register($def('export_global_styles', 'Export the active Elementor kit\'s global styles (colors, typography, and other page settings).', [], [], 'read', 'exportStyles')); + + $registry->register($def('import_global_styles', 'Write global styles into the active Elementor kit. merge=true (default) merges into existing settings; false replaces them.', + ['settings' => ['type' => 'object'], 'merge' => ['type' => 'boolean', 'default' => true]], + ['settings'], 'write', 'importStyles')); + + $registry->register($def('duplicate_elementor_page', 'Duplicate a page including its Elementor data into a new post (default draft).', + ['source_id' => ['type' => 'integer'], 'title' => ['type' => 'string'], 'status' => ['type' => 'string', 'default' => 'draft']], + ['source_id'], 'write', 'duplicatePage')); + } + + private static function regenerateIds(array $elements): array { + foreach ($elements as &$el) { + if (isset($el['id'])) $el['id'] = substr(md5(uniqid('', true)), 0, 7); + if (!empty($el['elements']) && is_array($el['elements'])) $el['elements'] = self::regenerateIds($el['elements']); + } + unset($el); + return $elements; + } + + public static function exportPage(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 = get_post_meta($id, '_elementor_data', true); + $data = is_string($raw) && $raw !== '' ? json_decode($raw, true) : []; + return [ + 'post_id' => $id, + 'title' => $post->post_title, + 'template' => get_post_meta($id, '_wp_page_template', true), + 'page_settings' => get_post_meta($id, '_elementor_page_settings', true) ?: [], + 'elementor_data' => is_array($data) ? $data : [], + 'element_count' => is_array($data) ? count($data) : 0, + ]; + } + + public static function importPage(array $args): array { + $id = (int) ($args['post_id'] ?? 0); + $post = $id ? get_post($id) : null; + if (!$post) throw new \RuntimeException("Post not found: {$id}"); + $data = $args['elementor_data'] ?? null; + if (is_string($data)) $data = json_decode($data, true); + if (!is_array($data)) throw new \RuntimeException('elementor_data must be a JSON array/string of elements.'); + if (!empty($args['regenerate_ids'])) $data = self::regenerateIds($data); + $json = wp_json_encode($data); + update_post_meta($id, '_elementor_data', wp_slash($json)); + update_post_meta($id, '_elementor_edit_mode', 'builder'); + if (defined('ELEMENTOR_VERSION')) update_post_meta($id, '_elementor_version', ELEMENTOR_VERSION); + if (class_exists('\Elementor\Plugin')) { + try { \Elementor\Plugin::$instance->files_manager->clear_cache(); } catch (\Throwable $e) {} + } + return ['post_id' => $id, 'elements_imported' => count($data), 'regenerated_ids' => !empty($args['regenerate_ids'])]; + } + + private static function activeKit(): int { + return (int) get_option('elementor_active_kit', 0); + } + + public static function exportStyles(array $args): array { + $kit = self::activeKit(); + if (!$kit) throw new \RuntimeException('No active Elementor kit.'); + return ['kit_id' => $kit, 'settings' => get_post_meta($kit, '_elementor_page_settings', true) ?: []]; + } + + public static function importStyles(array $args): array { + $kit = self::activeKit(); + if (!$kit) throw new \RuntimeException('No active Elementor kit.'); + $incoming = is_array($args['settings'] ?? null) ? $args['settings'] : []; + if (!$incoming) throw new \RuntimeException('settings (object) is required.'); + $merge = !isset($args['merge']) || !empty($args['merge']); + $current = get_post_meta($kit, '_elementor_page_settings', true) ?: []; + $new = $merge ? array_merge((array) $current, $incoming) : $incoming; + update_post_meta($kit, '_elementor_page_settings', $new); + return ['kit_id' => $kit, 'merged' => $merge, 'keys_written' => array_keys($incoming)]; + } + + public static function duplicatePage(array $args): array { + $src = (int) ($args['source_id'] ?? 0); + $post = $src ? get_post($src) : null; + if (!$post) throw new \RuntimeException("Source post not found: {$src}"); + $title = sanitize_text_field((string) ($args['title'] ?? ($post->post_title . ' (copy)'))); + $status = in_array(($args['status'] ?? 'draft'), ['draft', 'publish', 'pending', 'private'], true) ? $args['status'] : 'draft'; + $new = wp_insert_post([ + 'post_title' => $title, 'post_status' => $status, 'post_type' => $post->post_type, + 'post_content' => $post->post_content, + ], true); + if (is_wp_error($new)) throw new \RuntimeException($new->get_error_message()); + foreach (['_elementor_data', '_elementor_page_settings', '_wp_page_template', '_elementor_edit_mode', '_elementor_version'] as $k) { + $v = get_post_meta($src, $k, true); + if ($v !== '') update_post_meta($new, $k, is_string($v) ? wp_slash($v) : $v); + } + return ['new_post_id' => (int) $new, 'source_id' => $src, 'title' => $title, 'status' => $status]; + } +}