fix: update ElementorTools to use v1.9.0 by-ref methods, normalize_container_settings, insertAfter for duplicate, correct addContainer defaults, wp_slash save path
This commit is contained in:
@@ -6,11 +6,11 @@ defined('ABSPATH') || exit;
|
||||
* All Elementor MCP tools — pages, layouts, widgets, templates, globals.
|
||||
* No Freemius, no pro gates. All tools always available when Elementor is active.
|
||||
*
|
||||
* Tool counts:
|
||||
* - Original 32 layout/widget/template/kit tools
|
||||
* - +10 CSS/replace/custom-CSS/experiments/page-template tools
|
||||
* - +10 revisions/performance/CPT/maintenance/site-settings tools
|
||||
* Total: 52 Elementor tools / 200 plugin-wide
|
||||
* Updated to match methods in Elementor_MCP_Data v1.9.0:
|
||||
* - remove_element / insert_element / update_element_settings are by-reference (return bool)
|
||||
* - updateElement / removeElement / moveElement / duplicateElement updated accordingly
|
||||
* - normalize_container_settings called on container updates (via data layer)
|
||||
* - save_page_data fallback uses wp_slash() + CSS cache invalidation
|
||||
*/
|
||||
class ElementorTools {
|
||||
|
||||
@@ -22,18 +22,17 @@ class ElementorTools {
|
||||
|
||||
public function getToolDefinitions(): array {
|
||||
return [
|
||||
// ── Layout / Widget / Template / Kit (original 32) ─────────────
|
||||
$this->def('elementor_create_page','Create a new WordPress page with Elementor enabled. Returns post_id, edit_url, preview_url.',['title'=>['type'=>'string'],'status'=>['type'=>'string','default'=>'draft','enum'=>['draft','publish']],'post_type'=>['type'=>'string','default'=>'page'],'template'=>['type'=>'string'],'content'=>['type'=>'array','items'=>['type'=>'object']]],['title'],'write','createPage'),
|
||||
$this->def('elementor_get_page','Get the full Elementor element tree JSON for a page.',['post_id'=>['type'=>'integer']],['post_id'],'read','getPage'),
|
||||
$this->def('elementor_update_page_settings','Update page-level Elementor settings (background, padding, custom CSS, layout).',['post_id'=>['type'=>'integer'],'settings'=>['type'=>'object']],['post_id','settings'],'write','updatePageSettings'),
|
||||
$this->def('elementor_delete_page_content','Clear all Elementor content from a page, resetting it to blank.',['post_id'=>['type'=>'integer']],['post_id'],'write','deletePageContent'),
|
||||
$this->def('elementor_import_template','Import a JSON element structure into a page at an optional position.',['post_id'=>['type'=>'integer'],'template_json'=>['type'=>'array','items'=>['type'=>'object']],'position'=>['type'=>'integer']],['post_id','template_json'],'write','importTemplate'),
|
||||
$this->def('elementor_export_page','Export a page\'s full Elementor data as JSON.',['post_id'=>['type'=>'integer']],['post_id'],'read','exportPage'),
|
||||
$this->def('elementor_add_container','Add a flex or grid container to an Elementor page. Use parent_id for nesting, position for insertion index (-1 = append).',['post_id'=>['type'=>'integer'],'parent_id'=>['type'=>'string'],'position'=>['type'=>'integer'],'settings'=>['type'=>'object']],['post_id'],'write','addContainer'),
|
||||
$this->def('elementor_update_container','Update settings on an existing container element.',['post_id'=>['type'=>'integer'],'element_id'=>['type'=>'string'],'settings'=>['type'=>'object']],['post_id','element_id'],'write','updateContainer'),
|
||||
$this->def('elementor_update_element','Update settings on any Elementor element by ID.',['post_id'=>['type'=>'integer'],'element_id'=>['type'=>'string'],'settings'=>['type'=>'object'],'merge'=>['type'=>'boolean','default'=>true]],['post_id','element_id','settings'],'write','updateElement'),
|
||||
$this->def('elementor_add_container','Add a flex or grid container to an Elementor page. Use parent_id for nesting, position for insertion index (-1 = append). Settings: flex_direction, flex_wrap, flex_justify_content (or justify_content), flex_align_items (or align_items), gap, padding, background_color, border_border, etc. Shorthand justify_content/align_items/align_content are auto-remapped to flex_justify_content/flex_align_items/flex_align_content.',['post_id'=>['type'=>'integer'],'parent_id'=>['type'=>'string'],'position'=>['type'=>'integer'],'settings'=>['type'=>'object']],['post_id'],'write','addContainer'),
|
||||
$this->def('elementor_update_container','Update settings on an existing container element. Accepts flex shorthand justify_content/align_items/align_content and remaps to prefixed keys automatically.',['post_id'=>['type'=>'integer'],'element_id'=>['type'=>'string'],'settings'=>['type'=>'object']],['post_id','element_id'],'write','updateContainer'),
|
||||
$this->def('elementor_update_element','Update settings on any Elementor element by ID. For containers, flex shorthand keys are auto-normalised.',['post_id'=>['type'=>'integer'],'element_id'=>['type'=>'string'],'settings'=>['type'=>'object'],'merge'=>['type'=>'boolean','default'=>true]],['post_id','element_id','settings'],'write','updateElement'),
|
||||
$this->def('elementor_remove_element','Remove any element (container or widget) from a page by element ID.',['post_id'=>['type'=>'integer'],'element_id'=>['type'=>'string']],['post_id','element_id'],'write','removeElement'),
|
||||
$this->def('elementor_duplicate_element','Duplicate an element (and its children) within the same page.',['post_id'=>['type'=>'integer'],'element_id'=>['type'=>'string'],'position'=>['type'=>'integer']],['post_id','element_id'],'write','duplicateElement'),
|
||||
$this->def('elementor_duplicate_element','Duplicate an element (and its children) within the same page. Clone is placed immediately after the original.',['post_id'=>['type'=>'integer'],'element_id'=>['type'=>'string'],'position'=>['type'=>'integer']],['post_id','element_id'],'write','duplicateElement'),
|
||||
$this->def('elementor_move_element','Move an element to a different parent or position within the page.',['post_id'=>['type'=>'integer'],'element_id'=>['type'=>'string'],'target_parent_id'=>['type'=>'string'],'position'=>['type'=>'integer']],['post_id','element_id'],'write','moveElement'),
|
||||
$this->def('elementor_add_widget','Add any Elementor widget to a container. widget_type: heading, text-editor, image, button, video, icon, divider, spacer, google_maps, html, shortcode, accordion, tabs, toggle, icon-list, counter, progress, testimonial, alert, social-icons, star-rating, image-box, icon-box, basic-gallery, image-carousel, reviews, posts, nav-menu, search-form, woocommerce-product-images, etc.',['post_id'=>['type'=>'integer'],'container_id'=>['type'=>'string'],'widget_type'=>['type'=>'string'],'settings'=>['type'=>'object'],'position'=>['type'=>'integer']],['post_id','container_id','widget_type'],'write','addWidget'),
|
||||
$this->def('elementor_update_widget','Update an existing widget\'s settings.',['post_id'=>['type'=>'integer'],'element_id'=>['type'=>'string'],'settings'=>['type'=>'object'],'merge'=>['type'=>'boolean','default'=>true]],['post_id','element_id','settings'],'write','updateWidget'),
|
||||
@@ -55,11 +54,9 @@ class ElementorTools {
|
||||
$this->def('elementor_replace_text','Replace all occurrences of a text string across all widgets in a page.',['post_id'=>['type'=>'integer'],'search'=>['type'=>'string'],'replace'=>['type'=>'string'],'case_sensitive'=>['type'=>'boolean','default'=>false]],['post_id','search','replace'],'write','replaceText'),
|
||||
$this->def('elementor_get_page_outline','Get a simplified structural outline of an Elementor page (containers + widget types).',['post_id'=>['type'=>'integer']],['post_id'],'read','getPageOutline'),
|
||||
$this->def('elementor_delete_items','Delete one or more elements (containers or widgets) from a page by element_id. Use element_ids array.',['post_id'=>['type'=>'integer'],'element_ids'=>['type'=>'array','items'=>['type'=>'string']],'element_id'=>['type'=>'string']],['post_id'],'write','deleteItems'),
|
||||
|
||||
// ── CSS / Regeneration / Migration (+10) ───────────────────────
|
||||
$this->def('elementor_flush_css','Flush and regenerate all Elementor CSS files across the entire site. Call after bulk design changes.',[],[],'write','flushCss'),
|
||||
$this->def('elementor_regenerate_css','Regenerate the Elementor CSS file for a single post/page.',['post_id'=>['type'=>'integer']],['post_id'],'write','regenerateCss'),
|
||||
$this->def('elementor_replace_urls','Find and replace a URL string across ALL Elementor data in the database. Essential for domain migrations.',['old_url'=>['type'=>'string'],'new_url'=>['type'=>'string']],['old_url','new_url'],'admin','replaceUrls'),
|
||||
$this->def('elementor_replace_urls','Find and replace a URL string across ALL Elementor data in the database. Essential for domain migrations and staging to production pushes.',['old_url'=>['type'=>'string'],'new_url'=>['type'=>'string']],['old_url','new_url'],'admin','replaceUrls'),
|
||||
$this->def('elementor_get_kit_custom_css','Get the global custom CSS stored in the Elementor Site Kit.',[],[],'read','getKitCustomCss'),
|
||||
$this->def('elementor_update_kit_custom_css','Set the global custom CSS in the Elementor Site Kit. Replaces existing kit-level CSS.',['css'=>['type'=>'string']],['css'],'write','updateKitCustomCss'),
|
||||
$this->def('elementor_get_page_custom_css','Get the custom CSS set for a specific Elementor page (page-level, not kit-level).',['post_id'=>['type'=>'integer']],['post_id'],'read','getPageCustomCss'),
|
||||
@@ -67,18 +64,16 @@ class ElementorTools {
|
||||
$this->def('elementor_list_experiments','List all Elementor feature flags (experiments) with their current state.',[],[],'read','listExperiments'),
|
||||
$this->def('elementor_set_experiment','Activate or deactivate an Elementor feature flag by name.',['name'=>['type'=>'string'],'state'=>['type'=>'string','enum'=>['active','inactive','default']]],['name','state'],'admin','setExperiment'),
|
||||
$this->def('elementor_set_page_template','Set the WordPress page template for a post (e.g. elementor_canvas, elementor-full-width, default).',['post_id'=>['type'=>'integer'],'template'=>['type'=>'string']],['post_id','template'],'write','setPageTemplate'),
|
||||
|
||||
// ── Revisions / Performance / CPT / Maintenance / Site (+10) ───
|
||||
$this->def('elementor_get_revisions','List all saved revisions for an Elementor post. Returns id, type, author, timestamp.',['post_id'=>['type'=>'integer']],['post_id'],'read','getRevisions'),
|
||||
$this->def('elementor_restore_revision','Restore an Elementor post to a specific revision by revision ID.',['post_id'=>['type'=>'integer'],'revision_id'=>['type'=>'integer']],['post_id','revision_id'],'write','restoreRevision'),
|
||||
$this->def('elementor_get_performance_settings','Get all Elementor performance settings (CSS print method, font display, lazy load, FA4 shim, caching, etc.).',[],[],'read','getPerformanceSettings'),
|
||||
$this->def('elementor_update_performance_settings','Update one or more Elementor performance settings. Fields: css_print_method (external|internal), google_font (1|), load_fa4_shim (1|), lazy_load_background_images (1|), meta_generator_tag (1|), unfiltered_files_upload (1|), font_display (auto|block|swap|fallback|optional), google_maps_api_key (string), element_cache_ttl (int hours), optimized_gutenberg_loading (1|).',['settings'=>['type'=>'object']],['settings'],'admin','updatePerformanceSettings'),
|
||||
$this->def('elementor_update_performance_settings','Update one or more Elementor performance settings.',['settings'=>['type'=>'object']],['settings'],'admin','updatePerformanceSettings'),
|
||||
$this->def('elementor_get_cpt_support','Get the list of post types where Elementor editor is enabled.',[],[],'read','getCptSupport'),
|
||||
$this->def('elementor_set_cpt_support','Set which post types Elementor editor is enabled for. Pass post_types as array of slugs.',['post_types'=>['type'=>'array','items'=>['type'=>'string']]],['post_types'],'admin','setCptSupport'),
|
||||
$this->def('elementor_get_maintenance_mode','Get current Elementor maintenance/coming-soon mode state.',[],[],'read','getMaintenanceMode'),
|
||||
$this->def('elementor_set_maintenance_mode','Enable or disable Elementor maintenance or coming-soon mode.',['mode'=>['type'=>'string','enum'=>['maintenance','coming_soon','']],'page_id'=>['type'=>'integer'],'exclude_mode'=>['type'=>'string','enum'=>['logged_in','exclude_admins','']]],['mode'],'admin','setMaintenanceMode'),
|
||||
$this->def('elementor_get_site_settings','Get Elementor site-level settings: Google Maps API key, font settings, CSS method, and other global options.',[],[],'read','getSiteSettings'),
|
||||
$this->def('elementor_update_site_settings','Update Elementor site-level settings. Fields: google_maps_api_key, meta_generator_tag, google_font, font_display, load_fa4_shim, unfiltered_files_upload, css_print_method.',['settings'=>['type'=>'object']],['settings'],'admin','updateSiteSettings'),
|
||||
$this->def('elementor_get_site_settings','Get Elementor site-level settings: Google Maps API key, meta generator tag, font settings, and other global options.',[],[],'read','getSiteSettings'),
|
||||
$this->def('elementor_update_site_settings','Update Elementor site-level settings.',['settings'=>['type'=>'object']],['settings'],'admin','updateSiteSettings'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -88,13 +83,9 @@ class ElementorTools {
|
||||
return ['name'=>$name,'description'=>$desc,'scope'=>$scope,'handler'=>[$this,$method],'inputSchema'=>$schema];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// LAYOUT / WIDGET / TEMPLATE / KIT METHODS
|
||||
// =========================================================
|
||||
|
||||
public function createPage(array $args): array {
|
||||
$title = sanitize_text_field($args['title'] ?? '');
|
||||
$status = sanitize_key($args['status'] ?? 'draft');
|
||||
$title = sanitize_text_field($args['title'] ?? '');
|
||||
$status = sanitize_key($args['status'] ?? 'draft');
|
||||
$post_type = sanitize_key($args['post_type'] ?? 'page');
|
||||
if (empty($title)) throw new \RuntimeException('title is required.');
|
||||
$post_id = wp_insert_post(['post_title'=>$title,'post_status'=>$status,'post_type'=>$post_type,'meta_input'=>['_elementor_edit_mode'=>'builder','_elementor_template_type'=>'wp-'.$post_type]], true);
|
||||
@@ -114,22 +105,22 @@ class ElementorTools {
|
||||
|
||||
public function updatePageSettings(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$result = $this->data->save_page_settings($post_id, $args['settings'] ?? []);
|
||||
$result = $this->data->save_page_settings($post_id, $args['settings'] ?? []);
|
||||
if (is_wp_error($result)) throw new \RuntimeException($result->get_error_message());
|
||||
return ['success'=>true,'post_id'=>$post_id];
|
||||
}
|
||||
|
||||
public function deletePageContent(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$result = $this->data->save_page_data($post_id, []);
|
||||
$result = $this->data->save_page_data($post_id, []);
|
||||
if (is_wp_error($result)) throw new \RuntimeException($result->get_error_message());
|
||||
return ['success'=>true];
|
||||
}
|
||||
|
||||
public function importTemplate(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$template_json = $args['template_json'] ?? [];
|
||||
$position = (int)($args['position'] ?? -1);
|
||||
$position = (int)($args['position'] ?? -1);
|
||||
if (empty($template_json)) throw new \RuntimeException('template_json is required.');
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
if (is_wp_error($data)) throw new \RuntimeException($data->get_error_message());
|
||||
@@ -144,29 +135,42 @@ class ElementorTools {
|
||||
|
||||
public function exportPage(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
if (is_wp_error($data)) throw new \RuntimeException($data->get_error_message());
|
||||
return ['json'=>$data,'count'=>$this->data->count_elements($data)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a container. Uses insert_element() (by-reference) from the data layer.
|
||||
* Flex shorthand keys normalised before building the element.
|
||||
* Auto-sets flex_align_items=center for column containers (mirrors v1.9.0 factory defaults).
|
||||
*/
|
||||
public function addContainer(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$parent_id = $args['parent_id'] ?? null;
|
||||
$position = (int)($args['position'] ?? -1);
|
||||
$settings = $args['settings'] ?? [];
|
||||
foreach (['justify_content'=>'flex_justify_content','align_items'=>'flex_align_items','align_content'=>'flex_align_content'] as $from=>$to) {
|
||||
if (isset($settings[$from]) && !isset($settings[$to])) { $settings[$to] = $settings[$from]; unset($settings[$from]); }
|
||||
}
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$parent_id = $args['parent_id'] ?? '';
|
||||
$position = (int)($args['position'] ?? -1);
|
||||
$settings = $args['settings'] ?? [];
|
||||
|
||||
$settings = \AskTamMCP\Elementor\Data\ElementorData::normalize_container_settings($settings);
|
||||
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
if (is_wp_error($data)) throw new \RuntimeException($data->get_error_message());
|
||||
|
||||
$element_id = $this->data->generate_id();
|
||||
$container = ['id'=>$element_id,'elType'=>'container','settings'=>array_merge(['flex_direction'=>'row'],$settings),'elements'=>[],'isInner'=>false];
|
||||
if ($parent_id) {
|
||||
$data = $this->insertIntoParent($data, $parent_id, $container, $position);
|
||||
} else {
|
||||
if ($position < 0 || $position >= count($data)) $data[] = $container;
|
||||
else array_splice($data, $position, 0, [$container]);
|
||||
$defaults = ['container_type'=>'flex','content_width'=>'boxed'];
|
||||
|
||||
$direction = $settings['flex_direction'] ?? '';
|
||||
$is_row = in_array($direction, ['row','row-reverse'], true);
|
||||
$is_grid = ($settings['container_type'] ?? 'flex') === 'grid';
|
||||
if (!$is_grid && !$is_row && !isset($settings['flex_align_items'])) {
|
||||
$defaults['flex_align_items'] = 'center';
|
||||
}
|
||||
|
||||
$container = ['id'=>$element_id,'elType'=>'container','widgetType'=>null,'isInner'=>!empty($parent_id),'settings'=>array_merge($defaults,$settings),'elements'=>[]];
|
||||
|
||||
$inserted = $this->data->insert_element($data, $parent_id, $container, $position);
|
||||
if (!$inserted) throw new \RuntimeException("Parent element '{$parent_id}' not found.");
|
||||
|
||||
$result = $this->data->save_page_data($post_id, $data);
|
||||
if (is_wp_error($result)) throw new \RuntimeException($result->get_error_message());
|
||||
return ['element_id'=>$element_id,'post_id'=>$post_id];
|
||||
@@ -174,79 +178,114 @@ class ElementorTools {
|
||||
|
||||
public function updateContainer(array $args): array { return $this->updateElement($args); }
|
||||
|
||||
/**
|
||||
* Update any element. For containers the data layer auto-normalises flex shorthand keys.
|
||||
* update_element_settings() is by-reference and returns bool.
|
||||
*/
|
||||
public function updateElement(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$element_id = $args['element_id'] ?? '';
|
||||
$settings = $args['settings'] ?? [];
|
||||
$merge = (bool)($args['merge'] ?? true);
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
$settings = $args['settings'] ?? [];
|
||||
$merge = (bool)($args['merge'] ?? true);
|
||||
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
if (is_wp_error($data)) throw new \RuntimeException($data->get_error_message());
|
||||
$data = $this->data->update_element_settings($data, $element_id, $settings, $merge);
|
||||
|
||||
$updated = $this->data->update_element_settings($data, $element_id, $settings, $merge);
|
||||
if (!$updated) throw new \RuntimeException("Element not found: {$element_id}");
|
||||
|
||||
$result = $this->data->save_page_data($post_id, $data);
|
||||
if (is_wp_error($result)) throw new \RuntimeException($result->get_error_message());
|
||||
return ['success'=>true,'post_id'=>$post_id,'element_id'=>$element_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element. remove_element() is by-reference and returns bool.
|
||||
*/
|
||||
public function removeElement(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$element_id = $args['element_id'] ?? '';
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
if (is_wp_error($data)) throw new \RuntimeException($data->get_error_message());
|
||||
$data = $this->data->remove_element($data, $element_id);
|
||||
|
||||
$removed = $this->data->remove_element($data, $element_id);
|
||||
if (!$removed) throw new \RuntimeException("Element not found: {$element_id}");
|
||||
|
||||
$result = $this->data->save_page_data($post_id, $data);
|
||||
if (is_wp_error($result)) throw new \RuntimeException($result->get_error_message());
|
||||
return ['success'=>true,'removed_id'=>$element_id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate element — clone placed immediately after original (mirrors v1.9.0 insert_after).
|
||||
*/
|
||||
public function duplicateElement(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$element_id = $args['element_id'] ?? '';
|
||||
$position = (int)($args['position'] ?? -1);
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
if (is_wp_error($data)) throw new \RuntimeException($data->get_error_message());
|
||||
|
||||
$element = $this->data->find_element($data, $element_id);
|
||||
if (!$element) throw new \RuntimeException("Element not found: {$element_id}");
|
||||
$clone = $this->data->reassign_ids([$element])[0];
|
||||
if ($position < 0 || $position >= count($data)) $data[] = $clone;
|
||||
else array_splice($data, $position, 0, [$clone]);
|
||||
|
||||
$clone = $this->data->reassign_element_ids($element);
|
||||
$inserted = $this->insertAfter($data, $element_id, $clone);
|
||||
if (!$inserted) throw new \RuntimeException("Failed to insert duplicate after {$element_id}.");
|
||||
|
||||
$result = $this->data->save_page_data($post_id, $data);
|
||||
if (is_wp_error($result)) throw new \RuntimeException($result->get_error_message());
|
||||
return ['success'=>true,'new_element_id'=>$clone['id']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Move element — by-reference remove then insert.
|
||||
*/
|
||||
public function moveElement(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$element_id = $args['element_id'] ?? '';
|
||||
$target_parent_id = $args['target_parent_id'] ?? null;
|
||||
$position = (int)($args['position'] ?? -1);
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$element_id = $args['element_id'] ?? '';
|
||||
$target_parent_id = $args['target_parent_id'] ?? '';
|
||||
$position = (int)($args['position'] ?? -1);
|
||||
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
if (is_wp_error($data)) throw new \RuntimeException($data->get_error_message());
|
||||
|
||||
$element = $this->data->find_element($data, $element_id);
|
||||
if (!$element) throw new \RuntimeException("Element not found: {$element_id}");
|
||||
$data = $this->data->remove_element($data, $element_id);
|
||||
if ($target_parent_id) {
|
||||
$data = $this->insertIntoParent($data, $target_parent_id, $element, $position);
|
||||
} else {
|
||||
if ($position < 0 || $position >= count($data)) $data[] = $element;
|
||||
else array_splice($data, $position, 0, [$element]);
|
||||
}
|
||||
|
||||
$removed = $this->data->remove_element($data, $element_id);
|
||||
if (!$removed) throw new \RuntimeException("Failed to remove element: {$element_id}");
|
||||
|
||||
$inserted = $this->data->insert_element($data, $target_parent_id, $element, $position);
|
||||
if (!$inserted) throw new \RuntimeException("Failed to insert element at target '{$target_parent_id}'.");
|
||||
|
||||
$result = $this->data->save_page_data($post_id, $data);
|
||||
if (is_wp_error($result)) throw new \RuntimeException($result->get_error_message());
|
||||
return ['success'=>true,'element_id'=>$element_id];
|
||||
}
|
||||
|
||||
public function addWidget(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$container_id = $args['container_id'] ?? '';
|
||||
$widget_type = $args['widget_type'] ?? '';
|
||||
$settings = $args['settings'] ?? [];
|
||||
$position = (int)($args['position'] ?? -1);
|
||||
$widget_type = $args['widget_type'] ?? '';
|
||||
$settings = $args['settings'] ?? [];
|
||||
$position = (int)($args['position'] ?? -1);
|
||||
if (empty($widget_type)) throw new \RuntimeException('widget_type is required.');
|
||||
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
if (is_wp_error($data)) throw new \RuntimeException($data->get_error_message());
|
||||
|
||||
$element_id = $this->data->generate_id();
|
||||
$widget = ['id'=>$element_id,'elType'=>'widget','widgetType'=>$widget_type,'settings'=>$settings,'elements'=>[]];
|
||||
$data = $this->insertIntoParent($data, $container_id, $widget, $position);
|
||||
$widget = ['id'=>$element_id,'elType'=>'widget','widgetType'=>$widget_type,'isInner'=>false,'settings'=>$settings,'elements'=>[]];
|
||||
|
||||
$inserted = $this->data->insert_element($data, $container_id, $widget, $position);
|
||||
if (!$inserted && !empty($container_id)) {
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
if (is_wp_error($data)) throw new \RuntimeException($data->get_error_message());
|
||||
$data = $this->insertIntoParent($data, $container_id, $widget, $position);
|
||||
}
|
||||
|
||||
$result = $this->data->save_page_data($post_id, $data);
|
||||
if (is_wp_error($result)) throw new \RuntimeException($result->get_error_message());
|
||||
return ['element_id'=>$element_id,'post_id'=>$post_id,'widget_type'=>$widget_type];
|
||||
@@ -257,9 +296,9 @@ class ElementorTools {
|
||||
public function addHeading(array $args): array {
|
||||
$text = $args['title'] ?? $args['text'] ?? '';
|
||||
$settings = ['title'=>$text];
|
||||
if (!empty($args['tag'])) $settings['header_size'] = $args['tag'];
|
||||
if (!empty($args['align'])) $settings['align'] = $args['align'];
|
||||
if (!empty($args['color'])) $settings['title_color'] = $args['color'];
|
||||
if (!empty($args['tag'])) $settings['header_size'] = $args['tag'];
|
||||
if (!empty($args['align'])) $settings['align'] = $args['align'];
|
||||
if (!empty($args['color'])) $settings['title_color'] = $args['color'];
|
||||
if (!empty($args['size'])) $settings['typography_font_size'] = ['size'=>(int)$args['size'],'unit'=>'px'];
|
||||
return $this->addWidget(array_merge($args,['widget_type'=>'heading','settings'=>$settings]));
|
||||
}
|
||||
@@ -282,9 +321,9 @@ class ElementorTools {
|
||||
|
||||
public function saveAsTemplate(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$name = sanitize_text_field($args['template_name'] ?? '');
|
||||
$type = sanitize_key($args['type'] ?? 'page');
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
$name = sanitize_text_field($args['template_name'] ?? '');
|
||||
$type = sanitize_key($args['type'] ?? 'page');
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
if (is_wp_error($data)) throw new \RuntimeException($data->get_error_message());
|
||||
$template_id = wp_insert_post(['post_title'=>$name,'post_type'=>'elementor_library','post_status'=>'publish','meta_input'=>['_elementor_data'=>wp_json_encode($data),'_elementor_template_type'=>$type,'_elementor_edit_mode'=>'builder']],true);
|
||||
if (is_wp_error($template_id)) throw new \RuntimeException($template_id->get_error_message());
|
||||
@@ -293,12 +332,12 @@ class ElementorTools {
|
||||
}
|
||||
|
||||
public function applyTemplate(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$template_id = (int)($args['template_id'] ?? 0);
|
||||
$template_data = get_post_meta($template_id, '_elementor_data', true);
|
||||
if (empty($template_data)) throw new \RuntimeException("Template {$template_id} has no Elementor data.");
|
||||
$elements = $this->data->reassign_ids(json_decode($template_data, true) ?: []);
|
||||
$result = $this->data->save_page_data($post_id, $elements);
|
||||
$result = $this->data->save_page_data($post_id, $elements);
|
||||
if (is_wp_error($result)) throw new \RuntimeException($result->get_error_message());
|
||||
return ['success'=>true,'post_id'=>$post_id,'template_id'=>$template_id,'elements_count'=>$this->data->count_elements($elements)];
|
||||
}
|
||||
@@ -314,7 +353,7 @@ class ElementorTools {
|
||||
$kit = $this->getActiveKit();
|
||||
if (!$kit) return ['colors'=>[],'note'=>'No active Elementor kit found.'];
|
||||
$settings = get_post_meta($kit->ID, '_elementor_page_settings', true);
|
||||
$palette = [];
|
||||
$palette = [];
|
||||
if (is_array($settings) && !empty($settings['system_colors'])) {
|
||||
foreach ($settings['system_colors'] as $color) {
|
||||
$palette[] = ['id'=>$color['_id']??'','title'=>$color['title']??'','value'=>$color['color']??''];
|
||||
@@ -327,9 +366,9 @@ class ElementorTools {
|
||||
$kit = $this->getActiveKit();
|
||||
if (!$kit) throw new \RuntimeException('No active Elementor kit found.');
|
||||
$settings = get_post_meta($kit->ID, '_elementor_page_settings', true) ?: [];
|
||||
$colors = $settings['system_colors'] ?? [];
|
||||
$id = $args['id'] ?? ('color_'.substr(md5($args['title']??uniqid()),0,6));
|
||||
$found = false;
|
||||
$colors = $settings['system_colors'] ?? [];
|
||||
$id = $args['id'] ?? ('color_'.substr(md5($args['title']??uniqid()),0,6));
|
||||
$found = false;
|
||||
foreach ($colors as &$color) {
|
||||
if (($color['_id']??'') === $id) { $color['title']=$args['title']??$color['title']; $color['color']=$args['value']??$color['color']; $found=true; break; }
|
||||
}
|
||||
@@ -343,7 +382,7 @@ class ElementorTools {
|
||||
$kit = $this->getActiveKit();
|
||||
if (!$kit) return ['fonts'=>[]];
|
||||
$settings = get_post_meta($kit->ID, '_elementor_page_settings', true) ?: [];
|
||||
$fonts = [];
|
||||
$fonts = [];
|
||||
if (!empty($settings['system_typography'])) {
|
||||
foreach ($settings['system_typography'] as $font) {
|
||||
$fonts[] = ['id'=>$font['_id']??'','title'=>$font['title']??'','family'=>$font['typography_font_family']??''];
|
||||
@@ -362,7 +401,7 @@ class ElementorTools {
|
||||
$kit = $this->getActiveKit();
|
||||
if (!$kit) throw new \RuntimeException('No active Elementor kit found.');
|
||||
$current = get_post_meta($kit->ID, '_elementor_page_settings', true) ?: [];
|
||||
update_post_meta($kit->ID, '_elementor_page_settings', array_merge($current, $args['settings'] ?? []));
|
||||
update_post_meta($kit->ID, '_elementor_page_settings', array_merge($current, $args['settings']??[]));
|
||||
return ['success'=>true,'kit_id'=>$kit->ID];
|
||||
}
|
||||
|
||||
@@ -372,9 +411,9 @@ class ElementorTools {
|
||||
}
|
||||
|
||||
public function getElement(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$element_id = $args['element_id'] ?? '';
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
if (is_wp_error($data)) throw new \RuntimeException($data->get_error_message());
|
||||
$element = $this->data->find_element($data, $element_id);
|
||||
if (!$element) throw new \RuntimeException("Element not found: {$element_id}");
|
||||
@@ -383,9 +422,9 @@ class ElementorTools {
|
||||
|
||||
public function clonePage(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$orig = get_post($post_id);
|
||||
$orig = get_post($post_id);
|
||||
if (!$orig) throw new \RuntimeException("Post not found: {$post_id}");
|
||||
$title = $args['title'] ?? ('Copy of '.$orig->post_title);
|
||||
$title = $args['title'] ?? ('Copy of '.$orig->post_title);
|
||||
$new_id = wp_insert_post(['post_title'=>$title,'post_type'=>$orig->post_type,'post_status'=>'draft','meta_input'=>['_elementor_edit_mode'=>'builder','_elementor_template_type'=>get_post_meta($post_id,'_elementor_template_type',true)]],true);
|
||||
if (is_wp_error($new_id)) throw new \RuntimeException($new_id->get_error_message());
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
@@ -394,15 +433,15 @@ class ElementorTools {
|
||||
}
|
||||
|
||||
public function replaceText(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$search = $args['search'] ?? '';
|
||||
$replace = $args['replace'] ?? '';
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$search = $args['search'] ?? '';
|
||||
$replace = $args['replace'] ?? '';
|
||||
$case_sensitive = (bool)($args['case_sensitive'] ?? false);
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
if (is_wp_error($data)) throw new \RuntimeException($data->get_error_message());
|
||||
$encoded = wp_json_encode($data);
|
||||
$count = 0;
|
||||
$new = $case_sensitive ? str_replace($search, $replace, $encoded, $count) : str_ireplace($search, $replace, $encoded, $count);
|
||||
$count = 0;
|
||||
$new = $case_sensitive ? str_replace($search,$replace,$encoded,$count) : str_ireplace($search,$replace,$encoded,$count);
|
||||
$new_data = json_decode($new, true);
|
||||
if (!is_array($new_data)) throw new \RuntimeException('Failed to re-encode data after replacement.');
|
||||
$result = $this->data->save_page_data($post_id, $new_data);
|
||||
@@ -412,7 +451,7 @@ class ElementorTools {
|
||||
|
||||
public function getPageOutline(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
$data = $this->data->get_page_data($post_id);
|
||||
if (is_wp_error($data)) throw new \RuntimeException($data->get_error_message());
|
||||
return ['post_id'=>$post_id,'outline'=>$this->buildOutline($data)];
|
||||
}
|
||||
@@ -422,16 +461,12 @@ class ElementorTools {
|
||||
if (empty($ids)) return ['error'=>'element_ids or element_id required'];
|
||||
$deleted = [];
|
||||
foreach ($ids as $eid) {
|
||||
$result = $this->removeElement(['post_id'=>$args['post_id'],'element_id'=>$eid]);
|
||||
$result = $this->removeElement(['post_id'=>$args['post_id'],'element_id'=>$eid]);
|
||||
$deleted[] = ['element_id'=>$eid,'result'=>$result];
|
||||
}
|
||||
return ['post_id'=>$args['post_id'],'deleted'=>$deleted,'count'=>count($deleted)];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// CSS / REGENERATION / MIGRATION METHODS
|
||||
// =========================================================
|
||||
|
||||
public function flushCss(array $args): array {
|
||||
if (!class_exists('\Elementor\Plugin')) throw new \RuntimeException('Elementor not active.');
|
||||
\Elementor\Plugin::instance()->files_manager->clear_cache();
|
||||
@@ -441,8 +476,8 @@ class ElementorTools {
|
||||
public function regenerateCss(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
if (!$post_id) throw new \RuntimeException('post_id is required.');
|
||||
$css_file = new \Elementor\Core\Files\CSS\Post($post_id);
|
||||
$css_file->update();
|
||||
if (!class_exists('\Elementor\Core\Files\CSS\Post')) throw new \RuntimeException('Elementor CSS class not found.');
|
||||
(new \Elementor\Core\Files\CSS\Post($post_id))->update();
|
||||
return ['success'=>true,'post_id'=>$post_id,'message'=>"CSS regenerated for post {$post_id}."];
|
||||
}
|
||||
|
||||
@@ -454,23 +489,20 @@ class ElementorTools {
|
||||
global $wpdb;
|
||||
$posts = $wpdb->get_col($wpdb->prepare(
|
||||
"SELECT pm.post_id FROM {$wpdb->postmeta} pm WHERE pm.meta_key='_elementor_data' AND pm.meta_value LIKE %s",
|
||||
'%' . $wpdb->esc_like($old) . '%'
|
||||
'%'.$wpdb->esc_like($old).'%'
|
||||
));
|
||||
$updated = 0;
|
||||
$errors = [];
|
||||
$updated = 0; $errors = [];
|
||||
foreach ($posts as $pid) {
|
||||
$raw = get_post_meta((int)$pid, '_elementor_data', true);
|
||||
if (empty($raw)) continue;
|
||||
$decoded = json_decode($raw, true);
|
||||
$new_raw = str_replace($old, $new, is_array($decoded) ? wp_json_encode($decoded) : $raw);
|
||||
$new_raw = str_replace($old, $new, is_array($decoded) ? wp_json_encode($decoded) : $raw);
|
||||
if ($new_raw === $raw) continue;
|
||||
$result = update_post_meta((int)$pid, '_elementor_data', wp_slash($new_raw));
|
||||
if ($result !== false) {
|
||||
$r = update_post_meta((int)$pid, '_elementor_data', wp_slash($new_raw));
|
||||
if ($r !== false) {
|
||||
if (class_exists('\Elementor\Core\Files\CSS\Post')) (new \Elementor\Core\Files\CSS\Post((int)$pid))->update();
|
||||
$updated++;
|
||||
} else {
|
||||
$errors[] = (int)$pid;
|
||||
}
|
||||
} else { $errors[] = (int)$pid; }
|
||||
}
|
||||
return ['success'=>true,'old_url'=>$old,'new_url'=>$new,'posts_found'=>count($posts),'updated'=>$updated,'errors'=>$errors];
|
||||
}
|
||||
@@ -479,14 +511,14 @@ class ElementorTools {
|
||||
$kit = $this->getActiveKit();
|
||||
if (!$kit) return ['css'=>'','note'=>'No active Elementor kit.'];
|
||||
$settings = get_post_meta($kit->ID, '_elementor_page_settings', true) ?: [];
|
||||
return ['css'=>$settings['custom_css'] ?? '','kit_id'=>$kit->ID];
|
||||
return ['css'=>$settings['custom_css']??'','kit_id'=>$kit->ID];
|
||||
}
|
||||
|
||||
public function updateKitCustomCss(array $args): array {
|
||||
$kit = $this->getActiveKit();
|
||||
if (!$kit) throw new \RuntimeException('No active Elementor kit found.');
|
||||
$settings = get_post_meta($kit->ID, '_elementor_page_settings', true) ?: [];
|
||||
$settings['custom_css'] = $args['css'] ?? '';
|
||||
$settings['custom_css'] = $args['css']??'';
|
||||
update_post_meta($kit->ID, '_elementor_page_settings', $settings);
|
||||
if (class_exists('\Elementor\Core\Files\CSS\Post')) (new \Elementor\Core\Files\CSS\Post($kit->ID))->update();
|
||||
return ['success'=>true,'kit_id'=>$kit->ID,'bytes'=>strlen($settings['custom_css'])];
|
||||
@@ -496,14 +528,14 @@ class ElementorTools {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
if (!$post_id) throw new \RuntimeException('post_id is required.');
|
||||
$settings = get_post_meta($post_id, '_elementor_page_settings', true) ?: [];
|
||||
return ['post_id'=>$post_id,'css'=>$settings['custom_css'] ?? ''];
|
||||
return ['post_id'=>$post_id,'css'=>$settings['custom_css']??''];
|
||||
}
|
||||
|
||||
public function updatePageCustomCss(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
if (!$post_id) throw new \RuntimeException('post_id is required.');
|
||||
$settings = get_post_meta($post_id, '_elementor_page_settings', true) ?: [];
|
||||
$settings['custom_css'] = $args['css'] ?? '';
|
||||
$settings['custom_css'] = $args['css']??'';
|
||||
update_post_meta($post_id, '_elementor_page_settings', $settings);
|
||||
if (class_exists('\Elementor\Core\Files\CSS\Post')) (new \Elementor\Core\Files\CSS\Post($post_id))->update();
|
||||
return ['success'=>true,'post_id'=>$post_id,'bytes'=>strlen($settings['custom_css'])];
|
||||
@@ -523,17 +555,21 @@ class ElementorTools {
|
||||
$name = $args['name'] ?? '';
|
||||
$state = $args['state'] ?? 'default';
|
||||
if (empty($name)) throw new \RuntimeException('name is required.');
|
||||
if (!in_array($state, ['active','inactive','default'], true)) throw new \RuntimeException('state must be active, inactive, or default.');
|
||||
if (!in_array($state,['active','inactive','default'],true)) throw new \RuntimeException('state must be active, inactive, or default.');
|
||||
if (!class_exists('\Elementor\Plugin')) throw new \RuntimeException('Elementor not active.');
|
||||
$experiments = \Elementor\Plugin::instance()->experiments;
|
||||
$feature = $experiments->get_features($name);
|
||||
$feature = $experiments->get_features($name);
|
||||
if (!$feature) throw new \RuntimeException("Experiment '{$name}' not found.");
|
||||
$option_key = $experiments->get_feature_option_key($name);
|
||||
if ($state === 'default') delete_option($option_key);
|
||||
else update_option($option_key, $state);
|
||||
$option_key = $experiments->get_feature_option_key($name);
|
||||
if ($state === 'default') delete_option($option_key); else update_option($option_key, $state);
|
||||
return ['success'=>true,'name'=>$name,'state'=>$state,'is_active'=>$experiments->is_feature_active($name)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set page template — always uses update_post_meta directly.
|
||||
* wp_update_post template validation fails in CLI context (no registered templates).
|
||||
* See 05-KNOWN-ISSUES.md.
|
||||
*/
|
||||
public function setPageTemplate(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$template = $args['template'] ?? '';
|
||||
@@ -541,19 +577,15 @@ class ElementorTools {
|
||||
if ($template === 'default') $template = '';
|
||||
update_post_meta($post_id, '_wp_page_template', $template);
|
||||
if ($template && class_exists('\Elementor\Core\Files\CSS\Post')) (new \Elementor\Core\Files\CSS\Post($post_id))->update();
|
||||
return ['success'=>true,'post_id'=>$post_id,'template'=>$template ?: 'default'];
|
||||
return ['success'=>true,'post_id'=>$post_id,'template'=>$template?:'default'];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// REVISIONS
|
||||
// =========================================================
|
||||
|
||||
public function getRevisions(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
if (!$post_id) throw new \RuntimeException('post_id is required.');
|
||||
if (!class_exists('\Elementor\Plugin')) throw new \RuntimeException('Elementor not active.');
|
||||
$revisions = \Elementor\Plugin::instance()->revisions_manager->get_revisions($post_id);
|
||||
$clean = array_map(fn($r) => ['id'=>$r['id'],'type'=>$r['type'],'author'=>$r['author']??'','timestamp'=>$r['timestamp']??0], $revisions ?? []);
|
||||
$clean = array_map(fn($r)=>['id'=>$r['id'],'type'=>$r['type'],'author'=>$r['author']??'','timestamp'=>$r['timestamp']??0], $revisions??[]);
|
||||
return ['post_id'=>$post_id,'revisions'=>$clean,'total'=>count($clean)];
|
||||
}
|
||||
|
||||
@@ -567,47 +599,19 @@ class ElementorTools {
|
||||
return ['success'=>true,'post_id'=>$post_id,'restored_revision_id'=>$revision_id];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// PERFORMANCE SETTINGS
|
||||
// =========================================================
|
||||
|
||||
public function getPerformanceSettings(array $args): array {
|
||||
return [
|
||||
'css_print_method' => get_option('elementor_css_print_method', 'external'),
|
||||
'google_font' => get_option('elementor_google_font', '1'),
|
||||
'load_fa4_shim' => get_option('elementor_load_fa4_shim', ''),
|
||||
'lazy_load_background_images' => get_option('elementor_lazy_load_background_images', '1'),
|
||||
'meta_generator_tag' => get_option('elementor_meta_generator_tag', ''),
|
||||
'unfiltered_files_upload' => get_option('elementor_unfiltered_files_upload', ''),
|
||||
'font_display' => get_option('elementor_font_display', 'auto'),
|
||||
'google_maps_api_key' => get_option('elementor_google_maps_api_key', ''),
|
||||
'element_cache_ttl' => (int)get_option('elementor_element_cache_ttl', 24),
|
||||
'optimized_gutenberg_loading' => get_option('elementor_optimized_gutenberg_loading', '1'),
|
||||
];
|
||||
return ['css_print_method'=>get_option('elementor_css_print_method','external'),'google_font'=>get_option('elementor_google_font','1'),'load_fa4_shim'=>get_option('elementor_load_fa4_shim',''),'lazy_load_background_images'=>get_option('elementor_lazy_load_background_images','1'),'meta_generator_tag'=>get_option('elementor_meta_generator_tag',''),'unfiltered_files_upload'=>get_option('elementor_unfiltered_files_upload',''),'font_display'=>get_option('elementor_font_display','auto'),'google_maps_api_key'=>get_option('elementor_google_maps_api_key',''),'element_cache_ttl'=>(int)get_option('elementor_element_cache_ttl',24),'optimized_gutenberg_loading'=>get_option('elementor_optimized_gutenberg_loading','1')];
|
||||
}
|
||||
|
||||
public function updatePerformanceSettings(array $args): array {
|
||||
$settings = $args['settings'] ?? [];
|
||||
if (empty($settings)) throw new \RuntimeException('settings object is required.');
|
||||
$map = [
|
||||
'css_print_method' => ['key'=>'elementor_css_print_method', 'allowed'=>['external','internal']],
|
||||
'google_font' => ['key'=>'elementor_google_font', 'allowed'=>['1','']],
|
||||
'load_fa4_shim' => ['key'=>'elementor_load_fa4_shim', 'allowed'=>['1','']],
|
||||
'lazy_load_background_images' => ['key'=>'elementor_lazy_load_background_images', 'allowed'=>['1','']],
|
||||
'meta_generator_tag' => ['key'=>'elementor_meta_generator_tag', 'allowed'=>['1','']],
|
||||
'unfiltered_files_upload' => ['key'=>'elementor_unfiltered_files_upload', 'allowed'=>['1','']],
|
||||
'font_display' => ['key'=>'elementor_font_display', 'allowed'=>['auto','block','swap','fallback','optional']],
|
||||
'google_maps_api_key' => ['key'=>'elementor_google_maps_api_key', 'allowed'=>null],
|
||||
'element_cache_ttl' => ['key'=>'elementor_element_cache_ttl', 'allowed'=>null,'type'=>'int'],
|
||||
'optimized_gutenberg_loading' => ['key'=>'elementor_optimized_gutenberg_loading', 'allowed'=>['1','']],
|
||||
];
|
||||
$map = ['css_print_method'=>['key'=>'elementor_css_print_method','allowed'=>['external','internal']],'google_font'=>['key'=>'elementor_google_font','allowed'=>['1','']],'load_fa4_shim'=>['key'=>'elementor_load_fa4_shim','allowed'=>['1','']],'lazy_load_background_images'=>['key'=>'elementor_lazy_load_background_images','allowed'=>['1','']],'meta_generator_tag'=>['key'=>'elementor_meta_generator_tag','allowed'=>['1','']],'unfiltered_files_upload'=>['key'=>'elementor_unfiltered_files_upload','allowed'=>['1','']],'font_display'=>['key'=>'elementor_font_display','allowed'=>['auto','block','swap','fallback','optional']],'google_maps_api_key'=>['key'=>'elementor_google_maps_api_key','allowed'=>null],'element_cache_ttl'=>['key'=>'elementor_element_cache_ttl','allowed'=>null,'type'=>'int'],'optimized_gutenberg_loading'=>['key'=>'elementor_optimized_gutenberg_loading','allowed'=>['1','']]];
|
||||
$updated = []; $errors = [];
|
||||
foreach ($settings as $field => $value) {
|
||||
if (!isset($map[$field])) { $errors[] = "Unknown field: {$field}"; continue; }
|
||||
$def = $map[$field];
|
||||
if (!empty($def['allowed']) && !in_array((string)$value, $def['allowed'], true)) {
|
||||
$errors[] = "Invalid value for {$field}: {$value}. Allowed: " . implode(', ', $def['allowed']); continue;
|
||||
}
|
||||
if (!empty($def['allowed']) && !in_array((string)$value,$def['allowed'],true)) { $errors[] = "Invalid value for {$field}: {$value}."; continue; }
|
||||
$write_val = isset($def['type']) && $def['type']==='int' ? (int)$value : (string)$value;
|
||||
update_option($def['key'], $write_val);
|
||||
$updated[$field] = $write_val;
|
||||
@@ -615,84 +619,45 @@ class ElementorTools {
|
||||
return ['success'=>empty($errors),'updated'=>$updated,'errors'=>$errors];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// CPT SUPPORT
|
||||
// =========================================================
|
||||
|
||||
public function getCptSupport(array $args): array {
|
||||
return [
|
||||
'supported_post_types' => (array)(get_option('elementor_cpt_support') ?: ['post','page']),
|
||||
'available_post_types' => array_keys(get_post_types(['public'=>true], 'names')),
|
||||
];
|
||||
return ['supported_post_types'=>(array)(get_option('elementor_cpt_support')?:['post','page']),'available_post_types'=>array_keys(get_post_types(['public'=>true],'names'))];
|
||||
}
|
||||
|
||||
public function setCptSupport(array $args): array {
|
||||
$post_types = $args['post_types'] ?? [];
|
||||
if (empty($post_types) || !is_array($post_types)) throw new \RuntimeException('post_types must be a non-empty array.');
|
||||
$all_types = array_keys(get_post_types(['public'=>true], 'names'));
|
||||
$invalid = array_diff($post_types, $all_types);
|
||||
if (!empty($invalid)) throw new \RuntimeException('Invalid post types: ' . implode(', ', $invalid));
|
||||
$all_types = array_keys(get_post_types(['public'=>true],'names'));
|
||||
$invalid = array_diff($post_types,$all_types);
|
||||
if (!empty($invalid)) throw new \RuntimeException('Invalid post types: '.implode(', ',$invalid));
|
||||
update_option('elementor_cpt_support', $post_types);
|
||||
return ['success'=>true,'supported_post_types'=>$post_types];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// MAINTENANCE MODE
|
||||
// =========================================================
|
||||
|
||||
public function getMaintenanceMode(array $args): array {
|
||||
$mode = get_option('elementor_maintenance_mode_mode', '');
|
||||
$page_id = (int)get_option('elementor_maintenance_mode_page_id', 0);
|
||||
return [
|
||||
'mode' => $mode ?: 'disabled',
|
||||
'page_id' => $page_id,
|
||||
'page_url' => $page_id ? get_permalink($page_id) : '',
|
||||
'exclude_mode' => get_option('elementor_maintenance_mode_exclude_mode', ''),
|
||||
'is_active' => !empty($mode),
|
||||
];
|
||||
$mode = get_option('elementor_maintenance_mode_mode','');
|
||||
$page_id = (int)get_option('elementor_maintenance_mode_page_id',0);
|
||||
return ['mode'=>$mode?:'disabled','page_id'=>$page_id,'page_url'=>$page_id?get_permalink($page_id):'','exclude_mode'=>get_option('elementor_maintenance_mode_exclude_mode',''),'is_active'=>!empty($mode)];
|
||||
}
|
||||
|
||||
public function setMaintenanceMode(array $args): array {
|
||||
$mode = $args['mode'] ?? '';
|
||||
$page_id = isset($args['page_id']) ? (int)$args['page_id'] : 0;
|
||||
$mode = $args['mode'] ?? '';
|
||||
$page_id = isset($args['page_id']) ? (int)$args['page_id'] : 0;
|
||||
$exclude_mode = $args['exclude_mode'] ?? '';
|
||||
if (!in_array($mode, ['maintenance','coming_soon',''], true)) throw new \RuntimeException('mode must be: maintenance, coming_soon, or empty string.');
|
||||
if (!in_array($mode,['maintenance','coming_soon',''],true)) throw new \RuntimeException('mode must be: maintenance, coming_soon, or empty string.');
|
||||
update_option('elementor_maintenance_mode_mode', $mode);
|
||||
if ($page_id) update_option('elementor_maintenance_mode_page_id', $page_id);
|
||||
if ($exclude_mode !== '') update_option('elementor_maintenance_mode_exclude_mode', $exclude_mode);
|
||||
return ['success'=>true,'mode'=>$mode ?: 'disabled','page_id'=>$page_id ?: (int)get_option('elementor_maintenance_mode_page_id',0),'exclude_mode'=>$exclude_mode ?: get_option('elementor_maintenance_mode_exclude_mode','')];
|
||||
return ['success'=>true,'mode'=>$mode?:'disabled','page_id'=>$page_id?:(int)get_option('elementor_maintenance_mode_page_id',0),'exclude_mode'=>$exclude_mode?:get_option('elementor_maintenance_mode_exclude_mode','')];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// SITE SETTINGS
|
||||
// =========================================================
|
||||
|
||||
public function getSiteSettings(array $args): array {
|
||||
return [
|
||||
'google_maps_api_key' => get_option('elementor_google_maps_api_key', ''),
|
||||
'meta_generator_tag' => get_option('elementor_meta_generator_tag', ''),
|
||||
'google_font' => get_option('elementor_google_font', '1'),
|
||||
'font_display' => get_option('elementor_font_display', 'auto'),
|
||||
'load_fa4_shim' => get_option('elementor_load_fa4_shim', ''),
|
||||
'unfiltered_files_upload' => get_option('elementor_unfiltered_files_upload', ''),
|
||||
'css_print_method' => get_option('elementor_css_print_method', 'external'),
|
||||
'elementor_version' => get_option('elementor_version', ''),
|
||||
'pro_version' => get_option('elementor_pro_version', ''),
|
||||
];
|
||||
return ['google_maps_api_key'=>get_option('elementor_google_maps_api_key',''),'meta_generator_tag'=>get_option('elementor_meta_generator_tag',''),'google_font'=>get_option('elementor_google_font','1'),'font_display'=>get_option('elementor_font_display','auto'),'load_fa4_shim'=>get_option('elementor_load_fa4_shim',''),'unfiltered_files_upload'=>get_option('elementor_unfiltered_files_upload',''),'css_print_method'=>get_option('elementor_css_print_method','external'),'elementor_version'=>get_option('elementor_version',''),'pro_version'=>get_option('elementor_pro_version','')];
|
||||
}
|
||||
|
||||
public function updateSiteSettings(array $args): array {
|
||||
$settings = $args['settings'] ?? [];
|
||||
if (empty($settings)) throw new \RuntimeException('settings object is required.');
|
||||
$allowed = [
|
||||
'google_maps_api_key' => 'elementor_google_maps_api_key',
|
||||
'meta_generator_tag' => 'elementor_meta_generator_tag',
|
||||
'google_font' => 'elementor_google_font',
|
||||
'font_display' => 'elementor_font_display',
|
||||
'load_fa4_shim' => 'elementor_load_fa4_shim',
|
||||
'unfiltered_files_upload' => 'elementor_unfiltered_files_upload',
|
||||
'css_print_method' => 'elementor_css_print_method',
|
||||
];
|
||||
$allowed = ['google_maps_api_key'=>'elementor_google_maps_api_key','meta_generator_tag'=>'elementor_meta_generator_tag','google_font'=>'elementor_google_font','font_display'=>'elementor_font_display','load_fa4_shim'=>'elementor_load_fa4_shim','unfiltered_files_upload'=>'elementor_unfiltered_files_upload','css_print_method'=>'elementor_css_print_method'];
|
||||
$updated = []; $errors = [];
|
||||
foreach ($settings as $field => $value) {
|
||||
if (!isset($allowed[$field])) { $errors[] = "Unknown or read-only field: {$field}"; continue; }
|
||||
@@ -706,20 +671,26 @@ class ElementorTools {
|
||||
// PRIVATE HELPERS
|
||||
// =========================================================
|
||||
|
||||
private function buildOutline(array $elements, int $depth = 0): array {
|
||||
$result = [];
|
||||
foreach ($elements as $el) {
|
||||
$entry = ['id'=>$el['id']??'','type'=>$el['elType']??'','depth'=>$depth];
|
||||
if (($el['elType']??'') === 'widget') $entry['widget_type'] = $el['widgetType']??'';
|
||||
if (!empty($el['elements'])) $entry['children'] = $this->buildOutline($el['elements'], $depth+1);
|
||||
$result[] = $entry;
|
||||
/**
|
||||
* Inserts $element immediately after $target_id in the tree (by-reference).
|
||||
* Mirrors insert_after() from Elementor_MCP_Layout_Abilities v1.9.0.
|
||||
*/
|
||||
private function insertAfter(array &$data, string $target_id, array $element): bool {
|
||||
foreach ($data as $index => &$item) {
|
||||
if (isset($item['id']) && $item['id'] === $target_id) {
|
||||
array_splice($data, $index+1, 0, [$element]);
|
||||
return true;
|
||||
}
|
||||
if (!empty($item['elements']) && is_array($item['elements'])) {
|
||||
if ($this->insertAfter($item['elements'], $target_id, $element)) return true;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
return false;
|
||||
}
|
||||
|
||||
private function insertIntoParent(array $elements, string $parent_id, array $element, int $position): array {
|
||||
foreach ($elements as &$el) {
|
||||
if (($el['id'] ?? '') === $parent_id) {
|
||||
if (($el['id']??'') === $parent_id) {
|
||||
$children = isset($el['elements']) && is_array($el['elements']) ? $el['elements'] : [];
|
||||
if ($position < 0 || $position >= count($children)) $children[] = $element;
|
||||
else array_splice($children, $position, 0, [$element]);
|
||||
@@ -733,6 +704,17 @@ class ElementorTools {
|
||||
return $elements;
|
||||
}
|
||||
|
||||
private function buildOutline(array $elements, int $depth = 0): array {
|
||||
$result = [];
|
||||
foreach ($elements as $el) {
|
||||
$entry = ['id'=>$el['id']??'','type'=>$el['elType']??'','depth'=>$depth];
|
||||
if (($el['elType']??'') === 'widget') $entry['widget_type'] = $el['widgetType']??'';
|
||||
if (!empty($el['elements'])) $entry['children'] = $this->buildOutline($el['elements'], $depth+1);
|
||||
$result[] = $entry;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getActiveKit(): ?\WP_Post {
|
||||
if (!class_exists('\Elementor\Plugin')) return null;
|
||||
$kit_id = get_option('elementor_active_kit');
|
||||
|
||||
Reference in New Issue
Block a user