PluginProbe
ElasticPress / 4.5.1
ElasticPress v4.5.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.5.1, at includes/dashboard.php

938 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( $_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( $_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
590 // Save options for multisite.
591 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK && isset( $_POST['ep_language'] ) ) {
592 check_admin_referer( 'elasticpress-options' );
593
594 $language = sanitize_text_field( $_POST['ep_language'] );
595 Utils\update_option( 'ep_language', $language );
596
597 if ( isset( $_POST['ep_host'] ) ) {
598 $host = esc_url_raw( trim( $_POST['ep_host'] ) );
599 Utils\update_option( 'ep_host', $host );
600 }
601
602 if ( isset( $_POST['ep_credentials'] ) ) {
603 $credentials = ( isset( $_POST['ep_credentials'] ) ) ? Utils\sanitize_credentials( $_POST['ep_credentials'] ) : [
604 'username' => '',
605 'token' => '',
606 ];
607
608 Utils\update_option( 'ep_credentials', $credentials );
609 }
610
611 if ( isset( $_POST['ep_bulk_setting'] ) ) {
612 Utils\update_option( 'ep_bulk_setting', intval( $_POST['ep_bulk_setting'] ) );
613 }
614 } else {
615 register_setting( 'elasticpress', 'ep_host', 'esc_url_raw' );
616 register_setting( 'elasticpress', 'ep_credentials', 'ep_sanitize_credentials' );
617 register_setting( 'elasticpress', 'ep_language', 'sanitize_text_field' );
618 register_setting(
619 'elasticpress',
620 'ep_bulk_setting',
621 [
622 'type' => 'integer',
623 'sanitize_callback' => __NAMESPACE__ . '\sanitize_bulk_settings',
624 ]
625 );
626 }
627 }
628
629 /**
630 * Sanitize bulk settings.
631 *
632 * @param int $bulk_settings Number of bulk content items
633 * @return int
634 */
635 function sanitize_bulk_settings( $bulk_settings = 350 ) {
636 $bulk_settings = absint( $bulk_settings );
637
638 return ( 0 === $bulk_settings ) ? 350 : $bulk_settings;
639 }
640
641 /**
642 * Output current ElasticPress dashboard screen
643 *
644 * @since 3.0
645 */
646 function resolve_screen() {
647 Screen::factory()->output();
648 }
649
650 /**
651 * Admin menu actions
652 *
653 * Adds options page to admin menu.
654 *
655 * @since 1.9
656 * @return void
657 */
658 function action_admin_menu() {
659 $capability = ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) ? Utils\get_network_capability() : Utils\get_capability();
660
661 add_menu_page(
662 'ElasticPress',
663 'ElasticPress',
664 $capability,
665 'elasticpress',
666 __NAMESPACE__ . '\resolve_screen',
667 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNzMgNzEuMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNzMgNzEuMzsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGQ9Ik0zNi41LDQuN0MxOS40LDQuNyw1LjYsMTguNiw1LjYsMzUuN2MwLDEwLDQuNywxOC45LDEyLjEsMjQuNWw0LjUtNC41YzAuMS0wLjEsMC4xLTAuMiwwLjItMC4zbDAuNy0wLjdsNi40LTYuNGMyLjEsMS4yLDQuNSwxLjksNy4xLDEuOWM4LDAsMTQuNS02LjUsMTQuNS0xNC41cy02LjUtMTQuNS0xNC41LTE0LjVTMjIsMjcuNiwyMiwzNS42YzAsMi44LDAuOCw1LjMsMi4xLDcuNWwtNi40LDYuNGMtMi45LTMuOS00LjYtOC43LTQuNi0xMy45YzAtMTIuOSwxMC41LTIzLjQsMjMuNC0yMy40czIzLjQsMTAuNSwyMy40LDIzLjRTNDkuNCw1OSwzNi41LDU5Yy0yLjEsMC00LjEtMC4zLTYtMC44bC0wLjYsMC42bC01LjIsNS40YzMuNiwxLjUsNy42LDIuMywxMS44LDIuM2MxNy4xLDAsMzAuOS0xMy45LDMwLjktMzAuOVM1My42LDQuNywzNi41LDQuN3oiLz48L3N2Zz4='
668 );
669
670 add_submenu_page(
671 'elasticpress',
672 esc_html__( 'ElasticPress Features', 'elasticpress' ),
673 esc_html__( 'Features', 'elasticpress' ),
674 $capability,
675 'elasticpress',
676 __NAMESPACE__ . '\resolve_screen'
677 );
678
679 add_submenu_page(
680 'elasticpress',
681 esc_html__( 'ElasticPress Settings', 'elasticpress' ),
682 esc_html__( 'Settings', 'elasticpress' ),
683 $capability,
684 'elasticpress-settings',
685 __NAMESPACE__ . '\resolve_screen'
686 );
687
688 add_submenu_page(
689 'elasticpress',
690 'ElasticPress ' . esc_html__( 'Sync', 'elasticpress' ),
691 esc_html__( 'Sync', 'elasticpress' ),
692 $capability,
693 'elasticpress-sync',
694 __NAMESPACE__ . '\resolve_screen'
695 );
696
697 add_submenu_page(
698 'elasticpress',
699 esc_html__( 'ElasticPress Index Health', 'elasticpress' ),
700 esc_html__( 'Index Health', 'elasticpress' ),
701 $capability,
702 'elasticpress-health',
703 __NAMESPACE__ . '\resolve_screen'
704 );
705
706 add_submenu_page(
707 'elasticpress',
708 esc_html__( 'ElasticPress Status Report', 'elasticpress' ),
709 esc_html__( 'Status Report', 'elasticpress' ),
710 $capability,
711 'elasticpress-status-report',
712 __NAMESPACE__ . '\resolve_screen'
713 );
714 }
715
716 /**
717 * Uses the language from EP settings in mapping.
718 *
719 * @param string $language The current language.
720 * @param string $context The context where the function is running.
721 * @return string The updated language.
722 */
723 function use_language_in_setting( $language = 'english', $context = '' ) {
724 // Get the currently set language.
725 $ep_language = Utils\get_language();
726
727 // Bail early if no EP language is set.
728 if ( empty( $ep_language ) ) {
729 return $language;
730 }
731
732 require_once ABSPATH . 'wp-admin/includes/translation-install.php';
733 $translations = wp_get_available_translations();
734
735 // Bail early if not in the array of available translations.
736 if ( empty( $translations[ $ep_language ]['english_name'] ) ) {
737 return $language;
738 }
739
740 $wp_language = $translations[ $ep_language ]['language'];
741
742 /**
743 * Languages supported in Elasticsearch mappings.
744 * Array format: Elasticsearch analyzer name => WordPress language package name
745 *
746 * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lang-analyzer.html
747 */
748 $es_languages = [
749 'arabic' => [ 'ar', 'ary' ],
750 'armenian' => [ 'hy' ],
751 'basque' => [ 'eu' ],
752 'bengali' => [ 'bn', 'bn_BD' ],
753 'brazilian' => [ 'pt_BR' ],
754 'bulgarian' => [ 'bg' ],
755 'catalan' => [ 'ca' ],
756 'cjk' => [], // CJK characters (not a language)
757 'czech' => [ 'cs' ],
758 'danish' => [ 'da' ],
759 'dutch' => [ 'nl_NL_formal', 'nl_NL', 'nl_BE' ],
760 'english' => [ 'en', 'en_AU', 'en_GB', 'en_NZ', 'en_CA', 'en_ZA' ],
761 'estonian' => [ 'et' ],
762 'finnish' => [ 'fi' ],
763 'french' => [ 'fr', 'fr_CA', 'fr_FR', 'fr_BE' ],
764 'galician' => [ 'gl_ES' ],
765 'german' => [ 'de', 'de_DE', 'de_DE_formal', 'de_CH', 'de_CH_informal', 'de_AT' ],
766 'greek' => [ 'el' ],
767 'hindi' => [ 'hi_IN' ],
768 'hungarian' => [ 'hu_HU' ],
769 'indonesian' => [ 'id_ID' ],
770 'irish' => [], // WordPress doesn't support Irish as an active locale currently
771 'italian' => [ 'it_IT' ],
772 'latvian' => [ 'lv' ],
773 'lithuanian' => [ 'lt_LT' ],
774 'norwegian' => [ 'nb_NO' ],
775 'persian' => [ 'fa_IR' ],
776 'portuguese' => [ 'pt', 'pt_AO', 'pt_PT', 'pt_PT_ao90' ],
777 'romanian' => [ 'ro_RO' ],
778 'russian' => [ 'ru_RU' ],
779 'sorani' => [ 'ckb' ],
780 'spanish' => [ 'es_CR', 'es_MX', 'es_VE', 'es_AR', 'es_CL', 'es_GT', 'es_PE', 'es_ES', 'es_UY', 'es_CO' ],
781 'swedish' => [ 'sv_SE' ],
782 'turkish' => [ 'tr_TR' ],
783 'thai' => [ 'th' ],
784 ];
785
786 /**
787 * Languages supported in Elasticsearch snowball token filters.
788 *
789 * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-snowball-tokenfilter.html
790 */
791 $es_snowball_languages = [
792 'Armenian',
793 'Basque',
794 'Catalan',
795 'Danish',
796 'Dutch',
797 'English',
798 'Finnish',
799 'French',
800 'German',
801 'German2', // currently unused
802 'Hungarian',
803 'Italian',
804 'Kp', // currently unused
805 'Lithuanian',
806 'Lovins', // currently unused
807 'Norwegian',
808 'Porter', // currently unused
809 'Portuguese',
810 'Romanian',
811 'Russian',
812 'Spanish',
813 'Swedish',
814 'Turkish',
815 ];
816
817 foreach ( $es_languages as $analyzer_name => $analyzer_language_codes ) {
818 if ( in_array( $wp_language, $analyzer_language_codes, true ) ) {
819 $language = $analyzer_name;
820 break;
821 }
822 }
823
824 if ( 'filter_ewp_snowball' === $context ) {
825 if ( in_array( ucfirst( $language ), $es_snowball_languages, true ) ) {
826 return ucfirst( $language );
827 }
828
829 return 'English';
830 }
831
832 return $language;
833 }
834
835 /**
836 * Add column to sites admin table.
837 *
838 * @param string[] $columns Array of columns.
839 *
840 * @return string[]
841 */
842 function filter_blogs_columns( $columns ) {
843 $columns['elasticpress'] = esc_html__( 'ElasticPress Indexing', 'elasticpress' );
844
845 return $columns;
846 }
847
848 /**
849 * Populate column with checkbox/switch.
850 *
851 * @param string $column_name The name of the current column.
852 * @param int $blog_id The blog ID.
853 *
854 * @return void | string
855 */
856 function add_blogs_column( $column_name, $blog_id ) {
857 $site = get_site( $blog_id );
858 if ( $site->deleted || $site->archived || $site->spam ) {
859 return;
860 }
861 if ( 'elasticpress' === $column_name ) {
862 $is_indexable = get_blog_option( $blog_id, 'ep_indexable', 'yes' );
863
864 printf(
865 '<input %1$s class="index-toggle" data-blog-id="%2$s" disabled type="checkbox">',
866 checked( $is_indexable, 'yes', false ),
867 esc_attr( $blog_id )
868 );
869 }
870
871 return $column_name;
872 }
873
874 /**
875 * AJAX callback to update ep_indexable site option.
876 */
877 function action_wp_ajax_ep_site_admin() {
878 $blog_id = ( ! empty( $_POST['blog_id'] ) ) ? absint( wp_unslash( $_POST['blog_id'] ) ) : - 1;
879 $checked = ( ! empty( $_POST['checked'] ) ) ? sanitize_text_field( wp_unslash( $_POST['checked'] ) ) : 'no';
880
881 if ( - 1 === $blog_id || ! check_ajax_referer( 'epsa', 'nonce', false ) ) {
882 return wp_send_json_error();
883 }
884 $old = get_blog_option( $blog_id, 'ep_indexable' );
885 $result = update_blog_option( $blog_id, 'ep_indexable', $checked );
886 $data = [
887 'blog_id' => $blog_id,
888 'result' => $result,
889 ];
890
891 return wp_send_json_success( $data );
892 }
893
894 /**
895 * Registers the API endpoint
896 */
897 function setup_endpoint() {
898 register_rest_route(
899 'elasticpress/v1',
900 'indexing_status',
901 [
902 'methods' => 'GET',
903 'callback' => __NAMESPACE__ . '\handle_indexing_status',
904 'permission_callback' => '__return_true',
905 ]
906 );
907 }
908
909 /**
910 * Handle the fetch for indexing status
911 */
912 function handle_indexing_status() {
913 $indexing_status = \ElasticPress\Utils\get_indexing_status();
914
915 $status = array(
916 'method' => '',
917 'items_indexed' => 0,
918 'total_items' => 0,
919 'indexable' => '',
920 );
921
922 if ( ! empty( $indexing_status ) ) {
923 if ( isset( $indexing_status['method'] ) && 'cli' === $indexing_status['method'] ) {
924 $status['method'] = $indexing_status['method'];
925 $status['items_indexed'] = $indexing_status['items_indexed'];
926 $status['total_items'] = $indexing_status['total_items'];
927 $status['indexable'] = $indexing_status['slug'];
928 } else {
929 $status['method'] = 'dashboard';
930 $status['items_indexed'] = isset( $indexing_status['offset'] ) ? $indexing_status['offset'] : 0;
931 $status['total_items'] = isset( $indexing_status['found_items'] ) ? $indexing_status['found_items'] : 0;
932 $status['indexable'] = isset( $indexing_status['current_sync_item']['indexable'] ) ? $indexing_status['current_sync_item']['indexable'] : '';
933 }
934 }
935
936 return $status;
937 }
938