PluginProbe
ElasticPress / 4.3.1
ElasticPress v4.3.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.3.1, at includes/dashboard.php

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