PluginProbe
ElasticPress / 4.2.0
ElasticPress v4.2.0
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / includes / dashboard.php

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

923 lines 26.0 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 $data = Features::factory()->update_feature( $_POST['feature'], $_POST['settings'] );
432
433 // Since we deactivated, delete auto activate notice.
434 if ( empty( $_POST['settings']['active'] ) ) {
435 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
436 delete_site_option( 'ep_feature_auto_activated_sync' );
437 } else {
438 delete_option( 'ep_feature_auto_activated_sync' );
439 }
440 }
441
442 wp_send_json_success( $data );
443 }
444
445 /**
446 * Register and Enqueue JavaScripts for dashboard
447 *
448 * @since 2.2
449 */
450 function action_admin_enqueue_dashboard_scripts() {
451 if ( isset( get_current_screen()->id ) && strpos( get_current_screen()->id, 'sites-network' ) !== false ) {
452 wp_enqueue_style( 'wp-components' );
453
454 wp_enqueue_script(
455 'ep_admin_sites_scripts',
456 EP_URL . 'dist/js/sites-admin-script.min.js',
457 Utils\get_asset_info( 'sites-admin-script', 'dependencies' ),
458 Utils\get_asset_info( 'sites-admin-script', 'version' ),
459 true
460 );
461
462 $data = [
463 'ajax_url' => admin_url( 'admin-ajax.php' ),
464 'nonce' => wp_create_nonce( 'epsa' ),
465 ];
466
467 wp_localize_script( 'ep_admin_sites_scripts', 'epsa', $data );
468 }
469
470 if ( in_array( Screen::factory()->get_current_screen(), [ 'dashboard', 'settings', 'install', 'health', 'weighting', 'synonyms', 'sync' ], true ) ) {
471 wp_enqueue_style(
472 'ep_admin_styles',
473 EP_URL . 'dist/css/dashboard-styles.min.css',
474 Utils\get_asset_info( 'dashboard-styles', 'dependencies' ),
475 Utils\get_asset_info( 'dashboard-styles', 'version' )
476 );
477 }
478
479 if ( in_array( Screen::factory()->get_current_screen(), [ 'weighting', 'install' ], true ) ) {
480 wp_enqueue_script(
481 'ep_weighting_script',
482 EP_URL . 'dist/js/weighting-script.min.js',
483 Utils\get_asset_info( 'weighting-script', 'dependencies' ),
484 Utils\get_asset_info( 'weighting-script', 'version' ),
485 true
486 );
487 }
488
489 if ( in_array( Screen::factory()->get_current_screen(), [ 'dashboard', 'install' ], true ) ) {
490 wp_enqueue_script(
491 'ep_dashboard_scripts',
492 EP_URL . 'dist/js/dashboard-script.min.js',
493 Utils\get_asset_info( 'dashboard-script', 'dependencies' ),
494 Utils\get_asset_info( 'dashboard-script', 'version' ),
495 true
496 );
497
498 $sync_url = ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) ?
499 network_admin_url( 'admin.php?page=elasticpress-sync&do_sync' ) :
500 admin_url( 'admin.php?page=elasticpress-sync&do_sync' );
501
502 $skip_url = ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) ?
503 network_admin_url( 'admin.php?page=elasticpress' ) :
504 admin_url( 'admin.php?page=elasticpress' );
505
506 $data = array(
507 'skipUrl' => add_query_arg(
508 array(
509 'ep-skip-install' => 1,
510 'ep-skip-features' => 1,
511 'nonce' => wp_create_nonce( 'ep-skip-install' ),
512 ),
513 $skip_url
514 ),
515 'syncUrl' => $sync_url,
516 );
517
518 wp_localize_script( 'ep_dashboard_scripts', 'epDash', $data );
519 }
520
521 if ( in_array( Screen::factory()->get_current_screen(), [ 'settings' ], true ) ) {
522 wp_enqueue_script(
523 'ep_settings_scripts',
524 EP_URL . 'dist/js/settings-script.min.js',
525 Utils\get_asset_info( 'settings-script', 'dependencies' ),
526 Utils\get_asset_info( 'settings-script', 'version' ),
527 true
528 );
529 }
530
531 if ( in_array( Screen::factory()->get_current_screen(), [ 'health' ], true ) && ! empty( Utils\get_host() ) ) {
532 Stats::factory()->build_stats();
533
534 $data = Stats::factory()->get_localized();
535
536 wp_enqueue_script(
537 'ep_stats',
538 EP_URL . 'dist/js/stats-script.min.js',
539 Utils\get_asset_info( 'stats-script', 'dependencies' ),
540 Utils\get_asset_info( 'stats-script', 'version' ),
541 true
542 );
543
544 wp_localize_script( 'ep_stats', 'epChartData', $data );
545 }
546
547 wp_register_script(
548 'ep_notice_script',
549 EP_URL . 'dist/js/notice-script.min.js',
550 Utils\get_asset_info( 'notice-script', 'dependencies' ),
551 Utils\get_asset_info( 'notice-script', 'version' ),
552 true
553 );
554
555 wp_localize_script(
556 'ep_notice_script',
557 'epAdmin',
558 array(
559 'nonce' => wp_create_nonce( 'ep_admin_nonce' ),
560 )
561 );
562 }
563
564 /**
565 * Admin-init actions
566 *
567 * Sets up Settings API.
568 *
569 * @since 1.9
570 * @return void
571 */
572 function action_admin_init() {
573
574 // Save options for multisite.
575 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK && isset( $_POST['ep_language'] ) ) {
576 check_admin_referer( 'elasticpress-options' );
577
578 $language = sanitize_text_field( $_POST['ep_language'] );
579 update_site_option( 'ep_language', $language );
580
581 if ( isset( $_POST['ep_host'] ) ) {
582 $host = esc_url_raw( trim( $_POST['ep_host'] ) );
583 update_site_option( 'ep_host', $host );
584 }
585
586 if ( isset( $_POST['ep_prefix'] ) ) {
587 $prefix = ( isset( $_POST['ep_prefix'] ) ) ? sanitize_text_field( wp_unslash( $_POST['ep_prefix'] ) ) : '';
588 update_site_option( 'ep_prefix', $prefix );
589 }
590
591 if ( isset( $_POST['ep_credentials'] ) ) {
592 $credentials = ( isset( $_POST['ep_credentials'] ) ) ? Utils\sanitize_credentials( $_POST['ep_credentials'] ) : [
593 'username' => '',
594 'token' => '',
595 ];
596
597 update_site_option( 'ep_credentials', $credentials );
598 }
599
600 if ( isset( $_POST['ep_bulk_setting'] ) ) {
601 update_site_option( 'ep_bulk_setting', intval( $_POST['ep_bulk_setting'] ) );
602 }
603 } else {
604 register_setting( 'elasticpress', 'ep_host', 'esc_url_raw' );
605 register_setting( 'elasticpress', 'ep_prefix', 'sanitize_text_field' );
606 register_setting( 'elasticpress', 'ep_credentials', 'ep_sanitize_credentials' );
607 register_setting( 'elasticpress', 'ep_language', 'sanitize_text_field' );
608 register_setting(
609 'elasticpress',
610 'ep_bulk_setting',
611 [
612 'type' => 'integer',
613 'sanitize_callback' => __NAMESPACE__ . '\sanitize_bulk_settings',
614 ]
615 );
616 }
617 }
618
619 /**
620 * Sanitize bulk settings.
621 *
622 * @param int $bulk_settings Number of bulk content items
623 * @return int
624 */
625 function sanitize_bulk_settings( $bulk_settings = 350 ) {
626 $bulk_settings = absint( $bulk_settings );
627
628 return ( 0 === $bulk_settings ) ? 350 : $bulk_settings;
629 }
630
631 /**
632 * Output current ElasticPress dashboard screen
633 *
634 * @since 3.0
635 */
636 function resolve_screen() {
637 Screen::factory()->output();
638 }
639
640 /**
641 * Admin menu actions
642 *
643 * Adds options page to admin menu.
644 *
645 * @since 1.9
646 * @return void
647 */
648 function action_admin_menu() {
649 $capability = 'manage_options';
650
651 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
652 $capability = 'manage_network';
653 }
654
655 add_menu_page(
656 'ElasticPress',
657 'ElasticPress',
658 $capability,
659 'elasticpress',
660 __NAMESPACE__ . '\resolve_screen',
661 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNzMgNzEuMyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNzMgNzEuMzsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxwYXRoIGQ9Ik0zNi41LDQuN0MxOS40LDQuNyw1LjYsMTguNiw1LjYsMzUuN2MwLDEwLDQuNywxOC45LDEyLjEsMjQuNWw0LjUtNC41YzAuMS0wLjEsMC4xLTAuMiwwLjItMC4zbDAuNy0wLjdsNi40LTYuNGMyLjEsMS4yLDQuNSwxLjksNy4xLDEuOWM4LDAsMTQuNS02LjUsMTQuNS0xNC41cy02LjUtMTQuNS0xNC41LTE0LjVTMjIsMjcuNiwyMiwzNS42YzAsMi44LDAuOCw1LjMsMi4xLDcuNWwtNi40LDYuNGMtMi45LTMuOS00LjYtOC43LTQuNi0xMy45YzAtMTIuOSwxMC41LTIzLjQsMjMuNC0yMy40czIzLjQsMTAuNSwyMy40LDIzLjRTNDkuNCw1OSwzNi41LDU5Yy0yLjEsMC00LjEtMC4zLTYtMC44bC0wLjYsMC42bC01LjIsNS40YzMuNiwxLjUsNy42LDIuMywxMS44LDIuM2MxNy4xLDAsMzAuOS0xMy45LDMwLjktMzAuOVM1My42LDQuNywzNi41LDQuN3oiLz48L3N2Zz4='
662 );
663
664 add_submenu_page(
665 'elasticpress',
666 esc_html__( 'ElasticPress Features', 'elasticpress' ),
667 esc_html__( 'Features', 'elasticpress' ),
668 $capability,
669 'elasticpress',
670 __NAMESPACE__ . '\resolve_screen'
671 );
672
673 add_submenu_page(
674 'elasticpress',
675 esc_html__( 'ElasticPress Settings', 'elasticpress' ),
676 esc_html__( 'Settings', 'elasticpress' ),
677 $capability,
678 'elasticpress-settings',
679 __NAMESPACE__ . '\resolve_screen'
680 );
681
682 add_submenu_page(
683 'elasticpress',
684 'ElasticPress ' . esc_html__( 'Sync', 'elasticpress' ),
685 esc_html__( 'Sync', 'elasticpress' ),
686 $capability,
687 'elasticpress-sync',
688 __NAMESPACE__ . '\resolve_screen'
689 );
690
691 add_submenu_page(
692 'elasticpress',
693 esc_html__( 'ElasticPress Index Health', 'elasticpress' ),
694 esc_html__( 'Index Health', 'elasticpress' ),
695 $capability,
696 'elasticpress-health',
697 __NAMESPACE__ . '\resolve_screen'
698 );
699 }
700
701 /**
702 * Uses the language from EP settings in mapping.
703 *
704 * @param string $language The current language.
705 * @param string $context The context where the function is running.
706 * @return string The updated language.
707 */
708 function use_language_in_setting( $language = 'english', $context = '' ) {
709 // Get the currently set language.
710 $ep_language = Utils\get_language();
711
712 // Bail early if no EP language is set.
713 if ( empty( $ep_language ) ) {
714 return $language;
715 }
716
717 require_once ABSPATH . 'wp-admin/includes/translation-install.php';
718 $translations = wp_get_available_translations();
719
720 // Bail early if not in the array of available translations.
721 if ( empty( $translations[ $ep_language ]['english_name'] ) ) {
722 return $language;
723 }
724
725 $wp_language = $translations[ $ep_language ]['language'];
726
727 /**
728 * Languages supported in Elasticsearch mappings.
729 * Array format: Elasticsearch analyzer name => WordPress language package name
730 *
731 * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lang-analyzer.html
732 */
733 $es_languages = [
734 'arabic' => [ 'ar', 'ary' ],
735 'armenian' => [ 'hy' ],
736 'basque' => [ 'eu' ],
737 'bengali' => [ 'bn', 'bn_BD' ],
738 'brazilian' => [ 'pt_BR' ],
739 'bulgarian' => [ 'bg' ],
740 'catalan' => [ 'ca' ],
741 'cjk' => [], // CJK characters (not a language)
742 'czech' => [ 'cs' ],
743 'danish' => [ 'da' ],
744 'dutch' => [ 'nl_NL_formal', 'nl_NL', 'nl_BE' ],
745 'english' => [ 'en', 'en_AU', 'en_GB', 'en_NZ', 'en_CA', 'en_ZA' ],
746 'estonian' => [ 'et' ],
747 'finnish' => [ 'fi' ],
748 'french' => [ 'fr', 'fr_CA', 'fr_FR', 'fr_BE' ],
749 'galician' => [ 'gl_ES' ],
750 'german' => [ 'de', 'de_DE', 'de_DE_formal', 'de_CH', 'de_CH_informal', 'de_AT' ],
751 'greek' => [ 'el' ],
752 'hindi' => [ 'hi_IN' ],
753 'hungarian' => [ 'hu_HU' ],
754 'indonesian' => [ 'id_ID' ],
755 'irish' => [], // WordPress doesn't support Irish as an active locale currently
756 'italian' => [ 'it_IT' ],
757 'latvian' => [ 'lv' ],
758 'lithuanian' => [ 'lt_LT' ],
759 'norwegian' => [ 'nb_NO' ],
760 'persian' => [ 'fa_IR' ],
761 'portuguese' => [ 'pt', 'pt_AO', 'pt_PT', 'pt_PT_ao90' ],
762 'romanian' => [ 'ro_RO' ],
763 'russian' => [ 'ru_RU' ],
764 'sorani' => [ 'ckb' ],
765 'spanish' => [ 'es_CR', 'es_MX', 'es_VE', 'es_AR', 'es_CL', 'es_GT', 'es_PE', 'es_ES', 'es_UY', 'es_CO' ],
766 'swedish' => [ 'sv_SE' ],
767 'turkish' => [ 'tr_TR' ],
768 'thai' => [ 'th' ],
769 ];
770
771 /**
772 * Languages supported in Elasticsearch snowball token filters.
773 *
774 * @link https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-snowball-tokenfilter.html
775 */
776 $es_snowball_languages = [
777 'Armenian',
778 'Basque',
779 'Catalan',
780 'Danish',
781 'Dutch',
782 'English',
783 'Finnish',
784 'French',
785 'German',
786 'German2', // currently unused
787 'Hungarian',
788 'Italian',
789 'Kp', // currently unused
790 'Lithuanian',
791 'Lovins', // currently unused
792 'Norwegian',
793 'Porter', // currently unused
794 'Portuguese',
795 'Romanian',
796 'Russian',
797 'Spanish',
798 'Swedish',
799 'Turkish',
800 ];
801
802 foreach ( $es_languages as $analyzer_name => $analyzer_language_codes ) {
803 if ( in_array( $wp_language, $analyzer_language_codes, true ) ) {
804 $language = $analyzer_name;
805 break;
806 }
807 }
808
809 if ( 'filter_ewp_snowball' === $context ) {
810 if ( in_array( ucfirst( $language ), $es_snowball_languages, true ) ) {
811 return ucfirst( $language );
812 }
813
814 return 'English';
815 }
816
817 return $language;
818 }
819
820 /**
821 * Add column to sites admin table.
822 *
823 * @param string[] $columns Array of columns.
824 *
825 * @return string[]
826 */
827 function filter_blogs_columns( $columns ) {
828 $columns['elasticpress'] = esc_html__( 'ElasticPress Indexing', 'elasticpress' );
829
830 return $columns;
831 }
832
833 /**
834 * Populate column with checkbox/switch.
835 *
836 * @param string $column_name The name of the current column.
837 * @param int $blog_id The blog ID.
838 *
839 * @return void | string
840 */
841 function add_blogs_column( $column_name, $blog_id ) {
842 $site = get_site( $blog_id );
843 if ( $site->deleted || $site->archived || $site->spam ) {
844 return;
845 }
846 if ( 'elasticpress' === $column_name ) {
847 $is_indexable = get_blog_option( $blog_id, 'ep_indexable', 'yes' );
848
849 printf(
850 '<input %1$s class="index-toggle" data-blog-id="%2$s" disabled type="checkbox">',
851 checked( $is_indexable, 'yes', false ),
852 esc_attr( $blog_id )
853 );
854 }
855
856 return $column_name;
857 }
858
859 /**
860 * AJAX callback to update ep_indexable site option.
861 */
862 function action_wp_ajax_ep_site_admin() {
863 $blog_id = ( ! empty( $_POST['blog_id'] ) ) ? absint( wp_unslash( $_POST['blog_id'] ) ) : - 1;
864 $checked = ( ! empty( $_POST['checked'] ) ) ? sanitize_text_field( wp_unslash( $_POST['checked'] ) ) : 'no';
865
866 if ( - 1 === $blog_id || ! check_ajax_referer( 'epsa', 'nonce', false ) ) {
867 return wp_send_json_error();
868 }
869 $old = get_blog_option( $blog_id, 'ep_indexable' );
870 $result = update_blog_option( $blog_id, 'ep_indexable', $checked );
871 $data = [
872 'blog_id' => $blog_id,
873 'result' => $result,
874 ];
875
876 return wp_send_json_success( $data );
877 }
878
879 /**
880 * Registers the API endpoint
881 */
882 function setup_endpoint() {
883 register_rest_route(
884 'elasticpress/v1',
885 'indexing_status',
886 [
887 'methods' => 'GET',
888 'callback' => __NAMESPACE__ . '\handle_indexing_status',
889 'permission_callback' => '__return_true',
890 ]
891 );
892 }
893
894 /**
895 * Handle the fetch for indexing status
896 */
897 function handle_indexing_status() {
898 $indexing_status = \ElasticPress\Utils\get_indexing_status();
899
900 $status = array(
901 'method' => '',
902 'items_indexed' => 0,
903 'total_items' => 0,
904 'indexable' => '',
905 );
906
907 if ( ! empty( $indexing_status ) ) {
908 if ( isset( $indexing_status['method'] ) && 'cli' === $indexing_status['method'] ) {
909 $status['method'] = $indexing_status['method'];
910 $status['items_indexed'] = $indexing_status['items_indexed'];
911 $status['total_items'] = $indexing_status['total_items'];
912 $status['indexable'] = $indexing_status['slug'];
913 } else {
914 $status['method'] = 'dashboard';
915 $status['items_indexed'] = isset( $indexing_status['offset'] ) ? $indexing_status['offset'] : 0;
916 $status['total_items'] = isset( $indexing_status['found_items'] ) ? $indexing_status['found_items'] : 0;
917 $status['indexable'] = isset( $indexing_status['current_sync_item']['indexable'] ) ? $indexing_status['current_sync_item']['indexable'] : '';
918 }
919 }
920
921 return $status;
922 }
923