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
elasticpress / includes / classes / Features.php

Features.php in ElasticPress 5.3.5, at includes/classes/Features.php

477 lines 12.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles registering and storing feature instances
4 *
5 * @since 2.1
6 * @package elasticpress
7 */
8
9 namespace ElasticPress;
10
11 use ElasticPress\Utils;
12 use ElasticPress\FeatureRequirementsStatus;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit; // Exit if accessed directly.
16 }
17
18 /**
19 * Class for storing and managing features
20 */
21 class Features {
22
23 /**
24 * Stores all features that have been properly included (both active and inactive)
25 *
26 * @since 2.1
27 * @var array
28 */
29 public $registered_features = [];
30
31 /**
32 * Initiate class actions
33 *
34 * @since 2.1
35 */
36 public function setup() {
37 // hooks order matters, make sure feature activation goes before features setup
38 add_action( 'init', array( $this, 'handle_feature_activation' ), 0 );
39 add_action( 'init', array( $this, 'setup_features' ), 0 );
40 }
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 /**
80 * Activate a feature
81 *
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
85 */
86 public function activate_feature( $slug, $target = 'current' ) {
87 $this->update_feature( $slug, array( 'active' => true ), true, $target );
88 }
89
90 /**
91 * Deactivate a feature
92 *
93 * @param string $slug Feature slug
94 * @param bool $force Whether to force deactivation
95 * @since 2.2
96 */
97 public function deactivate_feature( $slug, $force = true ) {
98 $this->update_feature( $slug, array( 'active' => false ), $force );
99 }
100
101 /**
102 * Registers a feature for use in ElasticPress
103 *
104 * @param Feature $feature An instance of the Feature class
105 * @since 3.0
106 * @return boolean
107 */
108 public function register_feature( Feature $feature ) {
109 $this->registered_features[ $feature->slug ] = $feature;
110 return true;
111 }
112
113 /**
114 * Easy access function to get a Feature object from a slug
115 *
116 * @param string $slug Feature slug
117 * @since 2.1
118 * @return Feature
119 */
120 public function get_registered_feature( $slug ) {
121 if ( empty( $this->registered_features[ $slug ] ) ) {
122 return false;
123 }
124
125 return $this->registered_features[ $slug ];
126 }
127
128 /**
129 * Activate or deactivate a feature
130 *
131 * @param string $slug Feature slug
132 * @param array $settings Array of settings
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
136 * @return array|bool
137 */
138 public function update_feature( $slug, $settings, $force = true, $target = 'current' ) {
139 /**
140 * Get the feature being saved.
141 */
142 $feature = $this->get_registered_feature( $slug );
143
144 if ( empty( $feature ) ) {
145 return false;
146 }
147
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();
154
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 ];
160
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;
179 }
180
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 );
186
187 Utils\update_option( 'ep_feature_settings_draft', $new_settings );
188
189 // This is as far as we go if saving just a draft
190 if ( 'draft' === $target ) {
191 return true;
192 }
193
194 Utils\update_option( 'ep_feature_settings', $new_settings );
195
196 /**
197 * Prepare response.
198 */
199 $is_active = $new_settings[ $slug ]['active'];
200
201 $data = array(
202 'active' => $is_active,
203 'reindex' => false,
204 'setting' => '',
205 );
206
207 /**
208 * If the feature requires reindexing on activation, return whether
209 * reindexing is required.
210 */
211 if ( $is_active && ! $was_active ) {
212 if ( ! empty( $feature->requires_install_reindex ) ) {
213 $data['reindex'] = true;
214 }
215
216 $feature->post_activation();
217 }
218 if ( $was_active && ! $is_active && method_exists( $feature, 'post_deactivation' ) ) {
219 $feature->post_deactivation();
220 }
221
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 /**
244 * Fires after activating, inactivating, or just updating a feature.
245 *
246 * @hook ep_after_update_feature
247 * @param {string} $feature Feature slug
248 * @param {array} $settings Feature settings
249 * @param {array} $data Feature activation data
250 *
251 * @since 3.5.5
252 */
253 do_action(
254 'ep_after_update_feature',
255 $slug,
256 $settings,
257 $data
258 );
259
260 return $data;
261 }
262
263 /**
264 * When plugins are adjusted, we need to determine how to activate/deactivate features
265 *
266 * @since 2.2
267 */
268 public function handle_feature_activation() {
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 /**
277 * Save our current requirement statuses for later
278 */
279 $old_requirement_statuses = Utils\get_option( 'ep_feature_requirement_statuses', false );
280
281 $new_requirement_statuses = [];
282
283 foreach ( $this->registered_features as $slug => $feature ) {
284 $status = $feature->requirements_status();
285 $new_requirement_statuses[ $slug ] = (int) $status->get_code();
286 }
287
288 $is_wp_cli = defined( 'WP_CLI' ) && \WP_CLI;
289
290 if ( $is_wp_cli || is_admin() ) {
291 Utils\update_option( 'ep_feature_requirement_statuses', $new_requirement_statuses );
292 }
293
294 /**
295 * If feature settings aren't created, let's create them and finish
296 */
297
298 $feature_settings = Utils\get_option( 'ep_feature_settings', false );
299
300 if ( false === $feature_settings ) {
301 $registered_features = $this->registered_features;
302
303 foreach ( $registered_features as $slug => $feature ) {
304 if ( FeatureRequirementsStatus::AUTO_ENABLED === $feature->requirements_status()->get_code() ) {
305 $this->activate_feature( $slug );
306 }
307 }
308
309 /**
310 * Nothing else to do since we are doing initial activation
311 */
312 return;
313 }
314
315 /**
316 * If a requirement status changes, we need to handle that by activating/deactivating/showing notification
317 */
318
319 if ( ( ! $is_wp_cli && ! is_admin() ) || empty( $old_requirement_statuses ) ) {
320 return;
321 }
322
323 foreach ( $new_requirement_statuses as $slug => $code ) {
324 $feature = $this->get_registered_feature( $slug );
325
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 }
331
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 ) );
341 }
342
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 );
355
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 ) );
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 );
367 }
368 }
369 }
370 }
371
372 /**
373 * Set up all active features
374 *
375 * @since 2.1
376 */
377 public function setup_features() {
378 /**
379 * Fires before features are setup
380 *
381 * @hook ep_setup_features
382 * @since 2.1
383 */
384 do_action( 'ep_setup_features' );
385
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 ) ) {
416 $feature->setup();
417 }
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' );
458 }
459
460 /**
461 * Return singleton instance of class
462 *
463 * @return object
464 * @since 2.1
465 */
466 public static function factory() {
467 static $instance = false;
468
469 if ( ! $instance ) {
470 $instance = new self();
471 $instance->setup();
472 }
473
474 return $instance;
475 }
476 }
477