PluginProbe
ElementsKit Elementor Addons – Advanced Widgets & Templates Addons for Elementor / 4.0.4
ElementsKit Elementor Addons – Advanced Widgets & Templates Addons for Elementor v4.0.4
4.0.4 4.0.3 4.0.2 4.0.1 4.0.0 3.10.02 3.10.01 3.9.10 3.9.9 3.9.8 3.9.7 3.9.5 3.9.6 3.9.3 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.2.0 2.2.1 2.2.2 All 183 releases
elementskit-lite / widgets / init / nested-document-assets.php
nested-document-assets.php
297 lines 8.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace ElementsKit_Lite\Widgets\Init;
3
4 use ElementsKit_Lite\Modules\Header_Footer\Activator;
5 use ElementsKit_Lite\Utils;
6
7 defined( 'ABSPATH' ) || exit;
8
9 /**
10 * Preloads assets for Elementor documents rendered by ElementsKit.
11 */
12 class Nested_Document_Assets {
13
14 /**
15 * Widget Area documents, keyed by exact mega-menu title match.
16 * @var array|null
17 */
18 private $mega_menu_documents;
19
20 /**
21 * Widget Area documents whose titles use the "dynamic-content-widget-{key}-*" prefix form.
22 * Each entry is [ 'suffix' => string after the shared prefix, 'id' => int ].
23 * @var array|null
24 */
25 private $widget_area_candidates;
26
27 /**
28 * Per-request cache of resolved widget style/script dependencies, keyed by widget type and settings.
29 * Avoids rebuilding equivalent widget instances that repeat on a page.
30 * @var array
31 */
32 private $widget_dependency_cache = array();
33
34 /**
35 * Per-request cache of nav menu items, keyed by menu id/slug/object.
36 * @var array
37 */
38 private $nav_menu_items_cache = array();
39
40 /**
41 * Enqueue dependencies before wp_head prints styles.
42 * @since 4.0.3
43 * @return void
44 */
45 public function enqueue() {
46 if ( ! class_exists( '\\Elementor\\Plugin' ) ) {
47 return;
48 }
49
50 $document_ids = array_filter( array( get_queried_object_id() ) );
51
52 if ( class_exists( Activator::class ) ) {
53 $document_ids = array_merge( $document_ids, (array) Activator::template_ids() );
54 }
55
56 $checked = array();
57 $queue = array_values( array_unique( array_map( 'absint', array_filter( $document_ids ) ) ) );
58
59 while ( ! empty( $queue ) ) {
60 $document_id = array_shift( $queue );
61 if ( isset( $checked[ $document_id ] ) ) {
62 continue;
63 }
64
65 $checked[ $document_id ] = true;
66 $elements = $this->get_document_elements( $document_id );
67 if ( empty( $elements ) ) {
68 continue;
69 }
70
71 Utils::render_elementor_content_css( $document_id );
72 $widget_area_keys = array();
73 $this->enqueue_element_dependencies( $elements, $widget_area_keys );
74
75 foreach ( $this->get_widget_area_document_ids( $widget_area_keys ) as $nested_id ) {
76 if ( ! isset( $checked[ $nested_id ] ) ) {
77 $queue[] = $nested_id;
78 }
79 }
80 }
81 }
82
83 /**
84 * Get the saved Elementor element tree for a document.
85 * @since 4.0.3
86 * @param int $document_id Elementor document ID.
87 * @return array
88 */
89 private function get_document_elements( $document_id ) {
90 $data = get_post_meta( $document_id, '_elementor_data', true );
91 if ( is_string( $data ) ) {
92 $data = json_decode( $data, true );
93 }
94
95 return is_array( $data ) ? $data : array();
96 }
97
98 /**
99 * Walk an Elementor tree and enqueue every registered widget dependency.
100 * @since 4.0.3
101 * @param array $elements Elementor elements.
102 * @param array $widget_area_keys Dynamic-content lookup keys.
103 * @return void
104 */
105 private function enqueue_element_dependencies( $elements, &$widget_area_keys ) {
106 foreach ( $elements as $element ) {
107 if ( ! is_array( $element ) ) {
108 continue;
109 }
110
111 if ( ! empty( $element['id'] ) ) {
112 $widget_area_keys[] = (string) $element['id'];
113 }
114
115 if ( ! empty( $element['widgetType'] ) ) {
116 $this->enqueue_widget_dependencies( $element );
117 }
118
119 if ( ! empty( $element['settings']['elementskit_nav_menu'] ) ) {
120 foreach ( $this->get_nav_menu_items( $element['settings']['elementskit_nav_menu'] ) as $menu_item ) {
121 $widget_area_keys[] = 'megamenu-menuitem' . $menu_item->ID;
122 }
123 }
124
125 if ( ! empty( $element['settings'] ) ) {
126 array_walk_recursive(
127 $element['settings'],
128 static function ( $value ) use ( &$widget_area_keys ) {
129 if ( is_string( $value ) && preg_match( '/^([A-Za-z0-9_-]+)(?:\\*\\*\\*|$)/', $value, $matches ) ) {
130 $widget_area_keys[] = $matches[1];
131 }
132 }
133 );
134 }
135
136 if ( ! empty( $element['elements'] ) && is_array( $element['elements'] ) ) {
137 $this->enqueue_element_dependencies( $element['elements'], $widget_area_keys );
138 }
139 }
140 }
141
142 /**
143 * Resolve and enqueue a widget instance's style/script dependencies, caching
144 * the lookup for widgets with the same type and settings.
145 * @since 4.0.3
146 * @param array $element Saved Elementor widget data.
147 * @return void
148 */
149 private function enqueue_widget_dependencies( $element) {
150 //empty check for widgetType, because some widgets are not registered in Elementor and they don't have widgetType. So we need to check if widgetType is empty or not.
151 if ( empty( $element['widgetType'] ) ) {
152 return;
153 }
154
155 $widget_type = $element['widgetType'];
156 $settings = isset( $element['settings'] ) && is_array( $element['settings'] ) ? $element['settings'] : array();
157 $cache_key = $widget_type . ':' . md5( wp_json_encode( $settings ) );
158
159 if ( ! isset( $this->widget_dependency_cache[ $cache_key ] ) ) {
160 $dependencies = array(
161 'styles' => array(),
162 'scripts' => array(),
163 );
164
165 /*
166 * Elementor's registered widget objects are prototypes and may not
167 * contain initialized settings. Create a real instance from the saved
168 * Elementor element data before resolving conditional dependencies.
169 */
170 $element['elType'] = 'widget';
171 $element['settings'] = $settings;
172 $widget = \Elementor\Plugin::$instance->elements_manager->create_element_instance( $element );
173 if ( $widget instanceof \Elementor\Widget_Base ) {
174
175 $dependencies['styles'] = (array) $widget->get_style_depends();
176 $dependencies['scripts'] = (array) $widget->get_script_depends();
177 }
178
179 $this->widget_dependency_cache[ $cache_key ] = $dependencies;
180 }
181
182 foreach ( $this->widget_dependency_cache[ $cache_key ]['styles'] as $handle ) {
183 if ( ! empty( $handle ) ) {
184 wp_enqueue_style( $handle );
185 }
186 }
187
188 foreach ( $this->widget_dependency_cache[ $cache_key ]['scripts'] as $handle ) {
189 if ( ! empty( $handle ) ) {
190 wp_enqueue_script( $handle );
191 }
192 }
193 }
194
195 /**
196 * Get nav menu items for a menu, caching per menu so the same menu
197 * referenced multiple times in a tree is only fetched once.
198 * @since 4.0.3
199 * @param mixed $menu Menu ID, slug, or object.
200 * @return array
201 */
202 private function get_nav_menu_items( $menu ) {
203 $cache_key = is_scalar( $menu ) ? (string) $menu : md5( wp_json_encode( $menu ) );
204
205 if ( ! isset( $this->nav_menu_items_cache[ $cache_key ] ) ) {
206 $this->nav_menu_items_cache[ $cache_key ] = (array) wp_get_nav_menu_items( $menu );
207 }
208
209 return $this->nav_menu_items_cache[ $cache_key ];
210 }
211
212 /**
213 * Resolve saved Widget Area and Mega Menu documents linked by the element tree.
214 * @since 4.0.3
215 * @param array $keys Widget IDs and dynamic-content keys.
216 * @return int[]
217 */
218 private function get_widget_area_document_ids( $keys ) {
219 $keys = array_unique( array_filter( array_map( 'sanitize_key', $keys ) ) );
220 if ( empty( $keys ) ) {
221 return array();
222 }
223
224 $this->load_widget_area_documents();
225
226 $ids = array();
227
228 // Mega menu titles are exact matches, so this is an O(1) lookup per key
229 // instead of a linear scan over every candidate document.
230 foreach ( $keys as $key ) {
231 if ( isset( $this->mega_menu_documents[ 'dynamic-content-' . $key ] ) ) {
232 $ids[] = $this->mega_menu_documents[ 'dynamic-content-' . $key ];
233 }
234 }
235
236 // Widget area titles carry an arbitrary suffix after the key, so a prefix
237 // check is still required, but it now only scans the smaller, pre-filtered
238 // widget-area candidate list rather than every elementskit_content post.
239 foreach ( $this->widget_area_candidates as $candidate ) {
240 foreach ( $keys as $key ) {
241 if ( 0 === strpos( $candidate['suffix'], $key . '-' ) ) {
242 $ids[] = $candidate['id'];
243 break;
244 }
245 }
246 }
247
248 return array_values( array_unique( $ids ) );
249 }
250
251 /**
252 * Load and index elementskit_content documents once per request.
253 *
254 * Skips found-row counting and meta/term cache priming (unneeded for this
255 * lookup) and splits results into an exact-match hash map for mega menus
256 * and a smaller candidate list for widget-area prefix matching.
257 * @since 4.0.3
258 * @return void
259 */
260 private function load_widget_area_documents() {
261 if ( null !== $this->mega_menu_documents ) {
262 return;
263 }
264
265 $this->mega_menu_documents = array();
266 $this->widget_area_candidates = array();
267
268 $rows = get_posts(
269 array(
270 'post_type' => 'elementskit_content',
271 'post_status' => 'publish',
272 'posts_per_page' => -1,
273 'no_found_rows' => true,
274 'update_post_meta_cache' => false,
275 'update_post_term_cache' => false,
276 )
277 );
278
279 $widget_prefix = 'dynamic-content-widget-';
280 $widget_prefix_length = strlen( $widget_prefix );
281
282 foreach ( (array) $rows as $row ) {
283 $id = (int) $row->ID;
284 $title = (string) $row->post_title;
285
286 if ( 0 === strpos( $title, $widget_prefix ) ) {
287 $this->widget_area_candidates[] = array(
288 'suffix' => substr( $title, $widget_prefix_length ),
289 'id' => $id,
290 );
291 } else {
292 $this->mega_menu_documents[ $title ] = $id;
293 }
294 }
295 }
296 }
297