PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.0
Elementor Website Builder – more than just a page builder v4.3.0
4.3.1 4.3.0 4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 All 454 releases
elementor / modules / mcp / abilities / build-composition / composition-persister.php

composition-persister.php in Elementor Website Builder – more than just a page builder 4.3.0, at modules/mcp/abilities/build-composition/composition-persister.php

252 lines 7.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Elementor\Modules\Mcp\Abilities\Build_Composition;
4
5 use Elementor\Core\Base\Document;
6 use Elementor\Core\Utils\Document\Document_Mutator;
7 use Elementor\Modules\Components\Components_Repository;
8 use Elementor\Modules\Mcp\Abilities\Utils\Document_Mutation_Save;
9 use Elementor\Modules\Mcp\Events\Mcp_Event_Dispatcher;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13 }
14
15 class Composition_Persister {
16
17 const DOCUMENT_ROOT = 'document';
18 const MODE_APPEND = 'append';
19 const MODE_REPLACE_CHILDREN = 'replace_children';
20
21 private Document_Mutator $mutator;
22 private Xml_Parser $xml_parser;
23
24 public function __construct( Document_Mutator $mutator, Xml_Parser $xml_parser ) {
25 $this->mutator = $mutator;
26 $this->xml_parser = $xml_parser;
27 }
28
29 /**
30 * @param Document $document The target document.
31 * @param array[] $subtrees Root subtrees to insert.
32 * @param string $parent_id Parent element id ("document" for root).
33 * @param string $mode 'append' (default) or 'replace_children'.
34 * @return array{tree: array, root_ids: string[], removed_ids: string[]}|\WP_Error
35 */
36 public function insert_and_save( Document $document, array $subtrees, string $parent_id, string $mode = self::MODE_APPEND ) {
37 $tree = $document->get_elements_data();
38 $tree = is_array( $tree ) ? $tree : [];
39 $root_ids = [];
40 $removed_ids = [];
41
42 if ( self::MODE_REPLACE_CHILDREN === $mode ) {
43 $replace_result = $this->replace_children( $tree, $parent_id );
44 if ( is_wp_error( $replace_result ) ) {
45 return $replace_result;
46 }
47 $tree = $replace_result['tree'];
48 $removed_ids = $replace_result['removed_ids'];
49 }
50
51 foreach ( $subtrees as $subtree ) {
52 $new_tree = $this->mutator->insert_subtree( $tree, $parent_id, null, $subtree );
53 if ( is_wp_error( $new_tree ) ) {
54 return $new_tree;
55 }
56 $tree = $new_tree;
57 $root_ids[] = $this->find_last_root_id( $tree, $parent_id );
58 }
59
60 $save_result = Document_Mutation_Save::elements_preserving_live_status( $this->mutator, $document, $tree );
61 if ( is_wp_error( $save_result ) ) {
62 return $save_result;
63 }
64
65 $this->emit_insertion_events( $tree, $parent_id, $root_ids );
66
67 return [
68 'tree' => $tree,
69 'root_ids' => $root_ids,
70 'removed_ids' => $removed_ids,
71 ];
72 }
73
74 /**
75 * @return array{tree: array, removed_ids: string[]}|\WP_Error
76 */
77 private function replace_children( array $tree, string $parent_id ) {
78 if ( self::DOCUMENT_ROOT === $parent_id ) {
79 $removed_ids = array_map(
80 fn( $node ) => isset( $node['id'] ) ? (string) $node['id'] : '',
81 $tree
82 );
83 $removed_ids = array_filter( $removed_ids );
84 return [
85 'tree' => [],
86 'removed_ids' => array_values( $removed_ids ),
87 ];
88 }
89
90 $parent = $this->mutator->find_by_id( $tree, $parent_id );
91 if ( null === $parent ) {
92 return new \WP_Error(
93 'elementor_not_found',
94 __( 'Parent element not found.', 'elementor' ),
95 [ 'status' => \WP_Http::NOT_FOUND ]
96 );
97 }
98
99 $children = $parent['elements'] ?? [];
100 $removed_ids = [];
101
102 foreach ( $children as $child ) {
103 if ( ! isset( $child['id'] ) ) {
104 continue;
105 }
106 $child_id = (string) $child['id'];
107 $removed_ids[] = $child_id;
108 $new_tree = $this->mutator->remove( $tree, $child_id );
109 if ( is_wp_error( $new_tree ) ) {
110 return $new_tree;
111 }
112 $tree = $new_tree;
113 }
114
115 return [
116 'tree' => $tree,
117 'removed_ids' => $removed_ids,
118 ];
119 }
120
121 public function embed_ids_into_dom( \DOMDocument $dom, array $tree, string $parent_id, array $root_ids ): void {
122 $root = $this->xml_parser->get_root( $dom );
123 if ( ! $root ) {
124 return;
125 }
126
127 foreach ( $this->xml_parser->get_child_elements( $root ) as $index => $child ) {
128 if ( ! isset( $root_ids[ $index ] ) ) {
129 break;
130 }
131 $subtree_node = $this->find_persisted_subtree( $tree, $parent_id, $root_ids[ $index ] );
132 if ( $subtree_node ) {
133 $this->apply_ids_recursive( $child, $subtree_node );
134 }
135 }
136 }
137
138 private function find_last_root_id( array $tree, string $parent_id ): string {
139 if ( self::DOCUMENT_ROOT === $parent_id ) {
140 $last = end( $tree );
141 return is_array( $last ) && isset( $last['id'] ) ? (string) $last['id'] : '';
142 }
143
144 $parent = $this->mutator->find_by_id( $tree, $parent_id );
145 if ( ! $parent || empty( $parent['elements'] ) ) {
146 return '';
147 }
148 $last = end( $parent['elements'] );
149 return is_array( $last ) && isset( $last['id'] ) ? (string) $last['id'] : '';
150 }
151
152 private function find_persisted_subtree( array $tree, string $parent_id, string $root_id ): ?array {
153 if ( self::DOCUMENT_ROOT === $parent_id ) {
154 foreach ( $tree as $node ) {
155 if ( isset( $node['id'] ) && $node['id'] === $root_id ) {
156 return $node;
157 }
158 }
159 return null;
160 }
161 return $this->mutator->find_by_id( $tree, $root_id );
162 }
163
164 private function apply_ids_recursive( \DOMElement $node, array $subtree_node ): void {
165 if ( ! empty( $subtree_node['id'] ) ) {
166 $node->setAttribute( 'id', (string) $subtree_node['id'] );
167 }
168
169 $child_subtrees = $subtree_node['elements'] ?? [];
170 foreach ( $this->xml_parser->get_child_elements( $node ) as $index => $child ) {
171 if ( isset( $child_subtrees[ $index ] ) && is_array( $child_subtrees[ $index ] ) ) {
172 $this->apply_ids_recursive( $child, $child_subtrees[ $index ] );
173 }
174 }
175 }
176
177 private function emit_insertion_events( array $tree, string $parent_id, array $root_ids ): void {
178 $top_element_type = $this->resolve_top_element_type( $tree, $parent_id );
179
180 foreach ( $root_ids as $root_id ) {
181 $root_node = $this->mutator->find_by_id( $tree, $root_id );
182
183 if ( null === $root_node ) {
184 continue;
185 }
186
187 $this->walk_and_emit_insertion_events( $root_node, $top_element_type );
188 }
189 }
190
191 private function walk_and_emit_insertion_events( array $node, string $top_element_type ): void {
192 $element_name = $node['widgetType'] ?? $node['elType'] ?? '';
193
194 if ( '' !== $element_name ) {
195 Mcp_Event_Dispatcher::emit( 'element_added', [
196 'element_name' => $element_name,
197 ] );
198 }
199
200 if ( 'e-component' === ( $node['widgetType'] ?? '' ) ) {
201 [ 'id' => $component_id, 'name' => $component_name ] = $this->resolve_component_instance_meta( $node );
202
203 Mcp_Event_Dispatcher::emit( 'component_instance_added', [
204 'id' => $component_id,
205 'name' => $component_name,
206 'top_element_type' => $top_element_type,
207 ] );
208 }
209
210 foreach ( $node['elements'] ?? [] as $child ) {
211 $this->walk_and_emit_insertion_events( $child, $top_element_type );
212 }
213 }
214
215 /**
216 * @return array{id: string, name: string}
217 */
218 private function resolve_component_instance_meta( array $node ): array {
219 $raw_id = $node['settings']['component_instance']['value']['component_id']['value'] ?? null;
220
221 if ( null === $raw_id || '' === $raw_id ) {
222 return [
223 'id' => '',
224 'name' => '',
225 ];
226 }
227
228 $component_id = (int) $raw_id;
229 $component = Components_Repository::make()->get( $component_id, false );
230 $name = $component ? $component->get_post()->post_title : '';
231
232 return [
233 'id' => (string) $component_id,
234 'name' => (string) $name,
235 ];
236 }
237
238 private function resolve_top_element_type( array $tree, string $parent_id ): string {
239 if ( self::DOCUMENT_ROOT === $parent_id ) {
240 return 'document';
241 }
242
243 $parent = $this->mutator->find_by_id( $tree, $parent_id );
244
245 if ( null === $parent ) {
246 return '';
247 }
248
249 return $parent['widgetType'] ?? $parent['elType'] ?? '';
250 }
251 }
252