element-base.php (20736B)
1 <?php 2 namespace Elementor; 3 4 if ( ! defined( 'ABSPATH' ) ) { 5 exit; // Exit if accessed directly. 6 } 7 8 /** 9 * Elementor element base. 10 * 11 * An abstract class to register new Elementor elements. It extended the 12 * `Controls_Stack` class to inherit its properties. 13 * 14 * This abstract class must be extended in order to register new elements. 15 * 16 * @since 1.0.0 17 * @abstract 18 */ 19 abstract class Element_Base extends Controls_Stack { 20 21 /** 22 * Child elements. 23 * 24 * Holds all the child elements of the element. 25 * 26 * @access private 27 * 28 * @var Element_Base[] 29 */ 30 private $children; 31 32 /** 33 * Element default arguments. 34 * 35 * Holds all the default arguments of the element. Used to store additional 36 * data. For example WordPress widgets use this to store widget names. 37 * 38 * @access private 39 * 40 * @var array 41 */ 42 private $default_args = []; 43 44 /** 45 * Is type instance. 46 * 47 * Whether the element is an instance of that type or not. 48 * 49 * @access private 50 * 51 * @var bool 52 */ 53 private $is_type_instance = true; 54 55 /** 56 * Depended scripts. 57 * 58 * Holds all the element depended scripts to enqueue. 59 * 60 * @since 1.9.0 61 * @access private 62 * 63 * @var array 64 */ 65 private $depended_scripts = []; 66 67 /** 68 * Depended styles. 69 * 70 * Holds all the element depended styles to enqueue. 71 * 72 * @since 1.9.0 73 * @access private 74 * 75 * @var array 76 */ 77 private $depended_styles = []; 78 79 /** 80 * Add script depends. 81 * 82 * Register new script to enqueue by the handler. 83 * 84 * @since 1.9.0 85 * @access public 86 * 87 * @param string $handler Depend script handler. 88 */ 89 public function add_script_depends( $handler ) { 90 $this->depended_scripts[] = $handler; 91 } 92 93 /** 94 * Add style depends. 95 * 96 * Register new style to enqueue by the handler. 97 * 98 * @since 1.9.0 99 * @access public 100 * 101 * @param string $handler Depend style handler. 102 */ 103 public function add_style_depends( $handler ) { 104 $this->depended_styles[] = $handler; 105 } 106 107 /** 108 * Get script dependencies. 109 * 110 * Retrieve the list of script dependencies the element requires. 111 * 112 * @since 1.3.0 113 * @access public 114 * 115 * @return array Element scripts dependencies. 116 */ 117 public function get_script_depends() { 118 return $this->depended_scripts; 119 } 120 121 /** 122 * Enqueue scripts. 123 * 124 * Registers all the scripts defined as element dependencies and enqueues 125 * them. Use `get_script_depends()` method to add custom script dependencies. 126 * 127 * @since 1.3.0 128 * @access public 129 */ 130 final public function enqueue_scripts() { 131 $deprecated_scripts = [ 132 'jquery-slick' => [ 133 'version' => '2.7.0', 134 'replacement' => 'Swiper', 135 ], 136 ]; 137 138 foreach ( $this->get_script_depends() as $script ) { 139 if ( isset( $deprecated_scripts[ $script ] ) ) { 140 Utils::handle_deprecation( $script, $deprecated_scripts[ $script ]['version'], $deprecated_scripts[ $script ]['replacement'] ); 141 } 142 143 wp_enqueue_script( $script ); 144 } 145 } 146 147 /** 148 * Get style dependencies. 149 * 150 * Retrieve the list of style dependencies the element requires. 151 * 152 * @since 1.9.0 153 * @access public 154 * 155 * @return array Element styles dependencies. 156 */ 157 public function get_style_depends() { 158 return $this->depended_styles; 159 } 160 161 /** 162 * Enqueue styles. 163 * 164 * Registers all the styles defined as element dependencies and enqueues 165 * them. Use `get_style_depends()` method to add custom style dependencies. 166 * 167 * @since 1.9.0 168 * @access public 169 */ 170 final public function enqueue_styles() { 171 foreach ( $this->get_style_depends() as $style ) { 172 wp_enqueue_style( $style ); 173 } 174 } 175 176 /** 177 * @since 1.0.0 178 * @deprecated 2.6.0 179 * @access public 180 * @static 181 */ 182 final public static function add_edit_tool() {} 183 184 /** 185 * @since 2.2.0 186 * @deprecated 2.6.0 187 * @access public 188 * @static 189 */ 190 final public static function is_edit_buttons_enabled() { 191 return get_option( 'elementor_edit_buttons' ); 192 } 193 194 /** 195 * Get default child type. 196 * 197 * Retrieve the default child type based on element data. 198 * 199 * Note that not all elements support children. 200 * 201 * @since 1.0.0 202 * @access protected 203 * @abstract 204 * 205 * @param array $element_data Element data. 206 * 207 * @return Element_Base 208 */ 209 abstract protected function _get_default_child_type( array $element_data ); 210 211 /** 212 * Before element rendering. 213 * 214 * Used to add stuff before the element. 215 * 216 * @since 1.0.0 217 * @access public 218 */ 219 public function before_render() {} 220 221 /** 222 * After element rendering. 223 * 224 * Used to add stuff after the element. 225 * 226 * @since 1.0.0 227 * @access public 228 */ 229 public function after_render() {} 230 231 /** 232 * Get element title. 233 * 234 * Retrieve the element title. 235 * 236 * @since 1.0.0 237 * @access public 238 * 239 * @return string Element title. 240 */ 241 public function get_title() { 242 return ''; 243 } 244 245 /** 246 * Get element icon. 247 * 248 * Retrieve the element icon. 249 * 250 * @since 1.0.0 251 * @access public 252 * 253 * @return string Element icon. 254 */ 255 public function get_icon() { 256 return 'eicon-columns'; 257 } 258 259 public function get_help_url() { 260 return 'https://go.elementor.com/widget-' . $this->get_name(); 261 } 262 263 public function get_custom_help_url() { 264 return ''; 265 } 266 267 /** 268 * Whether the reload preview is required. 269 * 270 * Used to determine whether the reload preview is required or not. 271 * 272 * @since 1.0.0 273 * @access public 274 * 275 * @return bool Whether the reload preview is required. 276 */ 277 public function is_reload_preview_required() { 278 return false; 279 } 280 281 /** 282 * @since 2.3.1 283 * @access protected 284 */ 285 protected function should_print_empty() { 286 return true; 287 } 288 289 /** 290 * Get child elements. 291 * 292 * Retrieve all the child elements of this element. 293 * 294 * @since 1.0.0 295 * @access public 296 * 297 * @return Element_Base[] Child elements. 298 */ 299 public function get_children() { 300 if ( null === $this->children ) { 301 $this->init_children(); 302 } 303 304 return $this->children; 305 } 306 307 /** 308 * Get default arguments. 309 * 310 * Retrieve the element default arguments. Used to return all the default 311 * arguments or a specific default argument, if one is set. 312 * 313 * @since 1.0.0 314 * @access public 315 * 316 * @param array $item Optional. Default is null. 317 * 318 * @return array Default argument(s). 319 */ 320 public function get_default_args( $item = null ) { 321 return self::get_items( $this->default_args, $item ); 322 } 323 324 /** 325 * Add new child element. 326 * 327 * Register new child element to allow hierarchy. 328 * 329 * @since 1.0.0 330 * @access public 331 * @param array $child_data Child element data. 332 * @param array $child_args Child element arguments. 333 * 334 * @return Element_Base|false Child element instance, or false if failed. 335 */ 336 public function add_child( array $child_data, array $child_args = [] ) { 337 if ( null === $this->children ) { 338 $this->init_children(); 339 } 340 341 $child_type = $this->get_child_type( $child_data ); 342 343 if ( ! $child_type ) { 344 return false; 345 } 346 347 $child = Plugin::$instance->elements_manager->create_element_instance( $child_data, $child_args, $child_type ); 348 349 if ( $child ) { 350 $this->children[] = $child; 351 } 352 353 return $child; 354 } 355 356 /** 357 * Add link render attributes. 358 * 359 * Used to add link tag attributes to a specific HTML element. 360 * 361 * The HTML link tag is represented by the element parameter. The `url_control` parameter 362 * needs to be an array of link settings in the same format they are set by Elementor's URL control. 363 * 364 * Example usage: 365 * 366 * `$this->add_link_attributes( 'button', $settings['link'] );` 367 * 368 * @since 2.8.0 369 * @access public 370 * 371 * @param array|string $element The HTML element. 372 * @param array $url_control Array of link settings. 373 * @param bool $overwrite Optional. Whether to overwrite existing 374 * attribute. Default is false, not to overwrite. 375 * 376 * @return Element_Base Current instance of the element. 377 */ 378 379 public function add_link_attributes( $element, array $url_control, $overwrite = false ) { 380 $attributes = []; 381 382 if ( ! empty( $url_control['url'] ) ) { 383 $allowed_protocols = array_merge( wp_allowed_protocols(), [ 'skype', 'viber' ] ); 384 385 $attributes['href'] = esc_url( $url_control['url'], $allowed_protocols ); 386 } 387 388 if ( ! empty( $url_control['is_external'] ) ) { 389 $attributes['target'] = '_blank'; 390 } 391 392 if ( ! empty( $url_control['nofollow'] ) ) { 393 $attributes['rel'] = 'nofollow'; 394 } 395 396 if ( ! empty( $url_control['custom_attributes'] ) ) { 397 // Custom URL attributes should come as a string of comma-delimited key|value pairs 398 $attributes = array_merge( $attributes, Utils::parse_custom_attributes( $url_control['custom_attributes'] ) ); 399 } 400 401 if ( $attributes ) { 402 $this->add_render_attribute( $element, $attributes, $overwrite ); 403 } 404 405 return $this; 406 } 407 408 /** 409 * Print element. 410 * 411 * Used to generate the element final HTML on the frontend and the editor. 412 * 413 * @since 1.0.0 414 * @access public 415 */ 416 public function print_element() { 417 $element_type = $this->get_type(); 418 419 /** 420 * Before frontend element render. 421 * 422 * Fires before Elementor element is rendered in the frontend. 423 * 424 * @since 2.2.0 425 * 426 * @param Element_Base $this The element. 427 */ 428 do_action( 'elementor/frontend/before_render', $this ); 429 430 /** 431 * Before frontend element render. 432 * 433 * Fires before Elementor element is rendered in the frontend. 434 * 435 * The dynamic portion of the hook name, `$element_type`, refers to the element type. 436 * 437 * @since 1.0.0 438 * 439 * @param Element_Base $this The element. 440 */ 441 do_action( "elementor/frontend/{$element_type}/before_render", $this ); 442 443 ob_start(); 444 445 if ( $this->has_own_method( '_print_content', self::class ) ) { 446 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_print_content', '3.1.0', __CLASS__ . '::print_content()' ); 447 448 $this->_print_content(); 449 } else { 450 $this->print_content(); 451 } 452 453 $content = ob_get_clean(); 454 455 $should_render = ( ! empty( $content ) || $this->should_print_empty() ); 456 457 /** 458 * Should the element be rendered for frontend 459 * 460 * Filters if the element should be rendered on frontend. 461 * 462 * @since 2.3.3 463 * 464 * @param bool true The element. 465 * @param Element_Base $this The element. 466 */ 467 $should_render = apply_filters( "elementor/frontend/{$element_type}/should_render", $should_render, $this ); 468 469 if ( $should_render ) { 470 if ( $this->has_own_method( '_add_render_attributes', self::class ) ) { 471 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( '_add_render_attributes', '3.1.0', __CLASS__ . '::add_render_attributes()' ); 472 473 $this->_add_render_attributes(); 474 } else { 475 $this->add_render_attributes(); 476 } 477 478 $this->before_render(); 479 // PHPCS - The content has already been escaped by the `render` method. 480 echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 481 $this->after_render(); 482 483 $this->enqueue_scripts(); 484 $this->enqueue_styles(); 485 } 486 487 /** 488 * After frontend element render. 489 * 490 * Fires after Elementor element is rendered in the frontend. 491 * 492 * The dynamic portion of the hook name, `$element_type`, refers to the element type. 493 * 494 * @since 1.0.0 495 * 496 * @param Element_Base $this The element. 497 */ 498 do_action( "elementor/frontend/{$element_type}/after_render", $this ); 499 500 /** 501 * After frontend element render. 502 * 503 * Fires after Elementor element is rendered in the frontend. 504 * 505 * @since 2.3.0 506 * 507 * @param Element_Base $this The element. 508 */ 509 do_action( 'elementor/frontend/after_render', $this ); 510 } 511 512 /** 513 * Get the element raw data. 514 * 515 * Retrieve the raw element data, including the id, type, settings, child 516 * elements and whether it is an inner element. 517 * 518 * The data with the HTML used always to display the data, but the Elementor 519 * editor uses the raw data without the HTML in order not to render the data 520 * again. 521 * 522 * @since 1.0.0 523 * @access public 524 * 525 * @param bool $with_html_content Optional. Whether to return the data with 526 * HTML content or without. Used for caching. 527 * Default is false, without HTML. 528 * 529 * @return array Element raw data. 530 */ 531 public function get_raw_data( $with_html_content = false ) { 532 $data = $this->get_data(); 533 534 $elements = []; 535 536 foreach ( $this->get_children() as $child ) { 537 $elements[] = $child->get_raw_data( $with_html_content ); 538 } 539 540 return [ 541 'id' => $this->get_id(), 542 'elType' => $data['elType'], 543 'settings' => $data['settings'], 544 'elements' => $elements, 545 'isInner' => $data['isInner'], 546 ]; 547 } 548 549 /** 550 * Get unique selector. 551 * 552 * Retrieve the unique selector of the element. Used to set a unique HTML 553 * class for each HTML element. This way Elementor can set custom styles for 554 * each element. 555 * 556 * @since 1.0.0 557 * @access public 558 * 559 * @return string Unique selector. 560 */ 561 public function get_unique_selector() { 562 return '.elementor-element-' . $this->get_id(); 563 } 564 565 /** 566 * Is type instance. 567 * 568 * Used to determine whether the element is an instance of that type or not. 569 * 570 * @since 1.0.0 571 * @access public 572 * 573 * @return bool Whether the element is an instance of that type. 574 */ 575 public function is_type_instance() { 576 return $this->is_type_instance; 577 } 578 579 /** 580 * Add render attributes. 581 * 582 * Used to add attributes to the current element wrapper HTML tag. 583 * 584 * @since 1.3.0 585 * @access protected 586 * @deprecated 3.1.0 587 */ 588 protected function _add_render_attributes() { 589 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0', __CLASS__ . '::add_render_attributes()' ); 590 591 return $this->add_render_attributes(); 592 } 593 594 /** 595 * Add render attributes. 596 * 597 * Used to add attributes to the current element wrapper HTML tag. 598 * 599 * @since 3.1.0 600 * @access protected 601 */ 602 protected function add_render_attributes() { 603 $id = $this->get_id(); 604 605 $settings = $this->get_settings_for_display(); 606 $frontend_settings = $this->get_frontend_settings(); 607 $controls = $this->get_controls(); 608 609 $this->add_render_attribute( '_wrapper', [ 610 'class' => [ 611 'elementor-element', 612 'elementor-element-' . $id, 613 ], 614 'data-id' => $id, 615 'data-element_type' => $this->get_type(), 616 ] ); 617 618 $class_settings = []; 619 620 foreach ( $settings as $setting_key => $setting ) { 621 if ( isset( $controls[ $setting_key ]['prefix_class'] ) ) { 622 $class_settings[ $setting_key ] = $setting; 623 } 624 } 625 626 foreach ( $class_settings as $setting_key => $setting ) { 627 if ( empty( $setting ) && '0' !== $setting ) { 628 continue; 629 } 630 631 $this->add_render_attribute( '_wrapper', 'class', $controls[ $setting_key ]['prefix_class'] . $setting ); 632 } 633 634 $_animation = ! empty( $settings['_animation'] ); 635 $animation = ! empty( $settings['animation'] ); 636 $has_animation = $_animation && 'none' !== $settings['_animation'] || $animation && 'none' !== $settings['animation']; 637 638 if ( $has_animation ) { 639 $is_static_render_mode = Plugin::$instance->frontend->is_static_render_mode(); 640 641 if ( ! $is_static_render_mode ) { 642 // Hide the element until the animation begins 643 $this->add_render_attribute( '_wrapper', 'class', 'elementor-invisible' ); 644 } 645 } 646 647 if ( ! empty( $settings['_element_id'] ) ) { 648 $this->add_render_attribute( '_wrapper', 'id', trim( $settings['_element_id'] ) ); 649 } 650 651 if ( $frontend_settings ) { 652 $this->add_render_attribute( '_wrapper', 'data-settings', wp_json_encode( $frontend_settings ) ); 653 } 654 655 /** 656 * After element attribute rendered. 657 * 658 * Fires after the attributes of the element HTML tag are rendered. 659 * 660 * @since 2.3.0 661 * 662 * @param Element_Base $this The element. 663 */ 664 do_action( 'elementor/element/after_add_attributes', $this ); 665 } 666 667 /** 668 * Add Hidden Device Controls 669 * 670 * Adds controls for hiding elements within certain devices' viewport widths. Adds a control for each active device. 671 * 672 * @since 3.4.0 673 * @access protected 674 */ 675 protected function add_hidden_device_controls() { 676 // The 'Hide On X' controls are displayed from largest to smallest, while the method returns smallest to largest. 677 $active_devices = Plugin::$instance->breakpoints->get_active_devices_list( [ 'reverse' => true ] ); 678 $active_breakpoints = Plugin::$instance->breakpoints->get_active_breakpoints(); 679 680 foreach ( $active_devices as $breakpoint_key ) { 681 $label = 'desktop' === $breakpoint_key ? __( 'Desktop', 'elementor' ) : $active_breakpoints[ $breakpoint_key ]->get_label(); 682 683 $this->add_control( 684 'hide_' . $breakpoint_key, 685 [ 686 /* translators: %s: Device Name. */ 687 'label' => sprintf( __( 'Hide On %s', 'elementor' ), $label ), 688 'type' => Controls_Manager::SWITCHER, 689 'default' => '', 690 'prefix_class' => 'elementor-', 691 'label_on' => 'Hide', 692 'label_off' => 'Show', 693 'return_value' => 'hidden-' . $breakpoint_key, 694 ] 695 ); 696 } 697 } 698 699 /** 700 * Get default data. 701 * 702 * Retrieve the default element data. Used to reset the data on initialization. 703 * 704 * @since 1.0.0 705 * @access protected 706 * 707 * @return array Default data. 708 */ 709 protected function get_default_data() { 710 $data = parent::get_default_data(); 711 712 return array_merge( 713 $data, [ 714 'elements' => [], 715 'isInner' => false, 716 ] 717 ); 718 } 719 720 /** 721 * Print element content. 722 * 723 * Output the element final HTML on the frontend. 724 * 725 * @since 1.0.0 726 * @access protected 727 */ 728 protected function _print_content() { 729 Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0', __CLASS__ . '::print_content()' ); 730 731 $this->print_content(); 732 } 733 734 /** 735 * Print element content. 736 * 737 * Output the element final HTML on the frontend. 738 * 739 * @since 3.1.0 740 * @access protected 741 */ 742 protected function print_content() { 743 foreach ( $this->get_children() as $child ) { 744 $child->print_element(); 745 } 746 } 747 748 /** 749 * Get initial config. 750 * 751 * Retrieve the current element initial configuration. 752 * 753 * Adds more configuration on top of the controls list and the tabs assigned 754 * to the control. This method also adds element name, type, icon and more. 755 * 756 * @since 2.9.0 757 * @access protected 758 * 759 * @return array The initial config. 760 */ 761 protected function get_initial_config() { 762 $config = [ 763 'name' => $this->get_name(), 764 'elType' => $this->get_type(), 765 'title' => $this->get_title(), 766 'icon' => $this->get_icon(), 767 'reload_preview' => $this->is_reload_preview_required(), 768 ]; 769 770 if ( preg_match( '/^' . __NAMESPACE__ . '(Pro)?\\\\/', get_called_class() ) ) { 771 $config['help_url'] = $this->get_help_url(); 772 } else { 773 $config['help_url'] = $this->get_custom_help_url(); 774 } 775 776 if ( ! $this->is_editable() ) { 777 $config['editable'] = false; 778 } 779 780 return $config; 781 } 782 783 /** 784 * Get child type. 785 * 786 * Retrieve the element child type based on element data. 787 * 788 * @since 2.0.0 789 * @access private 790 * 791 * @param array $element_data Element ID. 792 * 793 * @return Element_Base|false Child type or false if type not found. 794 */ 795 private function get_child_type( $element_data ) { 796 $child_type = $this->_get_default_child_type( $element_data ); 797 798 // If it's not a valid widget ( like a deactivated plugin ) 799 if ( ! $child_type ) { 800 return false; 801 } 802 803 /** 804 * Element child type. 805 * 806 * Filters the child type of the element. 807 * 808 * @since 1.0.0 809 * 810 * @param Element_Base $child_type The child element. 811 * @param array $element_data The original element ID. 812 * @param Element_Base $this The original element. 813 */ 814 $child_type = apply_filters( 'elementor/element/get_child_type', $child_type, $element_data, $this ); 815 816 return $child_type; 817 } 818 819 /** 820 * Initialize children. 821 * 822 * Initializing the element child elements. 823 * 824 * @since 2.0.0 825 * @access private 826 */ 827 private function init_children() { 828 $this->children = []; 829 830 $children_data = $this->get_data( 'elements' ); 831 832 if ( ! $children_data ) { 833 return; 834 } 835 836 foreach ( $children_data as $child_data ) { 837 if ( ! $child_data ) { 838 continue; 839 } 840 841 $this->add_child( $child_data ); 842 } 843 } 844 845 /** 846 * Element base constructor. 847 * 848 * Initializing the element base class using `$data` and `$args`. 849 * 850 * The `$data` parameter is required for a normal instance because of the 851 * way Elementor renders data when initializing elements. 852 * 853 * @since 1.0.0 854 * @access public 855 * 856 * @param array $data Optional. Element data. Default is an empty array. 857 * @param array|null $args Optional. Element default arguments. Default is null. 858 **/ 859 public function __construct( array $data = [], array $args = null ) { 860 if ( $data ) { 861 $this->is_type_instance = false; 862 } elseif ( $args ) { 863 $this->default_args = $args; 864 } 865 866 parent::__construct( $data ); 867 } 868 }