PluginProbe
ElasticPress / 5.3.5
ElasticPress v5.3.5
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
← All changes | includes/classes/Features.php +246 -69 4.4.05.3.5 View file →
@@ -7,9 +7,10 @@
7 7 */
8 8
9 9 namespace ElasticPress;
10 10
11 -use ElasticPress\Utils as Utils;
11 +use ElasticPress\Utils;
12 +use ElasticPress\FeatureRequirementsStatus;
12 13
13 14 if ( ! defined( 'ABSPATH' ) ) {
14 15 exit; // Exit if accessed directly.
15 16 }
@@ -38,19 +39,57 @@
38 39 add_action( 'init', array( $this, 'setup_features' ), 0 );
39 40 }
40 41
41 42 /**
43 + * Get all registered feature groups.
44 + *
45 + * This centralizes group definitions and allows extensibility via a filter.
46 + *
47 + * @since 5.3.0
48 + * @return array Array of group slugs and their labels.
49 + */
50 + public function get_feature_groups() {
51 + $groups = [
52 + 'core-search' => [
53 + 'label' => esc_html__( 'Core Search', 'elasticpress' ),
54 + ],
55 + 'live-search' => [
56 + 'label' => esc_html__( 'Live Search', 'elasticpress' ),
57 + ],
58 + 'indexing-options' => [
59 + 'label' => esc_html__( 'Indexing Options', 'elasticpress' ),
60 + ],
61 + 'woocommerce' => [
62 + 'label' => esc_html__( 'WooCommerce', 'elasticpress' ),
63 + ],
64 + 'third-party-plugins' => [
65 + 'label' => esc_html__( 'Third Party Plugins', 'elasticpress' ),
66 + ],
67 + ];
68 + /**
69 + * Filter available groups.
70 + *
71 + * @hook ep_feature_groups
72 + * @since 5.3.0
73 + * @param {array} $groups Current groups
74 + * @return {array} New groups
75 + */
76 + return apply_filters( 'ep_feature_groups', $groups );
77 + }
78 +
79 + /**
42 80 * Activate a feature
43 81 *
44 - * @param string $slug Feature slug
45 - * @since 2.2
82 + * @param string $slug Feature slug
83 + * @param string $target Whether to update a feature settings' draft or current
84 + * @since 2.2, 5.0.0 added $target
46 85 */
47 - public function activate_feature( $slug ) {
48 - $this->update_feature( $slug, array( 'active' => true ) );
86 + public function activate_feature( $slug, $target = 'current' ) {
87 + $this->update_feature( $slug, array( 'active' => true ), true, $target );
49 88 }
50 89
51 90 /**
52 - * Dectivate a feature
91 + * Deactivate a feature
53 92 *
54 93 * @param string $slug Feature slug
55 94 * @param bool $force Whether to force deactivation
56 95 * @since 2.2
@@ -66,12 +105,9 @@
66 105 * @since 3.0
67 106 * @return boolean
68 107 */
69 108 public function register_feature( Feature $feature ) {
70 - $feature_args['slug'] = $feature->slug;
71 -
72 109 $this->registered_features[ $feature->slug ] = $feature;
73 -
74 110 return true;
75 111 }
76 112
77 113 /**
@@ -91,15 +127,19 @@
91 127
92 128 /**
93 129 * Activate or deactivate a feature
94 130 *
95 - * @param string $slug Feature slug
131 + * @param string $slug Feature slug
96 132 * @param array $settings Array of settings
97 - * @param bool $force Whether to force activate/deactivate
98 - * @since 2.2
133 + * @param bool $force Whether to force activate/deactivate
134 + * @param string $target Whether to update a feature settings' draft or current. Changing current will also save the draft.
135 + * @since 2.2, 5.0.0 added $target
99 136 * @return array|bool
100 137 */
101 - public function update_feature( $slug, $settings, $force = true ) {
138 + public function update_feature( $slug, $settings, $force = true, $target = 'current' ) {
139 + /**
140 + * Get the feature being saved.
141 + */
102 142 $feature = $this->get_registered_feature( $slug );
103 143
104 144 if ( empty( $feature ) ) {
105 145 return false;
@@ -104,44 +144,72 @@
104 144 if ( empty( $feature ) ) {
105 145 return false;
106 146 }
107 147
108 - $original_state = $feature->is_active();
148 + /**
149 + * Get whether the feature was already active, and the value of the
150 + * setting that requires a reindex, if it exists.
151 + */
152 + $was_active = $feature->is_active();
153 + $setting_was = $feature->get_reindex_setting();
109 154
110 - $feature_settings = Utils\get_option( 'ep_feature_settings', [] );
155 + /**
156 + * Prepare settings
157 + */
158 + $saved_settings = 'draft' === $target ? $this->get_feature_settings_draft() : $this->get_feature_settings();
159 + $feature_settings = isset( $saved_settings[ $slug ] ) ? $saved_settings[ $slug ] : [ 'force_inactive' => false ];
111 160
112 - if ( empty( $feature_settings[ $slug ] ) ) {
113 - // If doesn't exist, merge with feature defaults
114 - $feature_settings[ $slug ] = wp_parse_args( $settings, $feature->default_settings );
115 - } else {
116 - // If exist just merge changed values into current
117 - $feature_settings[ $slug ] = wp_parse_args( $settings, $feature_settings[ $slug ] );
161 + $new_feature_settings = wp_parse_args(
162 + $feature->default_settings,
163 + [
164 + 'active' => false,
165 + 'force_inactive' => false,
166 + ]
167 + );
168 + $new_feature_settings = wp_parse_args( $feature_settings, $new_feature_settings );
169 + $new_feature_settings = wp_parse_args( $settings, $new_feature_settings );
170 +
171 + $new_feature_settings['active'] = (bool) $new_feature_settings['active'];
172 + $new_feature_settings['force_inactive'] = $new_feature_settings['active'] ? false : (bool) $new_feature_settings['force_inactive'];
173 +
174 + /**
175 + * Flag if the feature was deactivated by a forced update.
176 + */
177 + if ( $force && $was_active && ! $new_feature_settings['active'] ) {
178 + $new_feature_settings['force_inactive'] = true;
118 179 }
119 180
120 - // Make sure active is a proper bool
121 - $feature_settings[ $slug ]['active'] = (bool) $feature_settings[ $slug ]['active'];
181 + /**
182 + * Save the settings.
183 + */
184 + $new_settings = wp_parse_args( [ $slug => $new_feature_settings ], $saved_settings );
185 + $new_settings = apply_filters( 'ep_sanitize_feature_settings', $new_settings, $feature );
122 186
123 - if ( $feature_settings[ $slug ]['active'] ) {
124 - $feature_settings[ $slug ]['force_inactive'] = false;
125 - }
187 + Utils\update_option( 'ep_feature_settings_draft', $new_settings );
126 188
127 - // This means someone has explicitly deactivated the feature
128 - if ( $force ) {
129 - if ( ! (bool) $settings['active'] && $original_state ) {
130 - $feature_settings[ $slug ]['force_inactive'] = true;
131 - }
189 + // This is as far as we go if saving just a draft
190 + if ( 'draft' === $target ) {
191 + return true;
132 192 }
133 193
134 - $sanitize_feature_settings = apply_filters( 'ep_sanitize_feature_settings', $feature_settings, $feature );
194 + Utils\update_option( 'ep_feature_settings', $new_settings );
135 195
136 - Utils\update_option( 'ep_feature_settings', $sanitize_feature_settings );
196 + /**
197 + * Prepare response.
198 + */
199 + $is_active = $new_settings[ $slug ]['active'];
137 200
138 201 $data = array(
139 - 'active' => $sanitize_feature_settings[ $slug ]['active'],
202 + 'active' => $is_active,
140 203 'reindex' => false,
204 + 'setting' => '',
141 205 );
142 206
143 - if ( $feature_settings[ $slug ]['active'] && ! $original_state ) {
207 + /**
208 + * If the feature requires reindexing on activation, return whether
209 + * reindexing is required.
210 + */
211 + if ( $is_active && ! $was_active ) {
144 212 if ( ! empty( $feature->requires_install_reindex ) ) {
145 213 $data['reindex'] = true;
146 214 }
147 215
@@ -146,10 +214,34 @@
146 214 }
147 215
148 216 $feature->post_activation();
149 217 }
218 + if ( $was_active && ! $is_active && method_exists( $feature, 'post_deactivation' ) ) {
219 + $feature->post_deactivation();
220 + }
150 221
151 222 /**
223 + * If the feature has a setting that requires reindexing, return
224 + * whether reindexing is required and the new value of the setting.
225 + */
226 + $setting = $feature->setting_requires_install_reindex;
227 +
228 + if ( $setting ) {
229 + $setting_is = ! empty( $new_settings[ $slug ][ $setting ] )
230 + ? $new_settings[ $slug ][ $setting ]
231 + : '';
232 +
233 + $data['setting'] = $setting_is;
234 +
235 + /**
236 + * If the setting has changed, a reindex is required.
237 + */
238 + if ( $is_active && $setting_is && $setting_is !== $setting_was ) {
239 + $data['reindex'] = true;
240 + }
241 + }
242 +
243 + /**
152 244 * Fires after activating, inactivating, or just updating a feature.
153 245 *
154 246 * @hook ep_after_update_feature
155 247 * @param {string} $feature Feature slug
@@ -174,11 +266,17 @@
174 266 * @since 2.2
175 267 */
176 268 public function handle_feature_activation() {
177 269 /**
270 + * Give a chance to features to modify each other's requirements status before the activation is handled.
271 + */
272 + foreach ( $this->registered_features as $feature ) {
273 + $feature->pre_handle_feature_activation();
274 + }
275 +
276 + /**
178 277 * Save our current requirement statuses for later
179 278 */
180 -
181 279 $old_requirement_statuses = Utils\get_option( 'ep_feature_requirement_statuses', false );
182 280
183 281 $new_requirement_statuses = [];
184 282
@@ -183,9 +281,9 @@
183 281 $new_requirement_statuses = [];
184 282
185 283 foreach ( $this->registered_features as $slug => $feature ) {
186 284 $status = $feature->requirements_status();
187 - $new_requirement_statuses[ $slug ] = (int) $status->code;
285 + $new_requirement_statuses[ $slug ] = (int) $status->get_code();
188 286 }
189 287
190 288 $is_wp_cli = defined( 'WP_CLI' ) && \WP_CLI;
191 289
@@ -202,9 +300,9 @@
202 300 if ( false === $feature_settings ) {
203 301 $registered_features = $this->registered_features;
204 302
205 303 foreach ( $registered_features as $slug => $feature ) {
206 - if ( 0 === $feature->requirements_status()->code ) {
304 + if ( FeatureRequirementsStatus::AUTO_ENABLED === $feature->requirements_status()->get_code() ) {
207 305 $this->activate_feature( $slug );
208 306 }
209 307 }
210 308
@@ -217,44 +315,56 @@
217 315 /**
218 316 * If a requirement status changes, we need to handle that by activating/deactivating/showing notification
219 317 */
220 318
221 - if ( ( $is_wp_cli || is_admin() ) && ! empty( $old_requirement_statuses ) ) {
222 - foreach ( $new_requirement_statuses as $slug => $code ) {
223 - $feature = $this->get_registered_feature( $slug );
319 + if ( ( ! $is_wp_cli && ! is_admin() ) || empty( $old_requirement_statuses ) ) {
320 + return;
321 + }
224 322
225 - // If a feature is forced inactive, do nothing
226 - $feature_settings = $feature->get_settings();
227 - if ( is_array( $feature_settings ) && ! empty( $feature_settings['force_inactive'] ) ) {
228 - continue;
229 - }
323 + foreach ( $new_requirement_statuses as $slug => $code ) {
324 + $feature = $this->get_registered_feature( $slug );
230 325
231 - // This is a new feature
232 - if ( ! isset( $old_requirement_statuses[ $slug ] ) ) {
233 - if ( 0 === $code ) {
234 - $this->activate_feature( $slug );
326 + // If a feature is forced inactive, do nothing
327 + $feature_settings = $feature->get_settings();
328 + if ( is_array( $feature_settings ) && ! empty( $feature_settings['force_inactive'] ) ) {
329 + continue;
330 + }
235 331
236 - if ( $feature->requires_install_reindex ) {
237 - Utils\update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) );
238 - }
332 + // By default we will activate the feature in the current settings. If it requires a sync, we'll only update the draft
333 + $activate_feature_target = 'current';
334 +
335 + // This is a new feature
336 + if ( ! isset( $old_requirement_statuses[ $slug ] ) ) {
337 + if ( FeatureRequirementsStatus::AUTO_ENABLED === $code ) {
338 + if ( $feature->requires_install_reindex ) {
339 + $activate_feature_target = 'draft';
340 + Utils\update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) );
239 341 }
240 - } else {
241 - // This feature has a 0 "ok" code when it did not before
242 - if ( $old_requirement_statuses[ $slug ] !== $code && ( 0 === $code || 2 === $code ) ) {
243 - $active = ( 0 === $code );
244 342
245 - if ( ! $feature->is_active() && $active ) {
246 - $this->activate_feature( $slug );
343 + $this->activate_feature( $slug, $activate_feature_target );
344 + }
345 + } elseif (
346 + $old_requirement_statuses[ $slug ] !== $code
347 + && in_array(
348 + $code,
349 + [ FeatureRequirementsStatus::AUTO_ENABLED, FeatureRequirementsStatus::FORCE_DISABLED ],
350 + true
351 + )
352 + ) {
353 + // This feature has an "ok" code when it did not before
354 + $active = ( FeatureRequirementsStatus::AUTO_ENABLED === $code );
247 355
248 - // Need to activate and maybe set a sync notice
249 - if ( $feature->requires_install_reindex ) {
250 - Utils\update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) );
251 - }
252 - } elseif ( $feature->is_active() && ! $active ) {
253 - // Just deactivate, don't force
254 - $this->deactivate_feature( $slug, false );
255 - }
356 + if ( ! $feature->is_active() && $active ) {
357 + // Need to activate and maybe set a sync notice
358 + if ( $feature->requires_install_reindex ) {
359 + $activate_feature_target = 'draft';
360 + Utils\update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) );
256 361 }
362 +
363 + $this->activate_feature( $slug, $activate_feature_target );
364 + } elseif ( $feature->is_active() && ! $active ) {
365 + // Just deactivate, don't force
366 + $this->deactivate_feature( $slug, false );
257 367 }
258 368 }
259 369 }
260 370 }
@@ -272,13 +382,80 @@
272 382 * @since 2.1
273 383 */
274 384 do_action( 'ep_setup_features' );
275 385
276 - foreach ( $this->registered_features as $feature_slug => $feature ) {
277 - if ( $feature->is_active() ) {
386 + foreach ( $this->registered_features as $feature ) {
387 + $feature->set_i18n_strings();
388 +
389 + $required_features = (array) $feature->get_required_feature();
390 + $are_required_features_active = true;
391 + foreach ( $required_features as $required_feature ) {
392 + if ( ! $this->get_registered_feature( $required_feature )->is_active() ) {
393 + $are_required_features_active = false;
394 + break;
395 + }
396 + }
397 +
398 + $should_setup = $feature->is_active()
399 + && $are_required_features_active
400 + && ! in_array(
401 + $feature->requirements_status()->get_code(),
402 + [ FeatureRequirementsStatus::FORCE_DISABLED, FeatureRequirementsStatus::TEMPORARILY_DISABLED ],
403 + true
404 + );
405 +
406 + /**
407 + * Filter whether the feature should be setup.
408 + *
409 + * @since 5.3.0
410 + * @hook ep_should_setup_feature
411 + * @param {bool} $should_setup Whether the feature should be setup.
412 + * @param {Feature} $feature The feature object.
413 + * @return {bool} New should_setup value.
414 + */
415 + if ( apply_filters( 'ep_should_setup_feature', $should_setup, $feature ) ) {
278 416 $feature->setup();
279 417 }
280 418 }
419 + }
420 +
421 + /**
422 + * Return current features settings
423 + *
424 + * @since 5.0.0
425 + * @return false|array
426 + */
427 + public function get_feature_settings() {
428 + return Utils\get_option( 'ep_feature_settings', false );
429 + }
430 +
431 + /**
432 + * Get features settings draft
433 + *
434 + * @since 5.0.0
435 + * @return false|array
436 + */
437 + public function get_feature_settings_draft() {
438 + return Utils\get_option( 'ep_feature_settings_draft', false );
439 + }
440 +
441 + /**
442 + * Apply settings draft (if present)
443 + *
444 + * @since 5.0.0
445 + */
446 + public function apply_draft_feature_settings() {
447 + $draft_settings = Utils\get_option( 'ep_feature_settings_draft', false );
448 + if ( ! $draft_settings ) {
449 + return;
450 + }
451 +
452 + foreach ( $draft_settings as $feature => $settings ) {
453 + $this->update_feature( $feature, $settings );
454 + }
455 + $this->setup_features();
456 +
457 + Utils\delete_option( 'ep_feature_settings_draft' );
281 458 }
282 459
283 460 /**
284 461 * Return singleton instance of class