PluginProbe
ElasticPress / 5.0.2
ElasticPress v5.0.2
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.0.2, at includes/classes/Features.php

397 lines 10.0 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
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit; // Exit if accessed directly.
15 }
16
17 /**
18 * Class for storing and managing features
19 */
20 class Features {
21
22 /**
23 * Stores all features that have been properly included (both active and inactive)
24 *
25 * @since 2.1
26 * @var array
27 */
28 public $registered_features = [];
29
30 /**
31 * Initiate class actions
32 *
33 * @since 2.1
34 */
35 public function setup() {
36 // hooks order matters, make sure feature activation goes before features setup
37 add_action( 'init', array( $this, 'handle_feature_activation' ), 0 );
38 add_action( 'init', array( $this, 'setup_features' ), 0 );
39 }
40
41 /**
42 * Activate a feature
43 *
44 * @param string $slug Feature slug
45 * @param string $target Whether to update a feature settings' draft or current
46 * @since 2.2, 5.0.0 added $target
47 */
48 public function activate_feature( $slug, $target = 'current' ) {
49 $this->update_feature( $slug, array( 'active' => true ), true, $target );
50 }
51
52 /**
53 * Deactivate a feature
54 *
55 * @param string $slug Feature slug
56 * @param bool $force Whether to force deactivation
57 * @since 2.2
58 */
59 public function deactivate_feature( $slug, $force = true ) {
60 $this->update_feature( $slug, array( 'active' => false ), $force );
61 }
62
63 /**
64 * Registers a feature for use in ElasticPress
65 *
66 * @param Feature $feature An instance of the Feature class
67 * @since 3.0
68 * @return boolean
69 */
70 public function register_feature( Feature $feature ) {
71 $this->registered_features[ $feature->slug ] = $feature;
72 return true;
73 }
74
75 /**
76 * Easy access function to get a Feature object from a slug
77 *
78 * @param string $slug Feature slug
79 * @since 2.1
80 * @return Feature
81 */
82 public function get_registered_feature( $slug ) {
83 if ( empty( $this->registered_features[ $slug ] ) ) {
84 return false;
85 }
86
87 return $this->registered_features[ $slug ];
88 }
89
90 /**
91 * Activate or deactivate a feature
92 *
93 * @param string $slug Feature slug
94 * @param array $settings Array of settings
95 * @param bool $force Whether to force activate/deactivate
96 * @param string $target Whether to update a feature settings' draft or current. Changing current will also save the draft.
97 * @since 2.2, 5.0.0 added $target
98 * @return array|bool
99 */
100 public function update_feature( $slug, $settings, $force = true, $target = 'current' ) {
101 /**
102 * Get the feature being saved.
103 */
104 $feature = $this->get_registered_feature( $slug );
105
106 if ( empty( $feature ) ) {
107 return false;
108 }
109
110 /**
111 * Get whether the feature was already active, and the value of the
112 * setting that requires a reindex, if it exists.
113 */
114 $was_active = $feature->is_active();
115 $setting_was = $feature->get_reindex_setting();
116
117 /**
118 * Prepare settings
119 */
120 $saved_settings = 'draft' === $target ? $this->get_feature_settings_draft() : $this->get_feature_settings();
121 $feature_settings = isset( $saved_settings[ $slug ] ) ? $saved_settings[ $slug ] : [ 'force_inactive' => false ];
122
123 $new_feature_settings = wp_parse_args(
124 $feature->default_settings,
125 [
126 'active' => false,
127 'force_inactive' => false,
128 ]
129 );
130 $new_feature_settings = wp_parse_args( $feature_settings, $new_feature_settings );
131 $new_feature_settings = wp_parse_args( $settings, $new_feature_settings );
132
133 $new_feature_settings['active'] = (bool) $new_feature_settings['active'];
134 $new_feature_settings['force_inactive'] = $new_feature_settings['active'] ? false : (bool) $new_feature_settings['force_inactive'];
135
136 /**
137 * Flag if the feature was deactivated by a forced update.
138 */
139 if ( $force && $was_active && ! $new_feature_settings['active'] ) {
140 $new_feature_settings['force_inactive'] = true;
141 }
142
143 /**
144 * Save the settings.
145 */
146 $new_settings = wp_parse_args( [ $slug => $new_feature_settings ], $saved_settings );
147 $new_settings = apply_filters( 'ep_sanitize_feature_settings', $new_settings, $feature );
148
149 Utils\update_option( 'ep_feature_settings_draft', $new_settings );
150
151 // This is as far as we go if saving just a draft
152 if ( 'draft' === $target ) {
153 return true;
154 }
155
156 Utils\update_option( 'ep_feature_settings', $new_settings );
157
158 /**
159 * Prepare response.
160 */
161 $is_active = $new_settings[ $slug ]['active'];
162
163 $data = array(
164 'active' => $is_active,
165 'reindex' => false,
166 'setting' => '',
167 );
168
169 /**
170 * If the feature requires reindexing on activation, return whether
171 * reindexing is required.
172 */
173 if ( $is_active && ! $was_active ) {
174 if ( ! empty( $feature->requires_install_reindex ) ) {
175 $data['reindex'] = true;
176 }
177
178 $feature->post_activation();
179 }
180
181 /**
182 * If the feature has a setting that requires reindexing, return
183 * whether reindexing is required and the new value of the setting.
184 */
185 $setting = $feature->setting_requires_install_reindex;
186
187 if ( $setting ) {
188 $setting_is = ! empty( $new_settings[ $slug ][ $setting ] )
189 ? $new_settings[ $slug ][ $setting ]
190 : '';
191
192 $data['setting'] = $setting_is;
193
194 /**
195 * If the setting has changed, a reindex is required.
196 */
197 if ( $is_active && $setting_is && $setting_is !== $setting_was ) {
198 $data['reindex'] = true;
199 }
200 }
201
202 /**
203 * Fires after activating, inactivating, or just updating a feature.
204 *
205 * @hook ep_after_update_feature
206 * @param {string} $feature Feature slug
207 * @param {array} $settings Feature settings
208 * @param {array} $data Feature activation data
209 *
210 * @since 3.5.5
211 */
212 do_action(
213 'ep_after_update_feature',
214 $slug,
215 $settings,
216 $data
217 );
218
219 return $data;
220 }
221
222 /**
223 * When plugins are adjusted, we need to determine how to activate/deactivate features
224 *
225 * @since 2.2
226 */
227 public function handle_feature_activation() {
228 /**
229 * Save our current requirement statuses for later
230 */
231
232 $old_requirement_statuses = Utils\get_option( 'ep_feature_requirement_statuses', false );
233
234 $new_requirement_statuses = [];
235
236 foreach ( $this->registered_features as $slug => $feature ) {
237 $status = $feature->requirements_status();
238 $new_requirement_statuses[ $slug ] = (int) $status->code;
239 }
240
241 $is_wp_cli = defined( 'WP_CLI' ) && \WP_CLI;
242
243 if ( $is_wp_cli || is_admin() ) {
244 Utils\update_option( 'ep_feature_requirement_statuses', $new_requirement_statuses );
245 }
246
247 /**
248 * If feature settings aren't created, let's create them and finish
249 */
250
251 $feature_settings = Utils\get_option( 'ep_feature_settings', false );
252
253 if ( false === $feature_settings ) {
254 $registered_features = $this->registered_features;
255
256 foreach ( $registered_features as $slug => $feature ) {
257 if ( 0 === $feature->requirements_status()->code ) {
258 $this->activate_feature( $slug );
259 }
260 }
261
262 /**
263 * Nothing else to do since we are doing initial activation
264 */
265 return;
266 }
267
268 /**
269 * If a requirement status changes, we need to handle that by activating/deactivating/showing notification
270 */
271
272 if ( ( ! $is_wp_cli && ! is_admin() ) || empty( $old_requirement_statuses ) ) {
273 return;
274 }
275
276 foreach ( $new_requirement_statuses as $slug => $code ) {
277 $feature = $this->get_registered_feature( $slug );
278
279 // If a feature is forced inactive, do nothing
280 $feature_settings = $feature->get_settings();
281 if ( is_array( $feature_settings ) && ! empty( $feature_settings['force_inactive'] ) ) {
282 continue;
283 }
284
285 // By default we will activate the feature in the current settings. If it requires a sync, we'll only update the draft
286 $activate_feature_target = 'current';
287
288 // This is a new feature
289 if ( ! isset( $old_requirement_statuses[ $slug ] ) ) {
290 if ( 0 === $code ) {
291 if ( $feature->requires_install_reindex ) {
292 $activate_feature_target = 'draft';
293 Utils\update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) );
294 }
295
296 $this->activate_feature( $slug, $activate_feature_target );
297 }
298 } else {
299 // This feature has a 0 "ok" code when it did not before
300 if ( $old_requirement_statuses[ $slug ] !== $code && ( 0 === $code || 2 === $code ) ) {
301 $active = ( 0 === $code );
302
303 if ( ! $feature->is_active() && $active ) {
304 // Need to activate and maybe set a sync notice
305 if ( $feature->requires_install_reindex ) {
306 $activate_feature_target = 'draft';
307 Utils\update_option( 'ep_feature_auto_activated_sync', sanitize_text_field( $slug ) );
308 }
309
310 $this->activate_feature( $slug, $activate_feature_target );
311 } elseif ( $feature->is_active() && ! $active ) {
312 // Just deactivate, don't force
313 $this->deactivate_feature( $slug, false );
314 }
315 }
316 }
317 }
318 }
319
320 /**
321 * Set up all active features
322 *
323 * @since 2.1
324 */
325 public function setup_features() {
326 /**
327 * Fires before features are setup
328 *
329 * @hook ep_setup_features
330 * @since 2.1
331 */
332 do_action( 'ep_setup_features' );
333
334 foreach ( $this->registered_features as $feature_slug => $feature ) {
335 if ( $feature->is_active() ) {
336 $feature->setup();
337 }
338 }
339 }
340
341 /**
342 * Return current features settings
343 *
344 * @since 5.0.0
345 * @return false|array
346 */
347 public function get_feature_settings() {
348 return Utils\get_option( 'ep_feature_settings', false );
349 }
350
351 /**
352 * Get features settings draft
353 *
354 * @since 5.0.0
355 * @return false|array
356 */
357 public function get_feature_settings_draft() {
358 return Utils\get_option( 'ep_feature_settings_draft', false );
359 }
360
361 /**
362 * Apply settings draft (if present)
363 *
364 * @since 5.0.0
365 */
366 public function apply_draft_feature_settings() {
367 $draft_settings = Utils\get_option( 'ep_feature_settings_draft', false );
368 if ( ! $draft_settings ) {
369 return;
370 }
371
372 foreach ( $draft_settings as $feature => $settings ) {
373 $this->update_feature( $feature, $settings );
374 }
375 $this->setup_features();
376
377 Utils\delete_option( 'ep_feature_settings_draft' );
378 }
379
380 /**
381 * Return singleton instance of class
382 *
383 * @return object
384 * @since 2.1
385 */
386 public static function factory() {
387 static $instance = false;
388
389 if ( ! $instance ) {
390 $instance = new self();
391 $instance->setup();
392 }
393
394 return $instance;
395 }
396 }
397