PluginProbe
ElasticPress / 4.6.1
ElasticPress v4.6.1
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 4.6.1, at includes/dashboard.php

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