PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 All 504 releases
← All changes | class.jetpack-modules-list-table.php +304 -132 13.5.216.3-a.1 View file →
@@ -6,8 +6,12 @@
6 6 */
7 7
8 8 use Automattic\Jetpack\Assets;
9 9
10 +if ( ! defined( 'ABSPATH' ) ) {
11 + exit( 0 );
12 +}
13 +
10 14 if ( ! class_exists( 'WP_List_Table' ) ) {
11 15 require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
12 16 }
13 17
@@ -15,8 +19,125 @@
15 19 * Jetpack modules list table.
16 20 */
17 21 class Jetpack_Modules_List_Table extends WP_List_Table {
18 22
23 + /**
24 + * Order in which product groups are displayed on the page.
25 + *
26 + * @var string[]
27 + */
28 + const PRODUCT_GROUP_ORDER = array( 'growth', 'performance', 'security', 'other' );
29 +
30 + /**
31 + * Map of module slug to product group.
32 + *
33 + * Modeled on the Offline Mode dashboard grouping so the modules page reads
34 + * as a set of focused product areas. Any module not listed falls back to
35 + * the "other" group.
36 + *
37 + * @return array<string, string> Module slug => product group slug.
38 + */
39 + public static function get_module_product_groups_map() {
40 + return array(
41 + // Grow your audience.
42 + 'blaze' => 'growth',
43 + 'comment-likes' => 'growth',
44 + 'comments' => 'growth',
45 + 'contact-form' => 'growth',
46 + 'gravatar-hovercards' => 'growth',
47 + 'likes' => 'growth',
48 + 'publicize' => 'growth',
49 + 'related-posts' => 'growth',
50 + 'sharedaddy' => 'growth',
51 + 'simple-payments' => 'growth',
52 + 'stats' => 'growth',
53 + 'subscriptions' => 'growth',
54 + 'woocommerce-analytics' => 'growth',
55 + 'wordads' => 'growth',
56 + 'wpcom-reader' => 'growth',
57 +
58 + // Speed up your site.
59 + 'carousel' => 'performance',
60 + 'google-fonts' => 'performance',
61 + 'infinite-scroll' => 'performance',
62 + 'photon' => 'performance',
63 + 'photon-cdn' => 'performance',
64 + 'search' => 'performance',
65 + 'tiled-gallery' => 'performance',
66 + 'videopress' => 'performance',
67 +
68 + // Protect your site.
69 + 'account-protection' => 'security',
70 + 'monitor' => 'security',
71 + 'notes' => 'security',
72 + 'protect' => 'security',
73 + 'sso' => 'security',
74 + 'vaultpress' => 'security',
75 + 'waf' => 'security',
76 + );
77 + }
78 +
79 + /**
80 + * Get the product group slug for a given module.
81 + *
82 + * @param string $slug Module slug.
83 + * @return string Product group slug.
84 + */
85 + public static function get_module_product_group( $slug ) {
86 + $map = self::get_module_product_groups_map();
87 +
88 + return $map[ $slug ] ?? 'other';
89 + }
90 +
91 + /**
92 + * Get the translated display name for a product group.
93 + *
94 + * @param string $group Product group slug.
95 + * @return string Translated group name.
96 + */
97 + public static function get_product_group_name( $group ) {
98 + switch ( $group ) {
99 + case 'growth':
100 + return __( 'Grow your audience', 'jetpack' );
101 + case 'performance':
102 + return __( 'Speed up your site', 'jetpack' );
103 + case 'security':
104 + return __( 'Protect your site', 'jetpack' );
105 + default:
106 + return __( 'Other features', 'jetpack' );
107 + }
108 + }
109 +
110 + /**
111 + * Whether a module is flagged as recommended.
112 + *
113 + * @param array $module Module data (with raw, untranslated module tags).
114 + * @return bool
115 + */
116 + public static function is_module_recommended( $module ) {
117 + return ! empty( $module['module_tags'] ) && in_array( 'Recommended', (array) $module['module_tags'], true );
118 + }
119 +
120 + /**
121 + * Remove the legacy `vaultpress` module from the modules screen.
122 + *
123 + * The `vaultpress` module is a leftover stub for the original standalone
124 + * VaultPress plugin. It is hard-coded as never activatable (see
125 + * Jetpack_Admin::is_module_available()) and is unrelated to the modern
126 + * VaultPress Backup product, which is delivered through My Jetpack and the
127 + * standalone Jetpack VaultPress Backup plugin rather than a Jetpack module.
128 + * Showing a permanently-unavailable row that merely reuses the modern
129 + * product's name is misleading, so we drop it from this screen only. Other
130 + * surfaces (REST, the React dashboard, standalone plugins) are unaffected.
131 + *
132 + * @param array $modules Array of Jetpack modules keyed by slug.
133 + * @return array Modules without the legacy `vaultpress` entry.
134 + */
135 + public static function hide_legacy_vaultpress_module( $modules ) {
136 + unset( $modules['vaultpress'] );
137 + return $modules;
138 + }
139 +
19 140 /** Constructor. */
20 141 public function __construct() {
21 142 parent::__construct();
22 143
@@ -21,8 +142,11 @@
21 142 parent::__construct();
22 143
23 144 Jetpack::init();
24 145
146 + // Scope this to the modules screen so REST/React/standalone surfaces keep the legacy slug.
147 + add_filter( 'jetpack_modules_list_table_items', array( __CLASS__, 'hide_legacy_vaultpress_module' ) );
148 +
25 149 if ( $this->compat_fields && is_array( $this->compat_fields ) ) {
26 150 array_push( $this->compat_fields, 'all_items' );
27 151 }
28 152
@@ -50,9 +174,9 @@
50 174 Assets::get_file_url_for_environment(
51 175 '_inc/build/jetpack-modules.models.min.js',
52 176 '_inc/jetpack-modules.models.js'
53 177 ),
54 - array( 'jquery', 'backbone', 'underscore' ),
178 + array( 'jquery', 'backbone' ),
55 179 JETPACK__VERSION,
56 180 false // @todo Can this be put in the footer?
57 181 );
58 182 wp_register_script(
@@ -60,9 +184,9 @@
60 184 Assets::get_file_url_for_environment(
61 185 '_inc/build/jetpack-modules.views.min.js',
62 186 '_inc/jetpack-modules.views.js'
63 187 ),
64 - array( 'jquery', 'backbone', 'underscore', 'wp-util' ),
188 + array( 'jquery', 'backbone', 'wp-util' ),
65 189 JETPACK__VERSION,
66 190 false // @todo Can this be put in the footer?
67 191 );
68 192 wp_register_script(
@@ -79,13 +203,23 @@
79 203 JETPACK__VERSION,
80 204 true
81 205 );
82 206
207 + $modules_for_js = (array) Jetpack::get_translated_modules( $this->all_items );
208 + foreach ( array_keys( $modules_for_js ) as $slug ) {
209 + $group = self::get_module_product_group( $slug );
210 +
211 + $modules_for_js[ $slug ]['product_group'] = $group;
212 + $modules_for_js[ $slug ]['product_group_name'] = self::get_product_group_name( $group );
213 + // Detect recommendation from the raw (untranslated) module tags.
214 + $modules_for_js[ $slug ]['recommended'] = isset( $this->all_items[ $slug ] ) && self::is_module_recommended( $this->all_items[ $slug ] );
215 + }
216 +
83 217 wp_localize_script(
84 218 'jetpack-modules-list-table',
85 219 'jetpackModulesData',
86 220 array(
87 - 'modules' => Jetpack::get_translated_modules( $this->all_items ),
221 + 'modules' => $modules_for_js,
88 222 'i18n' => array(
89 223 'search_placeholder' => __( 'Search modules…', 'jetpack' ),
90 224 ),
91 225 'modalinfo' => $this->module_info_check( $modal_info, $this->all_items ),
@@ -112,30 +246,50 @@
112 246 */
113 247 public function js_templates() {
114 248 ?>
115 249 <script type="text/html" id="tmpl-Jetpack_Modules_List_Table_Template">
116 - <# var i = 0;
250 + <# var i = 0; var currentGroup = null;
117 251 if ( data.items.length ) {
118 252 _.each( data.items, function( item, key, list ) {
119 - if ( item === undefined ) return; #>
120 - <tr class="jetpack-module <# if ( ++i % 2 ) { #> alternate<# } #><# if ( item.activated ) { #> active<# } #><# if ( ! item.available ) { #> unavailable<# } #>" id="{{{ item.module }}}">
253 + if ( item === undefined ) return;
254 + if ( item.product_group !== currentGroup ) {
255 + currentGroup = item.product_group; #>
256 + <tr class="jetpack-module-group-header" data-group="{{ item.product_group }}">
257 + <td colspan="2"><h2 class="jetpack-module-group-header__title">{{ item.product_group_name }}</h2></td>
258 + </tr>
259 + <# } #>
260 + <tr class="jetpack-module<# if ( ++i % 2 ) { #> alternate<# } #><# if ( item.activated ) { #> active<# } #><# if ( ! item.available ) { #> unavailable<# } #>" id="{{{ item.module }}}">
121 261 <th scope="row" class="check-column">
122 - <input type="checkbox" name="modules[]" value="{{{ item.module }}}" {{{ item.disabled }}} />
262 + <# if ( item.available ) { #><input type="checkbox" name="modules[]" value="{{{ item.module }}}" {{{ item.disabled }}} /><# } #>
123 263 </th>
124 - <td class='name column-name'>
125 - <p class='info'><a href="{{{item.learn_more_button}}}" target="blank" style="text-decoration: none;">{{{ item.name }}}</a></p>
126 - <div class="row-actions">
127 - <# if ( item.configurable ) { #>
128 - <span class='configure'>{{{ item.configurable }}}</span>
129 - <# } #>
130 - <# if ( item.activated && 'vaultpress' !== item.module && item.available ) { #>
131 - <span class='delete'><a class="dops-button is-compact" href="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>?page=jetpack&#038;action=deactivate&#038;module={{{ item.module }}}&#038;_wpnonce={{{ item.deactivate_nonce }}}"><?php esc_html_e( 'Deactivate', 'jetpack' ); ?></a></span>
132 - <# } else if ( item.available ) { #>
133 - <span class='activate'><a class="dops-button is-compact" href="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>?page=jetpack&#038;action=activate&#038;module={{{ item.module }}}&#038;_wpnonce={{{ item.activate_nonce }}}"><?php esc_html_e( 'Activate', 'jetpack' ); ?></a></span>
134 - <# } #>
135 - <# if ( ! item.available ) { #>
136 - <p class='unavailable_reason'>{{{ item.unavailable_reason }}}</p>
137 - <# } #>
264 + <td class="name column-name">
265 + <div class="jetpack-module__body">
266 + <div class="jetpack-module__info">
267 + <# if ( item.learn_more_button ) { #><a class="jetpack-module__name" href="{{{ item.learn_more_button }}}" target="_blank" rel="noopener noreferrer">{{ item.name }}</a><# } else { #><span class="jetpack-module__name">{{ item.name }}</span><# } #>
268 + <# if ( item.description ) { #><span class="jetpack-module__description">{{ item.description }}</span><# } #>
269 + </div>
270 + <div class="jetpack-module__meta">
271 + <span class="jetpack-module__badges">
272 + <# if ( item.recommended && item.available ) { #><span class="jetpack-module__badge is-recommended"><?php esc_html_e( 'Recommended', 'jetpack' ); ?></span><# } #>
273 + <# if ( item.available ) { #>
274 + <# if ( item.activated ) { #><span class="jetpack-module__badge is-active"><?php esc_html_e( 'Active', 'jetpack' ); ?></span><# } else { #><span class="jetpack-module__badge is-inactive"><?php esc_html_e( 'Inactive', 'jetpack' ); ?></span><# } #>
275 + <# } #>
276 + </span>
277 + <div class="row-actions">
278 + <# if ( item.configurable ) { #>
279 + <span class="configure">{{{ item.configurable }}}</span>
280 + <# } #>
281 + <# if ( item.available ) { #>
282 + <# if ( item.activated ) { #>
283 + <a class="jetpack-module__toggle is-active" role="switch" aria-checked="true" href="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>?page=jetpack&#038;action=deactivate&#038;module={{{ item.module }}}&#038;_wpnonce={{{ item.deactivate_nonce }}}"><span class="jetpack-module__toggle-track"><span class="jetpack-module__toggle-thumb"></span></span><span class="screen-reader-text"><?php esc_html_e( 'Deactivate', 'jetpack' ); ?></span></a>
284 + <# } else { #>
285 + <a class="jetpack-module__toggle" role="switch" aria-checked="false" href="<?php echo esc_url( admin_url( 'admin.php' ) ); ?>?page=jetpack&#038;action=activate&#038;module={{{ item.module }}}&#038;_wpnonce={{{ item.activate_nonce }}}"><span class="jetpack-module__toggle-track"><span class="jetpack-module__toggle-thumb"></span></span><span class="screen-reader-text"><?php esc_html_e( 'Activate', 'jetpack' ); ?></span></a>
286 + <# } #>
287 + <# } else { #>
288 + <span class="jetpack-module__unavailable">{{ item.unavailable_reason }}</span>
289 + <# } #>
290 + </div>
291 + </div>
138 292 </div>
139 293 </td>
140 294 </tr>
141 295 <#
@@ -283,8 +437,50 @@
283 437 return $actions;
284 438 }
285 439
286 440 /**
441 + * Print the module rows grouped by product group.
442 + *
443 + * Overrides WP_List_Table::display_rows() so the server-rendered (no-JS)
444 + * output matches the grouped, card-style layout produced by the Backbone
445 + * template.
446 + */
447 + public function display_rows() {
448 + $buckets = array();
449 + foreach ( self::PRODUCT_GROUP_ORDER as $group ) {
450 + $buckets[ $group ] = array();
451 + }
452 +
453 + foreach ( $this->items as $item ) {
454 + $group = self::get_module_product_group( $item['module'] );
455 + $buckets[ $group ][] = $item;
456 + }
457 +
458 + foreach ( $buckets as $group => $items ) {
459 + if ( empty( $items ) ) {
460 + continue;
461 + }
462 + $this->group_header_row( $group );
463 + foreach ( $items as $item ) {
464 + $this->single_row( $item );
465 + }
466 + }
467 + }
468 +
469 + /**
470 + * Print a product group header row.
471 + *
472 + * @param string $group Product group slug.
473 + */
474 + protected function group_header_row( $group ) {
475 + printf(
476 + '<tr class="jetpack-module-group-header" data-group="%1$s"><td colspan="2"><h2 class="jetpack-module-group-header__title">%2$s</h2></td></tr>',
477 + esc_attr( $group ),
478 + esc_html( self::get_product_group_name( $group ) )
479 + );
480 + }
481 +
482 + /**
287 483 * Print a single row of the table.
288 484 *
289 485 * @param object|array $item Item.
290 486 */
@@ -291,159 +487,135 @@
291 487 public function single_row( $item ) {
292 488 static $i = 0;
293 489 $row_class = ( ( ++$i ) % 2 ) ? ' alternate' : '';
294 490
491 + $available = Jetpack_Admin::is_module_available( $item );
492 +
295 493 if ( ! empty( $item['activated'] ) ) {
296 494 $row_class .= ' active';
297 495 }
298 -
299 - if ( ! Jetpack_Admin::is_module_available( $item ) ) {
496 + if ( ! $available ) {
300 497 $row_class .= ' unavailable';
301 498 }
302 499
303 500 echo '<tr class="jetpack-module' . esc_attr( $row_class ) . '" id="' . esc_attr( $item['module'] ) . '">';
304 - $this->single_row_columns( $item );
305 - echo '</tr>';
306 - }
307 501
308 - /**
309 - * Table classes.
310 - *
311 - * @return string[] HTML.
312 - */
313 - public function get_table_classes() {
314 - return array( 'table', 'table-bordered', 'wp-list-table', 'widefat', 'fixed' );
315 - }
502 + echo '<th scope="row" class="check-column">';
503 + if ( $available ) {
504 + printf( '<input type="checkbox" name="modules[]" value="%s" />', esc_attr( $item['module'] ) );
505 + }
506 + echo '</th>';
316 507
317 - /**
318 - * Column checkbox.
319 - *
320 - * @param object|array $item Item.
321 - * @return string HTML.
322 - */
323 - public function column_cb( $item ) {
324 - if ( ! Jetpack_Admin::is_module_available( $item ) ) {
325 - return '';
508 + echo '<td class="name column-name">';
509 + echo '<div class="jetpack-module__body">';
510 +
511 + echo '<div class="jetpack-module__info">';
512 + if ( ! empty( $item['learn_more_button'] ) ) {
513 + printf(
514 + '<a class="jetpack-module__name" href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a>',
515 + esc_url( $item['learn_more_button'] ),
516 + wp_kses_post( wptexturize( $item['name'] ) )
517 + );
518 + } else {
519 + echo '<span class="jetpack-module__name">' . wp_kses_post( wptexturize( $item['name'] ) ) . '</span>';
326 520 }
521 + if ( ! empty( $item['description'] ) ) {
522 + echo '<span class="jetpack-module__description">' . esc_html( wp_strip_all_tags( $item['description'] ) ) . '</span>';
523 + }
524 + echo '</div>';
327 525
328 - return sprintf( '<input type="checkbox" name="modules[]" value="%s" />', $item['module'] );
329 - }
526 + echo '<div class="jetpack-module__meta">';
527 + echo '<span class="jetpack-module__badges">';
528 + if ( $available && self::is_module_recommended( $item ) ) {
529 + echo '<span class="jetpack-module__badge is-recommended">' . esc_html__( 'Recommended', 'jetpack' ) . '</span>';
530 + }
531 + if ( $available ) {
532 + if ( ! empty( $item['activated'] ) ) {
533 + echo '<span class="jetpack-module__badge is-active">' . esc_html__( 'Active', 'jetpack' ) . '</span>';
534 + } else {
535 + echo '<span class="jetpack-module__badge is-inactive">' . esc_html__( 'Inactive', 'jetpack' ) . '</span>';
536 + }
537 + }
538 + echo '</span>';
330 539
331 - /**
332 - * Column icon.
333 - *
334 - * @return string HTML.
335 - */
336 - public function column_icon() {
337 - $badge_text = '';
338 - $free_text = '';
339 - ob_start();
340 - ?>
341 - <a href="#TB_inline?width=600&height=550&inlineId=more-info-module-settings-modal" class="thickbox">
342 - <div class="module-image">
343 - <p><span class="module-image-badge"><?php echo esc_html( $badge_text ); ?></span><span class="module-image-free" style="display: none"><?php echo esc_html( $free_text ); ?></span></p>
344 - </div>
345 - </a>
346 - <?php
347 - return ob_get_clean();
540 + echo '<div class="row-actions">';
541 + if ( ! empty( $item['configurable'] ) ) {
542 + echo '<span class="configure">' . wp_kses_post( $item['configurable'] ) . '</span>';
543 + }
544 + if ( $available ) {
545 + echo $this->get_module_toggle_html( $item ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped in helper.
546 + } elseif ( ! empty( $item['unavailable_reason'] ) ) {
547 + echo '<span class="jetpack-module__unavailable">' . wp_kses_post( $item['unavailable_reason'] ) . '</span>';
548 + }
549 + // Close .row-actions, .jetpack-module__meta, and .jetpack-module__body.
550 + echo '</div></div></div>';
551 + echo '</td>';
552 + echo '</tr>';
348 553 }
349 554
350 555 /**
351 - * Column name.
556 + * Build the activate/deactivate toggle control markup for a module row.
352 557 *
353 - * @param object|array $item Item.
558 + * @param object|array $item Module data.
354 559 * @return string HTML.
355 560 */
356 - public function column_name( $item ) {
357 - $actions = array(
358 - 'info' => sprintf( '<a href="%s" target="blank">%s</a>', esc_url( $item['learn_more_button'] ), esc_html__( 'Feature Info', 'jetpack' ) ),
359 - );
561 + protected function get_module_toggle_html( $item ) {
562 + $is_active = ! empty( $item['activated'] );
360 563
361 - if ( ! empty( $item['configurable'] ) ) {
362 - $actions['configure'] = $item['configurable'];
363 - }
364 -
365 - if ( empty( $item['activated'] ) && Jetpack_Admin::is_module_available( $item ) ) {
366 - $url = wp_nonce_url(
564 + if ( $is_active ) {
565 + $url = wp_nonce_url(
367 566 Jetpack::admin_url(
368 567 array(
369 568 'page' => 'jetpack',
370 - 'action' => 'activate',
569 + 'action' => 'deactivate',
371 570 'module' => $item['module'],
372 571 )
373 572 ),
374 - 'jetpack_activate-' . $item['module']
573 + 'jetpack_deactivate-' . $item['module']
375 574 );
376 - $actions['activate'] = sprintf( '<a href="%s">%s</a>', esc_url( $url ), esc_html__( 'Activate', 'jetpack' ) );
377 - } elseif ( ! empty( $item['activated'] ) ) {
378 - $url = wp_nonce_url(
575 + $label = sprintf(
576 + /* translators: %s: Jetpack module name. */
577 + __( 'Deactivate %s', 'jetpack' ),
578 + $item['name']
579 + );
580 + $class = 'jetpack-module__toggle is-active';
581 + $aria = 'true';
582 + } else {
583 + $url = wp_nonce_url(
379 584 Jetpack::admin_url(
380 585 array(
381 586 'page' => 'jetpack',
382 - 'action' => 'deactivate',
587 + 'action' => 'activate',
383 588 'module' => $item['module'],
384 589 )
385 590 ),
386 - 'jetpack_deactivate-' . $item['module']
591 + 'jetpack_activate-' . $item['module']
387 592 );
388 - $actions['delete'] = sprintf( '<a href="%s">%s</a>', esc_url( $url ), esc_html__( 'Deactivate', 'jetpack' ) );
593 + $label = sprintf(
594 + /* translators: %s: Jetpack module name. */
595 + __( 'Activate %s', 'jetpack' ),
596 + $item['name']
597 + );
598 + $class = 'jetpack-module__toggle';
599 + $aria = 'false';
389 600 }
390 601
391 - return $this->row_actions( $actions ) . wptexturize( $item['name'] );
602 + return sprintf(
603 + '<a class="%1$s" role="switch" aria-checked="%2$s" href="%3$s"><span class="jetpack-module__toggle-track"><span class="jetpack-module__toggle-thumb"></span></span><span class="screen-reader-text">%4$s</span></a>',
604 + esc_attr( $class ),
605 + esc_attr( $aria ),
606 + esc_url( $url ),
607 + esc_html( $label )
608 + );
392 609 }
393 610
394 611 /**
395 - * Column description.
612 + * Table classes.
396 613 *
397 - * @param object|array $item Item.
398 - * @return string HTML.
614 + * @return string[] HTML.
399 615 */
400 - public function column_description( $item ) {
401 - ob_start();
402 - // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
403 - /** This action is documented in class.jetpack-admin.php */
404 - echo apply_filters( 'jetpack_short_module_description', $item['description'], $item['module'] );
405 - /** This action is documented in class.jetpack-admin.php */
406 - do_action( 'jetpack_learn_more_button_' . $item['module'] );
407 - echo '<div id="more-info-' . $item['module'] . '" class="more-info">';
408 - /** This action is documented in class.jetpack-admin.php */
409 - do_action( 'jetpack_module_more_info_' . $item['module'] );
410 - echo '</div>';
411 - // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
412 - return ob_get_clean();
413 - }
414 -
415 - /**
416 - * Return module tags HTML.
417 - *
418 - * @param object|array $item Item.
419 - * @return string HTML.
420 - */
421 - public function column_module_tags( $item ) {
422 - $module_tags = array();
423 - foreach ( array_map( 'jetpack_get_module_i18n_tag', $item['module_tags'] ) as $module_tag ) {
424 - $module_tags[] = sprintf( '<a href="%3$s" data-title="%2$s">%1$s</a>', esc_html( $module_tag ), esc_attr( $module_tag ), esc_url( add_query_arg( 'module_tag', rawurlencode( $module_tag ) ) ) );
425 - }
426 - return implode( ', ', $module_tags );
427 - }
428 -
429 - /**
430 - * Column default value.
431 - *
432 - * @param object|array $item Item.
433 - * @param string $column_name Column name.
434 - * @return string
435 - */
436 - public function column_default( $item, $column_name ) {
437 - switch ( $column_name ) {
438 - case 'icon':
439 - case 'name':
440 - case 'description':
441 - return '';
442 - default:
443 - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
444 - return print_r( $item, true );
445 - }
616 + public function get_table_classes() {
617 + return array( 'table', 'table-bordered', 'wp-list-table', 'widefat', 'fixed' );
446 618 }
447 619
448 620 /**
449 621 * Check if the info parameter provided in the URL corresponds to an actual module.