PluginProbe
ElasticPress / 4.3.0
ElasticPress v4.3.0
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 / utils.php

utils.php in ElasticPress 4.3.0, at includes/utils.php

676 lines 17.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ElasticPress utility functions
4 *
5 * @since 3.0
6 * @package elasticpress
7 */
8
9 namespace ElasticPress\Utils;
10
11 use ElasticPress\IndexHelper;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit; // Exit if accessed directly.
15 }
16
17 /**
18 * Retrieve the EPIO subscription credentials.
19 *
20 * @since 2.5
21 * @return array
22 */
23 function get_epio_credentials() {
24 if ( defined( 'EP_CREDENTIALS' ) && EP_CREDENTIALS ) {
25 $raw_credentials = explode( ':', EP_CREDENTIALS );
26 if ( is_array( $raw_credentials ) && 2 === count( $raw_credentials ) ) {
27 $credentials = array(
28 'username' => $raw_credentials[0],
29 'token' => $raw_credentials[1],
30 );
31 }
32 $credentials = sanitize_credentials( $credentials );
33 } elseif ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK && is_epio() ) {
34 $credentials = sanitize_credentials( get_site_option( 'ep_credentials', false ) );
35 } elseif ( is_epio() ) {
36 $credentials = sanitize_credentials( get_option( 'ep_credentials', false ) );
37 } else {
38 $credentials = [
39 'username' => '',
40 'token' => '',
41 ];
42 }
43
44 if ( ! is_array( $credentials ) ) {
45 return [
46 'username' => '',
47 'token' => '',
48 ];
49 }
50
51 return $credentials;
52 }
53
54 /**
55 * Get shield credentials
56 *
57 * @since 3.0
58 * @return string|bool
59 */
60 function get_shield_credentials() {
61 if ( defined( 'ES_SHIELD' ) && ES_SHIELD ) {
62 return ES_SHIELD;
63 } elseif ( is_epio() ) {
64 $credentials = get_epio_credentials();
65
66 return $credentials['username'] . ':' . $credentials['token'];
67 }
68
69 return false;
70 }
71
72 /**
73 * Retrieve the appropriate index prefix. Will default to EP_INDEX_PREFIX constant if it exists
74 * AKA Subscription ID.
75 *
76 * @since 2.5
77 * @return string|bool
78 */
79 function get_index_prefix() {
80 if ( defined( 'EP_INDEX_PREFIX' ) && EP_INDEX_PREFIX ) {
81 $prefix = EP_INDEX_PREFIX;
82 } elseif ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK && is_epio() ) {
83 $prefix = get_site_option( 'ep_prefix', false );
84 } elseif ( is_epio() ) {
85 $prefix = get_option( 'ep_prefix', false );
86
87 if ( '-' !== substr( $prefix, - 1 ) ) {
88 $prefix .= '-';
89 }
90 } else {
91 $prefix = '';
92 }
93
94 /**
95 * Filter index prefix. Defaults to nothing
96 *
97 * @since 2.5
98 * @hook ep_index_prefix
99 * @param {string} $prefix Current prefix
100 * @return {string} New prefix
101 */
102 return apply_filters( 'ep_index_prefix', $prefix );
103 }
104
105 /**
106 * Check if the host is ElasticPress.io.
107 *
108 * @since 2.6
109 * @return bool
110 */
111 function is_epio() {
112 return filter_var( preg_match( '#elasticpress\.io#i', get_host() ), FILTER_VALIDATE_BOOLEAN );
113 }
114
115 /**
116 * Determine if we should index a blog/site
117 *
118 * @param int $blog_id Blog/site id.
119 * @since 3.2
120 * @return boolean
121 */
122 function is_site_indexable( $blog_id = null ) {
123 if ( is_multisite() ) {
124 $site = get_site( $blog_id );
125
126 $is_indexable = get_blog_option( (int) $blog_id, 'ep_indexable', 'yes' );
127
128 if ( 'no' === $is_indexable || $site['deleted'] || $site['archived'] || $site['spam'] ) {
129 return false;
130 }
131 }
132
133 return true;
134 }
135
136 /**
137 * Sanitize EPIO credentials prior to storing them.
138 *
139 * @param array $credentials Array containing username and token.
140 * @since 2.6
141 * @return array
142 */
143 function sanitize_credentials( $credentials ) {
144 if ( ! is_array( $credentials ) ) {
145 return [
146 'username' => '',
147 'token' => '',
148 ];
149 }
150
151 return [
152 'username' => ( isset( $credentials['username'] ) ) ? sanitize_text_field( $credentials['username'] ) : '',
153 'token' => ( isset( $credentials['token'] ) ) ? sanitize_text_field( $credentials['token'] ) : '',
154 ];
155 }
156
157 /**
158 * Determine if ElasticPress is in the middle of an index
159 *
160 * @since 3.0
161 * @return boolean
162 */
163 function is_indexing() {
164 /**
165 * Filter whether an index is occurring in dashboard or CLI
166 *
167 * @since 3.0
168 * @hook ep_is_indexing
169 * @param {bool} $indexing True for indexing
170 * @return {bool} New indexing value
171 */
172 return apply_filters( 'ep_is_indexing', ! empty( IndexHelper::factory()->get_index_meta() ) );
173 }
174
175 /**
176 * Check if wpcli indexing is occurring
177 *
178 * @since 3.0
179 * @return boolean
180 */
181 function is_indexing_wpcli() {
182 $index_meta = IndexHelper::factory()->get_index_meta();
183
184 /**
185 * Filter whether a CLI sync is occurring
186 *
187 * @since 3.0
188 * @hook ep_is_indexing_wpcli
189 * @param {bool} $indexing True for indexing
190 * @return {bool} New indexing value
191 */
192 return apply_filters( 'ep_is_indexing_wpcli', ( ! empty( $index_meta ) && 'cli' === $index_meta['method'] ) );
193 }
194
195 /**
196 * Retrieve the appropriate host. Will default to EP_HOST constant if it exists
197 *
198 * @since 2.1
199 * @return string|bool
200 */
201 function get_host() {
202
203 if ( defined( 'EP_HOST' ) && EP_HOST ) {
204 $host = EP_HOST;
205 } elseif ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
206 $host = get_site_option( 'ep_host', false );
207 } else {
208 $host = get_option( 'ep_host', false );
209 }
210
211 /**
212 * Filter ElasticPress host to use
213 *
214 * @since 2.1
215 * @hook ep_host
216 * @param {string} $host Current EP host
217 * @return {string} Host to use
218 */
219 return apply_filters( 'ep_host', $host );
220 }
221
222 /**
223 * Get a site. Wraps get_site for formatting purposes
224 *
225 * @param int $site_id Site/blog id
226 * @since 3.2
227 * @return array
228 */
229 function get_site( $site_id ) {
230 $site = \get_site( $site_id );
231
232 return [
233 'blog_id' => $site->blog_id,
234 'domain' => $site->domain,
235 'path' => $site->path,
236 'site_id' => $site->site_id,
237 'deleted' => $site->deleted,
238 'archived' => $site->archived,
239 'spam' => $site->spam,
240 ];
241 }
242
243 /**
244 * Wrapper function for get_sites - allows us to have one central place for the `ep_indexable_sites` filter
245 *
246 * @param int $limit The maximum amount of sites retrieved, Use 0 to return all sites.
247 * @since 3.0
248 * @return array
249 */
250 function get_sites( $limit = 0 ) {
251
252 if ( ! is_multisite() ) {
253 return [];
254 }
255
256 /**
257 * Filter arguments to use to query for sites on network
258 *
259 * @since 2.1
260 * @hook ep_indexable_sites_args
261 * @param {array} $args Array of args to query sites with. See WP_Site_Query
262 * @return {array} New arguments
263 */
264 $args = apply_filters(
265 'ep_indexable_sites_args',
266 array(
267 'limit' => $limit,
268 'number' => $limit,
269 )
270 );
271
272 $site_objects = \get_sites( $args );
273 $sites = [];
274
275 foreach ( $site_objects as $site ) {
276 $sites[] = get_site( $site->blog_id );
277 }
278
279 /**
280 * Filter indexable sites
281 *
282 * @since 3.0
283 * @hook ep_indexable_sites
284 * @param {array} $sites Current sites. Instances of WP_Site
285 * @return {array} New array of sites
286 */
287 return apply_filters( 'ep_indexable_sites', $sites );
288 }
289
290 /**
291 * Whether plugin is network activated
292 *
293 * Determines whether plugin is network activated or just on the local site.
294 *
295 * @since 3.0
296 * @param string $plugin the plugin base name.
297 * @return bool True if network activated or false.
298 */
299 function is_network_activated( $plugin ) {
300
301 $plugins = get_site_option( 'active_sitewide_plugins' );
302
303 if ( is_multisite() && isset( $plugins[ $plugin ] ) ) {
304 return true;
305 }
306
307 return false;
308 }
309
310
311 /**
312 * Performant utility function for building a term tree.
313 *
314 * Tree will look like this:
315 * [
316 * WP_Term(
317 * name
318 * slug
319 * children ->[
320 * WP_Term()
321 * ]
322 * ),
323 * WP_Term()
324 * ]
325 *
326 * @param array $all_terms Pass get_terms() as this argument where terms are objects NOT arrays.
327 * @param string|bool $orderby Can be count|name|false. This is how each tree branch will be ordered.
328 * @param string $order Can be asc|desc. This is the direction ordering will occur.
329 * @param bool $flat If false, a tree will be returned e.g. an array of top level terms
330 * which children linked within each node. If true, the tree will be
331 * "flattened".
332 * @since 2.5
333 * @return array
334 */
335 function get_term_tree( $all_terms, $orderby = 'count', $order = 'desc', $flat = false ) {
336 $terms_map = [];
337 $terms_tree = [];
338 $iteration_id = 0;
339
340 while ( true ) {
341 if ( empty( $all_terms ) ) {
342 break;
343 }
344
345 foreach ( $all_terms as $key => $term ) {
346 $iteration_id++;
347
348 if ( ! isset( $term->children ) ) {
349 $term->children = [];
350 }
351
352 if ( ! isset( $terms_map[ $term->term_id ] ) ) {
353 $terms_map[ $term->term_id ] = $term;
354 }
355
356 $parent_term = get_term( $term->parent, $term->taxonomy );
357
358 if ( empty( $term->parent ) || is_wp_error( $parent_term ) || ! $parent_term ) {
359 $term->level = 0;
360
361 if ( empty( $orderby ) ) {
362 $terms_tree[] = $term;
363 } elseif ( 'count' === $orderby ) {
364 /**
365 * We add this weird number to get past terms with the same count
366 */
367 $terms_tree[ ( ( $term->count * 10000000 ) + $iteration_id ) ] = $term;
368 } elseif ( 'name' === $orderby ) {
369 $terms_tree[ strtolower( $term->name ) ] = $term;
370 }
371
372 unset( $all_terms[ $key ] );
373 } else {
374 if ( ! empty( $terms_map[ $term->parent ] ) && isset( $terms_map[ $term->parent ]->level ) ) {
375
376 if ( empty( $orderby ) ) {
377 $terms_map[ $term->parent ]->children[] = $term;
378 } elseif ( 'count' === $orderby ) {
379 $terms_map[ $term->parent ]->children[ ( ( $term->count * 10000000 ) + $iteration_id ) ] = $term;
380 } elseif ( 'name' === $orderby ) {
381 $terms_map[ $term->parent ]->children[ $term->name ] = $term;
382 }
383
384 $parent_level = ( $terms_map[ $term->parent ]->level ) ? $terms_map[ $term->parent ]->level : 0;
385
386 $term->level = $parent_level + 1;
387 $term->parent_term = $terms_map[ $term->parent ];
388
389 unset( $all_terms[ $key ] );
390 }
391 }
392 }
393 }
394
395 if ( ! empty( $orderby ) ) {
396 if ( 'asc' === $order ) {
397 ksort( $terms_tree );
398 } else {
399 krsort( $terms_tree );
400 }
401
402 foreach ( $terms_map as $term ) {
403 if ( 'asc' === $order ) {
404 ksort( $term->children );
405 } else {
406 krsort( $term->children );
407 }
408
409 $term->children = array_values( $term->children );
410 }
411
412 $terms_tree = array_values( $terms_tree );
413 }
414
415 if ( $flat ) {
416 $flat_tree = [];
417
418 foreach ( $terms_tree as $term ) {
419 $flat_tree[] = $term;
420 $to_process = $term->children;
421 while ( ! empty( $to_process ) ) {
422 $term = array_shift( $to_process );
423 $flat_tree[] = $term;
424
425 if ( ! empty( $term->children ) ) {
426 $to_process = array_merge( $term->children, $to_process );
427 }
428 }
429 }
430
431 return $flat_tree;
432 }
433
434 return $terms_tree;
435 }
436
437 /**
438 * Returns the defaiult language for ES mapping.
439 *
440 * @return string Default EP language.
441 */
442 function get_language() {
443 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
444 $ep_language = get_site_option( 'ep_language' );
445 } else {
446 $ep_language = get_option( 'ep_language' );
447 }
448
449 $ep_language = ! empty( $ep_language ) ? $ep_language : get_locale();
450
451 /**
452 * Filter the default language to use at index time
453 *
454 * @since 3.1
455 * @param {string} The current language.
456 * @hook ep_default_language
457 * @return {string} New language
458 */
459 return apply_filters( 'ep_default_language', $ep_language );
460 }
461
462 /**
463 * Returns the status of an ongoing index operation.
464 *
465 * Returns the status of an ongoing index operation in array with the following fields:
466 * indexing | boolean | True if index operation is ongoing or false
467 * method | string | 'cli', 'web' or 'none'
468 * items_indexed | integer | Total number of items indexed
469 * total_items | integer | Total number of items indexed or -1 if not yet determined
470 * slug | string | The slug of the indexable
471 *
472 * @since 3.5.2
473 * @return array|boolean
474 */
475 function get_indexing_status() {
476
477 $index_status = false;
478
479 $index_meta = IndexHelper::factory()->get_index_meta();
480
481 if ( ! empty( $index_meta ) ) {
482 $index_status = $index_meta;
483
484 $index_status['indexing'] = true;
485
486 if ( ! empty( $index_meta['current_sync_item'] ) ) {
487 $index_status['items_indexed'] = $index_meta['current_sync_item']['synced'];
488 $index_status['url'] = $index_meta['current_sync_item']['url'] ?? ''; // Global indexables won't have a url.
489 $index_status['total_items'] = $index_meta['current_sync_item']['total'];
490 $index_status['slug'] = $index_meta['current_sync_item']['indexable'];
491 }
492
493 // Change method name for retrocompatibility.
494 // `dashboard` is used mainly because hooks names depend on that.
495 if ( ! empty( $index_status['method'] ) && 'dashboard' === $index_status['method'] ) {
496 $index_status['method'] = 'web';
497 }
498
499 if ( ! empty( $index_status['method'] ) && 'web' === $index_status['method'] ) {
500 $should_interrupt_sync = filter_var(
501 get_transient( 'ep_sync_interrupted' ),
502 FILTER_VALIDATE_BOOLEAN
503 );
504
505 $index_status['should_interrupt_sync'] = $should_interrupt_sync;
506 }
507 }
508
509 return $index_status;
510
511 }
512
513 /**
514 * Use the correct update option function depending on the context (multisite or not)
515 *
516 * @since 3.6.0
517 * @param string $option Name of the option to update.
518 * @param mixed $value Option value.
519 * @param mixed $autoload Whether to load the option when WordPress starts up.
520 * @return bool
521 */
522 function update_option( $option, $value, $autoload = null ) {
523 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
524 return \update_site_option( $option, $value );
525 }
526 return \update_option( $option, $value, $autoload );
527 }
528
529 /**
530 * Use the correct get option function depending on the context (multisite or not)
531 *
532 * @since 3.6.0
533 * @param string $option Name of the option to get.
534 * @param mixed $default_value Default value.
535 * @return bool
536 */
537 function get_option( $option, $default_value = false ) {
538 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
539 return \get_site_option( $option, $default_value );
540 }
541 return \get_option( $option, $default_value );
542 }
543
544 /**
545 * Use the correct delete option function depending on the context (multisite or not)
546 *
547 * @since 3.6.0
548 * @param string $option Name of the option to delete.
549 * @return bool
550 */
551 function delete_option( $option ) {
552 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
553 return \delete_site_option( $option );
554 }
555 return \delete_option( $option );
556 }
557
558 /**
559 * Check if queries for the current request are going to be integrated with
560 * ElasticPress.
561 *
562 * Public requests and REST API requests are integrated by default, but admin
563 * requests will only be integrated in if the `ep_admin_wp_query_integration`
564 * filter returns `true`, and and admin-ajax.php requests will only be
565 * integrated if the `ep_ajax_wp_query_integration` filter returns `true`.
566 *
567 * If specific types of requests are passed, true will only be returned if the
568 * current request also matches one of the passed types.
569 *
570 * This function is used by features to determine whether they should hook into
571 * the current request.
572 *
573 * @param string $context Slug of the feature that is performing the check.
574 * Passed to the `ep_is_integrated_request` filter.
575 * @param string[] $types Which types of request to check. Any of 'admin',
576 * 'ajax', 'public', and 'rest'. Defaults to all
577 * types.
578 * @return bool Whether the current request supports ElasticPress integration
579 * and is of a given type.
580 *
581 * @since 3.6.0
582 */
583 function is_integrated_request( $context, $types = [] ) {
584 if ( empty( $types ) ) {
585 $types = [ 'admin', 'ajax', 'public', 'rest' ];
586 }
587
588 $is_admin_request = is_admin();
589 $is_ajax_request = defined( 'DOING_AJAX' ) && DOING_AJAX;
590 $is_rest_request = defined( 'REST_REQUEST' ) && REST_REQUEST;
591 $is_integrated_admin_request = false;
592 $is_integrated_ajax_request = false;
593 $is_integrated_public_request = false;
594 $is_integrated_rest_request = false;
595
596 if ( $is_admin_request && ! $is_ajax_request && in_array( 'admin', $types, true ) ) {
597
598 /**
599 * Filter whether to integrate with admin queries.
600 *
601 * @hook ep_admin_wp_query_integration
602 * @param bool $integrate True to integrate.
603 * @return bool New value.
604 */
605 $is_integrated_admin_request = apply_filters( 'ep_admin_wp_query_integration', false );
606 }
607
608 if ( $is_ajax_request && in_array( 'ajax', $types, true ) ) {
609
610 /**
611 * Filter to integrate with admin ajax queries.
612 *
613 * @hook ep_ajax_wp_query_integration
614 * @param bool $integrate True to integrate.
615 * @return bool New value.
616 */
617 $is_integrated_ajax_request = apply_filters( 'ep_ajax_wp_query_integration', false );
618 }
619
620 if ( $is_rest_request && in_array( 'rest', $types, true ) ) {
621 $is_integrated_rest_request = true;
622 }
623
624 if ( ! $is_admin_request && ! $is_ajax_request && ! $is_rest_request && in_array( 'public', $types, true ) ) {
625 $is_integrated_public_request = true;
626 }
627
628 /**
629 * Is the current request any of the supported requests.
630 */
631 $is_integrated = (
632 $is_integrated_admin_request ||
633 $is_integrated_ajax_request ||
634 $is_integrated_public_request ||
635 $is_integrated_rest_request
636 );
637
638 /**
639 * Filter whether the queries for the current request should be integrated.
640 *
641 * @hook ep_is_integrated_request
642 * @param bool $is_integrated Whether queries for the request will be
643 * integrated.
644 * @param string $context Context for the original check. Usually the
645 * slug of the feature doing the check.
646 * @param array $types Which requests types are being checked.
647 * @return bool Whether queries for the request will be integrated.
648 *
649 * @since 3.6.2
650 */
651 return apply_filters( 'ep_is_integrated_request', $is_integrated, $context, $types );
652 }
653
654 /**
655 * Get asset info from extracted asset files
656 *
657 * @param string $slug Asset slug as defined in build/webpack configuration
658 * @param string $attribute Optional attribute to get. Can be version or dependencies
659 * @return string|array
660 */
661 function get_asset_info( $slug, $attribute = null ) {
662 if ( file_exists( EP_PATH . 'dist/js/' . $slug . '.min.asset.php' ) ) {
663 $asset = require EP_PATH . 'dist/js/' . $slug . '.min.asset.php';
664 } elseif ( file_exists( EP_PATH . 'dist/css/' . $slug . '.min.asset.php' ) ) {
665 $asset = require EP_PATH . 'dist/css/' . $slug . '.min.asset.php';
666 } else {
667 return null;
668 }
669
670 if ( ! empty( $attribute ) && isset( $asset[ $attribute ] ) ) {
671 return $asset[ $attribute ];
672 }
673
674 return $asset;
675 }
676