????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
?????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
???????????????????????????????????????
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
PNG
\x49\x44\x41\x54?\x89\x50
\x4E\x47\x0D\x0A\x1A\x0A
JFIF ?? C
!"$"$?? C
?? p
" ??
?? ?
????
(% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @
adadasdasdasasdasdas
.....................................................................................................................................??????????????????????
??? ???????????????????????????????????????...............................
JFIF ?? C
!"$"$?? C
?? p
" ??
?? ?
????
(% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @
adadasdasdasasdasdas
..................................................................................................................................... ????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
?????????????????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
????????????????????????????????????????
???????????????????????????????????????
PNG
\x49\x44\x41\x54?\x89\x50
\x4E\x47\x0D\x0A\x1A\x0A
JFIF ?? C
!"$"$?? C
?? p
" ??
?? ?
????
(% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @
adadasdasdasasdasdas
.....................................................................................................................................??????????????????????
??? ???????????????????????????????????????...............................
JFIF ?? C
!"$"$?? C
?? p
" ??
?? ?
????
(% aA*?XYD?(J??E RE,P XYae?)(E 2 B R BQ X?)X ? @
adadasdasdasasdasdas
.....................................................................................................................................????????????????????????????????
???????????????????????????????
???????????????????????????????
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.
Warning: Undefined variable $auth in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 695
Warning: Trying to access array offset on value of type null in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 695
Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 332
Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 333
Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 334
Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 335
Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 336
Warning: Cannot modify header information - headers already sent by (output started at /home/blacotuu/deliciouskenya.com/d94fc6/index.php:1) in /home/blacotuu/deliciouskenya.com/d94fc6/index.php on line 337
class-wp-themes-list-table.php 0000644 00000024224 15207570471 0012343 0 ustar 00 true,
'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
)
);
}
/**
* @return bool
*/
public function ajax_user_can() {
// Do not check edit_theme_options here. Ajax calls for available themes require switch_themes.
return current_user_can( 'switch_themes' );
}
/**
*/
public function prepare_items() {
$themes = wp_get_themes( array( 'allowed' => true ) );
if ( ! empty( $_REQUEST['s'] ) ) {
$this->search_terms = array_unique( array_filter( array_map( 'trim', explode( ',', strtolower( wp_unslash( $_REQUEST['s'] ) ) ) ) ) );
}
if ( ! empty( $_REQUEST['features'] ) ) {
$this->features = $_REQUEST['features'];
}
if ( $this->search_terms || $this->features ) {
foreach ( $themes as $key => $theme ) {
if ( ! $this->search_theme( $theme ) ) {
unset( $themes[ $key ] );
}
}
}
unset( $themes[ get_option( 'stylesheet' ) ] );
WP_Theme::sort_by_name( $themes );
$per_page = 36;
$page = $this->get_pagenum();
$start = ( $page - 1 ) * $per_page;
$this->items = array_slice( $themes, $start, $per_page, true );
$this->set_pagination_args(
array(
'total_items' => count( $themes ),
'per_page' => $per_page,
'infinite_scroll' => true,
)
);
}
/**
*/
public function no_items() {
if ( $this->search_terms || $this->features ) {
_e( 'No items found.' );
return;
}
$blog_id = get_current_blog_id();
if ( is_multisite() ) {
if ( current_user_can( 'install_themes' ) && current_user_can( 'manage_network_themes' ) ) {
printf(
/* translators: 1: URL to Themes tab on Edit Site screen, 2: URL to Add Themes screen. */
__( 'You only have one theme enabled for this site right now. Visit the Network Admin to enable or install more themes.' ),
network_admin_url( 'site-themes.php?id=' . $blog_id ),
network_admin_url( 'theme-install.php' )
);
return;
} elseif ( current_user_can( 'manage_network_themes' ) ) {
printf(
/* translators: %s: URL to Themes tab on Edit Site screen. */
__( 'You only have one theme enabled for this site right now. Visit the Network Admin to enable more themes.' ),
network_admin_url( 'site-themes.php?id=' . $blog_id )
);
return;
}
// Else, fallthrough. install_themes doesn't help if you can't enable it.
} else {
if ( current_user_can( 'install_themes' ) ) {
printf(
/* translators: %s: URL to Add Themes screen. */
__( 'You only have one theme installed right now. Live a little! You can choose from over 1,000 free themes in the WordPress Theme Directory at any time: just click on the Install Themes tab above.' ),
admin_url( 'theme-install.php' )
);
return;
}
}
// Fallthrough.
printf(
/* translators: %s: Network title. */
__( 'Only the active theme is available to you. Contact the %s administrator for information about accessing additional themes.' ),
get_site_option( 'site_name' )
);
}
/**
* @param string $which
*/
public function tablenav( $which = 'top' ) {
if ( $this->get_pagination_arg( 'total_pages' ) <= 1 ) {
return;
}
?>
| ' . __( 'Title' ) . ' | ' . __( 'Type' ) . ' | ' . __( 'Date' ) . ' | ' . __( 'Status' ) . ' | |
|---|---|---|---|---|
| '; $html .= ' | ' . esc_html( $post_types[ $post->post_type ]->labels->singular_name ) . ' | ' . esc_html( $time ) . ' | ' . esc_html( $stat ) . ' |
' . __( 'An error has occurred. Please reload the page and try again.' ) . '
'; $sidebars = wp_get_sidebars_widgets(); $sidebar = isset( $sidebars[ $sidebar_id ] ) ? $sidebars[ $sidebar_id ] : array(); // Delete. if ( isset( $_POST['delete_widget'] ) && $_POST['delete_widget'] ) { if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) { wp_die( $error ); } $sidebar = array_diff( $sidebar, array( $widget_id ) ); $_POST = array( 'sidebar' => $sidebar_id, 'widget-' . $id_base => array(), 'the-widget-id' => $widget_id, 'delete_widget' => '1', ); /** This action is documented in wp-admin/widgets.php */ do_action( 'delete_widget', $widget_id, $sidebar_id, $id_base ); } elseif ( $settings && preg_match( '/__i__|%i%/', key( $settings ) ) ) { if ( ! $multi_number ) { wp_die( $error ); } $_POST[ 'widget-' . $id_base ] = array( $multi_number => reset( $settings ) ); $widget_id = $id_base . '-' . $multi_number; $sidebar[] = $widget_id; } $_POST['widget-id'] = $sidebar; foreach ( (array) $wp_registered_widget_updates as $name => $control ) { if ( $name === $id_base ) { if ( ! is_callable( $control['callback'] ) ) { continue; } ob_start(); call_user_func_array( $control['callback'], $control['params'] ); ob_end_clean(); break; } } if ( isset( $_POST['delete_widget'] ) && $_POST['delete_widget'] ) { $sidebars[ $sidebar_id ] = $sidebar; wp_set_sidebars_widgets( $sidebars ); echo "deleted:$widget_id"; wp_die(); } if ( ! empty( $_POST['add_new'] ) ) { wp_die(); } $form = $wp_registered_widget_controls[ $widget_id ]; if ( $form ) { call_user_func_array( $form['callback'], $form['params'] ); } wp_die(); } /** * Handles updating a widget via AJAX. * * @since 3.9.0 * * @global WP_Customize_Manager $wp_customize */ function wp_ajax_update_widget() { global $wp_customize; $wp_customize->widgets->wp_ajax_update_widget(); } /** * Handles removing inactive widgets via AJAX. * * @since 4.4.0 */ function wp_ajax_delete_inactive_widgets() { check_ajax_referer( 'remove-inactive-widgets', 'removeinactivewidgets' ); if ( ! current_user_can( 'edit_theme_options' ) ) { wp_die( -1 ); } unset( $_POST['removeinactivewidgets'], $_POST['action'] ); /** This action is documented in wp-admin/includes/ajax-actions.php */ do_action( 'load-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores /** This action is documented in wp-admin/includes/ajax-actions.php */ do_action( 'widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores /** This action is documented in wp-admin/widgets.php */ do_action( 'sidebar_admin_setup' ); $sidebars_widgets = wp_get_sidebars_widgets(); foreach ( $sidebars_widgets['wp_inactive_widgets'] as $key => $widget_id ) { $pieces = explode( '-', $widget_id ); $multi_number = array_pop( $pieces ); $id_base = implode( '-', $pieces ); $widget = get_option( 'widget_' . $id_base ); unset( $widget[ $multi_number ] ); update_option( 'widget_' . $id_base, $widget ); unset( $sidebars_widgets['wp_inactive_widgets'][ $key ] ); } wp_set_sidebars_widgets( $sidebars_widgets ); wp_die(); } /** * Handles creating missing image sub-sizes for just uploaded images via AJAX. * * @since 5.3.0 */ function wp_ajax_media_create_image_subsizes() { check_ajax_referer( 'media-form' ); if ( ! current_user_can( 'upload_files' ) ) { wp_send_json_error( array( 'message' => __( 'Sorry, you are not allowed to upload files.' ) ) ); } if ( empty( $_POST['attachment_id'] ) ) { wp_send_json_error( array( 'message' => __( 'Upload failed. Please reload and try again.' ) ) ); } $attachment_id = (int) $_POST['attachment_id']; if ( ! empty( $_POST['_wp_upload_failed_cleanup'] ) ) { // Upload failed. Cleanup. if ( wp_attachment_is_image( $attachment_id ) && current_user_can( 'delete_post', $attachment_id ) ) { $attachment = get_post( $attachment_id ); // Created at most 10 min ago. if ( $attachment && ( time() - strtotime( $attachment->post_date_gmt ) < 600 ) ) { wp_delete_attachment( $attachment_id, true ); wp_send_json_success(); } } } /* * Set a custom header with the attachment_id. * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error. */ if ( ! headers_sent() ) { header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id ); } /* * This can still be pretty slow and cause timeout or out of memory errors. * The js that handles the response would need to also handle HTTP 500 errors. */ wp_update_image_subsizes( $attachment_id ); if ( ! empty( $_POST['_legacy_support'] ) ) { // The old (inline) uploader. Only needs the attachment_id. $response = array( 'id' => $attachment_id ); } else { // Media modal and Media Library grid view. $response = wp_prepare_attachment_for_js( $attachment_id ); if ( ! $response ) { wp_send_json_error( array( 'message' => __( 'Upload failed.' ) ) ); } } // At this point the image has been uploaded successfully. wp_send_json_success( $response ); } /** * Handles uploading attachments via AJAX. * * @since 3.3.0 */ function wp_ajax_upload_attachment() { check_ajax_referer( 'media-form' ); /* * This function does not use wp_send_json_success() / wp_send_json_error() * as the html4 Plupload handler requires a text/html Content-Type for older IE. * See https://core.trac.wordpress.org/ticket/31037 */ if ( ! current_user_can( 'upload_files' ) ) { echo wp_json_encode( array( 'success' => false, 'data' => array( 'message' => __( 'Sorry, you are not allowed to upload files.' ), 'filename' => esc_html( $_FILES['async-upload']['name'] ), ), ) ); wp_die(); } if ( isset( $_REQUEST['post_id'] ) ) { $post_id = $_REQUEST['post_id']; if ( ! current_user_can( 'edit_post', $post_id ) ) { echo wp_json_encode( array( 'success' => false, 'data' => array( 'message' => __( 'Sorry, you are not allowed to attach files to this post.' ), 'filename' => esc_html( $_FILES['async-upload']['name'] ), ), ) ); wp_die(); } } else { $post_id = null; } $post_data = ! empty( $_REQUEST['post_data'] ) ? _wp_get_allowed_postdata( _wp_translate_postdata( false, (array) $_REQUEST['post_data'] ) ) : array(); if ( is_wp_error( $post_data ) ) { wp_die( $post_data->get_error_message() ); } // If the context is custom header or background, make sure the uploaded file is an image. if ( isset( $post_data['context'] ) && in_array( $post_data['context'], array( 'custom-header', 'custom-background' ), true ) ) { $wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['name'] ); if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) { echo wp_json_encode( array( 'success' => false, 'data' => array( 'message' => __( 'The uploaded file is not a valid image. Please try again.' ), 'filename' => esc_html( $_FILES['async-upload']['name'] ), ), ) ); wp_die(); } } $attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data ); if ( is_wp_error( $attachment_id ) ) { echo wp_json_encode( array( 'success' => false, 'data' => array( 'message' => $attachment_id->get_error_message(), 'filename' => esc_html( $_FILES['async-upload']['name'] ), ), ) ); wp_die(); } if ( isset( $post_data['context'] ) && isset( $post_data['theme'] ) ) { if ( 'custom-background' === $post_data['context'] ) { update_post_meta( $attachment_id, '_wp_attachment_is_custom_background', $post_data['theme'] ); } if ( 'custom-header' === $post_data['context'] ) { update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', $post_data['theme'] ); } } $attachment = wp_prepare_attachment_for_js( $attachment_id ); if ( ! $attachment ) { wp_die(); } echo wp_json_encode( array( 'success' => true, 'data' => $attachment, ) ); wp_die(); } /** * Handles image editing via AJAX. * * @since 3.1.0 */ function wp_ajax_image_editor() { $attachment_id = (int) $_POST['postid']; if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) { wp_die( -1 ); } check_ajax_referer( "image_editor-$attachment_id" ); require_once ABSPATH . 'wp-admin/includes/image-edit.php'; $msg = false; switch ( $_POST['do'] ) { case 'save': $msg = wp_save_image( $attachment_id ); if ( ! empty( $msg->error ) ) { wp_send_json_error( $msg ); } wp_send_json_success( $msg ); break; case 'scale': $msg = wp_save_image( $attachment_id ); break; case 'restore': $msg = wp_restore_image( $attachment_id ); break; } ob_start(); wp_image_editor( $attachment_id, $msg ); $html = ob_get_clean(); if ( ! empty( $msg->error ) ) { wp_send_json_error( array( 'message' => $msg, 'html' => $html, ) ); } wp_send_json_success( array( 'message' => $msg, 'html' => $html, ) ); } /** * Handles setting the featured image via AJAX. * * @since 3.1.0 */ function wp_ajax_set_post_thumbnail() { $json = ! empty( $_REQUEST['json'] ); // New-style request. $post_id = (int) $_POST['post_id']; if ( ! current_user_can( 'edit_post', $post_id ) ) { wp_die( -1 ); } $thumbnail_id = (int) $_POST['thumbnail_id']; if ( $json ) { check_ajax_referer( "update-post_$post_id" ); } else { check_ajax_referer( "set_post_thumbnail-$post_id" ); } if ( -1 === $thumbnail_id ) { if ( delete_post_thumbnail( $post_id ) ) { $return = _wp_post_thumbnail_html( null, $post_id ); $json ? wp_send_json_success( $return ) : wp_die( $return ); } else { wp_die( 0 ); } } if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) { $return = _wp_post_thumbnail_html( $thumbnail_id, $post_id ); $json ? wp_send_json_success( $return ) : wp_die( $return ); } wp_die( 0 ); } /** * Handles retrieving HTML for the featured image via AJAX. * * @since 4.6.0 */ function wp_ajax_get_post_thumbnail_html() { $post_id = (int) $_POST['post_id']; check_ajax_referer( "update-post_$post_id" ); if ( ! current_user_can( 'edit_post', $post_id ) ) { wp_die( -1 ); } $thumbnail_id = (int) $_POST['thumbnail_id']; // For backward compatibility, -1 refers to no featured image. if ( -1 === $thumbnail_id ) { $thumbnail_id = null; } $return = _wp_post_thumbnail_html( $thumbnail_id, $post_id ); wp_send_json_success( $return ); } /** * Handles setting the featured image for an attachment via AJAX. * * @since 4.0.0 * * @see set_post_thumbnail() */ function wp_ajax_set_attachment_thumbnail() { if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) { wp_send_json_error(); } $thumbnail_id = (int) $_POST['thumbnail_id']; if ( empty( $thumbnail_id ) ) { wp_send_json_error(); } if ( false === check_ajax_referer( 'set-attachment-thumbnail', '_ajax_nonce', false ) ) { wp_send_json_error(); } $post_ids = array(); // For each URL, try to find its corresponding post ID. foreach ( $_POST['urls'] as $url ) { $post_id = attachment_url_to_postid( $url ); if ( ! empty( $post_id ) ) { $post_ids[] = $post_id; } } if ( empty( $post_ids ) ) { wp_send_json_error(); } $success = 0; // For each found attachment, set its thumbnail. foreach ( $post_ids as $post_id ) { if ( ! current_user_can( 'edit_post', $post_id ) ) { continue; } if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) { ++$success; } } if ( 0 === $success ) { wp_send_json_error(); } else { wp_send_json_success(); } wp_send_json_error(); } /** * Handles formatting a date via AJAX. * * @since 3.1.0 */ function wp_ajax_date_format() { wp_die( date_i18n( sanitize_option( 'date_format', wp_unslash( $_POST['date'] ) ) ) ); } /** * Handles formatting a time via AJAX. * * @since 3.1.0 */ function wp_ajax_time_format() { wp_die( date_i18n( sanitize_option( 'time_format', wp_unslash( $_POST['date'] ) ) ) ); } /** * Handles saving posts from the fullscreen editor via AJAX. * * @since 3.1.0 * @deprecated 4.3.0 */ function wp_ajax_wp_fullscreen_save_post() { $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0; $post = null; if ( $post_id ) { $post = get_post( $post_id ); } check_ajax_referer( 'update-post_' . $post_id, '_wpnonce' ); $post_id = edit_post(); if ( is_wp_error( $post_id ) ) { wp_send_json_error(); } if ( $post ) { $last_date = mysql2date( __( 'F j, Y' ), $post->post_modified ); $last_time = mysql2date( __( 'g:i a' ), $post->post_modified ); } else { $last_date = date_i18n( __( 'F j, Y' ) ); $last_time = date_i18n( __( 'g:i a' ) ); } $last_id = get_post_meta( $post_id, '_edit_last', true ); if ( $last_id ) { $last_user = get_userdata( $last_id ); /* translators: 1: User's display name, 2: Date of last edit, 3: Time of last edit. */ $last_edited = sprintf( __( 'Last edited by %1$s on %2$s at %3$s' ), esc_html( $last_user->display_name ), $last_date, $last_time ); } else { /* translators: 1: Date of last edit, 2: Time of last edit. */ $last_edited = sprintf( __( 'Last edited on %1$s at %2$s' ), $last_date, $last_time ); } wp_send_json_success( array( 'last_edited' => $last_edited ) ); } /** * Handles removing a post lock via AJAX. * * @since 3.1.0 */ function wp_ajax_wp_remove_post_lock() { if ( empty( $_POST['post_ID'] ) || empty( $_POST['active_post_lock'] ) ) { wp_die( 0 ); } $post_id = (int) $_POST['post_ID']; $post = get_post( $post_id ); if ( ! $post ) { wp_die( 0 ); } check_ajax_referer( 'update-post_' . $post_id ); if ( ! current_user_can( 'edit_post', $post_id ) ) { wp_die( -1 ); } $active_lock = array_map( 'absint', explode( ':', $_POST['active_post_lock'] ) ); if ( get_current_user_id() !== $active_lock[1] ) { wp_die( 0 ); } /** * Filters the post lock window duration. * * @since 3.3.0 * * @param int $interval The interval in seconds the post lock duration * should last, plus 5 seconds. Default 150. */ $new_lock = ( time() - apply_filters( 'wp_check_post_lock_window', 150 ) + 5 ) . ':' . $active_lock[1]; update_post_meta( $post_id, '_edit_lock', $new_lock, implode( ':', $active_lock ) ); wp_die( 1 ); } /** * Handles dismissing a WordPress pointer via AJAX. * * @since 3.1.0 */ function wp_ajax_dismiss_wp_pointer() { $pointer = $_POST['pointer']; if ( sanitize_key( $pointer ) !== $pointer ) { wp_die( 0 ); } // check_ajax_referer( 'dismiss-pointer_' . $pointer ); $dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) ); if ( in_array( $pointer, $dismissed, true ) ) { wp_die( 0 ); } $dismissed[] = $pointer; $dismissed = implode( ',', $dismissed ); update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed ); wp_die( 1 ); } /** * Handles getting an attachment via AJAX. * * @since 3.5.0 */ function wp_ajax_get_attachment() { if ( ! isset( $_REQUEST['id'] ) ) { wp_send_json_error(); } $id = absint( $_REQUEST['id'] ); if ( ! $id ) { wp_send_json_error(); } $post = get_post( $id ); if ( ! $post ) { wp_send_json_error(); } if ( 'attachment' !== $post->post_type ) { wp_send_json_error(); } if ( ! current_user_can( 'upload_files' ) ) { wp_send_json_error(); } $attachment = wp_prepare_attachment_for_js( $id ); if ( ! $attachment ) { wp_send_json_error(); } wp_send_json_success( $attachment ); } /** * Handles querying attachments via AJAX. * * @since 3.5.0 */ function wp_ajax_query_attachments() { if ( ! current_user_can( 'upload_files' ) ) { wp_send_json_error(); } $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array(); $keys = array( 's', 'order', 'orderby', 'posts_per_page', 'paged', 'post_mime_type', 'post_parent', 'author', 'post__in', 'post__not_in', 'year', 'monthnum', ); foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) { if ( $t->query_var && isset( $query[ $t->query_var ] ) ) { $keys[] = $t->query_var; } } $query = array_intersect_key( $query, array_flip( $keys ) ); $query['post_type'] = 'attachment'; if ( MEDIA_TRASH && ! empty( $_REQUEST['query']['post_status'] ) && 'trash' === $_REQUEST['query']['post_status'] ) { $query['post_status'] = 'trash'; } else { $query['post_status'] = 'inherit'; } if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) ) { $query['post_status'] .= ',private'; } // Filter query clauses to include filenames. if ( isset( $query['s'] ) ) { add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' ); } /** * Filters the arguments passed to WP_Query during an Ajax * call for querying attachments. * * @since 3.7.0 * * @see WP_Query::parse_query() * * @param array $query An array of query variables. */ $query = apply_filters( 'ajax_query_attachments_args', $query ); $attachments_query = new WP_Query( $query ); update_post_parent_caches( $attachments_query->posts ); $posts = array_map( 'wp_prepare_attachment_for_js', $attachments_query->posts ); $posts = array_filter( $posts ); $total_posts = $attachments_query->found_posts; if ( $total_posts < 1 ) { // Out-of-bounds, run the query again without LIMIT for total count. unset( $query['paged'] ); $count_query = new WP_Query(); $count_query->query( $query ); $total_posts = $count_query->found_posts; } $posts_per_page = (int) $attachments_query->get( 'posts_per_page' ); $max_pages = $posts_per_page ? (int) ceil( $total_posts / $posts_per_page ) : 0; header( 'X-WP-Total: ' . (int) $total_posts ); header( 'X-WP-TotalPages: ' . $max_pages ); wp_send_json_success( $posts ); } /** * Handles updating attachment attributes via AJAX. * * @since 3.5.0 */ function wp_ajax_save_attachment() { if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) ) { wp_send_json_error(); } $id = absint( $_REQUEST['id'] ); if ( ! $id ) { wp_send_json_error(); } check_ajax_referer( 'update-post_' . $id, 'nonce' ); if ( ! current_user_can( 'edit_post', $id ) ) { wp_send_json_error(); } $changes = $_REQUEST['changes']; $post = get_post( $id, ARRAY_A ); if ( 'attachment' !== $post['post_type'] ) { wp_send_json_error(); } if ( isset( $changes['parent'] ) ) { $post['post_parent'] = $changes['parent']; } if ( isset( $changes['title'] ) ) { $post['post_title'] = $changes['title']; } if ( isset( $changes['caption'] ) ) { $post['post_excerpt'] = $changes['caption']; } if ( isset( $changes['description'] ) ) { $post['post_content'] = $changes['description']; } if ( MEDIA_TRASH && isset( $changes['status'] ) ) { $post['post_status'] = $changes['status']; } if ( isset( $changes['alt'] ) ) { $alt = wp_unslash( $changes['alt'] ); if ( get_post_meta( $id, '_wp_attachment_image_alt', true ) !== $alt ) { $alt = wp_strip_all_tags( $alt, true ); update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) ); } } if ( wp_attachment_is( 'audio', $post['ID'] ) ) { $changed = false; $id3data = wp_get_attachment_metadata( $post['ID'] ); if ( ! is_array( $id3data ) ) { $changed = true; $id3data = array(); } foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) { if ( isset( $changes[ $key ] ) ) { $changed = true; $id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) ); } } if ( $changed ) { wp_update_attachment_metadata( $id, $id3data ); } } if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) { wp_delete_post( $id ); } else { wp_update_post( $post ); } wp_send_json_success(); } /** * Handles saving backward compatible attachment attributes via AJAX. * * @since 3.5.0 */ function wp_ajax_save_attachment_compat() { if ( ! isset( $_REQUEST['id'] ) ) { wp_send_json_error(); } $id = absint( $_REQUEST['id'] ); if ( ! $id ) { wp_send_json_error(); } if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) ) { wp_send_json_error(); } $attachment_data = $_REQUEST['attachments'][ $id ]; check_ajax_referer( 'update-post_' . $id, 'nonce' ); if ( ! current_user_can( 'edit_post', $id ) ) { wp_send_json_error(); } $post = get_post( $id, ARRAY_A ); if ( 'attachment' !== $post['post_type'] ) { wp_send_json_error(); } /** This filter is documented in wp-admin/includes/media.php */ $post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data ); if ( isset( $post['errors'] ) ) { $errors = $post['errors']; // @todo return me and display me! unset( $post['errors'] ); } wp_update_post( $post ); foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) { if ( isset( $attachment_data[ $taxonomy ] ) ) { wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false ); } } $attachment = wp_prepare_attachment_for_js( $id ); if ( ! $attachment ) { wp_send_json_error(); } wp_send_json_success( $attachment ); } /** * Handles saving the attachment order via AJAX. * * @since 3.5.0 */ function wp_ajax_save_attachment_order() { if ( ! isset( $_REQUEST['post_id'] ) ) { wp_send_json_error(); } $post_id = absint( $_REQUEST['post_id'] ); if ( ! $post_id ) { wp_send_json_error(); } if ( empty( $_REQUEST['attachments'] ) ) { wp_send_json_error(); } check_ajax_referer( 'update-post_' . $post_id, 'nonce' ); $attachments = $_REQUEST['attachments']; if ( ! current_user_can( 'edit_post', $post_id ) ) { wp_send_json_error(); } foreach ( $attachments as $attachment_id => $menu_order ) { if ( ! current_user_can( 'edit_post', $attachment_id ) ) { continue; } $attachment = get_post( $attachment_id ); if ( ! $attachment ) { continue; } if ( 'attachment' !== $attachment->post_type ) { continue; } wp_update_post( array( 'ID' => $attachment_id, 'menu_order' => $menu_order, ) ); } wp_send_json_success(); } /** * Handles sending an attachment to the editor via AJAX. * * Generates the HTML to send an attachment to the editor. * Backward compatible with the {@see 'media_send_to_editor'} filter * and the chain of filters that follow. * * @since 3.5.0 */ function wp_ajax_send_attachment_to_editor() { check_ajax_referer( 'media-send-to-editor', 'nonce' ); $attachment = wp_unslash( $_POST['attachment'] ); $id = (int) $attachment['id']; $post = get_post( $id ); if ( ! $post ) { wp_send_json_error(); } if ( 'attachment' !== $post->post_type ) { wp_send_json_error(); } if ( current_user_can( 'edit_post', $id ) ) { // If this attachment is unattached, attach it. Primarily a back compat thing. $insert_into_post_id = (int) $_POST['post_id']; if ( 0 === $post->post_parent && $insert_into_post_id ) { wp_update_post( array( 'ID' => $id, 'post_parent' => $insert_into_post_id, ) ); } } $url = empty( $attachment['url'] ) ? '' : $attachment['url']; $rel = ( str_contains( $url, 'attachment_id' ) || get_attachment_link( $id ) === $url ); remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' ); if ( str_starts_with( $post->post_mime_type, 'image' ) ) { $align = isset( $attachment['align'] ) ? $attachment['align'] : 'none'; $size = isset( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium'; $alt = isset( $attachment['image_alt'] ) ? $attachment['image_alt'] : ''; // No whitespace-only captions. $caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : ''; if ( '' === trim( $caption ) ) { $caption = ''; } $title = ''; // We no longer insert title tags into' . esc_html( $url ) . '' ),
)
);
}
if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
$styles = '';
$mce_styles = wpview_media_sandbox_styles();
foreach ( $mce_styles as $style ) {
$styles .= sprintf( '', $style );
}
$html = do_shortcode( $parsed );
global $wp_scripts;
if ( ! empty( $wp_scripts ) ) {
$wp_scripts->done = array();
}
ob_start();
wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
$scripts = ob_get_clean();
$parsed = $styles . $html . $scripts;
}
if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
preg_match( '%]*href="http://%', $parsed ) ) ) ) {
// Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
wp_send_json_error(
array(
'type' => 'not-ssl',
'message' => __( 'This preview is unavailable in the editor.' ),
)
);
}
$return = array(
'body' => $parsed,
'attr' => $wp_embed->last_attr,
);
if ( str_contains( $parsed, 'class="wp-embedded-content' ) ) {
if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
$script_src = includes_url( 'js/wp-embed.js' );
} else {
$script_src = includes_url( 'js/wp-embed.min.js' );
}
$return['head'] = '';
$return['sandbox'] = true;
}
wp_send_json_success( $return );
}
/**
* @since 4.0.0
*
* @global WP_Post $post Global post object.
* @global WP_Scripts $wp_scripts
*/
function wp_ajax_parse_media_shortcode() {
global $post, $wp_scripts;
if ( empty( $_POST['shortcode'] ) ) {
wp_send_json_error();
}
$shortcode = wp_unslash( $_POST['shortcode'] );
// Only process previews for media related shortcodes:
$found_shortcodes = get_shortcode_tags_in_content( $shortcode );
$media_shortcodes = array(
'audio',
'embed',
'playlist',
'video',
'gallery',
);
$other_shortcodes = array_diff( $found_shortcodes, $media_shortcodes );
if ( ! empty( $other_shortcodes ) ) {
wp_send_json_error();
}
if ( ! empty( $_POST['post_ID'] ) ) {
$post = get_post( (int) $_POST['post_ID'] );
}
// The embed shortcode requires a post.
if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
if ( in_array( 'embed', $found_shortcodes, true ) ) {
wp_send_json_error();
}
} else {
setup_postdata( $post );
}
$parsed = do_shortcode( $shortcode );
if ( empty( $parsed ) ) {
wp_send_json_error(
array(
'type' => 'no-items',
'message' => __( 'No items found.' ),
)
);
}
$head = '';
$styles = wpview_media_sandbox_styles();
foreach ( $styles as $style ) {
$head .= '';
}
if ( ! empty( $wp_scripts ) ) {
$wp_scripts->done = array();
}
ob_start();
echo $parsed;
if ( 'playlist' === $_REQUEST['type'] ) {
wp_underscore_playlist_templates();
wp_print_scripts( 'wp-playlist' );
} else {
wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
}
wp_send_json_success(
array(
'head' => $head,
'body' => ob_get_clean(),
)
);
}
/**
* Handles destroying multiple open sessions for a user via AJAX.
*
* @since 4.1.0
*/
function wp_ajax_destroy_sessions() {
$user = get_userdata( (int) $_POST['user_id'] );
if ( $user ) {
if ( ! current_user_can( 'edit_user', $user->ID ) ) {
$user = false;
} elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
$user = false;
}
}
if ( ! $user ) {
wp_send_json_error(
array(
'message' => __( 'Could not log out user sessions. Please try again.' ),
)
);
}
$sessions = WP_Session_Tokens::get_instance( $user->ID );
if ( get_current_user_id() === $user->ID ) {
$sessions->destroy_others( wp_get_session_token() );
$message = __( 'You are now logged out everywhere else.' );
} else {
$sessions->destroy_all();
/* translators: %s: User's display name. */
$message = sprintf( __( '%s has been logged out.' ), $user->display_name );
}
wp_send_json_success( array( 'message' => $message ) );
}
/**
* Handles cropping an image via AJAX.
*
* @since 4.3.0
*/
function wp_ajax_crop_image() {
$attachment_id = absint( $_POST['id'] );
check_ajax_referer( 'image_editor-' . $attachment_id, 'nonce' );
if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
wp_send_json_error();
}
$context = str_replace( '_', '-', $_POST['context'] );
$data = array_map( 'absint', $_POST['cropDetails'] );
$cropped = wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
if ( ! $cropped || is_wp_error( $cropped ) ) {
wp_send_json_error( array( 'message' => __( 'Image could not be processed.' ) ) );
}
switch ( $context ) {
case 'site-icon':
require_once ABSPATH . 'wp-admin/includes/class-wp-site-icon.php';
$wp_site_icon = new WP_Site_Icon();
// Skip creating a new attachment if the attachment is a Site Icon.
if ( get_post_meta( $attachment_id, '_wp_attachment_context', true ) === $context ) {
// Delete the temporary cropped file, we don't need it.
wp_delete_file( $cropped );
// Additional sizes in wp_prepare_attachment_for_js().
add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
break;
}
/** This filter is documented in wp-admin/includes/class-custom-image-header.php */
$cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
// Copy attachment properties.
$attachment = wp_copy_parent_attachment_properties( $cropped, $attachment_id, $context );
// Update the attachment.
add_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
$attachment_id = $wp_site_icon->insert_attachment( $attachment, $cropped );
remove_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
// Additional sizes in wp_prepare_attachment_for_js().
add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
break;
default:
/**
* Fires before a cropped image is saved.
*
* Allows to add filters to modify the way a cropped image is saved.
*
* @since 4.3.0
*
* @param string $context The Customizer control requesting the cropped image.
* @param int $attachment_id The attachment ID of the original image.
* @param string $cropped Path to the cropped image file.
*/
do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
/** This filter is documented in wp-admin/includes/class-custom-image-header.php */
$cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
// Copy attachment properties.
$attachment = wp_copy_parent_attachment_properties( $cropped, $attachment_id, $context );
$attachment_id = wp_insert_attachment( $attachment, $cropped );
$metadata = wp_generate_attachment_metadata( $attachment_id, $cropped );
/**
* Filters the cropped image attachment metadata.
*
* @since 4.3.0
*
* @see wp_generate_attachment_metadata()
*
* @param array $metadata Attachment metadata.
*/
$metadata = apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
wp_update_attachment_metadata( $attachment_id, $metadata );
/**
* Filters the attachment ID for a cropped image.
*
* @since 4.3.0
*
* @param int $attachment_id The attachment ID of the cropped image.
* @param string $context The Customizer control requesting the cropped image.
*/
$attachment_id = apply_filters( 'wp_ajax_cropped_attachment_id', $attachment_id, $context );
}
wp_send_json_success( wp_prepare_attachment_for_js( $attachment_id ) );
}
/**
* Handles generating a password via AJAX.
*
* @since 4.4.0
*/
function wp_ajax_generate_password() {
wp_send_json_success( wp_generate_password( 24 ) );
}
/**
* Handles generating a password in the no-privilege context via AJAX.
*
* @since 5.7.0
*/
function wp_ajax_nopriv_generate_password() {
wp_send_json_success( wp_generate_password( 24 ) );
}
/**
* Handles saving the user's WordPress.org username via AJAX.
*
* @since 4.4.0
*/
function wp_ajax_save_wporg_username() {
if ( ! current_user_can( 'install_themes' ) && ! current_user_can( 'install_plugins' ) ) {
wp_send_json_error();
}
check_ajax_referer( 'save_wporg_username_' . get_current_user_id() );
$username = isset( $_REQUEST['username'] ) ? wp_unslash( $_REQUEST['username'] ) : false;
if ( ! $username ) {
wp_send_json_error();
}
wp_send_json_success( update_user_meta( get_current_user_id(), 'wporg_favorites', $username ) );
}
/**
* Handles installing a theme via AJAX.
*
* @since 4.6.0
*
* @see Theme_Upgrader
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*/
function wp_ajax_install_theme() {
check_ajax_referer( 'updates' );
if ( empty( $_POST['slug'] ) ) {
wp_send_json_error(
array(
'slug' => '',
'errorCode' => 'no_theme_specified',
'errorMessage' => __( 'No theme specified.' ),
)
);
}
$slug = sanitize_key( wp_unslash( $_POST['slug'] ) );
$status = array(
'install' => 'theme',
'slug' => $slug,
);
if ( ! current_user_can( 'install_themes' ) ) {
$status['errorMessage'] = __( 'Sorry, you are not allowed to install themes on this site.' );
wp_send_json_error( $status );
}
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
require_once ABSPATH . 'wp-admin/includes/theme.php';
$api = themes_api(
'theme_information',
array(
'slug' => $slug,
'fields' => array( 'sections' => false ),
)
);
if ( is_wp_error( $api ) ) {
$status['errorMessage'] = $api->get_error_message();
wp_send_json_error( $status );
}
$skin = new WP_Ajax_Upgrader_Skin();
$upgrader = new Theme_Upgrader( $skin );
$result = $upgrader->install( $api->download_link );
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
$status['debug'] = $skin->get_upgrade_messages();
}
if ( is_wp_error( $result ) ) {
$status['errorCode'] = $result->get_error_code();
$status['errorMessage'] = $result->get_error_message();
wp_send_json_error( $status );
} elseif ( is_wp_error( $skin->result ) ) {
$status['errorCode'] = $skin->result->get_error_code();
$status['errorMessage'] = $skin->result->get_error_message();
wp_send_json_error( $status );
} elseif ( $skin->get_errors()->has_errors() ) {
$status['errorMessage'] = $skin->get_error_messages();
wp_send_json_error( $status );
} elseif ( is_null( $result ) ) {
global $wp_filesystem;
$status['errorCode'] = 'unable_to_connect_to_filesystem';
$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
// Pass through the error from WP_Filesystem if one was raised.
if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
}
wp_send_json_error( $status );
}
$status['themeName'] = wp_get_theme( $slug )->get( 'Name' );
if ( current_user_can( 'switch_themes' ) ) {
if ( is_multisite() ) {
$status['activateUrl'] = add_query_arg(
array(
'action' => 'enable',
'_wpnonce' => wp_create_nonce( 'enable-theme_' . $slug ),
'theme' => $slug,
),
network_admin_url( 'themes.php' )
);
} else {
$status['activateUrl'] = add_query_arg(
array(
'action' => 'activate',
'_wpnonce' => wp_create_nonce( 'switch-theme_' . $slug ),
'stylesheet' => $slug,
),
admin_url( 'themes.php' )
);
}
}
$theme = wp_get_theme( $slug );
$status['blockTheme'] = $theme->is_block_theme();
if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
$status['customizeUrl'] = add_query_arg(
array(
'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
),
wp_customize_url( $slug )
);
}
/*
* See WP_Theme_Install_List_Table::_get_theme_status() if we wanted to check
* on post-installation status.
*/
wp_send_json_success( $status );
}
/**
* Handles updating a theme via AJAX.
*
* @since 4.6.0
*
* @see Theme_Upgrader
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*/
function wp_ajax_update_theme() {
check_ajax_referer( 'updates' );
if ( empty( $_POST['slug'] ) ) {
wp_send_json_error(
array(
'slug' => '',
'errorCode' => 'no_theme_specified',
'errorMessage' => __( 'No theme specified.' ),
)
);
}
$stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
$status = array(
'update' => 'theme',
'slug' => $stylesheet,
'oldVersion' => '',
'newVersion' => '',
);
if ( ! current_user_can( 'update_themes' ) ) {
$status['errorMessage'] = __( 'Sorry, you are not allowed to update themes for this site.' );
wp_send_json_error( $status );
}
$theme = wp_get_theme( $stylesheet );
if ( $theme->exists() ) {
$status['oldVersion'] = $theme->get( 'Version' );
}
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
$current = get_site_transient( 'update_themes' );
if ( empty( $current ) ) {
wp_update_themes();
}
$skin = new WP_Ajax_Upgrader_Skin();
$upgrader = new Theme_Upgrader( $skin );
$result = $upgrader->bulk_upgrade( array( $stylesheet ) );
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
$status['debug'] = $skin->get_upgrade_messages();
}
if ( is_wp_error( $skin->result ) ) {
$status['errorCode'] = $skin->result->get_error_code();
$status['errorMessage'] = $skin->result->get_error_message();
wp_send_json_error( $status );
} elseif ( $skin->get_errors()->has_errors() ) {
$status['errorMessage'] = $skin->get_error_messages();
wp_send_json_error( $status );
} elseif ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) {
// Theme is already at the latest version.
if ( true === $result[ $stylesheet ] ) {
$status['errorMessage'] = $upgrader->strings['up_to_date'];
wp_send_json_error( $status );
}
$theme = wp_get_theme( $stylesheet );
if ( $theme->exists() ) {
$status['newVersion'] = $theme->get( 'Version' );
}
wp_send_json_success( $status );
} elseif ( false === $result ) {
global $wp_filesystem;
$status['errorCode'] = 'unable_to_connect_to_filesystem';
$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
// Pass through the error from WP_Filesystem if one was raised.
if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
}
wp_send_json_error( $status );
}
// An unhandled error occurred.
$status['errorMessage'] = __( 'Theme update failed.' );
wp_send_json_error( $status );
}
/**
* Handles deleting a theme via AJAX.
*
* @since 4.6.0
*
* @see delete_theme()
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*/
function wp_ajax_delete_theme() {
check_ajax_referer( 'updates' );
if ( empty( $_POST['slug'] ) ) {
wp_send_json_error(
array(
'slug' => '',
'errorCode' => 'no_theme_specified',
'errorMessage' => __( 'No theme specified.' ),
)
);
}
$stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
$status = array(
'delete' => 'theme',
'slug' => $stylesheet,
);
if ( ! current_user_can( 'delete_themes' ) ) {
$status['errorMessage'] = __( 'Sorry, you are not allowed to delete themes on this site.' );
wp_send_json_error( $status );
}
if ( ! wp_get_theme( $stylesheet )->exists() ) {
$status['errorMessage'] = __( 'The requested theme does not exist.' );
wp_send_json_error( $status );
}
// Check filesystem credentials. `delete_theme()` will bail otherwise.
$url = wp_nonce_url( 'themes.php?action=delete&stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet );
ob_start();
$credentials = request_filesystem_credentials( $url );
ob_end_clean();
if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
global $wp_filesystem;
$status['errorCode'] = 'unable_to_connect_to_filesystem';
$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
// Pass through the error from WP_Filesystem if one was raised.
if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
}
wp_send_json_error( $status );
}
require_once ABSPATH . 'wp-admin/includes/theme.php';
$result = delete_theme( $stylesheet );
if ( is_wp_error( $result ) ) {
$status['errorMessage'] = $result->get_error_message();
wp_send_json_error( $status );
} elseif ( false === $result ) {
$status['errorMessage'] = __( 'Theme could not be deleted.' );
wp_send_json_error( $status );
}
wp_send_json_success( $status );
}
/**
* Handles installing a plugin via AJAX.
*
* @since 4.6.0
*
* @see Plugin_Upgrader
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*/
function wp_ajax_install_plugin() {
check_ajax_referer( 'updates' );
if ( empty( $_POST['slug'] ) ) {
wp_send_json_error(
array(
'slug' => '',
'errorCode' => 'no_plugin_specified',
'errorMessage' => __( 'No plugin specified.' ),
)
);
}
$status = array(
'install' => 'plugin',
'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ),
);
if ( ! current_user_can( 'install_plugins' ) ) {
$status['errorMessage'] = __( 'Sorry, you are not allowed to install plugins on this site.' );
wp_send_json_error( $status );
}
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
$api = plugins_api(
'plugin_information',
array(
'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ),
'fields' => array(
'sections' => false,
),
)
);
if ( is_wp_error( $api ) ) {
$status['errorMessage'] = $api->get_error_message();
wp_send_json_error( $status );
}
$status['pluginName'] = $api->name;
$skin = new WP_Ajax_Upgrader_Skin();
$upgrader = new Plugin_Upgrader( $skin );
$result = $upgrader->install( $api->download_link );
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
$status['debug'] = $skin->get_upgrade_messages();
}
if ( is_wp_error( $result ) ) {
$status['errorCode'] = $result->get_error_code();
$status['errorMessage'] = $result->get_error_message();
wp_send_json_error( $status );
} elseif ( is_wp_error( $skin->result ) ) {
$status['errorCode'] = $skin->result->get_error_code();
$status['errorMessage'] = $skin->result->get_error_message();
wp_send_json_error( $status );
} elseif ( $skin->get_errors()->has_errors() ) {
$status['errorMessage'] = $skin->get_error_messages();
wp_send_json_error( $status );
} elseif ( is_null( $result ) ) {
global $wp_filesystem;
$status['errorCode'] = 'unable_to_connect_to_filesystem';
$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
// Pass through the error from WP_Filesystem if one was raised.
if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
}
wp_send_json_error( $status );
}
$install_status = install_plugin_install_status( $api );
$pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
// If installation request is coming from import page, do not return network activation link.
$plugins_url = ( 'import' === $pagenow ) ? admin_url( 'plugins.php' ) : network_admin_url( 'plugins.php' );
if ( current_user_can( 'activate_plugin', $install_status['file'] ) && is_plugin_inactive( $install_status['file'] ) ) {
$status['activateUrl'] = add_query_arg(
array(
'_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ),
'action' => 'activate',
'plugin' => $install_status['file'],
),
$plugins_url
);
}
if ( is_multisite() && current_user_can( 'manage_network_plugins' ) && 'import' !== $pagenow ) {
$status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] );
}
wp_send_json_success( $status );
}
/**
* Handles activating a plugin via AJAX.
*
* @since 6.5.0
*/
function wp_ajax_activate_plugin() {
check_ajax_referer( 'updates' );
if ( empty( $_POST['name'] ) || empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
wp_send_json_error(
array(
'slug' => '',
'pluginName' => '',
'plugin' => '',
'errorCode' => 'no_plugin_specified',
'errorMessage' => __( 'No plugin specified.' ),
)
);
}
$status = array(
'activate' => 'plugin',
'slug' => wp_unslash( $_POST['slug'] ),
'pluginName' => wp_unslash( $_POST['name'] ),
'plugin' => wp_unslash( $_POST['plugin'] ),
);
if ( ! current_user_can( 'activate_plugin', $status['plugin'] ) ) {
$status['errorMessage'] = __( 'Sorry, you are not allowed to activate plugins on this site.' );
wp_send_json_error( $status );
}
if ( is_plugin_active( $status['plugin'] ) ) {
$status['errorMessage'] = sprintf(
/* translators: %s: Plugin name. */
__( '%s is already active.' ),
$status['pluginName']
);
}
$activated = activate_plugin( $status['plugin'] );
if ( is_wp_error( $activated ) ) {
$status['errorMessage'] = $activated->get_error_message();
wp_send_json_error( $status );
}
wp_send_json_success( $status );
}
/**
* Handles updating a plugin via AJAX.
*
* @since 4.2.0
*
* @see Plugin_Upgrader
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*/
function wp_ajax_update_plugin() {
check_ajax_referer( 'updates' );
if ( empty( $_POST['plugin'] ) || empty( $_POST['slug'] ) ) {
wp_send_json_error(
array(
'slug' => '',
'errorCode' => 'no_plugin_specified',
'errorMessage' => __( 'No plugin specified.' ),
)
);
}
$plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
$status = array(
'update' => 'plugin',
'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ),
'oldVersion' => '',
'newVersion' => '',
);
if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) {
$status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
wp_send_json_error( $status );
}
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
$status['plugin'] = $plugin;
$status['pluginName'] = $plugin_data['Name'];
if ( $plugin_data['Version'] ) {
/* translators: %s: Plugin version. */
$status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
}
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
wp_update_plugins();
$skin = new WP_Ajax_Upgrader_Skin();
$upgrader = new Plugin_Upgrader( $skin );
$result = $upgrader->bulk_upgrade( array( $plugin ) );
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
$status['debug'] = $skin->get_upgrade_messages();
}
if ( is_wp_error( $skin->result ) ) {
$status['errorCode'] = $skin->result->get_error_code();
$status['errorMessage'] = $skin->result->get_error_message();
wp_send_json_error( $status );
} elseif ( $skin->get_errors()->has_errors() ) {
$status['errorMessage'] = $skin->get_error_messages();
wp_send_json_error( $status );
} elseif ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) {
/*
* Plugin is already at the latest version.
*
* This may also be the return value if the `update_plugins` site transient is empty,
* e.g. when you update two plugins in quick succession before the transient repopulates.
*
* Preferably something can be done to ensure `update_plugins` isn't empty.
* For now, surface some sort of error here.
*/
if ( true === $result[ $plugin ] ) {
$status['errorMessage'] = $upgrader->strings['up_to_date'];
wp_send_json_error( $status );
}
$plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
$plugin_data = reset( $plugin_data );
if ( $plugin_data['Version'] ) {
/* translators: %s: Plugin version. */
$status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
}
wp_send_json_success( $status );
} elseif ( false === $result ) {
global $wp_filesystem;
$status['errorCode'] = 'unable_to_connect_to_filesystem';
$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
// Pass through the error from WP_Filesystem if one was raised.
if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
}
wp_send_json_error( $status );
}
// An unhandled error occurred.
$status['errorMessage'] = __( 'Plugin update failed.' );
wp_send_json_error( $status );
}
/**
* Handles deleting a plugin via AJAX.
*
* @since 4.6.0
*
* @see delete_plugins()
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*/
function wp_ajax_delete_plugin() {
check_ajax_referer( 'updates' );
if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
wp_send_json_error(
array(
'slug' => '',
'errorCode' => 'no_plugin_specified',
'errorMessage' => __( 'No plugin specified.' ),
)
);
}
$plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
$status = array(
'delete' => 'plugin',
'slug' => sanitize_key( wp_unslash( $_POST['slug'] ) ),
);
if ( ! current_user_can( 'delete_plugins' ) || 0 !== validate_file( $plugin ) ) {
$status['errorMessage'] = __( 'Sorry, you are not allowed to delete plugins for this site.' );
wp_send_json_error( $status );
}
$plugin_data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
$status['plugin'] = $plugin;
$status['pluginName'] = $plugin_data['Name'];
if ( is_plugin_active( $plugin ) ) {
$status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' );
wp_send_json_error( $status );
}
// Check filesystem credentials. `delete_plugins()` will bail otherwise.
$url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin, 'bulk-plugins' );
ob_start();
$credentials = request_filesystem_credentials( $url );
ob_end_clean();
if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
global $wp_filesystem;
$status['errorCode'] = 'unable_to_connect_to_filesystem';
$status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
// Pass through the error from WP_Filesystem if one was raised.
if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
$status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
}
wp_send_json_error( $status );
}
$result = delete_plugins( array( $plugin ) );
if ( is_wp_error( $result ) ) {
$status['errorMessage'] = $result->get_error_message();
wp_send_json_error( $status );
} elseif ( false === $result ) {
$status['errorMessage'] = __( 'Plugin could not be deleted.' );
wp_send_json_error( $status );
}
wp_send_json_success( $status );
}
/**
* Handles searching plugins via AJAX.
*
* @since 4.6.0
*
* @global string $s Search term.
*/
function wp_ajax_search_plugins() {
check_ajax_referer( 'updates' );
// Ensure after_plugin_row_{$plugin_file} gets hooked.
wp_plugin_update_rows();
$pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
if ( 'plugins-network' === $pagenow || 'plugins' === $pagenow ) {
set_current_screen( $pagenow );
}
/** @var WP_Plugins_List_Table $wp_list_table */
$wp_list_table = _get_list_table(
'WP_Plugins_List_Table',
array(
'screen' => get_current_screen(),
)
);
$status = array();
if ( ! $wp_list_table->ajax_user_can() ) {
$status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
wp_send_json_error( $status );
}
// Set the correct requester, so pagination works.
$_SERVER['REQUEST_URI'] = add_query_arg(
array_diff_key(
$_POST,
array(
'_ajax_nonce' => null,
'action' => null,
)
),
network_admin_url( 'plugins.php', 'relative' )
);
$GLOBALS['s'] = wp_unslash( $_POST['s'] );
$wp_list_table->prepare_items();
ob_start();
$wp_list_table->display();
$status['count'] = count( $wp_list_table->items );
$status['items'] = ob_get_clean();
wp_send_json_success( $status );
}
/**
* Handles searching plugins to install via AJAX.
*
* @since 4.6.0
*/
function wp_ajax_search_install_plugins() {
check_ajax_referer( 'updates' );
$pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
if ( 'plugin-install-network' === $pagenow || 'plugin-install' === $pagenow ) {
set_current_screen( $pagenow );
}
/** @var WP_Plugin_Install_List_Table $wp_list_table */
$wp_list_table = _get_list_table(
'WP_Plugin_Install_List_Table',
array(
'screen' => get_current_screen(),
)
);
$status = array();
if ( ! $wp_list_table->ajax_user_can() ) {
$status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
wp_send_json_error( $status );
}
// Set the correct requester, so pagination works.
$_SERVER['REQUEST_URI'] = add_query_arg(
array_diff_key(
$_POST,
array(
'_ajax_nonce' => null,
'action' => null,
)
),
network_admin_url( 'plugin-install.php', 'relative' )
);
$wp_list_table->prepare_items();
ob_start();
$wp_list_table->display();
$status['count'] = (int) $wp_list_table->get_pagination_arg( 'total_items' );
$status['items'] = ob_get_clean();
wp_send_json_success( $status );
}
/**
* Handles editing a theme or plugin file via AJAX.
*
* @since 4.9.0
*
* @see wp_edit_theme_plugin_file()
*/
function wp_ajax_edit_theme_plugin_file() {
$r = wp_edit_theme_plugin_file( wp_unslash( $_POST ) ); // Validation of args is done in wp_edit_theme_plugin_file().
if ( is_wp_error( $r ) ) {
wp_send_json_error(
array_merge(
array(
'code' => $r->get_error_code(),
'message' => $r->get_error_message(),
),
(array) $r->get_error_data()
)
);
} else {
wp_send_json_success(
array(
'message' => __( 'File edited successfully.' ),
)
);
}
}
/**
* Handles exporting a user's personal data via AJAX.
*
* @since 4.9.6
*/
function wp_ajax_wp_privacy_export_personal_data() {
if ( empty( $_POST['id'] ) ) {
wp_send_json_error( __( 'Missing request ID.' ) );
}
$request_id = (int) $_POST['id'];
if ( $request_id < 1 ) {
wp_send_json_error( __( 'Invalid request ID.' ) );
}
if ( ! current_user_can( 'export_others_personal_data' ) ) {
wp_send_json_error( __( 'Sorry, you are not allowed to perform this action.' ) );
}
check_ajax_referer( 'wp-privacy-export-personal-data-' . $request_id, 'security' );
// Get the request.
$request = wp_get_user_request( $request_id );
if ( ! $request || 'export_personal_data' !== $request->action_name ) {
wp_send_json_error( __( 'Invalid request type.' ) );
}
$email_address = $request->email;
if ( ! is_email( $email_address ) ) {
wp_send_json_error( __( 'A valid email address must be given.' ) );
}
if ( ! isset( $_POST['exporter'] ) ) {
wp_send_json_error( __( 'Missing exporter index.' ) );
}
$exporter_index = (int) $_POST['exporter'];
if ( ! isset( $_POST['page'] ) ) {
wp_send_json_error( __( 'Missing page index.' ) );
}
$page = (int) $_POST['page'];
$send_as_email = isset( $_POST['sendAsEmail'] ) ? ( 'true' === $_POST['sendAsEmail'] ) : false;
/**
* Filters the array of exporter callbacks.
*
* @since 4.9.6
*
* @param array $args {
* An array of callable exporters of personal data. Default empty array.
*
* @type array ...$0 {
* Array of personal data exporters.
*
* @type callable $callback Callable exporter function that accepts an
* email address and a page number and returns an
* array of name => value pairs of personal data.
* @type string $exporter_friendly_name Translated user facing friendly name for the
* exporter.
* }
* }
*/
$exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
if ( ! is_array( $exporters ) ) {
wp_send_json_error( __( 'An exporter has improperly used the registration filter.' ) );
}
// Do we have any registered exporters?
if ( 0 < count( $exporters ) ) {
if ( $exporter_index < 1 ) {
wp_send_json_error( __( 'Exporter index cannot be negative.' ) );
}
if ( $exporter_index > count( $exporters ) ) {
wp_send_json_error( __( 'Exporter index is out of range.' ) );
}
if ( $page < 1 ) {
wp_send_json_error( __( 'Page index cannot be less than one.' ) );
}
$exporter_keys = array_keys( $exporters );
$exporter_key = $exporter_keys[ $exporter_index - 1 ];
$exporter = $exporters[ $exporter_key ];
if ( ! is_array( $exporter ) ) {
wp_send_json_error(
/* translators: %s: Exporter array index. */
sprintf( __( 'Expected an array describing the exporter at index %s.' ), $exporter_key )
);
}
if ( ! array_key_exists( 'exporter_friendly_name', $exporter ) ) {
wp_send_json_error(
/* translators: %s: Exporter array index. */
sprintf( __( 'Exporter array at index %s does not include a friendly name.' ), $exporter_key )
);
}
$exporter_friendly_name = $exporter['exporter_friendly_name'];
if ( ! array_key_exists( 'callback', $exporter ) ) {
wp_send_json_error(
/* translators: %s: Exporter friendly name. */
sprintf( __( 'Exporter does not include a callback: %s.' ), esc_html( $exporter_friendly_name ) )
);
}
if ( ! is_callable( $exporter['callback'] ) ) {
wp_send_json_error(
/* translators: %s: Exporter friendly name. */
sprintf( __( 'Exporter callback is not a valid callback: %s.' ), esc_html( $exporter_friendly_name ) )
);
}
$callback = $exporter['callback'];
$response = call_user_func( $callback, $email_address, $page );
if ( is_wp_error( $response ) ) {
wp_send_json_error( $response );
}
if ( ! is_array( $response ) ) {
wp_send_json_error(
/* translators: %s: Exporter friendly name. */
sprintf( __( 'Expected response as an array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
);
}
if ( ! array_key_exists( 'data', $response ) ) {
wp_send_json_error(
/* translators: %s: Exporter friendly name. */
sprintf( __( 'Expected data in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
);
}
if ( ! is_array( $response['data'] ) ) {
wp_send_json_error(
/* translators: %s: Exporter friendly name. */
sprintf( __( 'Expected data array in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
);
}
if ( ! array_key_exists( 'done', $response ) ) {
wp_send_json_error(
/* translators: %s: Exporter friendly name. */
sprintf( __( 'Expected done (boolean) in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
);
}
} else {
// No exporters, so we're done.
$exporter_key = '';
$response = array(
'data' => array(),
'done' => true,
);
}
/**
* Filters a page of personal data exporter data. Used to build the export report.
*
* Allows the export response to be consumed by destinations in addition to Ajax.
*
* @since 4.9.6
*
* @param array $response The personal data for the given exporter and page number.
* @param int $exporter_index The index of the exporter that provided this data.
* @param string $email_address The email address associated with this personal data.
* @param int $page The page number for this response.
* @param int $request_id The privacy request post ID associated with this request.
* @param bool $send_as_email Whether the final results of the export should be emailed to the user.
* @param string $exporter_key The key (slug) of the exporter that provided this data.
*/
$response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key );
if ( is_wp_error( $response ) ) {
wp_send_json_error( $response );
}
wp_send_json_success( $response );
}
/**
* Handles erasing personal data via AJAX.
*
* @since 4.9.6
*/
function wp_ajax_wp_privacy_erase_personal_data() {
if ( empty( $_POST['id'] ) ) {
wp_send_json_error( __( 'Missing request ID.' ) );
}
$request_id = (int) $_POST['id'];
if ( $request_id < 1 ) {
wp_send_json_error( __( 'Invalid request ID.' ) );
}
// Both capabilities are required to avoid confusion, see `_wp_personal_data_removal_page()`.
if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) {
wp_send_json_error( __( 'Sorry, you are not allowed to perform this action.' ) );
}
check_ajax_referer( 'wp-privacy-erase-personal-data-' . $request_id, 'security' );
// Get the request.
$request = wp_get_user_request( $request_id );
if ( ! $request || 'remove_personal_data' !== $request->action_name ) {
wp_send_json_error( __( 'Invalid request type.' ) );
}
$email_address = $request->email;
if ( ! is_email( $email_address ) ) {
wp_send_json_error( __( 'Invalid email address in request.' ) );
}
if ( ! isset( $_POST['eraser'] ) ) {
wp_send_json_error( __( 'Missing eraser index.' ) );
}
$eraser_index = (int) $_POST['eraser'];
if ( ! isset( $_POST['page'] ) ) {
wp_send_json_error( __( 'Missing page index.' ) );
}
$page = (int) $_POST['page'];
/**
* Filters the array of personal data eraser callbacks.
*
* @since 4.9.6
*
* @param array $args {
* An array of callable erasers of personal data. Default empty array.
*
* @type array ...$0 {
* Array of personal data exporters.
*
* @type callable $callback Callable eraser that accepts an email address and a page
* number, and returns an array with boolean values for
* whether items were removed or retained and any messages
* from the eraser, as well as if additional pages are
* available.
* @type string $exporter_friendly_name Translated user facing friendly name for the eraser.
* }
* }
*/
$erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() );
// Do we have any registered erasers?
if ( 0 < count( $erasers ) ) {
if ( $eraser_index < 1 ) {
wp_send_json_error( __( 'Eraser index cannot be less than one.' ) );
}
if ( $eraser_index > count( $erasers ) ) {
wp_send_json_error( __( 'Eraser index is out of range.' ) );
}
if ( $page < 1 ) {
wp_send_json_error( __( 'Page index cannot be less than one.' ) );
}
$eraser_keys = array_keys( $erasers );
$eraser_key = $eraser_keys[ $eraser_index - 1 ];
$eraser = $erasers[ $eraser_key ];
if ( ! is_array( $eraser ) ) {
/* translators: %d: Eraser array index. */
wp_send_json_error( sprintf( __( 'Expected an array describing the eraser at index %d.' ), $eraser_index ) );
}
if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) {
/* translators: %d: Eraser array index. */
wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) );
}
$eraser_friendly_name = $eraser['eraser_friendly_name'];
if ( ! array_key_exists( 'callback', $eraser ) ) {
wp_send_json_error(
sprintf(
/* translators: %s: Eraser friendly name. */
__( 'Eraser does not include a callback: %s.' ),
esc_html( $eraser_friendly_name )
)
);
}
if ( ! is_callable( $eraser['callback'] ) ) {
wp_send_json_error(
sprintf(
/* translators: %s: Eraser friendly name. */
__( 'Eraser callback is not valid: %s.' ),
esc_html( $eraser_friendly_name )
)
);
}
$callback = $eraser['callback'];
$response = call_user_func( $callback, $email_address, $page );
if ( is_wp_error( $response ) ) {
wp_send_json_error( $response );
}
if ( ! is_array( $response ) ) {
wp_send_json_error(
sprintf(
/* translators: 1: Eraser friendly name, 2: Eraser array index. */
__( 'Did not receive array from %1$s eraser (index %2$d).' ),
esc_html( $eraser_friendly_name ),
$eraser_index
)
);
}
if ( ! array_key_exists( 'items_removed', $response ) ) {
wp_send_json_error(
sprintf(
/* translators: 1: Eraser friendly name, 2: Eraser array index. */
__( 'Expected items_removed key in response array from %1$s eraser (index %2$d).' ),
esc_html( $eraser_friendly_name ),
$eraser_index
)
);
}
if ( ! array_key_exists( 'items_retained', $response ) ) {
wp_send_json_error(
sprintf(
/* translators: 1: Eraser friendly name, 2: Eraser array index. */
__( 'Expected items_retained key in response array from %1$s eraser (index %2$d).' ),
esc_html( $eraser_friendly_name ),
$eraser_index
)
);
}
if ( ! array_key_exists( 'messages', $response ) ) {
wp_send_json_error(
sprintf(
/* translators: 1: Eraser friendly name, 2: Eraser array index. */
__( 'Expected messages key in response array from %1$s eraser (index %2$d).' ),
esc_html( $eraser_friendly_name ),
$eraser_index
)
);
}
if ( ! is_array( $response['messages'] ) ) {
wp_send_json_error(
sprintf(
/* translators: 1: Eraser friendly name, 2: Eraser array index. */
__( 'Expected messages key to reference an array in response array from %1$s eraser (index %2$d).' ),
esc_html( $eraser_friendly_name ),
$eraser_index
)
);
}
if ( ! array_key_exists( 'done', $response ) ) {
wp_send_json_error(
sprintf(
/* translators: 1: Eraser friendly name, 2: Eraser array index. */
__( 'Expected done flag in response array from %1$s eraser (index %2$d).' ),
esc_html( $eraser_friendly_name ),
$eraser_index
)
);
}
} else {
// No erasers, so we're done.
$eraser_key = '';
$response = array(
'items_removed' => false,
'items_retained' => false,
'messages' => array(),
'done' => true,
);
}
/**
* Filters a page of personal data eraser data.
*
* Allows the erasure response to be consumed by destinations in addition to Ajax.
*
* @since 4.9.6
*
* @param array $response {
* The personal data for the given exporter and page number.
*
* @type bool $items_removed Whether items were actually removed or not.
* @type bool $items_retained Whether items were retained or not.
* @type string[] $messages An array of messages to add to the personal data export file.
* @type bool $done Whether the eraser is finished or not.
* }
* @param int $eraser_index The index of the eraser that provided this data.
* @param string $email_address The email address associated with this personal data.
* @param int $page The page number for this response.
* @param int $request_id The privacy request post ID associated with this request.
* @param string $eraser_key The key (slug) of the eraser that provided this data.
*/
$response = apply_filters( 'wp_privacy_personal_data_erasure_page', $response, $eraser_index, $email_address, $page, $request_id, $eraser_key );
if ( is_wp_error( $response ) ) {
wp_send_json_error( $response );
}
wp_send_json_success( $response );
}
/**
* Handles site health checks on server communication via AJAX.
*
* @since 5.2.0
* @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_dotorg_communication()
* @see WP_REST_Site_Health_Controller::test_dotorg_communication()
*/
function wp_ajax_health_check_dotorg_communication() {
_doing_it_wrong(
'wp_ajax_health_check_dotorg_communication',
sprintf(
/* translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it. */
__( 'The Site Health check for %1$s has been replaced with %2$s.' ),
'wp_ajax_health_check_dotorg_communication',
'WP_REST_Site_Health_Controller::test_dotorg_communication'
),
'5.6.0'
);
check_ajax_referer( 'health-check-site-status' );
if ( ! current_user_can( 'view_site_health_checks' ) ) {
wp_send_json_error();
}
if ( ! class_exists( 'WP_Site_Health' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
}
$site_health = WP_Site_Health::get_instance();
wp_send_json_success( $site_health->get_test_dotorg_communication() );
}
/**
* Handles site health checks on background updates via AJAX.
*
* @since 5.2.0
* @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_background_updates()
* @see WP_REST_Site_Health_Controller::test_background_updates()
*/
function wp_ajax_health_check_background_updates() {
_doing_it_wrong(
'wp_ajax_health_check_background_updates',
sprintf(
/* translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it. */
__( 'The Site Health check for %1$s has been replaced with %2$s.' ),
'wp_ajax_health_check_background_updates',
'WP_REST_Site_Health_Controller::test_background_updates'
),
'5.6.0'
);
check_ajax_referer( 'health-check-site-status' );
if ( ! current_user_can( 'view_site_health_checks' ) ) {
wp_send_json_error();
}
if ( ! class_exists( 'WP_Site_Health' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
}
$site_health = WP_Site_Health::get_instance();
wp_send_json_success( $site_health->get_test_background_updates() );
}
/**
* Handles site health checks on loopback requests via AJAX.
*
* @since 5.2.0
* @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_loopback_requests()
* @see WP_REST_Site_Health_Controller::test_loopback_requests()
*/
function wp_ajax_health_check_loopback_requests() {
_doing_it_wrong(
'wp_ajax_health_check_loopback_requests',
sprintf(
/* translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it. */
__( 'The Site Health check for %1$s has been replaced with %2$s.' ),
'wp_ajax_health_check_loopback_requests',
'WP_REST_Site_Health_Controller::test_loopback_requests'
),
'5.6.0'
);
check_ajax_referer( 'health-check-site-status' );
if ( ! current_user_can( 'view_site_health_checks' ) ) {
wp_send_json_error();
}
if ( ! class_exists( 'WP_Site_Health' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
}
$site_health = WP_Site_Health::get_instance();
wp_send_json_success( $site_health->get_test_loopback_requests() );
}
/**
* Handles site health check to update the result status via AJAX.
*
* @since 5.2.0
*/
function wp_ajax_health_check_site_status_result() {
check_ajax_referer( 'health-check-site-status-result' );
if ( ! current_user_can( 'view_site_health_checks' ) ) {
wp_send_json_error();
}
set_transient( 'health-check-site-status-result', wp_json_encode( $_POST['counts'] ) );
wp_send_json_success();
}
/**
* Handles site health check to get directories and database sizes via AJAX.
*
* @since 5.2.0
* @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::get_directory_sizes()
* @see WP_REST_Site_Health_Controller::get_directory_sizes()
*/
function wp_ajax_health_check_get_sizes() {
_doing_it_wrong(
'wp_ajax_health_check_get_sizes',
sprintf(
/* translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it. */
__( 'The Site Health check for %1$s has been replaced with %2$s.' ),
'wp_ajax_health_check_get_sizes',
'WP_REST_Site_Health_Controller::get_directory_sizes'
),
'5.6.0'
);
check_ajax_referer( 'health-check-site-status-result' );
if ( ! current_user_can( 'view_site_health_checks' ) || is_multisite() ) {
wp_send_json_error();
}
if ( ! class_exists( 'WP_Debug_Data' ) ) {
require_once ABSPATH . 'wp-admin/includes/class-wp-debug-data.php';
}
$sizes_data = WP_Debug_Data::get_sizes();
$all_sizes = array( 'raw' => 0 );
foreach ( $sizes_data as $name => $value ) {
$name = sanitize_text_field( $name );
$data = array();
if ( isset( $value['size'] ) ) {
if ( is_string( $value['size'] ) ) {
$data['size'] = sanitize_text_field( $value['size'] );
} else {
$data['size'] = (int) $value['size'];
}
}
if ( isset( $value['debug'] ) ) {
if ( is_string( $value['debug'] ) ) {
$data['debug'] = sanitize_text_field( $value['debug'] );
} else {
$data['debug'] = (int) $value['debug'];
}
}
if ( ! empty( $value['raw'] ) ) {
$data['raw'] = (int) $value['raw'];
}
$all_sizes[ $name ] = $data;
}
if ( isset( $all_sizes['total_size']['debug'] ) && 'not available' === $all_sizes['total_size']['debug'] ) {
wp_send_json_error( $all_sizes );
}
wp_send_json_success( $all_sizes );
}
/**
* Handles renewing the REST API nonce via AJAX.
*
* @since 5.3.0
*/
function wp_ajax_rest_nonce() {
exit( wp_create_nonce( 'wp_rest' ) );
}
/**
* Handles enabling or disable plugin and theme auto-updates via AJAX.
*
* @since 5.5.0
*/
function wp_ajax_toggle_auto_updates() {
check_ajax_referer( 'updates' );
if ( empty( $_POST['type'] ) || empty( $_POST['asset'] ) || empty( $_POST['state'] ) ) {
wp_send_json_error( array( 'error' => __( 'Invalid data. No selected item.' ) ) );
}
$asset = sanitize_text_field( urldecode( $_POST['asset'] ) );
if ( 'enable' !== $_POST['state'] && 'disable' !== $_POST['state'] ) {
wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown state.' ) ) );
}
$state = $_POST['state'];
if ( 'plugin' !== $_POST['type'] && 'theme' !== $_POST['type'] ) {
wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown type.' ) ) );
}
$type = $_POST['type'];
switch ( $type ) {
case 'plugin':
if ( ! current_user_can( 'update_plugins' ) ) {
$error_message = __( 'Sorry, you are not allowed to modify plugins.' );
wp_send_json_error( array( 'error' => $error_message ) );
}
$option = 'auto_update_plugins';
/** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
$all_items = apply_filters( 'all_plugins', get_plugins() );
break;
case 'theme':
if ( ! current_user_can( 'update_themes' ) ) {
$error_message = __( 'Sorry, you are not allowed to modify themes.' );
wp_send_json_error( array( 'error' => $error_message ) );
}
$option = 'auto_update_themes';
$all_items = wp_get_themes();
break;
default:
wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown type.' ) ) );
}
if ( ! array_key_exists( $asset, $all_items ) ) {
$error_message = __( 'Invalid data. The item does not exist.' );
wp_send_json_error( array( 'error' => $error_message ) );
}
$auto_updates = (array) get_site_option( $option, array() );
if ( 'disable' === $state ) {
$auto_updates = array_diff( $auto_updates, array( $asset ) );
} else {
$auto_updates[] = $asset;
$auto_updates = array_unique( $auto_updates );
}
// Remove items that have been deleted since the site option was last updated.
$auto_updates = array_intersect( $auto_updates, array_keys( $all_items ) );
update_site_option( $option, $auto_updates );
wp_send_json_success();
}
/**
* Handles sending a password reset link via AJAX.
*
* @since 5.7.0
*/
function wp_ajax_send_password_reset() {
// Validate the nonce for this action.
$user_id = isset( $_POST['user_id'] ) ? (int) $_POST['user_id'] : 0;
check_ajax_referer( 'reset-password-for-' . $user_id, 'nonce' );
// Verify user capabilities.
if ( ! current_user_can( 'edit_user', $user_id ) ) {
wp_send_json_error( __( 'Cannot send password reset, permission denied.' ) );
}
// Send the password reset link.
$user = get_userdata( $user_id );
$results = retrieve_password( $user->user_login );
if ( true === $results ) {
wp_send_json_success(
/* translators: %s: User's display name. */
sprintf( __( 'A password reset link was emailed to %s.' ), $user->display_name )
);
} else {
wp_send_json_error( $results->get_error_message() );
}
}
class-wp-upgrader.php 0000644 00000135551 15207570471 0010637 0 ustar 00 skin = new WP_Upgrader_Skin();
} else {
$this->skin = $skin;
}
}
/**
* Initializes the upgrader.
*
* This will set the relationship between the skin being used and this upgrader,
* and also add the generic strings to `WP_Upgrader::$strings`.
*
* Additionally, it will schedule a weekly task to clean up the temporary backup directory.
*
* @since 2.8.0
* @since 6.3.0 Added the `schedule_temp_backup_cleanup()` task.
*/
public function init() {
$this->skin->set_upgrader( $this );
$this->generic_strings();
if ( ! wp_installing() ) {
$this->schedule_temp_backup_cleanup();
}
}
/**
* Schedules the cleanup of the temporary backup directory.
*
* @since 6.3.0
*/
protected function schedule_temp_backup_cleanup() {
if ( false === wp_next_scheduled( 'wp_delete_temp_updater_backups' ) ) {
wp_schedule_event( time(), 'weekly', 'wp_delete_temp_updater_backups' );
}
}
/**
* Adds the generic strings to WP_Upgrader::$strings.
*
* @since 2.8.0
*/
public function generic_strings() {
$this->strings['bad_request'] = __( 'Invalid data provided.' );
$this->strings['fs_unavailable'] = __( 'Could not access filesystem.' );
$this->strings['fs_error'] = __( 'Filesystem error.' );
$this->strings['fs_no_root_dir'] = __( 'Unable to locate WordPress root directory.' );
/* translators: %s: Directory name. */
$this->strings['fs_no_content_dir'] = sprintf( __( 'Unable to locate WordPress content directory (%s).' ), 'wp-content' );
$this->strings['fs_no_plugins_dir'] = __( 'Unable to locate WordPress plugin directory.' );
$this->strings['fs_no_themes_dir'] = __( 'Unable to locate WordPress theme directory.' );
/* translators: %s: Directory name. */
$this->strings['fs_no_folder'] = __( 'Unable to locate needed folder (%s).' );
$this->strings['no_package'] = __( 'Package not available.' );
$this->strings['download_failed'] = __( 'Download failed.' );
$this->strings['installing_package'] = __( 'Installing the latest version…' );
$this->strings['no_files'] = __( 'The package contains no files.' );
$this->strings['folder_exists'] = __( 'Destination folder already exists.' );
$this->strings['mkdir_failed'] = __( 'Could not create directory.' );
$this->strings['incompatible_archive'] = __( 'The package could not be installed.' );
$this->strings['files_not_writable'] = __( 'The update cannot be installed because some files could not be copied. This is usually due to inconsistent file permissions.' );
$this->strings['dir_not_readable'] = __( 'A directory could not be read.' );
$this->strings['maintenance_start'] = __( 'Enabling Maintenance mode…' );
$this->strings['maintenance_end'] = __( 'Disabling Maintenance mode…' );
/* translators: %s: upgrade-temp-backup */
$this->strings['temp_backup_mkdir_failed'] = sprintf( __( 'Could not create the %s directory.' ), 'upgrade-temp-backup' );
/* translators: %s: upgrade-temp-backup */
$this->strings['temp_backup_move_failed'] = sprintf( __( 'Could not move the old version to the %s directory.' ), 'upgrade-temp-backup' );
/* translators: %s: The plugin or theme slug. */
$this->strings['temp_backup_restore_failed'] = __( 'Could not restore the original version of %s.' );
/* translators: %s: The plugin or theme slug. */
$this->strings['temp_backup_delete_failed'] = __( 'Could not delete the temporary backup directory for %s.' );
}
/**
* Connects to the filesystem.
*
* @since 2.8.0
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*
* @param string[] $directories Optional. Array of directories. If any of these do
* not exist, a WP_Error object will be returned.
* Default empty array.
* @param bool $allow_relaxed_file_ownership Whether to allow relaxed file ownership.
* Default false.
* @return bool|WP_Error True if able to connect, false or a WP_Error otherwise.
*/
public function fs_connect( $directories = array(), $allow_relaxed_file_ownership = false ) {
global $wp_filesystem;
$credentials = $this->skin->request_filesystem_credentials( false, $directories[0], $allow_relaxed_file_ownership );
if ( false === $credentials ) {
return false;
}
if ( ! WP_Filesystem( $credentials, $directories[0], $allow_relaxed_file_ownership ) ) {
$error = true;
if ( is_object( $wp_filesystem ) && $wp_filesystem->errors->has_errors() ) {
$error = $wp_filesystem->errors;
}
// Failed to connect. Error and request again.
$this->skin->request_filesystem_credentials( $error, $directories[0], $allow_relaxed_file_ownership );
return false;
}
if ( ! is_object( $wp_filesystem ) ) {
return new WP_Error( 'fs_unavailable', $this->strings['fs_unavailable'] );
}
if ( is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
return new WP_Error( 'fs_error', $this->strings['fs_error'], $wp_filesystem->errors );
}
foreach ( (array) $directories as $dir ) {
switch ( $dir ) {
case ABSPATH:
if ( ! $wp_filesystem->abspath() ) {
return new WP_Error( 'fs_no_root_dir', $this->strings['fs_no_root_dir'] );
}
break;
case WP_CONTENT_DIR:
if ( ! $wp_filesystem->wp_content_dir() ) {
return new WP_Error( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
}
break;
case WP_PLUGIN_DIR:
if ( ! $wp_filesystem->wp_plugins_dir() ) {
return new WP_Error( 'fs_no_plugins_dir', $this->strings['fs_no_plugins_dir'] );
}
break;
case get_theme_root():
if ( ! $wp_filesystem->wp_themes_dir() ) {
return new WP_Error( 'fs_no_themes_dir', $this->strings['fs_no_themes_dir'] );
}
break;
default:
if ( ! $wp_filesystem->find_folder( $dir ) ) {
return new WP_Error( 'fs_no_folder', sprintf( $this->strings['fs_no_folder'], esc_html( basename( $dir ) ) ) );
}
break;
}
}
return true;
}
/**
* Downloads a package.
*
* @since 2.8.0
* @since 5.2.0 Added the `$check_signatures` parameter.
* @since 5.5.0 Added the `$hook_extra` parameter.
*
* @param string $package The URI of the package. If this is the full path to an
* existing local file, it will be returned untouched.
* @param bool $check_signatures Whether to validate file signatures. Default false.
* @param array $hook_extra Extra arguments to pass to the filter hooks. Default empty array.
* @return string|WP_Error The full path to the downloaded package file, or a WP_Error object.
*/
public function download_package( $package, $check_signatures = false, $hook_extra = array() ) {
/**
* Filters whether to return the package.
*
* @since 3.7.0
* @since 5.5.0 Added the `$hook_extra` parameter.
*
* @param bool $reply Whether to bail without returning the package.
* Default false.
* @param string $package The package file name.
* @param WP_Upgrader $upgrader The WP_Upgrader instance.
* @param array $hook_extra Extra arguments passed to hooked filters.
*/
$reply = apply_filters( 'upgrader_pre_download', false, $package, $this, $hook_extra );
if ( false !== $reply ) {
return $reply;
}
if ( ! preg_match( '!^(http|https|ftp)://!i', $package ) && file_exists( $package ) ) { // Local file or remote?
return $package; // Must be a local file.
}
if ( empty( $package ) ) {
return new WP_Error( 'no_package', $this->strings['no_package'] );
}
$this->skin->feedback( 'downloading_package', $package );
$download_file = download_url( $package, 300, $check_signatures );
if ( is_wp_error( $download_file ) && ! $download_file->get_error_data( 'softfail-filename' ) ) {
return new WP_Error( 'download_failed', $this->strings['download_failed'], $download_file->get_error_message() );
}
return $download_file;
}
/**
* Unpacks a compressed package file.
*
* @since 2.8.0
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*
* @param string $package Full path to the package file.
* @param bool $delete_package Optional. Whether to delete the package file after attempting
* to unpack it. Default true.
* @return string|WP_Error The path to the unpacked contents, or a WP_Error on failure.
*/
public function unpack_package( $package, $delete_package = true ) {
global $wp_filesystem;
$this->skin->feedback( 'unpack_package' );
if ( ! $wp_filesystem->wp_content_dir() ) {
return new WP_Error( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
}
$upgrade_folder = $wp_filesystem->wp_content_dir() . 'upgrade/';
// Clean up contents of upgrade directory beforehand.
$upgrade_files = $wp_filesystem->dirlist( $upgrade_folder );
if ( ! empty( $upgrade_files ) ) {
foreach ( $upgrade_files as $file ) {
$wp_filesystem->delete( $upgrade_folder . $file['name'], true );
}
}
// We need a working directory - strip off any .tmp or .zip suffixes.
$working_dir = $upgrade_folder . basename( basename( $package, '.tmp' ), '.zip' );
// Clean up working directory.
if ( $wp_filesystem->is_dir( $working_dir ) ) {
$wp_filesystem->delete( $working_dir, true );
}
// Unzip package to working directory.
$result = unzip_file( $package, $working_dir );
// Once extracted, delete the package if required.
if ( $delete_package ) {
unlink( $package );
}
if ( is_wp_error( $result ) ) {
$wp_filesystem->delete( $working_dir, true );
if ( 'incompatible_archive' === $result->get_error_code() ) {
return new WP_Error( 'incompatible_archive', $this->strings['incompatible_archive'], $result->get_error_data() );
}
return $result;
}
return $working_dir;
}
/**
* Flattens the results of WP_Filesystem_Base::dirlist() for iterating over.
*
* @since 4.9.0
*
* @param array $nested_files Array of files as returned by WP_Filesystem_Base::dirlist().
* @param string $path Relative path to prepend to child nodes. Optional.
* @return array A flattened array of the $nested_files specified.
*/
protected function flatten_dirlist( $nested_files, $path = '' ) {
$files = array();
foreach ( $nested_files as $name => $details ) {
$files[ $path . $name ] = $details;
// Append children recursively.
if ( ! empty( $details['files'] ) ) {
$children = $this->flatten_dirlist( $details['files'], $path . $name . '/' );
// Merge keeping possible numeric keys, which array_merge() will reindex from 0..n.
$files = $files + $children;
}
}
return $files;
}
/**
* Clears the directory where this item is going to be installed into.
*
* @since 4.3.0
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*
* @param string $remote_destination The location on the remote filesystem to be cleared.
* @return true|WP_Error True upon success, WP_Error on failure.
*/
public function clear_destination( $remote_destination ) {
global $wp_filesystem;
$files = $wp_filesystem->dirlist( $remote_destination, true, true );
// False indicates that the $remote_destination doesn't exist.
if ( false === $files ) {
return true;
}
// Flatten the file list to iterate over.
$files = $this->flatten_dirlist( $files );
// Check all files are writable before attempting to clear the destination.
$unwritable_files = array();
// Check writability.
foreach ( $files as $filename => $file_details ) {
if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
// Attempt to alter permissions to allow writes and try again.
$wp_filesystem->chmod( $remote_destination . $filename, ( 'd' === $file_details['type'] ? FS_CHMOD_DIR : FS_CHMOD_FILE ) );
if ( ! $wp_filesystem->is_writable( $remote_destination . $filename ) ) {
$unwritable_files[] = $filename;
}
}
}
if ( ! empty( $unwritable_files ) ) {
return new WP_Error( 'files_not_writable', $this->strings['files_not_writable'], implode( ', ', $unwritable_files ) );
}
if ( ! $wp_filesystem->delete( $remote_destination, true ) ) {
return new WP_Error( 'remove_old_failed', $this->strings['remove_old_failed'] );
}
return true;
}
/**
* Install a package.
*
* Copies the contents of a package from a source directory, and installs them in
* a destination directory. Optionally removes the source. It can also optionally
* clear out the destination folder if it already exists.
*
* @since 2.8.0
* @since 6.2.0 Use move_dir() instead of copy_dir() when possible.
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
* @global string[] $wp_theme_directories
*
* @param array|string $args {
* Optional. Array or string of arguments for installing a package. Default empty array.
*
* @type string $source Required path to the package source. Default empty.
* @type string $destination Required path to a folder to install the package in.
* Default empty.
* @type bool $clear_destination Whether to delete any files already in the destination
* folder. Default false.
* @type bool $clear_working Whether to delete the files from the working directory
* after copying them to the destination. Default false.
* @type bool $abort_if_destination_exists Whether to abort the installation if
* the destination folder already exists. Default true.
* @type array $hook_extra Extra arguments to pass to the filter hooks called by
* WP_Upgrader::install_package(). Default empty array.
* }
*
* @return array|WP_Error The result (also stored in `WP_Upgrader::$result`), or a WP_Error on failure.
*/
public function install_package( $args = array() ) {
global $wp_filesystem, $wp_theme_directories;
$defaults = array(
'source' => '', // Please always pass this.
'destination' => '', // ...and this.
'clear_destination' => false,
'clear_working' => false,
'abort_if_destination_exists' => true,
'hook_extra' => array(),
);
$args = wp_parse_args( $args, $defaults );
// These were previously extract()'d.
$source = $args['source'];
$destination = $args['destination'];
$clear_destination = $args['clear_destination'];
/*
* Give the upgrade an additional 300 seconds (5 minutes) to ensure the install
* doesn't prematurely timeout having used up the maximum script execution time
* upacking and downloading in WP_Upgrader->run().
*/
if ( function_exists( 'set_time_limit' ) ) {
set_time_limit( 300 );
}
if (
( ! is_string( $source ) || '' === $source || trim( $source ) !== $source ) ||
( ! is_string( $destination ) || '' === $destination || trim( $destination ) !== $destination )
) {
return new WP_Error( 'bad_request', $this->strings['bad_request'] );
}
$this->skin->feedback( 'installing_package' );
/**
* Filters the installation response before the installation has started.
*
* Returning a value that could be evaluated as a `WP_Error` will effectively
* short-circuit the installation, returning that value instead.
*
* @since 2.8.0
*
* @param bool|WP_Error $response Installation response.
* @param array $hook_extra Extra arguments passed to hooked filters.
*/
$res = apply_filters( 'upgrader_pre_install', true, $args['hook_extra'] );
if ( is_wp_error( $res ) ) {
return $res;
}
// Retain the original source and destinations.
$remote_source = $args['source'];
$local_destination = $destination;
$dirlist = $wp_filesystem->dirlist( $remote_source );
if ( false === $dirlist ) {
return new WP_Error( 'source_read_failed', $this->strings['fs_error'], $this->strings['dir_not_readable'] );
}
$source_files = array_keys( $dirlist );
$remote_destination = $wp_filesystem->find_folder( $local_destination );
// Locate which directory to copy to the new folder. This is based on the actual folder holding the files.
if ( 1 === count( $source_files ) && $wp_filesystem->is_dir( trailingslashit( $args['source'] ) . $source_files[0] . '/' ) ) {
// Only one folder? Then we want its contents.
$source = trailingslashit( $args['source'] ) . trailingslashit( $source_files[0] );
} elseif ( 0 === count( $source_files ) ) {
// There are no files?
return new WP_Error( 'incompatible_archive_empty', $this->strings['incompatible_archive'], $this->strings['no_files'] );
} else {
/*
* It's only a single file, the upgrader will use the folder name of this file as the destination folder.
* Folder name is based on zip filename.
*/
$source = trailingslashit( $args['source'] );
}
/**
* Filters the source file location for the upgrade package.
*
* @since 2.8.0
* @since 4.4.0 The $hook_extra parameter became available.
*
* @param string $source File source location.
* @param string $remote_source Remote file source location.
* @param WP_Upgrader $upgrader WP_Upgrader instance.
* @param array $hook_extra Extra arguments passed to hooked filters.
*/
$source = apply_filters( 'upgrader_source_selection', $source, $remote_source, $this, $args['hook_extra'] );
if ( is_wp_error( $source ) ) {
return $source;
}
if ( ! empty( $args['hook_extra']['temp_backup'] ) ) {
$temp_backup = $this->move_to_temp_backup_dir( $args['hook_extra']['temp_backup'] );
if ( is_wp_error( $temp_backup ) ) {
return $temp_backup;
}
$this->temp_backups[] = $args['hook_extra']['temp_backup'];
}
// Has the source location changed? If so, we need a new source_files list.
if ( $source !== $remote_source ) {
$dirlist = $wp_filesystem->dirlist( $source );
if ( false === $dirlist ) {
return new WP_Error( 'new_source_read_failed', $this->strings['fs_error'], $this->strings['dir_not_readable'] );
}
$source_files = array_keys( $dirlist );
}
/*
* Protection against deleting files in any important base directories.
* Theme_Upgrader & Plugin_Upgrader also trigger this, as they pass the
* destination directory (WP_PLUGIN_DIR / wp-content/themes) intending
* to copy the directory into the directory, whilst they pass the source
* as the actual files to copy.
*/
$protected_directories = array( ABSPATH, WP_CONTENT_DIR, WP_PLUGIN_DIR, WP_CONTENT_DIR . '/themes' );
if ( is_array( $wp_theme_directories ) ) {
$protected_directories = array_merge( $protected_directories, $wp_theme_directories );
}
if ( in_array( $destination, $protected_directories, true ) ) {
$remote_destination = trailingslashit( $remote_destination ) . trailingslashit( basename( $source ) );
$destination = trailingslashit( $destination ) . trailingslashit( basename( $source ) );
}
if ( $clear_destination ) {
// We're going to clear the destination if there's something there.
$this->skin->feedback( 'remove_old' );
$removed = $this->clear_destination( $remote_destination );
/**
* Filters whether the upgrader cleared the destination.
*
* @since 2.8.0
*
* @param true|WP_Error $removed Whether the destination was cleared.
* True upon success, WP_Error on failure.
* @param string $local_destination The local package destination.
* @param string $remote_destination The remote package destination.
* @param array $hook_extra Extra arguments passed to hooked filters.
*/
$removed = apply_filters( 'upgrader_clear_destination', $removed, $local_destination, $remote_destination, $args['hook_extra'] );
if ( is_wp_error( $removed ) ) {
return $removed;
}
} elseif ( $args['abort_if_destination_exists'] && $wp_filesystem->exists( $remote_destination ) ) {
/*
* If we're not clearing the destination folder and something exists there already, bail.
* But first check to see if there are actually any files in the folder.
*/
$_files = $wp_filesystem->dirlist( $remote_destination );
if ( ! empty( $_files ) ) {
$wp_filesystem->delete( $remote_source, true ); // Clear out the source files.
return new WP_Error( 'folder_exists', $this->strings['folder_exists'], $remote_destination );
}
}
/*
* If 'clear_working' is false, the source should not be removed, so use copy_dir() instead.
*
* Partial updates, like language packs, may want to retain the destination.
* If the destination exists or has contents, this may be a partial update,
* and the destination should not be removed, so use copy_dir() instead.
*/
if ( $args['clear_working']
&& (
// Destination does not exist or has no contents.
! $wp_filesystem->exists( $remote_destination )
|| empty( $wp_filesystem->dirlist( $remote_destination ) )
)
) {
$result = move_dir( $source, $remote_destination, true );
} else {
// Create destination if needed.
if ( ! $wp_filesystem->exists( $remote_destination ) ) {
if ( ! $wp_filesystem->mkdir( $remote_destination, FS_CHMOD_DIR ) ) {
return new WP_Error( 'mkdir_failed_destination', $this->strings['mkdir_failed'], $remote_destination );
}
}
$result = copy_dir( $source, $remote_destination );
}
// Clear the working directory?
if ( $args['clear_working'] ) {
$wp_filesystem->delete( $remote_source, true );
}
if ( is_wp_error( $result ) ) {
return $result;
}
$destination_name = basename( str_replace( $local_destination, '', $destination ) );
if ( '.' === $destination_name ) {
$destination_name = '';
}
$this->result = compact( 'source', 'source_files', 'destination', 'destination_name', 'local_destination', 'remote_destination', 'clear_destination' );
/**
* Filters the installation response after the installation has finished.
*
* @since 2.8.0
*
* @param bool $response Installation response.
* @param array $hook_extra Extra arguments passed to hooked filters.
* @param array $result Installation result data.
*/
$res = apply_filters( 'upgrader_post_install', true, $args['hook_extra'], $this->result );
if ( is_wp_error( $res ) ) {
$this->result = $res;
return $res;
}
// Bombard the calling function will all the info which we've just used.
return $this->result;
}
/**
* Runs an upgrade/installation.
*
* Attempts to download the package (if it is not a local file), unpack it, and
* install it in the destination folder.
*
* @since 2.8.0
*
* @param array $options {
* Array or string of arguments for upgrading/installing a package.
*
* @type string $package The full path or URI of the package to install.
* Default empty.
* @type string $destination The full path to the destination folder.
* Default empty.
* @type bool $clear_destination Whether to delete any files already in the
* destination folder. Default false.
* @type bool $clear_working Whether to delete the files from the working
* directory after copying them to the destination.
* Default true.
* @type bool $abort_if_destination_exists Whether to abort the installation if the destination
* folder already exists. When true, `$clear_destination`
* should be false. Default true.
* @type bool $is_multi Whether this run is one of multiple upgrade/installation
* actions being performed in bulk. When true, the skin
* WP_Upgrader::header() and WP_Upgrader::footer()
* aren't called. Default false.
* @type array $hook_extra Extra arguments to pass to the filter hooks called by
* WP_Upgrader::run().
* }
* @return array|false|WP_Error The result from self::install_package() on success, otherwise a WP_Error,
* or false if unable to connect to the filesystem.
*/
public function run( $options ) {
$defaults = array(
'package' => '', // Please always pass this.
'destination' => '', // ...and this.
'clear_destination' => false,
'clear_working' => true,
'abort_if_destination_exists' => true, // Abort if the destination directory exists. Pass clear_destination as false please.
'is_multi' => false,
'hook_extra' => array(), // Pass any extra $hook_extra args here, this will be passed to any hooked filters.
);
$options = wp_parse_args( $options, $defaults );
/**
* Filters the package options before running an update.
*
* See also {@see 'upgrader_process_complete'}.
*
* @since 4.3.0
*
* @param array $options {
* Options used by the upgrader.
*
* @type string $package Package for update.
* @type string $destination Update location.
* @type bool $clear_destination Clear the destination resource.
* @type bool $clear_working Clear the working resource.
* @type bool $abort_if_destination_exists Abort if the Destination directory exists.
* @type bool $is_multi Whether the upgrader is running multiple times.
* @type array $hook_extra {
* Extra hook arguments.
*
* @type string $action Type of action. Default 'update'.
* @type string $type Type of update process. Accepts 'plugin', 'theme', or 'core'.
* @type bool $bulk Whether the update process is a bulk update. Default true.
* @type string $plugin Path to the plugin file relative to the plugins directory.
* @type string $theme The stylesheet or template name of the theme.
* @type string $language_update_type The language pack update type. Accepts 'plugin', 'theme',
* or 'core'.
* @type object $language_update The language pack update offer.
* }
* }
*/
$options = apply_filters( 'upgrader_package_options', $options );
if ( ! $options['is_multi'] ) { // Call $this->header separately if running multiple times.
$this->skin->header();
}
// Connect to the filesystem first.
$res = $this->fs_connect( array( WP_CONTENT_DIR, $options['destination'] ) );
// Mainly for non-connected filesystem.
if ( ! $res ) {
if ( ! $options['is_multi'] ) {
$this->skin->footer();
}
return false;
}
$this->skin->before();
if ( is_wp_error( $res ) ) {
$this->skin->error( $res );
$this->skin->after();
if ( ! $options['is_multi'] ) {
$this->skin->footer();
}
return $res;
}
/*
* Download the package. Note: If the package is the full path
* to an existing local file, it will be returned untouched.
*/
$download = $this->download_package( $options['package'], false, $options['hook_extra'] );
/*
* Allow for signature soft-fail.
* WARNING: This may be removed in the future.
*/
if ( is_wp_error( $download ) && $download->get_error_data( 'softfail-filename' ) ) {
// Don't output the 'no signature could be found' failure message for now.
if ( 'signature_verification_no_signature' !== $download->get_error_code() || WP_DEBUG ) {
// Output the failure error as a normal feedback, and not as an error.
$this->skin->feedback( $download->get_error_message() );
// Report this failure back to WordPress.org for debugging purposes.
wp_version_check(
array(
'signature_failure_code' => $download->get_error_code(),
'signature_failure_data' => $download->get_error_data(),
)
);
}
// Pretend this error didn't happen.
$download = $download->get_error_data( 'softfail-filename' );
}
if ( is_wp_error( $download ) ) {
$this->skin->error( $download );
$this->skin->after();
if ( ! $options['is_multi'] ) {
$this->skin->footer();
}
return $download;
}
$delete_package = ( $download !== $options['package'] ); // Do not delete a "local" file.
// Unzips the file into a temporary directory.
$working_dir = $this->unpack_package( $download, $delete_package );
if ( is_wp_error( $working_dir ) ) {
$this->skin->error( $working_dir );
$this->skin->after();
if ( ! $options['is_multi'] ) {
$this->skin->footer();
}
return $working_dir;
}
// With the given options, this installs it to the destination directory.
$result = $this->install_package(
array(
'source' => $working_dir,
'destination' => $options['destination'],
'clear_destination' => $options['clear_destination'],
'abort_if_destination_exists' => $options['abort_if_destination_exists'],
'clear_working' => $options['clear_working'],
'hook_extra' => $options['hook_extra'],
)
);
/**
* Filters the result of WP_Upgrader::install_package().
*
* @since 5.7.0
*
* @param array|WP_Error $result Result from WP_Upgrader::install_package().
* @param array $hook_extra Extra arguments passed to hooked filters.
*/
$result = apply_filters( 'upgrader_install_package_result', $result, $options['hook_extra'] );
$this->skin->set_result( $result );
if ( is_wp_error( $result ) ) {
// An automatic plugin update will have already performed its rollback.
if ( ! empty( $options['hook_extra']['temp_backup'] ) ) {
$this->temp_restores[] = $options['hook_extra']['temp_backup'];
/*
* Restore the backup on shutdown.
* Actions running on `shutdown` are immune to PHP timeouts,
* so in case the failure was due to a PHP timeout,
* it will still be able to properly restore the previous version.
*
* Zero arguments are accepted as a string can sometimes be passed
* internally during actions, causing an error because
* `WP_Upgrader::restore_temp_backup()` expects an array.
*/
add_action( 'shutdown', array( $this, 'restore_temp_backup' ), 10, 0 );
}
$this->skin->error( $result );
if ( ! method_exists( $this->skin, 'hide_process_failed' ) || ! $this->skin->hide_process_failed( $result ) ) {
$this->skin->feedback( 'process_failed' );
}
} else {
// Installation succeeded.
$this->skin->feedback( 'process_success' );
}
$this->skin->after();
// Clean up the backup kept in the temporary backup directory.
if ( ! empty( $options['hook_extra']['temp_backup'] ) ) {
// Delete the backup on `shutdown` to avoid a PHP timeout.
add_action( 'shutdown', array( $this, 'delete_temp_backup' ), 100, 0 );
}
if ( ! $options['is_multi'] ) {
/**
* Fires when the upgrader process is complete.
*
* See also {@see 'upgrader_package_options'}.
*
* @since 3.6.0
* @since 3.7.0 Added to WP_Upgrader::run().
* @since 4.6.0 `$translations` was added as a possible argument to `$hook_extra`.
*
* @param WP_Upgrader $upgrader WP_Upgrader instance. In other contexts this might be a
* Theme_Upgrader, Plugin_Upgrader, Core_Upgrade, or Language_Pack_Upgrader instance.
* @param array $hook_extra {
* Array of bulk item update data.
*
* @type string $action Type of action. Default 'update'.
* @type string $type Type of update process. Accepts 'plugin', 'theme', 'translation', or 'core'.
* @type bool $bulk Whether the update process is a bulk update. Default true.
* @type array $plugins Array of the basename paths of the plugins' main files.
* @type array $themes The theme slugs.
* @type array $translations {
* Array of translations update data.
*
* @type string $language The locale the translation is for.
* @type string $type Type of translation. Accepts 'plugin', 'theme', or 'core'.
* @type string $slug Text domain the translation is for. The slug of a theme/plugin or
* 'default' for core translations.
* @type string $version The version of a theme, plugin, or core.
* }
* }
*/
do_action( 'upgrader_process_complete', $this, $options['hook_extra'] );
$this->skin->footer();
}
return $result;
}
/**
* Toggles maintenance mode for the site.
*
* Creates/deletes the maintenance file to enable/disable maintenance mode.
*
* @since 2.8.0
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*
* @param bool $enable True to enable maintenance mode, false to disable.
*/
public function maintenance_mode( $enable = false ) {
global $wp_filesystem;
if ( ! $wp_filesystem ) {
if ( ! function_exists( 'WP_Filesystem' ) ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
}
ob_start();
$credentials = request_filesystem_credentials( '' );
ob_end_clean();
if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
wp_trigger_error( __FUNCTION__, __( 'Could not access filesystem.' ) );
return;
}
}
$file = $wp_filesystem->abspath() . '.maintenance';
if ( $enable ) {
if ( ! wp_doing_cron() ) {
$this->skin->feedback( 'maintenance_start' );
}
// Create maintenance file to signal that we are upgrading.
$maintenance_string = '';
$wp_filesystem->delete( $file );
$wp_filesystem->put_contents( $file, $maintenance_string, FS_CHMOD_FILE );
} elseif ( $wp_filesystem->exists( $file ) ) {
if ( ! wp_doing_cron() ) {
$this->skin->feedback( 'maintenance_end' );
}
$wp_filesystem->delete( $file );
}
}
/**
* Creates a lock using WordPress options.
*
* @since 4.5.0
*
* @global wpdb $wpdb The WordPress database abstraction object.
*
* @param string $lock_name The name of this unique lock.
* @param int $release_timeout Optional. The duration in seconds to respect an existing lock.
* Default: 1 hour.
* @return bool False if a lock couldn't be created or if the lock is still valid. True otherwise.
*/
public static function create_lock( $lock_name, $release_timeout = null ) {
global $wpdb;
if ( ! $release_timeout ) {
$release_timeout = HOUR_IN_SECONDS;
}
$lock_option = $lock_name . '.lock';
// Try to lock.
$lock_result = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO `$wpdb->options` ( `option_name`, `option_value`, `autoload` ) VALUES (%s, %s, 'off') /* LOCK */", $lock_option, time() ) );
if ( ! $lock_result ) {
$lock_result = get_option( $lock_option );
// If a lock couldn't be created, and there isn't a lock, bail.
if ( ! $lock_result ) {
return false;
}
// Check to see if the lock is still valid. If it is, bail.
if ( $lock_result > ( time() - $release_timeout ) ) {
return false;
}
// There must exist an expired lock, clear it and re-gain it.
WP_Upgrader::release_lock( $lock_name );
return WP_Upgrader::create_lock( $lock_name, $release_timeout );
}
// Update the lock, as by this point we've definitely got a lock, just need to fire the actions.
update_option( $lock_option, time(), false );
return true;
}
/**
* Releases an upgrader lock.
*
* @since 4.5.0
*
* @see WP_Upgrader::create_lock()
*
* @param string $lock_name The name of this unique lock.
* @return bool True if the lock was successfully released. False on failure.
*/
public static function release_lock( $lock_name ) {
return delete_option( $lock_name . '.lock' );
}
/**
* Moves the plugin or theme being updated into a temporary backup directory.
*
* @since 6.3.0
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*
* @param string[] $args {
* Array of data for the temporary backup.
*
* @type string $slug Plugin or theme slug.
* @type string $src Path to the root directory for plugins or themes.
* @type string $dir Destination subdirectory name. Accepts 'plugins' or 'themes'.
* }
*
* @return bool|WP_Error True on success, false on early exit, otherwise WP_Error.
*/
public function move_to_temp_backup_dir( $args ) {
global $wp_filesystem;
if ( empty( $args['slug'] ) || empty( $args['src'] ) || empty( $args['dir'] ) ) {
return false;
}
/*
* Skip any plugin that has "." as its slug.
* A slug of "." will result in a `$src` value ending in a period.
*
* On Windows, this will cause the 'plugins' folder to be moved,
* and will cause a failure when attempting to call `mkdir()`.
*/
if ( '.' === $args['slug'] ) {
return false;
}
if ( ! $wp_filesystem->wp_content_dir() ) {
return new WP_Error( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
}
$dest_dir = $wp_filesystem->wp_content_dir() . 'upgrade-temp-backup/';
$sub_dir = $dest_dir . $args['dir'] . '/';
// Create the temporary backup directory if it does not exist.
if ( ! $wp_filesystem->is_dir( $sub_dir ) ) {
if ( ! $wp_filesystem->is_dir( $dest_dir ) ) {
$wp_filesystem->mkdir( $dest_dir, FS_CHMOD_DIR );
}
if ( ! $wp_filesystem->mkdir( $sub_dir, FS_CHMOD_DIR ) ) {
// Could not create the backup directory.
return new WP_Error( 'fs_temp_backup_mkdir', $this->strings['temp_backup_mkdir_failed'] );
}
}
$src_dir = $wp_filesystem->find_folder( $args['src'] );
$src = trailingslashit( $src_dir ) . $args['slug'];
$dest = $dest_dir . trailingslashit( $args['dir'] ) . $args['slug'];
// Delete the temporary backup directory if it already exists.
if ( $wp_filesystem->is_dir( $dest ) ) {
$wp_filesystem->delete( $dest, true );
}
// Move to the temporary backup directory.
$result = move_dir( $src, $dest, true );
if ( is_wp_error( $result ) ) {
return new WP_Error( 'fs_temp_backup_move', $this->strings['temp_backup_move_failed'] );
}
return true;
}
/**
* Restores the plugin or theme from temporary backup.
*
* @since 6.3.0
* @since 6.6.0 Added the `$temp_backups` parameter.
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*
* @param array[] $temp_backups {
* Optional. An array of temporary backups.
*
* @type array ...$0 {
* Information about the backup.
*
* @type string $dir The temporary backup location in the upgrade-temp-backup directory.
* @type string $slug The item's slug.
* @type string $src The directory where the original is stored. For example, `WP_PLUGIN_DIR`.
* }
* }
* @return bool|WP_Error True on success, false on early exit, otherwise WP_Error.
*/
public function restore_temp_backup( array $temp_backups = array() ) {
global $wp_filesystem;
$errors = new WP_Error();
if ( empty( $temp_backups ) ) {
$temp_backups = $this->temp_restores;
}
foreach ( $temp_backups as $args ) {
if ( empty( $args['slug'] ) || empty( $args['src'] ) || empty( $args['dir'] ) ) {
return false;
}
if ( ! $wp_filesystem->wp_content_dir() ) {
$errors->add( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
return $errors;
}
$src = $wp_filesystem->wp_content_dir() . 'upgrade-temp-backup/' . $args['dir'] . '/' . $args['slug'];
$dest_dir = $wp_filesystem->find_folder( $args['src'] );
$dest = trailingslashit( $dest_dir ) . $args['slug'];
if ( $wp_filesystem->is_dir( $src ) ) {
// Cleanup.
if ( $wp_filesystem->is_dir( $dest ) && ! $wp_filesystem->delete( $dest, true ) ) {
$errors->add(
'fs_temp_backup_delete',
sprintf( $this->strings['temp_backup_restore_failed'], $args['slug'] )
);
continue;
}
// Move it.
$result = move_dir( $src, $dest, true );
if ( is_wp_error( $result ) ) {
$errors->add(
'fs_temp_backup_delete',
sprintf( $this->strings['temp_backup_restore_failed'], $args['slug'] )
);
continue;
}
}
}
return $errors->has_errors() ? $errors : true;
}
/**
* Deletes a temporary backup.
*
* @since 6.3.0
* @since 6.6.0 Added the `$temp_backups` parameter.
*
* @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
*
* @param array[] $temp_backups {
* Optional. An array of temporary backups.
*
* @type array ...$0 {
* Information about the backup.
*
* @type string $dir The temporary backup location in the upgrade-temp-backup directory.
* @type string $slug The item's slug.
* @type string $src The directory where the original is stored. For example, `WP_PLUGIN_DIR`.
* }
* }
* @return bool|WP_Error True on success, false on early exit, otherwise WP_Error.
*/
public function delete_temp_backup( array $temp_backups = array() ) {
global $wp_filesystem;
$errors = new WP_Error();
if ( empty( $temp_backups ) ) {
$temp_backups = $this->temp_backups;
}
foreach ( $temp_backups as $args ) {
if ( empty( $args['slug'] ) || empty( $args['dir'] ) ) {
return false;
}
if ( ! $wp_filesystem->wp_content_dir() ) {
$errors->add( 'fs_no_content_dir', $this->strings['fs_no_content_dir'] );
return $errors;
}
$temp_backup_dir = $wp_filesystem->wp_content_dir() . "upgrade-temp-backup/{$args['dir']}/{$args['slug']}";
if ( ! $wp_filesystem->delete( $temp_backup_dir, true ) ) {
$errors->add(
'temp_backup_delete_failed',
sprintf( $this->strings['temp_backup_delete_failed'], $args['slug'] )
);
continue;
}
}
return $errors->has_errors() ? $errors : true;
}
}
/** Plugin_Upgrader class */
require_once ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php';
/** Theme_Upgrader class */
require_once ABSPATH . 'wp-admin/includes/class-theme-upgrader.php';
/** Language_Pack_Upgrader class */
require_once ABSPATH . 'wp-admin/includes/class-language-pack-upgrader.php';
/** Core_Upgrader class */
require_once ABSPATH . 'wp-admin/includes/class-core-upgrader.php';
/** File_Upload_Upgrader class */
require_once ABSPATH . 'wp-admin/includes/class-file-upload-upgrader.php';
/** WP_Automatic_Updater class */
require_once ABSPATH . 'wp-admin/includes/class-wp-automatic-updater.php';
class-plugin-upgrader-skin.php 0000644 00000006316 15207570471 0012445 0 ustar 00 '',
'plugin' => '',
'nonce' => '',
'title' => __( 'Update Plugin' ),
);
$args = wp_parse_args( $args, $defaults );
$this->plugin = $args['plugin'];
$this->plugin_active = is_plugin_active( $this->plugin );
$this->plugin_network_active = is_plugin_active_for_network( $this->plugin );
parent::__construct( $args );
}
/**
* Performs an action following a single plugin update.
*
* @since 2.8.0
*/
public function after() {
$this->plugin = $this->upgrader->plugin_info();
if ( ! empty( $this->plugin ) && ! is_wp_error( $this->result ) && $this->plugin_active ) {
// Currently used only when JS is off for a single plugin update?
printf(
'',
esc_attr__( 'Update progress' ),
wp_nonce_url( 'update.php?action=activate-plugin&networkwide=' . $this->plugin_network_active . '&plugin=' . urlencode( $this->plugin ), 'activate-plugin_' . $this->plugin )
);
}
$this->decrement_update_count( 'plugin' );
$update_actions = array(
'activate_plugin' => sprintf(
'%s',
wp_nonce_url( 'plugins.php?action=activate&plugin=' . urlencode( $this->plugin ), 'activate-plugin_' . $this->plugin ),
__( 'Activate Plugin' )
),
'plugins_page' => sprintf(
'%s',
self_admin_url( 'plugins.php' ),
__( 'Go to Plugins page' )
),
);
if ( $this->plugin_active || ! $this->result || is_wp_error( $this->result ) || ! current_user_can( 'activate_plugin', $this->plugin ) ) {
unset( $update_actions['activate_plugin'] );
}
/**
* Filters the list of action links available following a single plugin update.
*
* @since 2.7.0
*
* @param string[] $update_actions Array of plugin action links.
* @param string $plugin Path to the plugin file relative to the plugins directory.
*/
$update_actions = apply_filters( 'update_plugin_complete_actions', $update_actions, $this->plugin );
if ( ! empty( $update_actions ) ) {
$this->feedback( implode( ' | ', (array) $update_actions ) );
}
}
}
menu.php 0000644 00000022622 15207570471 0006235 0 ustar 00 $sub ) {
foreach ( $sub as $index => $data ) {
if ( ! current_user_can( $data[1] ) ) {
unset( $submenu[ $parent ][ $index ] );
$_wp_submenu_nopriv[ $parent ][ $data[2] ] = true;
}
}
unset( $index, $data );
if ( empty( $submenu[ $parent ] ) ) {
unset( $submenu[ $parent ] );
}
}
unset( $sub, $parent );
/*
* Loop over the top-level menu.
* Menus for which the original parent is not accessible due to lack of privileges
* will have the next submenu in line be assigned as the new menu parent.
*/
foreach ( $menu as $id => $data ) {
if ( empty( $submenu[ $data[2] ] ) ) {
continue;
}
$subs = $submenu[ $data[2] ];
$first_sub = reset( $subs );
$old_parent = $data[2];
$new_parent = $first_sub[2];
/*
* If the first submenu is not the same as the assigned parent,
* make the first submenu the new parent.
*/
if ( $new_parent !== $old_parent ) {
$_wp_real_parent_file[ $old_parent ] = $new_parent;
$menu[ $id ][2] = $new_parent;
foreach ( $submenu[ $old_parent ] as $index => $data ) {
$submenu[ $new_parent ][ $index ] = $submenu[ $old_parent ][ $index ];
unset( $submenu[ $old_parent ][ $index ] );
}
unset( $submenu[ $old_parent ], $index );
if ( isset( $_wp_submenu_nopriv[ $old_parent ] ) ) {
$_wp_submenu_nopriv[ $new_parent ] = $_wp_submenu_nopriv[ $old_parent ];
}
}
}
unset( $id, $data, $subs, $first_sub, $old_parent, $new_parent );
if ( is_network_admin() ) {
/**
* Fires before the administration menu loads in the Network Admin.
*
* @since 3.1.0
*
* @param string $context Empty context.
*/
do_action( 'network_admin_menu', '' );
} elseif ( is_user_admin() ) {
/**
* Fires before the administration menu loads in the User Admin.
*
* @since 3.1.0
*
* @param string $context Empty context.
*/
do_action( 'user_admin_menu', '' );
} else {
/**
* Fires before the administration menu loads in the admin.
*
* @since 1.5.0
*
* @param string $context Empty context.
*/
do_action( 'admin_menu', '' );
}
/*
* Remove menus that have no accessible submenus and require privileges
* that the user does not have. Run re-parent loop again.
*/
foreach ( $menu as $id => $data ) {
if ( ! current_user_can( $data[1] ) ) {
$_wp_menu_nopriv[ $data[2] ] = true;
}
/*
* If there is only one submenu and it is has same destination as the parent,
* remove the submenu.
*/
if ( ! empty( $submenu[ $data[2] ] ) && 1 === count( $submenu[ $data[2] ] ) ) {
$subs = $submenu[ $data[2] ];
$first_sub = reset( $subs );
if ( $data[2] === $first_sub[2] ) {
unset( $submenu[ $data[2] ] );
}
}
// If submenu is empty...
if ( empty( $submenu[ $data[2] ] ) ) {
// And user doesn't have privs, remove menu.
if ( isset( $_wp_menu_nopriv[ $data[2] ] ) ) {
unset( $menu[ $id ] );
}
}
}
unset( $id, $data, $subs, $first_sub );
/**
* Adds a CSS class to a string.
*
* @since 2.7.0
*
* @param string $class_to_add The CSS class to add.
* @param string $classes The string to add the CSS class to.
* @return string The string with the CSS class added.
*/
function add_cssclass( $class_to_add, $classes ) {
if ( empty( $classes ) ) {
return $class_to_add;
}
return $classes . ' ' . $class_to_add;
}
/**
* Adds CSS classes for top-level administration menu items.
*
* The list of added classes includes `.menu-top-first` and `.menu-top-last`.
*
* @since 2.7.0
*
* @param array $menu The array of administration menu items.
* @return array The array of administration menu items with the CSS classes added.
*/
function add_menu_classes( $menu ) {
$first_item = false;
$last_order = false;
$items_count = count( $menu );
$i = 0;
foreach ( $menu as $order => $top ) {
++$i;
if ( 0 === $order ) { // Dashboard is always shown/single.
$menu[0][4] = add_cssclass( 'menu-top-first', $top[4] );
$last_order = 0;
continue;
}
if ( str_starts_with( $top[2], 'separator' ) && false !== $last_order ) { // If separator.
$first_item = true;
$classes = $menu[ $last_order ][4];
$menu[ $last_order ][4] = add_cssclass( 'menu-top-last', $classes );
continue;
}
if ( $first_item ) {
$first_item = false;
$classes = $menu[ $order ][4];
$menu[ $order ][4] = add_cssclass( 'menu-top-first', $classes );
}
if ( $i === $items_count ) { // Last item.
$classes = $menu[ $order ][4];
$menu[ $order ][4] = add_cssclass( 'menu-top-last', $classes );
}
$last_order = $order;
}
/**
* Filters administration menu array with classes added for top-level items.
*
* @since 2.7.0
*
* @param array $menu Associative array of administration menu items.
*/
return apply_filters( 'add_menu_classes', $menu );
}
uksort( $menu, 'strnatcasecmp' ); // Make it all pretty.
/**
* Filters whether to enable custom ordering of the administration menu.
*
* See the {@see 'menu_order'} filter for reordering menu items.
*
* @since 2.8.0
*
* @param bool $custom Whether custom ordering is enabled. Default false.
*/
if ( apply_filters( 'custom_menu_order', false ) ) {
$menu_order = array();
foreach ( $menu as $menu_item ) {
$menu_order[] = $menu_item[2];
}
unset( $menu_item );
$default_menu_order = $menu_order;
/**
* Filters the order of administration menu items.
*
* A truthy value must first be passed to the {@see 'custom_menu_order'} filter
* for this filter to work. Use the following to enable custom menu ordering:
*
* add_filter( 'custom_menu_order', '__return_true' );
*
* @since 2.8.0
*
* @param array $menu_order An ordered array of menu items.
*/
$menu_order = apply_filters( 'menu_order', $menu_order );
$menu_order = array_flip( $menu_order );
$default_menu_order = array_flip( $default_menu_order );
/**
* @global array $menu_order
* @global array $default_menu_order
*
* @param array $a
* @param array $b
* @return int
*/
function sort_menu( $a, $b ) {
global $menu_order, $default_menu_order;
$a = $a[2];
$b = $b[2];
if ( isset( $menu_order[ $a ] ) && ! isset( $menu_order[ $b ] ) ) {
return -1;
} elseif ( ! isset( $menu_order[ $a ] ) && isset( $menu_order[ $b ] ) ) {
return 1;
} elseif ( isset( $menu_order[ $a ] ) && isset( $menu_order[ $b ] ) ) {
if ( $menu_order[ $a ] === $menu_order[ $b ] ) {
return 0;
}
return ( $menu_order[ $a ] < $menu_order[ $b ] ) ? -1 : 1;
} else {
return ( $default_menu_order[ $a ] <= $default_menu_order[ $b ] ) ? -1 : 1;
}
}
usort( $menu, 'sort_menu' );
unset( $menu_order, $default_menu_order );
}
// Prevent adjacent separators.
$prev_menu_was_separator = false;
foreach ( $menu as $id => $data ) {
if ( false === stristr( $data[4], 'wp-menu-separator' ) ) {
// This item is not a separator, so falsey the toggler and do nothing.
$prev_menu_was_separator = false;
} else {
// The previous item was a separator, so unset this one.
if ( true === $prev_menu_was_separator ) {
unset( $menu[ $id ] );
}
// This item is a separator, so truthy the toggler and move on.
$prev_menu_was_separator = true;
}
}
unset( $id, $data, $prev_menu_was_separator );
// Remove the last menu item if it is a separator.
$last_menu_key = array_keys( $menu );
$last_menu_key = array_pop( $last_menu_key );
if ( ! empty( $menu ) && 'wp-menu-separator' === $menu[ $last_menu_key ][4] ) {
unset( $menu[ $last_menu_key ] );
}
unset( $last_menu_key );
if ( ! user_can_access_admin_page() ) {
/**
* Fires when access to an admin page is denied.
*
* @since 2.5.0
*/
do_action( 'admin_page_access_denied' );
wp_die( __( 'Sorry, you are not allowed to access this page.' ), 403 );
}
$menu = add_menu_classes( $menu );
class-wp-ms-themes-list-table.php 0000644 00000067426 15207570471 0012773 0 ustar 00 'themes',
'screen' => isset( $args['screen'] ) ? $args['screen'] : null,
)
);
$status = isset( $_REQUEST['theme_status'] ) ? $_REQUEST['theme_status'] : 'all';
if ( ! in_array( $status, array( 'all', 'enabled', 'disabled', 'upgrade', 'search', 'broken', 'auto-update-enabled', 'auto-update-disabled' ), true ) ) {
$status = 'all';
}
$page = $this->get_pagenum();
$this->is_site_themes = ( 'site-themes-network' === $this->screen->id ) ? true : false;
if ( $this->is_site_themes ) {
$this->site_id = isset( $_REQUEST['id'] ) ? (int) $_REQUEST['id'] : 0;
}
$this->show_autoupdates = wp_is_auto_update_enabled_for_type( 'theme' ) &&
! $this->is_site_themes && current_user_can( 'update_themes' );
}
/**
* @return array
*/
protected function get_table_classes() {
// @todo Remove and add CSS for .themes.
return array( 'widefat', 'plugins' );
}
/**
* @return bool
*/
public function ajax_user_can() {
if ( $this->is_site_themes ) {
return current_user_can( 'manage_sites' );
} else {
return current_user_can( 'manage_network_themes' );
}
}
/**
* @global string $status
* @global array $totals
* @global int $page
* @global string $orderby
* @global string $order
* @global string $s
*/
public function prepare_items() {
global $status, $totals, $page, $orderby, $order, $s;
$orderby = ! empty( $_REQUEST['orderby'] ) ? sanitize_text_field( $_REQUEST['orderby'] ) : '';
$order = ! empty( $_REQUEST['order'] ) ? sanitize_text_field( $_REQUEST['order'] ) : '';
$s = ! empty( $_REQUEST['s'] ) ? sanitize_text_field( $_REQUEST['s'] ) : '';
$themes = array(
/**
* Filters the full array of WP_Theme objects to list in the Multisite
* themes list table.
*
* @since 3.1.0
*
* @param WP_Theme[] $all Array of WP_Theme objects to display in the list table.
*/
'all' => apply_filters( 'all_themes', wp_get_themes() ),
'search' => array(),
'enabled' => array(),
'disabled' => array(),
'upgrade' => array(),
'broken' => $this->is_site_themes ? array() : wp_get_themes( array( 'errors' => true ) ),
);
if ( $this->show_autoupdates ) {
$auto_updates = (array) get_site_option( 'auto_update_themes', array() );
$themes['auto-update-enabled'] = array();
$themes['auto-update-disabled'] = array();
}
if ( $this->is_site_themes ) {
$themes_per_page = $this->get_items_per_page( 'site_themes_network_per_page' );
$allowed_where = 'site';
} else {
$themes_per_page = $this->get_items_per_page( 'themes_network_per_page' );
$allowed_where = 'network';
}
$current = get_site_transient( 'update_themes' );
$maybe_update = current_user_can( 'update_themes' ) && ! $this->is_site_themes && $current;
foreach ( (array) $themes['all'] as $key => $theme ) {
if ( $this->is_site_themes && $theme->is_allowed( 'network' ) ) {
unset( $themes['all'][ $key ] );
continue;
}
if ( $maybe_update && isset( $current->response[ $key ] ) ) {
$themes['all'][ $key ]->update = true;
$themes['upgrade'][ $key ] = $themes['all'][ $key ];
}
$filter = $theme->is_allowed( $allowed_where, $this->site_id ) ? 'enabled' : 'disabled';
$themes[ $filter ][ $key ] = $themes['all'][ $key ];
$theme_data = array(
'update_supported' => isset( $theme->update_supported ) ? $theme->update_supported : true,
);
// Extra info if known. array_merge() ensures $theme_data has precedence if keys collide.
if ( isset( $current->response[ $key ] ) ) {
$theme_data = array_merge( (array) $current->response[ $key ], $theme_data );
} elseif ( isset( $current->no_update[ $key ] ) ) {
$theme_data = array_merge( (array) $current->no_update[ $key ], $theme_data );
} else {
$theme_data['update_supported'] = false;
}
$theme->update_supported = $theme_data['update_supported'];
/*
* Create the expected payload for the auto_update_theme filter, this is the same data
* as contained within $updates or $no_updates but used when the Theme is not known.
*/
$filter_payload = array(
'theme' => $key,
'new_version' => '',
'url' => '',
'package' => '',
'requires' => '',
'requires_php' => '',
);
$filter_payload = (object) array_merge( $filter_payload, array_intersect_key( $theme_data, $filter_payload ) );
$auto_update_forced = wp_is_auto_update_forced_for_item( 'theme', null, $filter_payload );
if ( ! is_null( $auto_update_forced ) ) {
$theme->auto_update_forced = $auto_update_forced;
}
if ( $this->show_autoupdates ) {
$enabled = in_array( $key, $auto_updates, true ) && $theme->update_supported;
if ( isset( $theme->auto_update_forced ) ) {
$enabled = (bool) $theme->auto_update_forced;
}
if ( $enabled ) {
$themes['auto-update-enabled'][ $key ] = $theme;
} else {
$themes['auto-update-disabled'][ $key ] = $theme;
}
}
}
if ( $s ) {
$status = 'search';
$themes['search'] = array_filter( array_merge( $themes['all'], $themes['broken'] ), array( $this, '_search_callback' ) );
}
$totals = array();
$js_themes = array();
foreach ( $themes as $type => $list ) {
$totals[ $type ] = count( $list );
$js_themes[ $type ] = array_keys( $list );
}
if ( empty( $themes[ $status ] ) && ! in_array( $status, array( 'all', 'search' ), true ) ) {
$status = 'all';
}
$this->items = $themes[ $status ];
WP_Theme::sort_by_name( $this->items );
$this->has_items = ! empty( $themes['all'] );
$total_this_page = $totals[ $status ];
wp_localize_script(
'updates',
'_wpUpdatesItemCounts',
array(
'themes' => $js_themes,
'totals' => wp_get_update_data(),
)
);
if ( $orderby ) {
$orderby = ucfirst( $orderby );
$order = strtoupper( $order );
if ( 'Name' === $orderby ) {
if ( 'ASC' === $order ) {
$this->items = array_reverse( $this->items );
}
} else {
uasort( $this->items, array( $this, '_order_callback' ) );
}
}
$start = ( $page - 1 ) * $themes_per_page;
if ( $total_this_page > $themes_per_page ) {
$this->items = array_slice( $this->items, $start, $themes_per_page, true );
}
$this->set_pagination_args(
array(
'total_items' => $total_this_page,
'per_page' => $themes_per_page,
)
);
}
/**
* @param WP_Theme $theme
* @return bool
*/
public function _search_callback( $theme ) {
static $term = null;
if ( is_null( $term ) ) {
$term = wp_unslash( $_REQUEST['s'] );
}
foreach ( array( 'Name', 'Description', 'Author', 'Author', 'AuthorURI' ) as $field ) {
// Don't mark up; Do translate.
if ( false !== stripos( $theme->display( $field, false, true ), $term ) ) {
return true;
}
}
if ( false !== stripos( $theme->get_stylesheet(), $term ) ) {
return true;
}
if ( false !== stripos( $theme->get_template(), $term ) ) {
return true;
}
return false;
}
// Not used by any core columns.
/**
* @global string $orderby
* @global string $order
* @param array $theme_a
* @param array $theme_b
* @return int
*/
public function _order_callback( $theme_a, $theme_b ) {
global $orderby, $order;
$a = $theme_a[ $orderby ];
$b = $theme_b[ $orderby ];
if ( $a === $b ) {
return 0;
}
if ( 'DESC' === $order ) {
return ( $a < $b ) ? 1 : -1;
} else {
return ( $a < $b ) ? -1 : 1;
}
}
/**
*/
public function no_items() {
if ( $this->has_items ) {
_e( 'No themes found.' );
} else {
_e( 'No themes are currently available.' );
}
}
/**
* @return string[] Array of column titles keyed by their column name.
*/
public function get_columns() {
$columns = array(
'cb' => '',
'name' => __( 'Theme' ),
'description' => __( 'Description' ),
);
if ( $this->show_autoupdates ) {
$columns['auto-updates'] = __( 'Automatic Updates' );
}
return $columns;
}
/**
* @return array
*/
protected function get_sortable_columns() {
return array(
'name' => array( 'name', false, __( 'Theme' ), __( 'Table ordered by Theme Name.' ), 'asc' ),
);
}
/**
* Gets the name of the primary column.
*
* @since 4.3.0
*
* @return string Unalterable name of the primary column name, in this case, 'name'.
*/
protected function get_primary_column_name() {
return 'name';
}
/**
* @global array $totals
* @global string $status
* @return array
*/
protected function get_views() {
global $totals, $status;
$status_links = array();
foreach ( $totals as $type => $count ) {
if ( ! $count ) {
continue;
}
switch ( $type ) {
case 'all':
/* translators: %s: Number of themes. */
$text = _nx(
'All (%s)',
'All (%s)',
$count,
'themes'
);
break;
case 'enabled':
/* translators: %s: Number of themes. */
$text = _nx(
'Enabled (%s)',
'Enabled (%s)',
$count,
'themes'
);
break;
case 'disabled':
/* translators: %s: Number of themes. */
$text = _nx(
'Disabled (%s)',
'Disabled (%s)',
$count,
'themes'
);
break;
case 'upgrade':
/* translators: %s: Number of themes. */
$text = _nx(
'Update Available (%s)',
'Update Available (%s)',
$count,
'themes'
);
break;
case 'broken':
/* translators: %s: Number of themes. */
$text = _nx(
'Broken (%s)',
'Broken (%s)',
$count,
'themes'
);
break;
case 'auto-update-enabled':
/* translators: %s: Number of themes. */
$text = _n(
'Auto-updates Enabled (%s)',
'Auto-updates Enabled (%s)',
$count
);
break;
case 'auto-update-disabled':
/* translators: %s: Number of themes. */
$text = _n(
'Auto-updates Disabled (%s)',
'Auto-updates Disabled (%s)',
$count
);
break;
}
if ( $this->is_site_themes ) {
$url = 'site-themes.php?id=' . $this->site_id;
} else {
$url = 'themes.php';
}
if ( 'search' !== $type ) {
$status_links[ $type ] = array(
'url' => esc_url( add_query_arg( 'theme_status', $type, $url ) ),
'label' => sprintf( $text, number_format_i18n( $count ) ),
'current' => $type === $status,
);
}
}
return $this->get_views_links( $status_links );
}
/**
* @global string $status
*
* @return array
*/
protected function get_bulk_actions() {
global $status;
$actions = array();
if ( 'enabled' !== $status ) {
$actions['enable-selected'] = $this->is_site_themes ? __( 'Enable' ) : __( 'Network Enable' );
}
if ( 'disabled' !== $status ) {
$actions['disable-selected'] = $this->is_site_themes ? __( 'Disable' ) : __( 'Network Disable' );
}
if ( ! $this->is_site_themes ) {
if ( current_user_can( 'update_themes' ) ) {
$actions['update-selected'] = __( 'Update' );
}
if ( current_user_can( 'delete_themes' ) ) {
$actions['delete-selected'] = __( 'Delete' );
}
}
if ( $this->show_autoupdates ) {
if ( 'auto-update-enabled' !== $status ) {
$actions['enable-auto-update-selected'] = __( 'Enable Auto-updates' );
}
if ( 'auto-update-disabled' !== $status ) {
$actions['disable-auto-update-selected'] = __( 'Disable Auto-updates' );
}
}
return $actions;
}
/**
* Generates the list table rows.
*
* @since 3.1.0
*/
public function display_rows() {
foreach ( $this->items as $theme ) {
$this->single_row( $theme );
}
}
/**
* Handles the checkbox column output.
*
* @since 4.3.0
* @since 5.9.0 Renamed `$theme` to `$item` to match parent class for PHP 8 named parameter support.
*
* @param WP_Theme $item The current WP_Theme object.
*/
public function column_cb( $item ) {
// Restores the more descriptive, specific name for use within this method.
$theme = $item;
$checkbox_id = 'checkbox_' . md5( $theme->get( 'Name' ) );
?>
is_site_themes ) {
$url = "site-themes.php?id={$this->site_id}&";
$allowed = $theme->is_allowed( 'site', $this->site_id );
} else {
$url = 'themes.php?';
$allowed = $theme->is_allowed( 'network' );
}
// Pre-order.
$actions = array(
'enable' => '',
'disable' => '',
'delete' => '',
);
$stylesheet = $theme->get_stylesheet();
$theme_key = urlencode( $stylesheet );
if ( ! $allowed ) {
if ( ! $theme->errors() ) {
$url = add_query_arg(
array(
'action' => 'enable',
'theme' => $theme_key,
'paged' => $page,
's' => $s,
),
$url
);
if ( $this->is_site_themes ) {
/* translators: %s: Theme name. */
$aria_label = sprintf( __( 'Enable %s' ), $theme->display( 'Name' ) );
} else {
/* translators: %s: Theme name. */
$aria_label = sprintf( __( 'Network Enable %s' ), $theme->display( 'Name' ) );
}
$actions['enable'] = sprintf(
'%s',
esc_url( wp_nonce_url( $url, 'enable-theme_' . $stylesheet ) ),
esc_attr( $aria_label ),
( $this->is_site_themes ? __( 'Enable' ) : __( 'Network Enable' ) )
);
}
} else {
$url = add_query_arg(
array(
'action' => 'disable',
'theme' => $theme_key,
'paged' => $page,
's' => $s,
),
$url
);
if ( $this->is_site_themes ) {
/* translators: %s: Theme name. */
$aria_label = sprintf( __( 'Disable %s' ), $theme->display( 'Name' ) );
} else {
/* translators: %s: Theme name. */
$aria_label = sprintf( __( 'Network Disable %s' ), $theme->display( 'Name' ) );
}
$actions['disable'] = sprintf(
'%s',
esc_url( wp_nonce_url( $url, 'disable-theme_' . $stylesheet ) ),
esc_attr( $aria_label ),
( $this->is_site_themes ? __( 'Disable' ) : __( 'Network Disable' ) )
);
}
if ( ! $allowed && ! $this->is_site_themes
&& current_user_can( 'delete_themes' )
&& get_option( 'stylesheet' ) !== $stylesheet
&& get_option( 'template' ) !== $stylesheet
) {
$url = add_query_arg(
array(
'action' => 'delete-selected',
'checked[]' => $theme_key,
'theme_status' => $context,
'paged' => $page,
's' => $s,
),
'themes.php'
);
/* translators: %s: Theme name. */
$aria_label = sprintf( _x( 'Delete %s', 'theme' ), $theme->display( 'Name' ) );
$actions['delete'] = sprintf(
'%s',
esc_url( wp_nonce_url( $url, 'bulk-themes' ) ),
esc_attr( $aria_label ),
__( 'Delete' )
);
}
/**
* Filters the action links displayed for each theme in the Multisite
* themes list table.
*
* The action links displayed are determined by the theme's status, and
* which Multisite themes list table is being displayed - the Network
* themes list table (themes.php), which displays all installed themes,
* or the Site themes list table (site-themes.php), which displays the
* non-network enabled themes when editing a site in the Network admin.
*
* The default action links for the Network themes list table include
* 'Network Enable', 'Network Disable', and 'Delete'.
*
* The default action links for the Site themes list table include
* 'Enable', and 'Disable'.
*
* @since 2.8.0
*
* @param string[] $actions An array of action links.
* @param WP_Theme $theme The current WP_Theme object.
* @param string $context Status of the theme, one of 'all', 'enabled', or 'disabled'.
*/
$actions = apply_filters( 'theme_action_links', array_filter( $actions ), $theme, $context );
/**
* Filters the action links of a specific theme in the Multisite themes
* list table.
*
* The dynamic portion of the hook name, `$stylesheet`, refers to the
* directory name of the theme, which in most cases is synonymous
* with the template name.
*
* @since 3.1.0
*
* @param string[] $actions An array of action links.
* @param WP_Theme $theme The current WP_Theme object.
* @param string $context Status of the theme, one of 'all', 'enabled', or 'disabled'.
*/
$actions = apply_filters( "theme_action_links_{$stylesheet}", $actions, $theme, $context );
echo $this->row_actions( $actions, true );
}
/**
* Handles the description column output.
*
* @since 4.3.0
*
* @global string $status
* @global array $totals
*
* @param WP_Theme $theme The current WP_Theme object.
*/
public function column_description( $theme ) {
global $status, $totals;
if ( $theme->errors() ) {
$pre = 'broken' === $status ? '' . __( 'Broken Theme:' ) . ' ' : '';
wp_admin_notice(
$pre . $theme->errors()->get_error_message(),
array(
'type' => 'error',
'additional_classes' => 'inline',
)
);
}
if ( $this->is_site_themes ) {
$allowed = $theme->is_allowed( 'site', $this->site_id );
} else {
$allowed = $theme->is_allowed( 'network' );
}
$class = ! $allowed ? 'inactive' : 'active';
if ( ! empty( $totals['upgrade'] ) && ! empty( $theme->update ) ) {
$class .= ' update';
}
echo "" . $theme->display( 'Description' ) . "
$message
\n"; wp_ob_end_flush_all(); flush(); } /** * @since 2.8.0 * * @param string $content * @return string[] Array of function names. */ function wp_doc_link_parse( $content ) { if ( ! is_string( $content ) || empty( $content ) ) { return array(); } if ( ! function_exists( 'token_get_all' ) ) { return array(); } $tokens = token_get_all( $content ); $count = count( $tokens ); $functions = array(); $ignore_functions = array(); for ( $t = 0; $t < $count - 2; $t++ ) { if ( ! is_array( $tokens[ $t ] ) ) { continue; } if ( T_STRING === $tokens[ $t ][0] && ( '(' === $tokens[ $t + 1 ] || '(' === $tokens[ $t + 2 ] ) ) { // If it's a function or class defined locally, there's not going to be any docs available. if ( ( isset( $tokens[ $t - 2 ][1] ) && in_array( $tokens[ $t - 2 ][1], array( 'function', 'class' ), true ) ) || ( isset( $tokens[ $t - 2 ][0] ) && T_OBJECT_OPERATOR === $tokens[ $t - 1 ][0] ) ) { $ignore_functions[] = $tokens[ $t ][1]; } // Add this to our stack of unique references. $functions[] = $tokens[ $t ][1]; } } $functions = array_unique( $functions ); sort( $functions ); /** * Filters the list of functions and classes to be ignored from the documentation lookup. * * @since 2.8.0 * * @param string[] $ignore_functions Array of names of functions and classes to be ignored. */ $ignore_functions = apply_filters( 'documentation_ignore_functions', $ignore_functions ); $ignore_functions = array_unique( $ignore_functions ); $output = array(); foreach ( $functions as $function ) { if ( in_array( $function, $ignore_functions, true ) ) { continue; } $output[] = $function; } return $output; } /** * Saves option for number of rows when listing posts, pages, comments, etc. * * @since 2.8.0 */ function set_screen_options() { if ( ! isset( $_POST['wp_screen_options'] ) || ! is_array( $_POST['wp_screen_options'] ) ) { return; } check_admin_referer( 'screen-options-nonce', 'screenoptionnonce' ); $user = wp_get_current_user(); if ( ! $user ) { return; } $option = $_POST['wp_screen_options']['option']; $value = $_POST['wp_screen_options']['value']; if ( sanitize_key( $option ) !== $option ) { return; } $map_option = $option; $type = str_replace( 'edit_', '', $map_option ); $type = str_replace( '_per_page', '', $type ); if ( in_array( $type, get_taxonomies(), true ) ) { $map_option = 'edit_tags_per_page'; } elseif ( in_array( $type, get_post_types(), true ) ) { $map_option = 'edit_per_page'; } else { $option = str_replace( '-', '_', $option ); } switch ( $map_option ) { case 'edit_per_page': case 'users_per_page': case 'edit_comments_per_page': case 'upload_per_page': case 'edit_tags_per_page': case 'plugins_per_page': case 'export_personal_data_requests_per_page': case 'remove_personal_data_requests_per_page': // Network admin. case 'sites_network_per_page': case 'users_network_per_page': case 'site_users_network_per_page': case 'plugins_network_per_page': case 'themes_network_per_page': case 'site_themes_network_per_page': $value = (int) $value; if ( $value < 1 || $value > 999 ) { return; } break; default: $screen_option = false; if ( str_ends_with( $option, '_page' ) || 'layout_columns' === $option ) { /** * Filters a screen option value before it is set. * * The filter can also be used to modify non-standard `[items]_per_page` * settings. See the parent function for a full list of standard options. * * Returning false from the filter will skip saving the current option. * * @since 2.8.0 * @since 5.4.2 Only applied to options ending with '_page', * or the 'layout_columns' option. * * @see set_screen_options() * * @param mixed $screen_option The value to save instead of the option value. * Default false (to skip saving the current option). * @param string $option The option name. * @param int $value The option value. */ $screen_option = apply_filters( 'set-screen-option', $screen_option, $option, $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores } /** * Filters a screen option value before it is set. * * The dynamic portion of the hook name, `$option`, refers to the option name. * * Returning false from the filter will skip saving the current option. * * @since 5.4.2 * * @see set_screen_options() * * @param mixed $screen_option The value to save instead of the option value. * Default false (to skip saving the current option). * @param string $option The option name. * @param int $value The option value. */ $value = apply_filters( "set_screen_option_{$option}", $screen_option, $option, $value ); if ( false === $value ) { return; } break; } update_user_meta( $user->ID, $option, $value ); $url = remove_query_arg( array( 'pagenum', 'apage', 'paged' ), wp_get_referer() ); if ( isset( $_POST['mode'] ) ) { $url = add_query_arg( array( 'mode' => $_POST['mode'] ), $url ); } wp_safe_redirect( $url ); exit; } /** * Checks if rewrite rule for WordPress already exists in the IIS 7+ configuration file. * * @since 2.8.0 * * @param string $filename The file path to the configuration file. * @return bool */ function iis7_rewrite_rule_exists( $filename ) { if ( ! file_exists( $filename ) ) { return false; } if ( ! class_exists( 'DOMDocument', false ) ) { return false; } $doc = new DOMDocument(); if ( $doc->load( $filename ) === false ) { return false; } $xpath = new DOMXPath( $doc ); $rules = $xpath->query( '/configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'wordpress\')] | /configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'WordPress\')]' ); if ( 0 === $rules->length ) { return false; } return true; } /** * Deletes WordPress rewrite rule from web.config file if it exists there. * * @since 2.8.0 * * @param string $filename Name of the configuration file. * @return bool */ function iis7_delete_rewrite_rule( $filename ) { // If configuration file does not exist then rules also do not exist, so there is nothing to delete. if ( ! file_exists( $filename ) ) { return true; } if ( ! class_exists( 'DOMDocument', false ) ) { return false; } $doc = new DOMDocument(); $doc->preserveWhiteSpace = false; if ( $doc->load( $filename ) === false ) { return false; } $xpath = new DOMXPath( $doc ); $rules = $xpath->query( '/configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'wordpress\')] | /configuration/system.webServer/rewrite/rules/rule[starts-with(@name,\'WordPress\')]' ); if ( $rules->length > 0 ) { $child = $rules->item( 0 ); $parent = $child->parentNode; $parent->removeChild( $child ); $doc->formatOutput = true; saveDomDocument( $doc, $filename ); } return true; } /** * Adds WordPress rewrite rule to the IIS 7+ configuration file. * * @since 2.8.0 * * @param string $filename The file path to the configuration file. * @param string $rewrite_rule The XML fragment with URL Rewrite rule. * @return bool */ function iis7_add_rewrite_rule( $filename, $rewrite_rule ) { if ( ! class_exists( 'DOMDocument', false ) ) { return false; } // If configuration file does not exist then we create one. if ( ! file_exists( $filename ) ) { $fp = fopen( $filename, 'w' ); fwrite( $fp, '
" . esc_html( $template ) . ''; } } /** * Prints out option HTML elements for the page parents drop-down. * * @since 1.5.0 * @since 4.4.0 `$post` argument was added. * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $default_page Optional. The default page ID to be pre-selected. Default 0. * @param int $parent_page Optional. The parent page ID. Default 0. * @param int $level Optional. Page depth level. Default 0. * @param int|WP_Post $post Post ID or WP_Post object. * @return void|false Void on success, false if the page has no children. */ function parent_dropdown( $default_page = 0, $parent_page = 0, $level = 0, $post = null ) { global $wpdb; $post = get_post( $post ); $items = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_parent, post_title FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'page' ORDER BY menu_order", $parent_page ) ); if ( $items ) { foreach ( $items as $item ) { // A page cannot be its own parent. if ( $post && $post->ID && (int) $item->ID === $post->ID ) { continue; } $pad = str_repeat( ' ', $level * 3 ); $selected = selected( $default_page, $item->ID, false ); echo "\n\t'; parent_dropdown( $default_page, $item->ID, $level + 1 ); } } else { return false; } } /** * Prints out option HTML elements for role selectors. * * @since 2.1.0 * * @param string $selected Slug for the role that should be already selected. */ function wp_dropdown_roles( $selected = '' ) { $r = ''; $editable_roles = array_reverse( get_editable_roles() ); foreach ( $editable_roles as $role => $details ) { $name = translate_user_role( $details['name'] ); // Preselect specified role. if ( $selected === $role ) { $r .= "\n\t"; } else { $r .= "\n\t"; } } echo $r; } /** * Outputs the form used by the importers to accept the data to be imported. * * @since 2.0.0 * * @param string $action The action attribute for the form. */ function wp_import_upload_form( $action ) { /** * Filters the maximum allowed upload size for import files. * * @since 2.3.0 * * @see wp_max_upload_size() * * @param int $max_upload_size Allowed upload size. Default 1 MB. */ $bytes = apply_filters( 'import_upload_size_limit', wp_max_upload_size() ); $size = size_format( $bytes ); $upload_dir = wp_upload_dir(); if ( ! empty( $upload_dir['error'] ) ) : $upload_directory_error = '
' . __( 'Before you can upload your import file, you will need to fix the following error:' ) . '
'; $upload_directory_error .= '' . $upload_dir['error'] . '
'; wp_admin_notice( $upload_directory_error, array( 'additional_classes' => array( 'error' ), 'paragraph_wrap' => false, ) ); else : ?> id ) ) { return; } $page = $screen->id; if ( ! isset( $wp_meta_boxes ) ) { $wp_meta_boxes = array(); } if ( ! isset( $wp_meta_boxes[ $page ] ) ) { $wp_meta_boxes[ $page ] = array(); } if ( ! isset( $wp_meta_boxes[ $page ][ $context ] ) ) { $wp_meta_boxes[ $page ][ $context ] = array(); } foreach ( array_keys( $wp_meta_boxes[ $page ] ) as $a_context ) { foreach ( array( 'high', 'core', 'default', 'low' ) as $a_priority ) { if ( ! isset( $wp_meta_boxes[ $page ][ $a_context ][ $a_priority ][ $id ] ) ) { continue; } // If a core box was previously removed, don't add. if ( ( 'core' === $priority || 'sorted' === $priority ) && false === $wp_meta_boxes[ $page ][ $a_context ][ $a_priority ][ $id ] ) { return; } // If a core box was previously added by a plugin, don't add. if ( 'core' === $priority ) { /* * If the box was added with default priority, give it core priority * to maintain sort order. */ if ( 'default' === $a_priority ) { $wp_meta_boxes[ $page ][ $a_context ]['core'][ $id ] = $wp_meta_boxes[ $page ][ $a_context ]['default'][ $id ]; unset( $wp_meta_boxes[ $page ][ $a_context ]['default'][ $id ] ); } return; } // If no priority given and ID already present, use existing priority. if ( empty( $priority ) ) { $priority = $a_priority; /* * Else, if we're adding to the sorted priority, we don't know the title * or callback. Grab them from the previously added context/priority. */ } elseif ( 'sorted' === $priority ) { $title = $wp_meta_boxes[ $page ][ $a_context ][ $a_priority ][ $id ]['title']; $callback = $wp_meta_boxes[ $page ][ $a_context ][ $a_priority ][ $id ]['callback']; $callback_args = $wp_meta_boxes[ $page ][ $a_context ][ $a_priority ][ $id ]['args']; } // An ID can be in only one priority and one context. if ( $priority !== $a_priority || $context !== $a_context ) { unset( $wp_meta_boxes[ $page ][ $a_context ][ $a_priority ][ $id ] ); } } } if ( empty( $priority ) ) { $priority = 'low'; } if ( ! isset( $wp_meta_boxes[ $page ][ $context ][ $priority ] ) ) { $wp_meta_boxes[ $page ][ $context ][ $priority ] = array(); } $wp_meta_boxes[ $page ][ $context ][ $priority ][ $id ] = array( 'id' => $id, 'title' => $title, 'callback' => $callback, 'args' => $callback_args, ); } /** * Renders a "fake" meta box with an information message, * shown on the block editor, when an incompatible meta box is found. * * @since 5.0.0 * * @param mixed $data_object The data object being rendered on this screen. * @param array $box { * Custom formats meta box arguments. * * @type string $id Meta box 'id' attribute. * @type string $title Meta box title. * @type callable $old_callback The original callback for this meta box. * @type array $args Extra meta box arguments. * } */ function do_block_editor_incompatible_meta_box( $data_object, $box ) { $plugin = _get_plugin_from_callback( $box['old_callback'] ); $plugins = get_plugins(); echo ''; if ( $plugin ) { /* translators: %s: The name of the plugin that generated this meta box. */ printf( __( 'This meta box, from the %s plugin, is not compatible with the block editor.' ), "{$plugin['Name']}" ); } else { _e( 'This meta box is not compatible with the block editor.' ); } echo '
'; if ( empty( $plugins['classic-editor/classic-editor.php'] ) ) { if ( current_user_can( 'install_plugins' ) ) { $install_url = wp_nonce_url( self_admin_url( 'plugin-install.php?tab=favorites&user=wordpressdotorg&save=0' ), 'save_wporg_username_' . get_current_user_id() ); echo ''; /* translators: %s: A link to install the Classic Editor plugin. */ printf( __( 'Please install the Classic Editor plugin to use this meta box.' ), esc_url( $install_url ) ); echo '
'; } } elseif ( is_plugin_inactive( 'classic-editor/classic-editor.php' ) ) { if ( current_user_can( 'activate_plugins' ) ) { $activate_url = wp_nonce_url( self_admin_url( 'plugins.php?action=activate&plugin=classic-editor/classic-editor.php' ), 'activate-plugin_classic-editor/classic-editor.php' ); echo ''; /* translators: %s: A link to activate the Classic Editor plugin. */ printf( __( 'Please activate the Classic Editor plugin to use this meta box.' ), esc_url( $activate_url ) ); echo '
'; } } elseif ( $data_object instanceof WP_Post ) { $edit_url = add_query_arg( array( 'classic-editor' => '', 'classic-editor__forget' => '', ), get_edit_post_link( $data_object ) ); echo ''; /* translators: %s: A link to use the Classic Editor plugin. */ printf( __( 'Please open the classic editor to use this meta box.' ), esc_url( $edit_url ) ); echo '
'; } } /** * Internal helper function to find the plugin from a meta box callback. * * @since 5.0.0 * * @access private * * @param callable $callback The callback function to check. * @return array|null The plugin that the callback belongs to, or null if it doesn't belong to a plugin. */ function _get_plugin_from_callback( $callback ) { try { if ( is_array( $callback ) ) { $reflection = new ReflectionMethod( $callback[0], $callback[1] ); } elseif ( is_string( $callback ) && str_contains( $callback, '::' ) ) { $reflection = new ReflectionMethod( $callback ); } else { $reflection = new ReflectionFunction( $callback ); } } catch ( ReflectionException $exception ) { // We could not properly reflect on the callable, so we abort here. return null; } // Don't show an error if it's an internal PHP function. if ( ! $reflection->isInternal() ) { // Only show errors if the meta box was registered by a plugin. $filename = wp_normalize_path( $reflection->getFileName() ); $plugin_dir = wp_normalize_path( WP_PLUGIN_DIR ); if ( str_starts_with( $filename, $plugin_dir ) ) { $filename = str_replace( $plugin_dir, '', $filename ); $filename = preg_replace( '|^/([^/]*/).*$|', '\\1', $filename ); $plugins = get_plugins(); foreach ( $plugins as $name => $plugin ) { if ( str_starts_with( $name, $filename ) ) { return $plugin; } } } } return null; } /** * Meta-Box template function. * * @since 2.5.0 * * @global array $wp_meta_boxes Global meta box state. * * @param string|WP_Screen $screen The screen identifier. If you have used add_menu_page() or * add_submenu_page() to create a new screen (and hence screen_id) * make sure your menu slug conforms to the limits of sanitize_key() * otherwise the 'screen' menu may not correctly render on your page. * @param string $context The screen context for which to display meta boxes. * @param mixed $data_object Gets passed to the meta box callback function as the first parameter. * Often this is the object that's the focus of the current screen, * for example a `WP_Post` or `WP_Comment` object. * @return int Number of meta_boxes. */ function do_meta_boxes( $screen, $context, $data_object ) { global $wp_meta_boxes; static $already_sorted = false; if ( empty( $screen ) ) { $screen = get_current_screen(); } elseif ( is_string( $screen ) ) { $screen = convert_to_screen( $screen ); } $page = $screen->id; $hidden = get_hidden_meta_boxes( $screen ); printf( ''; return $i; } /** * Removes a meta box from one or more screens. * * @since 2.6.0 * @since 4.4.0 The `$screen` parameter now accepts an array of screen IDs. * * @global array $wp_meta_boxes Global meta box state. * * @param string $id Meta box ID (used in the 'id' attribute for the meta box). * @param string|array|WP_Screen $screen The screen or screens on which the meta box is shown (such as a * post type, 'link', or 'comment'). Accepts a single screen ID, * WP_Screen object, or array of screen IDs. * @param string $context The context within the screen where the box is set to display. * Contexts vary from screen to screen. Post edit screen contexts * include 'normal', 'side', and 'advanced'. Comments screen contexts * include 'normal' and 'side'. Menus meta boxes (accordion sections) * all use the 'side' context. */ function remove_meta_box( $id, $screen, $context ) { global $wp_meta_boxes; if ( empty( $screen ) ) { $screen = get_current_screen(); } elseif ( is_string( $screen ) ) { $screen = convert_to_screen( $screen ); } elseif ( is_array( $screen ) ) { foreach ( $screen as $single_screen ) { remove_meta_box( $id, $single_screen, $context ); } } if ( ! isset( $screen->id ) ) { return; } $page = $screen->id; if ( ! isset( $wp_meta_boxes ) ) { $wp_meta_boxes = array(); } if ( ! isset( $wp_meta_boxes[ $page ] ) ) { $wp_meta_boxes[ $page ] = array(); } if ( ! isset( $wp_meta_boxes[ $page ][ $context ] ) ) { $wp_meta_boxes[ $page ][ $context ] = array(); } foreach ( array( 'high', 'core', 'default', 'low' ) as $priority ) { $wp_meta_boxes[ $page ][ $context ][ $priority ][ $id ] = false; } } /** * Meta Box Accordion Template Function. * * Largely made up of abstracted code from do_meta_boxes(), this * function serves to build meta boxes as list items for display as * a collapsible accordion. * * @since 3.6.0 * * @uses global $wp_meta_boxes Used to retrieve registered meta boxes. * * @param string|object $screen The screen identifier. * @param string $context The screen context for which to display accordion sections. * @param mixed $data_object Gets passed to the section callback function as the first parameter. * @return int Number of meta boxes as accordion sections. */ function do_accordion_sections( $screen, $context, $data_object ) { global $wp_meta_boxes; wp_enqueue_script( 'accordion' ); if ( empty( $screen ) ) { $screen = get_current_screen(); } elseif ( is_string( $screen ) ) { $screen = convert_to_screen( $screen ); } $page = $screen->id; $hidden = get_hidden_meta_boxes( $screen ); ?>