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 / dashboard.php

dashboard.php in ElasticPress 5.0.2, at includes/dashboard.php

985 lines 27.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Create an ElasticPress dashboard page.
4 *
5 * @package elasticpress
6 * @since 1.9
7 */
8
9 namespace ElasticPress\Dashboard;
10
11 use ElasticPress\AdminNotices;
12 use ElasticPress\Elasticsearch;
13 use ElasticPress\Features;
14 use ElasticPress\Installer;
15 use ElasticPress\Screen;
16 use ElasticPress\Stats;
17 use ElasticPress\Utils;
18
19 if ( ! defined( 'ABSPATH' ) ) {
20 exit; // Exit if accessed directly.
21 }
22
23 /**
24 * Setup actions and filters for all things settings
25 *
26 * @since 2.1
27 */
28 function setup() {
29 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) { // Must be network admin in multisite.
30 add_action( 'network_admin_menu', __NAMESPACE__ . '\action_admin_menu' );
31 add_action( 'admin_bar_menu', __NAMESPACE__ . '\action_network_admin_bar_menu', 50 );
32 }
33
34 add_action( 'admin_menu', __NAMESPACE__ . '\action_admin_menu' );
35 add_action( 'wp_ajax_ep_save_feature', __NAMESPACE__ . '\action_wp_ajax_ep_save_feature' );
36 add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\action_admin_enqueue_dashboard_scripts' );
37 add_action( 'admin_init', __NAMESPACE__ . '\maybe_clear_es_info_cache' );
38 add_action( 'admin_init', __NAMESPACE__ . '\maybe_skip_install' );
39 add_action( 'wp_ajax_ep_notice_dismiss', __NAMESPACE__ . '\action_wp_ajax_ep_notice_dismiss' );
40 add_action( 'admin_notices', __NAMESPACE__ . '\maybe_notice' );
41 add_action( 'network_admin_notices', __NAMESPACE__ . '\maybe_notice' );
42 add_filter( 'plugin_action_links', __NAMESPACE__ . '\filter_plugin_action_links', 10, 2 );
43 add_filter( 'network_admin_plugin_action_links', __NAMESPACE__ . '\filter_plugin_action_links', 10, 2 );
44 add_action( 'ep_add_query_log', __NAMESPACE__ . '\log_version_query_error' );
45 add_filter( 'ep_analyzer_language', __NAMESPACE__ . '\use_language_in_setting', 10, 2 );
46 add_filter( 'wp_kses_allowed_html', __NAMESPACE__ . '\filter_allowed_html', 10, 2 );
47 add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\block_assets' );
48
49 if ( version_compare( get_bloginfo( 'version' ), '5.8', '>=' ) ) {
50 add_action( 'block_categories_all', __NAMESPACE__ . '\block_categories' );
51 } else {
52 add_action( 'block_categories', __NAMESPACE__ . '\block_categories' );
53 }
54
55 /**
56 * Filter whether to show 'ElasticPress Indexing' option on Multisite in admin UI or not.
57 *
58 * @since 3.6.0
59 * @hook ep_show_indexing_option_on_multisite
60 * @param {bool} $show True to show.
61 * @return {bool} New value
62 */
63 $show_indexing_option_on_multisite = apply_filters( 'ep_show_indexing_option_on_multisite', defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK );
64
65 if ( $show_indexing_option_on_multisite ) {
66 add_filter( 'wpmu_blogs_columns', __NAMESPACE__ . '\filter_blogs_columns', 10, 1 );
67 add_action( 'manage_sites_custom_column', __NAMESPACE__ . '\add_blogs_column', 10, 2 );
68 add_action( 'wp_ajax_ep_site_admin', __NAMESPACE__ . '\action_wp_ajax_ep_site_admin' );
69 }
70 }
71
72 /**
73 * Add ep-html kses context
74 *
75 * @param array $allowedtags HTML tags
76 * @param string $context Context string
77 * @since 3.0
78 * @return array
79 */
80 function filter_allowed_html( $allowedtags, $context ) {
81 global $allowedposttags;
82
83 if ( 'ep-html' === $context ) {
84 $ep_tags = $allowedposttags;
85
86 $atts = [
87 'type' => true,
88 'checked' => true,
89 'selected' => true,
90 'disabled' => true,
91 'value' => true,
92 'href' => true,
93 'class' => true,
94 'data-*' => true,
95 'data-field-name' => true,
96 'data-ep-notice' => true,
97 'data-feature' => true,
98 'id' => true,
99 'style' => true,
100 'title' => true,
101 'name' => true,
102 'placeholder' => '',
103 ];
104
105 $ep_tags['input'] = $atts;
106 $ep_tags['select'] = $atts;
107 $ep_tags['textarea'] = $atts;
108 $ep_tags['option'] = $atts;
109
110 $ep_tags['form'] = [
111 'action' => true,
112 'accept' => true,
113 'accept-charset' => true,
114 'enctype' => true,
115 'method' => true,
116 'name' => true,
117 'target' => true,
118 ];
119
120 $ep_tags['a'] = array_merge(
121 $atts,
122 [ 'target' => true ]
123 );
124
125 return $ep_tags;
126 }
127
128 return $allowedtags;
129 }
130
131 /**
132 * Stores the results of the version query.
133 *
134 * @param array $query The version query.
135 * @since 3.0
136 */
137 function log_version_query_error( $query ) {
138 // Ignore fake requests like the autosuggest template generation
139 if ( ! empty( $query['request'] ) && is_array( $query['request'] ) && ! empty( $query['request']['is_ep_fake_request'] ) ) {
140 return;
141 }
142
143 $logging_key = 'logging_ep_es_info';
144
145 $logging = Utils\get_transient( $logging_key );
146
147 // Are we logging the version query results?
148 if ( '1' === $logging ) {
149 /**
150 * Filter how long results of Elasticsearch version query are stored
151 *
152 * @since 23.0
153 * @hook ep_es_info_cache_expiration
154 * @param {int} Time in seconds
155 * @return {int} New time in seconds
156 */
157 $cache_time = apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) );
158 $response_code_key = 'ep_es_info_response_code';
159 $response_error_key = 'ep_es_info_response_error';
160 $response_code = 0;
161 $response_error = '';
162
163 if ( ! empty( $query['request'] ) ) {
164 $response_code = absint( wp_remote_retrieve_response_code( $query['request'] ) );
165 $response_error = wp_remote_retrieve_response_message( $query['request'] );
166 if ( empty( $response_error ) && is_wp_error( $query['request'] ) ) {
167 $response_error = $query['request']->get_error_message();
168 }
169 }
170
171 // Store the response code, and remove the flag that says
172 // we're logging the response code so we don't log additional
173 // queries.
174 Utils\set_transient( $response_code_key, $response_code, $cache_time );
175 Utils\set_transient( $response_error_key, $response_error, $cache_time );
176 Utils\delete_transient( $logging_key );
177 }
178 }
179
180 /**
181 * Allow user to skip install process.
182 *
183 * @since 3.5
184 */
185 function maybe_skip_install() {
186 if ( ! is_admin() && ! is_network_admin() ) {
187 return;
188 }
189
190 if ( empty( $_GET['ep-skip-install'] ) || empty( $_GET['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['nonce'] ), 'ep-skip-install' ) || ! in_array( Screen::factory()->get_current_screen(), [ 'install' ], true ) ) { // phpcs:ignore WordPress.Security.NonceVerification
191 return;
192 }
193
194 if ( ! empty( $_GET['ep-skip-features'] ) ) {
195 $features = \ElasticPress\Features::factory()->registered_features;
196
197 foreach ( $features as $slug => $feature ) {
198 \ElasticPress\Features::factory()->deactivate_feature( $slug );
199 }
200 }
201
202 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
203 $redirect_url = network_admin_url( 'admin.php?page=elasticpress' );
204 } else {
205 $redirect_url = admin_url( 'admin.php?page=elasticpress' );
206 }
207 Utils\update_option( 'ep_skip_install', true );
208
209 wp_safe_redirect( $redirect_url );
210 exit;
211 }
212
213 /**
214 * Clear ES info cache whenever EP dash or settings page is viewed. Also clear cache
215 * when "try again" notification link is clicked.
216 *
217 * @since 2.3.1
218 */
219 function maybe_clear_es_info_cache() {
220 if ( ! is_admin() && ! is_network_admin() ) {
221 return;
222 }
223
224 if ( empty( $_GET['ep-retry'] ) && ! in_array( Screen::factory()->get_current_screen(), [ 'dashboard', 'settings', 'install' ], true ) ) { // phpcs:ignore WordPress.Security.NonceVerification
225 return;
226 }
227
228 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
229 delete_site_transient( 'ep_es_info' );
230 } else {
231 delete_transient( 'ep_es_info' );
232 }
233
234 if ( ! empty( $_GET['ep-retry'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
235 wp_safe_redirect( remove_query_arg( 'ep-retry' ) );
236 exit();
237 }
238 }
239
240 /**
241 * Show ElasticPress in network admin menu bar
242 *
243 * @param object $admin_bar WP_Admin Bar reference.
244 * @since 2.2
245 */
246 function action_network_admin_bar_menu( $admin_bar ) {
247 $admin_bar->add_menu(
248 array(
249 'id' => 'network-admin-elasticpress',
250 'parent' => 'network-admin',
251 'title' => 'ElasticPress',
252 'href' => esc_url( network_admin_url( 'admin.php?page=elasticpress' ) ),
253 )
254 );
255 }
256
257 /**
258 * Output dashboard link in plugin actions
259 *
260 * @param array $plugin_actions Array of HTML.
261 * @param string $plugin_file Path to plugin file.
262 * @since 2.1
263 * @return array
264 */
265 function filter_plugin_action_links( $plugin_actions, $plugin_file ) {
266
267 if ( is_network_admin() ) {
268 $url = admin_url( 'network/admin.php?page=elasticpress' );
269
270 if ( ! defined( 'EP_IS_NETWORK' ) || ! EP_IS_NETWORK ) {
271 return $plugin_actions;
272 }
273 } else {
274 $url = admin_url( 'admin.php?page=elasticpress' );
275
276 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
277 return $plugin_actions;
278 }
279 }
280
281 $new_actions = [];
282
283 if ( basename( EP_PATH ) . '/elasticpress.php' === $plugin_file ) {
284 $new_actions['ep_dashboard'] = sprintf( '<a href="%s">%s</a>', esc_url( $url ), __( 'Dashboard', 'elasticpress' ) );
285 }
286
287 return array_merge( $new_actions, $plugin_actions );
288 }
289
290 /**
291 * Output variety of dashboard notices.
292 *
293 * @param bool $force Force ES info hard lookup.
294 * @since 3.0
295 */
296 function maybe_notice( $force = false ) {
297 // Admins only.
298 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
299 if ( ! is_super_admin() || ! is_network_admin() ) {
300 return false;
301 }
302 } else {
303 if ( is_network_admin() || ! current_user_can( Utils\get_capability() ) ) {
304 return false;
305 }
306 }
307
308 /**
309 * Filter how long results of Elasticsearch version query are stored
310 *
311 * @since 23.0
312 * @hook ep_es_info_cache_expiration
313 * @param {int} Time in seconds
314 * @return {int} New time in seconds
315 */
316 $cache_time = apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) );
317
318 Utils\set_transient(
319 'logging_ep_es_info',
320 '1',
321 $cache_time
322 );
323
324 // Fetch ES version
325 Elasticsearch::factory()->get_elasticsearch_version( $force );
326
327 AdminNotices::factory()->process_notices();
328
329 $notices = AdminNotices::factory()->get_notices();
330
331 foreach ( $notices as $notice_key => $notice ) {
332 ?>
333 <div data-ep-notice="<?php echo esc_attr( $notice_key ); ?>" class="notice notice-<?php echo esc_attr( $notice['type'] ); ?> <?php
334 if ( $notice['dismiss'] ) :
335 ?>
336 is-dismissible<?php endif; ?>">
337 <p>
338 <?php echo wp_kses( $notice['html'], 'ep-html' ); ?>
339 </p>
340 </div>
341 <?php
342 }
343
344 wp_enqueue_script( 'ep_notice_script' );
345
346 return $notices;
347 }
348
349 /**
350 * Dismiss notice via ajax
351 *
352 * @since 2.2
353 */
354 function action_wp_ajax_ep_notice_dismiss() {
355 if ( empty( $_POST['notice'] ) || ! check_ajax_referer( 'ep_admin_nonce', 'nonce', false ) ) {
356 wp_send_json_error();
357 exit;
358 }
359
360 if ( ! current_user_can( Utils\get_capability() ) ) {
361 wp_send_json_error();
362 exit;
363 }
364
365 AdminNotices::factory()->dismiss_notice( sanitize_key( $_POST['notice'] ) );
366
367 wp_send_json_success();
368 }
369
370 /**
371 * Getting the status of ongoing index fired by WP CLI
372 *
373 * @since 2.1
374 */
375 function action_wp_ajax_ep_cli_index() {
376 _deprecated_function( __CLASS__, '3.6.0', '\ElasticPress\Screen::factory()->sync_screen->action_wp_ajax_ep_cli_index()' );
377 }
378
379 /**
380 * Continue index
381 *
382 * @since 2.1
383 */
384 function action_wp_ajax_ep_index() {
385 _deprecated_function( __CLASS__, '3.6.0', '\ElasticPress\Screen::factory()->sync_screen->action_wp_ajax_ep_index()' );
386 }
387
388 /**
389 * Cancel index
390 *
391 * @since 2.1
392 */
393 function action_wp_ajax_ep_cancel_index() {
394 _deprecated_function( __CLASS__, '3.6.0', '\ElasticPress\Screen::factory()->sync_screen->action_wp_ajax_ep_cancel_index()' );
395 }
396
397 /**
398 * Save individual feature settings
399 *
400 * @since 2.2
401 */
402 function action_wp_ajax_ep_save_feature() {
403 $post = wp_unslash( $_POST );
404
405 if ( empty( $post['feature'] ) || empty( $post['settings'] ) || ! check_ajax_referer( 'ep_dashboard_nonce', 'nonce', false ) ) {
406 wp_send_json_error();
407 exit;
408 }
409
410 if ( Utils\is_indexing() ) {
411 $error = new \WP_Error( 'is_indexing' );
412
413 wp_send_json_error( $error );
414 exit;
415 }
416
417 $data = Features::factory()->update_feature( $post['feature'], $post['settings'] );
418
419 // Since we deactivated, delete auto activate notice.
420 if ( empty( $post['settings']['active'] ) ) {
421 Utils\delete_option( 'ep_feature_auto_activated_sync' );
422 }
423
424 wp_send_json_success( $data );
425 }
426
427 /**
428 * Register and Enqueue JavaScripts for dashboard
429 *
430 * @since 2.2
431 */
432 function action_admin_enqueue_dashboard_scripts() {
433 if ( isset( get_current_screen()->id ) && strpos( get_current_screen()->id, 'sites-network' ) !== false ) {
434 wp_enqueue_style( 'wp-components' );
435
436 wp_enqueue_script(
437 'ep_admin_sites_scripts',
438 EP_URL . 'dist/js/sites-admin-script.js',
439 Utils\get_asset_info( 'sites-admin-script', 'dependencies' ),
440 Utils\get_asset_info( 'sites-admin-script', 'version' ),
441 true
442 );
443
444 wp_set_script_translations( 'ep_admin_sites_scripts', 'elasticpress' );
445
446 $data = [
447 'ajax_url' => admin_url( 'admin-ajax.php' ),
448 'nonce' => wp_create_nonce( 'epsa' ),
449 ];
450
451 wp_localize_script( 'ep_admin_sites_scripts', 'epsa', $data );
452 }
453
454 if ( in_array( Screen::factory()->get_current_screen(), [ 'dashboard', 'settings', 'install', 'health', 'weighting', 'synonyms', 'sync', 'status-report' ], true ) ) {
455 wp_enqueue_style(
456 'ep_admin_styles',
457 EP_URL . 'dist/css/dashboard-styles.css',
458 Utils\get_asset_info( 'dashboard-styles', 'dependencies' ),
459 Utils\get_asset_info( 'dashboard-styles', 'version' )
460 );
461 wp_enqueue_script(
462 'ep_admin_script',
463 EP_URL . 'dist/js/admin-script.js',
464 Utils\get_asset_info( 'admin-script', 'dependencies' ),
465 Utils\get_asset_info( 'admin-script', 'version' ),
466 true
467 );
468
469 wp_set_script_translations( 'ep_admin_script', 'elasticpress' );
470 }
471
472 if ( 'weighting' === Screen::factory()->get_current_screen() ) {
473
474 wp_enqueue_style(
475 'ep_weighting_styles',
476 EP_URL . 'dist/css/weighting-script.css',
477 [ 'wp-components', 'wp-edit-post' ],
478 Utils\get_asset_info( 'weighting-script', 'version' )
479 );
480
481 wp_enqueue_script(
482 'ep_weighting_script',
483 EP_URL . 'dist/js/weighting-script.js',
484 Utils\get_asset_info( 'weighting-script', 'dependencies' ),
485 Utils\get_asset_info( 'weighting-script', 'version' ),
486 true
487 );
488
489 $weighting = Features::factory()->get_registered_feature( 'search' )->weighting;
490
491 $api_url = esc_url_raw( rest_url( 'elasticpress/v1/weighting' ) );
492 $meta_mode = $weighting->get_meta_mode();
493 $weightable_fields = $weighting->get_weightable_fields();
494 $weighting_configuration = $weighting->get_weighting_configuration_with_defaults();
495
496 wp_localize_script(
497 'ep_weighting_script',
498 'epWeighting',
499 array(
500 'apiUrl' => $api_url,
501 'metaMode' => $meta_mode,
502 'weightableFields' => $weightable_fields,
503 'weightingConfiguration' => $weighting_configuration,
504 )
505 );
506
507 wp_set_script_translations( 'ep_weighting_script', 'elasticpress' );
508 }
509
510 if ( in_array( Screen::factory()->get_current_screen(), [ 'dashboard', 'install' ], true ) ) {
511 wp_enqueue_script(
512 'ep_dashboard_scripts',
513 EP_URL . 'dist/js/dashboard-script.js',
514 Utils\get_asset_info( 'dashboard-script', 'dependencies' ),
515 Utils\get_asset_info( 'dashboard-script', 'version' ),
516 true
517 );
518
519 wp_set_script_translations( 'ep_dashboard_scripts', 'elasticpress' );
520
521 $sync_url = ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) ?
522 network_admin_url( 'admin.php?page=elasticpress-sync&do_sync' ) :
523 admin_url( 'admin.php?page=elasticpress-sync&do_sync' );
524
525 $skip_url = ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) ?
526 network_admin_url( 'admin.php?page=elasticpress' ) :
527 admin_url( 'admin.php?page=elasticpress' );
528
529 $data = array(
530 'skipUrl' => add_query_arg(
531 array(
532 'ep-skip-install' => 1,
533 'ep-skip-features' => 1,
534 'nonce' => wp_create_nonce( 'ep-skip-install' ),
535 ),
536 $skip_url
537 ),
538 'syncUrl' => $sync_url,
539 );
540
541 wp_localize_script( 'ep_dashboard_scripts', 'epDash', $data );
542 }
543
544 if ( in_array( Screen::factory()->get_current_screen(), [ 'health' ], true ) && ! empty( Utils\get_host() ) ) {
545 Stats::factory()->build_stats();
546
547 $data = Stats::factory()->get_localized();
548
549 wp_enqueue_script(
550 'ep_stats',
551 EP_URL . 'dist/js/stats-script.js',
552 Utils\get_asset_info( 'stats-script', 'dependencies' ),
553 Utils\get_asset_info( 'stats-script', 'version' ),
554 true
555 );
556
557 wp_set_script_translations( 'ep_stats', 'elasticpress' );
558
559 wp_localize_script( 'ep_stats', 'epChartData', $data );
560 }
561
562 wp_register_script(
563 'ep_notice_script',
564 EP_URL . 'dist/js/notice-script.js',
565 Utils\get_asset_info( 'notice-script', 'dependencies' ),
566 Utils\get_asset_info( 'notice-script', 'version' ),
567 true
568 );
569
570 wp_set_script_translations( 'ep_notice_script', 'elasticpress' );
571
572 wp_localize_script(
573 'ep_notice_script',
574 'epAdmin',
575 array(
576 'nonce' => wp_create_nonce( 'ep_admin_nonce' ),
577 )
578 );
579 }
580
581 /**
582 * Output current ElasticPress dashboard screen
583 *
584 * @since 3.0
585 */
586 function resolve_screen() {
587 Screen::factory()->output();
588 }
589
590 /**
591 * Admin menu actions
592 *
593 * Adds options page to admin menu.
594 *
595 * @since 1.9
596 * @return void
597 */
598 function action_admin_menu() {
599 Installer::factory()->calculate_install_status();
600 if ( true !== Installer::factory()->get_install_status() && ! Utils\is_top_level_admin_context() ) {
601 return;
602 }
603
604 if ( ! Utils\is_site_indexable() && ! is_network_admin() ) {
605 return;
606 }
607
608 $capability = ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) ? Utils\get_network_capability() : Utils\get_capability();
609
610 add_menu_page(
611 'ElasticPress',
612 'ElasticPress',
613 $capability,
614 'elasticpress',
615 __NAMESPACE__ . '\resolve_screen',
616 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNzMgNzEuMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNzMgNzEuMzsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGQ9Ik0zNi41LDQuN0MxOS40LDQuNyw1LjYsMTguNiw1LjYsMzUuN2MwLDEwLDQuNywxOC45LDEyLjEsMjQuNWw0LjUtNC41YzAuMS0wLjEsMC4xLTAuMiwwLjItMC4zbDAuNy0wLjdsNi40LTYuNGMyLjEsMS4yLDQuNSwxLjksNy4xLDEuOWM4LDAsMTQuNS02LjUsMTQuNS0xNC41cy02LjUtMTQuNS0xNC41LTE0LjVTMjIsMjcuNiwyMiwzNS42YzAsMi44LDAuOCw1LjMsMi4xLDcuNWwtNi40LDYuNGMtMi45LTMuOS00LjYtOC43LTQuNi0xMy45YzAtMTIuOSwxMC41LTIzLjQsMjMuNC0yMy40czIzLjQsMTAuNSwyMy40LDIzLjRTNDkuNCw1OSwzNi41LDU5Yy0yLjEsMC00LjEtMC4zLTYtMC44bC0wLjYsMC42bC01LjIsNS40YzMuNiwxLjUsNy42LDIuMywxMS44LDIuM2MxNy4xLDAsMzAuOS0xMy45LDMwLjktMzAuOVM1My42LDQuNywzNi41LDQuN3oiLz48L3N2Zz4='
617 );
618
619 if ( ! Utils\is_top_level_admin_context() ) {
620 return;
621 }
622
623 add_submenu_page(
624 'elasticpress',
625 esc_html__( 'ElasticPress Features', 'elasticpress' ),
626 esc_html__( 'Features', 'elasticpress' ),
627 $capability,
628 'elasticpress',
629 __NAMESPACE__ . '\resolve_screen'
630 );
631
632 add_submenu_page(
633 'elasticpress',
634 esc_html__( 'ElasticPress Settings', 'elasticpress' ),
635 esc_html__( 'Settings', 'elasticpress' ),
636 $capability,
637 'elasticpress-settings',
638 __NAMESPACE__ . '\resolve_screen'
639 );
640
641 add_submenu_page(
642 'elasticpress',
643 'ElasticPress ' . esc_html__( 'Sync', 'elasticpress' ),
644 esc_html__( 'Sync', 'elasticpress' ),
645 $capability,
646 'elasticpress-sync',
647 __NAMESPACE__ . '\resolve_screen'
648 );
649
650 add_submenu_page(
651 'elasticpress',
652 esc_html__( 'ElasticPress Index Health', 'elasticpress' ),
653 esc_html__( 'Index Health', 'elasticpress' ),
654 $capability,
655 'elasticpress-health',
656 __NAMESPACE__ . '\resolve_screen'
657 );
658
659 add_submenu_page(
660 'elasticpress',
661 esc_html__( 'ElasticPress Status Report', 'elasticpress' ),
662 esc_html__( 'Status Report', 'elasticpress' ),
663 $capability,
664 'elasticpress-status-report',
665 __NAMESPACE__ . '\resolve_screen'
666 );
667 }
668
669 /**
670 * Languages supported in Elasticsearch mappings.
671 *
672 * If $format is 'elasticsearch', the array format is `Elasticsearch analyzer name => [ WordPress language package names ]`.
673 *
674 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lang-analyzer.html
675 * @since 4.7.0
676 * @param string $format Format of the return ('locales' or 'elasticsearch' )
677 * @return array
678 */
679 function get_available_languages( string $format = 'elasticsearch' ) : array {
680 /**
681 * Filter available languages in Elasticsearch.
682 *
683 * The returned array should follow the format `Elasticsearch analyzer name => [ WordPress language package names ]`.
684 *
685 * @since 4.7.0
686 * @hook ep_available_languages
687 * @param {bool} $available_languages List of available languages
688 * @return {bool} New list
689 */
690 $es_languages = apply_filters(
691 'ep_available_languages',
692 [
693 'arabic' => [ 'ar', 'ary' ],
694 'armenian' => [ 'hy' ],
695 'basque' => [ 'eu' ],
696 'bengali' => [ 'bn', 'bn_BD' ],
697 'brazilian' => [ 'pt_BR' ],
698 'bulgarian' => [ 'bg', 'bg_BG' ],
699 'catalan' => [ 'ca' ],
700 'cjk' => [], // CJK characters (not a language)
701 'czech' => [ 'cs', 'cs_CZ' ],
702 'danish' => [ 'da', 'da_DK' ],
703 'dutch' => [ 'nl_NL_formal', 'nl_NL', 'nl_BE' ],
704 'english' => [ 'en', 'en_AU', 'en_GB', 'en_NZ', 'en_CA', 'en_US', 'en_ZA' ],
705 'estonian' => [ 'et' ],
706 'finnish' => [ 'fi' ],
707 'french' => [ 'fr', 'fr_CA', 'fr_FR', 'fr_BE' ],
708 'galician' => [ 'gl_ES' ],
709 'german' => [ 'de', 'de_DE', 'de_DE_formal', 'de_CH', 'de_CH_informal', 'de_AT' ],
710 'greek' => [ 'el' ],
711 'hindi' => [ 'hi_IN' ],
712 'hungarian' => [ 'hu_HU' ],
713 'indonesian' => [ 'id_ID' ],
714 'irish' => [], // WordPress doesn't support Irish as an active locale currently
715 'italian' => [ 'it_IT' ],
716 'latvian' => [ 'lv' ],
717 'lithuanian' => [ 'lt_LT' ],
718 'norwegian' => [ 'nb_NO' ],
719 'persian' => [ 'fa_IR' ],
720 'portuguese' => [ 'pt', 'pt_AO', 'pt_PT', 'pt_PT_ao90' ],
721 'romanian' => [ 'ro_RO' ],
722 'russian' => [ 'ru_RU' ],
723 'sorani' => [ 'ckb' ],
724 'spanish' => [ 'es_CR', 'es_MX', 'es_VE', 'es_AR', 'es_CL', 'es_GT', 'es_PE', 'es_ES', 'es_UY', 'es_CO' ],
725 'swedish' => [ 'sv_SE' ],
726 'turkish' => [ 'tr_TR' ],
727 'thai' => [ 'th' ],
728 ]
729 );
730
731 if ( 'locales' === $format ) {
732 $arr = array_reduce(
733 $es_languages,
734 function ( $acc, $lang ) {
735 $lang = array_filter(
736 $lang,
737 function ( $locale ) {
738 // English is always added. This removes the duplicates
739 return ! in_array( $locale, [ 'en', 'en_US' ], true );
740 }
741 );
742 $acc = array_merge( $acc, $lang );
743 return $acc;
744 },
745 []
746 );
747 return $arr;
748 }
749
750 return $es_languages;
751 }
752
753 /**
754 * Uses the language from EP settings in mapping.
755 *
756 * @param string $language The current language.
757 * @param string $context The context where the function is running.
758 * @return string The updated language.
759 */
760 function use_language_in_setting( $language = 'english', $context = '' ) {
761 global $locale, $wp_local_package;
762
763 // Get the currently set language.
764 $ep_language = Utils\get_language();
765
766 // Bail early if no EP language is set.
767 if ( empty( $ep_language ) ) {
768 return $language;
769 }
770
771 /**
772 * WordPress does not reset the language when switch_blog() is called.
773 *
774 * @see https://core.trac.wordpress.org/ticket/49263
775 */
776 if ( 'site-default' === $ep_language ) {
777 $locale = null;
778 $wp_local_package = null;
779 $ep_language = get_locale();
780 }
781
782 require_once ABSPATH . 'wp-admin/includes/translation-install.php';
783 $translations = wp_get_available_translations();
784
785 // Default to en_US if not in the array of available translations.
786 if ( ! empty( $translations[ $ep_language ]['english_name'] ) ) {
787 $wp_language = $translations[ $ep_language ]['language'];
788 } else {
789 $wp_language = 'en_US';
790 }
791
792 $es_languages = get_available_languages();
793
794 /**
795 * Languages supported in Elasticsearch snowball token filters.
796 *
797 * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-snowball-tokenfilter.html
798 */
799 $es_snowball_languages = [
800 'Armenian',
801 'Basque',
802 'Catalan',
803 'Danish',
804 'Dutch',
805 'English',
806 'Finnish',
807 'French',
808 'German',
809 'German2', // currently unused
810 'Hungarian',
811 'Italian',
812 'Kp', // currently unused
813 'Lithuanian',
814 'Lovins', // currently unused
815 'Norwegian',
816 'Porter', // currently unused
817 'Portuguese',
818 'Romanian',
819 'Russian',
820 'Spanish',
821 'Swedish',
822 'Turkish',
823 ];
824
825 $es_snowball_similar = [
826 'Brazilian' => 'Portuguese',
827 ];
828
829 foreach ( $es_languages as $analyzer_name => $analyzer_language_codes ) {
830 if ( in_array( $wp_language, $analyzer_language_codes, true ) ) {
831 $language = $analyzer_name;
832 break;
833 }
834 }
835
836 if ( 'filter_ewp_snowball' === $context ) {
837 $uc_first_language = ucfirst( $language );
838 if ( in_array( $uc_first_language, $es_snowball_languages, true ) ) {
839 return $uc_first_language;
840 }
841
842 return $es_snowball_similar[ $uc_first_language ] ?? 'English';
843 }
844
845 if ( 'filter_ep_stop' === $context ) {
846 return "_{$language}_";
847 }
848
849 return $language;
850 }
851
852 /**
853 * Add column to sites admin table.
854 *
855 * @param string[] $columns Array of columns.
856 *
857 * @return string[]
858 */
859 function filter_blogs_columns( $columns ) {
860 $columns['elasticpress'] = esc_html__( 'ElasticPress Indexing', 'elasticpress' );
861
862 return $columns;
863 }
864
865 /**
866 * Populate column with checkbox/switch.
867 *
868 * @param string $column_name The name of the current column.
869 * @param int $blog_id The blog ID.
870 *
871 * @return void | string
872 */
873 function add_blogs_column( $column_name, $blog_id ) {
874 if ( 'elasticpress' !== $column_name ) {
875 return;
876 }
877
878 $site = get_site( $blog_id );
879 if ( $site->deleted || $site->archived || $site->spam ) {
880 return;
881 }
882
883 $is_indexable = get_site_meta( $blog_id, 'ep_indexable', true );
884 $is_indexable = '' !== $is_indexable ? $is_indexable : 'yes';
885
886 printf(
887 '<input %1$s class="index-toggle" data-blog-id="%2$s" disabled type="checkbox">',
888 checked( $is_indexable, 'yes', false ),
889 esc_attr( $blog_id )
890 );
891 }
892
893 /**
894 * AJAX callback to update ep_indexable site option.
895 */
896 function action_wp_ajax_ep_site_admin() {
897 $blog_id = ( ! empty( $_POST['blog_id'] ) ) ? absint( wp_unslash( $_POST['blog_id'] ) ) : - 1;
898 $checked = ( ! empty( $_POST['checked'] ) ) ? sanitize_text_field( wp_unslash( $_POST['checked'] ) ) : 'no';
899
900 if ( - 1 === $blog_id || ! check_ajax_referer( 'epsa', 'nonce', false ) ) {
901 return wp_send_json_error();
902 }
903
904 /**
905 * NOTE: This will be removed in ElasticPress 5.0.0. Implementations should rely on site_meta since 4.7.0.
906 */
907 $result = update_blog_option( $blog_id, 'ep_indexable', $checked );
908
909 $result = update_site_meta( $blog_id, 'ep_indexable', $checked );
910 $data = [
911 'blog_id' => $blog_id,
912 'result' => $result,
913 ];
914
915 return wp_send_json_success( $data );
916 }
917
918 /**
919 * Handle the fetch for indexing status
920 */
921 function handle_indexing_status() {
922 $indexing_status = \ElasticPress\Utils\get_indexing_status();
923
924 $status = array(
925 'method' => '',
926 'items_indexed' => 0,
927 'total_items' => 0,
928 'indexable' => '',
929 );
930
931 if ( ! empty( $indexing_status ) ) {
932 if ( isset( $indexing_status['method'] ) && 'cli' === $indexing_status['method'] ) {
933 $status['method'] = $indexing_status['method'];
934 $status['items_indexed'] = $indexing_status['items_indexed'];
935 $status['total_items'] = $indexing_status['total_items'];
936 $status['indexable'] = $indexing_status['slug'];
937 } else {
938 $status['method'] = 'dashboard';
939 $status['items_indexed'] = isset( $indexing_status['offset'] ) ? $indexing_status['offset'] : 0;
940 $status['total_items'] = isset( $indexing_status['found_items'] ) ? $indexing_status['found_items'] : 0;
941 $status['indexable'] = isset( $indexing_status['current_sync_item']['indexable'] ) ? $indexing_status['current_sync_item']['indexable'] : '';
942 }
943 }
944
945 return $status;
946 }
947
948 /**
949 * Add an ElasticPress block category.
950 *
951 * @param array $block_categories Array of categories for block types.
952 * @return array Array of categories for block types.
953 */
954 function block_categories( $block_categories ) {
955 $block_categories[] = [
956 'slug' => 'elasticpress',
957 'title' => 'ElasticPress',
958 ];
959
960 return $block_categories;
961 };
962
963 /**
964 * Enqueue shared block editor assets.
965 *
966 * @return void
967 */
968 function block_assets() {
969 wp_enqueue_script(
970 'elasticpress-blocks',
971 EP_URL . 'dist/js/blocks-script.js',
972 Utils\get_asset_info( 'blocks-script', 'dependencies' ),
973 Utils\get_asset_info( 'blocks-script', 'version' ),
974 true
975 );
976
977 wp_localize_script(
978 'elasticpress-blocks',
979 'epBlocks',
980 [
981 'syncUrl' => Utils\get_sync_url(),
982 ]
983 );
984 }
985