Ticket #38073: 38073.patch
File 38073.patch, 490.0 KB (added by , 7 years ago) |
---|
-
src/wp-admin/comment.php
diff --git a/src/wp-admin/comment.php b/src/wp-admin/comment.php index c2eca11ec3..01081116b0 100644
a b require_once( dirname( __FILE__ ) . '/admin.php' ); 12 12 $parent_file = 'edit-comments.php'; 13 13 $submenu_file = 'edit-comments.php'; 14 14 15 /** 16 * @global string $action 17 */ 18 global $action; 19 wp_reset_vars( array('action') ); 15 $action = wp_assign_request_var('action'); 20 16 21 17 if ( isset( $_POST['deletecomment'] ) ) 22 18 $action = 'deletecomment'; 23 19 24 20 if ( 'cdc' == $action ) 25 21 $action = 'delete'; 26 22 elseif ( 'mac' == $action ) 27 23 $action = 'approve'; 28 24 29 25 if ( isset( $_GET['dt'] ) ) { 30 31 32 33 26 if ( 'spam' == $_GET['dt'] ) 27 $action = 'spam'; 28 elseif ( 'trash' == $_GET['dt'] ) 29 $action = 'trash'; 34 30 } 35 31 36 32 switch( $action ) { 37 33 38 34 case 'editcomment' : 39 35 $title = __('Edit Comment'); 40 36 41 42 43 44 45 46 47 37 get_current_screen()->add_help_tab( array( 38 'id' => 'overview', 39 'title' => __('Overview'), 40 'content' => 41 '<p>' . __( 'You can edit the information left in a comment if needed. This is often useful when you notice that a commenter has made a typographical error.' ) . '</p>' . 42 '<p>' . __( 'You can also moderate the comment from this screen using the Status box, where you can also change the timestamp of the comment.' ) . '</p>' 43 ) ); 48 44 49 50 51 52 53 45 get_current_screen()->set_help_sidebar( 46 '<p><strong>' . __( 'For more information:' ) . '</strong></p>' . 47 '<p>' . __( '<a href="https://codex.wordpress.org/Administration_Screens#Comments">Documentation on Comments</a>' ) . '</p>' . 48 '<p>' . __( '<a href="https://wordpress.org/support/">Support Forums</a>' ) . '</p>' 49 ); 54 50 55 56 51 wp_enqueue_script('comment'); 52 require_once( ABSPATH . 'wp-admin/admin-header.php' ); 57 53 58 54 $comment_id = absint( $_GET['c'] ); 59 55 60 61 56 if ( !$comment = get_comment( $comment_id ) ) 57 comment_footer_die( __( 'Invalid comment ID.' ) . sprintf(' <a href="%s">' . __('Go back') . '</a>.', 'javascript:history.go(-1)') ); 62 58 63 64 59 if ( !current_user_can( 'edit_comment', $comment_id ) ) 60 comment_footer_die( __('Sorry, you are not allowed to edit this comment.') ); 65 61 66 67 62 if ( 'trash' == $comment->comment_approved ) 63 comment_footer_die( __('This comment is in the Trash. Please move it out of the Trash if you want to edit it.') ); 68 64 69 65 $comment = get_comment_to_edit( $comment_id ); 70 66 71 67 include( ABSPATH . 'wp-admin/edit-form-comment.php' ); 72 68 73 69 break; 74 70 75 71 case 'delete' : 76 72 case 'approve' : 77 73 case 'trash' : 78 74 case 'spam' : 79 75 80 76 $title = __('Moderate Comment'); 81 77 82 78 $comment_id = absint( $_GET['c'] ); 83 79 84 85 86 87 80 if ( ! $comment = get_comment( $comment_id ) ) { 81 wp_redirect( admin_url('edit-comments.php?error=1') ); 82 die(); 83 } 88 84 89 90 91 92 85 if ( !current_user_can( 'edit_comment', $comment->comment_ID ) ) { 86 wp_redirect( admin_url('edit-comments.php?error=2') ); 87 die(); 88 } 93 89 94 95 96 97 98 90 // No need to re-approve/re-trash/re-spam a comment. 91 if ( $action == str_replace( '1', 'approve', $comment->comment_approved ) ) { 92 wp_redirect( admin_url( 'edit-comments.php?same=' . $comment_id ) ); 93 die(); 94 } 99 95 100 96 require_once( ABSPATH . 'wp-admin/admin-header.php' ); 101 97 102 103 104 98 $formaction = $action . 'comment'; 99 $nonce_action = 'approve' == $action ? 'approve-comment_' : 'delete-comment_'; 100 $nonce_action .= $comment_id; 105 101 106 102 ?> 107 103 <div class="wrap"> … … case 'spam' : 110 106 111 107 <?php 112 108 switch ( $action ) { 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 109 case 'spam' : 110 $caution_msg = __('You are about to mark the following comment as spam:'); 111 $button = _x( 'Mark as Spam', 'comment' ); 112 break; 113 case 'trash' : 114 $caution_msg = __('You are about to move the following comment to the Trash:'); 115 $button = __('Move to Trash'); 116 break; 117 case 'delete' : 118 $caution_msg = __('You are about to delete the following comment:'); 119 $button = __('Permanently Delete Comment'); 120 break; 121 default : 122 $caution_msg = __('You are about to approve the following comment:'); 123 $button = __('Approve Comment'); 124 break; 129 125 } 130 126 131 127 if ( $comment->comment_approved != '0' ) { // if not unapproved 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 128 $message = ''; 129 switch ( $comment->comment_approved ) { 130 case '1' : 131 $message = __('This comment is currently approved.'); 132 break; 133 case 'spam' : 134 $message = __('This comment is currently marked as spam.'); 135 break; 136 case 'trash' : 137 $message = __('This comment is currently in the Trash.'); 138 break; 139 } 140 if ( $message ) { 141 echo '<div id="message" class="notice notice-info"><p>' . $message . '</p></div>'; 142 } 147 143 } 148 144 ?> 149 145 <div id="message" class="notice notice-warning"><p><strong><?php _e( 'Caution:' ); ?></strong> <?php echo $caution_msg; ?></p></div> … … if ( $comment->comment_approved != '0' ) { // if not unapproved 166 162 </tr> 167 163 <?php } ?> 168 164 <tr> 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 165 <th scope="row"><?php /* translators: column name or table row header */ _e( 'In Response To' ); ?></th> 166 <td> 167 <?php 168 $post_id = $comment->comment_post_ID; 169 if ( current_user_can( 'edit_post', $post_id ) ) { 170 $post_link = "<a href='" . esc_url( get_edit_post_link( $post_id ) ) . "'>"; 171 $post_link .= esc_html( get_the_title( $post_id ) ) . '</a>'; 172 } else { 173 $post_link = esc_html( get_the_title( $post_id ) ); 174 } 175 echo $post_link; 176 177 if ( $comment->comment_parent ) { 178 $parent = get_comment( $comment->comment_parent ); 179 $parent_link = esc_url( get_comment_link( $parent ) ); 180 $name = get_comment_author( $parent ); 181 printf( 182 /* translators: %s: comment link */ 183 ' | ' . __( 'In reply to %s.' ), 184 '<a href="' . $parent_link . '">' . $name . '</a>' 185 ); 186 } 187 ?> 188 </td> 193 189 </tr> 194 190 <tr> 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 191 <th scope="row"><?php _e( 'Submitted on' ); ?></th> 192 <td> 193 <?php 194 /* translators: 1: comment date, 2: comment time */ 195 $submitted = sprintf( __( '%1$s at %2$s' ), 196 /* translators: comment date format. See https://secure.php.net/date */ 197 get_comment_date( __( 'Y/m/d' ), $comment ), 198 get_comment_date( __( 'g:i a' ), $comment ) 199 ); 200 if ( 'approved' === wp_get_comment_status( $comment ) && ! empty ( $comment->comment_post_ID ) ) { 201 echo '<a href="' . esc_url( get_comment_link( $comment ) ) . '">' . $submitted . '</a>'; 202 } else { 203 echo $submitted; 204 } 205 ?> 206 </td> 211 207 </tr> 212 208 <tr> 213 209 <th scope="row"><?php /* translators: field name in comment form */ _ex('Comment', 'noun'); ?></th> 214 210 <td class="comment-content"> 215 216 211 <?php comment_text( $comment ); ?> 212 <p class="edit-comment"><a href="<?php echo admin_url( "comment.php?action=editcomment&c={$comment->comment_ID}" ); ?>"><?php esc_html_e( 'Edit' ); ?></a></p> 217 213 </td> 218 214 </tr> 219 215 </table> … … if ( $comment->comment_approved != '0' ) { // if not unapproved 221 217 <form action="comment.php" method="get" class="comment-ays-submit"> 222 218 223 219 <p> 224 225 220 <?php submit_button( $button, 'primary', 'submit', false ); ?> 221 <a href="<?php echo admin_url('edit-comments.php'); ?>" class="button-cancel"><?php esc_html_e( 'Cancel' ); ?></a> 226 222 </p> 227 223 228 224 <?php wp_nonce_field( $nonce_action ); ?> … … if ( $comment->comment_approved != '0' ) { // if not unapproved 233 229 234 230 </div> 235 231 <?php 236 232 break; 237 233 238 234 case 'deletecomment' : 239 235 case 'trashcomment' : … … case 'spamcomment' : 242 238 case 'unspamcomment' : 243 239 case 'approvecomment' : 244 240 case 'unapprovecomment' : 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 241 $comment_id = absint( $_REQUEST['c'] ); 242 243 if ( in_array( $action, array( 'approvecomment', 'unapprovecomment' ) ) ) 244 check_admin_referer( 'approve-comment_' . $comment_id ); 245 else 246 check_admin_referer( 'delete-comment_' . $comment_id ); 247 248 $noredir = isset($_REQUEST['noredir']); 249 250 if ( !$comment = get_comment($comment_id) ) 251 comment_footer_die( __( 'Invalid comment ID.' ) . sprintf(' <a href="%s">' . __('Go back') . '</a>.', 'edit-comments.php') ); 252 if ( !current_user_can( 'edit_comment', $comment->comment_ID ) ) 253 comment_footer_die( __('Sorry, you are not allowed to edit comments on this post.') ); 254 255 if ( '' != wp_get_referer() && ! $noredir && false === strpos(wp_get_referer(), 'comment.php') ) 256 $redir = wp_get_referer(); 257 elseif ( '' != wp_get_original_referer() && ! $noredir ) 258 $redir = wp_get_original_referer(); 259 elseif ( in_array( $action, array( 'approvecomment', 'unapprovecomment' ) ) ) 260 $redir = admin_url('edit-comments.php?p=' . absint( $comment->comment_post_ID ) ); 261 else 262 $redir = admin_url('edit-comments.php'); 263 264 $redir = remove_query_arg( array('spammed', 'unspammed', 'trashed', 'untrashed', 'deleted', 'ids', 'approved', 'unapproved'), $redir ); 265 266 switch ( $action ) { 267 case 'deletecomment' : 268 wp_delete_comment( $comment ); 269 $redir = add_query_arg( array('deleted' => '1'), $redir ); 270 break; 271 case 'trashcomment' : 272 wp_trash_comment( $comment ); 273 $redir = add_query_arg( array('trashed' => '1', 'ids' => $comment_id), $redir ); 274 break; 275 case 'untrashcomment' : 276 wp_untrash_comment( $comment ); 277 $redir = add_query_arg( array('untrashed' => '1'), $redir ); 278 break; 279 case 'spamcomment' : 280 wp_spam_comment( $comment ); 281 $redir = add_query_arg( array('spammed' => '1', 'ids' => $comment_id), $redir ); 282 break; 283 case 'unspamcomment' : 284 wp_unspam_comment( $comment ); 285 $redir = add_query_arg( array('unspammed' => '1'), $redir ); 286 break; 287 case 'approvecomment' : 288 wp_set_comment_status( $comment, 'approve' ); 289 $redir = add_query_arg( array( 'approved' => 1 ), $redir ); 290 break; 291 case 'unapprovecomment' : 292 wp_set_comment_status( $comment, 'hold' ); 293 $redir = add_query_arg( array( 'unapproved' => 1 ), $redir ); 294 break; 295 } 296 297 wp_redirect( $redir ); 298 die; 303 299 304 300 case 'editedcomment' : 305 301 306 307 302 $comment_id = absint( $_POST['comment_ID'] ); 303 $comment_post_id = absint( $_POST['comment_post_ID'] ); 308 304 309 305 check_admin_referer( 'update-comment_' . $comment_id ); 310 306 311 307 edit_comment(); 312 308 313 309 $location = ( empty( $_POST['referredby'] ) ? "edit-comments.php?p=$comment_post_id" : $_POST['referredby'] ) . '#comment-' . $comment_id; 314 310 315 316 317 318 319 320 321 322 323 324 311 /** 312 * Filters the URI the user is redirected to after editing a comment in the admin. 313 * 314 * @since 2.1.0 315 * 316 * @param string $location The URI the user will be redirected to. 317 * @param int $comment_id The ID of the comment being edited. 318 */ 319 $location = apply_filters( 'comment_edit_redirect', $location, $comment_id ); 320 wp_redirect( $location ); 325 321 326 322 exit(); 327 323 328 324 default: 329 325 wp_die( __('Unknown action.') ); 330 326 331 327 } // end switch 332 328 -
src/wp-admin/customize.php
diff --git a/src/wp-admin/customize.php b/src/wp-admin/customize.php index f1bf8aa9a1..2401ada6d0 100644
a b define( 'IFRAME_REQUEST', true ); 13 13 require_once( dirname( __FILE__ ) . '/admin.php' ); 14 14 15 15 if ( ! current_user_can( 'customize' ) ) { 16 17 18 19 20 16 wp_die( 17 '<h1>' . __( 'Cheatin’ uh?' ) . '</h1>' . 18 '<p>' . __( 'Sorry, you are not allowed to customize this site.' ) . '</p>', 19 403 20 ); 21 21 } 22 22 23 23 /** … … if ( ! current_user_can( 'customize' ) ) { 27 27 global $wp_scripts, $wp_customize; 28 28 29 29 if ( $wp_customize->changeset_post_id() ) { 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 30 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->edit_post, $wp_customize->changeset_post_id() ) ) { 31 wp_die( 32 '<h1>' . __( 'Cheatin’ uh?' ) . '</h1>' . 33 '<p>' . __( 'Sorry, you are not allowed to edit this changeset.' ) . '</p>', 34 403 35 ); 36 } 37 if ( in_array( get_post_status( $wp_customize->changeset_post_id() ), array( 'publish', 'trash' ), true ) ) { 38 wp_die( 39 '<h1>' . __( 'Cheatin’ uh?' ) . '</h1>' . 40 '<p>' . __( 'This changeset has already been published and cannot be further modified.' ) . '</p>' . 41 '<p><a href="' . esc_url( remove_query_arg( 'changeset_uuid' ) ) . '">' . __( 'Customize New Changes' ) . '</a></p>', 42 403 43 ); 44 } 45 45 } 46 46 47 48 wp_reset_vars( array( 'url', 'return', 'autofocus' ) ); 47 $url = wp_assign_request_var('url'); 49 48 if ( ! empty( $url ) ) { 50 49 $wp_customize->set_preview_url( wp_unslash( $url ) ); 51 50 } 51 52 $return = wp_assign_request_var('return'); 52 53 if ( ! empty( $return ) ) { 53 54 $wp_customize->set_return_url( wp_unslash( $return ) ); 54 55 } 56 57 $autofocus = wp_assign_request_var('autofocus'); 55 58 if ( ! empty( $autofocus ) && is_array( $autofocus ) ) { 56 59 $wp_customize->set_autofocus( wp_unslash( $autofocus ) ); 57 60 } 58 61 59 62 $registered = $wp_scripts->registered; … … _wp_admin_html_begin(); 90 93 $body_class = 'wp-core-ui wp-customizer js'; 91 94 92 95 if ( wp_is_mobile() ) : 93 96 $body_class .= ' mobile'; 94 97 95 98 ?><meta name="viewport" id="viewport-meta" content="width=device-width, initial-scale=1.0, minimum-scale=0.5, maximum-scale=1.2" /><?php 96 99 endif; 97 100 98 101 if ( $wp_customize->is_ios() ) { 99 102 $body_class .= ' ios'; 100 103 } 101 104 102 105 if ( is_rtl() ) { 103 106 $body_class .= ' rtl'; 104 107 } 105 108 $body_class .= ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_user_locale() ) ) ); 106 109 … … do_action( 'customize_controls_print_scripts' ); 130 133 </head> 131 134 <body class="<?php echo esc_attr( $body_class ); ?>"> 132 135 <div class="wp-full-overlay expanded"> 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 136 <form id="customize-controls" class="wrap wp-full-overlay-sidebar"> 137 <div id="customize-header-actions" class="wp-full-overlay-header"> 138 <?php 139 $save_text = $wp_customize->is_theme_active() ? __( 'Save & Publish' ) : __( 'Save & Activate' ); 140 $save_attrs = array(); 141 if ( ! current_user_can( get_post_type_object( 'customize_changeset' )->cap->publish_posts ) ) { 142 $save_attrs['style'] = 'display: none'; 143 } 144 submit_button( $save_text, 'primary save', 'save', false, $save_attrs ); 145 ?> 146 <span class="spinner"></span> 147 <button type="button" class="customize-controls-preview-toggle"> 148 <span class="controls"><?php _e( 'Customize' ); ?></span> 149 <span class="preview"><?php _e( 'Preview' ); ?></span> 150 </button> 151 <a class="customize-controls-close" href="<?php echo esc_url( $wp_customize->get_return_url() ); ?>"> 152 <span class="screen-reader-text"><?php _e( 'Close the Customizer and go back to the previous page' ); ?></span> 153 </a> 154 </div> 155 156 <div id="widgets-right" class="wp-clearfix"><!-- For Widget Customizer, many widgets try to look for instances under div#widgets-right, so we have to add that ID to a container div in the Customizer for compat --> 157 <div class="wp-full-overlay-sidebar-content" tabindex="-1"> 158 <div id="customize-info" class="accordion-section customize-info"> 159 <div class="accordion-section-title"> 160 <span class="preview-notice"><?php 161 echo sprintf( __( 'You are customizing %s' ), '<strong class="panel-title site-title">' . get_bloginfo( 'name', 'display' ) . '</strong>' ); 162 ?></span> 163 <button type="button" class="customize-help-toggle dashicons dashicons-editor-help" aria-expanded="false"><span class="screen-reader-text"><?php _e( 'Help' ); ?></span></button> 164 </div> 165 <div class="customize-panel-description"><?php 166 _e( 'The Customizer allows you to preview changes to your site before publishing them. You can navigate to different pages on your site within the preview. Edit shortcuts are shown for some editable elements.' ); 167 ?></div> 168 </div> 169 170 <div id="customize-theme-controls"> 171 <ul class="customize-pane-parent"><?php // Panels and sections are managed here via JavaScript ?></ul> 172 </div> 173 </div> 174 </div> 175 176 <div id="customize-footer-actions" class="wp-full-overlay-footer"> 177 <button type="button" class="collapse-sidebar button" aria-expanded="true" aria-label="<?php echo esc_attr( _x( 'Hide Controls', 'label for hide controls button without length constraints' ) ); ?>"> 178 <span class="collapse-sidebar-arrow"></span> 179 <span class="collapse-sidebar-label"><?php _ex( 'Hide Controls', 'short (~12 characters) label for hide controls button' ); ?></span> 180 </button> 181 <?php $previewable_devices = $wp_customize->get_previewable_devices(); ?> 182 <?php if ( ! empty( $previewable_devices ) ) : ?> 183 <div class="devices-wrapper"> 184 <div class="devices"> 185 <?php foreach ( (array) $previewable_devices as $device => $settings ) : ?> 186 <?php 187 if ( empty( $settings['label'] ) ) { 188 continue; 189 } 190 $active = ! empty( $settings['default'] ); 191 $class = 'preview-' . $device; 192 if ( $active ) { 193 $class .= ' active'; 194 } 195 ?> 196 <button type="button" class="<?php echo esc_attr( $class ); ?>" aria-pressed="<?php echo esc_attr( $active ) ?>" data-device="<?php echo esc_attr( $device ); ?>"> 197 <span class="screen-reader-text"><?php echo esc_html( $settings['label'] ); ?></span> 198 </button> 199 <?php endforeach; ?> 200 </div> 201 </div> 202 <?php endif; ?> 203 </div> 204 </form> 205 <div id="customize-preview" class="wp-full-overlay-main"></div> 206 <?php 207 208 /** 209 * Prints templates, control scripts, and settings in the footer. 210 * 211 * @since 3.4.0 212 */ 213 do_action( 'customize_controls_print_footer_scripts' ); 214 ?> 212 215 </div> 213 216 </body> 214 217 </html> -
src/wp-admin/edit-tag-form.php
diff --git a/src/wp-admin/edit-tag-form.php b/src/wp-admin/edit-tag-form.php index e3fb222bc9..3108186446 100644
a b 8 8 9 9 // don't load directly 10 10 if ( ! defined( 'ABSPATH' ) ) { 11 11 die( '-1' ); 12 12 } 13 13 14 14 // Back compat hooks 15 15 if ( 'category' == $taxonomy ) { 16 17 18 19 20 21 22 23 24 16 /** 17 * Fires before the Edit Category form. 18 * 19 * @since 2.1.0 20 * @deprecated 3.0.0 Use {$taxonomy}_pre_edit_form instead. 21 * 22 * @param object $tag Current category term object. 23 */ 24 do_action( 'edit_category_form_pre', $tag ); 25 25 } elseif ( 'link_category' == $taxonomy ) { 26 27 28 29 30 31 32 33 34 26 /** 27 * Fires before the Edit Link Category form. 28 * 29 * @since 2.3.0 30 * @deprecated 3.0.0 Use {$taxonomy}_pre_edit_form instead. 31 * 32 * @param object $tag Current link category term object. 33 */ 34 do_action( 'edit_link_category_form_pre', $tag ); 35 35 } else { 36 37 38 39 40 41 42 43 44 36 /** 37 * Fires before the Edit Tag form. 38 * 39 * @since 2.5.0 40 * @deprecated 3.0.0 Use {$taxonomy}_pre_edit_form instead. 41 * 42 * @param object $tag Current tag term object. 43 */ 44 do_action( 'edit_tag_form_pre', $tag ); 45 45 } 46 46 47 /** 48 * Use with caution, see https://codex.wordpress.org/Function_Reference/wp_reset_vars 49 */ 50 wp_reset_vars( array( 'wp_http_referer' ) ); 47 $wp_http_referer = wp_assign_request_var('$wp_http_referer'); 51 48 52 49 $wp_http_referer = remove_query_arg( array( 'action', 'message', 'tag_ID' ), $wp_http_referer ); 53 50 … … do_action( "{$taxonomy}_pre_edit_form", $tag, $taxonomy ); ?> 72 69 73 70 <?php if ( $message ) : ?> 74 71 <div id="message" class="updated"> 75 76 77 78 79 80 81 72 <p><strong><?php echo $message; ?></strong></p> 73 <?php if ( $wp_http_referer ) { ?> 74 <p><a href="<?php echo esc_url( $wp_http_referer ); ?>"><?php 75 /* translators: %s: taxonomy name */ 76 printf( _x( '← Back to %s', 'admin screen' ), $tax->labels->name ); 77 ?></a></p> 78 <?php } ?> 82 79 </div> 83 80 <?php endif; ?> 84 81 … … wp_nonce_field( 'update-tag_' . $tag_ID ); 115 112 */ 116 113 do_action( "{$taxonomy}_term_edit_form_top", $tag, $taxonomy ); 117 114 ?> 118 119 120 121 122 123 115 <table class="form-table"> 116 <tr class="form-field form-required term-name-wrap"> 117 <th scope="row"><label for="name"><?php _ex( 'Name', 'term name' ); ?></label></th> 118 <td><input name="name" id="name" type="text" value="<?php if ( isset( $tag->name ) ) echo esc_attr($tag->name); ?>" size="40" aria-required="true" /> 119 <p class="description"><?php _e('The name is how it appears on your site.'); ?></p></td> 120 </tr> 124 121 <?php if ( !global_terms_enabled() ) { ?> 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 122 <tr class="form-field term-slug-wrap"> 123 <th scope="row"><label for="slug"><?php _e( 'Slug' ); ?></label></th> 124 <?php 125 /** 126 * Filters the editable slug. 127 * 128 * Note: This is a multi-use hook in that it is leveraged both for editable 129 * post URIs and term slugs. 130 * 131 * @since 2.6.0 132 * @since 4.4.0 The `$tag` parameter was added. 133 * 134 * @param string $slug The editable slug. Will be either a term slug or post URI depending 135 * upon the context in which it is evaluated. 136 * @param object|WP_Post $tag Term or WP_Post object. 137 */ 138 $slug = isset( $tag->slug ) ? apply_filters( 'editable_slug', $tag->slug, $tag ) : ''; 139 ?> 140 <td><input name="slug" id="slug" type="text" value="<?php echo esc_attr( $slug ); ?>" size="40" /> 141 <p class="description"><?php _e('The “slug” is the URL-friendly version of the name. It is usually all lowercase and contains only letters, numbers, and hyphens.'); ?></p></td> 142 </tr> 146 143 <?php } ?> 147 144 <?php if ( is_taxonomy_hierarchical($taxonomy) ) : ?> 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 145 <tr class="form-field term-parent-wrap"> 146 <th scope="row"><label for="parent"><?php echo esc_html( $tax->labels->parent_item ); ?></label></th> 147 <td> 148 <?php 149 $dropdown_args = array( 150 'hide_empty' => 0, 151 'hide_if_empty' => false, 152 'taxonomy' => $taxonomy, 153 'name' => 'parent', 154 'orderby' => 'name', 155 'selected' => $tag->parent, 156 'exclude_tree' => $tag->term_id, 157 'hierarchical' => true, 158 'show_option_none' => __( 'None' ), 159 ); 163 160 164 165 166 167 168 169 170 171 172 173 161 /** This filter is documented in wp-admin/edit-tags.php */ 162 $dropdown_args = apply_filters( 'taxonomy_parent_dropdown_args', $dropdown_args, $taxonomy, 'edit' ); 163 wp_dropdown_categories( $dropdown_args ); ?> 164 <?php if ( 'category' == $taxonomy ) : ?> 165 <p class="description"><?php _e( 'Categories, unlike tags, can have a hierarchy. You might have a Jazz category, and under that have children categories for Bebop and Big Band. Totally optional.' ); ?></p> 166 <?php else : ?> 167 <p class="description"><?php _e( 'Assign a parent term to create a hierarchy. The term Jazz, for example, would be the parent of Bebop and Big Band.' ); ?></p> 168 <?php endif; ?> 169 </td> 170 </tr> 174 171 <?php endif; // is_taxonomy_hierarchical() ?> 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 172 <tr class="form-field term-description-wrap"> 173 <th scope="row"><label for="description"><?php _e( 'Description' ); ?></label></th> 174 <td><textarea name="description" id="description" rows="5" cols="50" class="large-text"><?php echo $tag->description; // textarea_escaped ?></textarea> 175 <p class="description"><?php _e('The description is not prominent by default; however, some themes may show it.'); ?></p></td> 176 </tr> 177 <?php 178 // Back compat hooks 179 if ( 'category' == $taxonomy ) { 180 /** 181 * Fires after the Edit Category form fields are displayed. 182 * 183 * @since 2.9.0 184 * @deprecated 3.0.0 Use {$taxonomy}_edit_form_fields instead. 185 * 186 * @param object $tag Current category term object. 187 */ 188 do_action( 'edit_category_form_fields', $tag ); 189 } elseif ( 'link_category' == $taxonomy ) { 190 /** 191 * Fires after the Edit Link Category form fields are displayed. 192 * 193 * @since 2.9.0 194 * @deprecated 3.0.0 Use {$taxonomy}_edit_form_fields instead. 195 * 196 * @param object $tag Current link category term object. 197 */ 198 do_action( 'edit_link_category_form_fields', $tag ); 199 } else { 200 /** 201 * Fires after the Edit Tag form fields are displayed. 202 * 203 * @since 2.9.0 204 * @deprecated 3.0.0 Use {$taxonomy}_edit_form_fields instead. 205 * 206 * @param object $tag Current tag term object. 207 */ 208 do_action( 'edit_tag_form_fields', $tag ); 209 } 210 /** 211 * Fires after the Edit Term form fields are displayed. 212 * 213 * The dynamic portion of the hook name, `$taxonomy`, refers to 214 * the taxonomy slug. 215 * 216 * @since 3.0.0 217 * 218 * @param object $tag Current taxonomy term object. 219 * @param string $taxonomy Current taxonomy slug. 220 */ 221 do_action( "{$taxonomy}_edit_form_fields", $tag, $taxonomy ); 222 ?> 223 </table> 227 224 <?php 228 225 // Back compat hooks 229 226 if ( 'category' == $taxonomy ) { 230 231 227 /** This action is documented in wp-admin/edit-tags.php */ 228 do_action( 'edit_category_form', $tag ); 232 229 } elseif ( 'link_category' == $taxonomy ) { 233 234 230 /** This action is documented in wp-admin/edit-tags.php */ 231 do_action( 'edit_link_category_form', $tag ); 235 232 } else { 236 237 238 239 240 241 242 243 244 233 /** 234 * Fires at the end of the Edit Term form. 235 * 236 * @since 2.5.0 237 * @deprecated 3.0.0 Use {$taxonomy}_edit_form instead. 238 * 239 * @param object $tag Current taxonomy term object. 240 */ 241 do_action( 'edit_tag_form', $tag ); 245 242 } 246 243 /** 247 244 * Fires at the end of the Edit Term form for all taxonomies. … … do_action( "{$taxonomy}_edit_form", $tag, $taxonomy ); 258 255 259 256 <div class="edit-tag-actions"> 260 257 261 258 <?php submit_button( __( 'Update' ), 'primary', null, false ); ?> 262 259 263 264 265 266 267 260 <?php if ( current_user_can( 'delete_term', $tag->term_id ) ) : ?> 261 <span id="delete-link"> 262 <a class="delete" href="<?php echo admin_url( wp_nonce_url( "edit-tags.php?action=delete&taxonomy=$taxonomy&tag_ID=$tag->term_id", 'delete-tag_' . $tag->term_id ) ) ?>"><?php _e( 'Delete' ); ?></a> 263 </span> 264 <?php endif; ?> 268 265 269 266 </div> 270 267 -
src/wp-admin/includes/class-wp-links-list-table.php
diff --git a/src/wp-admin/includes/class-wp-links-list-table.php b/src/wp-admin/includes/class-wp-links-list-table.php index 222d6dfc2d..97fd848e90 100644
a b 17 17 */ 18 18 class WP_Links_List_Table extends WP_List_Table { 19 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 20 /** 21 * Constructor. 22 * 23 * @since 3.1.0 24 * @access public 25 * 26 * @see WP_List_Table::__construct() for more information on default arguments. 27 * 28 * @param array $args An associative array of arguments. 29 */ 30 public function __construct( $args = array() ) { 31 parent::__construct( array( 32 'plural' => 'bookmarks', 33 'screen' => isset( $args['screen'] ) ? $args['screen'] : null, 34 ) ); 35 } 36 36 37 38 39 40 41 42 43 37 /** 38 * 39 * @return bool 40 */ 41 public function ajax_user_can() { 42 return current_user_can( 'manage_links' ); 43 } 44 44 45 /**46 *47 * @global int $cat_id48 * @global string $s49 * @global string $orderby50 * @global string $order51 */52 public function prepare_items() {53 global $cat_id, $s, $orderby, $order;54 45 55 wp_reset_vars( array( 'action', 'cat_id', 'link_id', 'orderby', 'order', 's' ) ); 46 public function prepare_items() { 47 $cat_id = wp_assign_request_var('cat_id'); 48 $s = wp_assign_request_var('s'); 49 $orderby = wp_assign_request_var('orderby'); 50 $order = wp_assign_request_var('order'); 56 51 57 $args = array( 'hide_invisible' => 0, 'hide_empty' => 0 );58 52 59 if ( 'all' != $cat_id ) 60 $args['category'] = $cat_id; 61 if ( !empty( $s ) ) 62 $args['search'] = $s; 63 if ( !empty( $orderby ) ) 64 $args['orderby'] = $orderby; 65 if ( !empty( $order ) ) 66 $args['order'] = $order; 53 $args = array( 'hide_invisible' => 0, 'hide_empty' => 0 ); 67 54 68 $this->items = get_bookmarks( $args ); 69 } 55 if ( 'all' != $cat_id ) { 56 $args['category'] = $cat_id; 57 } 58 if ( !empty( $s ) ) { 59 $args['search'] = $s; 60 } 61 if ( !empty( $orderby ) ) { 62 $args['orderby'] = $orderby; 63 } 64 if ( !empty( $order ) ) { 65 $args['order'] = $order; 66 } 70 67 71 /** 72 * @access public 73 */ 74 public function no_items() { 75 _e( 'No links found.' ); 76 } 68 $this->items = get_bookmarks( $args ); 69 } 77 70 78 /** 79 * 80 * @return array 81 */ 82 protected function get_bulk_actions() { 83 $actions = array(); 84 $actions['delete'] = __( 'Delete' ); 71 /** 72 * @access public 73 */ 74 public function no_items() { 75 _e( 'No links found.' ); 76 } 85 77 86 return $actions; 87 } 78 /** 79 * 80 * @return array 81 */ 82 protected function get_bulk_actions() { 83 $actions = array(); 84 $actions['delete'] = __( 'Delete' ); 88 85 89 /** 90 * 91 * @global int $cat_id 92 * @param string $which 93 */ 94 protected function extra_tablenav( $which ) { 95 global $cat_id; 86 return $actions; 87 } 96 88 97 if ( 'top' != $which ) 98 return; 89 /** 90 * 91 * @global int $cat_id 92 * @param string $which 93 */ 94 protected function extra_tablenav( $which ) { 95 global $cat_id; 96 97 if ( 'top' != $which ) 98 return; 99 99 ?> 100 100 <div class="alignleft actions"> 101 101 <?php 102 103 104 105 106 107 108 109 110 111 102 $dropdown_options = array( 103 'selected' => $cat_id, 104 'name' => 'cat_id', 105 'taxonomy' => 'link_category', 106 'show_option_all' => get_taxonomy( 'link_category' )->labels->all_items, 107 'hide_empty' => true, 108 'hierarchical' => 1, 109 'show_count' => 0, 110 'orderby' => 'name', 111 ); 112 112 113 114 115 113 echo '<label class="screen-reader-text" for="cat_id">' . __( 'Filter by category' ) . '</label>'; 114 wp_dropdown_categories( $dropdown_options ); 115 submit_button( __( 'Filter' ), '', 'filter_action', false, array( 'id' => 'post-query-submit' ) ); 116 116 ?> 117 117 </div> 118 118 <?php 119 119 } 120 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 121 /** 122 * 123 * @return array 124 */ 125 public function get_columns() { 126 return array( 127 'cb' => '<input type="checkbox" />', 128 'name' => _x( 'Name', 'link name' ), 129 'url' => __( 'URL' ), 130 'categories' => __( 'Categories' ), 131 'rel' => __( 'Relationship' ), 132 'visible' => __( 'Visible' ), 133 'rating' => __( 'Rating' ) 134 ); 135 } 136 136 137 138 139 140 141 142 143 144 145 146 147 148 137 /** 138 * 139 * @return array 140 */ 141 protected function get_sortable_columns() { 142 return array( 143 'name' => 'name', 144 'url' => 'url', 145 'visible' => 'visible', 146 'rating' => 'rating' 147 ); 148 } 149 149 150 151 152 153 154 155 156 157 158 159 160 150 /** 151 * Get the name of the default primary column. 152 * 153 * @since 4.3.0 154 * @access protected 155 * 156 * @return string Name of the default primary column, in this case, 'name'. 157 */ 158 protected function get_default_primary_column_name() { 159 return 'name'; 160 } 161 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 162 /** 163 * Handles the checkbox column output. 164 * 165 * @since 4.3.0 166 * @access public 167 * 168 * @param object $link The current link object. 169 */ 170 public function column_cb( $link ) { 171 ?> 172 <label class="screen-reader-text" for="cb-select-<?php echo $link->link_id; ?>"><?php echo sprintf( __( 'Select %s' ), $link->link_name ); ?></label> 173 <input type="checkbox" name="linkcheck[]" id="cb-select-<?php echo $link->link_id; ?>" value="<?php echo esc_attr( $link->link_id ); ?>" /> 174 <?php 175 } 176 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 177 /** 178 * Handles the link name column output. 179 * 180 * @since 4.3.0 181 * @access public 182 * 183 * @param object $link The current link object. 184 */ 185 public function column_name( $link ) { 186 $edit_link = get_edit_bookmark_link( $link ); 187 printf( '<strong><a class="row-title" href="%s" aria-label="%s">%s</a></strong>', 188 $edit_link, 189 /* translators: %s: link name */ 190 esc_attr( sprintf( __( 'Edit “%s”' ), $link->link_name ) ), 191 $link->link_name 192 ); 193 } 194 194 195 196 197 198 199 200 201 202 203 204 205 206 195 /** 196 * Handles the link URL column output. 197 * 198 * @since 4.3.0 199 * @access public 200 * 201 * @param object $link The current link object. 202 */ 203 public function column_url( $link ) { 204 $short_url = url_shorten( $link->link_url ); 205 echo "<a href='$link->link_url'>$short_url</a>"; 206 } 207 207 208 209 210 211 212 213 214 215 216 217 218 219 208 /** 209 * Handles the link categories column output. 210 * 211 * @since 4.3.0 212 * @access public 213 * 214 * @global $cat_id 215 * 216 * @param object $link The current link object. 217 */ 218 public function column_categories( $link ) { 219 global $cat_id; 220 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 221 $cat_names = array(); 222 foreach ( $link->link_category as $category ) { 223 $cat = get_term( $category, 'link_category', OBJECT, 'display' ); 224 if ( is_wp_error( $cat ) ) { 225 echo $cat->get_error_message(); 226 } 227 $cat_name = $cat->name; 228 if ( $cat_id != $category ) { 229 $cat_name = "<a href='link-manager.php?cat_id=$category'>$cat_name</a>"; 230 } 231 $cat_names[] = $cat_name; 232 } 233 echo implode( ', ', $cat_names ); 234 } 235 235 236 237 238 239 240 241 242 243 244 245 246 236 /** 237 * Handles the link relation column output. 238 * 239 * @since 4.3.0 240 * @access public 241 * 242 * @param object $link The current link object. 243 */ 244 public function column_rel( $link ) { 245 echo empty( $link->link_rel ) ? '<br />' : $link->link_rel; 246 } 247 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 248 /** 249 * Handles the link visibility column output. 250 * 251 * @since 4.3.0 252 * @access public 253 * 254 * @param object $link The current link object. 255 */ 256 public function column_visible( $link ) { 257 if ( 'Y' === $link->link_visible ) { 258 _e( 'Yes' ); 259 } else { 260 _e( 'No' ); 261 } 262 } 263 263 264 265 266 267 268 269 270 271 272 273 274 264 /** 265 * Handles the link rating column output. 266 * 267 * @since 4.3.0 268 * @access public 269 * 270 * @param object $link The current link object. 271 */ 272 public function column_rating( $link ) { 273 echo $link->link_rating; 274 } 275 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 276 /** 277 * Handles the default column output. 278 * 279 * @since 4.3.0 280 * @access public 281 * 282 * @param object $link Link object. 283 * @param string $column_name Current column name. 284 */ 285 public function column_default( $link, $column_name ) { 286 /** 287 * Fires for each registered custom link column. 288 * 289 * @since 2.1.0 290 * 291 * @param string $column_name Name of the custom column. 292 * @param int $link_id Link ID. 293 */ 294 do_action( 'manage_link_custom_column', $column_name, $link->link_id ); 295 } 296 296 297 298 299 300 301 297 public function display_rows() { 298 foreach ( $this->items as $link ) { 299 $link = sanitize_bookmark( $link ); 300 $link->link_name = esc_attr( $link->link_name ); 301 $link->link_category = wp_get_link_cats( $link->link_id ); 302 302 ?> 303 304 305 303 <tr id="link-<?php echo $link->link_id; ?>"> 304 <?php $this->single_row_columns( $link ) ?> 305 </tr> 306 306 <?php 307 308 307 } 308 } 309 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 310 /** 311 * Generates and displays row action links. 312 * 313 * @since 4.3.0 314 * @access protected 315 * 316 * @param object $link Link being acted upon. 317 * @param string $column_name Current column name. 318 * @param string $primary Primary column name. 319 * @return string Row action output for links. 320 */ 321 protected function handle_row_actions( $link, $column_name, $primary ) { 322 if ( $primary !== $column_name ) { 323 return ''; 324 } 325 325 326 326 $edit_link = get_edit_bookmark_link( $link ); 327 327 328 329 330 331 332 328 $actions = array(); 329 $actions['edit'] = '<a href="' . $edit_link . '">' . __('Edit') . '</a>'; 330 $actions['delete'] = "<a class='submitdelete' href='" . wp_nonce_url("link.php?action=delete&link_id=$link->link_id", 'delete-bookmark_' . $link->link_id) . "' onclick=\"if ( confirm( '" . esc_js(sprintf(__("You are about to delete this link '%s'\n 'Cancel' to stop, 'OK' to delete."), $link->link_name)) . "' ) ) { return true;}return false;\">" . __('Delete') . "</a>"; 331 return $this->row_actions( $actions ); 332 } 333 333 } -
src/wp-admin/includes/class-wp-ms-themes-list-table.php
diff --git a/src/wp-admin/includes/class-wp-ms-themes-list-table.php b/src/wp-admin/includes/class-wp-ms-themes-list-table.php index 0f3865a985..0104da97a0 100644
a b 17 17 */ 18 18 class WP_MS_Themes_List_Table extends WP_List_Table { 19 19 20 public $site_id; 21 public $is_site_themes; 22 23 private $has_items; 24 25 /** 26 * Constructor. 27 * 28 * @since 3.1.0 29 * @access public 30 * 31 * @see WP_List_Table::__construct() for more information on default arguments. 32 * 33 * @global string $status 34 * @global int $page 35 * 36 * @param array $args An associative array of arguments. 37 */ 38 public function __construct( $args = array() ) { 39 global $status, $page; 40 41 parent::__construct( array( 42 'plural' => 'themes', 43 'screen' => isset( $args['screen'] ) ? $args['screen'] : null, 44 ) ); 45 46 $status = isset( $_REQUEST['theme_status'] ) ? $_REQUEST['theme_status'] : 'all'; 47 if ( !in_array( $status, array( 'all', 'enabled', 'disabled', 'upgrade', 'search', 'broken' ) ) ) 48 $status = 'all'; 49 50 $page = $this->get_pagenum(); 51 52 $this->is_site_themes = ( 'site-themes-network' === $this->screen->id ) ? true : false; 53 54 if ( $this->is_site_themes ) 55 $this->site_id = isset( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : 0; 56 } 57 58 /** 59 * 60 * @return array 61 */ 62 protected function get_table_classes() { 63 // todo: remove and add CSS for .themes 64 return array( 'widefat', 'plugins' ); 65 } 66 67 /** 68 * 69 * @return bool 70 */ 71 public function ajax_user_can() { 72 if ( $this->is_site_themes ) 73 return current_user_can( 'manage_sites' ); 74 else 75 return current_user_can( 'manage_network_themes' ); 76 } 77 78 /** 79 * 80 * @global string $status 81 * @global array $totals 82 * @global int $page 83 * @global string $orderby 84 * @global string $order 85 * @global string $s 86 */ 87 public function prepare_items() { 88 global $status, $totals, $page, $orderby, $order, $s; 89 90 wp_reset_vars( array( 'orderby', 'order', 's' ) ); 91 92 $themes = array( 93 /** 94 * Filters the full array of WP_Theme objects to list in the Multisite 95 * themes list table. 96 * 97 * @since 3.1.0 98 * 99 * @param array $all An array of WP_Theme objects to display in the list table. 100 */ 101 'all' => apply_filters( 'all_themes', wp_get_themes() ), 102 'search' => array(), 103 'enabled' => array(), 104 'disabled' => array(), 105 'upgrade' => array(), 106 'broken' => $this->is_site_themes ? array() : wp_get_themes( array( 'errors' => true ) ), 107 ); 108 109 if ( $this->is_site_themes ) { 110 $themes_per_page = $this->get_items_per_page( 'site_themes_network_per_page' ); 111 $allowed_where = 'site'; 112 } else { 113 $themes_per_page = $this->get_items_per_page( 'themes_network_per_page' ); 114 $allowed_where = 'network'; 115 } 116 117 $maybe_update = current_user_can( 'update_themes' ) && ! $this->is_site_themes && $current = get_site_transient( 'update_themes' ); 118 119 foreach ( (array) $themes['all'] as $key => $theme ) { 120 if ( $this->is_site_themes && $theme->is_allowed( 'network' ) ) { 121 unset( $themes['all'][ $key ] ); 122 continue; 123 } 124 125 if ( $maybe_update && isset( $current->response[ $key ] ) ) { 126 $themes['all'][ $key ]->update = true; 127 $themes['upgrade'][ $key ] = $themes['all'][ $key ]; 128 } 129 130 $filter = $theme->is_allowed( $allowed_where, $this->site_id ) ? 'enabled' : 'disabled'; 131 $themes[ $filter ][ $key ] = $themes['all'][ $key ]; 132 } 133 134 if ( $s ) { 135 $status = 'search'; 136 $themes['search'] = array_filter( array_merge( $themes['all'], $themes['broken'] ), array( $this, '_search_callback' ) ); 137 } 138 139 $totals = array(); 140 foreach ( $themes as $type => $list ) 141 $totals[ $type ] = count( $list ); 142 143 if ( empty( $themes[ $status ] ) && !in_array( $status, array( 'all', 'search' ) ) ) 144 $status = 'all'; 145 146 $this->items = $themes[ $status ]; 147 WP_Theme::sort_by_name( $this->items ); 148 149 $this->has_items = ! empty( $themes['all'] ); 150 $total_this_page = $totals[ $status ]; 151 152 wp_localize_script( 'updates', '_wpUpdatesItemCounts', array( 153 'themes' => $totals, 154 'totals' => wp_get_update_data(), 155 ) ); 156 157 if ( $orderby ) { 158 $orderby = ucfirst( $orderby ); 159 $order = strtoupper( $order ); 160 161 if ( $orderby === 'Name' ) { 162 if ( 'ASC' === $order ) { 163 $this->items = array_reverse( $this->items ); 164 } 165 } else { 166 uasort( $this->items, array( $this, '_order_callback' ) ); 167 } 168 } 169 170 $start = ( $page - 1 ) * $themes_per_page; 171 172 if ( $total_this_page > $themes_per_page ) 173 $this->items = array_slice( $this->items, $start, $themes_per_page, true ); 174 175 $this->set_pagination_args( array( 176 'total_items' => $total_this_page, 177 'per_page' => $themes_per_page, 178 ) ); 179 } 180 181 /** 182 * @staticvar string $term 183 * @param WP_Theme $theme 184 * @return bool 185 */ 186 public function _search_callback( $theme ) { 187 static $term = null; 188 if ( is_null( $term ) ) 189 $term = wp_unslash( $_REQUEST['s'] ); 190 191 foreach ( array( 'Name', 'Description', 'Author', 'Author', 'AuthorURI' ) as $field ) { 192 // Don't mark up; Do translate. 193 if ( false !== stripos( $theme->display( $field, false, true ), $term ) ) 194 return true; 195 } 196 197 if ( false !== stripos( $theme->get_stylesheet(), $term ) ) 198 return true; 199 200 if ( false !== stripos( $theme->get_template(), $term ) ) 201 return true; 202 203 return false; 204 } 205 206 // Not used by any core columns. 207 /** 208 * @global string $orderby 209 * @global string $order 210 * @param array $theme_a 211 * @param array $theme_b 212 * @return int 213 */ 214 public function _order_callback( $theme_a, $theme_b ) { 215 global $orderby, $order; 216 217 $a = $theme_a[ $orderby ]; 218 $b = $theme_b[ $orderby ]; 219 220 if ( $a == $b ) 221 return 0; 222 223 if ( 'DESC' === $order ) 224 return ( $a < $b ) ? 1 : -1; 225 else 226 return ( $a < $b ) ? -1 : 1; 227 } 228 229 /** 230 * @access public 231 */ 232 public function no_items() { 233 if ( $this->has_items ) { 234 _e( 'No themes found.' ); 235 } else { 236 _e( 'You do not appear to have any themes available at this time.' ); 237 } 238 } 239 240 /** 241 * 242 * @return array 243 */ 244 public function get_columns() { 245 return array( 246 'cb' => '<input type="checkbox" />', 247 'name' => __( 'Theme' ), 248 'description' => __( 'Description' ), 249 ); 250 } 251 252 /** 253 * 254 * @return array 255 */ 256 protected function get_sortable_columns() { 257 return array( 258 'name' => 'name', 259 ); 260 } 261 262 /** 263 * Gets the name of the primary column. 264 * 265 * @since 4.3.0 266 * @access protected 267 * 268 * @return string Unalterable name of the primary column name, in this case, 'name'. 269 */ 270 protected function get_primary_column_name() { 271 return 'name'; 272 } 273 274 /** 275 * 276 * @global array $totals 277 * @global string $status 278 * @return array 279 */ 280 protected function get_views() { 281 global $totals, $status; 282 283 $status_links = array(); 284 foreach ( $totals as $type => $count ) { 285 if ( !$count ) 286 continue; 287 288 switch ( $type ) { 289 case 'all': 290 $text = _nx( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $count, 'themes' ); 291 break; 292 case 'enabled': 293 $text = _n( 'Enabled <span class="count">(%s)</span>', 'Enabled <span class="count">(%s)</span>', $count ); 294 break; 295 case 'disabled': 296 $text = _n( 'Disabled <span class="count">(%s)</span>', 'Disabled <span class="count">(%s)</span>', $count ); 297 break; 298 case 'upgrade': 299 $text = _n( 'Update Available <span class="count">(%s)</span>', 'Update Available <span class="count">(%s)</span>', $count ); 300 break; 301 case 'broken' : 302 $text = _n( 'Broken <span class="count">(%s)</span>', 'Broken <span class="count">(%s)</span>', $count ); 303 break; 304 } 305 306 if ( $this->is_site_themes ) 307 $url = 'site-themes.php?id=' . $this->site_id; 308 else 309 $url = 'themes.php'; 310 311 if ( 'search' != $type ) { 312 $status_links[$type] = sprintf( "<a href='%s' %s>%s</a>", 313 esc_url( add_query_arg('theme_status', $type, $url) ), 314 ( $type === $status ) ? ' class="current"' : '', 315 sprintf( $text, number_format_i18n( $count ) ) 316 ); 317 } 318 } 319 320 return $status_links; 321 } 322 323 /** 324 * @global string $status 325 * 326 * @return array 327 */ 328 protected function get_bulk_actions() { 329 global $status; 330 331 $actions = array(); 332 if ( 'enabled' != $status ) 333 $actions['enable-selected'] = $this->is_site_themes ? __( 'Enable' ) : __( 'Network Enable' ); 334 if ( 'disabled' != $status ) 335 $actions['disable-selected'] = $this->is_site_themes ? __( 'Disable' ) : __( 'Network Disable' ); 336 if ( ! $this->is_site_themes ) { 337 if ( current_user_can( 'update_themes' ) ) 338 $actions['update-selected'] = __( 'Update' ); 339 if ( current_user_can( 'delete_themes' ) ) 340 $actions['delete-selected'] = __( 'Delete' ); 341 } 342 return $actions; 343 } 344 345 /** 346 * @access public 347 */ 348 public function display_rows() { 349 foreach ( $this->items as $theme ) 350 $this->single_row( $theme ); 351 } 352 353 /** 354 * Handles the checkbox column output. 355 * 356 * @since 4.3.0 357 * @access public 358 * 359 * @param WP_Theme $theme The current WP_Theme object. 360 */ 361 public function column_cb( $theme ) { 362 $checkbox_id = 'checkbox_' . md5( $theme->get('Name') ); 363 ?> 364 <input type="checkbox" name="checked[]" value="<?php echo esc_attr( $theme->get_stylesheet() ) ?>" id="<?php echo $checkbox_id ?>" /> 365 <label class="screen-reader-text" for="<?php echo $checkbox_id ?>" ><?php _e( 'Select' ) ?> <?php echo $theme->display( 'Name' ) ?></label> 366 <?php 367 } 368 369 /** 370 * Handles the name column output. 371 * 372 * @since 4.3.0 373 * @access public 374 * 375 * @global string $status 376 * @global int $page 377 * @global string $s 378 * 379 * @param WP_Theme $theme The current WP_Theme object. 380 */ 381 public function column_name( $theme ) { 382 global $status, $page, $s; 383 384 $context = $status; 385 386 if ( $this->is_site_themes ) { 387 $url = "site-themes.php?id={$this->site_id}&"; 388 $allowed = $theme->is_allowed( 'site', $this->site_id ); 389 } else { 390 $url = 'themes.php?'; 391 $allowed = $theme->is_allowed( 'network' ); 392 } 393 394 // Pre-order. 395 $actions = array( 396 'enable' => '', 397 'disable' => '', 398 'edit' => '', 399 'delete' => '' 400 ); 401 402 $stylesheet = $theme->get_stylesheet(); 403 $theme_key = urlencode( $stylesheet ); 404 405 if ( ! $allowed ) { 406 if ( ! $theme->errors() ) { 407 $url = add_query_arg( array( 408 'action' => 'enable', 409 'theme' => $theme_key, 410 'paged' => $page, 411 's' => $s, 412 ), $url ); 413 414 if ( $this->is_site_themes ) { 415 /* translators: %s: theme name */ 416 $aria_label = sprintf( __( 'Enable %s' ), $theme->display( 'Name' ) ); 417 } else { 418 /* translators: %s: theme name */ 419 $aria_label = sprintf( __( 'Network Enable %s' ), $theme->display( 'Name' ) ); 420 } 421 422 $actions['enable'] = sprintf( '<a href="%s" class="edit" aria-label="%s">%s</a>', 423 esc_url( wp_nonce_url( $url, 'enable-theme_' . $stylesheet ) ), 424 esc_attr( $aria_label ), 425 ( $this->is_site_themes ? __( 'Enable' ) : __( 'Network Enable' ) ) 426 ); 427 } 428 } else { 429 $url = add_query_arg( array( 430 'action' => 'disable', 431 'theme' => $theme_key, 432 'paged' => $page, 433 's' => $s, 434 ), $url ); 435 436 if ( $this->is_site_themes ) { 437 /* translators: %s: theme name */ 438 $aria_label = sprintf( __( 'Disable %s' ), $theme->display( 'Name' ) ); 439 } else { 440 /* translators: %s: theme name */ 441 $aria_label = sprintf( __( 'Network Disable %s' ), $theme->display( 'Name' ) ); 442 } 443 444 $actions['disable'] = sprintf( '<a href="%s" aria-label="%s">%s</a>', 445 esc_url( wp_nonce_url( $url, 'disable-theme_' . $stylesheet ) ), 446 esc_attr( $aria_label ), 447 ( $this->is_site_themes ? __( 'Disable' ) : __( 'Network Disable' ) ) 448 ); 449 } 450 451 if ( current_user_can('edit_themes') ) { 452 $url = add_query_arg( array( 453 'theme' => $theme_key, 454 ), 'theme-editor.php' ); 455 456 /* translators: %s: theme name */ 457 $aria_label = sprintf( __( 'Open %s in the Theme Editor' ), $theme->display( 'Name' ) ); 458 459 $actions['edit'] = sprintf( '<a href="%s" class="edit" aria-label="%s">%s</a>', 460 esc_url( $url ), 461 esc_attr( $aria_label ), 462 __( 'Edit' ) 463 ); 464 } 465 466 if ( ! $allowed && current_user_can( 'delete_themes' ) && ! $this->is_site_themes && $stylesheet != get_option( 'stylesheet' ) && $stylesheet != get_option( 'template' ) ) { 467 $url = add_query_arg( array( 468 'action' => 'delete-selected', 469 'checked[]' => $theme_key, 470 'theme_status' => $context, 471 'paged' => $page, 472 's' => $s, 473 ), 'themes.php' ); 474 475 /* translators: %s: theme name */ 476 $aria_label = sprintf( _x( 'Delete %s', 'theme' ), $theme->display( 'Name' ) ); 477 478 $actions['delete'] = sprintf( '<a href="%s" class="delete" aria-label="%s">%s</a>', 479 esc_url( wp_nonce_url( $url, 'bulk-themes' ) ), 480 esc_attr( $aria_label ), 481 __( 'Delete' ) 482 ); 483 } 484 /** 485 * Filters the action links displayed for each theme in the Multisite 486 * themes list table. 487 * 488 * The action links displayed are determined by the theme's status, and 489 * which Multisite themes list table is being displayed - the Network 490 * themes list table (themes.php), which displays all installed themes, 491 * or the Site themes list table (site-themes.php), which displays the 492 * non-network enabled themes when editing a site in the Network admin. 493 * 494 * The default action links for the Network themes list table include 495 * 'Network Enable', 'Network Disable', 'Edit', and 'Delete'. 496 * 497 * The default action links for the Site themes list table include 498 * 'Enable', 'Disable', and 'Edit'. 499 * 500 * @since 2.8.0 501 * 502 * @param array $actions An array of action links. 503 * @param WP_Theme $theme The current WP_Theme object. 504 * @param string $context Status of the theme. 505 */ 506 $actions = apply_filters( 'theme_action_links', array_filter( $actions ), $theme, $context ); 507 508 /** 509 * Filters the action links of a specific theme in the Multisite themes 510 * list table. 511 * 512 * The dynamic portion of the hook name, `$stylesheet`, refers to the 513 * directory name of the theme, which in most cases is synonymous 514 * with the template name. 515 * 516 * @since 3.1.0 517 * 518 * @param array $actions An array of action links. 519 * @param WP_Theme $theme The current WP_Theme object. 520 * @param string $context Status of the theme. 521 */ 522 $actions = apply_filters( "theme_action_links_{$stylesheet}", $actions, $theme, $context ); 523 524 echo $this->row_actions( $actions, true ); 525 } 526 527 /** 528 * Handles the description column output. 529 * 530 * @since 4.3.0 531 * @access public 532 * 533 * @global string $status 534 * @global array $totals 535 * 536 * @param WP_Theme $theme The current WP_Theme object. 537 */ 538 public function column_description( $theme ) { 539 global $status, $totals; 540 if ( $theme->errors() ) { 541 $pre = $status === 'broken' ? __( 'Broken Theme:' ) . ' ' : ''; 542 echo '<p><strong class="error-message">' . $pre . $theme->errors()->get_error_message() . '</strong></p>'; 543 } 544 545 if ( $this->is_site_themes ) { 546 $allowed = $theme->is_allowed( 'site', $this->site_id ); 547 } else { 548 $allowed = $theme->is_allowed( 'network' ); 549 } 550 551 $class = ! $allowed ? 'inactive' : 'active'; 552 if ( ! empty( $totals['upgrade'] ) && ! empty( $theme->update ) ) 553 $class .= ' update'; 554 555 echo "<div class='theme-description'><p>" . $theme->display( 'Description' ) . "</p></div> 556 <div class='$class second theme-version-author-uri'>"; 557 558 $stylesheet = $theme->get_stylesheet(); 559 $theme_meta = array(); 560 561 if ( $theme->get('Version') ) { 562 $theme_meta[] = sprintf( __( 'Version %s' ), $theme->display('Version') ); 563 } 564 $theme_meta[] = sprintf( __( 'By %s' ), $theme->display('Author') ); 565 566 if ( $theme->get('ThemeURI') ) { 567 /* translators: %s: theme name */ 568 $aria_label = sprintf( __( 'Visit %s homepage' ), $theme->display( 'Name' ) ); 569 570 $theme_meta[] = sprintf( '<a href="%s" aria-label="%s">%s</a>', 571 $theme->display( 'ThemeURI' ), 572 esc_attr( $aria_label ), 573 __( 'Visit Theme Site' ) 574 ); 575 } 576 /** 577 * Filters the array of row meta for each theme in the Multisite themes 578 * list table. 579 * 580 * @since 3.1.0 581 * 582 * @param array $theme_meta An array of the theme's metadata, 583 * including the version, author, and 584 * theme URI. 585 * @param string $stylesheet Directory name of the theme. 586 * @param WP_Theme $theme WP_Theme object. 587 * @param string $status Status of the theme. 588 */ 589 $theme_meta = apply_filters( 'theme_row_meta', $theme_meta, $stylesheet, $theme, $status ); 590 echo implode( ' | ', $theme_meta ); 591 592 echo '</div>'; 593 } 594 595 /** 596 * Handles default column output. 597 * 598 * @since 4.3.0 599 * @access public 600 * 601 * @param WP_Theme $theme The current WP_Theme object. 602 * @param string $column_name The current column name. 603 */ 604 public function column_default( $theme, $column_name ) { 605 $stylesheet = $theme->get_stylesheet(); 606 607 /** 608 * Fires inside each custom column of the Multisite themes list table. 609 * 610 * @since 3.1.0 611 * 612 * @param string $column_name Name of the column. 613 * @param string $stylesheet Directory name of the theme. 614 * @param WP_Theme $theme Current WP_Theme object. 615 */ 616 do_action( 'manage_themes_custom_column', $column_name, $stylesheet, $theme ); 617 } 618 619 /** 620 * Handles the output for a single table row. 621 * 622 * @since 4.3.0 623 * @access public 624 * 625 * @param WP_Theme $item The current WP_Theme object. 626 */ 627 public function single_row_columns( $item ) { 628 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info(); 629 630 foreach ( $columns as $column_name => $column_display_name ) { 631 $extra_classes = ''; 632 if ( in_array( $column_name, $hidden ) ) { 633 $extra_classes .= ' hidden'; 634 } 635 636 switch ( $column_name ) { 637 case 'cb': 638 echo '<th scope="row" class="check-column">'; 639 640 $this->column_cb( $item ); 641 642 echo '</th>'; 643 break; 644 645 case 'name': 646 echo "<td class='theme-title column-primary{$extra_classes}'><strong>" . $item->display('Name') . "</strong>"; 647 648 $this->column_name( $item ); 649 650 echo "</td>"; 651 break; 652 653 case 'description': 654 echo "<td class='column-description desc{$extra_classes}'>"; 655 656 $this->column_description( $item ); 657 658 echo '</td>'; 659 break; 660 661 default: 662 echo "<td class='$column_name column-$column_name{$extra_classes}'>"; 663 664 $this->column_default( $item, $column_name ); 665 666 echo "</td>"; 667 break; 668 } 669 } 670 } 671 672 /** 673 * @global string $status 674 * @global array $totals 675 * 676 * @param WP_Theme $theme 677 */ 678 public function single_row( $theme ) { 679 global $status, $totals; 680 681 if ( $this->is_site_themes ) { 682 $allowed = $theme->is_allowed( 'site', $this->site_id ); 683 } else { 684 $allowed = $theme->is_allowed( 'network' ); 685 } 686 687 $stylesheet = $theme->get_stylesheet(); 688 689 $class = ! $allowed ? 'inactive' : 'active'; 690 if ( ! empty( $totals['upgrade'] ) && ! empty( $theme->update ) ) { 691 $class .= ' update'; 692 } 693 694 printf( '<tr class="%s" data-slug="%s">', 695 esc_attr( $class ), 696 esc_attr( $stylesheet ) 697 ); 698 699 $this->single_row_columns( $theme ); 700 701 echo "</tr>"; 702 703 if ( $this->is_site_themes ) 704 remove_action( "after_theme_row_$stylesheet", 'wp_theme_update_row' ); 705 706 /** 707 * Fires after each row in the Multisite themes list table. 708 * 709 * @since 3.1.0 710 * 711 * @param string $stylesheet Directory name of the theme. 712 * @param WP_Theme $theme Current WP_Theme object. 713 * @param string $status Status of the theme. 714 */ 715 do_action( 'after_theme_row', $stylesheet, $theme, $status ); 716 717 /** 718 * Fires after each specific row in the Multisite themes list table. 719 * 720 * The dynamic portion of the hook name, `$stylesheet`, refers to the 721 * directory name of the theme, most often synonymous with the template 722 * name of the theme. 723 * 724 * @since 3.5.0 725 * 726 * @param string $stylesheet Directory name of the theme. 727 * @param WP_Theme $theme Current WP_Theme object. 728 * @param string $status Status of the theme. 729 */ 730 do_action( "after_theme_row_{$stylesheet}", $stylesheet, $theme, $status ); 731 } 20 public $site_id; 21 public $is_site_themes; 22 23 private $has_items; 24 25 /** 26 * Constructor. 27 * 28 * @since 3.1.0 29 * @access public 30 * 31 * @see WP_List_Table::__construct() for more information on default arguments. 32 * 33 * @global string $status 34 * @global int $page 35 * 36 * @param array $args An associative array of arguments. 37 */ 38 public function __construct( $args = array() ) { 39 global $status, $page; 40 41 parent::__construct( array( 42 'plural' => 'themes', 43 'screen' => isset( $args['screen'] ) ? $args['screen'] : null, 44 ) ); 45 46 $status = isset( $_REQUEST['theme_status'] ) ? $_REQUEST['theme_status'] : 'all'; 47 if ( !in_array( $status, array( 'all', 'enabled', 'disabled', 'upgrade', 'search', 'broken' ) ) ) 48 $status = 'all'; 49 50 $page = $this->get_pagenum(); 51 52 $this->is_site_themes = ( 'site-themes-network' === $this->screen->id ) ? true : false; 53 54 if ( $this->is_site_themes ) 55 $this->site_id = isset( $_REQUEST['id'] ) ? intval( $_REQUEST['id'] ) : 0; 56 } 57 58 /** 59 * 60 * @return array 61 */ 62 protected function get_table_classes() { 63 // todo: remove and add CSS for .themes 64 return array( 'widefat', 'plugins' ); 65 } 66 67 /** 68 * 69 * @return bool 70 */ 71 public function ajax_user_can() { 72 if ( $this->is_site_themes ) 73 return current_user_can( 'manage_sites' ); 74 else 75 return current_user_can( 'manage_network_themes' ); 76 } 77 78 public function prepare_items() { 79 $status = wp_assign_request_var('status'); 80 $total = wp_assign_request_var('total'); 81 $page = wp_assign_request_var('page'); 82 $orderby = wp_assign_request_var('orderby'); 83 $order = wp_assign_request_var('order'); 84 $s = wp_assign_request_var('s'); 85 86 $themes = array( 87 /** 88 * Filters the full array of WP_Theme objects to list in the Multisite 89 * themes list table. 90 * 91 * @since 3.1.0 92 * 93 * @param array $all An array of WP_Theme objects to display in the list table. 94 */ 95 'all' => apply_filters( 'all_themes', wp_get_themes() ), 96 'search' => array(), 97 'enabled' => array(), 98 'disabled' => array(), 99 'upgrade' => array(), 100 'broken' => $this->is_site_themes ? array() : wp_get_themes( array( 'errors' => true ) ), 101 ); 102 103 if ( $this->is_site_themes ) { 104 $themes_per_page = $this->get_items_per_page( 'site_themes_network_per_page' ); 105 $allowed_where = 'site'; 106 } else { 107 $themes_per_page = $this->get_items_per_page( 'themes_network_per_page' ); 108 $allowed_where = 'network'; 109 } 110 111 $maybe_update = current_user_can( 'update_themes' ) && ! $this->is_site_themes && $current = get_site_transient( 'update_themes' ); 112 113 foreach ( (array) $themes['all'] as $key => $theme ) { 114 if ( $this->is_site_themes && $theme->is_allowed( 'network' ) ) { 115 unset( $themes['all'][ $key ] ); 116 continue; 117 } 118 119 if ( $maybe_update && isset( $current->response[ $key ] ) ) { 120 $themes['all'][ $key ]->update = true; 121 $themes['upgrade'][ $key ] = $themes['all'][ $key ]; 122 } 123 124 $filter = $theme->is_allowed( $allowed_where, $this->site_id ) ? 'enabled' : 'disabled'; 125 $themes[ $filter ][ $key ] = $themes['all'][ $key ]; 126 } 127 128 if ( $s ) { 129 $status = 'search'; 130 $themes['search'] = array_filter( array_merge( $themes['all'], $themes['broken'] ), array( $this, '_search_callback' ) ); 131 } 132 133 $totals = array(); 134 foreach ( $themes as $type => $list ) 135 $totals[ $type ] = count( $list ); 136 137 if ( empty( $themes[ $status ] ) && !in_array( $status, array( 'all', 'search' ) ) ) 138 $status = 'all'; 139 140 $this->items = $themes[ $status ]; 141 WP_Theme::sort_by_name( $this->items ); 142 143 $this->has_items = ! empty( $themes['all'] ); 144 $total_this_page = $totals[ $status ]; 145 146 wp_localize_script( 'updates', '_wpUpdatesItemCounts', array( 147 'themes' => $totals, 148 'totals' => wp_get_update_data(), 149 ) ); 150 151 if ( $orderby ) { 152 $orderby = ucfirst( $orderby ); 153 $order = strtoupper( $order ); 154 155 if ( $orderby === 'Name' ) { 156 if ( 'ASC' === $order ) { 157 $this->items = array_reverse( $this->items ); 158 } 159 } else { 160 uasort( $this->items, array( $this, '_order_callback' ) ); 161 } 162 } 163 164 $start = ( $page - 1 ) * $themes_per_page; 165 166 if ( $total_this_page > $themes_per_page ) 167 $this->items = array_slice( $this->items, $start, $themes_per_page, true ); 168 169 $this->set_pagination_args( array( 170 'total_items' => $total_this_page, 171 'per_page' => $themes_per_page, 172 ) ); 173 } 174 175 /** 176 * @staticvar string $term 177 * @param WP_Theme $theme 178 * @return bool 179 */ 180 public function _search_callback( $theme ) { 181 static $term = null; 182 if ( is_null( $term ) ) 183 $term = wp_unslash( $_REQUEST['s'] ); 184 185 foreach ( array( 'Name', 'Description', 'Author', 'Author', 'AuthorURI' ) as $field ) { 186 // Don't mark up; Do translate. 187 if ( false !== stripos( $theme->display( $field, false, true ), $term ) ) 188 return true; 189 } 190 191 if ( false !== stripos( $theme->get_stylesheet(), $term ) ) 192 return true; 193 194 if ( false !== stripos( $theme->get_template(), $term ) ) 195 return true; 196 197 return false; 198 } 199 200 // Not used by any core columns. 201 /** 202 * @global string $orderby 203 * @global string $order 204 * @param array $theme_a 205 * @param array $theme_b 206 * @return int 207 */ 208 public function _order_callback( $theme_a, $theme_b ) { 209 global $orderby, $order; 210 211 $a = $theme_a[ $orderby ]; 212 $b = $theme_b[ $orderby ]; 213 214 if ( $a == $b ) 215 return 0; 216 217 if ( 'DESC' === $order ) 218 return ( $a < $b ) ? 1 : -1; 219 else 220 return ( $a < $b ) ? -1 : 1; 221 } 222 223 /** 224 * @access public 225 */ 226 public function no_items() { 227 if ( $this->has_items ) { 228 _e( 'No themes found.' ); 229 } else { 230 _e( 'You do not appear to have any themes available at this time.' ); 231 } 232 } 233 234 /** 235 * 236 * @return array 237 */ 238 public function get_columns() { 239 return array( 240 'cb' => '<input type="checkbox" />', 241 'name' => __( 'Theme' ), 242 'description' => __( 'Description' ), 243 ); 244 } 245 246 /** 247 * 248 * @return array 249 */ 250 protected function get_sortable_columns() { 251 return array( 252 'name' => 'name', 253 ); 254 } 255 256 /** 257 * Gets the name of the primary column. 258 * 259 * @since 4.3.0 260 * @access protected 261 * 262 * @return string Unalterable name of the primary column name, in this case, 'name'. 263 */ 264 protected function get_primary_column_name() { 265 return 'name'; 266 } 267 268 /** 269 * 270 * @global array $totals 271 * @global string $status 272 * @return array 273 */ 274 protected function get_views() { 275 global $totals, $status; 276 277 $status_links = array(); 278 foreach ( $totals as $type => $count ) { 279 if ( !$count ) 280 continue; 281 282 switch ( $type ) { 283 case 'all': 284 $text = _nx( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $count, 'themes' ); 285 break; 286 case 'enabled': 287 $text = _n( 'Enabled <span class="count">(%s)</span>', 'Enabled <span class="count">(%s)</span>', $count ); 288 break; 289 case 'disabled': 290 $text = _n( 'Disabled <span class="count">(%s)</span>', 'Disabled <span class="count">(%s)</span>', $count ); 291 break; 292 case 'upgrade': 293 $text = _n( 'Update Available <span class="count">(%s)</span>', 'Update Available <span class="count">(%s)</span>', $count ); 294 break; 295 case 'broken' : 296 $text = _n( 'Broken <span class="count">(%s)</span>', 'Broken <span class="count">(%s)</span>', $count ); 297 break; 298 } 299 300 if ( $this->is_site_themes ) 301 $url = 'site-themes.php?id=' . $this->site_id; 302 else 303 $url = 'themes.php'; 304 305 if ( 'search' != $type ) { 306 $status_links[$type] = sprintf( "<a href='%s' %s>%s</a>", 307 esc_url( add_query_arg('theme_status', $type, $url) ), 308 ( $type === $status ) ? ' class="current"' : '', 309 sprintf( $text, number_format_i18n( $count ) ) 310 ); 311 } 312 } 313 314 return $status_links; 315 } 316 317 /** 318 * @global string $status 319 * 320 * @return array 321 */ 322 protected function get_bulk_actions() { 323 global $status; 324 325 $actions = array(); 326 if ( 'enabled' != $status ) 327 $actions['enable-selected'] = $this->is_site_themes ? __( 'Enable' ) : __( 'Network Enable' ); 328 if ( 'disabled' != $status ) 329 $actions['disable-selected'] = $this->is_site_themes ? __( 'Disable' ) : __( 'Network Disable' ); 330 if ( ! $this->is_site_themes ) { 331 if ( current_user_can( 'update_themes' ) ) 332 $actions['update-selected'] = __( 'Update' ); 333 if ( current_user_can( 'delete_themes' ) ) 334 $actions['delete-selected'] = __( 'Delete' ); 335 } 336 return $actions; 337 } 338 339 /** 340 * @access public 341 */ 342 public function display_rows() { 343 foreach ( $this->items as $theme ) 344 $this->single_row( $theme ); 345 } 346 347 /** 348 * Handles the checkbox column output. 349 * 350 * @since 4.3.0 351 * @access public 352 * 353 * @param WP_Theme $theme The current WP_Theme object. 354 */ 355 public function column_cb( $theme ) { 356 $checkbox_id = 'checkbox_' . md5( $theme->get('Name') ); 357 ?> 358 <input type="checkbox" name="checked[]" value="<?php echo esc_attr( $theme->get_stylesheet() ) ?>" id="<?php echo $checkbox_id ?>" /> 359 <label class="screen-reader-text" for="<?php echo $checkbox_id ?>" ><?php _e( 'Select' ) ?> <?php echo $theme->display( 'Name' ) ?></label> 360 <?php 361 } 362 363 /** 364 * Handles the name column output. 365 * 366 * @since 4.3.0 367 * @access public 368 * 369 * @global string $status 370 * @global int $page 371 * @global string $s 372 * 373 * @param WP_Theme $theme The current WP_Theme object. 374 */ 375 public function column_name( $theme ) { 376 global $status, $page, $s; 377 378 $context = $status; 379 380 if ( $this->is_site_themes ) { 381 $url = "site-themes.php?id={$this->site_id}&"; 382 $allowed = $theme->is_allowed( 'site', $this->site_id ); 383 } else { 384 $url = 'themes.php?'; 385 $allowed = $theme->is_allowed( 'network' ); 386 } 387 388 // Pre-order. 389 $actions = array( 390 'enable' => '', 391 'disable' => '', 392 'edit' => '', 393 'delete' => '' 394 ); 395 396 $stylesheet = $theme->get_stylesheet(); 397 $theme_key = urlencode( $stylesheet ); 398 399 if ( ! $allowed ) { 400 if ( ! $theme->errors() ) { 401 $url = add_query_arg( array( 402 'action' => 'enable', 403 'theme' => $theme_key, 404 'paged' => $page, 405 's' => $s, 406 ), $url ); 407 408 if ( $this->is_site_themes ) { 409 /* translators: %s: theme name */ 410 $aria_label = sprintf( __( 'Enable %s' ), $theme->display( 'Name' ) ); 411 } else { 412 /* translators: %s: theme name */ 413 $aria_label = sprintf( __( 'Network Enable %s' ), $theme->display( 'Name' ) ); 414 } 415 416 $actions['enable'] = sprintf( '<a href="%s" class="edit" aria-label="%s">%s</a>', 417 esc_url( wp_nonce_url( $url, 'enable-theme_' . $stylesheet ) ), 418 esc_attr( $aria_label ), 419 ( $this->is_site_themes ? __( 'Enable' ) : __( 'Network Enable' ) ) 420 ); 421 } 422 } else { 423 $url = add_query_arg( array( 424 'action' => 'disable', 425 'theme' => $theme_key, 426 'paged' => $page, 427 's' => $s, 428 ), $url ); 429 430 if ( $this->is_site_themes ) { 431 /* translators: %s: theme name */ 432 $aria_label = sprintf( __( 'Disable %s' ), $theme->display( 'Name' ) ); 433 } else { 434 /* translators: %s: theme name */ 435 $aria_label = sprintf( __( 'Network Disable %s' ), $theme->display( 'Name' ) ); 436 } 437 438 $actions['disable'] = sprintf( '<a href="%s" aria-label="%s">%s</a>', 439 esc_url( wp_nonce_url( $url, 'disable-theme_' . $stylesheet ) ), 440 esc_attr( $aria_label ), 441 ( $this->is_site_themes ? __( 'Disable' ) : __( 'Network Disable' ) ) 442 ); 443 } 444 445 if ( current_user_can('edit_themes') ) { 446 $url = add_query_arg( array( 447 'theme' => $theme_key, 448 ), 'theme-editor.php' ); 449 450 /* translators: %s: theme name */ 451 $aria_label = sprintf( __( 'Open %s in the Theme Editor' ), $theme->display( 'Name' ) ); 452 453 $actions['edit'] = sprintf( '<a href="%s" class="edit" aria-label="%s">%s</a>', 454 esc_url( $url ), 455 esc_attr( $aria_label ), 456 __( 'Edit' ) 457 ); 458 } 459 460 if ( ! $allowed && current_user_can( 'delete_themes' ) && ! $this->is_site_themes && $stylesheet != get_option( 'stylesheet' ) && $stylesheet != get_option( 'template' ) ) { 461 $url = add_query_arg( array( 462 'action' => 'delete-selected', 463 'checked[]' => $theme_key, 464 'theme_status' => $context, 465 'paged' => $page, 466 's' => $s, 467 ), 'themes.php' ); 468 469 /* translators: %s: theme name */ 470 $aria_label = sprintf( _x( 'Delete %s', 'theme' ), $theme->display( 'Name' ) ); 471 472 $actions['delete'] = sprintf( '<a href="%s" class="delete" aria-label="%s">%s</a>', 473 esc_url( wp_nonce_url( $url, 'bulk-themes' ) ), 474 esc_attr( $aria_label ), 475 __( 'Delete' ) 476 ); 477 } 478 /** 479 * Filters the action links displayed for each theme in the Multisite 480 * themes list table. 481 * 482 * The action links displayed are determined by the theme's status, and 483 * which Multisite themes list table is being displayed - the Network 484 * themes list table (themes.php), which displays all installed themes, 485 * or the Site themes list table (site-themes.php), which displays the 486 * non-network enabled themes when editing a site in the Network admin. 487 * 488 * The default action links for the Network themes list table include 489 * 'Network Enable', 'Network Disable', 'Edit', and 'Delete'. 490 * 491 * The default action links for the Site themes list table include 492 * 'Enable', 'Disable', and 'Edit'. 493 * 494 * @since 2.8.0 495 * 496 * @param array $actions An array of action links. 497 * @param WP_Theme $theme The current WP_Theme object. 498 * @param string $context Status of the theme. 499 */ 500 $actions = apply_filters( 'theme_action_links', array_filter( $actions ), $theme, $context ); 501 502 /** 503 * Filters the action links of a specific theme in the Multisite themes 504 * list table. 505 * 506 * The dynamic portion of the hook name, `$stylesheet`, refers to the 507 * directory name of the theme, which in most cases is synonymous 508 * with the template name. 509 * 510 * @since 3.1.0 511 * 512 * @param array $actions An array of action links. 513 * @param WP_Theme $theme The current WP_Theme object. 514 * @param string $context Status of the theme. 515 */ 516 $actions = apply_filters( "theme_action_links_{$stylesheet}", $actions, $theme, $context ); 517 518 echo $this->row_actions( $actions, true ); 519 } 520 521 /** 522 * Handles the description column output. 523 * 524 * @since 4.3.0 525 * @access public 526 * 527 * @global string $status 528 * @global array $totals 529 * 530 * @param WP_Theme $theme The current WP_Theme object. 531 */ 532 public function column_description( $theme ) { 533 global $status, $totals; 534 if ( $theme->errors() ) { 535 $pre = $status === 'broken' ? __( 'Broken Theme:' ) . ' ' : ''; 536 echo '<p><strong class="error-message">' . $pre . $theme->errors()->get_error_message() . '</strong></p>'; 537 } 538 539 if ( $this->is_site_themes ) { 540 $allowed = $theme->is_allowed( 'site', $this->site_id ); 541 } else { 542 $allowed = $theme->is_allowed( 'network' ); 543 } 544 545 $class = ! $allowed ? 'inactive' : 'active'; 546 if ( ! empty( $totals['upgrade'] ) && ! empty( $theme->update ) ) 547 $class .= ' update'; 548 549 echo "<div class='theme-description'><p>" . $theme->display( 'Description' ) . "</p></div> 550 <div class='$class second theme-version-author-uri'>"; 551 552 $stylesheet = $theme->get_stylesheet(); 553 $theme_meta = array(); 554 555 if ( $theme->get('Version') ) { 556 $theme_meta[] = sprintf( __( 'Version %s' ), $theme->display('Version') ); 557 } 558 $theme_meta[] = sprintf( __( 'By %s' ), $theme->display('Author') ); 559 560 if ( $theme->get('ThemeURI') ) { 561 /* translators: %s: theme name */ 562 $aria_label = sprintf( __( 'Visit %s homepage' ), $theme->display( 'Name' ) ); 563 564 $theme_meta[] = sprintf( '<a href="%s" aria-label="%s">%s</a>', 565 $theme->display( 'ThemeURI' ), 566 esc_attr( $aria_label ), 567 __( 'Visit Theme Site' ) 568 ); 569 } 570 /** 571 * Filters the array of row meta for each theme in the Multisite themes 572 * list table. 573 * 574 * @since 3.1.0 575 * 576 * @param array $theme_meta An array of the theme's metadata, 577 * including the version, author, and 578 * theme URI. 579 * @param string $stylesheet Directory name of the theme. 580 * @param WP_Theme $theme WP_Theme object. 581 * @param string $status Status of the theme. 582 */ 583 $theme_meta = apply_filters( 'theme_row_meta', $theme_meta, $stylesheet, $theme, $status ); 584 echo implode( ' | ', $theme_meta ); 585 586 echo '</div>'; 587 } 588 589 /** 590 * Handles default column output. 591 * 592 * @since 4.3.0 593 * @access public 594 * 595 * @param WP_Theme $theme The current WP_Theme object. 596 * @param string $column_name The current column name. 597 */ 598 public function column_default( $theme, $column_name ) { 599 $stylesheet = $theme->get_stylesheet(); 600 601 /** 602 * Fires inside each custom column of the Multisite themes list table. 603 * 604 * @since 3.1.0 605 * 606 * @param string $column_name Name of the column. 607 * @param string $stylesheet Directory name of the theme. 608 * @param WP_Theme $theme Current WP_Theme object. 609 */ 610 do_action( 'manage_themes_custom_column', $column_name, $stylesheet, $theme ); 611 } 612 613 /** 614 * Handles the output for a single table row. 615 * 616 * @since 4.3.0 617 * @access public 618 * 619 * @param WP_Theme $item The current WP_Theme object. 620 */ 621 public function single_row_columns( $item ) { 622 list( $columns, $hidden, $sortable, $primary ) = $this->get_column_info(); 623 624 foreach ( $columns as $column_name => $column_display_name ) { 625 $extra_classes = ''; 626 if ( in_array( $column_name, $hidden ) ) { 627 $extra_classes .= ' hidden'; 628 } 629 630 switch ( $column_name ) { 631 case 'cb': 632 echo '<th scope="row" class="check-column">'; 633 634 $this->column_cb( $item ); 635 636 echo '</th>'; 637 break; 638 639 case 'name': 640 echo "<td class='theme-title column-primary{$extra_classes}'><strong>" . $item->display('Name') . "</strong>"; 641 642 $this->column_name( $item ); 643 644 echo "</td>"; 645 break; 646 647 case 'description': 648 echo "<td class='column-description desc{$extra_classes}'>"; 649 650 $this->column_description( $item ); 651 652 echo '</td>'; 653 break; 654 655 default: 656 echo "<td class='$column_name column-$column_name{$extra_classes}'>"; 657 658 $this->column_default( $item, $column_name ); 659 660 echo "</td>"; 661 break; 662 } 663 } 664 } 665 666 /** 667 * @global string $status 668 * @global array $totals 669 * 670 * @param WP_Theme $theme 671 */ 672 public function single_row( $theme ) { 673 global $status, $totals; 674 675 if ( $this->is_site_themes ) { 676 $allowed = $theme->is_allowed( 'site', $this->site_id ); 677 } else { 678 $allowed = $theme->is_allowed( 'network' ); 679 } 680 681 $stylesheet = $theme->get_stylesheet(); 682 683 $class = ! $allowed ? 'inactive' : 'active'; 684 if ( ! empty( $totals['upgrade'] ) && ! empty( $theme->update ) ) { 685 $class .= ' update'; 686 } 687 688 printf( '<tr class="%s" data-slug="%s">', 689 esc_attr( $class ), 690 esc_attr( $stylesheet ) 691 ); 692 693 $this->single_row_columns( $theme ); 694 695 echo "</tr>"; 696 697 if ( $this->is_site_themes ) 698 remove_action( "after_theme_row_$stylesheet", 'wp_theme_update_row' ); 699 700 /** 701 * Fires after each row in the Multisite themes list table. 702 * 703 * @since 3.1.0 704 * 705 * @param string $stylesheet Directory name of the theme. 706 * @param WP_Theme $theme Current WP_Theme object. 707 * @param string $status Status of the theme. 708 */ 709 do_action( 'after_theme_row', $stylesheet, $theme, $status ); 710 711 /** 712 * Fires after each specific row in the Multisite themes list table. 713 * 714 * The dynamic portion of the hook name, `$stylesheet`, refers to the 715 * directory name of the theme, most often synonymous with the template 716 * name of the theme. 717 * 718 * @since 3.5.0 719 * 720 * @param string $stylesheet Directory name of the theme. 721 * @param WP_Theme $theme Current WP_Theme object. 722 * @param string $status Status of the theme. 723 */ 724 do_action( "after_theme_row_{$stylesheet}", $stylesheet, $theme, $status ); 725 } 732 726 } -
src/wp-admin/includes/class-wp-plugin-install-list-table.php
diff --git a/src/wp-admin/includes/class-wp-plugin-install-list-table.php b/src/wp-admin/includes/class-wp-plugin-install-list-table.php index cd718360cb..fd119cc6da 100644
a b 17 17 */ 18 18 class WP_Plugin_Install_List_Table extends WP_List_Table { 19 19 20 public $order = 'ASC'; 21 public $orderby = null; 22 public $groups = array(); 23 24 private $error; 25 26 /** 27 * 28 * @return bool 29 */ 30 public function ajax_user_can() { 31 return current_user_can('install_plugins'); 32 } 33 34 /** 35 * Return a list of slugs of installed plugins, if known. 36 * 37 * Uses the transient data from the updates API to determine the slugs of 38 * known installed plugins. This might be better elsewhere, perhaps even 39 * within get_plugins(). 40 * 41 * @since 4.0.0 42 * @access protected 43 * 44 * @return array 45 */ 46 protected function get_installed_plugin_slugs() { 47 $slugs = array(); 48 49 $plugin_info = get_site_transient( 'update_plugins' ); 50 if ( isset( $plugin_info->no_update ) ) { 51 foreach ( $plugin_info->no_update as $plugin ) { 52 $slugs[] = $plugin->slug; 53 } 54 } 55 56 if ( isset( $plugin_info->response ) ) { 57 foreach ( $plugin_info->response as $plugin ) { 58 $slugs[] = $plugin->slug; 59 } 60 } 61 62 return $slugs; 63 } 64 65 /** 66 * 67 * @global array $tabs 68 * @global string $tab 69 * @global int $paged 70 * @global string $type 71 * @global string $term 72 */ 73 public function prepare_items() { 74 include( ABSPATH . 'wp-admin/includes/plugin-install.php' ); 75 76 global $tabs, $tab, $paged, $type, $term; 77 78 wp_reset_vars( array( 'tab' ) ); 79 80 $paged = $this->get_pagenum(); 81 82 $per_page = 30; 83 84 // These are the tabs which are shown on the page 85 $tabs = array(); 86 87 if ( 'search' === $tab ) { 88 $tabs['search'] = __( 'Search Results' ); 89 } 90 if ( $tab === 'beta' || false !== strpos( get_bloginfo( 'version' ), '-' ) ) { 91 $tabs['beta'] = _x( 'Beta Testing', 'Plugin Installer' ); 92 } 93 $tabs['featured'] = _x( 'Featured', 'Plugin Installer' ); 94 $tabs['popular'] = _x( 'Popular', 'Plugin Installer' ); 95 $tabs['recommended'] = _x( 'Recommended', 'Plugin Installer' ); 96 $tabs['favorites'] = _x( 'Favorites', 'Plugin Installer' ); 97 if ( current_user_can( 'upload_plugins' ) ) { 98 // No longer a real tab. Here for filter compatibility. 99 // Gets skipped in get_views(). 100 $tabs['upload'] = __( 'Upload Plugin' ); 101 } 102 103 $nonmenu_tabs = array( 'plugin-information' ); // Valid actions to perform which do not have a Menu item. 104 105 /** 106 * Filters the tabs shown on the Plugin Install screen. 107 * 108 * @since 2.7.0 109 * 110 * @param array $tabs The tabs shown on the Plugin Install screen. Defaults include 'featured', 'popular', 111 * 'recommended', 'favorites', and 'upload'. 112 */ 113 $tabs = apply_filters( 'install_plugins_tabs', $tabs ); 114 115 /** 116 * Filters tabs not associated with a menu item on the Plugin Install screen. 117 * 118 * @since 2.7.0 119 * 120 * @param array $nonmenu_tabs The tabs that don't have a Menu item on the Plugin Install screen. 121 */ 122 $nonmenu_tabs = apply_filters( 'install_plugins_nonmenu_tabs', $nonmenu_tabs ); 123 124 // If a non-valid menu tab has been selected, And it's not a non-menu action. 125 if ( empty( $tab ) || ( !isset( $tabs[ $tab ] ) && !in_array( $tab, (array) $nonmenu_tabs ) ) ) 126 $tab = key( $tabs ); 127 128 $args = array( 129 'page' => $paged, 130 'per_page' => $per_page, 131 'fields' => array( 132 'last_updated' => true, 133 'icons' => true, 134 'active_installs' => true 135 ), 136 // Send the locale and installed plugin slugs to the API so it can provide context-sensitive results. 137 'locale' => get_user_locale(), 138 'installed_plugins' => $this->get_installed_plugin_slugs(), 139 ); 140 141 switch ( $tab ) { 142 case 'search': 143 $type = isset( $_REQUEST['type'] ) ? wp_unslash( $_REQUEST['type'] ) : 'term'; 144 $term = isset( $_REQUEST['s'] ) ? wp_unslash( $_REQUEST['s'] ) : ''; 145 146 switch ( $type ) { 147 case 'tag': 148 $args['tag'] = sanitize_title_with_dashes( $term ); 149 break; 150 case 'term': 151 $args['search'] = $term; 152 break; 153 case 'author': 154 $args['author'] = $term; 155 break; 156 } 157 158 break; 159 160 case 'featured': 161 $args['fields']['group'] = true; 162 $this->orderby = 'group'; 163 // No break! 164 case 'popular': 165 case 'new': 166 case 'beta': 167 case 'recommended': 168 $args['browse'] = $tab; 169 break; 170 171 case 'favorites': 172 $action = 'save_wporg_username_' . get_current_user_id(); 173 if ( isset( $_GET['_wpnonce'] ) && wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), $action ) ) { 174 $user = isset( $_GET['user'] ) ? wp_unslash( $_GET['user'] ) : get_user_option( 'wporg_favorites' ); 175 update_user_meta( get_current_user_id(), 'wporg_favorites', $user ); 176 } else { 177 $user = get_user_option( 'wporg_favorites' ); 178 } 179 if ( $user ) 180 $args['user'] = $user; 181 else 182 $args = false; 183 184 add_action( 'install_plugins_favorites', 'install_plugins_favorites_form', 9, 0 ); 185 break; 186 187 default: 188 $args = false; 189 break; 190 } 191 192 /** 193 * Filters API request arguments for each Plugin Install screen tab. 194 * 195 * The dynamic portion of the hook name, `$tab`, refers to the plugin install tabs. 196 * Default tabs include 'featured', 'popular', 'recommended', 'favorites', and 'upload'. 197 * 198 * @since 3.7.0 199 * 200 * @param array|bool $args Plugin Install API arguments. 201 */ 202 $args = apply_filters( "install_plugins_table_api_args_{$tab}", $args ); 203 204 if ( !$args ) 205 return; 206 207 $api = plugins_api( 'query_plugins', $args ); 208 209 if ( is_wp_error( $api ) ) { 210 $this->error = $api; 211 return; 212 } 213 214 $this->items = $api->plugins; 215 216 if ( $this->orderby ) { 217 uasort( $this->items, array( $this, 'order_callback' ) ); 218 } 219 220 $this->set_pagination_args( array( 221 'total_items' => $api->info['results'], 222 'per_page' => $args['per_page'], 223 ) ); 224 225 if ( isset( $api->info['groups'] ) ) { 226 $this->groups = $api->info['groups']; 227 } 228 } 229 230 /** 231 * @access public 232 */ 233 public function no_items() { 234 if ( isset( $this->error ) ) { 235 $message = $this->error->get_error_message() . '<p class="hide-if-no-js"><a href="#" class="button" onclick="document.location.reload(); return false;">' . __( 'Try again' ) . '</a></p>'; 236 } else { 237 $message = __( 'No plugins match your request.' ); 238 } 239 echo '<div class="no-plugin-results">' . $message . '</div>'; 240 } 241 242 /** 243 * 244 * @global array $tabs 245 * @global string $tab 246 * 247 * @return array 248 */ 249 protected function get_views() { 250 global $tabs, $tab; 251 252 $display_tabs = array(); 253 foreach ( (array) $tabs as $action => $text ) { 254 $class = ( $action === $tab ) ? ' current' : ''; 255 $href = self_admin_url('plugin-install.php?tab=' . $action); 256 $display_tabs['plugin-install-'.$action] = "<a href='$href' class='$class'>$text</a>"; 257 } 258 // No longer a real tab. 259 unset( $display_tabs['plugin-install-upload'] ); 260 261 return $display_tabs; 262 } 263 264 /** 265 * Override parent views so we can use the filter bar display. 266 */ 267 public function views() { 268 $views = $this->get_views(); 269 270 /** This filter is documented in wp-admin/inclues/class-wp-list-table.php */ 271 $views = apply_filters( "views_{$this->screen->id}", $views ); 272 273 $this->screen->render_screen_reader_content( 'heading_views' ); 20 public $order = 'ASC'; 21 public $orderby = null; 22 public $groups = array(); 23 24 private $error; 25 26 /** 27 * 28 * @return bool 29 */ 30 public function ajax_user_can() { 31 return current_user_can('install_plugins'); 32 } 33 34 /** 35 * Return a list of slugs of installed plugins, if known. 36 * 37 * Uses the transient data from the updates API to determine the slugs of 38 * known installed plugins. This might be better elsewhere, perhaps even 39 * within get_plugins(). 40 * 41 * @since 4.0.0 42 * @access protected 43 * 44 * @return array 45 */ 46 protected function get_installed_plugin_slugs() { 47 $slugs = array(); 48 49 $plugin_info = get_site_transient( 'update_plugins' ); 50 if ( isset( $plugin_info->no_update ) ) { 51 foreach ( $plugin_info->no_update as $plugin ) { 52 $slugs[] = $plugin->slug; 53 } 54 } 55 56 if ( isset( $plugin_info->response ) ) { 57 foreach ( $plugin_info->response as $plugin ) { 58 $slugs[] = $plugin->slug; 59 } 60 } 61 62 return $slugs; 63 } 64 65 public function prepare_items() { 66 include( ABSPATH . 'wp-admin/includes/plugin-install.php' ); 67 68 $tab = wp_assign_request_var('tab'); 69 $type = wp_assign_request_var('type'); 70 $term = wp_assign_request_var('term'); 71 72 $paged = $this->get_pagenum(); 73 74 $per_page = 30; 75 76 // These are the tabs which are shown on the page 77 $tabs = array(); 78 79 if ( 'search' === $tab ) { 80 $tabs['search'] = __( 'Search Results' ); 81 } 82 if ( $tab === 'beta' || false !== strpos( get_bloginfo( 'version' ), '-' ) ) { 83 $tabs['beta'] = _x( 'Beta Testing', 'Plugin Installer' ); 84 } 85 $tabs['featured'] = _x( 'Featured', 'Plugin Installer' ); 86 $tabs['popular'] = _x( 'Popular', 'Plugin Installer' ); 87 $tabs['recommended'] = _x( 'Recommended', 'Plugin Installer' ); 88 $tabs['favorites'] = _x( 'Favorites', 'Plugin Installer' ); 89 if ( current_user_can( 'upload_plugins' ) ) { 90 // No longer a real tab. Here for filter compatibility. 91 // Gets skipped in get_views(). 92 $tabs['upload'] = __( 'Upload Plugin' ); 93 } 94 95 $nonmenu_tabs = array( 'plugin-information' ); // Valid actions to perform which do not have a Menu item. 96 97 /** 98 * Filters the tabs shown on the Plugin Install screen. 99 * 100 * @since 2.7.0 101 * 102 * @param array $tabs The tabs shown on the Plugin Install screen. Defaults include 'featured', 'popular', 103 * 'recommended', 'favorites', and 'upload'. 104 */ 105 $tabs = apply_filters( 'install_plugins_tabs', $tabs ); 106 107 /** 108 * Filters tabs not associated with a menu item on the Plugin Install screen. 109 * 110 * @since 2.7.0 111 * 112 * @param array $nonmenu_tabs The tabs that don't have a Menu item on the Plugin Install screen. 113 */ 114 $nonmenu_tabs = apply_filters( 'install_plugins_nonmenu_tabs', $nonmenu_tabs ); 115 116 // If a non-valid menu tab has been selected, And it's not a non-menu action. 117 if ( empty( $tab ) || ( !isset( $tabs[ $tab ] ) && !in_array( $tab, (array) $nonmenu_tabs ) ) ) 118 $tab = key( $tabs ); 119 120 $args = array( 121 'page' => $paged, 122 'per_page' => $per_page, 123 'fields' => array( 124 'last_updated' => true, 125 'icons' => true, 126 'active_installs' => true 127 ), 128 // Send the locale and installed plugin slugs to the API so it can provide context-sensitive results. 129 'locale' => get_user_locale(), 130 'installed_plugins' => $this->get_installed_plugin_slugs(), 131 ); 132 133 switch ( $tab ) { 134 case 'search': 135 $type = isset( $_REQUEST['type'] ) ? wp_unslash( $_REQUEST['type'] ) : 'term'; 136 $term = isset( $_REQUEST['s'] ) ? wp_unslash( $_REQUEST['s'] ) : ''; 137 138 switch ( $type ) { 139 case 'tag': 140 $args['tag'] = sanitize_title_with_dashes( $term ); 141 break; 142 case 'term': 143 $args['search'] = $term; 144 break; 145 case 'author': 146 $args['author'] = $term; 147 break; 148 } 149 150 break; 151 152 case 'featured': 153 $args['fields']['group'] = true; 154 $this->orderby = 'group'; 155 // No break! 156 case 'popular': 157 case 'new': 158 case 'beta': 159 case 'recommended': 160 $args['browse'] = $tab; 161 break; 162 163 case 'favorites': 164 $action = 'save_wporg_username_' . get_current_user_id(); 165 if ( isset( $_GET['_wpnonce'] ) && wp_verify_nonce( wp_unslash( $_GET['_wpnonce'] ), $action ) ) { 166 $user = isset( $_GET['user'] ) ? wp_unslash( $_GET['user'] ) : get_user_option( 'wporg_favorites' ); 167 update_user_meta( get_current_user_id(), 'wporg_favorites', $user ); 168 } else { 169 $user = get_user_option( 'wporg_favorites' ); 170 } 171 if ( $user ) 172 $args['user'] = $user; 173 else 174 $args = false; 175 176 add_action( 'install_plugins_favorites', 'install_plugins_favorites_form', 9, 0 ); 177 break; 178 179 default: 180 $args = false; 181 break; 182 } 183 184 /** 185 * Filters API request arguments for each Plugin Install screen tab. 186 * 187 * The dynamic portion of the hook name, `$tab`, refers to the plugin install tabs. 188 * Default tabs include 'featured', 'popular', 'recommended', 'favorites', and 'upload'. 189 * 190 * @since 3.7.0 191 * 192 * @param array|bool $args Plugin Install API arguments. 193 */ 194 $args = apply_filters( "install_plugins_table_api_args_{$tab}", $args ); 195 196 if ( !$args ) 197 return; 198 199 $api = plugins_api( 'query_plugins', $args ); 200 201 if ( is_wp_error( $api ) ) { 202 $this->error = $api; 203 return; 204 } 205 206 $this->items = $api->plugins; 207 208 if ( $this->orderby ) { 209 uasort( $this->items, array( $this, 'order_callback' ) ); 210 } 211 212 $this->set_pagination_args( array( 213 'total_items' => $api->info['results'], 214 'per_page' => $args['per_page'], 215 ) ); 216 217 if ( isset( $api->info['groups'] ) ) { 218 $this->groups = $api->info['groups']; 219 } 220 } 221 222 /** 223 * @access public 224 */ 225 public function no_items() { 226 if ( isset( $this->error ) ) { 227 $message = $this->error->get_error_message() . '<p class="hide-if-no-js"><a href="#" class="button" onclick="document.location.reload(); return false;">' . __( 'Try again' ) . '</a></p>'; 228 } else { 229 $message = __( 'No plugins match your request.' ); 230 } 231 echo '<div class="no-plugin-results">' . $message . '</div>'; 232 } 233 234 /** 235 * 236 * @global array $tabs 237 * @global string $tab 238 * 239 * @return array 240 */ 241 protected function get_views() { 242 global $tabs, $tab; 243 244 $display_tabs = array(); 245 foreach ( (array) $tabs as $action => $text ) { 246 $class = ( $action === $tab ) ? ' current' : ''; 247 $href = self_admin_url('plugin-install.php?tab=' . $action); 248 $display_tabs['plugin-install-'.$action] = "<a href='$href' class='$class'>$text</a>"; 249 } 250 // No longer a real tab. 251 unset( $display_tabs['plugin-install-upload'] ); 252 253 return $display_tabs; 254 } 255 256 /** 257 * Override parent views so we can use the filter bar display. 258 */ 259 public function views() { 260 $views = $this->get_views(); 261 262 /** This filter is documented in wp-admin/inclues/class-wp-list-table.php */ 263 $views = apply_filters( "views_{$this->screen->id}", $views ); 264 265 $this->screen->render_screen_reader_content( 'heading_views' ); 274 266 ?> 275 267 <div class="wp-filter"> 276 277 278 279 280 281 282 283 284 285 286 287 268 <ul class="filter-links"> 269 <?php 270 if ( ! empty( $views ) ) { 271 foreach ( $views as $class => $view ) { 272 $views[ $class ] = "\t<li class='$class'>$view"; 273 } 274 echo implode( " </li>\n", $views ) . "</li>\n"; 275 } 276 ?> 277 </ul> 278 279 <?php install_search_form(); ?> 288 280 </div> 289 281 <?php 290 282 } 291 283 292 293 294 295 296 284 /** 285 * Override the parent display() so we can provide a different container. 286 */ 287 public function display() { 288 $singular = $this->_args['singular']; 297 289 298 290 $data_attr = ''; 299 291 300 301 302 292 if ( $singular ) { 293 $data_attr = " data-wp-lists='list:$singular'"; 294 } 303 295 304 296 $this->display_tablenav( 'top' ); 305 297 306 298 ?> 307 299 <div class="wp-list-table <?php echo implode( ' ', $this->get_table_classes() ); ?>"> 308 300 <?php 309 301 $this->screen->render_screen_reader_content( 'heading_list' ); 310 302 ?> 311 312 313 303 <div id="the-list"<?php echo $data_attr; ?>> 304 <?php $this->display_rows_or_placeholder(); ?> 305 </div> 314 306 </div> 315 307 <?php 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 308 $this->display_tablenav( 'bottom' ); 309 } 310 311 /** 312 * @global string $tab 313 * 314 * @param string $which 315 */ 316 protected function display_tablenav( $which ) { 317 if ( $GLOBALS['tab'] === 'featured' ) { 318 return; 319 } 320 321 if ( 'top' === $which ) { 322 wp_referer_field(); 323 ?> 324 <div class="tablenav top"> 325 <div class="alignleft actions"> 326 <?php 327 /** 328 * Fires before the Plugin Install table header pagination is displayed. 329 * 330 * @since 2.7.0 331 */ 332 do_action( 'install_plugins_table_header' ); ?> 333 </div> 334 <?php $this->pagination( $which ); ?> 335 <br class="clear" /> 336 </div> 337 <?php } else { ?> 338 <div class="tablenav bottom"> 339 <?php $this->pagination( $which ); ?> 340 <br class="clear" /> 341 </div> 342 <?php 343 } 344 } 345 346 /** 347 * @return array 348 */ 349 protected function get_table_classes() { 350 return array( 'widefat', $this->_args['plural'] ); 351 } 352 353 /** 354 * @return array 355 */ 356 public function get_columns() { 357 return array(); 358 } 359 360 /** 361 * @param object $plugin_a 362 * @param object $plugin_b 363 * @return int 364 */ 365 private function order_callback( $plugin_a, $plugin_b ) { 366 $orderby = $this->orderby; 367 if ( ! isset( $plugin_a->$orderby, $plugin_b->$orderby ) ) { 368 return 0; 369 } 370 371 $a = $plugin_a->$orderby; 372 $b = $plugin_b->$orderby; 373 374 if ( $a == $b ) { 375 return 0; 376 } 377 378 if ( 'DESC' === $this->order ) { 379 return ( $a < $b ) ? 1 : -1; 380 } else { 381 return ( $a < $b ) ? -1 : 1; 382 } 383 } 384 385 public function display_rows() { 386 $plugins_allowedtags = array( 387 'a' => array( 'href' => array(),'title' => array(), 'target' => array() ), 388 'abbr' => array( 'title' => array() ),'acronym' => array( 'title' => array() ), 389 'code' => array(), 'pre' => array(), 'em' => array(),'strong' => array(), 390 'ul' => array(), 'ol' => array(), 'li' => array(), 'p' => array(), 'br' => array() 391 ); 392 393 $plugins_group_titles = array( 394 'Performance' => _x( 'Performance', 'Plugin installer group title' ), 395 'Social' => _x( 'Social', 'Plugin installer group title' ), 396 'Tools' => _x( 'Tools', 'Plugin installer group title' ), 397 ); 398 399 $group = null; 400 401 foreach ( (array) $this->items as $plugin ) { 402 if ( is_object( $plugin ) ) { 403 $plugin = (array) $plugin; 404 } 405 406 // Display the group heading if there is one 407 if ( isset( $plugin['group'] ) && $plugin['group'] != $group ) { 408 if ( isset( $this->groups[ $plugin['group'] ] ) ) { 409 $group_name = $this->groups[ $plugin['group'] ]; 410 if ( isset( $plugins_group_titles[ $group_name ] ) ) { 411 $group_name = $plugins_group_titles[ $group_name ]; 412 } 413 } else { 414 $group_name = $plugin['group']; 415 } 416 417 // Starting a new group, close off the divs of the last one 418 if ( ! empty( $group ) ) { 419 echo '</div></div>'; 420 } 421 422 echo '<div class="plugin-group"><h3>' . esc_html( $group_name ) . '</h3>'; 423 // needs an extra wrapping div for nth-child selectors to work 424 echo '<div class="plugin-items">'; 425 426 $group = $plugin['group']; 427 } 428 $title = wp_kses( $plugin['name'], $plugins_allowedtags ); 429 430 // Remove any HTML from the description. 431 $description = strip_tags( $plugin['short_description'] ); 432 $version = wp_kses( $plugin['version'], $plugins_allowedtags ); 433 434 $name = strip_tags( $title . ' ' . $version ); 435 436 $author = wp_kses( $plugin['author'], $plugins_allowedtags ); 437 if ( ! empty( $author ) ) { 438 $author = ' <cite>' . sprintf( __( 'By %s' ), $author ) . '</cite>'; 439 } 440 441 $action_links = array(); 442 443 if ( current_user_can( 'install_plugins' ) || current_user_can( 'update_plugins' ) ) { 444 $status = install_plugin_install_status( $plugin ); 445 446 switch ( $status['status'] ) { 447 case 'install': 448 if ( $status['url'] ) { 449 /* translators: 1: Plugin name and version. */ 450 $action_links[] = '<a class="install-now button" data-slug="' . esc_attr( $plugin['slug'] ) . '" href="' . esc_url( $status['url'] ) . '" aria-label="' . esc_attr( sprintf( __( 'Install %s now' ), $name ) ) . '" data-name="' . esc_attr( $name ) . '">' . __( 'Install Now' ) . '</a>'; 451 } 452 break; 453 454 case 'update_available': 455 if ( $status['url'] ) { 456 /* translators: 1: Plugin name and version */ 457 $action_links[] = '<a class="update-now button aria-button-if-js" data-plugin="' . esc_attr( $status['file'] ) . '" data-slug="' . esc_attr( $plugin['slug'] ) . '" href="' . esc_url( $status['url'] ) . '" aria-label="' . esc_attr( sprintf( __( 'Update %s now' ), $name ) ) . '" data-name="' . esc_attr( $name ) . '">' . __( 'Update Now' ) . '</a>'; 458 } 459 break; 460 461 case 'latest_installed': 462 case 'newer_installed': 463 if ( is_plugin_active( $status['file'] ) ) { 464 $action_links[] = '<button type="button" class="button button-disabled" disabled="disabled">' . _x( 'Active', 'plugin' ) . '</button>'; 465 } elseif ( current_user_can( 'activate_plugins' ) ) { 466 $button_text = __( 'Activate' ); 467 /* translators: %s: Plugin name */ 468 $button_label = _x( 'Activate %s', 'plugin' ); 469 $activate_url = add_query_arg( array( 470 '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $status['file'] ), 471 'action' => 'activate', 472 'plugin' => $status['file'], 473 ), network_admin_url( 'plugins.php' ) ); 474 475 if ( is_network_admin() ) { 476 $button_text = __( 'Network Activate' ); 477 /* translators: %s: Plugin name */ 478 $button_label = _x( 'Network Activate %s', 'plugin' ); 479 $activate_url = add_query_arg( array( 'networkwide' => 1 ), $activate_url ); 480 } 481 482 $action_links[] = sprintf( 483 '<a href="%1$s" class="button activate-now" aria-label="%2$s">%3$s</a>', 484 esc_url( $activate_url ), 485 esc_attr( sprintf( $button_label, $plugin['name'] ) ), 486 $button_text 487 ); 488 } else { 489 $action_links[] = '<button type="button" class="button button-disabled" disabled="disabled">' . _x( 'Installed', 'plugin' ) . '</button>'; 490 } 491 break; 492 } 493 } 494 495 $details_link = self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $plugin['slug'] . 496 '&TB_iframe=true&width=600&height=550' ); 497 498 /* translators: 1: Plugin name and version. */ 499 $action_links[] = '<a href="' . esc_url( $details_link ) . '" class="thickbox open-plugin-details-modal" aria-label="' . esc_attr( sprintf( __( 'More information about %s' ), $name ) ) . '" data-title="' . esc_attr( $name ) . '">' . __( 'More Details' ) . '</a>'; 500 501 if ( !empty( $plugin['icons']['svg'] ) ) { 502 $plugin_icon_url = $plugin['icons']['svg']; 503 } elseif ( !empty( $plugin['icons']['2x'] ) ) { 504 $plugin_icon_url = $plugin['icons']['2x']; 505 } elseif ( !empty( $plugin['icons']['1x'] ) ) { 506 $plugin_icon_url = $plugin['icons']['1x']; 507 } else { 508 $plugin_icon_url = $plugin['icons']['default']; 509 } 510 511 /** 512 * Filters the install action links for a plugin. 513 * 514 * @since 2.7.0 515 * 516 * @param array $action_links An array of plugin action hyperlinks. Defaults are links to Details and Install Now. 517 * @param array $plugin The plugin currently being listed. 518 */ 519 $action_links = apply_filters( 'plugin_install_action_links', $action_links, $plugin ); 520 521 $last_updated_timestamp = strtotime( $plugin['last_updated'] ); 522 ?> 523 <div class="plugin-card plugin-card-<?php echo sanitize_html_class( $plugin['slug'] ); ?>"> 524 <div class="plugin-card-top"> 525 <div class="name column-name"> 526 <h3> 527 <a href="<?php echo esc_url( $details_link ); ?>" class="thickbox open-plugin-details-modal"> 528 <?php echo $title; ?> 529 <img src="<?php echo esc_attr( $plugin_icon_url ) ?>" class="plugin-icon" alt=""> 530 </a> 531 </h3> 532 </div> 533 <div class="action-links"> 534 <?php 535 if ( $action_links ) { 536 echo '<ul class="plugin-action-buttons"><li>' . implode( '</li><li>', $action_links ) . '</li></ul>'; 537 } 538 ?> 539 </div> 540 <div class="desc column-description"> 541 <p><?php echo $description; ?></p> 542 <p class="authors"><?php echo $author; ?></p> 543 </div> 544 </div> 545 <div class="plugin-card-bottom"> 546 <div class="vers column-rating"> 547 <?php wp_star_rating( array( 'rating' => $plugin['rating'], 'type' => 'percent', 'number' => $plugin['num_ratings'] ) ); ?> 548 <span class="num-ratings" aria-hidden="true">(<?php echo number_format_i18n( $plugin['num_ratings'] ); ?>)</span> 549 </div> 550 <div class="column-updated"> 551 <strong><?php _e( 'Last Updated:' ); ?></strong> <?php printf( __( '%s ago' ), human_time_diff( $last_updated_timestamp ) ); ?> 552 </div> 553 <div class="column-downloaded"> 554 <?php 555 if ( $plugin['active_installs'] >= 1000000 ) { 556 $active_installs_text = _x( '1+ Million', 'Active plugin installs' ); 557 } elseif ( 0 == $plugin['active_installs'] ) { 558 $active_installs_text = _x( 'Less Than 10', 'Active plugin installs' ); 559 } else { 560 $active_installs_text = number_format_i18n( $plugin['active_installs'] ) . '+'; 561 } 562 printf( __( '%s Active Installs' ), $active_installs_text ); 563 ?> 564 </div> 565 <div class="column-compatibility"> 566 <?php 567 $wp_version = get_bloginfo( 'version' ); 568 569 if ( ! empty( $plugin['tested'] ) && version_compare( substr( $wp_version, 0, strlen( $plugin['tested'] ) ), $plugin['tested'], '>' ) ) { 570 echo '<span class="compatibility-untested">' . __( 'Untested with your version of WordPress' ) . '</span>'; 571 } elseif ( ! empty( $plugin['requires'] ) && version_compare( substr( $wp_version, 0, strlen( $plugin['requires'] ) ), $plugin['requires'], '<' ) ) { 572 echo '<span class="compatibility-incompatible">' . __( '<strong>Incompatible</strong> with your version of WordPress' ) . '</span>'; 573 } else { 574 echo '<span class="compatibility-compatible">' . __( '<strong>Compatible</strong> with your version of WordPress' ) . '</span>'; 575 } 576 ?> 577 </div> 578 </div> 579 </div> 580 <?php 581 } 582 583 // Close off the group divs of the last one 584 if ( ! empty( $group ) ) { 585 echo '</div></div>'; 586 } 587 } 596 588 } -
src/wp-admin/includes/class-wp-plugins-list-table.php
diff --git a/src/wp-admin/includes/class-wp-plugins-list-table.php b/src/wp-admin/includes/class-wp-plugins-list-table.php index 136fbd4c73..8622411681 100644
a b 17 17 */ 18 18 class WP_Plugins_List_Table extends WP_List_Table { 19 19 20 /** 21 * Constructor. 22 * 23 * @since 3.1.0 24 * @access public 25 * 26 * @see WP_List_Table::__construct() for more information on default arguments. 27 * 28 * @global string $status 29 * @global int $page 30 * 31 * @param array $args An associative array of arguments. 32 */ 33 public function __construct( $args = array() ) { 34 global $status, $page; 35 36 parent::__construct( array( 37 'plural' => 'plugins', 38 'screen' => isset( $args['screen'] ) ? $args['screen'] : null, 39 ) ); 40 41 $status = 'all'; 42 if ( isset( $_REQUEST['plugin_status'] ) && in_array( $_REQUEST['plugin_status'], array( 'active', 'inactive', 'recently_activated', 'upgrade', 'mustuse', 'dropins', 'search' ) ) ) 43 $status = $_REQUEST['plugin_status']; 44 45 if ( isset($_REQUEST['s']) ) 46 $_SERVER['REQUEST_URI'] = add_query_arg('s', wp_unslash($_REQUEST['s']) ); 47 48 $page = $this->get_pagenum(); 49 } 50 51 /** 52 * @return array 53 */ 54 protected function get_table_classes() { 55 return array( 'widefat', $this->_args['plural'] ); 56 } 57 58 /** 59 * @return bool 60 */ 61 public function ajax_user_can() { 62 return current_user_can('activate_plugins'); 63 } 64 65 /** 66 * 67 * @global string $status 68 * @global array $plugins 69 * @global array $totals 70 * @global int $page 71 * @global string $orderby 72 * @global string $order 73 * @global string $s 74 */ 75 public function prepare_items() { 76 global $status, $plugins, $totals, $page, $orderby, $order, $s; 77 78 wp_reset_vars( array( 'orderby', 'order' ) ); 79 80 /** 81 * Filters the full array of plugins to list in the Plugins list table. 82 * 83 * @since 3.0.0 84 * 85 * @see get_plugins() 86 * 87 * @param array $all_plugins An array of plugins to display in the list table. 88 */ 89 $all_plugins = apply_filters( 'all_plugins', get_plugins() ); 90 91 $plugins = array( 92 'all' => $all_plugins, 93 'search' => array(), 94 'active' => array(), 95 'inactive' => array(), 96 'recently_activated' => array(), 97 'upgrade' => array(), 98 'mustuse' => array(), 99 'dropins' => array(), 100 ); 101 102 $screen = $this->screen; 103 104 if ( ! is_multisite() || ( $screen->in_admin( 'network' ) && current_user_can( 'manage_network_plugins' ) ) ) { 105 106 /** 107 * Filters whether to display the advanced plugins list table. 108 * 109 * There are two types of advanced plugins - must-use and drop-ins - 110 * which can be used in a single site or Multisite network. 111 * 112 * The $type parameter allows you to differentiate between the type of advanced 113 * plugins to filter the display of. Contexts include 'mustuse' and 'dropins'. 114 * 115 * @since 3.0.0 116 * 117 * @param bool $show Whether to show the advanced plugins for the specified 118 * plugin type. Default true. 119 * @param string $type The plugin type. Accepts 'mustuse', 'dropins'. 120 */ 121 if ( apply_filters( 'show_advanced_plugins', true, 'mustuse' ) ) { 122 $plugins['mustuse'] = get_mu_plugins(); 123 } 124 125 /** This action is documented in wp-admin/includes/class-wp-plugins-list-table.php */ 126 if ( apply_filters( 'show_advanced_plugins', true, 'dropins' ) ) 127 $plugins['dropins'] = get_dropins(); 128 129 if ( current_user_can( 'update_plugins' ) ) { 130 $current = get_site_transient( 'update_plugins' ); 131 foreach ( (array) $plugins['all'] as $plugin_file => $plugin_data ) { 132 if ( isset( $current->response[ $plugin_file ] ) ) { 133 $plugins['all'][ $plugin_file ]['update'] = true; 134 $plugins['upgrade'][ $plugin_file ] = $plugins['all'][ $plugin_file ]; 135 } 136 } 137 } 138 } 139 140 if ( ! $screen->in_admin( 'network' ) ) { 141 $show = current_user_can( 'manage_network_plugins' ); 142 /** 143 * Filters whether to display network-active plugins alongside plugins active for the current site. 144 * 145 * This also controls the display of inactive network-only plugins (plugins with 146 * "Network: true" in the plugin header). 147 * 148 * Plugins cannot be network-activated or network-deactivated from this screen. 149 * 150 * @since 4.4.0 151 * 152 * @param bool $show Whether to show network-active plugins. Default is whether the current 153 * user can manage network plugins (ie. a Super Admin). 154 */ 155 $show_network_active = apply_filters( 'show_network_active_plugins', $show ); 156 } 157 158 set_transient( 'plugin_slugs', array_keys( $plugins['all'] ), DAY_IN_SECONDS ); 159 160 if ( $screen->in_admin( 'network' ) ) { 161 $recently_activated = get_site_option( 'recently_activated', array() ); 162 } else { 163 $recently_activated = get_option( 'recently_activated', array() ); 164 } 165 166 foreach ( $recently_activated as $key => $time ) { 167 if ( $time + WEEK_IN_SECONDS < time() ) { 168 unset( $recently_activated[$key] ); 169 } 170 } 171 172 if ( $screen->in_admin( 'network' ) ) { 173 update_site_option( 'recently_activated', $recently_activated ); 174 } else { 175 update_option( 'recently_activated', $recently_activated ); 176 } 177 178 $plugin_info = get_site_transient( 'update_plugins' ); 179 180 foreach ( (array) $plugins['all'] as $plugin_file => $plugin_data ) { 181 // Extra info if known. array_merge() ensures $plugin_data has precedence if keys collide. 182 if ( isset( $plugin_info->response[ $plugin_file ] ) ) { 183 $plugins['all'][ $plugin_file ] = $plugin_data = array_merge( (array) $plugin_info->response[ $plugin_file ], $plugin_data ); 184 // Make sure that $plugins['upgrade'] also receives the extra info since it is used on ?plugin_status=upgrade 185 if ( isset( $plugins['upgrade'][ $plugin_file ] ) ) { 186 $plugins['upgrade'][ $plugin_file ] = $plugin_data = array_merge( (array) $plugin_info->response[ $plugin_file ], $plugin_data ); 187 } 188 189 } elseif ( isset( $plugin_info->no_update[ $plugin_file ] ) ) { 190 $plugins['all'][ $plugin_file ] = $plugin_data = array_merge( (array) $plugin_info->no_update[ $plugin_file ], $plugin_data ); 191 // Make sure that $plugins['upgrade'] also receives the extra info since it is used on ?plugin_status=upgrade 192 if ( isset( $plugins['upgrade'][ $plugin_file ] ) ) { 193 $plugins['upgrade'][ $plugin_file ] = $plugin_data = array_merge( (array) $plugin_info->no_update[ $plugin_file ], $plugin_data ); 194 } 195 } 196 197 // Filter into individual sections 198 if ( is_multisite() && ! $screen->in_admin( 'network' ) && is_network_only_plugin( $plugin_file ) && ! is_plugin_active( $plugin_file ) ) { 199 if ( $show_network_active ) { 200 // On the non-network screen, show inactive network-only plugins if allowed 201 $plugins['inactive'][ $plugin_file ] = $plugin_data; 202 } else { 203 // On the non-network screen, filter out network-only plugins as long as they're not individually active 204 unset( $plugins['all'][ $plugin_file ] ); 205 } 206 } elseif ( ! $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) { 207 if ( $show_network_active ) { 208 // On the non-network screen, show network-active plugins if allowed 209 $plugins['active'][ $plugin_file ] = $plugin_data; 210 } else { 211 // On the non-network screen, filter out network-active plugins 212 unset( $plugins['all'][ $plugin_file ] ); 213 } 214 } elseif ( ( ! $screen->in_admin( 'network' ) && is_plugin_active( $plugin_file ) ) 215 || ( $screen->in_admin( 'network' ) && is_plugin_active_for_network( $plugin_file ) ) ) { 216 // On the non-network screen, populate the active list with plugins that are individually activated 217 // On the network-admin screen, populate the active list with plugins that are network activated 218 $plugins['active'][ $plugin_file ] = $plugin_data; 219 } else { 220 if ( isset( $recently_activated[ $plugin_file ] ) ) { 221 // Populate the recently activated list with plugins that have been recently activated 222 $plugins['recently_activated'][ $plugin_file ] = $plugin_data; 223 } 224 // Populate the inactive list with plugins that aren't activated 225 $plugins['inactive'][ $plugin_file ] = $plugin_data; 226 } 227 } 228 229 if ( strlen( $s ) ) { 230 $status = 'search'; 231 $plugins['search'] = array_filter( $plugins['all'], array( $this, '_search_callback' ) ); 232 } 233 234 $totals = array(); 235 foreach ( $plugins as $type => $list ) 236 $totals[ $type ] = count( $list ); 237 238 if ( empty( $plugins[ $status ] ) && !in_array( $status, array( 'all', 'search' ) ) ) 239 $status = 'all'; 240 241 $this->items = array(); 242 foreach ( $plugins[ $status ] as $plugin_file => $plugin_data ) { 243 // Translate, Don't Apply Markup, Sanitize HTML 244 $this->items[$plugin_file] = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, false, true ); 245 } 246 247 $total_this_page = $totals[ $status ]; 248 249 $js_plugins = array(); 250 foreach ( $plugins as $key => $list ) { 251 $js_plugins[ $key ] = array_keys( (array) $list ); 252 } 253 254 wp_localize_script( 'updates', '_wpUpdatesItemCounts', array( 255 'plugins' => $js_plugins, 256 'totals' => wp_get_update_data(), 257 ) ); 258 259 if ( ! $orderby ) { 260 $orderby = 'Name'; 261 } else { 262 $orderby = ucfirst( $orderby ); 263 } 264 265 $order = strtoupper( $order ); 266 267 uasort( $this->items, array( $this, '_order_callback' ) ); 268 269 $plugins_per_page = $this->get_items_per_page( str_replace( '-', '_', $screen->id . '_per_page' ), 999 ); 270 271 $start = ( $page - 1 ) * $plugins_per_page; 272 273 if ( $total_this_page > $plugins_per_page ) 274 $this->items = array_slice( $this->items, $start, $plugins_per_page ); 275 276 $this->set_pagination_args( array( 277 'total_items' => $total_this_page, 278 'per_page' => $plugins_per_page, 279 ) ); 280 } 281 282 /** 283 * @global string $s URL encoded search term. 284 * 285 * @param array $plugin 286 * @return bool 287 */ 288 public function _search_callback( $plugin ) { 289 global $s; 290 291 foreach ( $plugin as $value ) { 292 if ( is_string( $value ) && false !== stripos( strip_tags( $value ), urldecode( $s ) ) ) { 293 return true; 294 } 295 } 296 297 return false; 298 } 299 300 /** 301 * @global string $orderby 302 * @global string $order 303 * @param array $plugin_a 304 * @param array $plugin_b 305 * @return int 306 */ 307 public function _order_callback( $plugin_a, $plugin_b ) { 308 global $orderby, $order; 309 310 $a = $plugin_a[$orderby]; 311 $b = $plugin_b[$orderby]; 312 313 if ( $a == $b ) 314 return 0; 315 316 if ( 'DESC' === $order ) { 317 return strcasecmp( $b, $a ); 318 } else { 319 return strcasecmp( $a, $b ); 320 } 321 } 322 323 /** 324 * 325 * @global array $plugins 326 */ 327 public function no_items() { 328 global $plugins; 329 330 if ( ! empty( $_REQUEST['s'] ) ) { 331 $s = esc_html( wp_unslash( $_REQUEST['s'] ) ); 332 333 printf( __( 'No plugins found for “%s”.' ), $s ); 334 335 // We assume that somebody who can install plugins in multisite is experienced enough to not need this helper link. 336 if ( ! is_multisite() && current_user_can( 'install_plugins' ) ) { 337 echo ' <a href="' . esc_url( admin_url( 'plugin-install.php?tab=search&s=' . urlencode( $s ) ) ) . '">' . __( 'Search for plugins in the WordPress Plugin Directory.' ) . '</a>'; 338 } 339 } elseif ( ! empty( $plugins['all'] ) ) 340 _e( 'No plugins found.' ); 341 else 342 _e( 'You do not appear to have any plugins available at this time.' ); 343 } 344 345 /** 346 * Displays the search box. 347 * 348 * @since 4.6.0 349 * @access public 350 * 351 * @param string $text The 'submit' button label. 352 * @param string $input_id ID attribute value for the search input field. 353 */ 354 public function search_box( $text, $input_id ) { 355 if ( empty( $_REQUEST['s'] ) && ! $this->has_items() ) { 356 return; 357 } 358 359 $input_id = $input_id . '-search-input'; 360 361 if ( ! empty( $_REQUEST['orderby'] ) ) { 362 echo '<input type="hidden" name="orderby" value="' . esc_attr( $_REQUEST['orderby'] ) . '" />'; 363 } 364 if ( ! empty( $_REQUEST['order'] ) ) { 365 echo '<input type="hidden" name="order" value="' . esc_attr( $_REQUEST['order'] ) . '" />'; 366 } 367 ?> 368 <p class="search-box"> 369 <label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo $text; ?>:</label> 370 <input type="search" id="<?php echo esc_attr( $input_id ); ?>" class="wp-filter-search" name="s" value="<?php _admin_search_query(); ?>" placeholder="<?php esc_attr_e( 'Search installed plugins...' ); ?>"/> 371 <?php submit_button( $text, 'hide-if-js', '', false, array( 'id' => 'search-submit' ) ); ?> 372 </p> 373 <?php 374 } 375 376 /** 377 * 378 * @global string $status 379 * @return array 380 */ 381 public function get_columns() { 382 global $status; 383 384 return array( 385 'cb' => !in_array( $status, array( 'mustuse', 'dropins' ) ) ? '<input type="checkbox" />' : '', 386 'name' => __( 'Plugin' ), 387 'description' => __( 'Description' ), 388 ); 389 } 390 391 /** 392 * @return array 393 */ 394 protected function get_sortable_columns() { 395 return array(); 396 } 397 398 /** 399 * 400 * @global array $totals 401 * @global string $status 402 * @return array 403 */ 404 protected function get_views() { 405 global $totals, $status; 406 407 $status_links = array(); 408 foreach ( $totals as $type => $count ) { 409 if ( !$count ) 410 continue; 411 412 switch ( $type ) { 413 case 'all': 414 $text = _nx( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $count, 'plugins' ); 415 break; 416 case 'active': 417 $text = _n( 'Active <span class="count">(%s)</span>', 'Active <span class="count">(%s)</span>', $count ); 418 break; 419 case 'recently_activated': 420 $text = _n( 'Recently Active <span class="count">(%s)</span>', 'Recently Active <span class="count">(%s)</span>', $count ); 421 break; 422 case 'inactive': 423 $text = _n( 'Inactive <span class="count">(%s)</span>', 'Inactive <span class="count">(%s)</span>', $count ); 424 break; 425 case 'mustuse': 426 $text = _n( 'Must-Use <span class="count">(%s)</span>', 'Must-Use <span class="count">(%s)</span>', $count ); 427 break; 428 case 'dropins': 429 $text = _n( 'Drop-ins <span class="count">(%s)</span>', 'Drop-ins <span class="count">(%s)</span>', $count ); 430 break; 431 case 'upgrade': 432 $text = _n( 'Update Available <span class="count">(%s)</span>', 'Update Available <span class="count">(%s)</span>', $count ); 433 break; 434 } 435 436 if ( 'search' !== $type ) { 437 $status_links[$type] = sprintf( "<a href='%s' %s>%s</a>", 438 add_query_arg('plugin_status', $type, 'plugins.php'), 439 ( $type === $status ) ? ' class="current"' : '', 440 sprintf( $text, number_format_i18n( $count ) ) 441 ); 442 } 443 } 444 445 return $status_links; 446 } 447 448 /** 449 * 450 * @global string $status 451 * @return array 452 */ 453 protected function get_bulk_actions() { 454 global $status; 455 456 $actions = array(); 457 458 if ( 'active' != $status ) 459 $actions['activate-selected'] = $this->screen->in_admin( 'network' ) ? __( 'Network Activate' ) : __( 'Activate' ); 460 461 if ( 'inactive' != $status && 'recent' != $status ) 462 $actions['deactivate-selected'] = $this->screen->in_admin( 'network' ) ? __( 'Network Deactivate' ) : __( 'Deactivate' ); 463 464 if ( !is_multisite() || $this->screen->in_admin( 'network' ) ) { 465 if ( current_user_can( 'update_plugins' ) ) 466 $actions['update-selected'] = __( 'Update' ); 467 if ( current_user_can( 'delete_plugins' ) && ( 'active' != $status ) ) 468 $actions['delete-selected'] = __( 'Delete' ); 469 } 470 471 return $actions; 472 } 473 474 /** 475 * @global string $status 476 * @param string $which 477 */ 478 public function bulk_actions( $which = '' ) { 479 global $status; 480 481 if ( in_array( $status, array( 'mustuse', 'dropins' ) ) ) 482 return; 483 484 parent::bulk_actions( $which ); 485 } 486 487 /** 488 * @global string $status 489 * @param string $which 490 */ 491 protected function extra_tablenav( $which ) { 492 global $status; 493 494 if ( ! in_array($status, array('recently_activated', 'mustuse', 'dropins') ) ) 495 return; 496 497 echo '<div class="alignleft actions">'; 498 499 if ( 'recently_activated' == $status ) { 500 submit_button( __( 'Clear List' ), '', 'clear-recent-list', false ); 501 } elseif ( 'top' === $which && 'mustuse' === $status ) { 502 /* translators: %s: mu-plugins directory name */ 503 echo '<p>' . sprintf( __( 'Files in the %s directory are executed automatically.' ), 504 '<code>' . str_replace( ABSPATH, '/', WPMU_PLUGIN_DIR ) . '</code>' 505 ) . '</p>'; 506 } elseif ( 'top' === $which && 'dropins' === $status ) { 507 /* translators: %s: wp-content directory name */ 508 echo '<p>' . sprintf( __( 'Drop-ins are advanced plugins in the %s directory that replace WordPress functionality when present.' ), 509 '<code>' . str_replace( ABSPATH, '', WP_CONTENT_DIR ) . '</code>' 510 ) . '</p>'; 511 } 512 echo '</div>'; 513 } 514 515 /** 516 * @return string 517 */ 518 public function current_action() { 519 if ( isset($_POST['clear-recent-list']) ) 520 return 'clear-recent-list'; 521 522 return parent::current_action(); 523 } 524 525 /** 526 * 527 * @global string $status 528 */ 529 public function display_rows() { 530 global $status; 531 532 if ( is_multisite() && ! $this->screen->in_admin( 'network' ) && in_array( $status, array( 'mustuse', 'dropins' ) ) ) 533 return; 534 535 foreach ( $this->items as $plugin_file => $plugin_data ) 536 $this->single_row( array( $plugin_file, $plugin_data ) ); 537 } 538 539 /** 540 * @global string $status 541 * @global int $page 542 * @global string $s 543 * @global array $totals 544