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 +250 -99 4.3.15.3.5 View file →
@@ -7,8 +7,11 @@
7 7 */
8 8
9 9 namespace ElasticPress;
10 10
11 +use ElasticPress\Utils;
12 +use ElasticPress\FeatureRequirementsStatus;
13 +
11 14 if ( ! defined( 'ABSPATH' ) ) {
12 15 exit; // Exit if accessed directly.
13 16 }
14 17
@@ -36,19 +39,57 @@
36 39 add_action( 'init', array( $this, 'setup_features' ), 0 );
37 40 }
38 41
39 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 + /**
40 80 * Activate a feature
41 81 *
42 - * @param string $slug Feature slug
43 - * @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
44 85 */
45 - public function activate_feature( $slug ) {
46 - $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 );
47 88 }
48 89
49 90 /**
50 - * Dectivate a feature
91 + * Deactivate a feature
51 92 *
52 93 * @param string $slug Feature slug
53 94 * @param bool $force Whether to force deactivation
54 95 * @since 2.2
@@ -64,12 +105,9 @@
64 105 * @since 3.0
65 106 * @return boolean
66 107 */
67 108 public function register_feature( Feature $feature ) {
68 - $feature_args['slug'] = $feature->slug;
69 -
70 109 $this->registered_features[ $feature->slug ] = $feature;
71 -
72 110 return true;
73 111 }
74 112
75 113 /**
@@ -89,15 +127,19 @@
89 127
90 128 /**
91 129 * Activate or deactivate a feature
92 130 *
93 - * @param string $slug Feature slug
131 + * @param string $slug Feature slug
94 132 * @param array $settings Array of settings
95 - * @param bool $force Whether to force activate/deactivate
96 - * @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
97 136 * @return array|bool
98 137 */
99 - 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 + */
100 142 $feature = $this->get_registered_feature( $slug );
101 143
102 144 if ( empty( $feature ) ) {
103 145 return false;
@@ -102,52 +144,72 @@
102 144 if ( empty( $feature ) ) {
103 145 return false;
104 146 }
105 147
106 - $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();
107 154
108 - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
109 - $feature_settings = get_site_option( 'ep_feature_settings', [] );
110 - } else {
111 - $feature_settings = get_option( 'ep_feature_settings', [] );
112 - }
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 ];
113 160
114 - if ( empty( $feature_settings[ $slug ] ) ) {
115 - // If doesn't exist, merge with feature defaults
116 - $feature_settings[ $slug ] = wp_parse_args( $settings, $feature->default_settings );
117 - } else {
118 - // If exist just merge changed values into current
119 - $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;
120 179 }
121 180
122 - // Make sure active is a proper bool
123 - $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 );
124 186
125 - if ( $feature_settings[ $slug ]['active'] ) {
126 - $feature_settings[ $slug ]['force_inactive'] = false;
127 - }
187 + Utils\update_option( 'ep_feature_settings_draft', $new_settings );
128 188
129 - // This means someone has explicitly deactivated the feature
130 - if ( $force ) {
131 - if ( ! (bool) $settings['active'] && $original_state ) {
132 - $feature_settings[ $slug ]['force_inactive'] = true;
133 - }
189 + // This is as far as we go if saving just a draft
190 + if ( 'draft' === $target ) {
191 + return true;
134 192 }
135 193
136 - $sanitize_feature_settings = apply_filters( 'ep_sanitize_feature_settings', $feature_settings, $feature );
194 + Utils\update_option( 'ep_feature_settings', $new_settings );
137 195
138 - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
139 - update_site_option( 'ep_feature_settings', $sanitize_feature_settings );
140 - } else {
141 - update_option( 'ep_feature_settings', $sanitize_feature_settings );
142 - }
196 + /**
197 + * Prepare response.
198 + */
199 + $is_active = $new_settings[ $slug ]['active'];
143 200
144 201 $data = array(
145 - 'active' => $sanitize_feature_settings[ $slug ]['active'],
202 + 'active' => $is_active,
146 203 'reindex' => false,
204 + 'setting' => '',
147 205 );
148 206
149 - 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 ) {
150 212 if ( ! empty( $feature->requires_install_reindex ) ) {
151 213 $data['reindex'] = true;
152 214 }
153 215
@@ -152,10 +214,34 @@
152 214 }
153 215
154 216 $feature->post_activation();
155 217 }
218 + if ( $was_active && ! $is_active && method_exists( $feature, 'post_deactivation' ) ) {
219 + $feature->post_deactivation();
220 + }
156 221
157 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 + /**
158 244 * Fires after activating, inactivating, or just updating a feature.
159 245 *
160 246 * @hook ep_after_update_feature
161 247 * @param {string} $feature Feature slug
@@ -180,32 +266,30 @@
180 266 * @since 2.2
181 267 */
182 268 public function handle_feature_activation() {
183 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 + /**
184 277 * Save our current requirement statuses for later
185 278 */
279 + $old_requirement_statuses = Utils\get_option( 'ep_feature_requirement_statuses', false );
186 280
187 - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
188 - $old_requirement_statuses = get_site_option( 'ep_feature_requirement_statuses', false );
189 - } else {
190 - $old_requirement_statuses = get_option( 'ep_feature_requirement_statuses', false );
191 - }
192 -
193 281 $new_requirement_statuses = [];
194 282
195 283 foreach ( $this->registered_features as $slug => $feature ) {
196 284 $status = $feature->requirements_status();
197 - $new_requirement_statuses[ $slug ] = (int) $status->code;
285 + $new_requirement_statuses[ $slug ] = (int) $status->get_code();
198 286 }
199 287
200 288 $is_wp_cli = defined( 'WP_CLI' ) && \WP_CLI;
201 289
202 290 if ( $is_wp_cli || is_admin() ) {
203 - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
204 - update_site_option( 'ep_feature_requirement_statuses', $new_requirement_statuses );
205 - } else {
206 - update_option( 'ep_feature_requirement_statuses', $new_requirement_statuses );
207 - }
291 + Utils\update_option( 'ep_feature_requirement_statuses', $new_requirement_statuses );
208 292 }
209 293
210 294 /**
211 295 * If feature settings aren't created, let's create them and finish
@@ -210,19 +294,15 @@
210 294 /**
211 295 * If feature settings aren't created, let's create them and finish
212 296 */
213 297
214 - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
215 - $feature_settings = get_site_option( 'ep_feature_settings', false );
216 - } else {
217 - $feature_settings = get_option( 'ep_feature_settings', false );
218 - }
298 + $feature_settings = Utils\get_option( 'ep_feature_settings', false );
219 299
220 300 if ( false === $feature_settings ) {
221 301 $registered_features = $this->registered_features;
222 302
223 303 foreach ( $registered_features as $slug => $feature ) {
224 - if ( 0 === $feature->requirements_status()->code ) {
304 + if ( FeatureRequirementsStatus::AUTO_ENABLED === $feature->requirements_status()->get_code() ) {
225 305 $this->activate_feature( $slug );
226 306 }
227 307 }
228 308
@@ -235,52 +315,56 @@
235 315 /**
236 316 * If a requirement status changes, we need to handle that by activating/deactivating/showing notification
237 317 */
238 318
239 - if ( ( $is_wp_cli || is_admin() ) && ! empty( $old_requirement_statuses ) ) {
240 - foreach ( $new_requirement_statuses as $slug => $code ) {
241 - $feature = $this->get_registered_feature( $slug );
319 + if ( ( ! $is_wp_cli && ! is_admin() ) || empty( $old_requirement_statuses ) ) {
320 + return;
321 + }
242 322
243 - // If a feature is forced inactive, do nothing
244 - $feature_settings = $feature->get_settings();
245 - if ( is_array( $feature_settings ) && ! empty( $feature_settings['force_inactive'] ) ) {
246 - continue;
247 - }
323 + foreach ( $new_requirement_statuses as $slug => $code ) {
324 + $feature = $this->get_registered_feature( $slug );
248 325
249 - // This is a new feature
250 - if ( ! isset( $old_requirement_statuses[ $slug ] ) ) {
251 - if ( 0 === $code ) {
252 - $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 + }
253 331
254 - if ( $feature->requires_install_reindex ) {
255 - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
256 - update_site_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) );
257 - } else {
258 - update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) );
259 - }
260 - }
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 ) );
261 341 }
262 - } else {
263 - // This feature has a 0 "ok" code when it did not before
264 - if ( $old_requirement_statuses[ $slug ] !== $code && ( 0 === $code || 2 === $code ) ) {
265 - $active = ( 0 === $code );
266 342
267 - if ( ! $feature->is_active() && $active ) {
268 - $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 );
269 355
270 - // Need to activate and maybe set a sync notice
271 - if ( $feature->requires_install_reindex ) {
272 - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
273 - update_site_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) );
274 - } else {
275 - update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) );
276 - }
277 - }
278 - } elseif ( $feature->is_active() && ! $active ) {
279 - // Just deactivate, don't force
280 - $this->deactivate_feature( $slug, false );
281 - }
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 ) );
282 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 );
283 367 }
284 368 }
285 369 }
286 370 }
@@ -298,13 +382,80 @@
298 382 * @since 2.1
299 383 */
300 384 do_action( 'ep_setup_features' );
301 385
302 - foreach ( $this->registered_features as $feature_slug => $feature ) {
303 - 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 ) ) {
304 416 $feature->setup();
305 417 }
306 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' );
307 458 }
308 459
309 460 /**
310 461 * Return singleton instance of class