暫無描述

class.jetpack-modules-list-table.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. <?php
  2. use Automattic\Jetpack\Assets;
  3. if ( ! class_exists( 'WP_List_Table' ) ) {
  4. require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
  5. }
  6. class Jetpack_Modules_List_Table extends WP_List_Table {
  7. function __construct() {
  8. parent::__construct();
  9. Jetpack::init();
  10. if ( $this->compat_fields && is_array( $this->compat_fields ) ) {
  11. array_push( $this->compat_fields, 'all_items' );
  12. }
  13. /**
  14. * Filters the list of modules available to be displayed in the Jetpack Settings screen.
  15. *
  16. * @since 3.0.0
  17. *
  18. * @param array $modules Array of Jetpack modules.
  19. */
  20. $this->all_items = apply_filters( 'jetpack_modules_list_table_items', Jetpack_Admin::init()->get_modules() );
  21. $this->items = $this->all_items;
  22. $this->items = $this->filter_displayed_table_items( $this->items );
  23. $this->_column_headers = array( $this->get_columns(), array(), array(), 'name' );
  24. $modal_info = isset( $_GET['info'] ) ? $_GET['info'] : false;
  25. wp_register_script(
  26. 'models.jetpack-modules',
  27. Assets::get_file_url_for_environment(
  28. '_inc/build/jetpack-modules.models.min.js',
  29. '_inc/jetpack-modules.models.js'
  30. ),
  31. array( 'backbone', 'underscore' ),
  32. JETPACK__VERSION
  33. );
  34. wp_register_script(
  35. 'views.jetpack-modules',
  36. Assets::get_file_url_for_environment(
  37. '_inc/build/jetpack-modules.views.min.js',
  38. '_inc/jetpack-modules.views.js'
  39. ),
  40. array( 'backbone', 'underscore', 'wp-util' ),
  41. JETPACK__VERSION
  42. );
  43. wp_register_script(
  44. 'jetpack-modules-list-table',
  45. Assets::get_file_url_for_environment(
  46. '_inc/build/jetpack-modules.min.js',
  47. '_inc/jetpack-modules.js'
  48. ),
  49. array(
  50. 'views.jetpack-modules',
  51. 'models.jetpack-modules',
  52. 'jquery',
  53. ),
  54. JETPACK__VERSION,
  55. true
  56. );
  57. wp_localize_script(
  58. 'jetpack-modules-list-table',
  59. 'jetpackModulesData',
  60. array(
  61. 'modules' => Jetpack::get_translated_modules( $this->all_items ),
  62. 'i18n' => array(
  63. 'search_placeholder' => __( 'Search Modules…', 'jetpack' ),
  64. ),
  65. 'modalinfo' => $this->module_info_check( $modal_info, $this->all_items ),
  66. 'nonces' => array(
  67. 'bulk' => wp_create_nonce( 'bulk-jetpack_page_jetpack_modules' ),
  68. ),
  69. )
  70. );
  71. wp_enqueue_script( 'jetpack-modules-list-table' );
  72. /**
  73. * Filters the js_templates callback value.
  74. *
  75. * @since 3.6.0
  76. *
  77. * @param array array( $this, 'js_templates' ) js_templates callback.
  78. */
  79. add_action( 'admin_footer', apply_filters( 'jetpack_modules_list_table_js_template_callback', array( $this, 'js_templates' ) ), 9 );
  80. }
  81. function js_templates() {
  82. ?>
  83. <script type="text/html" id="tmpl-Jetpack_Modules_List_Table_Template">
  84. <# var i = 0;
  85. if ( data.items.length ) {
  86. _.each( data.items, function( item, key, list ) {
  87. if ( item === undefined ) return; #>
  88. <tr class="jetpack-module <# if ( ++i % 2 ) { #> alternate<# } #><# if ( item.activated ) { #> active<# } #><# if ( ! item.available ) { #> unavailable<# } #>" id="{{{ item.module }}}">
  89. <th scope="row" class="check-column">
  90. <input type="checkbox" name="modules[]" value="{{{ item.module }}}" />
  91. </th>
  92. <td class='name column-name'>
  93. <span class='info'><a href="{{{item.learn_more_button}}}" target="blank">{{{ item.name }}}</a></span>
  94. <div class="row-actions">
  95. <# if ( item.configurable ) { #>
  96. <span class='configure'>{{{ item.configurable }}}</span>
  97. <# } #>
  98. <# if ( item.activated && 'vaultpress' !== item.module && item.available ) { #>
  99. <span class='delete'><a href="<?php echo admin_url( 'admin.php' ); ?>?page=jetpack&#038;action=deactivate&#038;module={{{ item.module }}}&#038;_wpnonce={{{ item.deactivate_nonce }}}"><?php _e( 'Deactivate', 'jetpack' ); ?></a></span>
  100. <# } else if ( item.available ) { #>
  101. <span class='activate'><a href="<?php echo admin_url( 'admin.php' ); ?>?page=jetpack&#038;action=activate&#038;module={{{ item.module }}}&#038;_wpnonce={{{ item.activate_nonce }}}"><?php _e( 'Activate', 'jetpack' ); ?></a></span>
  102. <# } #>
  103. <# if ( ! item.available ) { #>
  104. <span class='unavailable_reason'>{{{ item.unavailable_reason }}}</span>
  105. <# } #>
  106. </div>
  107. </td>
  108. </tr>
  109. <#
  110. });
  111. } else {
  112. #>
  113. <tr class="no-modules-found">
  114. <td colspan="2"><?php esc_html_e( 'No Modules Found', 'jetpack' ); ?></td>
  115. </tr>
  116. <#
  117. }
  118. #>
  119. </script>
  120. <?php
  121. }
  122. function get_views() {
  123. /** This filter is already documented in class.jetpack-modules-list-table.php */
  124. $modules = apply_filters( 'jetpack_modules_list_table_items', Jetpack_Admin::init()->get_modules() );
  125. $array_of_module_tags = wp_list_pluck( $modules, 'module_tags' );
  126. $module_tags = array_merge( ...array_values( $array_of_module_tags ) );
  127. $module_tags_unique = array_count_values( $module_tags );
  128. ksort( $module_tags_unique );
  129. $format = '<a href="%3$s"%4$s data-title="%1$s">%1$s <span class="count">(%2$s)</span></a>';
  130. $title = __( 'All', 'jetpack' );
  131. $count = count( $modules );
  132. $url = esc_url( remove_query_arg( 'module_tag' ) );
  133. $current = empty( $_GET['module_tag'] ) ? ' class="current all"' : ' class="all"';
  134. $views = array(
  135. 'all' => sprintf( $format, $title, $count, $url, $current ),
  136. );
  137. foreach ( $module_tags_unique as $title => $count ) {
  138. $key = sanitize_title( $title );
  139. $display_title = esc_html( wptexturize( $title ) );
  140. $url = esc_url( add_query_arg( 'module_tag', urlencode( $title ) ) );
  141. $current = '';
  142. if ( ! empty( $_GET['module_tag'] ) && $title == $_GET['module_tag'] ) {
  143. $current = ' class="current"';
  144. }
  145. $views[ $key ] = sprintf( $format, $display_title, $count, $url, $current );
  146. }
  147. return $views;
  148. }
  149. function views() {
  150. $views = $this->get_views();
  151. echo "<ul class='subsubsub'>\n";
  152. foreach ( $views as $class => $view ) {
  153. $views[ $class ] = "\t<li class='$class'>$view</li>";
  154. }
  155. echo implode( "\n", $views ) . "\n";
  156. echo '</ul>';
  157. }
  158. function filter_displayed_table_items( $modules ) {
  159. return array_filter( $modules, array( $this, 'is_module_displayed' ) );
  160. }
  161. static function is_module_displayed( $module ) {
  162. // Handle module tag based filtering.
  163. if ( ! empty( $_REQUEST['module_tag'] ) ) {
  164. $module_tag = sanitize_text_field( $_REQUEST['module_tag'] );
  165. if ( ! in_array( $module_tag, $module['module_tags'] ) ) {
  166. return false;
  167. }
  168. }
  169. // If nothing rejected it, include it!
  170. return true;
  171. }
  172. static function sort_requires_connection_last( $module1, $module2 ) {
  173. if ( $module1['requires_connection'] == $module2['requires_connection'] ) {
  174. return 0;
  175. }
  176. if ( $module1['requires_connection'] ) {
  177. return 1;
  178. }
  179. if ( $module2['requires_connection'] ) {
  180. return -1;
  181. }
  182. return 0;
  183. }
  184. function get_columns() {
  185. $columns = array(
  186. 'cb' => '<input type="checkbox" />',
  187. 'name' => __( 'Name', 'jetpack' ),
  188. );
  189. return $columns;
  190. }
  191. function get_bulk_actions() {
  192. $actions = array(
  193. 'bulk-activate' => __( 'Activate', 'jetpack' ),
  194. 'bulk-deactivate' => __( 'Deactivate', 'jetpack' ),
  195. );
  196. return $actions;
  197. }
  198. function single_row( $item ) {
  199. static $i = 0;
  200. $row_class = ( ++$i % 2 ) ? ' alternate' : '';
  201. if ( ! empty( $item['activated'] ) ) {
  202. $row_class .= ' active';
  203. }
  204. if ( ! Jetpack_Admin::is_module_available( $item ) ) {
  205. $row_class .= ' unavailable';
  206. }
  207. echo '<tr class="jetpack-module' . esc_attr( $row_class ) . '" id="' . esc_attr( $item['module'] ) . '">';
  208. $this->single_row_columns( $item );
  209. echo '</tr>';
  210. }
  211. function get_table_classes() {
  212. return array( 'table', 'table-bordered', 'wp-list-table', 'widefat', 'fixed', 'jetpack-modules' );
  213. }
  214. function column_cb( $item ) {
  215. if ( ! Jetpack_Admin::is_module_available( $item ) ) {
  216. return '';
  217. }
  218. return sprintf( '<input type="checkbox" name="modules[]" value="%s" />', $item['module'] );
  219. }
  220. function column_icon( $item ) {
  221. $badge_text = $free_text = '';
  222. ob_start();
  223. ?>
  224. <a href="#TB_inline?width=600&height=550&inlineId=more-info-module-settings-modal" class="thickbox">
  225. <div class="module-image">
  226. <p><span class="module-image-badge"><?php echo $badge_text; ?></span><span class="module-image-free" style="display: none"><?php echo $free_text; ?></span></p>
  227. </div>
  228. </a>
  229. <?php
  230. return ob_get_clean();
  231. }
  232. function column_name( $item ) {
  233. $actions = array(
  234. 'info' => sprintf( '<a href="%s" target="blank">%s</a>', esc_url( $item['learn_more_button'] ), esc_html__( 'Feature Info', 'jetpack' ) ),
  235. );
  236. if ( ! empty( $item['configurable'] ) ) {
  237. $actions['configure'] = $item['configurable'];
  238. }
  239. if ( empty( $item['activated'] ) && Jetpack_Admin::is_module_available( $item ) ) {
  240. $url = wp_nonce_url(
  241. Jetpack::admin_url(
  242. array(
  243. 'page' => 'jetpack',
  244. 'action' => 'activate',
  245. 'module' => $item['module'],
  246. )
  247. ),
  248. 'jetpack_activate-' . $item['module']
  249. );
  250. $actions['activate'] = sprintf( '<a href="%s">%s</a>', esc_url( $url ), esc_html__( 'Activate', 'jetpack' ) );
  251. } elseif ( ! empty( $item['activated'] ) ) {
  252. $url = wp_nonce_url(
  253. Jetpack::admin_url(
  254. array(
  255. 'page' => 'jetpack',
  256. 'action' => 'deactivate',
  257. 'module' => $item['module'],
  258. )
  259. ),
  260. 'jetpack_deactivate-' . $item['module']
  261. );
  262. $actions['delete'] = sprintf( '<a href="%s">%s</a>', esc_url( $url ), esc_html__( 'Deactivate', 'jetpack' ) );
  263. }
  264. return $this->row_actions( $actions ) . wptexturize( $item['name'] );
  265. }
  266. function column_description( $item ) {
  267. ob_start();
  268. /** This action is documented in class.jetpack-admin.php */
  269. echo apply_filters( 'jetpack_short_module_description', $item['description'], $item['module'] );
  270. /** This action is documented in class.jetpack-admin.php */
  271. do_action( 'jetpack_learn_more_button_' . $item['module'] );
  272. echo '<div id="more-info-' . $item['module'] . '" class="more-info">';
  273. /** This action is documented in class.jetpack-admin.php */
  274. do_action( 'jetpack_module_more_info_' . $item['module'] );
  275. echo '</div>';
  276. return ob_get_clean();
  277. }
  278. function column_module_tags( $item ) {
  279. $module_tags = array();
  280. foreach ( $item['module_tags'] as $module_tag ) {
  281. $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', urlencode( $module_tag ) ) ) );
  282. }
  283. return implode( ', ', $module_tags );
  284. }
  285. function column_default( $item, $column_name ) {
  286. switch ( $column_name ) {
  287. case 'icon':
  288. case 'name':
  289. case 'description':
  290. break;
  291. default:
  292. return print_r( $item, true );
  293. }
  294. }
  295. // Check if the info parameter provided in the URL corresponds to an actual module
  296. function module_info_check( $info, $modules ) {
  297. if ( false == $info ) {
  298. return false;
  299. } elseif ( array_key_exists( $info, $modules ) ) {
  300. return $info;
  301. }
  302. }
  303. /**
  304. * Core switched their `display_tablenav()` method to protected, so we can't access it directly.
  305. * Instead, let's include an access function to make it doable without errors!
  306. *
  307. * @see https://github.com/WordPress/WordPress/commit/d28f6344de97616de8ece543ed290c4ba2383622
  308. *
  309. * @param string $which
  310. *
  311. * @return mixed
  312. */
  313. function unprotected_display_tablenav( $which = 'top' ) {
  314. return $this->display_tablenav( $which );
  315. }
  316. }