| @@ -120,11 +120,9 @@ | ||
| 120 | 120 | private function install_compare( $version ) { |
| 121 | 121 | $installs_history = Upgrade_Manager::get_installs_history(); |
| 122 | 122 | |
| 123 | 123 | if ( empty( $installs_history ) ) { |
| 124 | - // Fresh installation: upgrade manager hasn't written history yet on this first request. | |
| 125 | - // Use the current plugin version as the effective first-install version. | |
| 126 | - return version_compare( ELEMENTOR_VERSION, $version, '>=' ); | |
| 124 | + return false; | |
| 127 | 125 | } |
| 128 | 126 | |
| 129 | 127 | $cleaned_version = preg_replace( '/-(beta|cloud|dev)\d*$/', '', key( $installs_history ) ); |
| 130 | 128 | |
| @@ -249,11 +247,9 @@ | ||
| 249 | 247 | * |
| 250 | 248 | * @since 3.1.0 |
| 251 | 249 | * @access public |
| 252 | 250 | * |
| 253 | - * @param string $feature_name Experiment feature name. | |
| 254 | - * @param bool $check_dependencies When true, also require dependency experiments to be active. | |
| 255 | - * Missing or hidden dependencies are treated as active for compatibility. | |
| 251 | + * @param string $feature_name | |
| 256 | 252 | * |
| 257 | 253 | * @return bool |
| 258 | 254 | */ |
| 259 | 255 | public function is_feature_active( $feature_name, $check_dependencies = false ) { |
| @@ -264,18 +260,9 @@ | ||
| 264 | 260 | } |
| 265 | 261 | |
| 266 | 262 | if ( $check_dependencies && isset( $feature['dependencies'] ) && is_array( $feature['dependencies'] ) ) { |
| 267 | 263 | foreach ( $feature['dependencies'] as $dependency ) { |
| 268 | - if ( $dependency instanceof Non_Existing_Dependency ) { | |
| 269 | - continue; | |
| 270 | - } | |
| 271 | - | |
| 272 | 264 | $dependent_feature = $this->get_features( $dependency->get_name() ); |
| 273 | - | |
| 274 | - if ( $this->is_removed_or_hidden_dependency( $dependent_feature ) ) { | |
| 275 | - continue; | |
| 276 | - } | |
| 277 | - | |
| 278 | 265 | $feature_state = self::STATE_ACTIVE === $this->get_feature_actual_state( $dependent_feature ); |
| 279 | 266 | |
| 280 | 267 | if ( ! $feature_state ) { |
| 281 | 268 | return false; |
| @@ -353,13 +340,14 @@ | ||
| 353 | 340 | $this->add_feature( [ |
| 354 | 341 | 'name' => 'container', |
| 355 | 342 | 'title' => esc_html__( 'Container', 'elementor' ), |
| 356 | 343 | 'description' => sprintf( |
| 357 | - /* translators: 1: Link opening tag, 2: Link closing tag, 3: Link opening tag, 4: Link closing tag */ | |
| 358 | - esc_html__( 'Create advanced layouts and responsive designs with %1$sFlexbox%2$s and %3$sGrid%4$s container elements.', 'elementor' ), | |
| 344 | + esc_html__( 'Create advanced layouts and responsive designs with %1$sFlexbox%2$s and %3$sGrid%4$s container elements. Give it a try using the %5$sContainer playground%6$s.', 'elementor' ), | |
| 359 | 345 | '<a target="_blank" href="https://go.elementor.com/wp-dash-flex-container/">', |
| 360 | 346 | '</a>', |
| 361 | 347 | '<a target="_blank" href="https://go.elementor.com/wp-dash-grid-container/">', |
| 348 | + '</a>', | |
| 349 | + '<a target="_blank" href="https://go.elementor.com/wp-dash-flex-container-playground/">', | |
| 362 | 350 | '</a>' |
| 363 | 351 | ), |
| 364 | 352 | 'release_status' => self::RELEASE_STATUS_STABLE, |
| 365 | 353 | 'default' => self::STATE_INACTIVE, |
| @@ -380,24 +368,20 @@ | ||
| 380 | 368 | 'name' => 'e_optimized_markup', |
| 381 | 369 | 'title' => esc_html__( 'Optimized Markup', 'elementor' ), |
| 382 | 370 | 'tag' => esc_html__( 'Performance', 'elementor' ), |
| 383 | 371 | 'description' => esc_html__( 'Reduce the DOM size by eliminating HTML tags in various elements and widgets. This experiment includes markup changes so it might require updating custom CSS/JS code and cause compatibility issues with third party plugins.', 'elementor' ), |
| 384 | - 'release_status' => self::RELEASE_STATUS_STABLE, | |
| 372 | + 'release_status' => self::RELEASE_STATUS_BETA, | |
| 385 | 373 | 'default' => self::STATE_INACTIVE, |
| 386 | - 'new_site' => [ | |
| 387 | - 'default_active' => true, | |
| 388 | - 'minimum_installation_version' => '3.30.0', | |
| 389 | - ], | |
| 390 | 374 | ] ); |
| 391 | 375 | |
| 392 | 376 | $this->add_feature( [ |
| 393 | - 'name' => 'e_optimized_css_files', | |
| 394 | - 'title' => esc_html__( 'Optimized CSS Files', 'elementor' ), | |
| 377 | + 'name' => 'e_local_google_fonts', | |
| 378 | + 'title' => esc_html__( 'Load Google Fonts locally', 'elementor' ), | |
| 379 | + 'description' => esc_html__( "To improve page load performance and user privacy, replace Google Fonts CDN links with self-hosted font files. This approach downloads and serves font files directly from your server, eliminating external requests to Google's servers.", 'elementor' ), | |
| 395 | 380 | 'tag' => esc_html__( 'Performance', 'elementor' ), |
| 396 | - 'description' => esc_html__( 'Keeps external CSS files available and consistent for sites behind page caching or a CDN.', 'elementor' ), | |
| 397 | - 'release_status' => self::RELEASE_STATUS_ALPHA, | |
| 398 | - 'default' => self::STATE_INACTIVE, | |
| 381 | + 'release_status' => self::RELEASE_STATUS_STABLE, | |
| 399 | 382 | 'generator_tag' => true, |
| 383 | + 'default' => self::STATE_ACTIVE, | |
| 400 | 384 | ] ); |
| 401 | 385 | } |
| 402 | 386 | |
| 403 | 387 | /** |
| @@ -673,12 +657,9 @@ | ||
| 673 | 657 | <div class="e-experiment__title"> |
| 674 | 658 | <div class="<?php echo $indicator_classes; ?>" data-tooltip="<?php echo $indicator_tooltip; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"></div> |
| 675 | 659 | <label class="e-experiment__title__label" for="e-experiment-<?php echo $feature['name']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"><?php echo $feature['title']; ?></label> |
| 676 | 660 | <?php foreach ( $feature['tags'] as $tag ) { ?> |
| 677 | - <?php | |
| 678 | - $tag_classes = 'e-experiment__title__tag e-experiment__title__tag__' . $tag['type'] . ' e-editor-one'; | |
| 679 | - ?> | |
| 680 | - <span class="<?php echo esc_attr( $tag_classes ); ?>"><?php echo $tag['label']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span> | |
| 661 | + <span class="e-experiment__title__tag e-experiment__title__tag__<?php echo $tag['type']; ?>"><?php echo $tag['label']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span> | |
| 681 | 662 | <?php } ?> |
| 682 | 663 | <?php if ( $feature['deprecated'] ) { ?> |
| 683 | 664 | <span class="e-experiment__title__tag e-experiment__title__tag__deprecated"><?php echo esc_html__( 'Deprecated', 'elementor' ); ?></span> |
| 684 | 665 | <?php } ?> |
| @@ -749,8 +730,10 @@ | ||
| 749 | 730 | * |
| 750 | 731 | * @param array $old_feature_data |
| 751 | 732 | * @param string $new_state |
| 752 | 733 | * @param string $old_state |
| 734 | + * | |
| 735 | + * @throws Dependency_Exception If the feature dependency is not available or not active. | |
| 753 | 736 | */ |
| 754 | 737 | private function on_feature_state_change( array $old_feature_data, $new_state, $old_state ) { |
| 755 | 738 | $new_feature_data = $this->get_features( $old_feature_data['name'] ); |
| 756 | 739 | $this->validate_dependency( $new_feature_data, $new_state ); |
| @@ -767,9 +750,9 @@ | ||
| 767 | 750 | do_action( 'elementor/experiments/feature-state-change/' . $old_feature_data['name'], $old_state, $new_state ); |
| 768 | 751 | } |
| 769 | 752 | |
| 770 | 753 | /** |
| 771 | - * @throws Exceptions\Dependency_Exception If the feature dependency is not available or not active. | |
| 754 | + * @throws Dependency_Exception If the feature dependency is not available or not active. | |
| 772 | 755 | */ |
| 773 | 756 | private function validate_dependency( array $feature, $new_state ) { |
| 774 | 757 | $rollback = function ( $feature_option_key, $state ) { |
| 775 | 758 | remove_all_actions( 'add_option_' . $feature_option_key ); |
| @@ -790,43 +773,22 @@ | ||
| 790 | 773 | } |
| 791 | 774 | |
| 792 | 775 | // Validate if the current feature dependency is available. |
| 793 | 776 | foreach ( $feature['dependencies'] as $dependency ) { |
| 794 | - if ( $dependency instanceof Non_Existing_Dependency ) { | |
| 795 | - $this->warn_removed_or_hidden_dependency( | |
| 796 | - sprintf( | |
| 797 | - 'The feature `%s` has a dependency `%s` that is not available in Core.', | |
| 798 | - esc_html( $feature['name'] ), | |
| 799 | - esc_html( $dependency->get_name() ) | |
| 800 | - ) | |
| 801 | - ); | |
| 802 | - continue; | |
| 803 | - } | |
| 804 | - | |
| 805 | 777 | $dependency_feature = $this->get_features( $dependency->get_name() ); |
| 806 | 778 | |
| 807 | 779 | if ( ! $dependency_feature ) { |
| 808 | - $this->warn_removed_or_hidden_dependency( | |
| 780 | + $rollback( $feature_option_key, self::STATE_INACTIVE ); | |
| 781 | + | |
| 782 | + throw new Exceptions\Dependency_Exception( | |
| 809 | 783 | sprintf( |
| 810 | - 'The feature `%s` has a dependency `%s` that is not available in Core.', | |
| 784 | + 'The feature `%s` has a dependency `%s` that is not available.', | |
| 811 | 785 | esc_html( $feature['name'] ), |
| 812 | 786 | esc_html( $dependency->get_name() ) |
| 813 | 787 | ) |
| 814 | 788 | ); |
| 815 | - continue; | |
| 816 | 789 | } |
| 817 | 790 | |
| 818 | - if ( $this->is_removed_or_hidden_dependency( $dependency_feature ) ) { | |
| 819 | - $this->warn_removed_or_hidden_dependency( | |
| 820 | - sprintf( | |
| 821 | - 'The feature `%1$s` depends on hidden experiment `%2$s`.', | |
| 822 | - esc_html( $feature['name'] ), | |
| 823 | - esc_html( $dependency_feature['name'] ) | |
| 824 | - ) | |
| 825 | - ); | |
| 826 | - continue; | |
| 827 | - } | |
| 828 | - | |
| 829 | 791 | $dependency_state = $this->get_feature_actual_state( $dependency_feature ); |
| 830 | 792 | |
| 831 | 793 | // If dependency is not active. |
| 832 | 794 | if ( self::STATE_INACTIVE === $dependency_state ) { |
| @@ -977,8 +939,10 @@ | ||
| 977 | 939 | |
| 978 | 940 | /** |
| 979 | 941 | * @param array $experimental_data |
| 980 | 942 | * @return array |
| 943 | + * | |
| 944 | + * @throws Dependency_Exception If the feature dependency is not initialized or depends on a hidden experiment. | |
| 981 | 945 | */ |
| 982 | 946 | private function initialize_feature_dependencies( array $experimental_data ): array { |
| 983 | 947 | foreach ( $experimental_data['dependencies'] as $key => $dependency ) { |
| 984 | 948 | $feature = $this->get_features( $dependency ); |
| @@ -983,28 +947,20 @@ | ||
| 983 | 947 | foreach ( $experimental_data['dependencies'] as $key => $dependency ) { |
| 984 | 948 | $feature = $this->get_features( $dependency ); |
| 985 | 949 | |
| 986 | 950 | if ( ! isset( $feature ) ) { |
| 987 | - $this->warn_removed_or_hidden_dependency( | |
| 951 | + // since we must validate the state of each dependency, we have to make sure that dependencies are initialized in the correct order, otherwise, error. | |
| 952 | + throw new Exceptions\Dependency_Exception( | |
| 988 | 953 | sprintf( |
| 989 | - 'Feature %1$s depends on experiment %2$s that is not registered in Core.', | |
| 954 | + 'Feature %s cannot be initialized before dependency feature: %s.', | |
| 990 | 955 | esc_html( $experimental_data['name'] ), |
| 991 | 956 | esc_html( $dependency ) |
| 992 | 957 | ) |
| 993 | 958 | ); |
| 994 | - | |
| 995 | - $experimental_data['dependencies'][ $key ] = $this->create_dependency_class( $dependency, null ); | |
| 996 | - continue; | |
| 997 | 959 | } |
| 998 | 960 | |
| 999 | 961 | if ( ! empty( $feature[ static::TYPE_HIDDEN ] ) ) { |
| 1000 | - $this->warn_removed_or_hidden_dependency( | |
| 1001 | - sprintf( | |
| 1002 | - 'Feature %1$s depends on hidden experiment %2$s.', | |
| 1003 | - esc_html( $experimental_data['name'] ), | |
| 1004 | - esc_html( $dependency ) | |
| 1005 | - ) | |
| 1006 | - ); | |
| 962 | + throw new Exceptions\Dependency_Exception( 'Depending on a hidden experiment is not allowed.' ); | |
| 1007 | 963 | } |
| 1008 | 964 | |
| 1009 | 965 | $experimental_data['dependencies'][ $key ] = $this->create_dependency_class( $dependency, $feature ); |
| 1010 | 966 | $experimental_data = $this->set_feature_default_state_to_match_dependencies( $feature, $experimental_data ); |
| @@ -1010,25 +966,8 @@ | ||
| 1010 | 966 | $experimental_data = $this->set_feature_default_state_to_match_dependencies( $feature, $experimental_data ); |
| 1011 | 967 | } |
| 1012 | 968 | |
| 1013 | 969 | return $experimental_data; |
| 1014 | - } | |
| 1015 | - | |
| 1016 | - private function is_removed_or_hidden_dependency( $dependency_feature ): bool { | |
| 1017 | - if ( ! $dependency_feature ) { | |
| 1018 | - return true; | |
| 1019 | - } | |
| 1020 | - | |
| 1021 | - return ! empty( $dependency_feature[ static::TYPE_HIDDEN ] ); | |
| 1022 | - } | |
| 1023 | - | |
| 1024 | - private function warn_removed_or_hidden_dependency( string $message ): void { | |
| 1025 | - if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) { | |
| 1026 | - return; | |
| 1027 | - } | |
| 1028 | - | |
| 1029 | - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Developer notice; message is escaped. | |
| 1030 | - _doing_it_wrong( __METHOD__, esc_html( $message ), ELEMENTOR_VERSION ); | |
| 1031 | 970 | } |
| 1032 | 971 | |
| 1033 | 972 | /** |
| 1034 | 973 | * @param array $feature |