balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

manager.php (17633B)


      1 <?php
      2 
      3 namespace Elementor\Core\Experiments;
      4 
      5 use Elementor\Core\Base\Base_Object;
      6 use Elementor\Core\Upgrade\Manager as Upgrade_Manager;
      7 use Elementor\Plugin;
      8 use Elementor\Settings;
      9 use Elementor\Tracker;
     10 
     11 if ( ! defined( 'ABSPATH' ) ) {
     12 	exit; // Exit if accessed directly
     13 }
     14 
     15 class Manager extends Base_Object {
     16 
     17 	const RELEASE_STATUS_DEV = 'dev';
     18 
     19 	const RELEASE_STATUS_ALPHA = 'alpha';
     20 
     21 	const RELEASE_STATUS_BETA = 'beta';
     22 
     23 	const RELEASE_STATUS_RC = 'rc';
     24 
     25 	const RELEASE_STATUS_STABLE = 'stable';
     26 
     27 	const STATE_DEFAULT = 'default';
     28 
     29 	const STATE_ACTIVE = 'active';
     30 
     31 	const STATE_INACTIVE = 'inactive';
     32 
     33 	private $states;
     34 
     35 	private $release_statuses;
     36 
     37 	private $features;
     38 
     39 	/**
     40 	 * Add Feature
     41 	 *
     42 	 * @since 3.1.0
     43 	 * @access public
     44 	 *
     45 	 * @param array $options {
     46 	 *     @type string $name
     47 	 *     @type string $title
     48 	 *     @type string $description
     49 	 *     @type string $release_status
     50 	 *     @type string $default
     51 	 *     @type callable $on_state_change
     52 	 * }
     53 	 *
     54 	 * @return array|null
     55 	 */
     56 	public function add_feature( array $options ) {
     57 		if ( isset( $this->features[ $options['name'] ] ) ) {
     58 			return null;
     59 		}
     60 
     61 		$default_experimental_data = [
     62 			'description' => '',
     63 			'release_status' => self::RELEASE_STATUS_ALPHA,
     64 			'default' => self::STATE_INACTIVE,
     65 			'new_site' => [
     66 				'default_active' => false,
     67 				'always_active' => false,
     68 				'minimum_installation_version' => null,
     69 			],
     70 			'on_state_change' => null,
     71 		];
     72 
     73 		$allowed_options = [ 'name', 'title', 'description', 'release_status', 'default', 'new_site', 'on_state_change' ];
     74 
     75 		$experimental_data = $this->merge_properties( $default_experimental_data, $options, $allowed_options );
     76 
     77 		$new_site = $experimental_data['new_site'];
     78 
     79 		$feature_is_mutable = true;
     80 
     81 		if ( $new_site['default_active'] || $new_site['always_active'] ) {
     82 			$is_new_installation = Upgrade_Manager::install_compare( $new_site['minimum_installation_version'], '>=' );
     83 
     84 			if ( $is_new_installation ) {
     85 				if ( $new_site['always_active'] ) {
     86 					$experimental_data['state'] = self::STATE_ACTIVE;
     87 
     88 					$feature_is_mutable = false;
     89 				} elseif ( $new_site['default_active'] ) {
     90 					$experimental_data['default'] = self::STATE_ACTIVE;
     91 				}
     92 			}
     93 		}
     94 
     95 		$experimental_data['mutable'] = $feature_is_mutable;
     96 
     97 		if ( $feature_is_mutable ) {
     98 			$state = $this->get_saved_feature_state( $options['name'] );
     99 
    100 			if ( ! $state ) {
    101 				$state = self::STATE_DEFAULT;
    102 			}
    103 
    104 			$experimental_data['state'] = $state;
    105 		}
    106 
    107 		$this->features[ $options['name'] ] = $experimental_data;
    108 
    109 		if ( $feature_is_mutable && is_admin() ) {
    110 			$feature_option_key = $this->get_feature_option_key( $options['name'] );
    111 
    112 			$on_state_change_callback = function( $old_state, $new_state ) use ( $experimental_data ) {
    113 				$this->on_feature_state_change( $experimental_data, $new_state );
    114 			};
    115 
    116 			add_action( 'add_option_' . $feature_option_key, $on_state_change_callback, 10, 2 );
    117 			add_action( 'update_option_' . $feature_option_key, $on_state_change_callback, 10, 2 );
    118 		}
    119 
    120 		do_action( 'elementor/experiments/feature-registered', $this, $experimental_data );
    121 
    122 		return $experimental_data;
    123 	}
    124 
    125 	/**
    126 	 * Remove Feature
    127 	 *
    128 	 * @since 3.1.0
    129 	 * @access public
    130 	 *
    131 	 * @param string $feature_name
    132 	 */
    133 	public function remove_feature( $feature_name ) {
    134 		unset( $this->features[ $feature_name ] );
    135 	}
    136 
    137 	/**
    138 	 * Get Features
    139 	 *
    140 	 * @since 3.1.0
    141 	 * @access public
    142 	 *
    143 	 * @param string $feature_name Optional. Default is null
    144 	 *
    145 	 * @return array|null
    146 	 */
    147 	public function get_features( $feature_name = null ) {
    148 		return self::get_items( $this->features, $feature_name );
    149 	}
    150 
    151 	/**
    152 	 * Get Active Features
    153 	 *
    154 	 * @since 3.1.0
    155 	 * @access public
    156 	 *
    157 	 * @return array
    158 	 */
    159 	public function get_active_features() {
    160 		return array_filter( $this->features, [ $this, 'is_feature_active' ], ARRAY_FILTER_USE_KEY );
    161 	}
    162 
    163 	/**
    164 	 * Is Feature Active
    165 	 *
    166 	 * @since 3.1.0
    167 	 * @access public
    168 	 *
    169 	 * @param string $feature_name
    170 	 *
    171 	 * @return bool
    172 	 */
    173 	public function is_feature_active( $feature_name ) {
    174 		$feature = $this->get_features( $feature_name );
    175 
    176 		if ( ! $feature ) {
    177 			return false;
    178 		}
    179 
    180 		return self::STATE_ACTIVE === $this->get_feature_actual_state( $feature );
    181 	}
    182 
    183 	/**
    184 	 * Set Feature Default State
    185 	 *
    186 	 * @since 3.1.0
    187 	 * @access public
    188 	 *
    189 	 * @param string $feature_name
    190 	 * @param int $default_state
    191 	 */
    192 	public function set_feature_default_state( $feature_name, $default_state ) {
    193 		$feature = $this->get_features( $feature_name );
    194 
    195 		if ( ! $feature ) {
    196 			return;
    197 		}
    198 
    199 		$this->features[ $feature_name ]['default'] = $default_state;
    200 	}
    201 
    202 	/**
    203 	 * Get Feature Option Key
    204 	 *
    205 	 * @since 3.1.0
    206 	 * @access private
    207 	 *
    208 	 * @param string $feature_name
    209 	 *
    210 	 * @return string
    211 	 */
    212 	private function get_feature_option_key( $feature_name ) {
    213 		return 'elementor_experiment-' . $feature_name;
    214 	}
    215 
    216 	private function add_default_features() {
    217 		$this->add_feature( [
    218 			'name' => 'e_dom_optimization',
    219 			'title' => esc_html__( 'Optimized DOM Output', 'elementor' ),
    220 			'description' => esc_html__( 'Developers, Please Note! This experiment includes some markup changes. If you\'ve used custom code in Elementor, you might have experienced a snippet of code not running. Turning this experiment off allows you to keep prior Elementor markup output settings, and have that lovely code running again.', 'elementor' )
    221 				. ' <a href="https://go.elementor.com/wp-dash-legacy-optimized-dom" target="_blank">'
    222 				. esc_html__( 'Learn More', 'elementor' ) . '</a>',
    223 			'release_status' => self::RELEASE_STATUS_BETA,
    224 			'new_site' => [
    225 				'default_active' => true,
    226 				'minimum_installation_version' => '3.1.0-beta',
    227 			],
    228 		] );
    229 
    230 		$this->add_feature( [
    231 			'name' => 'e_optimized_assets_loading',
    232 			'title' => esc_html__( 'Improved Asset Loading', 'elementor' ),
    233 			'description' => esc_html__( 'Please Note! The "Improved Asset Loading" mode reduces the amount of code that is loaded on the page by default. When activated, parts of the infrastructure code will be loaded dynamically, only when needed. Keep in mind that activating this experiment may cause conflicts with incompatible plugins.', 'elementor' )
    234 				. ' <a href="https://go.elementor.com/wp-dash-improved-asset-loading/" target="_blank">'
    235 				. esc_html__( 'Learn More', 'elementor' ) . '</a>',
    236 			'release_status' => self::RELEASE_STATUS_ALPHA,
    237 		] );
    238 
    239 		$this->add_feature( [
    240 			'name' => 'e_optimized_css_loading',
    241 			'title' => __( 'Improved CSS Loading', 'elementor' ),
    242 			'description' => __( 'Please Note! The “Improved CSS Loading” mode reduces the amount of CSS code that is loaded on the page by default. When activated, the CSS code will be loaded, rather inline or in a dedicated file, only when needed. Activating this experiment may cause conflicts with incompatible plugins.', 'elementor' )
    243 				. ' <a href="https://go.elementor.com/wp-dash-improved-css-loading/" target="_blank">'
    244 				. esc_html__( 'Learn More', 'elementor' ) . '</a>',
    245 			'release_status' => self::RELEASE_STATUS_ALPHA,
    246 		] );
    247 
    248 		$this->add_feature( [
    249 			'name' => 'e_font_icon_svg',
    250 			'title' => __( 'Font-Awesome Inline', 'elementor' ),
    251 			'description' => __( 'The "Font-Awesome Inline" will render the Font-Awesome icons as inline SVG without loading the Font-Awesome library and its related CSS files and fonts.', 'elementor' )
    252 				. ' <a href="https://go.elementor.com/wp-dash-inline-font-awesome/" target="_blank">'
    253 				. esc_html__( 'Learn More', 'elementor' ) . '</a>',
    254 			'release_status' => self::RELEASE_STATUS_ALPHA,
    255 		] );
    256 
    257 		$this->add_feature( [
    258 			'name' => 'a11y_improvements',
    259 			'title' => esc_html__( 'Accessibility Improvements', 'elementor' ),
    260 			'description' => esc_html__( 'An array of accessibility enhancements in Elementor pages.', 'elementor' )
    261 				. '<br><strong>' . esc_html__( 'Please note!', 'elementor' ) . '</strong> ' . esc_html__( 'These enhancements may include some markup changes to existing elementor widgets', 'elementor' )
    262 				. ' <a href="https://go.elementor.com/wp-dash-a11y-improvements" target="_blank">'
    263 				. esc_html__( 'Learn More', 'elementor' ) . '</a>',
    264 			'release_status' => self::RELEASE_STATUS_BETA,
    265 			'new_site' => [
    266 				'default_active' => true,
    267 				'minimum_installation_version' => '3.1.0-beta',
    268 			],
    269 		] );
    270 
    271 		$this->add_feature( [
    272 			'name' => 'e_import_export',
    273 			'title' => esc_html__( 'Import Export Template Kit', 'elementor' ),
    274 			'description' => esc_html__( 'Design sites faster with a template kit that contains some or all components of a complete site, like templates, content & site settings.', 'elementor' )
    275 				. '<br>'
    276 				. esc_html__( 'You can import a kit and apply it to your site, or export the elements from this site to be used anywhere else.', 'elementor' ),
    277 			'release_status' => self::RELEASE_STATUS_BETA,
    278 			'default' => self::STATE_ACTIVE,
    279 		] );
    280 
    281 		$this->add_feature( [
    282 			'name' => 'additional_custom_breakpoints',
    283 			'title' => esc_html__( 'Additional Custom Breakpoints', 'elementor' ),
    284 			'description' => esc_html__( 'Get pixel-perfect design for every screen size. You can now add up to 6 customizable breakpoints beyond the default desktop setting: mobile, mobile extra, tablet, tablet extra, laptop, and widescreen.', 'elementor' )
    285 							. '<br /><strong>' . esc_html__( 'Please note! Conditioning controls on values of responsive controls is not supported when this mode is active.', 'elementor' ) . '</strong>'
    286 				. ' <a href="https://go.elementor.com/wp-dash-additional-custom-breakpoints/" target="_blank">'
    287 				. esc_html__( 'Learn More', 'elementor' ) . '</a>',
    288 			'release_status' => self::RELEASE_STATUS_BETA,
    289 			'new_site' => [
    290 				'default_active' => true,
    291 				'minimum_installation_version' => '3.4.0-beta',
    292 			],
    293 		] );
    294 	}
    295 
    296 	/**
    297 	 * Init States
    298 	 *
    299 	 * @since 3.1.0
    300 	 * @access private
    301 	 */
    302 	private function init_states() {
    303 		$this->states = [
    304 			self::STATE_DEFAULT => esc_html__( 'Default', 'elementor' ),
    305 			self::STATE_ACTIVE => esc_html__( 'Active', 'elementor' ),
    306 			self::STATE_INACTIVE => esc_html__( 'Inactive', 'elementor' ),
    307 		];
    308 	}
    309 
    310 	/**
    311 	 * Init Statuses
    312 	 *
    313 	 * @since 3.1.0
    314 	 * @access private
    315 	 */
    316 	private function init_release_statuses() {
    317 		$this->release_statuses = [
    318 			self::RELEASE_STATUS_DEV => esc_html__( 'Development', 'elementor' ),
    319 			self::RELEASE_STATUS_ALPHA => esc_html__( 'Alpha', 'elementor' ),
    320 			self::RELEASE_STATUS_BETA => esc_html__( 'Beta', 'elementor' ),
    321 			self::RELEASE_STATUS_RC => esc_html__( 'Release Candidate', 'elementor' ),
    322 			self::RELEASE_STATUS_STABLE => esc_html__( 'Stable', 'elementor' ),
    323 		];
    324 	}
    325 
    326 	/**
    327 	 * Init Features
    328 	 *
    329 	 * @since 3.1.0
    330 	 * @access private
    331 	 */
    332 	private function init_features() {
    333 		$this->features = [];
    334 
    335 		$this->add_default_features();
    336 
    337 		do_action( 'elementor/experiments/default-features-registered', $this );
    338 	}
    339 
    340 	/**
    341 	 * Register Settings Fields
    342 	 *
    343 	 * @param Settings $settings
    344 	 *
    345 	 * @since 3.1.0
    346 	 * @access private
    347 	 *
    348 	 */
    349 	private function register_settings_fields( Settings $settings ) {
    350 		$features = $this->get_features();
    351 
    352 		$fields = [];
    353 
    354 		foreach ( $features as $feature_name => $feature ) {
    355 			if ( ! $feature['mutable'] ) {
    356 				unset( $features[ $feature_name ] );
    357 
    358 				continue;
    359 			}
    360 
    361 			$feature_key = 'experiment-' . $feature_name;
    362 
    363 			$fields[ $feature_key ]['label'] = $this->get_feature_settings_label_html( $feature );
    364 
    365 			$fields[ $feature_key ]['field_args'] = $feature;
    366 
    367 			$fields[ $feature_key ]['render'] = function( $feature ) {
    368 				$this->render_feature_settings_field( $feature );
    369 			};
    370 		}
    371 
    372 		if ( ! $features ) {
    373 			$fields['no_features'] = [
    374 				'label' => esc_html__( 'No available experiments', 'elementor' ),
    375 				'field_args' => [
    376 					'type' => 'raw_html',
    377 					'html' => esc_html__( 'The current version of Elementor doesn\'t have any experimental features . if you\'re feeling curious make sure to come back in future versions.', 'elementor' ),
    378 				],
    379 			];
    380 		}
    381 
    382 		if ( ! Tracker::is_allow_track() ) {
    383 			$fields += $settings->get_usage_fields();
    384 		}
    385 
    386 		$settings->add_tab(
    387 			'experiments', [
    388 				'label' => esc_html__( 'Experiments', 'elementor' ),
    389 				'sections' => [
    390 					'experiments' => [
    391 						'callback' => function() {
    392 							$this->render_settings_intro();
    393 						},
    394 						'fields' => $fields,
    395 					],
    396 				],
    397 			]
    398 		);
    399 	}
    400 
    401 	/**
    402 	 * Render Settings Intro
    403 	 *
    404 	 * @since 3.1.0
    405 	 * @access private
    406 	 */
    407 	private function render_settings_intro() {
    408 		?>
    409 		<h2><?php echo esc_html__( 'Experiments', 'elementor' ); ?></h2>
    410 		<p>
    411 			<?php
    412 				printf(
    413 					/* translators: %1$s Link open tag, %2$s: Link close tag. */
    414 					esc_html__( 'Access new and experimental features from Elementor before they\'re officially released. As these features are still in development, they are likely to change, evolve or even be removed altogether. %1$sLearn More.%2$s', 'elementor' ),
    415 					'<a href="https://go.elementor.com/wp-dash-experiments/" target="_blank">',
    416 					'</a>'
    417 				);
    418 			?>
    419 		</p>
    420 		<p><?php echo esc_html__( 'To use an experiment on your site, simply click on the dropdown next to it and switch to Active. You can always deactivate them at any time.', 'elementor' ); ?></p>
    421 		<p>
    422 			<?php
    423 				printf(
    424 					/* translators: %1$s Link open tag, %2$s: Link close tag. */
    425 					esc_html__( 'Your feedback is important - %1$shelp us%2$s improve these features by sharing your thoughts and inputs.', 'elementor' ),
    426 					'<a href="https://go.elementor.com/wp-dash-experiments-report-an-issue/" target="_blank">',
    427 					'</a>'
    428 				);
    429 			?>
    430 		</p>
    431 		<?php
    432 	}
    433 
    434 	/**
    435 	 * Render Feature Settings Field
    436 	 *
    437 	 * @since 3.1.0
    438 	 * @access private
    439 	 *
    440 	 * @param array $feature
    441 	 */
    442 	private function render_feature_settings_field( array $feature ) {
    443 		?>
    444 		<div class="e-experiment__content">
    445 			<select id="e-experiment-<?php echo $feature['name']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" class="e-experiment__select" name="<?php echo $this->get_feature_option_key( $feature['name'] ); ?>">
    446 				<?php foreach ( $this->states as $state_key => $state_title ) { ?>
    447 					<option value="<?php echo $state_key; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" <?php selected( $state_key, $feature['state'] ); ?>><?php echo $state_title; ?></option>
    448 				<?php } ?>
    449 			</select>
    450 			<p class="description"><?php echo $feature['description']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></p>
    451 			<div class="e-experiment__status"><?php echo sprintf( esc_html__( 'Status: %s', 'elementor' ), $this->release_statuses[ $feature['release_status'] ] );  // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
    452 		</div>
    453 		<?php
    454 	}
    455 
    456 	/**
    457 	 * Get Feature Settings Label HTML
    458 	 *
    459 	 * @since 3.1.0
    460 	 * @access private
    461 	 *
    462 	 * @param array $feature
    463 	 *
    464 	 * @return string
    465 	 */
    466 	private function get_feature_settings_label_html( array $feature ) {
    467 		ob_start();
    468 
    469 		$is_feature_active = $this->is_feature_active( $feature['name'] );
    470 
    471 		$indicator_classes = 'e-experiment__title__indicator';
    472 
    473 		if ( $is_feature_active ) {
    474 			$indicator_classes .= ' e-experiment__title__indicator--active';
    475 		}
    476 
    477 		if ( self::STATE_DEFAULT === $feature['state'] ) {
    478 			$indicator_tooltip = $is_feature_active ? esc_html__( 'Active by default', 'elementor' ) : esc_html__( 'Inactive by default', 'elementor' );
    479 		} else {
    480 			$indicator_tooltip = self::STATE_ACTIVE === $feature['state'] ? esc_html__( 'Active', 'elementor' ) : esc_html__( 'Inactive', 'elementor' );
    481 		}
    482 		?>
    483 		<div class="e-experiment__title">
    484 			<div class="<?php echo $indicator_classes; ?>" data-tooltip="<?php echo $indicator_tooltip; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"></div>
    485 			<label class="e-experiment__title__label" for="e-experiment-<?php echo $feature['name']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>"><?php echo $feature['title']; ?></label>
    486 		</div>
    487 		<?php
    488 
    489 		return ob_get_clean();
    490 	}
    491 
    492 	/**
    493 	 * Get Feature Settings Label HTML
    494 	 *
    495 	 * @since 3.1.0
    496 	 * @access private
    497 	 *
    498 	 * @param string $feature_name
    499 	 *
    500 	 * @return int
    501 	 */
    502 	private function get_saved_feature_state( $feature_name ) {
    503 		return get_option( $this->get_feature_option_key( $feature_name ) );
    504 	}
    505 
    506 	/**
    507 	 * Get Feature Actual State
    508 	 *
    509 	 * @since 3.1.0
    510 	 * @access private
    511 	 *
    512 	 * @param array $feature
    513 	 *
    514 	 * @return string
    515 	 */
    516 	private function get_feature_actual_state( array $feature ) {
    517 		if ( self::STATE_DEFAULT !== $feature['state'] ) {
    518 			return $feature['state'];
    519 		}
    520 
    521 		return $feature['default'];
    522 	}
    523 
    524 	/**
    525 	 * On Feature State Change
    526 	 *
    527 	 * @since 3.1.0
    528 	 * @access private
    529 	 *
    530 	 * @param array $old_feature_data
    531 	 * @param string $new_state
    532 	 */
    533 	private function on_feature_state_change( array $old_feature_data, $new_state ) {
    534 		$this->features[ $old_feature_data['name'] ]['state'] = $new_state;
    535 
    536 		$new_feature_data = $this->get_features( $old_feature_data['name'] );
    537 
    538 		$actual_old_state = $this->get_feature_actual_state( $old_feature_data );
    539 
    540 		$actual_new_state = $this->get_feature_actual_state( $new_feature_data );
    541 
    542 		if ( $actual_old_state === $actual_new_state ) {
    543 			return;
    544 		}
    545 
    546 		Plugin::$instance->files_manager->clear_cache();
    547 
    548 		if ( $new_feature_data['on_state_change'] ) {
    549 			$new_feature_data['on_state_change']( $actual_old_state, $actual_new_state );
    550 		}
    551 	}
    552 
    553 	public function __construct() {
    554 		$this->init_states();
    555 
    556 		$this->init_release_statuses();
    557 
    558 		$this->init_features();
    559 
    560 		if ( is_admin() ) {
    561 			$page_id = Settings::PAGE_ID;
    562 
    563 			add_action( "elementor/admin/after_create_settings/{$page_id}", function( Settings $settings ) {
    564 				$this->register_settings_fields( $settings );
    565 			}, 11 );
    566 		}
    567 	}
    568 }