feat(batch9): backup/export — export/import Elementor page, global styles, duplicate page (Elementor-guarded)
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* Batch 9 — Backup / export / portability (Pro-equivalent, net-new).
|
||||
*
|
||||
* Export & import Elementor page data and global styles, and duplicate pages.
|
||||
* Elementor-only (inert without it). 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 Batch9 {
|
||||
|
||||
public static function register($registry): void {
|
||||
if (!defined('ELEMENTOR_VERSION')) return; // Elementor-only batch.
|
||||
|
||||
$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\\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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user