feat: add 10 new Elementor tools — flush_css, regenerate_css, replace_urls, kit/page custom CSS, experiments, set_page_template
This commit is contained in:
@@ -16,6 +16,7 @@ class ElementorTools {
|
||||
|
||||
public function getToolDefinitions(): array {
|
||||
return [
|
||||
// ── existing 32 tools ──────────────────────────────────────────
|
||||
$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'),
|
||||
@@ -48,6 +49,30 @@ 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'),
|
||||
|
||||
// ── NEW 10 tools ───────────────────────────────────────────────
|
||||
|
||||
// CSS / Regeneration
|
||||
$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'),
|
||||
|
||||
// Replace URLs (migration / domain change)
|
||||
$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'),
|
||||
|
||||
// Kit custom CSS
|
||||
$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'),
|
||||
|
||||
// Per-page custom CSS
|
||||
$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'),
|
||||
$this->def('elementor_update_page_custom_css','Set the custom CSS for a specific Elementor page.',['post_id'=>['type'=>'integer'],'css'=>['type'=>'string']],['post_id','css'],'write','updatePageCustomCss'),
|
||||
|
||||
// Experiments / Feature Flags
|
||||
$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'),
|
||||
|
||||
// Page template
|
||||
$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'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -57,6 +82,10 @@ class ElementorTools {
|
||||
return ['name'=>$name,'description'=>$desc,'scope'=>$scope,'handler'=>[$this,$method],'inputSchema'=>$schema];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// EXISTING METHODS
|
||||
// =========================================================
|
||||
|
||||
public function createPage(array $args): array {
|
||||
$title = sanitize_text_field($args['title'] ?? '');
|
||||
$status = sanitize_key($args['status'] ?? 'draft');
|
||||
@@ -220,7 +249,6 @@ class ElementorTools {
|
||||
public function updateWidget(array $args): array { return $this->updateElement($args); }
|
||||
|
||||
public function addHeading(array $args): array {
|
||||
// Accept both 'title' and 'text' for compatibility
|
||||
$text = $args['title'] ?? $args['text'] ?? '';
|
||||
$settings = ['title'=>$text];
|
||||
if (!empty($args['tag'])) $settings['header_size'] = $args['tag'];
|
||||
@@ -383,41 +411,6 @@ class ElementorTools {
|
||||
return ['post_id'=>$post_id,'outline'=>$this->buildOutline($data)];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an element into a parent by ID.
|
||||
*
|
||||
* Bug fix: use isset+is_array instead of !empty for the recursive check.
|
||||
* !empty([]) === false, so containers with an empty elements array were
|
||||
* skipped entirely — the widget was never inserted and silently lost.
|
||||
*/
|
||||
private function insertIntoParent(array $elements, string $parent_id, array $element, int $position): array {
|
||||
foreach ($elements as &$el) {
|
||||
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]);
|
||||
$el['elements'] = $children;
|
||||
return $elements;
|
||||
}
|
||||
// Must use isset+is_array, NOT !empty — empty array must still be recursed into
|
||||
if (isset($el['elements']) && is_array($el['elements'])) {
|
||||
$el['elements'] = $this->insertIntoParent($el['elements'], $parent_id, $element, $position);
|
||||
}
|
||||
}
|
||||
return $elements;
|
||||
}
|
||||
|
||||
public function deleteItems(array $args): array {
|
||||
$ids = $args['element_ids'] ?? (isset($args['element_id']) ? [$args['element_id']] : []);
|
||||
if (empty($ids)) return ['error'=>'element_ids or element_id required'];
|
||||
@@ -429,6 +422,192 @@ class ElementorTools {
|
||||
return ['post_id'=>$args['post_id'],'deleted'=>$deleted,'count'=>count($deleted)];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// NEW METHODS — CSS / Regeneration
|
||||
// =========================================================
|
||||
|
||||
public function flushCss(array $args): array {
|
||||
if (!class_exists('\Elementor\Plugin')) throw new \RuntimeException('Elementor not active.');
|
||||
\Elementor\Plugin::instance()->files_manager->clear_cache();
|
||||
return ['success'=>true,'message'=>'All Elementor CSS files flushed and queued for regeneration.'];
|
||||
}
|
||||
|
||||
public function regenerateCss(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
if (!$post_id) throw new \RuntimeException('post_id is required.');
|
||||
if (!class_exists('\Elementor\Core\Files\CSS\Post')) throw new \RuntimeException('Elementor CSS class not found.');
|
||||
$css_file = new \Elementor\Core\Files\CSS\Post($post_id);
|
||||
$css_file->update();
|
||||
return ['success'=>true,'post_id'=>$post_id,'message'=>"CSS regenerated for post {$post_id}."];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// NEW METHODS — Replace URLs
|
||||
// =========================================================
|
||||
|
||||
public function replaceUrls(array $args): array {
|
||||
$old = trim($args['old_url'] ?? '');
|
||||
$new = trim($args['new_url'] ?? '');
|
||||
if (empty($old) || empty($new)) throw new \RuntimeException('old_url and new_url are required.');
|
||||
if ($old === $new) return ['success'=>true,'updated'=>0,'message'=>'old_url and new_url are identical.'];
|
||||
|
||||
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) . '%'
|
||||
));
|
||||
|
||||
$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);
|
||||
if ($new_raw === $raw) continue;
|
||||
$result = update_post_meta((int)$pid, '_elementor_data', wp_slash($new_raw));
|
||||
if ($result !== false) {
|
||||
if (class_exists('\Elementor\Core\Files\CSS\Post')) {
|
||||
(new \Elementor\Core\Files\CSS\Post((int)$pid))->update();
|
||||
}
|
||||
$updated++;
|
||||
} else {
|
||||
$errors[] = (int)$pid;
|
||||
}
|
||||
}
|
||||
|
||||
return ['success'=>true,'old_url'=>$old,'new_url'=>$new,'posts_found'=>count($posts),'updated'=>$updated,'errors'=>$errors];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// NEW METHODS — Kit & page-level custom CSS
|
||||
// =========================================================
|
||||
|
||||
public function getKitCustomCss(array $args): array {
|
||||
$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];
|
||||
}
|
||||
|
||||
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'] ?? '';
|
||||
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'])];
|
||||
}
|
||||
|
||||
public function getPageCustomCss(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) ?: [];
|
||||
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'] ?? '';
|
||||
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'])];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// NEW METHODS — Experiments / Feature Flags
|
||||
// =========================================================
|
||||
|
||||
public function listExperiments(array $args): array {
|
||||
if (!class_exists('\Elementor\Plugin')) throw new \RuntimeException('Elementor not active.');
|
||||
$features = \Elementor\Plugin::instance()->experiments->get_features();
|
||||
$list = [];
|
||||
foreach ($features as $name => $feature) {
|
||||
$list[] = [
|
||||
'name' => $name,
|
||||
'title' => $feature['title'] ?? $name,
|
||||
'description' => $feature['description'] ?? '',
|
||||
'state' => $feature['state'] ?? 'default',
|
||||
'default' => $feature['default'] ?? 'inactive',
|
||||
'is_active' => \Elementor\Plugin::instance()->experiments->is_feature_active($name),
|
||||
];
|
||||
}
|
||||
return ['experiments'=>$list,'total'=>count($list)];
|
||||
}
|
||||
|
||||
public function setExperiment(array $args): array {
|
||||
$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 (!class_exists('\Elementor\Plugin')) throw new \RuntimeException('Elementor not active.');
|
||||
$experiments = \Elementor\Plugin::instance()->experiments;
|
||||
$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);
|
||||
}
|
||||
return ['success'=>true,'name'=>$name,'state'=>$state,'is_active'=>$experiments->is_feature_active($name)];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// NEW METHODS — Page Template
|
||||
// =========================================================
|
||||
|
||||
public function setPageTemplate(array $args): array {
|
||||
$post_id = (int)($args['post_id'] ?? 0);
|
||||
$template = $args['template'] ?? '';
|
||||
if (!$post_id) throw new \RuntimeException('post_id is required.');
|
||||
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'];
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// 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;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function insertIntoParent(array $elements, string $parent_id, array $element, int $position): array {
|
||||
foreach ($elements as &$el) {
|
||||
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]);
|
||||
$el['elements'] = $children;
|
||||
return $elements;
|
||||
}
|
||||
if (isset($el['elements']) && is_array($el['elements'])) {
|
||||
$el['elements'] = $this->insertIntoParent($el['elements'], $parent_id, $element, $position);
|
||||
}
|
||||
}
|
||||
return $elements;
|
||||
}
|
||||
|
||||
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