controls-stack.php (64600B)
1 <?php 2 namespace Elementor; 3 4 use Elementor\Core\Base\Base_Object; 5 use Elementor\Core\DynamicTags\Manager; 6 use Elementor\Core\Schemes\Manager as Schemes_Manager; 7 use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager; 8 9 if ( ! defined( 'ABSPATH' ) ) { 10 exit; // Exit if accessed directly. 11 } 12 13 /** 14 * Elementor controls stack. 15 * 16 * An abstract class that provides the needed properties and methods to 17 * manage and handle controls in the editor panel to inheriting classes. 18 * 19 * @since 1.4.0 20 * @abstract 21 */ 22 abstract class Controls_Stack extends Base_Object { 23 24 /** 25 * Responsive 'desktop' device name. 26 * 27 * @deprecated 3.4.0 28 */ 29 const RESPONSIVE_DESKTOP = 'desktop'; 30 31 /** 32 * Responsive 'tablet' device name. 33 * 34 * @deprecated 3.4.0 35 */ 36 const RESPONSIVE_TABLET = 'tablet'; 37 38 /** 39 * Responsive 'mobile' device name. 40 * 41 * @deprecated 3.4.0 42 */ 43 const RESPONSIVE_MOBILE = 'mobile'; 44 45 /** 46 * Generic ID. 47 * 48 * Holds the unique ID. 49 * 50 * @access private 51 * 52 * @var string 53 */ 54 private $id; 55 56 private $active_settings; 57 58 private $parsed_active_settings; 59 60 /** 61 * Parsed Dynamic Settings. 62 * 63 * @access private 64 * 65 * @var null|array 66 */ 67 private $parsed_dynamic_settings; 68 69 /** 70 * Raw Data. 71 * 72 * Holds all the raw data including the element type, the child elements, 73 * the user data. 74 * 75 * @access private 76 * 77 * @var null|array 78 */ 79 private $data; 80 81 /** 82 * The configuration. 83 * 84 * Holds the configuration used to generate the Elementor editor. It includes 85 * the element name, icon, categories, etc. 86 * 87 * @access private 88 * 89 * @var null|array 90 */ 91 private $config; 92 93 /** 94 * Current section. 95 * 96 * Holds the current section while inserting a set of controls sections. 97 * 98 * @access private 99 * 100 * @var null|array 101 */ 102 private $current_section; 103 104 /** 105 * Current tab. 106 * 107 * Holds the current tab while inserting a set of controls tabs. 108 * 109 * @access private 110 * 111 * @var null|array 112 */ 113 private $current_tab; 114 115 /** 116 * Current popover. 117 * 118 * Holds the current popover while inserting a set of controls. 119 * 120 * @access private 121 * 122 * @var null|array 123 */ 124 private $current_popover; 125 126 /** 127 * Injection point. 128 * 129 * Holds the injection point in the stack where the control will be inserted. 130 * 131 * @access private 132 * 133 * @var null|array 134 */ 135 private $injection_point; 136 137 138 /** 139 * Data sanitized. 140 * 141 * @access private 142 * 143 * @var bool 144 */ 145 private $settings_sanitized = false; 146 147 /** 148 * Element render attributes. 149 * 150 * Holds all the render attributes of the element. Used to store data like 151 * the HTML class name and the class value, or HTML element ID name and value. 152 * 153 * @access private 154 * 155 * @var array 156 */ 157 private $render_attributes = []; 158 159 /** 160 * Get element name. 161 * 162 * Retrieve the element name. 163 * 164 * @since 1.4.0 165 * @access public 166 * @abstract 167 * 168 * @return string The name. 169 */ 170 abstract public function get_name(); 171 172 /** 173 * Get unique name. 174 * 175 * Some classes need to use unique names, this method allows you to create 176 * them. By default it retrieves the regular name. 177 * 178 * @since 1.6.0 179 * @access public 180 * 181 * @return string Unique name. 182 */ 183 public function get_unique_name() { 184 return $this->get_name(); 185 } 186 187 /** 188 * Get element ID. 189 * 190 * Retrieve the element generic ID. 191 * 192 * @since 1.4.0 193 * @access public 194 * 195 * @return string The ID. 196 */ 197 public function get_id() { 198 return $this->id; 199 } 200 201 /** 202 * Get element ID. 203 * 204 * Retrieve the element generic ID as integer. 205 * 206 * @since 1.8.0 207 * @access public 208 * 209 * @return string The converted ID. 210 */ 211 public function get_id_int() { 212 /** We ignore possible notices, in order to support elements created prior to v1.8.0 and might include 213 * non-base 16 characters as part of their ID. 214 */ 215 return @hexdec( $this->id ); 216 } 217 218 /** 219 * Get the type. 220 * 221 * Retrieve the type, e.g. 'stack', 'section', 'widget' etc. 222 * 223 * @since 1.4.0 224 * @access public 225 * @static 226 * 227 * @return string The type. 228 */ 229 public static function get_type() { 230 return 'stack'; 231 } 232 233 /** 234 * @since 2.9.0 235 * @access public 236 * 237 * @return bool 238 */ 239 public function is_editable() { 240 return true; 241 } 242 243 /** 244 * Get current section. 245 * 246 * When inserting new controls, this method will retrieve the current section. 247 * 248 * @since 1.7.1 249 * @access public 250 * 251 * @return null|array Current section. 252 */ 253 public function get_current_section() { 254 return $this->current_section; 255 } 256 257 /** 258 * Get current tab. 259 * 260 * When inserting new controls, this method will retrieve the current tab. 261 * 262 * @since 1.7.1 263 * @access public 264 * 265 * @return null|array Current tab. 266 */ 267 public function get_current_tab() { 268 return $this->current_tab; 269 } 270 271 /** 272 * Get controls. 273 * 274 * Retrieve all the controls or, when requested, a specific control. 275 * 276 * @since 1.4.0 277 * @access public 278 * 279 * @param string $control_id The ID of the requested control. Optional field, 280 * when set it will return a specific control. 281 * Default is null. 282 * 283 * @return mixed Controls list. 284 */ 285 public function get_controls( $control_id = null ) { 286 return self::get_items( $this->get_stack()['controls'], $control_id ); 287 } 288 289 /** 290 * Get active controls. 291 * 292 * Retrieve an array of active controls that meet the condition field. 293 * 294 * If specific controls was given as a parameter, retrieve active controls 295 * from that list, otherwise check for all the controls available. 296 * 297 * @since 1.4.0 298 * @since 2.0.9 Added the `controls` and the `settings` parameters. 299 * @access public 300 * @deprecated 3.0.0 301 * 302 * @param array $controls Optional. An array of controls. Default is null. 303 * @param array $settings Optional. Controls settings. Default is null. 304 * 305 * @return array Active controls. 306 */ 307 public function get_active_controls( array $controls = null, array $settings = null ) { 308 // _deprecated_function( __METHOD__, '3.0.0' ); 309 310 if ( ! $controls ) { 311 $controls = $this->get_controls(); 312 } 313 314 if ( ! $settings ) { 315 $settings = $this->get_controls_settings(); 316 } 317 318 $active_controls = array_reduce( 319 array_keys( $controls ), function( $active_controls, $control_key ) use ( $controls, $settings ) { 320 $control = $controls[ $control_key ]; 321 322 if ( $this->is_control_visible( $control, $settings ) ) { 323 $active_controls[ $control_key ] = $control; 324 } 325 326 return $active_controls; 327 }, [] 328 ); 329 330 return $active_controls; 331 } 332 333 /** 334 * Get controls settings. 335 * 336 * Retrieve the settings for all the controls that represent them. 337 * 338 * @since 1.5.0 339 * @access public 340 * 341 * @return array Controls settings. 342 */ 343 public function get_controls_settings() { 344 return array_intersect_key( $this->get_settings(), $this->get_controls() ); 345 } 346 347 /** 348 * Add new control to stack. 349 * 350 * Register a single control to allow the user to set/update data. 351 * 352 * This method should be used inside `register_controls()`. 353 * 354 * @since 1.4.0 355 * @access public 356 * 357 * @param string $id Control ID. 358 * @param array $args Control arguments. 359 * @param array $options Optional. Control options. Default is an empty array. 360 * 361 * @return bool True if control added, False otherwise. 362 */ 363 public function add_control( $id, array $args, $options = [] ) { 364 $default_options = [ 365 'overwrite' => false, 366 'position' => null, 367 ]; 368 369 if ( isset( $args['scheme'] ) ) { 370 $args['global'] = [ 371 'default' => Plugin::$instance->kits_manager->convert_scheme_to_global( $args['scheme'] ), 372 ]; 373 374 unset( $args['scheme'] ); 375 } 376 377 $options = array_merge( $default_options, $options ); 378 379 if ( $options['position'] ) { 380 $this->start_injection( $options['position'] ); 381 } 382 383 if ( $this->injection_point ) { 384 $options['index'] = $this->injection_point['index']++; 385 } 386 387 if ( empty( $args['type'] ) || ! in_array( $args['type'], [ Controls_Manager::SECTION, Controls_Manager::WP_WIDGET ], true ) ) { 388 $args = $this->handle_control_position( $args, $id, $options['overwrite'] ); 389 } 390 391 if ( $options['position'] ) { 392 $this->end_injection(); 393 } 394 395 unset( $options['position'] ); 396 397 if ( $this->current_popover && ! $this->current_popover['initialized'] ) { 398 $args['popover'] = [ 399 'start' => true, 400 ]; 401 402 $this->current_popover['initialized'] = true; 403 } 404 405 return Plugin::$instance->controls_manager->add_control_to_stack( $this, $id, $args, $options ); 406 } 407 408 /** 409 * Remove control from stack. 410 * 411 * Unregister an existing control and remove it from the stack. 412 * 413 * @since 1.4.0 414 * @access public 415 * 416 * @param string $control_id Control ID. 417 * 418 * @return bool|\WP_Error 419 */ 420 public function remove_control( $control_id ) { 421 return Plugin::$instance->controls_manager->remove_control_from_stack( $this->get_unique_name(), $control_id ); 422 } 423 424 /** 425 * Update control in stack. 426 * 427 * Change the value of an existing control in the stack. When you add new 428 * control you set the `$args` parameter, this method allows you to update 429 * the arguments by passing new data. 430 * 431 * @since 1.4.0 432 * @since 1.8.1 New `$options` parameter added. 433 * 434 * @access public 435 * 436 * @param string $control_id Control ID. 437 * @param array $args Control arguments. Only the new fields you want 438 * to update. 439 * @param array $options Optional. Some additional options. Default is 440 * an empty array. 441 * 442 * @return bool 443 */ 444 public function update_control( $control_id, array $args, array $options = [] ) { 445 $is_updated = Plugin::$instance->controls_manager->update_control_in_stack( $this, $control_id, $args, $options ); 446 447 if ( ! $is_updated ) { 448 return false; 449 } 450 451 $control = $this->get_controls( $control_id ); 452 453 if ( Controls_Manager::SECTION === $control['type'] ) { 454 $section_args = $this->get_section_args( $control_id ); 455 456 $section_controls = $this->get_section_controls( $control_id ); 457 458 foreach ( $section_controls as $section_control_id => $section_control ) { 459 $this->update_control( $section_control_id, $section_args, $options ); 460 } 461 } 462 463 return true; 464 } 465 466 /** 467 * Get stack. 468 * 469 * Retrieve the stack of controls. 470 * 471 * @since 1.9.2 472 * @access public 473 * 474 * @return array Stack of controls. 475 */ 476 public function get_stack() { 477 $stack = Plugin::$instance->controls_manager->get_element_stack( $this ); 478 479 if ( null === $stack ) { 480 $this->init_controls(); 481 482 return Plugin::$instance->controls_manager->get_element_stack( $this ); 483 } 484 485 return $stack; 486 } 487 488 /** 489 * Get position information. 490 * 491 * Retrieve the position while injecting data, based on the element type. 492 * 493 * @since 1.7.0 494 * @access public 495 * 496 * @param array $position { 497 * The injection position. 498 * 499 * @type string $type Injection type, either `control` or `section`. 500 * Default is `control`. 501 * @type string $at Where to inject. If `$type` is `control` accepts 502 * `before` and `after`. If `$type` is `section` 503 * accepts `start` and `end`. Default values based on 504 * the `type`. 505 * @type string $of Control/Section ID. 506 * @type array $fallback Fallback injection position. When the position is 507 * not found it will try to fetch the fallback 508 * position. 509 * } 510 * 511 * @return bool|array Position info. 512 */ 513 final public function get_position_info( array $position ) { 514 $default_position = [ 515 'type' => 'control', 516 'at' => 'after', 517 ]; 518 519 if ( ! empty( $position['type'] ) && 'section' === $position['type'] ) { 520 $default_position['at'] = 'end'; 521 } 522 523 $position = array_merge( $default_position, $position ); 524 525 if ( 526 'control' === $position['type'] && in_array( $position['at'], [ 'start', 'end' ], true ) || 527 'section' === $position['type'] && in_array( $position['at'], [ 'before', 'after' ], true ) 528 ) { 529 _doing_it_wrong( sprintf( '%s::%s', get_called_class(), __FUNCTION__ ), 'Invalid position arguments. Use `before` / `after` for control or `start` / `end` for section.', '1.7.0' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 530 531 return false; 532 } 533 534 $target_control_index = $this->get_control_index( $position['of'] ); 535 536 if ( false === $target_control_index ) { 537 if ( ! empty( $position['fallback'] ) ) { 538 return $this->get_position_info( $position['fallback'] ); 539 } 540 541 return false; 542 } 543 544 $target_section_index = $target_control_index; 545 546 $registered_controls = $this->get_controls(); 547 548 $controls_keys = array_keys( $registered_controls ); 549 550 while ( Controls_Manager::SECTION !== $registered_controls[ $controls_keys[ $target_section_index ] ]['type'] ) { 551 $target_section_index--; 552 } 553 554 if ( 'section' === $position['type'] ) { 555 $target_control_index++; 556 557 if ( 'end' === $position['at'] ) { 558 while ( Controls_Manager::SECTION !== $registered_controls[ $controls_keys[ $target_control_index ] ]['type'] ) { 559 if ( ++$target_control_index >= count( $registered_controls ) ) { 560 break; 561 } 562 } 563 } 564 } 565 566 $target_control = $registered_controls[ $controls_keys[ $target_control_index ] ]; 567 568 if ( 'after' === $position['at'] ) { 569 $target_control_index++; 570 } 571 572 $section_id = $registered_controls[ $controls_keys[ $target_section_index ] ]['name']; 573 574 $position_info = [ 575 'index' => $target_control_index, 576 'section' => $this->get_section_args( $section_id ), 577 ]; 578 579 if ( ! empty( $target_control['tabs_wrapper'] ) ) { 580 $position_info['tab'] = [ 581 'tabs_wrapper' => $target_control['tabs_wrapper'], 582 'inner_tab' => $target_control['inner_tab'], 583 ]; 584 } 585 586 return $position_info; 587 } 588 589 /** 590 * Get control key. 591 * 592 * Retrieve the key of the control based on a given index of the control. 593 * 594 * @since 1.9.2 595 * @access public 596 * 597 * @param string $control_index Control index. 598 * 599 * @return int Control key. 600 */ 601 final public function get_control_key( $control_index ) { 602 $registered_controls = $this->get_controls(); 603 604 $controls_keys = array_keys( $registered_controls ); 605 606 return $controls_keys[ $control_index ]; 607 } 608 609 /** 610 * Get control index. 611 * 612 * Retrieve the index of the control based on a given key of the control. 613 * 614 * @since 1.7.6 615 * @access public 616 * 617 * @param string $control_key Control key. 618 * 619 * @return false|int Control index. 620 */ 621 final public function get_control_index( $control_key ) { 622 $controls = $this->get_controls(); 623 624 $controls_keys = array_keys( $controls ); 625 626 return array_search( $control_key, $controls_keys ); 627 } 628 629 /** 630 * Get section controls. 631 * 632 * Retrieve all controls under a specific section. 633 * 634 * @since 1.7.6 635 * @access public 636 * 637 * @param string $section_id Section ID. 638 * 639 * @return array Section controls 640 */ 641 final public function get_section_controls( $section_id ) { 642 $section_index = $this->get_control_index( $section_id ); 643 644 $section_controls = []; 645 646 $registered_controls = $this->get_controls(); 647 648 $controls_keys = array_keys( $registered_controls ); 649 650 while ( true ) { 651 $section_index++; 652 653 if ( ! isset( $controls_keys[ $section_index ] ) ) { 654 break; 655 } 656 657 $control_key = $controls_keys[ $section_index ]; 658 659 if ( Controls_Manager::SECTION === $registered_controls[ $control_key ]['type'] ) { 660 break; 661 } 662 663 $section_controls[ $control_key ] = $registered_controls[ $control_key ]; 664 }; 665 666 return $section_controls; 667 } 668 669 /** 670 * Add new group control to stack. 671 * 672 * Register a set of related controls grouped together as a single unified 673 * control. For example grouping together like typography controls into a 674 * single, easy-to-use control. 675 * 676 * @since 1.4.0 677 * @access public 678 * 679 * @param string $group_name Group control name. 680 * @param array $args Group control arguments. Default is an empty array. 681 * @param array $options Optional. Group control options. Default is an 682 * empty array. 683 */ 684 final public function add_group_control( $group_name, array $args = [], array $options = [] ) { 685 $group = Plugin::$instance->controls_manager->get_control_groups( $group_name ); 686 687 if ( ! $group ) { 688 wp_die( sprintf( '%s::%s: Group "%s" not found.', get_called_class(), __FUNCTION__, $group_name ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 689 } 690 691 $group->add_controls( $this, $args, $options ); 692 } 693 694 /** 695 * Get scheme controls. 696 * 697 * Retrieve all the controls that use schemes. 698 * 699 * @since 1.4.0 700 * @access public 701 * @deprecated 3.0.0 702 * 703 * @return array Scheme controls. 704 */ 705 final public function get_scheme_controls() { 706 // _deprecated_function( __METHOD__, '3.0.0' ); 707 $enabled_schemes = Schemes_Manager::get_enabled_schemes(); 708 709 return array_filter( 710 $this->get_controls(), function( $control ) use ( $enabled_schemes ) { 711 return ( ! empty( $control['scheme'] ) && in_array( $control['scheme']['type'], $enabled_schemes ) ); 712 } 713 ); 714 } 715 716 /** 717 * Get style controls. 718 * 719 * Retrieve style controls for all active controls or, when requested, from 720 * a specific set of controls. 721 * 722 * @since 1.4.0 723 * @since 2.0.9 Added the `settings` parameter. 724 * @access public 725 * @deprecated 3.0.0 726 * 727 * @param array $controls Optional. Controls list. Default is null. 728 * @param array $settings Optional. Controls settings. Default is null. 729 * 730 * @return array Style controls. 731 */ 732 final public function get_style_controls( array $controls = null, array $settings = null ) { 733 // _deprecated_function( __METHOD__, '3.0.0' ); 734 735 $controls = $this->get_active_controls( $controls, $settings ); 736 737 $style_controls = []; 738 739 foreach ( $controls as $control_name => $control ) { 740 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] ); 741 742 if ( ! $control_obj instanceof Base_Data_Control ) { 743 continue; 744 } 745 746 $control = array_merge( $control_obj->get_settings(), $control ); 747 748 if ( $control_obj instanceof Control_Repeater ) { 749 $style_fields = []; 750 751 foreach ( $this->get_settings( $control_name ) as $item ) { 752 $style_fields[] = $this->get_style_controls( $control['fields'], $item ); 753 } 754 755 $control['style_fields'] = $style_fields; 756 } 757 758 if ( ! empty( $control['selectors'] ) || ! empty( $control['dynamic'] ) || ! empty( $control['style_fields'] ) ) { 759 $style_controls[ $control_name ] = $control; 760 } 761 } 762 763 return $style_controls; 764 } 765 766 /** 767 * Get tabs controls. 768 * 769 * Retrieve all the tabs assigned to the control. 770 * 771 * @since 1.4.0 772 * @access public 773 * 774 * @return array Tabs controls. 775 */ 776 final public function get_tabs_controls() { 777 return $this->get_stack()['tabs']; 778 } 779 780 /** 781 * Add new responsive control to stack. 782 * 783 * Register a set of controls to allow editing based on user screen size. 784 * This method registers one or more controls per screen size/device, depending on the current Responsive Control 785 * Duplication Mode. There are 3 control duplication modes: 786 * * 'off' - Only a single control is generated. In the Editor, this control is duplicated in JS. 787 * * 'on' - Multiple controls are generated, one control per enabled device/breakpoint + a default/desktop control. 788 * * 'dynamic' - If the control includes the `'dynamic' => 'active' => true` property - the control is duplicated, 789 * once for each device/breakpoint + default/desktop. 790 * If the control doesn't include the `'dynamic' => 'active' => true` property - the control is not duplicated. 791 * 792 * @since 1.4.0 793 * @access public 794 * 795 * @param string $id Responsive control ID. 796 * @param array $args Responsive control arguments. 797 * @param array $options Optional. Responsive control options. Default is 798 * an empty array. 799 */ 800 final public function add_responsive_control( $id, array $args, $options = [] ) { 801 $args['responsive'] = []; 802 803 $active_breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints(); 804 805 $devices = Plugin::$instance->breakpoints->get_active_devices_list( [ 'reverse' => true ] ); 806 807 if ( isset( $args['devices'] ) ) { 808 $devices = array_intersect( $devices, $args['devices'] ); 809 810 $args['responsive']['devices'] = $devices; 811 812 unset( $args['devices'] ); 813 } 814 815 $responsive_duplication_mode = Plugin::$instance->breakpoints->get_responsive_control_duplication_mode(); 816 $additional_breakpoints_active = Plugin::$instance->experiments->is_feature_active( 'additional_custom_breakpoints' ); 817 $control_is_dynamic = ! empty( $args['dynamic']['active'] ); 818 $is_frontend_available = ! empty( $args['frontend_available'] ); 819 $has_prefix_class = ! empty( $args['prefix_class'] ); 820 821 // If the new responsive controls experiment is active, create only one control - duplicates per device will 822 // be created in JS in the Editor. 823 if ( 824 $additional_breakpoints_active 825 && ( 'off' === $responsive_duplication_mode || ( 'dynamic' === $responsive_duplication_mode && ! $control_is_dynamic ) ) 826 // Some responsive controls need responsive settings to be available to the widget handler, even when empty. 827 && ! $is_frontend_available 828 && ! $has_prefix_class 829 ) { 830 $args['is_responsive'] = true; 831 832 if ( ! empty( $options['overwrite'] ) ) { 833 $this->update_control( $id, $args, [ 834 'recursive' => ! empty( $options['recursive'] ), 835 ] ); 836 } else { 837 $this->add_control( $id, $args, $options ); 838 } 839 840 return; 841 } 842 843 if ( isset( $args['default'] ) ) { 844 $args['desktop_default'] = $args['default']; 845 846 unset( $args['default'] ); 847 } 848 849 foreach ( $devices as $device_name ) { 850 $control_args = $args; 851 852 // Set parent using the name from previous iteration. 853 $control_args['parent'] = isset( $control_name ) ? $control_name : null; 854 855 if ( isset( $control_args['device_args'] ) ) { 856 if ( ! empty( $control_args['device_args'][ $device_name ] ) ) { 857 $control_args = array_merge( $control_args, $control_args['device_args'][ $device_name ] ); 858 } 859 860 unset( $control_args['device_args'] ); 861 } 862 863 if ( ! empty( $args['prefix_class'] ) ) { 864 $device_to_replace = Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP === $device_name ? '' : '-' . $device_name; 865 866 $control_args['prefix_class'] = sprintf( $args['prefix_class'], $device_to_replace ); 867 } 868 869 $direction = 'max'; 870 871 if ( Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP !== $device_name ) { 872 $direction = $active_breakpoints[ $device_name ]->get_direction(); 873 } 874 875 $control_args['responsive'][ $direction ] = $device_name; 876 877 if ( isset( $control_args['min_affected_device'] ) ) { 878 if ( ! empty( $control_args['min_affected_device'][ $device_name ] ) ) { 879 $control_args['responsive']['min'] = $control_args['min_affected_device'][ $device_name ]; 880 } 881 882 unset( $control_args['min_affected_device'] ); 883 } 884 885 if ( isset( $control_args[ $device_name . '_default' ] ) ) { 886 $control_args['default'] = $control_args[ $device_name . '_default' ]; 887 } 888 889 foreach ( $devices as $device ) { 890 unset( $control_args[ $device . '_default' ] ); 891 } 892 893 $id_suffix = Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP === $device_name ? '' : '_' . $device_name; 894 $control_name = $id . $id_suffix; 895 896 // Set this control as child of previous iteration control. 897 $this->update_control( $control_args['parent'], [ 'inheritors' => [ $control_name ] ] ); 898 899 if ( ! empty( $options['overwrite'] ) ) { 900 $this->update_control( $control_name, $control_args, [ 901 'recursive' => ! empty( $options['recursive'] ), 902 ] ); 903 } else { 904 $this->add_control( $control_name, $control_args, $options ); 905 } 906 } 907 } 908 909 /** 910 * Update responsive control in stack. 911 * 912 * Change the value of an existing responsive control in the stack. When you 913 * add new control you set the `$args` parameter, this method allows you to 914 * update the arguments by passing new data. 915 * 916 * @since 1.4.0 917 * @access public 918 * 919 * @param string $id Responsive control ID. 920 * @param array $args Responsive control arguments. 921 * @param array $options Optional. Additional options. 922 */ 923 final public function update_responsive_control( $id, array $args, array $options = [] ) { 924 $this->add_responsive_control( $id, $args, [ 925 'overwrite' => true, 926 'recursive' => ! empty( $options['recursive'] ), 927 ] ); 928 } 929 930 /** 931 * Remove responsive control from stack. 932 * 933 * Unregister an existing responsive control and remove it from the stack. 934 * 935 * @since 1.4.0 936 * @access public 937 * 938 * @param string $id Responsive control ID. 939 */ 940 final public function remove_responsive_control( $id ) { 941 $devices = [ 942 Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP, 943 Breakpoints_Manager::BREAKPOINT_KEY_TABLET, 944 Breakpoints_Manager::BREAKPOINT_KEY_MOBILE, 945 ]; 946 947 foreach ( $devices as $device_name ) { 948 $id_suffix = Breakpoints_Manager::BREAKPOINT_KEY_DESKTOP === $device_name ? '' : '_' . $device_name; 949 950 $this->remove_control( $id . $id_suffix ); 951 } 952 } 953 954 /** 955 * Get class name. 956 * 957 * Retrieve the name of the current class. 958 * 959 * @since 1.4.0 960 * @access public 961 * 962 * @return string Class name. 963 */ 964 final public function get_class_name() { 965 return get_called_class(); 966 } 967 968 /** 969 * Get the config. 970 * 971 * Retrieve the config or, if non set, use the initial config. 972 * 973 * @since 1.4.0 974 * @access public 975 * 976 * @return array|null The config. 977 */ 978 final public function get_config() { 979 if ( null === $this->config ) { 980 // TODO: This is for backwards compatibility starting from 2.9.0 981 // This if statement should be removed when the method is hard-deprecated 982 if ( $this->has_own_method( '_get_initial_config', self::class ) ) { 983 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_get_initial_config', '2.9.0', __CLASS__ . '::get_initial_config()' ); 984 985 $this->config = $this->_get_initial_config(); 986 } else { 987 $this->config = $this->get_initial_config(); 988 } 989 } 990 991 return $this->config; 992 } 993 994 /** 995 * Get frontend settings keys. 996 * 997 * Retrieve settings keys for all frontend controls. 998 * 999 * @since 1.6.0 1000 * @access public 1001 * 1002 * @return array Settings keys for each control. 1003 */ 1004 final public function get_frontend_settings_keys() { 1005 $controls = []; 1006 1007 foreach ( $this->get_controls() as $control ) { 1008 if ( ! empty( $control['frontend_available'] ) ) { 1009 $controls[] = $control['name']; 1010 } 1011 } 1012 1013 return $controls; 1014 } 1015 1016 /** 1017 * Get controls pointer index. 1018 * 1019 * Retrieve pointer index where the next control should be added. 1020 * 1021 * While using injection point, it will return the injection point index. 1022 * Otherwise index of the last control plus one. 1023 * 1024 * @since 1.9.2 1025 * @access public 1026 * 1027 * @return int Controls pointer index. 1028 */ 1029 public function get_pointer_index() { 1030 if ( null !== $this->injection_point ) { 1031 return $this->injection_point['index']; 1032 } 1033 1034 return count( $this->get_controls() ); 1035 } 1036 1037 /** 1038 * Get the raw data. 1039 * 1040 * Retrieve all the items or, when requested, a specific item. 1041 * 1042 * @since 1.4.0 1043 * @access public 1044 * 1045 * @param string $item Optional. The requested item. Default is null. 1046 * 1047 * @return mixed The raw data. 1048 */ 1049 public function get_data( $item = null ) { 1050 if ( ! $this->settings_sanitized && ( ! $item || 'settings' === $item ) ) { 1051 $this->data['settings'] = $this->sanitize_settings( $this->data['settings'] ); 1052 1053 $this->settings_sanitized = true; 1054 } 1055 1056 return self::get_items( $this->data, $item ); 1057 } 1058 1059 /** 1060 * @since 2.0.14 1061 * @access public 1062 */ 1063 public function get_parsed_dynamic_settings( $setting = null, $settings = null ) { 1064 if ( null === $settings ) { 1065 $settings = $this->get_settings(); 1066 } 1067 1068 if ( null === $this->parsed_dynamic_settings ) { 1069 $this->parsed_dynamic_settings = $this->parse_dynamic_settings( $settings ); 1070 } 1071 1072 return self::get_items( $this->parsed_dynamic_settings, $setting ); 1073 } 1074 1075 /** 1076 * Get active settings. 1077 * 1078 * Retrieve the settings from all the active controls. 1079 * 1080 * @since 1.4.0 1081 * @since 2.1.0 Added the `controls` and the `settings` parameters. 1082 * @access public 1083 * 1084 * @param array $controls Optional. An array of controls. Default is null. 1085 * @param array $settings Optional. Controls settings. Default is null. 1086 * 1087 * @return array Active settings. 1088 */ 1089 public function get_active_settings( $settings = null, $controls = null ) { 1090 $is_first_request = ! $settings && ! $this->active_settings; 1091 1092 if ( ! $settings ) { 1093 if ( $this->active_settings ) { 1094 return $this->active_settings; 1095 } 1096 1097 $settings = $this->get_controls_settings(); 1098 1099 $controls = $this->get_controls(); 1100 } 1101 1102 $active_settings = []; 1103 1104 foreach ( $settings as $setting_key => $setting ) { 1105 if ( ! isset( $controls[ $setting_key ] ) ) { 1106 $active_settings[ $setting_key ] = $setting; 1107 1108 continue; 1109 } 1110 1111 $control = $controls[ $setting_key ]; 1112 1113 if ( $this->is_control_visible( $control, $settings ) ) { 1114 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] ); 1115 1116 if ( $control_obj instanceof Control_Repeater ) { 1117 foreach ( $setting as & $item ) { 1118 $item = $this->get_active_settings( $item, $control['fields'] ); 1119 } 1120 } 1121 1122 $active_settings[ $setting_key ] = $setting; 1123 } else { 1124 $active_settings[ $setting_key ] = null; 1125 } 1126 } 1127 1128 if ( $is_first_request ) { 1129 $this->active_settings = $active_settings; 1130 } 1131 1132 return $active_settings; 1133 } 1134 1135 /** 1136 * Get settings for display. 1137 * 1138 * Retrieve all the settings or, when requested, a specific setting for display. 1139 * 1140 * Unlike `get_settings()` method, this method retrieves only active settings 1141 * that passed all the conditions, rendered all the shortcodes and all the dynamic 1142 * tags. 1143 * 1144 * @since 2.0.0 1145 * @access public 1146 * 1147 * @param string $setting_key Optional. The key of the requested setting. 1148 * Default is null. 1149 * 1150 * @return mixed The settings. 1151 */ 1152 public function get_settings_for_display( $setting_key = null ) { 1153 if ( ! $this->parsed_active_settings ) { 1154 $this->parsed_active_settings = $this->get_active_settings( $this->get_parsed_dynamic_settings(), $this->get_controls() ); 1155 } 1156 1157 return self::get_items( $this->parsed_active_settings, $setting_key ); 1158 } 1159 1160 /** 1161 * Parse dynamic settings. 1162 * 1163 * Retrieve the settings with rendered dynamic tags. 1164 * 1165 * @since 2.0.0 1166 * @access public 1167 * 1168 * @param array $settings Optional. The requested setting. Default is null. 1169 * @param array $controls Optional. The controls array. Default is null. 1170 * @param array $all_settings Optional. All the settings. Default is null. 1171 * 1172 * @return array The settings with rendered dynamic tags. 1173 */ 1174 public function parse_dynamic_settings( $settings, $controls = null, $all_settings = null ) { 1175 if ( null === $all_settings ) { 1176 $all_settings = $this->get_settings(); 1177 } 1178 1179 if ( null === $controls ) { 1180 $controls = $this->get_controls(); 1181 } 1182 1183 foreach ( $controls as $control ) { 1184 $control_name = $control['name']; 1185 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] ); 1186 1187 if ( ! $control_obj instanceof Base_Data_Control ) { 1188 continue; 1189 } 1190 1191 if ( $control_obj instanceof Control_Repeater ) { 1192 if ( ! isset( $settings[ $control_name ] ) ) { 1193 continue; 1194 } 1195 1196 foreach ( $settings[ $control_name ] as & $field ) { 1197 $field = $this->parse_dynamic_settings( $field, $control['fields'], $field ); 1198 } 1199 1200 continue; 1201 } 1202 1203 $dynamic_settings = $control_obj->get_settings( 'dynamic' ); 1204 1205 if ( ! $dynamic_settings ) { 1206 $dynamic_settings = []; 1207 } 1208 1209 if ( ! empty( $control['dynamic'] ) ) { 1210 $dynamic_settings = array_merge( $dynamic_settings, $control['dynamic'] ); 1211 } 1212 1213 if ( empty( $dynamic_settings ) || ! isset( $all_settings[ Manager::DYNAMIC_SETTING_KEY ][ $control_name ] ) ) { 1214 continue; 1215 } 1216 1217 if ( ! empty( $dynamic_settings['active'] ) && ! empty( $all_settings[ Manager::DYNAMIC_SETTING_KEY ][ $control_name ] ) ) { 1218 $parsed_value = $control_obj->parse_tags( $all_settings[ Manager::DYNAMIC_SETTING_KEY ][ $control_name ], $dynamic_settings ); 1219 1220 $dynamic_property = ! empty( $dynamic_settings['property'] ) ? $dynamic_settings['property'] : null; 1221 1222 if ( $dynamic_property ) { 1223 $settings[ $control_name ][ $dynamic_property ] = $parsed_value; 1224 } else { 1225 $settings[ $control_name ] = $parsed_value; 1226 } 1227 } 1228 } 1229 1230 return $settings; 1231 } 1232 1233 /** 1234 * Get frontend settings. 1235 * 1236 * Retrieve the settings for all frontend controls. 1237 * 1238 * @since 1.6.0 1239 * @access public 1240 * 1241 * @return array Frontend settings. 1242 */ 1243 public function get_frontend_settings() { 1244 $frontend_settings = array_intersect_key( $this->get_settings_for_display(), array_flip( $this->get_frontend_settings_keys() ) ); 1245 1246 foreach ( $frontend_settings as $key => $setting ) { 1247 if ( in_array( $setting, [ null, '' ], true ) ) { 1248 unset( $frontend_settings[ $key ] ); 1249 } 1250 } 1251 1252 return $frontend_settings; 1253 } 1254 1255 /** 1256 * Filter controls settings. 1257 * 1258 * Receives controls, settings and a callback function to filter the settings by 1259 * and returns filtered settings. 1260 * 1261 * @since 1.5.0 1262 * @access public 1263 * 1264 * @param callable $callback The callback function. 1265 * @param array $settings Optional. Control settings. Default is an empty 1266 * array. 1267 * @param array $controls Optional. Controls list. Default is an empty 1268 * array. 1269 * 1270 * @return array Filtered settings. 1271 */ 1272 public function filter_controls_settings( callable $callback, array $settings = [], array $controls = [] ) { 1273 if ( ! $settings ) { 1274 $settings = $this->get_settings(); 1275 } 1276 1277 if ( ! $controls ) { 1278 $controls = $this->get_controls(); 1279 } 1280 1281 return array_reduce( 1282 array_keys( $settings ), function( $filtered_settings, $setting_key ) use ( $controls, $settings, $callback ) { 1283 if ( isset( $controls[ $setting_key ] ) ) { 1284 $result = $callback( $settings[ $setting_key ], $controls[ $setting_key ] ); 1285 1286 if ( null !== $result ) { 1287 $filtered_settings[ $setting_key ] = $result; 1288 } 1289 } 1290 1291 return $filtered_settings; 1292 }, [] 1293 ); 1294 } 1295 1296 /** 1297 * Whether the control is visible or not. 1298 * 1299 * Used to determine whether the control is visible or not. 1300 * 1301 * @since 1.4.0 1302 * @access public 1303 * 1304 * @param array $control The control. 1305 * @param array $values Optional. Condition values. Default is null. 1306 * 1307 * @return bool Whether the control is visible. 1308 */ 1309 public function is_control_visible( $control, $values = null ) { 1310 if ( null === $values ) { 1311 $values = $this->get_settings(); 1312 } 1313 1314 if ( ! empty( $control['conditions'] ) && ! Conditions::check( $control['conditions'], $values ) ) { 1315 return false; 1316 } 1317 1318 if ( empty( $control['condition'] ) ) { 1319 return true; 1320 } 1321 1322 foreach ( $control['condition'] as $condition_key => $condition_value ) { 1323 preg_match( '/([a-z_\-0-9]+)(?:\[([a-z_]+)])?(!?)$/i', $condition_key, $condition_key_parts ); 1324 1325 $pure_condition_key = $condition_key_parts[1]; 1326 $condition_sub_key = $condition_key_parts[2]; 1327 $is_negative_condition = ! ! $condition_key_parts[3]; 1328 1329 if ( ! isset( $values[ $pure_condition_key ] ) || null === $values[ $pure_condition_key ] ) { 1330 return false; 1331 } 1332 1333 $instance_value = $values[ $pure_condition_key ]; 1334 1335 if ( $condition_sub_key && is_array( $instance_value ) ) { 1336 if ( ! isset( $instance_value[ $condition_sub_key ] ) ) { 1337 return false; 1338 } 1339 1340 $instance_value = $instance_value[ $condition_sub_key ]; 1341 } 1342 1343 /** 1344 * If the $condition_value is a non empty array - check if the $condition_value contains the $instance_value, 1345 * If the $instance_value is a non empty array - check if the $instance_value contains the $condition_value 1346 * otherwise check if they are equal. ( and give the ability to check if the value is an empty array ) 1347 */ 1348 if ( is_array( $condition_value ) && ! empty( $condition_value ) ) { 1349 $is_contains = in_array( $instance_value, $condition_value, true ); 1350 } elseif ( is_array( $instance_value ) && ! empty( $instance_value ) ) { 1351 $is_contains = in_array( $condition_value, $instance_value, true ); 1352 } else { 1353 $is_contains = $instance_value === $condition_value; 1354 } 1355 1356 if ( $is_negative_condition && $is_contains || ! $is_negative_condition && ! $is_contains ) { 1357 return false; 1358 } 1359 } 1360 1361 return true; 1362 } 1363 1364 /** 1365 * Start controls section. 1366 * 1367 * Used to add a new section of controls. When you use this method, all the 1368 * registered controls from this point will be assigned to this section, 1369 * until you close the section using `end_controls_section()` method. 1370 * 1371 * This method should be used inside `register_controls()`. 1372 * 1373 * @since 1.4.0 1374 * @access public 1375 * 1376 * @param string $section_id Section ID. 1377 * @param array $args Section arguments Optional. 1378 */ 1379 public function start_controls_section( $section_id, array $args = [] ) { 1380 $section_name = $this->get_name(); 1381 1382 /** 1383 * Before section start. 1384 * 1385 * Fires before Elementor section starts in the editor panel. 1386 * 1387 * @since 1.4.0 1388 * 1389 * @param Controls_Stack $this The control. 1390 * @param string $section_id Section ID. 1391 * @param array $args Section arguments. 1392 */ 1393 do_action( 'elementor/element/before_section_start', $this, $section_id, $args ); 1394 1395 /** 1396 * Before section start. 1397 * 1398 * Fires before Elementor section starts in the editor panel. 1399 * 1400 * The dynamic portions of the hook name, `$section_name` and `$section_id`, refers to the section name and section ID, respectively. 1401 * 1402 * @since 1.4.0 1403 * 1404 * @param Controls_Stack $this The control. 1405 * @param array $args Section arguments. 1406 */ 1407 do_action( "elementor/element/{$section_name}/{$section_id}/before_section_start", $this, $args ); 1408 1409 $args['type'] = Controls_Manager::SECTION; 1410 1411 $this->add_control( $section_id, $args ); 1412 1413 if ( null !== $this->current_section ) { 1414 wp_die( sprintf( 'Elementor: You can\'t start a section before the end of the previous section "%s".', $this->current_section['section'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 1415 } 1416 1417 $this->current_section = $this->get_section_args( $section_id ); 1418 1419 if ( $this->injection_point ) { 1420 $this->injection_point['section'] = $this->current_section; 1421 } 1422 1423 /** 1424 * After section start. 1425 * 1426 * Fires after Elementor section starts in the editor panel. 1427 * 1428 * @since 1.4.0 1429 * 1430 * @param Controls_Stack $this The control. 1431 * @param string $section_id Section ID. 1432 * @param array $args Section arguments. 1433 */ 1434 do_action( 'elementor/element/after_section_start', $this, $section_id, $args ); 1435 1436 /** 1437 * After section start. 1438 * 1439 * Fires after Elementor section starts in the editor panel. 1440 * 1441 * The dynamic portions of the hook name, `$section_name` and `$section_id`, refers to the section name and section ID, respectively. 1442 * 1443 * @since 1.4.0 1444 * 1445 * @param Controls_Stack $this The control. 1446 * @param array $args Section arguments. 1447 */ 1448 do_action( "elementor/element/{$section_name}/{$section_id}/after_section_start", $this, $args ); 1449 } 1450 1451 /** 1452 * End controls section. 1453 * 1454 * Used to close an existing open controls section. When you use this method 1455 * it stops adding new controls to this section. 1456 * 1457 * This method should be used inside `register_controls()`. 1458 * 1459 * @since 1.4.0 1460 * @access public 1461 */ 1462 public function end_controls_section() { 1463 $stack_name = $this->get_name(); 1464 1465 // Save the current section for the action. 1466 $current_section = $this->current_section; 1467 $section_id = $current_section['section']; 1468 $args = [ 1469 'tab' => $current_section['tab'], 1470 ]; 1471 1472 /** 1473 * Before section end. 1474 * 1475 * Fires before Elementor section ends in the editor panel. 1476 * 1477 * @since 1.4.0 1478 * 1479 * @param Controls_Stack $this The control. 1480 * @param string $section_id Section ID. 1481 * @param array $args Section arguments. 1482 */ 1483 do_action( 'elementor/element/before_section_end', $this, $section_id, $args ); 1484 1485 /** 1486 * Before section end. 1487 * 1488 * Fires before Elementor section ends in the editor panel. 1489 * 1490 * The dynamic portions of the hook name, `$stack_name` and `$section_id`, refers to the stack name and section ID, respectively. 1491 * 1492 * @since 1.4.0 1493 * 1494 * @param Controls_Stack $this The control. 1495 * @param array $args Section arguments. 1496 */ 1497 do_action( "elementor/element/{$stack_name}/{$section_id}/before_section_end", $this, $args ); 1498 1499 $this->current_section = null; 1500 1501 /** 1502 * After section end. 1503 * 1504 * Fires after Elementor section ends in the editor panel. 1505 * 1506 * @since 1.4.0 1507 * 1508 * @param Controls_Stack $this The control. 1509 * @param string $section_id Section ID. 1510 * @param array $args Section arguments. 1511 */ 1512 do_action( 'elementor/element/after_section_end', $this, $section_id, $args ); 1513 1514 /** 1515 * After section end. 1516 * 1517 * Fires after Elementor section ends in the editor panel. 1518 * 1519 * The dynamic portions of the hook name, `$stack_name` and `$section_id`, refers to the section name and section ID, respectively. 1520 * 1521 * @since 1.4.0 1522 * 1523 * @param Controls_Stack $this The control. 1524 * @param array $args Section arguments. 1525 */ 1526 do_action( "elementor/element/{$stack_name}/{$section_id}/after_section_end", $this, $args ); 1527 } 1528 1529 /** 1530 * Start controls tabs. 1531 * 1532 * Used to add a new set of tabs inside a section. You should use this 1533 * method before adding new individual tabs using `start_controls_tab()`. 1534 * Each tab added after this point will be assigned to this group of tabs, 1535 * until you close it using `end_controls_tabs()` method. 1536 * 1537 * This method should be used inside `register_controls()`. 1538 * 1539 * @since 1.4.0 1540 * @access public 1541 * 1542 * @param string $tabs_id Tabs ID. 1543 * @param array $args Tabs arguments. 1544 */ 1545 public function start_controls_tabs( $tabs_id, array $args = [] ) { 1546 if ( null !== $this->current_tab ) { 1547 wp_die( sprintf( 'Elementor: You can\'t start tabs before the end of the previous tabs "%s".', $this->current_tab['tabs_wrapper'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 1548 } 1549 1550 $args['type'] = Controls_Manager::TABS; 1551 1552 $this->add_control( $tabs_id, $args ); 1553 1554 $this->current_tab = [ 1555 'tabs_wrapper' => $tabs_id, 1556 ]; 1557 1558 foreach ( [ 'condition', 'conditions' ] as $key ) { 1559 if ( ! empty( $args[ $key ] ) ) { 1560 $this->current_tab[ $key ] = $args[ $key ]; 1561 } 1562 } 1563 1564 if ( $this->injection_point ) { 1565 $this->injection_point['tab'] = $this->current_tab; 1566 } 1567 } 1568 1569 /** 1570 * End controls tabs. 1571 * 1572 * Used to close an existing open controls tabs. When you use this method it 1573 * stops adding new controls to this tabs. 1574 * 1575 * This method should be used inside `register_controls()`. 1576 * 1577 * @since 1.4.0 1578 * @access public 1579 */ 1580 public function end_controls_tabs() { 1581 $this->current_tab = null; 1582 } 1583 1584 /** 1585 * Start controls tab. 1586 * 1587 * Used to add a new tab inside a group of tabs. Use this method before 1588 * adding new individual tabs using `start_controls_tab()`. 1589 * Each tab added after this point will be assigned to this group of tabs, 1590 * until you close it using `end_controls_tab()` method. 1591 * 1592 * This method should be used inside `register_controls()`. 1593 * 1594 * @since 1.4.0 1595 * @access public 1596 * 1597 * @param string $tab_id Tab ID. 1598 * @param array $args Tab arguments. 1599 */ 1600 public function start_controls_tab( $tab_id, $args ) { 1601 if ( ! empty( $this->current_tab['inner_tab'] ) ) { 1602 wp_die( sprintf( 'Elementor: You can\'t start a tab before the end of the previous tab "%s".', $this->current_tab['inner_tab'] ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 1603 } 1604 1605 $args['type'] = Controls_Manager::TAB; 1606 $args['tabs_wrapper'] = $this->current_tab['tabs_wrapper']; 1607 1608 $this->add_control( $tab_id, $args ); 1609 1610 $this->current_tab['inner_tab'] = $tab_id; 1611 1612 if ( $this->injection_point ) { 1613 $this->injection_point['tab']['inner_tab'] = $this->current_tab['inner_tab']; 1614 } 1615 } 1616 1617 /** 1618 * End controls tab. 1619 * 1620 * Used to close an existing open controls tab. When you use this method it 1621 * stops adding new controls to this tab. 1622 * 1623 * This method should be used inside `register_controls()`. 1624 * 1625 * @since 1.4.0 1626 * @access public 1627 */ 1628 public function end_controls_tab() { 1629 unset( $this->current_tab['inner_tab'] ); 1630 } 1631 1632 /** 1633 * Start popover. 1634 * 1635 * Used to add a new set of controls in a popover. When you use this method, 1636 * all the registered controls from this point will be assigned to this 1637 * popover, until you close the popover using `end_popover()` method. 1638 * 1639 * This method should be used inside `register_controls()`. 1640 * 1641 * @since 1.9.0 1642 * @access public 1643 */ 1644 final public function start_popover() { 1645 $this->current_popover = [ 1646 'initialized' => false, 1647 ]; 1648 } 1649 1650 /** 1651 * End popover. 1652 * 1653 * Used to close an existing open popover. When you use this method it stops 1654 * adding new controls to this popover. 1655 * 1656 * This method should be used inside `register_controls()`. 1657 * 1658 * @since 1.9.0 1659 * @access public 1660 */ 1661 final public function end_popover() { 1662 $this->current_popover = null; 1663 1664 $last_control_key = $this->get_control_key( $this->get_pointer_index() - 1 ); 1665 1666 $args = [ 1667 'popover' => [ 1668 'end' => true, 1669 ], 1670 ]; 1671 1672 $options = [ 1673 'recursive' => true, 1674 ]; 1675 1676 $this->update_control( $last_control_key, $args, $options ); 1677 } 1678 1679 /** 1680 * Add render attribute. 1681 * 1682 * Used to add attributes to a specific HTML element. 1683 * 1684 * The HTML tag is represented by the element parameter, then you need to 1685 * define the attribute key and the attribute key. The final result will be: 1686 * `<element attribute_key="attribute_value">`. 1687 * 1688 * Example usage: 1689 * 1690 * `$this->add_render_attribute( 'wrapper', 'class', 'custom-widget-wrapper-class' );` 1691 * `$this->add_render_attribute( 'widget', 'id', 'custom-widget-id' );` 1692 * `$this->add_render_attribute( 'button', [ 'class' => 'custom-button-class', 'id' => 'custom-button-id' ] );` 1693 * 1694 * @since 1.0.0 1695 * @access public 1696 * 1697 * @param array|string $element The HTML element. 1698 * @param array|string $key Optional. Attribute key. Default is null. 1699 * @param array|string $value Optional. Attribute value. Default is null. 1700 * @param bool $overwrite Optional. Whether to overwrite existing 1701 * attribute. Default is false, not to overwrite. 1702 * 1703 * @return self Current instance of the element. 1704 */ 1705 public function add_render_attribute( $element, $key = null, $value = null, $overwrite = false ) { 1706 if ( is_array( $element ) ) { 1707 foreach ( $element as $element_key => $attributes ) { 1708 $this->add_render_attribute( $element_key, $attributes, null, $overwrite ); 1709 } 1710 1711 return $this; 1712 } 1713 1714 if ( is_array( $key ) ) { 1715 foreach ( $key as $attribute_key => $attributes ) { 1716 $this->add_render_attribute( $element, $attribute_key, $attributes, $overwrite ); 1717 } 1718 1719 return $this; 1720 } 1721 1722 if ( empty( $this->render_attributes[ $element ][ $key ] ) ) { 1723 $this->render_attributes[ $element ][ $key ] = []; 1724 } 1725 1726 settype( $value, 'array' ); 1727 1728 if ( $overwrite ) { 1729 $this->render_attributes[ $element ][ $key ] = $value; 1730 } else { 1731 $this->render_attributes[ $element ][ $key ] = array_merge( $this->render_attributes[ $element ][ $key ], $value ); 1732 } 1733 1734 return $this; 1735 } 1736 1737 /** 1738 * Get Render Attributes 1739 * 1740 * Used to retrieve render attribute. 1741 * 1742 * The returned array is either all elements and their attributes if no `$element` is specified, an array of all 1743 * attributes of a specific element or a specific attribute properties if `$key` is specified. 1744 * 1745 * Returns null if one of the requested parameters isn't set. 1746 * 1747 * @since 2.2.6 1748 * @access public 1749 * @param string $element 1750 * @param string $key 1751 * 1752 * @return array 1753 */ 1754 public function get_render_attributes( $element = '', $key = '' ) { 1755 $attributes = $this->render_attributes; 1756 1757 if ( $element ) { 1758 if ( ! isset( $attributes[ $element ] ) ) { 1759 return null; 1760 } 1761 1762 $attributes = $attributes[ $element ]; 1763 1764 if ( $key ) { 1765 if ( ! isset( $attributes[ $key ] ) ) { 1766 return null; 1767 } 1768 1769 $attributes = $attributes[ $key ]; 1770 } 1771 } 1772 1773 return $attributes; 1774 } 1775 1776 /** 1777 * Set render attribute. 1778 * 1779 * Used to set the value of the HTML element render attribute or to update 1780 * an existing render attribute. 1781 * 1782 * @since 1.0.0 1783 * @access public 1784 * 1785 * @param array|string $element The HTML element. 1786 * @param array|string $key Optional. Attribute key. Default is null. 1787 * @param array|string $value Optional. Attribute value. Default is null. 1788 * 1789 * @return self Current instance of the element. 1790 */ 1791 public function set_render_attribute( $element, $key = null, $value = null ) { 1792 return $this->add_render_attribute( $element, $key, $value, true ); 1793 } 1794 1795 /** 1796 * Remove render attribute. 1797 * 1798 * Used to remove an element (with its keys and their values), key (with its values), 1799 * or value/s from an HTML element's render attribute. 1800 * 1801 * @since 2.7.0 1802 * @access public 1803 * 1804 * @param string $element The HTML element. 1805 * @param string $key Optional. Attribute key. Default is null. 1806 * @param array|string $values Optional. Attribute value/s. Default is null. 1807 */ 1808 public function remove_render_attribute( $element, $key = null, $values = null ) { 1809 if ( $key && ! isset( $this->render_attributes[ $element ][ $key ] ) ) { 1810 return; 1811 } 1812 1813 if ( $values ) { 1814 $values = (array) $values; 1815 1816 $this->render_attributes[ $element ][ $key ] = array_diff( $this->render_attributes[ $element ][ $key ], $values ); 1817 1818 return; 1819 } 1820 1821 if ( $key ) { 1822 unset( $this->render_attributes[ $element ][ $key ] ); 1823 1824 return; 1825 } 1826 1827 if ( isset( $this->render_attributes[ $element ] ) ) { 1828 unset( $this->render_attributes[ $element ] ); 1829 } 1830 } 1831 1832 /** 1833 * Get render attribute string. 1834 * 1835 * Used to retrieve the value of the render attribute. 1836 * 1837 * @since 1.0.0 1838 * @access public 1839 * 1840 * @param string $element The element. 1841 * 1842 * @return string Render attribute string, or an empty string if the attribute 1843 * is empty or not exist. 1844 */ 1845 public function get_render_attribute_string( $element ) { 1846 if ( empty( $this->render_attributes[ $element ] ) ) { 1847 return ''; 1848 } 1849 1850 return Utils::render_html_attributes( $this->render_attributes[ $element ] ); 1851 } 1852 1853 /** 1854 * Print render attribute string. 1855 * 1856 * Used to output the rendered attribute. 1857 * 1858 * @since 2.0.0 1859 * @access public 1860 * 1861 * @param array|string $element The element. 1862 */ 1863 public function print_render_attribute_string( $element ) { 1864 echo $this->get_render_attribute_string( $element ); // XSS ok. 1865 } 1866 1867 /** 1868 * Print element template. 1869 * 1870 * Used to generate the element template on the editor. 1871 * 1872 * @since 2.0.0 1873 * @access public 1874 */ 1875 public function print_template() { 1876 ob_start(); 1877 1878 // TODO: This is for backwards compatibility starting from 2.9.0 1879 // This `if` statement should be removed when the method is removed 1880 if ( $this->has_own_method( '_content_template', self::class ) ) { 1881 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_content_template', '2.9.0', __CLASS__ . '::content_template()' ); 1882 1883 $this->_content_template(); 1884 } else { 1885 $this->content_template(); 1886 } 1887 1888 $template_content = ob_get_clean(); 1889 1890 $element_type = $this->get_type(); 1891 1892 /** 1893 * Template content. 1894 * 1895 * Filters the controls stack template content before it's printed in the editor. 1896 * 1897 * The dynamic portion of the hook name, `$element_type`, refers to the element type. 1898 * 1899 * @since 1.0.0 1900 * 1901 * @param string $content_template The controls stack template in the editor. 1902 * @param Controls_Stack $this The controls stack. 1903 */ 1904 $template_content = apply_filters( "elementor/{$element_type}/print_template", $template_content, $this ); 1905 1906 if ( empty( $template_content ) ) { 1907 return; 1908 } 1909 ?> 1910 <script type="text/html" id="tmpl-elementor-<?php echo esc_attr( $this->get_name() ); ?>-content"> 1911 <?php $this->print_template_content( $template_content ); ?> 1912 </script> 1913 <?php 1914 } 1915 1916 /** 1917 * Start injection. 1918 * 1919 * Used to inject controls and sections to a specific position in the stack. 1920 * 1921 * When you use this method, all the registered controls and sections will 1922 * be injected to a specific position in the stack, until you stop the 1923 * injection using `end_injection()` method. 1924 * 1925 * @since 1.7.1 1926 * @access public 1927 * 1928 * @param array $position { 1929 * The position where to start the injection. 1930 * 1931 * @type string $type Injection type, either `control` or `section`. 1932 * Default is `control`. 1933 * @type string $at Where to inject. If `$type` is `control` accepts 1934 * `before` and `after`. If `$type` is `section` 1935 * accepts `start` and `end`. Default values based on 1936 * the `type`. 1937 * @type string $of Control/Section ID. 1938 * } 1939 */ 1940 final public function start_injection( array $position ) { 1941 if ( $this->injection_point ) { 1942 wp_die( 'A controls injection is already opened. Please close current injection before starting a new one (use `end_injection`).' ); 1943 } 1944 1945 $this->injection_point = $this->get_position_info( $position ); 1946 } 1947 1948 /** 1949 * End injection. 1950 * 1951 * Used to close an existing opened injection point. 1952 * 1953 * When you use this method it stops adding new controls and sections to 1954 * this point and continue to add controls to the regular position in the 1955 * stack. 1956 * 1957 * @since 1.7.1 1958 * @access public 1959 */ 1960 final public function end_injection() { 1961 $this->injection_point = null; 1962 } 1963 1964 /** 1965 * Get injection point. 1966 * 1967 * Retrieve the injection point in the stack where new controls and sections 1968 * will be inserted. 1969 * 1970 * @since 1.9.2 1971 * @access public 1972 * 1973 * @return array|null An array when an injection point is defined, null 1974 * otherwise. 1975 */ 1976 final public function get_injection_point() { 1977 return $this->injection_point; 1978 } 1979 1980 /** 1981 * Register controls. 1982 * 1983 * Used to add new controls to any element type. For example, external 1984 * developers use this method to register controls in a widget. 1985 * 1986 * Should be inherited and register new controls using `add_control()`, 1987 * `add_responsive_control()` and `add_group_control()`, inside control 1988 * wrappers like `start_controls_section()`, `start_controls_tabs()` and 1989 * `start_controls_tab()`. 1990 * 1991 * @since 1.4.0 1992 * @access protected 1993 * @deprecated 3.1.0 Use `Controls_Stack::register_controls()` instead 1994 */ 1995 protected function _register_controls() { 1996 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0', __CLASS__ . '::register_controls()' ); 1997 1998 $this->register_controls(); 1999 } 2000 2001 /** 2002 * Register controls. 2003 * 2004 * Used to add new controls to any element type. For example, external 2005 * developers use this method to register controls in a widget. 2006 * 2007 * Should be inherited and register new controls using `add_control()`, 2008 * `add_responsive_control()` and `add_group_control()`, inside control 2009 * wrappers like `start_controls_section()`, `start_controls_tabs()` and 2010 * `start_controls_tab()`. 2011 * 2012 * @since 3.1.0 2013 * @access protected 2014 */ 2015 protected function register_controls() {} 2016 2017 /** 2018 * Get default data. 2019 * 2020 * Retrieve the default data. Used to reset the data on initialization. 2021 * 2022 * @since 1.4.0 2023 * @access protected 2024 * 2025 * @return array Default data. 2026 */ 2027 protected function get_default_data() { 2028 return [ 2029 'id' => 0, 2030 'settings' => [], 2031 ]; 2032 } 2033 2034 /** 2035 * @since 2.3.0 2036 * @access protected 2037 */ 2038 protected function get_init_settings() { 2039 $settings = $this->get_data( 'settings' ); 2040 2041 foreach ( $this->get_controls() as $control ) { 2042 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] ); 2043 2044 if ( ! $control_obj instanceof Base_Data_Control ) { 2045 continue; 2046 } 2047 2048 $control = array_merge_recursive( $control_obj->get_settings(), $control ); 2049 2050 $settings[ $control['name'] ] = $control_obj->get_value( $control, $settings ); 2051 } 2052 2053 return $settings; 2054 } 2055 2056 /** 2057 * Get initial config. 2058 * 2059 * Retrieve the current element initial configuration - controls list and 2060 * the tabs assigned to the control. 2061 * 2062 * @since 2.9.0 2063 * @access protected 2064 * 2065 * @return array The initial config. 2066 */ 2067 protected function get_initial_config() { 2068 return [ 2069 'controls' => $this->get_controls(), 2070 ]; 2071 } 2072 2073 /** 2074 * Get initial config. 2075 * 2076 * Retrieve the current element initial configuration - controls list and 2077 * the tabs assigned to the control. 2078 * 2079 * @since 1.4.0 2080 * @deprecated 2.9.0 use `get_initial_config()` instead 2081 * @access protected 2082 * 2083 * @return array The initial config. 2084 */ 2085 protected function _get_initial_config() { 2086 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '2.9.0', __CLASS__ . '::get_initial_config()' ); 2087 2088 return $this->get_initial_config(); 2089 } 2090 2091 /** 2092 * Get section arguments. 2093 * 2094 * Retrieve the section arguments based on section ID. 2095 * 2096 * @since 1.4.0 2097 * @access protected 2098 * 2099 * @param string $section_id Section ID. 2100 * 2101 * @return array Section arguments. 2102 */ 2103 protected function get_section_args( $section_id ) { 2104 $section_control = $this->get_controls( $section_id ); 2105 2106 $section_args_keys = [ 'tab', 'condition' ]; 2107 2108 $args = array_intersect_key( $section_control, array_flip( $section_args_keys ) ); 2109 2110 $args['section'] = $section_id; 2111 2112 return $args; 2113 } 2114 2115 /** 2116 * Render element. 2117 * 2118 * Generates the final HTML on the frontend. 2119 * 2120 * @since 2.0.0 2121 * @access protected 2122 */ 2123 protected function render() {} 2124 2125 /** 2126 * Render element in static mode. 2127 * 2128 * If not inherent will call the base render. 2129 */ 2130 protected function render_static() { 2131 $this->render(); 2132 } 2133 2134 /** 2135 * Determine the render logic. 2136 */ 2137 protected function render_by_mode() { 2138 if ( Plugin::$instance->frontend->is_static_render_mode() ) { 2139 $this->render_static(); 2140 2141 return; 2142 } 2143 2144 $this->render(); 2145 } 2146 2147 /** 2148 * Print content template. 2149 * 2150 * Used to generate the content template on the editor, using a 2151 * Backbone JavaScript template. 2152 * 2153 * @access protected 2154 * @since 2.0.0 2155 * 2156 * @param string $template_content Template content. 2157 */ 2158 protected function print_template_content( $template_content ) { 2159 echo $template_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 2160 } 2161 2162 /** 2163 * Render element output in the editor. 2164 * 2165 * Used to generate the live preview, using a Backbone JavaScript template. 2166 * 2167 * @since 2.9.0 2168 * @access protected 2169 */ 2170 protected function content_template() {} 2171 2172 /** 2173 * Render element output in the editor. 2174 * 2175 * Used to generate the live preview, using a Backbone JavaScript template. 2176 * 2177 * @since 2.0.0 2178 * @deprecated 2.9.0 use `content_template()` instead 2179 * @access protected 2180 */ 2181 protected function _content_template() { 2182 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '2.9.0', __CLASS__ . '::content_template()' ); 2183 2184 $this->content_template(); 2185 } 2186 2187 /** 2188 * Initialize controls. 2189 * 2190 * Register the all controls added by `register_controls()`. 2191 * 2192 * @since 2.0.0 2193 * @access protected 2194 */ 2195 protected function init_controls() { 2196 Plugin::$instance->controls_manager->open_stack( $this ); 2197 2198 // TODO: This is for backwards compatibility starting from 2.9.0 2199 // This `if` statement should be removed when the method is removed 2200 if ( $this->has_own_method( '_register_controls', self::class ) ) { 2201 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_register_controls', '3.1.0', __CLASS__ . '::register_controls()' ); 2202 2203 $this->_register_controls(); 2204 } else { 2205 $this->register_controls(); 2206 } 2207 } 2208 2209 protected function handle_control_position( array $args, $control_id, $overwrite ) { 2210 if ( isset( $args['type'] ) && in_array( $args['type'], [ Controls_Manager::SECTION, Controls_Manager::WP_WIDGET ], true ) ) { 2211 return $args; 2212 } 2213 2214 $target_section_args = $this->current_section; 2215 2216 $target_tab = $this->current_tab; 2217 2218 if ( $this->injection_point ) { 2219 $target_section_args = $this->injection_point['section']; 2220 2221 if ( ! empty( $this->injection_point['tab'] ) ) { 2222 $target_tab = $this->injection_point['tab']; 2223 } 2224 } 2225 2226 if ( null !== $target_section_args ) { 2227 if ( ! empty( $args['section'] ) || ! empty( $args['tab'] ) ) { 2228 _doing_it_wrong( sprintf( '%s::%s', get_called_class(), __FUNCTION__ ), sprintf( 'Cannot redeclare control with `tab` or `section` args inside section "%s".', $control_id ), '1.0.0' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 2229 } 2230 2231 $args = array_replace_recursive( $target_section_args, $args ); 2232 2233 if ( null !== $target_tab ) { 2234 $args = array_replace_recursive( $target_tab, $args ); 2235 } 2236 } elseif ( empty( $args['section'] ) && ( ! $overwrite || is_wp_error( Plugin::$instance->controls_manager->get_control_from_stack( $this->get_unique_name(), $control_id ) ) ) ) { 2237 wp_die( sprintf( '%s::%s: Cannot add a control outside of a section (use `start_controls_section`).', get_called_class(), __FUNCTION__ ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 2238 } 2239 2240 return $args; 2241 } 2242 2243 /** 2244 * Initialize the class. 2245 * 2246 * Set the raw data, the ID and the parsed settings. 2247 * 2248 * @since 2.9.0 2249 * @access protected 2250 * 2251 * @param array $data Initial data. 2252 */ 2253 protected function init( $data ) { 2254 $this->data = array_merge( $this->get_default_data(), $data ); 2255 2256 $this->id = $data['id']; 2257 } 2258 2259 /** 2260 * Initialize the class. 2261 * 2262 * Set the raw data, the ID and the parsed settings. 2263 * 2264 * @since 1.4.0 2265 * @deprecated 2.9.0 use `init()` instead 2266 * @access protected 2267 * 2268 * @param array $data Initial data. 2269 */ 2270 protected function _init( $data ) { 2271 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '2.9.0', __CLASS__ . '::init()' ); 2272 2273 $this->init( $data ); 2274 } 2275 2276 /** 2277 * Sanitize initial data. 2278 * 2279 * Performs settings cleaning and sanitization. 2280 * 2281 * @since 2.1.5 2282 * @access private 2283 * 2284 * @param array $settings Settings to sanitize. 2285 * @param array $controls Optional. An array of controls. Default is an 2286 * empty array. 2287 * 2288 * @return array Sanitized settings. 2289 */ 2290 private function sanitize_settings( array $settings, array $controls = [] ) { 2291 if ( ! $controls ) { 2292 $controls = $this->get_controls(); 2293 } 2294 2295 foreach ( $controls as $control ) { 2296 $control_obj = Plugin::$instance->controls_manager->get_control( $control['type'] ); 2297 2298 if ( $control_obj instanceof Control_Repeater ) { 2299 if ( empty( $settings[ $control['name'] ] ) ) { 2300 continue; 2301 } 2302 2303 foreach ( $settings[ $control['name'] ] as $index => $repeater_row_data ) { 2304 $sanitized_row_data = $this->sanitize_settings( $repeater_row_data, $control['fields'] ); 2305 2306 $settings[ $control['name'] ][ $index ] = $sanitized_row_data; 2307 } 2308 2309 continue; 2310 } 2311 2312 $is_dynamic = isset( $settings[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ); 2313 2314 if ( ! $is_dynamic ) { 2315 continue; 2316 } 2317 2318 $value_to_check = $settings[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ]; 2319 2320 $tag_text_data = Plugin::$instance->dynamic_tags->tag_text_to_tag_data( $value_to_check ); 2321 2322 if ( ! Plugin::$instance->dynamic_tags->get_tag_info( $tag_text_data['name'] ) ) { 2323 unset( $settings[ Manager::DYNAMIC_SETTING_KEY ][ $control['name'] ] ); 2324 } 2325 } 2326 2327 return $settings; 2328 } 2329 2330 /** 2331 * Controls stack constructor. 2332 * 2333 * Initializing the control stack class using `$data`. The `$data` is required 2334 * for a normal instance. It is optional only for internal `type instance`. 2335 * 2336 * @since 1.4.0 2337 * @access public 2338 * 2339 * @param array $data Optional. Control stack data. Default is an empty array. 2340 */ 2341 public function __construct( array $data = [] ) { 2342 if ( $data ) { 2343 // TODO: This is for backwards compatibility starting from 2.9.0 2344 // This if statement should be removed when the method is hard-deprecated 2345 if ( $this->has_own_method( '_init', self::class ) ) { 2346 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_init', '2.9.0', __CLASS__ . '::init()' ); 2347 2348 $this->_init( $data ); 2349 } else { 2350 $this->init( $data ); 2351 } 2352 } 2353 } 2354 }