PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
← All changes | class/class-mainwp-plugins-install-list-table.php +186 -75 5.2 → 6.2.1 View file →
@@ -6,8 +6,13 @@
6 6 */
7 7
8 8 namespace MainWP\Dashboard;
9 9
10 +// Exit if accessed directly.
11 +if ( ! defined( 'ABSPATH' ) ) {
12 + exit;
13 +}
14 +
10 15 // Include class-wp-list-table.php.
11 16 if ( ! class_exists( '\WP_List_Table' ) ) {
12 17 require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php'; // NOSONAR - WP compatible.
13 18 }
@@ -87,13 +92,13 @@
87 92
88 93 if ( 'search' === $tab ) {
89 94 $tabs['search'] = esc_html__( 'Search Results', 'mainwp' );
90 95 }
91 - $tabs['featured'] = _x( 'Featured', 'Plugin Installer' );
92 - $tabs['popular'] = _x( 'Popular', 'Plugin Installer' );
93 - $tabs['recommended'] = _x( 'Recommended', 'Plugin Installer' );
96 + $tabs['featured'] = _x( 'Featured', 'Plugin Installer', 'mainwp' );
97 + $tabs['popular'] = _x( 'Popular', 'Plugin Installer', 'mainwp' );
98 + $tabs['recommended'] = _x( 'Recommended', 'Plugin Installer', 'mainwp' );
94 99 if ( 'beta' === $tab || false !== strpos( $GLOBALS['wp_version'], '-' ) ) {
95 - $tabs['beta'] = _x( 'Beta Testing', 'Plugin Installer' );
100 + $tabs['beta'] = _x( 'Beta Testing', 'Plugin Installer', 'mainwp' );
96 101 }
97 102 if ( current_user_can( 'upload_plugins' ) ) {
98 103 // No longer a real tab. Here for filter compatibility.
99 104 // Gets skipped in get_views().
@@ -202,14 +207,11 @@
202 207 *
203 208 * Check for errors.
204 209 */
205 210 public function no_items() {
206 - if ( isset( $this->error ) ) {
207 - $message = $this->error->get_error_message() . '<p class="hide-if-no-js"><a href="#" class="button" onclick="document.location.reload(); return false;">' . esc_html__( 'Try again', 'mainwp' ) . '</a></p>';
208 - } else {
209 - $message = esc_html__( 'No plugins match your request.', 'mainwp' );
210 - }
211 - echo '<div class="ui message yellow">' . $message . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput
211 + echo '<div class="ui center aligned segment">';
212 + MainWP_UI::render_empty_page_placeholder( __( 'No plugins found.', 'mainwp' ), __( 'Please try a different search.', 'mainwp' ), '<em data-emoji=":mag:" class="big"></em>' );
213 + echo '</div>';
212 214 }
213 215
214 216 /**
215 217 * Method dislpay()
@@ -216,20 +218,43 @@
216 218 *
217 219 * Override the parent display() so we can provide a different container.
218 220 */
219 221 public function display() {
222 + $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
220 223 ?>
221 - <div id="mainwp-install-plugins-container" class="ui four cards">
222 - <?php $this->display_rows_or_placeholder(); ?>
224 + <div class="ui two column stackable grid">
225 + <div class="column left aligned middle aligned">
226 + <?php if ( ! empty( $search ) ) : ?>
227 + <h3 class="ui header"><?php
228 + /* translators: %s: Search term. */
229 + echo esc_html( sprintf( __( 'Search results for: %s', 'mainwp' ), $search ) ); ?></h3>
230 + <?php endif; ?>
223 231 </div>
224 - <div class="ui hidden divider"></div>
225 - <div class="ui column grid">
226 - <div class="column right aligned">
227 - <div class="inline field">
228 - <?php $this->display_tablenav( 'bottom' ); ?>
229 - </div>
232 + <div class="column right aligned middle aligned">
233 + <div class="inline field">
234 + <?php $this->display_tablenav( 'top' ); ?>
230 235 </div>
231 236 </div>
237 + </div>
238 + <div class="ui hidden divider"></div>
239 + <div id="mainwp-install-plugins-container" class="ui three mainwp-cards cards">
240 + <?php $this->display_rows_or_placeholder(); ?>
241 + </div>
242 + <div class="ui hidden divider"></div>
243 + <div class="ui two column stackable grid">
244 + <div class="column left aligned middle aligned">
245 + <?php if ( ! empty( $search ) ) : ?>
246 + <?php
247 + /* translators: %s: Search term. */
248 + echo esc_html( sprintf( __( 'Search results for: %s', 'mainwp' ), $search ) ); ?>
249 + <?php endif; ?>
250 + </div>
251 + <div class="column right aligned middle aligned">
252 + <div class="inline field">
253 + <?php $this->display_tablenav( 'bottom' ); ?>
254 + </div>
255 + </div>
256 + </div>
232 257 <?php
233 258 }
234 259
235 260 /**
@@ -271,9 +296,10 @@
271 296
272 297 $total_items = $this->_pagination_args['total_items'];
273 298 $total_pages = (int) $this->_pagination_args['total_pages'];
274 299
275 - $perpage_paging = '<span>' . sprintf( _n( '%s item', '%s items', $total_items ), number_format_i18n( $total_items ) ) . '</span>';
300 + /* translators: %s: Number of items. */
301 + $perpage_paging = '<span>' . sprintf( _n( '%s item', '%s items', $total_items, 'mainwp' ), number_format_i18n( $total_items ) ) . '</span>';
276 302
277 303 $current = (int) $this->get_pagenum();
278 304 $removable_query_args = wp_removable_query_args();
279 305
@@ -282,12 +308,12 @@
282 308 $current_url = remove_query_arg( $removable_query_args, $current_url );
283 309
284 310 $page_links = array();
285 311
286 - $disable_first = false;
287 - $disable_last = false;
288 - $disable_prev = false;
289 - $disable_next = false;
312 + $disable_first = false;
313 + $disable_last = false;
314 + $disable_prev = false;
315 + $disable_next = false;
290 316
291 317 if ( 1 === $current ) {
292 318 $disable_first = true;
293 319 $disable_prev = true;
@@ -305,15 +331,15 @@
305 331
306 332 if ( $disable_first ) {
307 333 $page_links[] = '<a class="item disabled" aria-hidden="true"><i class="angle double left icon"></i></a>';
308 334 } else {
309 - $page_links[] = sprintf( "<a class='item' href='%s' title='" . esc_html__( 'First page' ) . "' aria-hidden='true'>%s</a>", esc_url( remove_query_arg( 'paged', $current_url ) ), '<i class="angle double left icon"></i>' );
335 + $page_links[] = sprintf( "<a class='item' href='%s' title='" . esc_html__( 'First page', 'mainwp' ) . "' aria-hidden='true'>%s</a>", esc_url( remove_query_arg( 'paged', $current_url ) ), '<i class="angle double left icon"></i>' );
310 336 }
311 337
312 338 if ( $disable_prev ) {
313 339 $page_links[] = '<a class="item disabled" aria-hidden="true"><i class="angle left icon"></i></a>';
314 340 } else {
315 - $page_links[] = sprintf( "<a class='item' href='%s' title='" . esc_html__( 'Previous page' ) . "' aria-hidden='true'>%s</a>", esc_url( add_query_arg( 'paged', max( 1, $current - 1 ), $current_url ) ), '<i class="angle left icon"></i>' );
341 + $page_links[] = sprintf( "<a class='item' href='%s' title='" . esc_html__( 'Previous page', 'mainwp' ) . "' aria-hidden='true'>%s</a>", esc_url( add_query_arg( 'paged', max( 1, $current - 1 ), $current_url ) ), '<i class="angle left icon"></i>' );
316 342 }
317 343
318 344 if ( $current - 1 > 0 ) {
319 345 $page_links[] = sprintf( "<a class='item' href='%s'>%s</a>", esc_url( add_query_arg( 'paged', $current - 1, $current_url ) ), $current - 1 );
@@ -337,9 +363,9 @@
337 363 $page_links[] = sprintf( "<a class='item' href='%s' title='" . esc_html__( 'Last page', 'mainwp' ) . "' aria-hidden='true'>%s</a>", esc_url( add_query_arg( 'paged', $total_pages, $current_url ) ), '<i class="right angle double icon"></i>' );
338 364 }
339 365
340 366 if ( $total_pages > 1 ) {
341 - $perpage_paging = $perpage_paging . "&nbsp;&nbsp;<div class='ui pagination menu'>" . join( "\n", $page_links ) . '</div>';
367 + $perpage_paging = $perpage_paging . "&nbsp;&nbsp;<div class='ui mini pagination menu'>" . join( "\n", $page_links ) . '</div>';
342 368 }
343 369
344 370 ob_start();
345 371
@@ -391,8 +417,73 @@
391 417 }
392 418 }
393 419
394 420 /**
421 + * Check if a search term matches mainwp or mainwp child (case-insensitive).
422 + *
423 + * @param string $term Search term.
424 + * @return bool True if matches.
425 + */
426 + private function is_mainwp_search( $term ) {
427 + $normalized = strtolower( trim( $term ) );
428 + return (bool) preg_match( '/^mainwp(\s*-?\s*child)?$/i', $normalized );
429 + }
430 +
431 + /**
432 + * Render hardcoded MainWP Child plugin card.
433 + *
434 + * @return void
435 + */
436 + private function render_mainwp_child_card() {
437 + $details_link = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=mainwp-child&name=' . rawurlencode( 'MainWP Child' ) );
438 + $icon_url = MAINWP_PLUGIN_URL . 'assets/images/mainwp-icon.svg';
439 + $plugin_name = esc_attr__( 'MainWP Child &#8211; Securely Connects to the MainWP Dashboard to Manage Multiple Sites', 'mainwp' );
440 + ?>
441 + <div class="card plugin-card-mainwp-child" plugin-slug="mainwp-child" plugin-name="MainWP Child">
442 + <?php do_action( 'mainwp_install_plugin_card_top' ); ?>
443 + <div class="content">
444 + <a class="right floated mini ui image open-plugin-details-modal" href="<?php echo esc_url( $details_link ); ?>"><img src="<?php echo esc_url( $icon_url ); ?>" alt="<?php esc_attr_e( 'MainWP Child', 'mainwp' ); ?>" /></a>
445 + <div class="header">
446 + <a class="open-plugin-details-modal" href="<?php echo esc_url( $details_link ); ?>"><?php esc_html_e( 'MainWP Child', 'mainwp' ); ?></a>
447 + </div>
448 + <div class="meta"><cite><?php
449 + /* translators: %s: Plugin author name. */
450 + printf( esc_html__( 'By %s', 'mainwp' ), '<a href="' . esc_url( 'https://profiles.wordpress.org/mainwp/' ) . '">mainwp</a>' ); // phpcs:ignore WordPress.Security.EscapeOutput ?></cite></div>
451 + <div class="description">
452 + <p><?php esc_html_e( 'MainWP Child connects your WordPress sites to this Dashboard.', 'mainwp' ); ?></p>
453 + <p><?php esc_html_e( 'Already managing a site here? Select Install Plugin, choose your sites in the right panel, and click Complete Installation.', 'mainwp' ); ?></p>
454 + <p><?php esc_html_e( 'Setting up a new site? Download the plugin, then:', 'mainwp' ); ?></p>
455 + <div class="ui list">
456 + <div class="item"><p>1. <?php esc_html_e( 'Log in to your WordPress site', 'mainwp' ); ?></p></div>
457 + <div class="item"><p>2. <?php esc_html_e( 'Go to Plugins → Add New → Upload Plugin', 'mainwp' ); ?></p></div>
458 + <div class="item"><p>3. <?php esc_html_e( 'Upload and activate the downloaded file', 'mainwp' ); ?></p></div>
459 + <div class="item"><p>4. <?php esc_html_e( 'Return here to add the site to your Dashboard', 'mainwp' ); ?></p></div>
460 + </div>
461 + </div>
462 + </div>
463 + <div class="extra content">
464 + <div class="left aligned middle aligned column">
465 + <a href="<?php echo esc_url( 'https://downloads.wordpress.org/plugin/mainwp-child.latest-stable.zip' ); ?>" class="ui mini green basic fluid button"><i class="download icon"></i><?php esc_html_e( 'Download', 'mainwp' ); ?></a>
466 + </div>
467 + </div>
468 + <div class="extra content">
469 + <div class="ui two column grid">
470 + <div class="left aligned middle aligned column">
471 + <a href="<?php echo esc_url( $details_link ); ?>" class="ui mini button open-plugin-details-modal"><?php esc_html_e( 'Plugin Details', 'mainwp' ); ?></a>
472 + </div>
473 + <div class="right aligned middle aligned column">
474 + <div class="ui radio checkbox">
475 + <input name="install-plugin" type="radio" id="install-plugin-mainwp-child" plugin-name="<?php echo esc_attr( $plugin_name ); ?>" plugin-version="">
476 + <label><?php esc_html_e( 'Install Plugin', 'mainwp' ); ?></label>
477 + </div>
478 + </div>
479 + </div>
480 + </div>
481 + </div>
482 + <?php
483 + }
484 +
485 + /**
395 486 * Method display_rows()
396 487 *
397 488 * Build Plugin Cards.
398 489 *
@@ -398,8 +489,21 @@
398 489 *
399 490 * @return mixed Plugin cards.
400 491 */
401 492 public function display_rows() { //phpcs:ignore -- NOSONAR - complex.
493 + $search_term = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
494 +
495 + if ( ! empty( $search_term ) && $this->is_mainwp_search( $search_term ) ) {
496 + $this->render_mainwp_child_card();
497 + $this->items = array_filter(
498 + $this->items,
499 + function ( $plugin ) {
500 + $slug = is_array( $plugin ) ? ( $plugin['slug'] ?? '' ) : ( $plugin->slug ?? '' );
501 + return 'mainwp-child' !== $slug;
502 + }
503 + );
504 + }
505 +
402 506 $plugins_allowedtags = array(
403 507 'a' => array(
404 508 'href' => array(),
405 509 'title' => array(),
@@ -418,11 +522,11 @@
418 522 'br' => array(),
419 523 );
420 524
421 525 $plugins_group_titles = array(
422 - 'Performance' => _x( 'Performance', 'Plugin installer group title' ),
423 - 'Social' => _x( 'Social', 'Plugin installer group title' ),
424 - 'Tools' => _x( 'Tools', 'Plugin installer group title' ),
526 + 'Performance' => _x( 'Performance', 'Plugin installer group title', 'mainwp' ),
527 + 'Social' => _x( 'Social', 'Plugin installer group title', 'mainwp' ),
528 + 'Tools' => _x( 'Tools', 'Plugin installer group title', 'mainwp' ),
425 529 );
426 530
427 531 $group = null;
428 532
@@ -460,9 +564,10 @@
460 564 $version = wp_kses( $plugin['version'], $plugins_allowedtags );
461 565
462 566 $author = wp_kses( $plugin['author'], $plugins_allowedtags );
463 567 if ( ! empty( $author ) ) {
464 - $author = ' <cite>' . sprintf( esc_html__( 'By %s', 'mainwp' ), $author ) . '</cite>';
568 + /* translators: %s: Plugin author name. */
569 + $author = ' <cite>' . sprintf( esc_html__( 'By %s', 'mainwp' ), $author ) . '</cite>';
465 570 }
466 571
467 572 $details_link = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin['slug'] . '&url=' . ( isset( $plugin['PluginURI'] ) ? rawurlencode( $plugin['PluginURI'] ) : '' ) . '&name=' . rawurlencode( $plugin['name'] ) );
468 573
@@ -479,64 +584,70 @@
479 584 $last_updated_timestamp = strtotime( $plugin['last_updated'] );
480 585 ?>
481 586
482 587 <div class="card plugin-card-<?php echo sanitize_html_class( $plugin['slug'] ); ?>" plugin-slug="<?php echo esc_attr( $plugin['slug'] ); ?>" plugin-name="<?php echo esc_attr( $plugin['name'] ); ?>">
483 - <?php
484 - /**
485 - * Action: mainwp_install_plugin_card_top
486 - *
487 - * Fires at the plugin card at top on the Install Plugins page.
488 - *
489 - * @since 4.1
490 - */
491 - do_action( 'mainwp_install_plugin_card_top' );
492 - ?>
493 - <div class="content">
494 - <a class="right floated mini ui image open-plugin-details-modal" href="<?php echo esc_url( $details_link ); ?>"><img src="<?php echo esc_attr( $plugin_icon_url ); ?>" alt="<?php esc_attr_e( $plugin['name'] ); ?>" /></a>
495 - <div class="header">
496 - <a class="open-plugin-details-modal" href="<?php echo esc_url( $details_link ); ?>"><?php echo $title; // phpcs:ignore WordPress.Security.EscapeOutput ?></a>
588 + <?php
589 + /**
590 + * Action: mainwp_install_plugin_card_top
591 + *
592 + * Fires at the plugin card at top on the Install Plugins page.
593 + *
594 + * @since 4.1
595 + */
596 + do_action( 'mainwp_install_plugin_card_top' );
597 + ?>
598 + <div class="content">
599 + <a class="right floated mini ui image open-plugin-details-modal" href="<?php echo esc_url( $details_link ); ?>"><img src="<?php echo esc_attr( $plugin_icon_url ); ?>" alt="<?php echo esc_attr( $plugin['name'] ); ?>" /></a>
600 + <div class="header">
601 + <a class="open-plugin-details-modal" href="<?php echo esc_url( $details_link ); ?>"><?php echo $title; // phpcs:ignore WordPress.Security.EscapeOutput ?></a>
497 602 </div>
498 - <div class="meta">
499 - <?php echo $author; // phpcs:ignore WordPress.Security.EscapeOutput ?>
603 + <div class="meta"><?php echo $author; // phpcs:ignore WordPress.Security.EscapeOutput ?></div>
604 + <div class="description">
605 + <?php echo esc_html( wp_strip_all_tags( $description ) ); ?>
500 606 </div>
501 - <div class="description">
502 - <?php echo esc_html( wp_strip_all_tags( $description ) ); ?>
503 607 </div>
504 - </div>
505 608 <div class="extra content">
506 609 <div class="ui stacking grid">
507 610 <div class="four wide left aligned column">
508 - <?php
509 - wp_star_rating(
510 - array(
511 - 'rating' => $plugin['rating'],
512 - 'type' => 'percent',
513 - 'number' => $plugin['num_ratings'],
514 - )
515 - );
516 - ?>
611 + <?php
612 + wp_star_rating(
613 + array(
614 + 'rating' => $plugin['rating'],
615 + 'type' => 'percent',
616 + 'number' => $plugin['num_ratings'],
617 + )
618 + );
619 + ?>
620 + </div>
621 + <div class="twelve wide right aligned column"><span class="ui small text"><?php
622 + /* translators: %s: Human-readable time difference. */
623 + printf( esc_html__( 'Updated %s ago', 'mainwp' ), human_time_diff( $last_updated_timestamp ) ); // phpcs:ignore WordPress.Security.EscapeOutput ?></span></div>
517 624 </div>
518 - <div class="twelve wide right aligned column"><span class="ui small text"><?php esc_html_e( 'Updated: ', 'mainwp' ); ?><?php printf( esc_html__( '%s ago', 'mainwp' ), human_time_diff( $last_updated_timestamp ) ); // phpcs:ignore WordPress.Security.EscapeOutput ?></span></div>
519 - </div>
520 625 </div>
521 - <div class="extra content">
522 - <a href="<?php echo esc_attr( $details_link ); ?>" class="ui mini button open-plugin-details-modal"><?php echo esc_html__( 'Plugin Details', 'mainwp' ); ?></a>
523 - <div class="ui radio checkbox right floated">
524 - <input name="install-plugin" type="radio" id="install-plugin-<?php echo sanitize_html_class( $plugin['slug'] ); ?>" plugin-name="<?php echo esc_attr( $title ); ?>" plugin-version="<?php echo esc_attr( $version ); ?>">
525 - <label><?php esc_html_e( 'Install Plugin', 'mainwp' ); ?></label>
626 + <div class="extra content">
627 + <div class="ui two column grid">
628 + <div class="left aligned middle aligned column">
629 + <a href="<?php echo esc_attr( $details_link ); ?>" class="ui mini button open-plugin-details-modal"><?php echo esc_html__( 'Plugin Details', 'mainwp' ); ?></a>
526 630 </div>
631 + <div class="right aligned middle aligned column">
632 + <div class="ui radio checkbox">
633 + <input name="install-plugin" type="radio" id="install-plugin-<?php echo sanitize_html_class( $plugin['slug'] ); ?>" plugin-name="<?php echo esc_attr( $title ); ?>" plugin-version="<?php echo esc_attr( $version ); ?>">
634 + <label><?php esc_html_e( 'Install Plugin', 'mainwp' ); ?></label>
635 + </div>
527 636 </div>
528 - <?php
529 - /**
530 - * Action: mainwp_install_plugin_card_bottom
531 - *
532 - * Fires at the plugin card at bottom on the Install Plugins page.
533 - *
534 - * @since 4.1
535 - */
536 - do_action( 'mainwp_install_plugin_card_bottom', $plugin );
537 - ?>
538 637 </div>
638 + </div>
639 + <?php
640 + /**
641 + * Action: mainwp_install_plugin_card_bottom
642 + *
643 + * Fires at the plugin card at bottom on the Install Plugins page.
644 + *
645 + * @since 4.1
646 + */
647 + do_action( 'mainwp_install_plugin_card_bottom', $plugin );
648 + ?>
649 + </div>
539 650 <?php
540 651 }
541 652 ?>
542 653 <script type="text/javascript">