PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-beta.2
Jetpack – WP Security, Backup, Speed, & Growth v16.1-beta.2
16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / class.jetpack-modules-list-table.php
jetpack Last commit date
3rd-party 1 month ago _inc 5 days ago css 5 days ago extensions 5 days ago images 5 days ago jetpack_vendor 5 days ago json-endpoints 5 days ago modules 5 days ago sal 5 days ago src 5 days ago vendor 5 days ago views 4 months ago CHANGELOG.md 5 days ago LICENSE.txt 7 months ago SECURITY.md 2 months ago class-jetpack-connection-status.php 2 years ago class-jetpack-gallery-settings.php 8 months ago class-jetpack-newsletter-dashboard-widget.php 8 months ago class-jetpack-pre-connection-jitms.php 2 years ago class-jetpack-stats-dashboard-widget.php 5 days ago class-jetpack-xmlrpc-methods.php 2 months ago class.frame-nonce-preview.php 8 months ago class.jetpack-admin.php 5 days ago class.jetpack-autoupdate.php 8 months ago class.jetpack-cli.php 1 month ago class.jetpack-client-server.php 2 years ago class.jetpack-gutenberg.php 5 days ago class.jetpack-heartbeat.php 2 weeks ago class.jetpack-modules-list-table.php 5 days ago class.jetpack-network-sites-list-table.php 1 month ago class.jetpack-network.php 2 months ago class.jetpack-plan.php 3 years ago class.jetpack-post-images.php 5 months ago class.jetpack-twitter-cards.php 5 months ago class.jetpack-user-agent.php 1 month ago class.jetpack.php 5 days ago class.json-api-endpoints.php 2 weeks ago class.json-api.php 1 month ago class.photon.php 3 years ago composer.json 5 days ago enhanced-open-graph.php 2 months ago functions.compat.php 5 months ago functions.cookies.php 2 years ago functions.global.php 1 month ago functions.is-mobile.php 2 years ago functions.opengraph.php 5 months ago functions.photon.php 2 years ago jetpack.php 5 days ago json-api-config.php 3 years ago json-endpoints.php 2 years ago load-jetpack.php 1 month ago locales.php 8 months ago readme.txt 5 days ago unauth-file-upload.php 1 month ago uninstall.php 1 month ago wpml-config.xml 4 years ago
class.jetpack-modules-list-table.php
648 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Jetpack modules list table.
4 *
5 * @package automattic/jetpack
6 */
7
8 use Automattic\Jetpack\Assets;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit( 0 );
12 }
13
14 if ( ! class_exists( 'WP_List_Table' ) ) {
15 require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
16 }
17
18 /**
19 * Jetpack modules list table.
20 */
21 class Jetpack_Modules_List_Table extends WP_List_Table {
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
140 /** Constructor. */
141 public function __construct() {
142 parent::__construct();
143
144 Jetpack::init();
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
149 if ( $this->compat_fields && is_array( $this->compat_fields ) ) {
150 array_push( $this->compat_fields, 'all_items' );
151 }
152
153 /**
154 * Filters the list of modules available to be displayed in the Jetpack Settings screen.
155 *
156 * @since 3.0.0
157 *
158 * @param array $modules Array of Jetpack modules.
159 */
160 $this->all_items = apply_filters( 'jetpack_modules_list_table_items', Jetpack_Admin::init()->get_modules() );
161 $this->items = $this->all_items;
162 $this->items = $this->filter_displayed_table_items( $this->items );
163 $this->_column_headers = array( $this->get_columns(), array(), array(), 'name' );
164 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce: This is a view, not a model or controller. InputNotSanitized: Sanitized below via `$this->module_info_check()`.
165 $modal_info = isset( $_GET['info'] ) ? wp_unslash( $_GET['info'] ) : false;
166
167 // Adding in a hidden h1 heading for screen-readers.
168 ?>
169 <h1 class="screen-reader-text"><?php esc_html_e( 'Jetpack Modules List', 'jetpack' ); ?></h1>
170 <?php
171
172 wp_register_script(
173 'models.jetpack-modules',
174 Assets::get_file_url_for_environment(
175 '_inc/build/jetpack-modules.models.min.js',
176 '_inc/jetpack-modules.models.js'
177 ),
178 array( 'jquery', 'backbone' ),
179 JETPACK__VERSION,
180 false // @todo Can this be put in the footer?
181 );
182 wp_register_script(
183 'views.jetpack-modules',
184 Assets::get_file_url_for_environment(
185 '_inc/build/jetpack-modules.views.min.js',
186 '_inc/jetpack-modules.views.js'
187 ),
188 array( 'jquery', 'backbone', 'wp-util' ),
189 JETPACK__VERSION,
190 false // @todo Can this be put in the footer?
191 );
192 wp_register_script(
193 'jetpack-modules-list-table',
194 Assets::get_file_url_for_environment(
195 '_inc/build/jetpack-modules.min.js',
196 '_inc/jetpack-modules.js'
197 ),
198 array(
199 'views.jetpack-modules',
200 'models.jetpack-modules',
201 'jquery',
202 ),
203 JETPACK__VERSION,
204 true
205 );
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
217 wp_localize_script(
218 'jetpack-modules-list-table',
219 'jetpackModulesData',
220 array(
221 'modules' => $modules_for_js,
222 'i18n' => array(
223 'search_placeholder' => __( 'Search modules…', 'jetpack' ),
224 ),
225 'modalinfo' => $this->module_info_check( $modal_info, $this->all_items ),
226 'nonces' => array(
227 'bulk' => wp_create_nonce( 'bulk-jetpack_page_jetpack_modules' ),
228 ),
229 )
230 );
231
232 wp_enqueue_script( 'jetpack-modules-list-table' );
233
234 /**
235 * Filters the js_templates callback value.
236 *
237 * @since 3.6.0
238 *
239 * @param array array( $this, 'js_templates' ) js_templates callback.
240 */
241 add_action( 'admin_footer', apply_filters( 'jetpack_modules_list_table_js_template_callback', array( $this, 'js_templates' ) ), 9 );
242 }
243
244 /**
245 * Output row template.
246 */
247 public function js_templates() {
248 ?>
249 <script type="text/html" id="tmpl-Jetpack_Modules_List_Table_Template">
250 <# var i = 0; var currentGroup = null;
251 if ( data.items.length ) {
252 _.each( data.items, function( item, key, list ) {
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 }}}">
261 <th scope="row" class="check-column">
262 <# if ( item.available ) { #><input type="checkbox" name="modules[]" value="{{{ item.module }}}" {{{ item.disabled }}} /><# } #>
263 </th>
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>
292 </div>
293 </td>
294 </tr>
295 <#
296 });
297 } else {
298 #>
299 <tr class="no-modules-found">
300 <td colspan="2"><?php esc_html_e( 'No Modules Found', 'jetpack' ); ?></td>
301 </tr>
302 <#
303 }
304 #>
305 </script>
306 <?php
307 }
308
309 /**
310 * Get views data.
311 *
312 * @return array Maps identifier to display HTML.
313 */
314 public function get_views() {
315 /** This filter is already documented in class.jetpack-modules-list-table.php */
316 $modules = apply_filters( 'jetpack_modules_list_table_items', Jetpack_Admin::init()->get_modules() );
317 $array_of_module_tags = wp_list_pluck( $modules, 'module_tags' );
318 $module_tags = array_merge( ...array_values( $array_of_module_tags ) );
319 $module_tags = array_map( 'jetpack_get_module_i18n_tag', $module_tags );
320 $module_tags_unique = array_count_values( $module_tags );
321 ksort( $module_tags_unique );
322
323 $format = '<a href="%3$s" %4$s data-title="%1$s">%1$s</a> <span class="count">(%2$s)</span>';
324 $title = __( 'All', 'jetpack' );
325 $count = is_countable( $modules ) ? count( $modules ) : 0;
326 $url = esc_url( remove_query_arg( 'module_tag' ) );
327 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a view, not a model or controller.
328 $views = array(
329 'all' => sprintf( $format, $title, $count, $url, 'class="all"' ),
330 );
331 foreach ( $module_tags_unique as $title => $count ) {
332 $key = sanitize_title( $title );
333 $display_title = esc_html( wptexturize( $title ) );
334 $url = esc_url( add_query_arg( 'module_tag', rawurlencode( $title ) ) );
335 $views[ $key ] = sprintf( $format, $display_title, $count, $url, '' );
336 }
337 return $views;
338 }
339
340 /**
341 * Output views HTML.
342 */
343 public function views() {
344 $views = $this->get_views();
345
346 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a view, not a model or controller.
347 $module_tag = empty( $_GET['module_tag'] ) ? 'all' : sanitize_title( wp_unslash( $_GET['module_tag'] ) );
348
349 echo "<ul class='subsubsub'>\n";
350 foreach ( $views as $class => $view ) {
351 $class_name = $class;
352 if ( $class === $module_tag ) {
353 $class_name .= ' current';
354 }
355
356 $views[ $class ] = "\t<li class='$class_name'>$view</li>";
357 }
358 echo implode( "\n", $views ) . "\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Is HTML. Escaping happens in get_views().
359 echo '</ul>';
360 }
361
362 /**
363 * Filter a modules array for displayed items.
364 *
365 * @param array $modules Modules.
366 * @return array Displayed modules.
367 */
368 public function filter_displayed_table_items( $modules ) {
369 return array_filter( $modules, array( $this, 'is_module_displayed' ) );
370 }
371
372 /**
373 * Determine if a module is displayed.
374 *
375 * @param array $module Module data.
376 * @return bool
377 */
378 public static function is_module_displayed( $module ) {
379 // Handle module tag based filtering.
380 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a view, not a model or controller.
381 if ( ! empty( $_REQUEST['module_tag'] ) ) {
382 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a view, not a model or controller.
383 $module_tag = sanitize_text_field( wp_unslash( $_REQUEST['module_tag'] ) );
384 if ( ! in_array( $module_tag, array_map( 'jetpack_get_module_i18n_tag', $module['module_tags'] ), true ) ) {
385 return false;
386 }
387 }
388
389 // If nothing rejected it, include it!
390 return true;
391 }
392
393 /**
394 * Sort callback to put modules with `requires_connection` last.
395 *
396 * @param array $module1 Module data.
397 * @param array $module2 Module data.
398 * @return int Indicating the relative ordering of module1 and module2.
399 */
400 public static function sort_requires_connection_last( $module1, $module2 ) {
401 if ( (bool) $module1['requires_connection'] === (bool) $module2['requires_connection'] ) {
402 return 0;
403 }
404 if ( $module1['requires_connection'] ) {
405 return 1;
406 }
407 if ( $module2['requires_connection'] ) {
408 return -1;
409 }
410
411 return 0;
412 }
413
414 /**
415 * Get table columns.
416 *
417 * @return string[] Column name to header HTML.
418 */
419 public function get_columns() {
420 $columns = array(
421 'cb' => '<input type="checkbox" />',
422 'name' => __( 'Name', 'jetpack' ),
423 );
424 return $columns;
425 }
426
427 /**
428 * Get bulk actions for the table.
429 *
430 * @return string[] Actions, code => text.
431 */
432 public function get_bulk_actions() {
433 $actions = array(
434 'bulk-activate' => __( 'Activate', 'jetpack' ),
435 'bulk-deactivate' => __( 'Deactivate', 'jetpack' ),
436 );
437 return $actions;
438 }
439
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 /**
483 * Print a single row of the table.
484 *
485 * @param object|array $item Item.
486 */
487 public function single_row( $item ) {
488 static $i = 0;
489 $row_class = ( ( ++$i ) % 2 ) ? ' alternate' : '';
490
491 $available = Jetpack_Admin::is_module_available( $item );
492
493 if ( ! empty( $item['activated'] ) ) {
494 $row_class .= ' active';
495 }
496 if ( ! $available ) {
497 $row_class .= ' unavailable';
498 }
499
500 echo '<tr class="jetpack-module' . esc_attr( $row_class ) . '" id="' . esc_attr( $item['module'] ) . '">';
501
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>';
507
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>';
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>';
525
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>';
539
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>';
553 }
554
555 /**
556 * Build the activate/deactivate toggle control markup for a module row.
557 *
558 * @param object|array $item Module data.
559 * @return string HTML.
560 */
561 protected function get_module_toggle_html( $item ) {
562 $is_active = ! empty( $item['activated'] );
563
564 if ( $is_active ) {
565 $url = wp_nonce_url(
566 Jetpack::admin_url(
567 array(
568 'page' => 'jetpack',
569 'action' => 'deactivate',
570 'module' => $item['module'],
571 )
572 ),
573 'jetpack_deactivate-' . $item['module']
574 );
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(
584 Jetpack::admin_url(
585 array(
586 'page' => 'jetpack',
587 'action' => 'activate',
588 'module' => $item['module'],
589 )
590 ),
591 'jetpack_activate-' . $item['module']
592 );
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';
600 }
601
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 );
609 }
610
611 /**
612 * Table classes.
613 *
614 * @return string[] HTML.
615 */
616 public function get_table_classes() {
617 return array( 'table', 'table-bordered', 'wp-list-table', 'widefat', 'fixed' );
618 }
619
620 /**
621 * Check if the info parameter provided in the URL corresponds to an actual module.
622 *
623 * @param string|false $info Info parameter.
624 * @param array $modules Modules array.
625 * @return string|false
626 */
627 public function module_info_check( $info, $modules ) {
628 if ( ! $info ) {
629 return false;
630 } elseif ( array_key_exists( $info, $modules ) ) {
631 return $info;
632 }
633 }
634
635 /**
636 * Core switched their `display_tablenav()` method to protected, so we can't access it directly.
637 * Instead, let's include an access function to make it doable without errors!
638 *
639 * @see https://github.com/WordPress/WordPress/commit/d28f6344de97616de8ece543ed290c4ba2383622
640 *
641 * @param string $which Which nav table to display.
642 * @return mixed
643 */
644 public function unprotected_display_tablenav( $which = 'top' ) {
645 return $this->display_tablenav( $which );
646 }
647 }
648