PluginProbe
Elementor Website Builder – more than just a page builder / 4.3.0-beta3
Elementor Website Builder – more than just a page builder v4.3.0-beta3
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 4.1.0-beta1 4.1.0-dev1 All 452 releases
elementor / modules / editor-one / classes / menu-data-provider.php

menu-data-provider.php in Elementor Website Builder – more than just a page builder 4.3.0-beta3, at modules/editor-one/classes/menu-data-provider.php

625 lines 17.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\EditorOne\Classes;
4
5 use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Interface;
6 use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_Third_Level_Interface;
7 use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_With_Custom_Url_Interface;
8 use Elementor\Core\Admin\EditorOneMenu\Interfaces\Menu_Item_With_Event_Id_Interface;
9 use Elementor\Plugin;
10 use Elementor\Utils;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 class Menu_Data_Provider {
17 public const THIRD_LEVEL_EDITOR_FLYOUT = 'editor_flyout';
18 public const THIRD_LEVEL_FLYOUT_MENU = 'flyout_menu';
19
20 private static ?Menu_Data_Provider $instance = null;
21 private array $level3_items = [];
22 private array $level4_items = [];
23 private ?string $theme_builder_url = null;
24 private ?array $cached_level3_sidebar_data = null;
25 private ?array $cached_level4_sidebar_data = null;
26 private ?array $cached_flyout_menu_data = null;
27 private Slug_Normalizer $slug_normalizer;
28 private array $registration_options = [];
29
30 public static function instance(): self {
31 if ( null === self::$instance ) {
32 self::$instance = new self();
33 }
34
35 return self::$instance;
36 }
37
38 private function __construct() {
39 $this->slug_normalizer = new Slug_Normalizer();
40 }
41
42 public function get_slug_normalizer(): Slug_Normalizer {
43 return $this->slug_normalizer;
44 }
45
46 public function register_menu( Menu_Item_Interface $item, array $options = [] ): void {
47 if ( ! empty( $options['preserve_label_casing'] ) ) {
48 $this->registration_options[ $item->get_slug() ]['preserve_label_casing'] = true;
49 }
50
51 if ( ! ( $item instanceof Menu_Item_Third_Level_Interface ) ) {
52 $this->register_level4_item( $item );
53 return;
54 }
55
56 $group_id = $item->get_group_id();
57 $collapsible_groups = [
58 Menu_Config::TEMPLATES_GROUP_ID,
59 Menu_Config::CUSTOM_ELEMENTS_GROUP_ID,
60 Menu_Config::SYSTEM_GROUP_ID,
61 ];
62
63 if ( in_array( $group_id, $collapsible_groups, true ) && ! $item->has_children() ) {
64 $this->register_level4_item( $item );
65 } else {
66 $this->register_level3_item( $item );
67 }
68 }
69
70 public function register_level3_item( Menu_Item_Third_Level_Interface $item ): void {
71 $group_id = $item->get_group_id();
72 $item_slug = $item->get_slug();
73
74 if ( ! isset( $this->level3_items[ $group_id ] ) ) {
75 $this->level3_items[ $group_id ] = [];
76 }
77
78 $this->level3_items[ $group_id ][ $item_slug ] = $item;
79
80 $this->invalidate_cache();
81 }
82
83 public function register_level4_item( Menu_Item_Interface $item ): void {
84 $group_id = $item->get_group_id();
85 $item_slug = $item->get_slug();
86
87 if ( ! isset( $this->level4_items[ $group_id ] ) ) {
88 $this->level4_items[ $group_id ] = [];
89 }
90
91 $this->level4_items[ $group_id ][ $item_slug ] = $item;
92 $this->invalidate_cache();
93 }
94
95 public function get_level3_items(): array {
96 return $this->level3_items;
97 }
98
99 public function get_level4_items(): array {
100 return $this->level4_items;
101 }
102
103 public function is_item_already_registered( string $item_slug ): bool {
104 $all_items = array_merge( $this->level3_items, $this->level4_items );
105
106 foreach ( $all_items as $group_items ) {
107 if ( isset( $group_items[ $item_slug ] ) ) {
108 return true;
109 }
110 }
111
112 return false;
113 }
114
115 public function get_third_level_data( string $variant ): array {
116 if ( self::THIRD_LEVEL_EDITOR_FLYOUT === $variant ) {
117 return $this->get_third_level_data_from_cache(
118 $this->cached_level3_sidebar_data,
119 [ $this, 'build_level3_flyout_items' ]
120 );
121 }
122
123 if ( self::THIRD_LEVEL_FLYOUT_MENU === $variant ) {
124 return $this->get_third_level_data_from_cache(
125 $this->cached_flyout_menu_data,
126 [ $this, 'build_flyout_items_with_expanded_third_party' ]
127 );
128 }
129
130 return [];
131 }
132
133 public function get_level4_flyout_data(): array {
134 if ( null !== $this->cached_level4_sidebar_data ) {
135 return $this->cached_level4_sidebar_data;
136 }
137
138 $groups = $this->build_level4_flyout_groups();
139
140 foreach ( $groups as $group_id => $group ) {
141 if ( ! empty( $group['items'] ) ) {
142 $this->sort_items_by_priority( $groups[ $group_id ]['items'] );
143 }
144 }
145
146 $this->cached_level4_sidebar_data = $groups;
147
148 return $this->cached_level4_sidebar_data;
149 }
150
151 private function get_third_level_data_from_cache( ?array &$cache, callable $items_builder ): array {
152 if ( null !== $cache ) {
153 return $cache;
154 }
155
156 $items = $items_builder();
157
158 $this->sort_items_by_priority( $items );
159
160 $cache = [
161 'parent_slug' => Menu_Config::EDITOR_MENU_SLUG,
162 'items' => $items,
163 ];
164
165 return $cache;
166 }
167
168 public function get_theme_builder_url(): string {
169 if ( null === $this->theme_builder_url ) {
170 $pro_url = Plugin::$instance->app ? Plugin::$instance->app->get_settings( 'menu_url' ) : null;
171 $return_to = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) );
172
173 if ( $pro_url ) {
174 if ( false !== strpos( $pro_url, '#' ) ) {
175 $url = $this->add_return_to_url( $pro_url, $return_to );
176 } else {
177 $url = $pro_url;
178 }
179 } else {
180 $url = $this->add_return_to_url(
181 admin_url( 'admin.php?page=elementor-app' ) . '#/site-editor/promotion',
182 $return_to
183 );
184 }
185
186 $this->theme_builder_url = apply_filters( 'elementor/editor-one/menu/theme_builder_url', $url );
187 }
188
189 return $this->theme_builder_url;
190 }
191
192 private function add_return_to_url( string $url, string $return_to ): string {
193 $hash_position = strpos( $url, '#' );
194
195 if ( false === $hash_position ) {
196 return add_query_arg( [ 'return_to' => $return_to ], $url );
197 }
198
199 $base_url = substr( $url, 0, $hash_position );
200 $hash_fragment = substr( $url, $hash_position );
201
202 return add_query_arg( [ 'return_to' => $return_to ], $base_url ) . $hash_fragment;
203 }
204
205 public function get_all_sidebar_page_slugs(): array {
206 $base_slugs = [
207 Menu_Config::ELEMENTOR_MENU_SLUG,
208 Menu_Config::EDITOR_MENU_SLUG,
209 ];
210
211 $slugs = array_merge(
212 $base_slugs,
213 $this->get_dynamic_page_slugs()
214 );
215
216 return array_values( array_unique( $slugs ) );
217 }
218
219 public function is_elementor_editor_page(): bool {
220 if ( ! get_current_screen() ) {
221 return false;
222 }
223
224 $page = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ?? '';
225
226 if ( Menu_Config::ELEMENTOR_HOME_MENU_SLUG === $page ) {
227 return false;
228 }
229
230 if ( in_array( $page, $this->get_all_sidebar_page_slugs(), true ) ) {
231 return true;
232 }
233
234 $post_type = filter_input( INPUT_GET, 'post_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ?? '';
235
236 return $this->is_elementor_post_type( $post_type );
237 }
238
239 public function is_editor_one_post_edit_screen(): bool {
240 $screen = get_current_screen();
241
242 if ( ! $screen || empty( $screen->post_type ) ) {
243 return false;
244 }
245
246 if ( 'post' !== $screen->base ) {
247 return false;
248 }
249
250 $post_types = apply_filters( 'elementor/editor-one/admin-edit-post-types', [] );
251
252 return in_array( $screen->post_type, $post_types, true );
253 }
254
255 public function is_editor_one_admin_page(): bool {
256 return $this->is_elementor_editor_page() || $this->is_editor_one_post_edit_screen();
257 }
258
259 private function is_elementor_post_type( string $post_type ): bool {
260 if ( empty( $post_type ) ) {
261 return false;
262 }
263
264 return isset( Menu_Config::get_elementor_post_types()[ $post_type ] );
265 }
266
267 public static function get_elementor_post_types(): array {
268 return Menu_Config::get_elementor_post_types();
269 }
270
271 private function get_dynamic_page_slugs(): array {
272 $slugs = [];
273
274 foreach ( $this->level3_items as $group_items ) {
275 $slugs = array_merge( $slugs, array_keys( $group_items ) );
276 }
277
278 foreach ( $this->level4_items as $group_items ) {
279 foreach ( $group_items as $item_slug => $item ) {
280 $slugs[] = $item_slug;
281 }
282 }
283
284 $allowed_prefixes = [ 'elementor', 'e-', 'popup_templates' ];
285
286 return array_values( array_filter( $slugs, function( string $slug ) use ( $allowed_prefixes ): bool {
287 foreach ( $allowed_prefixes as $prefix ) {
288 if ( 0 === strpos( $slug, $prefix ) ) {
289 return true;
290 }
291 }
292
293 return false;
294 } ) );
295 }
296
297 private function build_level3_flyout_items(): array {
298 return $this->build_flyout_items( false, Menu_Config::get_excluded_level3_slugs() );
299 }
300
301 private function build_flyout_items_with_expanded_third_party(): array {
302 return $this->build_flyout_items(
303 true,
304 array_merge(
305 Menu_Config::get_excluded_level3_slugs(),
306 Menu_Config::get_excluded_flyout_menu_level3_slugs()
307 )
308 );
309 }
310
311 private function build_flyout_items( bool $expand_third_party, array $excluded_slugs ): array {
312 $items = [];
313 $existing_slugs = [];
314 $excluded_level4_slugs = $expand_third_party ? Menu_Config::get_excluded_level4_slugs() : [];
315
316 foreach ( $this->level3_items as $group_items ) {
317 foreach ( $group_items as $item_slug => $item ) {
318 if ( ! $this->should_include_flyout_item( $item, $item_slug, $existing_slugs, $excluded_slugs ) ) {
319 continue;
320 }
321
322 if ( $expand_third_party && $this->is_third_party_parent_with_children( $item ) ) {
323 $children = $this->level4_items[ Menu_Config::THIRD_PARTY_GROUP_ID ] ?? [];
324 $is_first_child = true;
325
326 foreach ( $children as $child_slug => $child ) {
327 if ( ! $this->is_item_accessible( $child ) ) {
328 continue;
329 }
330
331 if ( $this->is_slug_excluded( $child_slug, $excluded_level4_slugs, true ) ) {
332 continue;
333 }
334
335 if ( in_array( $child_slug, $existing_slugs, true ) ) {
336 continue;
337 }
338
339 $child_data = $this->create_expanded_child_item_data( $child, $child_slug, $is_first_child );
340 $items[] = $child_data;
341 $existing_slugs[] = $child_slug;
342 $is_first_child = false;
343 }
344 continue;
345 }
346
347 $items[] = $this->create_flyout_item_data( $item, $item_slug );
348 $existing_slugs[] = $item_slug;
349 }
350 }
351
352 return $items;
353 }
354
355 private function is_third_party_parent_with_children( Menu_Item_Interface $item ): bool {
356 if ( Menu_Config::THIRD_PARTY_GROUP_ID !== $item->get_group_id() ) {
357 return false;
358 }
359
360 if ( ! $item->has_children() ) {
361 return false;
362 }
363
364 $children = $this->level4_items[ Menu_Config::THIRD_PARTY_GROUP_ID ] ?? [];
365
366 return ! empty( $children );
367 }
368
369 private function create_expanded_child_item_data( Menu_Item_Interface $item, string $item_slug, bool $is_first ): array {
370 $url = $this->resolve_item_url( $item, $item_slug );
371
372 return [
373 'slug' => $item_slug,
374 'label' => $this->format_flyout_label( $item, $item_slug ),
375 'url' => $url,
376 'group_id' => '',
377 'priority' => $this->get_item_priority( $item ),
378 'has_divider_before' => $is_first,
379 'event_id' => $this->resolve_event_id( $item, $item_slug ),
380 ];
381 }
382
383 private function should_include_flyout_item( Menu_Item_Interface $item, string $item_slug, array $existing_slugs, array $excluded_slugs ): bool {
384 if ( ! $this->is_item_accessible( $item ) ) {
385 return false;
386 }
387
388 if ( in_array( $item_slug, $existing_slugs, true ) ) {
389 return false;
390 }
391
392 if ( $this->is_slug_excluded( $item_slug, $excluded_slugs ) ) {
393 return false;
394 }
395
396 if ( empty( trim( wp_strip_all_tags( $item->get_label() ) ) ) ) {
397 return false;
398 }
399
400 return true;
401 }
402
403 private function create_flyout_item_data( Menu_Item_Interface $item, string $item_slug ): array {
404 $has_children = $item->has_children();
405 $group_id = $has_children ? $item->get_group_id() : '';
406 $is_third_party_parent = Menu_Config::THIRD_PARTY_GROUP_ID === $item->get_group_id();
407
408 return [
409 'slug' => $item_slug,
410 'label' => $this->format_flyout_label( $item, $item_slug ),
411 'url' => $this->resolve_flyout_item_url( $item, $item_slug ),
412 'icon' => $item->get_icon(),
413 'group_id' => $group_id,
414 'priority' => $this->get_item_priority( $item ),
415 'has_divider_before' => $is_third_party_parent,
416 'event_id' => $this->resolve_event_id( $item, $item_slug ),
417 ];
418 }
419
420 private function resolve_flyout_item_url( Menu_Item_Interface $item, string $item_slug ): string {
421 $url = $this->resolve_item_url( $item, $item_slug );
422
423 if ( ! $item->has_children() ) {
424 return $url;
425 }
426
427 $children = $this->get_level4_items()[ $item->get_group_id() ] ?? [];
428
429 if ( empty( $children ) ) {
430 return $url;
431 }
432
433 $first_child_url = $this->get_first_accessible_child_url( $children );
434
435 return $first_child_url ?? $url;
436 }
437
438 private function get_first_accessible_child_url( array $children ): ?string {
439 $children_data = [];
440
441 foreach ( $children as $child_slug => $child_item ) {
442 if ( ! $this->is_item_accessible( $child_item ) ) {
443 continue;
444 }
445
446 $children_data[] = [
447 'url' => $this->resolve_item_url( $child_item, $child_slug ),
448 'priority' => $this->get_item_priority( $child_item ),
449 ];
450 }
451
452 if ( empty( $children_data ) ) {
453 return null;
454 }
455
456 $this->sort_items_by_priority( $children_data );
457
458 return $children_data[0]['url'];
459 }
460
461 private function build_level4_flyout_groups(): array {
462 $groups = [];
463 $excluded_slugs = Menu_Config::get_excluded_level4_slugs();
464
465 foreach ( $this->level4_items as $group_id => $items ) {
466 $groups[ $group_id ] = [ 'items' => [] ];
467 $existing_labels = [];
468
469 foreach ( $items as $item_slug => $item ) {
470 if ( ! $this->is_item_accessible( $item ) ) {
471 continue;
472 }
473
474 if ( $this->is_slug_excluded( $item_slug, $excluded_slugs, true ) ) {
475 continue;
476 }
477
478 $label = $item->get_label();
479 $label_lower = strtolower( $label );
480
481 if ( in_array( $label_lower, $existing_labels, true ) ) {
482 continue;
483 }
484
485 $url = $this->resolve_item_url( $item, $item_slug );
486
487 $groups[ $group_id ]['items'][] = [
488 'slug' => $item_slug,
489 'label' => $this->format_flyout_label( $item, $item_slug ),
490 'url' => $url,
491 'priority' => $this->get_item_priority( $item ),
492 'event_id' => $this->resolve_event_id( $item, $item_slug ),
493 ];
494
495 $existing_labels[] = $label_lower;
496 }
497 }
498
499 return $groups;
500 }
501
502 public function is_item_accessible( Menu_Item_Interface $item ): bool {
503 return $item->is_visible() && current_user_can( $item->get_capability() );
504 }
505
506 private function get_item_url( string $item_slug, ?string $parent_slug = null ): string {
507 $admin_path_prefixes = [ 'edit.php', 'post-new.php', 'admin.php' ];
508
509 foreach ( $admin_path_prefixes as $prefix ) {
510 if ( 0 === strpos( $item_slug, $prefix ) ) {
511 return admin_url( $item_slug );
512 }
513 }
514
515 if ( 0 === strpos( $item_slug, 'http' ) ) {
516 return $item_slug;
517 }
518
519 if ( $parent_slug && 0 === strpos( $parent_slug, 'edit.php' ) ) {
520 return admin_url( $parent_slug . '&page=' . $item_slug );
521 }
522
523 return admin_url( 'admin.php?page=' . $item_slug );
524 }
525
526 private function resolve_item_url( Menu_Item_Interface $item, string $item_slug ): string {
527 if ( $item instanceof Menu_Item_With_Custom_Url_Interface ) {
528 return $item->get_menu_url();
529 }
530
531 return $this->get_item_url( $item_slug, $item->get_parent_slug() );
532 }
533
534 private function get_item_priority( Menu_Item_Interface $item ): int {
535 return $item->get_position() ?? 100;
536 }
537
538 private function is_slug_excluded( string $item_slug, array $excluded_slugs, bool $use_normalizer = false ): bool {
539 if ( $use_normalizer ) {
540 return $this->slug_normalizer->is_excluded( $item_slug, $excluded_slugs );
541 }
542
543 return in_array( $item_slug, $excluded_slugs, true );
544 }
545
546 private function sort_items_by_priority( array &$items ): void {
547 usort( $items, function ( array $a, array $b ): int {
548 return ( $a['priority'] ?? 100 ) <=> ( $b['priority'] ?? 100 );
549 } );
550 }
551
552 private function resolve_event_id( Menu_Item_Interface $item, string $item_slug ): string {
553 if ( $item instanceof Menu_Item_With_Event_Id_Interface ) {
554 return $item->get_event_id();
555 }
556
557 return $this->derive_event_id_from_slug( $item_slug );
558 }
559
560 private function derive_event_id_from_slug( string $slug ): string {
561 $slug_event_map = [
562 'edit.php?post_type=elementor_library' => 'saved_templates',
563 'elementor-app' => 'theme_builder',
564 ];
565
566 if ( isset( $slug_event_map[ $slug ] ) ) {
567 return $slug_event_map[ $slug ];
568 }
569
570 $event_id = preg_replace( '/^elementor-/', '', $slug );
571 $event_id = preg_replace( '/[?#].*$/', '', $event_id );
572 $event_id = str_replace( [ '-', ' ' ], '_', $event_id );
573
574 return strtolower( $event_id );
575 }
576
577 private function format_flyout_label( Menu_Item_Interface $item, string $item_slug ): string {
578 $label = $item->get_label();
579
580 if ( ! empty( $this->registration_options[ $item_slug ]['preserve_label_casing'] ) ) {
581 return $label;
582 }
583
584 return $this->title_case( $label );
585 }
586
587 private function title_case( string $text ): string {
588 if ( function_exists( 'mb_convert_case' ) ) {
589 return mb_convert_case( $text, MB_CASE_TITLE, 'UTF-8' );
590 }
591
592 return ucwords( strtolower( $text ) );
593 }
594
595 private function invalidate_cache(): void {
596 $this->cached_level3_sidebar_data = null;
597 $this->cached_level4_sidebar_data = null;
598 $this->cached_flyout_menu_data = null;
599 }
600
601 public static function get_current_user_capabilities(): array {
602 $user = wp_get_current_user();
603
604 if ( ! $user || ! $user->exists() ) {
605 return [
606 'user' => null,
607 'has_edit_posts' => false,
608 'has_manage_options' => false,
609 'is_edit_posts_user' => false,
610 ];
611 }
612
613 $has_edit_posts = isset( $user->allcaps[ Menu_Config::CAPABILITY_EDIT_POSTS ] ) && $user->allcaps[ Menu_Config::CAPABILITY_EDIT_POSTS ];
614 $has_manage_options = isset( $user->allcaps[ Menu_Config::CAPABILITY_MANAGE_OPTIONS ] ) && $user->allcaps[ Menu_Config::CAPABILITY_MANAGE_OPTIONS ];
615 $is_edit_posts_user = $has_edit_posts && ! $has_manage_options;
616
617 return [
618 'user' => $user,
619 'has_edit_posts' => $has_edit_posts,
620 'has_manage_options' => $has_manage_options,
621 'is_edit_posts_user' => $is_edit_posts_user,
622 ];
623 }
624 }
625