| @@ -89,10 +89,8 @@ | ||
| 89 | 89 | } |
| 90 | 90 | |
| 91 | 91 | $this->features[ $options['name'] ] = $experimental_data; |
| 92 | 92 | |
| 93 | - $this->resolve_registered_dependants( $options['name'], $experimental_data ); | |
| 94 | - | |
| 95 | 93 | if ( $experimental_data['mutable'] && is_admin() ) { |
| 96 | 94 | $feature_option_key = $this->get_feature_option_key( $options['name'] ); |
| 97 | 95 | |
| 98 | 96 | $on_state_change_callback = function( $old_state, $new_state ) use ( $experimental_data, $feature_option_key ) { |
| @@ -251,11 +249,9 @@ | ||
| 251 | 249 | * |
| 252 | 250 | * @since 3.1.0 |
| 253 | 251 | * @access public |
| 254 | 252 | * |
| 255 | - * @param string $feature_name Experiment feature name. | |
| 256 | - * @param bool $check_dependencies When true, also require dependency experiments to be active. | |
| 257 | - * Missing dependencies always fail closed. Hidden dependencies are treated as satisfied. | |
| 253 | + * @param string $feature_name | |
| 258 | 254 | * |
| 259 | 255 | * @return bool |
| 260 | 256 | */ |
| 261 | 257 | public function is_feature_active( $feature_name, $check_dependencies = false ) { |
| @@ -264,36 +260,11 @@ | ||
| 264 | 260 | if ( ! $feature || self::STATE_ACTIVE !== $this->get_feature_actual_state( $feature ) ) { |
| 265 | 261 | return false; |
| 266 | 262 | } |
| 267 | 263 | |
| 268 | - if ( isset( $feature['dependencies'] ) && is_array( $feature['dependencies'] ) ) { | |
| 264 | + if ( $check_dependencies && isset( $feature['dependencies'] ) && is_array( $feature['dependencies'] ) ) { | |
| 269 | 265 | foreach ( $feature['dependencies'] as $dependency ) { |
| 270 | 266 | $dependent_feature = $this->get_features( $dependency->get_name() ); |
| 271 | - | |
| 272 | - // A dependency that Core still does not ship can never be verified as active. | |
| 273 | - // Non existing dependencies are re-resolved first to cover later registrations. | |
| 274 | - if ( $dependency instanceof Non_Existing_Dependency && ! $dependent_feature ) { | |
| 275 | - return false; | |
| 276 | - } | |
| 277 | - | |
| 278 | - if ( ! $dependent_feature ) { | |
| 279 | - // A Core experiment dependency that no longer resolves fails closed. | |
| 280 | - // Class dependencies are not experiment names and stay exempt. | |
| 281 | - if ( $dependency instanceof Wrap_Core_Dependency ) { | |
| 282 | - return false; | |
| 283 | - } | |
| 284 | - | |
| 285 | - continue; | |
| 286 | - } | |
| 287 | - | |
| 288 | - if ( ! $check_dependencies ) { | |
| 289 | - continue; | |
| 290 | - } | |
| 291 | - | |
| 292 | - if ( ! empty( $dependent_feature[ static::TYPE_HIDDEN ] ) ) { | |
| 293 | - continue; | |
| 294 | - } | |
| 295 | - | |
| 296 | 267 | $feature_state = self::STATE_ACTIVE === $this->get_feature_actual_state( $dependent_feature ); |
| 297 | 268 | |
| 298 | 269 | if ( ! $feature_state ) { |
| 299 | 270 | return false; |
| @@ -371,13 +342,15 @@ | ||
| 371 | 342 | $this->add_feature( [ |
| 372 | 343 | 'name' => 'container', |
| 373 | 344 | 'title' => esc_html__( 'Container', 'elementor' ), |
| 374 | 345 | 'description' => sprintf( |
| 375 | - /* translators: 1: Link opening tag, 2: Link closing tag, 3: Link opening tag, 4: Link closing tag */ | |
| 376 | - esc_html__( 'Create advanced layouts and responsive designs with %1$sFlexbox%2$s and %3$sGrid%4$s container elements.', 'elementor' ), | |
| 346 | + /* translators: 1: Link opening tag, 2: Link closing tag, 3: Link opening tag, 4: Link closing tag, 5: Link opening tag, 6: Link closing tag */ | |
| 347 | + 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' ), | |
| 377 | 348 | '<a target="_blank" href="https://go.elementor.com/wp-dash-flex-container/">', |
| 378 | 349 | '</a>', |
| 379 | 350 | '<a target="_blank" href="https://go.elementor.com/wp-dash-grid-container/">', |
| 351 | + '</a>', | |
| 352 | + '<a target="_blank" href="https://go.elementor.com/wp-dash-flex-container-playground/">', | |
| 380 | 353 | '</a>' |
| 381 | 354 | ), |
| 382 | 355 | 'release_status' => self::RELEASE_STATUS_STABLE, |
| 383 | 356 | 'default' => self::STATE_INACTIVE, |
| @@ -405,18 +378,8 @@ | ||
| 405 | 378 | 'default_active' => true, |
| 406 | 379 | 'minimum_installation_version' => '3.30.0', |
| 407 | 380 | ], |
| 408 | 381 | ] ); |
| 409 | - | |
| 410 | - $this->add_feature( [ | |
| 411 | - 'name' => 'e_optimized_css_files', | |
| 412 | - 'title' => esc_html__( 'Optimized CSS Files', 'elementor' ), | |
| 413 | - 'tag' => esc_html__( 'Performance', 'elementor' ), | |
| 414 | - 'description' => esc_html__( 'Keeps external CSS files available and consistent for sites behind page caching or a CDN.', 'elementor' ), | |
| 415 | - 'release_status' => self::RELEASE_STATUS_ALPHA, | |
| 416 | - 'default' => self::STATE_INACTIVE, | |
| 417 | - 'generator_tag' => true, | |
| 418 | - ] ); | |
| 419 | 382 | } |
| 420 | 383 | |
| 421 | 384 | /** |
| 422 | 385 | * Init States |
| @@ -808,43 +771,22 @@ | ||
| 808 | 771 | } |
| 809 | 772 | |
| 810 | 773 | // Validate if the current feature dependency is available. |
| 811 | 774 | foreach ( $feature['dependencies'] as $dependency ) { |
| 812 | - if ( $dependency instanceof Non_Existing_Dependency ) { | |
| 813 | - $this->warn_removed_or_hidden_dependency( | |
| 814 | - sprintf( | |
| 815 | - 'The feature `%s` has a dependency `%s` that is not available in Core.', | |
| 816 | - esc_html( $feature['name'] ), | |
| 817 | - esc_html( $dependency->get_name() ) | |
| 818 | - ) | |
| 819 | - ); | |
| 820 | - continue; | |
| 821 | - } | |
| 822 | - | |
| 823 | 775 | $dependency_feature = $this->get_features( $dependency->get_name() ); |
| 824 | 776 | |
| 825 | 777 | if ( ! $dependency_feature ) { |
| 826 | - $this->warn_removed_or_hidden_dependency( | |
| 778 | + $rollback( $feature_option_key, self::STATE_INACTIVE ); | |
| 779 | + | |
| 780 | + throw new Exceptions\Dependency_Exception( | |
| 827 | 781 | sprintf( |
| 828 | - 'The feature `%s` has a dependency `%s` that is not available in Core.', | |
| 782 | + 'The feature `%s` has a dependency `%s` that is not available.', | |
| 829 | 783 | esc_html( $feature['name'] ), |
| 830 | 784 | esc_html( $dependency->get_name() ) |
| 831 | 785 | ) |
| 832 | 786 | ); |
| 833 | - continue; | |
| 834 | 787 | } |
| 835 | 788 | |
| 836 | - if ( $this->is_removed_or_hidden_dependency( $dependency_feature ) ) { | |
| 837 | - $this->warn_removed_or_hidden_dependency( | |
| 838 | - sprintf( | |
| 839 | - 'The feature `%1$s` depends on hidden experiment `%2$s`.', | |
| 840 | - esc_html( $feature['name'] ), | |
| 841 | - esc_html( $dependency_feature['name'] ) | |
| 842 | - ) | |
| 843 | - ); | |
| 844 | - continue; | |
| 845 | - } | |
| 846 | - | |
| 847 | 789 | $dependency_state = $this->get_feature_actual_state( $dependency_feature ); |
| 848 | 790 | |
| 849 | 791 | // If dependency is not active. |
| 850 | 792 | if ( self::STATE_INACTIVE === $dependency_state ) { |
| @@ -995,8 +937,10 @@ | ||
| 995 | 937 | |
| 996 | 938 | /** |
| 997 | 939 | * @param array $experimental_data |
| 998 | 940 | * @return array |
| 941 | + * | |
| 942 | + * @throws Exceptions\Dependency_Exception If the feature dependency is not initialized or depends on a hidden experiment. | |
| 999 | 943 | */ |
| 1000 | 944 | private function initialize_feature_dependencies( array $experimental_data ): array { |
| 1001 | 945 | foreach ( $experimental_data['dependencies'] as $key => $dependency ) { |
| 1002 | 946 | $feature = $this->get_features( $dependency ); |
| @@ -1001,28 +945,20 @@ | ||
| 1001 | 945 | foreach ( $experimental_data['dependencies'] as $key => $dependency ) { |
| 1002 | 946 | $feature = $this->get_features( $dependency ); |
| 1003 | 947 | |
| 1004 | 948 | if ( ! isset( $feature ) ) { |
| 1005 | - $this->warn_removed_or_hidden_dependency( | |
| 949 | + // since we must validate the state of each dependency, we have to make sure that dependencies are initialized in the correct order, otherwise, error. | |
| 950 | + throw new Exceptions\Dependency_Exception( | |
| 1006 | 951 | sprintf( |
| 1007 | - 'Feature %1$s depends on experiment %2$s that is not registered in Core.', | |
| 952 | + 'Feature %s cannot be initialized before dependency feature: %s.', | |
| 1008 | 953 | esc_html( $experimental_data['name'] ), |
| 1009 | 954 | esc_html( $dependency ) |
| 1010 | 955 | ) |
| 1011 | 956 | ); |
| 1012 | - | |
| 1013 | - $experimental_data['dependencies'][ $key ] = $this->create_dependency_class( $dependency, null ); | |
| 1014 | - continue; | |
| 1015 | 957 | } |
| 1016 | 958 | |
| 1017 | 959 | if ( ! empty( $feature[ static::TYPE_HIDDEN ] ) ) { |
| 1018 | - $this->warn_removed_or_hidden_dependency( | |
| 1019 | - sprintf( | |
| 1020 | - 'Feature %1$s depends on hidden experiment %2$s.', | |
| 1021 | - esc_html( $experimental_data['name'] ), | |
| 1022 | - esc_html( $dependency ) | |
| 1023 | - ) | |
| 1024 | - ); | |
| 960 | + throw new Exceptions\Dependency_Exception( 'Depending on a hidden experiment is not allowed.' ); | |
| 1025 | 961 | } |
| 1026 | 962 | |
| 1027 | 963 | $experimental_data['dependencies'][ $key ] = $this->create_dependency_class( $dependency, $feature ); |
| 1028 | 964 | $experimental_data = $this->set_feature_default_state_to_match_dependencies( $feature, $experimental_data ); |
| @@ -1028,48 +964,8 @@ | ||
| 1028 | 964 | $experimental_data = $this->set_feature_default_state_to_match_dependencies( $feature, $experimental_data ); |
| 1029 | 965 | } |
| 1030 | 966 | |
| 1031 | 967 | return $experimental_data; |
| 1032 | - } | |
| 1033 | - | |
| 1034 | - private function is_removed_or_hidden_dependency( $dependency_feature ): bool { | |
| 1035 | - if ( ! $dependency_feature ) { | |
| 1036 | - return true; | |
| 1037 | - } | |
| 1038 | - | |
| 1039 | - return ! empty( $dependency_feature[ static::TYPE_HIDDEN ] ); | |
| 1040 | - } | |
| 1041 | - | |
| 1042 | - /** | |
| 1043 | - * Re-resolve non existing dependency placeholders of dependants that were | |
| 1044 | - * registered before this feature existed. | |
| 1045 | - * | |
| 1046 | - * @param string $feature_name Newly registered feature name. | |
| 1047 | - * @param array $feature_data Newly registered feature data. | |
| 1048 | - */ | |
| 1049 | - private function resolve_registered_dependants( string $feature_name, array $feature_data ): void { | |
| 1050 | - foreach ( $this->features as $name => $registered_feature ) { | |
| 1051 | - if ( $name === $feature_name || empty( $registered_feature['dependencies'] ) || ! is_array( $registered_feature['dependencies'] ) ) { | |
| 1052 | - continue; | |
| 1053 | - } | |
| 1054 | - | |
| 1055 | - foreach ( $registered_feature['dependencies'] as $key => $dependency ) { | |
| 1056 | - if ( ! ( $dependency instanceof Non_Existing_Dependency ) || $dependency->get_name() !== $feature_name ) { | |
| 1057 | - continue; | |
| 1058 | - } | |
| 1059 | - | |
| 1060 | - $this->features[ $name ]['dependencies'][ $key ] = $this->create_dependency_class( $feature_name, $feature_data ); | |
| 1061 | - } | |
| 1062 | - } | |
| 1063 | - } | |
| 1064 | - | |
| 1065 | - private function warn_removed_or_hidden_dependency( string $message ): void { | |
| 1066 | - if ( ! defined( 'WP_DEBUG' ) || ! WP_DEBUG ) { | |
| 1067 | - return; | |
| 1068 | - } | |
| 1069 | - | |
| 1070 | - // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Developer notice; message is escaped. | |
| 1071 | - _doing_it_wrong( __METHOD__, esc_html( $message ), ELEMENTOR_VERSION ); | |
| 1072 | 968 | } |
| 1073 | 969 | |
| 1074 | 970 | /** |
| 1075 | 971 | * @param array $feature |