PluginProbe
Elementor Website Builder – more than just a page builder / trunk
Elementor Website Builder – more than just a page builder vtrunk
4.3.3 4.3.2 4.3.1 4.3.0 4.3.0-beta3 4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 All 456 releases
← All changes | core/experiments/manager.php +120 -16 4.1.0-beta3 → trunk View file →
@@ -89,8 +89,10 @@
89 89 }
90 90
91 91 $this->features[ $options['name'] ] = $experimental_data;
92 92
93 + $this->resolve_registered_dependants( $options['name'], $experimental_data );
94 +
93 95 if ( $experimental_data['mutable'] && is_admin() ) {
94 96 $feature_option_key = $this->get_feature_option_key( $options['name'] );
95 97
96 98 $on_state_change_callback = function( $old_state, $new_state ) use ( $experimental_data, $feature_option_key ) {
@@ -249,9 +251,11 @@
249 251 *
250 252 * @since 3.1.0
251 253 * @access public
252 254 *
253 - * @param string $feature_name
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.
254 258 *
255 259 * @return bool
256 260 */
257 261 public function is_feature_active( $feature_name, $check_dependencies = false ) {
@@ -260,11 +264,36 @@
260 264 if ( ! $feature || self::STATE_ACTIVE !== $this->get_feature_actual_state( $feature ) ) {
261 265 return false;
262 266 }
263 267
264 - if ( $check_dependencies && isset( $feature['dependencies'] ) && is_array( $feature['dependencies'] ) ) {
268 + if ( isset( $feature['dependencies'] ) && is_array( $feature['dependencies'] ) ) {
265 269 foreach ( $feature['dependencies'] as $dependency ) {
266 270 $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 +
267 296 $feature_state = self::STATE_ACTIVE === $this->get_feature_actual_state( $dependent_feature );
268 297
269 298 if ( ! $feature_state ) {
270 299 return false;
@@ -342,15 +371,13 @@
342 371 $this->add_feature( [
343 372 'name' => 'container',
344 373 'title' => esc_html__( 'Container', 'elementor' ),
345 374 'description' => sprintf(
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' ),
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' ),
348 377 '<a target="_blank" href="https://go.elementor.com/wp-dash-flex-container/">',
349 378 '</a>',
350 379 '<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/">',
353 380 '</a>'
354 381 ),
355 382 'release_status' => self::RELEASE_STATUS_STABLE,
356 383 'default' => self::STATE_INACTIVE,
@@ -378,8 +405,18 @@
378 405 'default_active' => true,
379 406 'minimum_installation_version' => '3.30.0',
380 407 ],
381 408 ] );
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 + ] );
382 419 }
383 420
384 421 /**
385 422 * Init States
@@ -771,20 +808,41 @@
771 808 }
772 809
773 810 // Validate if the current feature dependency is available.
774 811 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 +
775 823 $dependency_feature = $this->get_features( $dependency->get_name() );
776 824
777 825 if ( ! $dependency_feature ) {
778 - $rollback( $feature_option_key, self::STATE_INACTIVE );
826 + $this->warn_removed_or_hidden_dependency(
827 + sprintf(
828 + 'The feature `%s` has a dependency `%s` that is not available in Core.',
829 + esc_html( $feature['name'] ),
830 + esc_html( $dependency->get_name() )
831 + )
832 + );
833 + continue;
834 + }
779 835
780 - throw new Exceptions\Dependency_Exception(
836 + if ( $this->is_removed_or_hidden_dependency( $dependency_feature ) ) {
837 + $this->warn_removed_or_hidden_dependency(
781 838 sprintf(
782 - 'The feature `%s` has a dependency `%s` that is not available.',
839 + 'The feature `%1$s` depends on hidden experiment `%2$s`.',
783 840 esc_html( $feature['name'] ),
784 - esc_html( $dependency->get_name() )
841 + esc_html( $dependency_feature['name'] )
785 842 )
786 843 );
844 + continue;
787 845 }
788 846
789 847 $dependency_state = $this->get_feature_actual_state( $dependency_feature );
790 848
@@ -937,10 +995,8 @@
937 995
938 996 /**
939 997 * @param array $experimental_data
940 998 * @return array
941 - *
942 - * @throws Exceptions\Dependency_Exception If the feature dependency is not initialized or depends on a hidden experiment.
943 999 */
944 1000 private function initialize_feature_dependencies( array $experimental_data ): array {
945 1001 foreach ( $experimental_data['dependencies'] as $key => $dependency ) {
946 1002 $feature = $this->get_features( $dependency );
@@ -945,20 +1001,28 @@
945 1001 foreach ( $experimental_data['dependencies'] as $key => $dependency ) {
946 1002 $feature = $this->get_features( $dependency );
947 1003
948 1004 if ( ! isset( $feature ) ) {
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(
1005 + $this->warn_removed_or_hidden_dependency(
951 1006 sprintf(
952 - 'Feature %s cannot be initialized before dependency feature: %s.',
1007 + 'Feature %1$s depends on experiment %2$s that is not registered in Core.',
953 1008 esc_html( $experimental_data['name'] ),
954 1009 esc_html( $dependency )
955 1010 )
956 1011 );
1012 +
1013 + $experimental_data['dependencies'][ $key ] = $this->create_dependency_class( $dependency, null );
1014 + continue;
957 1015 }
958 1016
959 1017 if ( ! empty( $feature[ static::TYPE_HIDDEN ] ) ) {
960 - throw new Exceptions\Dependency_Exception( 'Depending on a hidden experiment is not allowed.' );
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 + );
961 1025 }
962 1026
963 1027 $experimental_data['dependencies'][ $key ] = $this->create_dependency_class( $dependency, $feature );
964 1028 $experimental_data = $this->set_feature_default_state_to_match_dependencies( $feature, $experimental_data );
@@ -964,8 +1028,48 @@
964 1028 $experimental_data = $this->set_feature_default_state_to_match_dependencies( $feature, $experimental_data );
965 1029 }
966 1030
967 1031 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 );
968 1072 }
969 1073
970 1074 /**
971 1075 * @param array $feature