396 lines
10 KiB
PHP
396 lines
10 KiB
PHP
<?php
|
|
/**
|
|
* Handles registering and storing feature instances
|
|
*
|
|
* @since 2.1
|
|
* @package elasticpress
|
|
*/
|
|
|
|
namespace ElasticPress;
|
|
|
|
use ElasticPress\Utils;
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit; // Exit if accessed directly.
|
|
}
|
|
|
|
/**
|
|
* Class for storing and managing features
|
|
*/
|
|
class Features {
|
|
|
|
/**
|
|
* Stores all features that have been properly included (both active and inactive)
|
|
*
|
|
* @since 2.1
|
|
* @var array
|
|
*/
|
|
public $registered_features = [];
|
|
|
|
/**
|
|
* Initiate class actions
|
|
*
|
|
* @since 2.1
|
|
*/
|
|
public function setup() {
|
|
// hooks order matters, make sure feature activation goes before features setup
|
|
add_action( 'init', array( $this, 'handle_feature_activation' ), 0 );
|
|
add_action( 'init', array( $this, 'setup_features' ), 0 );
|
|
}
|
|
|
|
/**
|
|
* Activate a feature
|
|
*
|
|
* @param string $slug Feature slug
|
|
* @param string $target Whether to update a feature settings' draft or current
|
|
* @since 2.2, 5.0.0 added $target
|
|
*/
|
|
public function activate_feature( $slug, $target = 'current' ) {
|
|
$this->update_feature( $slug, array( 'active' => true ), true, $target );
|
|
}
|
|
|
|
/**
|
|
* Deactivate a feature
|
|
*
|
|
* @param string $slug Feature slug
|
|
* @param bool $force Whether to force deactivation
|
|
* @since 2.2
|
|
*/
|
|
public function deactivate_feature( $slug, $force = true ) {
|
|
$this->update_feature( $slug, array( 'active' => false ), $force );
|
|
}
|
|
|
|
/**
|
|
* Registers a feature for use in ElasticPress
|
|
*
|
|
* @param Feature $feature An instance of the Feature class
|
|
* @since 3.0
|
|
* @return boolean
|
|
*/
|
|
public function register_feature( Feature $feature ) {
|
|
$this->registered_features[ $feature->slug ] = $feature;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Easy access function to get a Feature object from a slug
|
|
*
|
|
* @param string $slug Feature slug
|
|
* @since 2.1
|
|
* @return Feature
|
|
*/
|
|
public function get_registered_feature( $slug ) {
|
|
if ( empty( $this->registered_features[ $slug ] ) ) {
|
|
return false;
|
|
}
|
|
|
|
return $this->registered_features[ $slug ];
|
|
}
|
|
|
|
/**
|
|
* Activate or deactivate a feature
|
|
*
|
|
* @param string $slug Feature slug
|
|
* @param array $settings Array of settings
|
|
* @param bool $force Whether to force activate/deactivate
|
|
* @param string $target Whether to update a feature settings' draft or current. Changing current will also save the draft.
|
|
* @since 2.2, 5.0.0 added $target
|
|
* @return array|bool
|
|
*/
|
|
public function update_feature( $slug, $settings, $force = true, $target = 'current' ) {
|
|
/**
|
|
* Get the feature being saved.
|
|
*/
|
|
$feature = $this->get_registered_feature( $slug );
|
|
|
|
if ( empty( $feature ) ) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get whether the feature was already active, and the value of the
|
|
* setting that requires a reindex, if it exists.
|
|
*/
|
|
$was_active = $feature->is_active();
|
|
$setting_was = $feature->get_reindex_setting();
|
|
|
|
/**
|
|
* Prepare settings
|
|
*/
|
|
$saved_settings = 'draft' === $target ? $this->get_feature_settings_draft() : $this->get_feature_settings();
|
|
$feature_settings = isset( $saved_settings[ $slug ] ) ? $saved_settings[ $slug ] : [ 'force_inactive' => false ];
|
|
|
|
$new_feature_settings = wp_parse_args(
|
|
$feature->default_settings,
|
|
[
|
|
'active' => false,
|
|
'force_inactive' => false,
|
|
]
|
|
);
|
|
$new_feature_settings = wp_parse_args( $feature_settings, $new_feature_settings );
|
|
$new_feature_settings = wp_parse_args( $settings, $new_feature_settings );
|
|
|
|
$new_feature_settings['active'] = (bool) $new_feature_settings['active'];
|
|
$new_feature_settings['force_inactive'] = $new_feature_settings['active'] ? false : (bool) $new_feature_settings['force_inactive'];
|
|
|
|
/**
|
|
* Flag if the feature was deactivated by a forced update.
|
|
*/
|
|
if ( $force && $was_active && ! $new_feature_settings['active'] ) {
|
|
$new_feature_settings['force_inactive'] = true;
|
|
}
|
|
|
|
/**
|
|
* Save the settings.
|
|
*/
|
|
$new_settings = wp_parse_args( [ $slug => $new_feature_settings ], $saved_settings );
|
|
$new_settings = apply_filters( 'ep_sanitize_feature_settings', $new_settings, $feature );
|
|
|
|
Utils\update_option( 'ep_feature_settings_draft', $new_settings );
|
|
|
|
// This is as far as we go if saving just a draft
|
|
if ( 'draft' === $target ) {
|
|
return true;
|
|
}
|
|
|
|
Utils\update_option( 'ep_feature_settings', $new_settings );
|
|
|
|
/**
|
|
* Prepare response.
|
|
*/
|
|
$is_active = $new_settings[ $slug ]['active'];
|
|
|
|
$data = array(
|
|
'active' => $is_active,
|
|
'reindex' => false,
|
|
'setting' => '',
|
|
);
|
|
|
|
/**
|
|
* If the feature requires reindexing on activation, return whether
|
|
* reindexing is required.
|
|
*/
|
|
if ( $is_active && ! $was_active ) {
|
|
if ( ! empty( $feature->requires_install_reindex ) ) {
|
|
$data['reindex'] = true;
|
|
}
|
|
|
|
$feature->post_activation();
|
|
}
|
|
|
|
/**
|
|
* If the feature has a setting that requires reindexing, return
|
|
* whether reindexing is required and the new value of the setting.
|
|
*/
|
|
$setting = $feature->setting_requires_install_reindex;
|
|
|
|
if ( $setting ) {
|
|
$setting_is = ! empty( $new_settings[ $slug ][ $setting ] )
|
|
? $new_settings[ $slug ][ $setting ]
|
|
: '';
|
|
|
|
$data['setting'] = $setting_is;
|
|
|
|
/**
|
|
* If the setting has changed, a reindex is required.
|
|
*/
|
|
if ( $is_active && $setting_is && $setting_is !== $setting_was ) {
|
|
$data['reindex'] = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fires after activating, inactivating, or just updating a feature.
|
|
*
|
|
* @hook ep_after_update_feature
|
|
* @param {string} $feature Feature slug
|
|
* @param {array} $settings Feature settings
|
|
* @param {array} $data Feature activation data
|
|
*
|
|
* @since 3.5.5
|
|
*/
|
|
do_action(
|
|
'ep_after_update_feature',
|
|
$slug,
|
|
$settings,
|
|
$data
|
|
);
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* When plugins are adjusted, we need to determine how to activate/deactivate features
|
|
*
|
|
* @since 2.2
|
|
*/
|
|
public function handle_feature_activation() {
|
|
/**
|
|
* Save our current requirement statuses for later
|
|
*/
|
|
|
|
$old_requirement_statuses = Utils\get_option( 'ep_feature_requirement_statuses', false );
|
|
|
|
$new_requirement_statuses = [];
|
|
|
|
foreach ( $this->registered_features as $slug => $feature ) {
|
|
$status = $feature->requirements_status();
|
|
$new_requirement_statuses[ $slug ] = (int) $status->code;
|
|
}
|
|
|
|
$is_wp_cli = defined( 'WP_CLI' ) && \WP_CLI;
|
|
|
|
if ( $is_wp_cli || is_admin() ) {
|
|
Utils\update_option( 'ep_feature_requirement_statuses', $new_requirement_statuses );
|
|
}
|
|
|
|
/**
|
|
* If feature settings aren't created, let's create them and finish
|
|
*/
|
|
|
|
$feature_settings = Utils\get_option( 'ep_feature_settings', false );
|
|
|
|
if ( false === $feature_settings ) {
|
|
$registered_features = $this->registered_features;
|
|
|
|
foreach ( $registered_features as $slug => $feature ) {
|
|
if ( 0 === $feature->requirements_status()->code ) {
|
|
$this->activate_feature( $slug );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Nothing else to do since we are doing initial activation
|
|
*/
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* If a requirement status changes, we need to handle that by activating/deactivating/showing notification
|
|
*/
|
|
|
|
if ( ( ! $is_wp_cli && ! is_admin() ) || empty( $old_requirement_statuses ) ) {
|
|
return;
|
|
}
|
|
|
|
foreach ( $new_requirement_statuses as $slug => $code ) {
|
|
$feature = $this->get_registered_feature( $slug );
|
|
|
|
// If a feature is forced inactive, do nothing
|
|
$feature_settings = $feature->get_settings();
|
|
if ( is_array( $feature_settings ) && ! empty( $feature_settings['force_inactive'] ) ) {
|
|
continue;
|
|
}
|
|
|
|
// By default we will activate the feature in the current settings. If it requires a sync, we'll only update the draft
|
|
$activate_feature_target = 'current';
|
|
|
|
// This is a new feature
|
|
if ( ! isset( $old_requirement_statuses[ $slug ] ) ) {
|
|
if ( 0 === $code ) {
|
|
if ( $feature->requires_install_reindex ) {
|
|
$activate_feature_target = 'draft';
|
|
Utils\update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) );
|
|
}
|
|
|
|
$this->activate_feature( $slug, $activate_feature_target );
|
|
}
|
|
} else {
|
|
// This feature has a 0 "ok" code when it did not before
|
|
if ( $old_requirement_statuses[ $slug ] !== $code && ( 0 === $code || 2 === $code ) ) {
|
|
$active = ( 0 === $code );
|
|
|
|
if ( ! $feature->is_active() && $active ) {
|
|
// Need to activate and maybe set a sync notice
|
|
if ( $feature->requires_install_reindex ) {
|
|
$activate_feature_target = 'draft';
|
|
Utils\update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) );
|
|
}
|
|
|
|
$this->activate_feature( $slug, $activate_feature_target );
|
|
} elseif ( $feature->is_active() && ! $active ) {
|
|
// Just deactivate, don't force
|
|
$this->deactivate_feature( $slug, false );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set up all active features
|
|
*
|
|
* @since 2.1
|
|
*/
|
|
public function setup_features() {
|
|
/**
|
|
* Fires before features are setup
|
|
*
|
|
* @hook ep_setup_features
|
|
* @since 2.1
|
|
*/
|
|
do_action( 'ep_setup_features' );
|
|
|
|
foreach ( $this->registered_features as $feature_slug => $feature ) {
|
|
if ( $feature->is_active() ) {
|
|
$feature->setup();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return current features settings
|
|
*
|
|
* @since 5.0.0
|
|
* @return false|array
|
|
*/
|
|
public function get_feature_settings() {
|
|
return Utils\get_option( 'ep_feature_settings', false );
|
|
}
|
|
|
|
/**
|
|
* Get features settings draft
|
|
*
|
|
* @since 5.0.0
|
|
* @return false|array
|
|
*/
|
|
public function get_feature_settings_draft() {
|
|
return Utils\get_option( 'ep_feature_settings_draft', false );
|
|
}
|
|
|
|
/**
|
|
* Apply settings draft (if present)
|
|
*
|
|
* @since 5.0.0
|
|
*/
|
|
public function apply_draft_feature_settings() {
|
|
$draft_settings = Utils\get_option( 'ep_feature_settings_draft', false );
|
|
if ( ! $draft_settings ) {
|
|
return;
|
|
}
|
|
|
|
foreach ( $draft_settings as $feature => $settings ) {
|
|
$this->update_feature( $feature, $settings );
|
|
}
|
|
$this->setup_features();
|
|
|
|
Utils\delete_option( 'ep_feature_settings_draft' );
|
|
}
|
|
|
|
/**
|
|
* Return singleton instance of class
|
|
*
|
|
* @return object
|
|
* @since 2.1
|
|
*/
|
|
public static function factory() {
|
|
static $instance = false;
|
|
|
|
if ( ! $instance ) {
|
|
$instance = new self();
|
|
$instance->setup();
|
|
}
|
|
|
|
return $instance;
|
|
}
|
|
}
|