WordPress.org

Make WordPress Core

Ticket #44583: ajax-actions.php

File ajax-actions.php, 132.1 KB (added by burhandodhy, 3 years ago)

Update file

Line 
1<?php
2/**
3 * Administration API: Core Ajax handlers
4 *
5 * @package WordPress
6 * @subpackage Administration
7 * @since 2.1.0
8 */
9
10//
11// No-privilege Ajax handlers.
12//
13
14/**
15 * Ajax handler for the Heartbeat API in
16 * the no-privilege context.
17 *
18 * Runs when the user is not logged in.
19 *
20 * @since 3.6.0
21 */
22function wp_ajax_nopriv_heartbeat() {
23        $response = array();
24
25        // screen_id is the same as $current_screen->id and the JS global 'pagenow'.
26        if ( ! empty( $_POST['screen_id'] ) ) {
27                $screen_id = sanitize_key( $_POST['screen_id'] );
28        } else {
29                $screen_id = 'front';
30        }
31
32        if ( ! empty( $_POST['data'] ) ) {
33                $data = wp_unslash( (array) $_POST['data'] );
34
35                /**
36                 * Filters Heartbeat Ajax response in no-privilege environments.
37                 *
38                 * @since 3.6.0
39                 *
40                 * @param array  $response  The no-priv Heartbeat response.
41                 * @param array  $data      The $_POST data sent.
42                 * @param string $screen_id The screen id.
43                 */
44                $response = apply_filters( 'heartbeat_nopriv_received', $response, $data, $screen_id );
45        }
46
47        /**
48         * Filters Heartbeat Ajax response in no-privilege environments when no data is passed.
49         *
50         * @since 3.6.0
51         *
52         * @param array  $response  The no-priv Heartbeat response.
53         * @param string $screen_id The screen id.
54         */
55        $response = apply_filters( 'heartbeat_nopriv_send', $response, $screen_id );
56
57        /**
58         * Fires when Heartbeat ticks in no-privilege environments.
59         *
60         * Allows the transport to be easily replaced with long-polling.
61         *
62         * @since 3.6.0
63         *
64         * @param array  $response  The no-priv Heartbeat response.
65         * @param string $screen_id The screen id.
66         */
67        do_action( 'heartbeat_nopriv_tick', $response, $screen_id );
68
69        // Send the current time according to the server.
70        $response['server_time'] = time();
71
72        wp_send_json( $response );
73}
74
75//
76// GET-based Ajax handlers.
77//
78
79/**
80 * Ajax handler for fetching a list table.
81 *
82 * @since 3.1.0
83 */
84function wp_ajax_fetch_list() {
85        $list_class = $_GET['list_args']['class'];
86        check_ajax_referer( "fetch-list-$list_class", '_ajax_fetch_list_nonce' );
87
88        $wp_list_table = _get_list_table( $list_class, array( 'screen' => $_GET['list_args']['screen']['id'] ) );
89        if ( ! $wp_list_table ) {
90                wp_die( 0 );
91        }
92
93        if ( ! $wp_list_table->ajax_user_can() ) {
94                wp_die( -1 );
95        }
96
97        $wp_list_table->ajax_response();
98
99        wp_die( 0 );
100}
101
102/**
103 * Ajax handler for compression testing.
104 *
105 * @since 3.1.0
106 */
107function wp_ajax_wp_compression_test() {
108        if ( ! current_user_can( 'manage_options' ) ) {
109                wp_die( -1 );
110        }
111
112        if ( ini_get( 'zlib.output_compression' ) || 'ob_gzhandler' == ini_get( 'output_handler' ) ) {
113                update_site_option( 'can_compress_scripts', 0 );
114                wp_die( 0 );
115        }
116
117        if ( isset( $_GET['test'] ) ) {
118                header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' );
119                header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
120                header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
121                header( 'Content-Type: application/javascript; charset=UTF-8' );
122                $force_gzip = ( defined( 'ENFORCE_GZIP' ) && ENFORCE_GZIP );
123                $test_str   = '"wpCompressionTest Lorem ipsum dolor sit amet consectetuer mollis sapien urna ut a. Eu nonummy condimentum fringilla tempor pretium platea vel nibh netus Maecenas. Hac molestie amet justo quis pellentesque est ultrices interdum nibh Morbi. Cras mattis pretium Phasellus ante ipsum ipsum ut sociis Suspendisse Lorem. Ante et non molestie. Porta urna Vestibulum egestas id congue nibh eu risus gravida sit. Ac augue auctor Ut et non a elit massa id sodales. Elit eu Nulla at nibh adipiscing mattis lacus mauris at tempus. Netus nibh quis suscipit nec feugiat eget sed lorem et urna. Pellentesque lacus at ut massa consectetuer ligula ut auctor semper Pellentesque. Ut metus massa nibh quam Curabitur molestie nec mauris congue. Volutpat molestie elit justo facilisis neque ac risus Ut nascetur tristique. Vitae sit lorem tellus et quis Phasellus lacus tincidunt nunc Fusce. Pharetra wisi Suspendisse mus sagittis libero lacinia Integer consequat ac Phasellus. Et urna ac cursus tortor aliquam Aliquam amet tellus volutpat Vestibulum. Justo interdum condimentum In augue congue tellus sollicitudin Quisque quis nibh."';
124
125                if ( 1 == $_GET['test'] ) {
126                        echo $test_str;
127                        wp_die();
128                } elseif ( 2 == $_GET['test'] ) {
129                        if ( ! isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
130                                wp_die( -1 );
131                        }
132                        if ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate' ) && function_exists( 'gzdeflate' ) && ! $force_gzip ) {
133                                header( 'Content-Encoding: deflate' );
134                                $out = gzdeflate( $test_str, 1 );
135                        } elseif ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip' ) && function_exists( 'gzencode' ) ) {
136                                header( 'Content-Encoding: gzip' );
137                                $out = gzencode( $test_str, 1 );
138                        } else {
139                                wp_die( -1 );
140                        }
141                        echo $out;
142                        wp_die();
143                } elseif ( 'no' == $_GET['test'] ) {
144                        check_ajax_referer( 'update_can_compress_scripts' );
145                        update_site_option( 'can_compress_scripts', 0 );
146                } elseif ( 'yes' == $_GET['test'] ) {
147                        check_ajax_referer( 'update_can_compress_scripts' );
148                        update_site_option( 'can_compress_scripts', 1 );
149                }
150        }
151
152        wp_die( 0 );
153}
154
155/**
156 * Ajax handler for image editor previews.
157 *
158 * @since 3.1.0
159 */
160function wp_ajax_imgedit_preview() {
161        $post_id = intval( $_GET['postid'] );
162        if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) {
163                wp_die( -1 );
164        }
165
166        check_ajax_referer( "image_editor-$post_id" );
167
168        include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
169        if ( ! stream_preview_image( $post_id ) ) {
170                wp_die( -1 );
171        }
172
173        wp_die();
174}
175
176/**
177 * Ajax handler for oEmbed caching.
178 *
179 * @since 3.1.0
180 *
181 * @global WP_Embed $wp_embed
182 */
183function wp_ajax_oembed_cache() {
184        $GLOBALS['wp_embed']->cache_oembed( $_GET['post'] );
185        wp_die( 0 );
186}
187
188/**
189 * Ajax handler for user autocomplete.
190 *
191 * @since 3.4.0
192 */
193function wp_ajax_autocomplete_user() {
194        if ( ! is_multisite() || ! current_user_can( 'promote_users' ) || wp_is_large_network( 'users' ) ) {
195                wp_die( -1 );
196        }
197
198        /** This filter is documented in wp-admin/user-new.php */
199        if ( ! current_user_can( 'manage_network_users' ) && ! apply_filters( 'autocomplete_users_for_site_admins', false ) ) {
200                wp_die( -1 );
201        }
202
203        $return = array();
204
205        // Check the type of request
206        // Current allowed values are `add` and `search`
207        if ( isset( $_REQUEST['autocomplete_type'] ) && 'search' === $_REQUEST['autocomplete_type'] ) {
208                $type = $_REQUEST['autocomplete_type'];
209        } else {
210                $type = 'add';
211        }
212
213        // Check the desired field for value
214        // Current allowed values are `user_email` and `user_login`
215        if ( isset( $_REQUEST['autocomplete_field'] ) && 'user_email' === $_REQUEST['autocomplete_field'] ) {
216                $field = $_REQUEST['autocomplete_field'];
217        } else {
218                $field = 'user_login';
219        }
220
221        // Exclude current users of this blog
222        if ( isset( $_REQUEST['site_id'] ) ) {
223                $id = absint( $_REQUEST['site_id'] );
224        } else {
225                $id = get_current_blog_id();
226        }
227
228        $include_blog_users = ( $type == 'search' ? get_users(
229                array(
230                        'blog_id' => $id,
231                        'fields'  => 'ID',
232                )
233        ) : array() );
234        $exclude_blog_users = ( $type == 'add' ? get_users(
235                array(
236                        'blog_id' => $id,
237                        'fields'  => 'ID',
238                )
239        ) : array() );
240
241        $users = get_users(
242                array(
243                        'blog_id'        => false,
244                        'search'         => '*' . $_REQUEST['term'] . '*',
245                        'include'        => $include_blog_users,
246                        'exclude'        => $exclude_blog_users,
247                        'search_columns' => array( 'user_login', 'user_nicename', 'user_email' ),
248                )
249        );
250
251        foreach ( $users as $user ) {
252                $return[] = array(
253                        /* translators: 1: user_login, 2: user_email */
254                        'label' => sprintf( _x( '%1$s (%2$s)', 'user autocomplete result' ), $user->user_login, $user->user_email ),
255                        'value' => $user->$field,
256                );
257        }
258
259        wp_die( wp_json_encode( $return ) );
260}
261
262/**
263 * Handles AJAX requests for community events
264 *
265 * @since 4.8.0
266 */
267function wp_ajax_get_community_events() {
268        require_once( ABSPATH . 'wp-admin/includes/class-wp-community-events.php' );
269
270        check_ajax_referer( 'community_events' );
271
272        $search         = isset( $_POST['location'] ) ? wp_unslash( $_POST['location'] ) : '';
273        $timezone       = isset( $_POST['timezone'] ) ? wp_unslash( $_POST['timezone'] ) : '';
274        $user_id        = get_current_user_id();
275        $saved_location = get_user_option( 'community-events-location', $user_id );
276        $events_client  = new WP_Community_Events( $user_id, $saved_location );
277        $events         = $events_client->get_events( $search, $timezone );
278        $ip_changed     = false;
279
280        if ( is_wp_error( $events ) ) {
281                wp_send_json_error(
282                        array(
283                                'error' => $events->get_error_message(),
284                        )
285                );
286        } else {
287                if ( empty( $saved_location['ip'] ) && ! empty( $events['location']['ip'] ) ) {
288                        $ip_changed = true;
289                } elseif ( isset( $saved_location['ip'] ) && ! empty( $events['location']['ip'] ) && $saved_location['ip'] !== $events['location']['ip'] ) {
290                        $ip_changed = true;
291                }
292
293                /*
294                 * The location should only be updated when it changes. The API doesn't always return
295                 * a full location; sometimes it's missing the description or country. The location
296                 * that was saved during the initial request is known to be good and complete, though.
297                 * It should be left intact until the user explicitly changes it (either by manually
298                 * searching for a new location, or by changing their IP address).
299                 *
300                 * If the location was updated with an incomplete response from the API, then it could
301                 * break assumptions that the UI makes (e.g., that there will always be a description
302                 * that corresponds to a latitude/longitude location).
303                 *
304                 * The location is stored network-wide, so that the user doesn't have to set it on each site.
305                 */
306                if ( $ip_changed || $search ) {
307                        update_user_option( $user_id, 'community-events-location', $events['location'], true );
308                }
309
310                wp_send_json_success( $events );
311        }
312}
313
314/**
315 * Ajax handler for dashboard widgets.
316 *
317 * @since 3.4.0
318 */
319function wp_ajax_dashboard_widgets() {
320        require_once ABSPATH . 'wp-admin/includes/dashboard.php';
321
322        $pagenow = $_GET['pagenow'];
323        if ( $pagenow === 'dashboard-user' || $pagenow === 'dashboard-network' || $pagenow === 'dashboard' ) {
324                set_current_screen( $pagenow );
325        }
326
327        switch ( $_GET['widget'] ) {
328                case 'dashboard_primary':
329                        wp_dashboard_primary();
330                        break;
331        }
332        wp_die();
333}
334
335/**
336 * Ajax handler for Customizer preview logged-in status.
337 *
338 * @since 3.4.0
339 */
340function wp_ajax_logged_in() {
341        wp_die( 1 );
342}
343
344//
345// Ajax helpers.
346//
347
348/**
349 * Sends back current comment total and new page links if they need to be updated.
350 *
351 * Contrary to normal success Ajax response ("1"), die with time() on success.
352 *
353 * @access private
354 * @since 2.7.0
355 *
356 * @param int $comment_id
357 * @param int $delta
358 */
359function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) {
360        $total    = isset( $_POST['_total'] ) ? (int) $_POST['_total'] : 0;
361        $per_page = isset( $_POST['_per_page'] ) ? (int) $_POST['_per_page'] : 0;
362        $page     = isset( $_POST['_page'] ) ? (int) $_POST['_page'] : 0;
363        $url      = isset( $_POST['_url'] ) ? esc_url_raw( $_POST['_url'] ) : '';
364
365        // JS didn't send us everything we need to know. Just die with success message
366        if ( ! $total || ! $per_page || ! $page || ! $url ) {
367                $time           = time();
368                $comment        = get_comment( $comment_id );
369                $comment_status = '';
370                $comment_link   = '';
371
372                if ( $comment ) {
373                        $comment_status = $comment->comment_approved;
374                }
375
376                if ( 1 === (int) $comment_status ) {
377                        $comment_link = get_comment_link( $comment );
378                }
379
380                $counts = wp_count_comments();
381
382                $x = new WP_Ajax_Response(
383                        array(
384                                'what'         => 'comment',
385                                // Here for completeness - not used.
386                                'id'           => $comment_id,
387                                'supplemental' => array(
388                                        'status'               => $comment_status,
389                                        'postId'               => $comment ? $comment->comment_post_ID : '',
390                                        'time'                 => $time,
391                                        'in_moderation'        => $counts->moderated,
392                                        'i18n_comments_text'   => sprintf(
393                                                _n( '%s Comment', '%s Comments', $counts->approved ),
394                                                number_format_i18n( $counts->approved )
395                                        ),
396                                        'i18n_moderation_text' => sprintf(
397                                                _nx( '%s in moderation', '%s in moderation', $counts->moderated, 'comments' ),
398                                                number_format_i18n( $counts->moderated )
399                                        ),
400                                        'comment_link'         => $comment_link,
401                                ),
402                        )
403                );
404                $x->send();
405        }
406
407        $total += $delta;
408        if ( $total < 0 ) {
409                $total = 0;
410        }
411
412        // Only do the expensive stuff on a page-break, and about 1 other time per page
413        if ( 0 == $total % $per_page || 1 == mt_rand( 1, $per_page ) ) {
414                $post_id = 0;
415                // What type of comment count are we looking for?
416                $status = 'all';
417                $parsed = parse_url( $url );
418                if ( isset( $parsed['query'] ) ) {
419                        parse_str( $parsed['query'], $query_vars );
420                        if ( ! empty( $query_vars['comment_status'] ) ) {
421                                $status = $query_vars['comment_status'];
422                        }
423                        if ( ! empty( $query_vars['p'] ) ) {
424                                $post_id = (int) $query_vars['p'];
425                        }
426                        if ( ! empty( $query_vars['comment_type'] ) ) {
427                                $type = $query_vars['comment_type'];
428                        }
429                }
430
431                if ( empty( $type ) ) {
432                        // Only use the comment count if not filtering by a comment_type.
433                        $comment_count = wp_count_comments( $post_id );
434
435                        // We're looking for a known type of comment count.
436                        if ( isset( $comment_count->$status ) ) {
437                                $total = $comment_count->$status;
438                        }
439                }
440                // Else use the decremented value from above.
441        }
442
443        // The time since the last comment count.
444        $time    = time();
445        $comment = get_comment( $comment_id );
446
447        $x = new WP_Ajax_Response(
448                array(
449                        'what'         => 'comment',
450                        // Here for completeness - not used.
451                        'id'           => $comment_id,
452                        'supplemental' => array(
453                                'status'           => $comment ? $comment->comment_approved : '',
454                                'postId'           => $comment ? $comment->comment_post_ID : '',
455                                'total_items_i18n' => sprintf( _n( '%s item', '%s items', $total ), number_format_i18n( $total ) ),
456                                'total_pages'      => ceil( $total / $per_page ),
457                                'total_pages_i18n' => number_format_i18n( ceil( $total / $per_page ) ),
458                                'total'            => $total,
459                                'time'             => $time,
460                        ),
461                )
462        );
463        $x->send();
464}
465
466//
467// POST-based Ajax handlers.
468//
469
470/**
471 * Ajax handler for adding a hierarchical term.
472 *
473 * @access private
474 * @since 3.1.0
475 */
476function _wp_ajax_add_hierarchical_term() {
477        $action   = $_POST['action'];
478        $taxonomy = get_taxonomy( substr( $action, 4 ) );
479        check_ajax_referer( $action, '_ajax_nonce-add-' . $taxonomy->name );
480        if ( ! current_user_can( $taxonomy->cap->edit_terms ) ) {
481                wp_die( -1 );
482        }
483        $names  = explode( ',', $_POST[ 'new' . $taxonomy->name ] );
484        $parent = isset( $_POST[ 'new' . $taxonomy->name . '_parent' ] ) ? (int) $_POST[ 'new' . $taxonomy->name . '_parent' ] : 0;
485        if ( 0 > $parent ) {
486                $parent = 0;
487        }
488        if ( $taxonomy->name == 'category' ) {
489                $post_category = isset( $_POST['post_category'] ) ? (array) $_POST['post_category'] : array();
490        } else {
491                $post_category = ( isset( $_POST['tax_input'] ) && isset( $_POST['tax_input'][ $taxonomy->name ] ) ) ? (array) $_POST['tax_input'][ $taxonomy->name ] : array();
492        }
493        $checked_categories = array_map( 'absint', (array) $post_category );
494        $popular_ids        = wp_popular_terms_checklist( $taxonomy->name, 0, 10, false );
495
496        foreach ( $names as $cat_name ) {
497                $cat_name          = trim( $cat_name );
498                $category_nicename = sanitize_title( $cat_name );
499                if ( '' === $category_nicename ) {
500                        continue;
501                }
502
503                $cat_id = wp_insert_term( $cat_name, $taxonomy->name, array( 'parent' => $parent ) );
504                if ( ! $cat_id || is_wp_error( $cat_id ) ) {
505                        continue;
506                } else {
507                        $cat_id = $cat_id['term_id'];
508                }
509                $checked_categories[] = $cat_id;
510                if ( $parent ) { // Do these all at once in a second
511                        continue;
512                }
513
514                ob_start();
515
516                wp_terms_checklist(
517                        0, array(
518                                'taxonomy'             => $taxonomy->name,
519                                'descendants_and_self' => $cat_id,
520                                'selected_cats'        => $checked_categories,
521                                'popular_cats'         => $popular_ids,
522                        )
523                );
524
525                $data = ob_get_clean();
526
527                $add = array(
528                        'what'     => $taxonomy->name,
529                        'id'       => $cat_id,
530                        'data'     => str_replace( array( "\n", "\t" ), '', $data ),
531                        'position' => -1,
532                );
533        }
534
535        if ( $parent ) { // Foncy - replace the parent and all its children
536                $parent  = get_term( $parent, $taxonomy->name );
537                $term_id = $parent->term_id;
538
539                while ( $parent->parent ) { // get the top parent
540                        $parent = get_term( $parent->parent, $taxonomy->name );
541                        if ( is_wp_error( $parent ) ) {
542                                break;
543                        }
544                        $term_id = $parent->term_id;
545                }
546
547                ob_start();
548
549                wp_terms_checklist(
550                        0, array(
551                                'taxonomy'             => $taxonomy->name,
552                                'descendants_and_self' => $term_id,
553                                'selected_cats'        => $checked_categories,
554                                'popular_cats'         => $popular_ids,
555                        )
556                );
557
558                $data = ob_get_clean();
559
560                $add = array(
561                        'what'     => $taxonomy->name,
562                        'id'       => $term_id,
563                        'data'     => str_replace( array( "\n", "\t" ), '', $data ),
564                        'position' => -1,
565                );
566        }
567
568        ob_start();
569
570        wp_dropdown_categories(
571                array(
572                        'taxonomy'         => $taxonomy->name,
573                        'hide_empty'       => 0,
574                        'name'             => 'new' . $taxonomy->name . '_parent',
575                        'orderby'          => 'name',
576                        'hierarchical'     => 1,
577                        'show_option_none' => '&mdash; ' . $taxonomy->labels->parent_item . ' &mdash;',
578                )
579        );
580
581        $sup = ob_get_clean();
582
583        $add['supplemental'] = array( 'newcat_parent' => $sup );
584
585        $x = new WP_Ajax_Response( $add );
586        $x->send();
587}
588
589/**
590 * Ajax handler for deleting a comment.
591 *
592 * @since 3.1.0
593 */
594function wp_ajax_delete_comment() {
595        $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
596
597        if ( ! $comment = get_comment( $id ) ) {
598                wp_die( time() );
599        }
600        if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) ) {
601                wp_die( -1 );
602        }
603
604        check_ajax_referer( "delete-comment_$id" );
605        $status = wp_get_comment_status( $comment );
606
607        $delta = -1;
608        if ( isset( $_POST['trash'] ) && 1 == $_POST['trash'] ) {
609                if ( 'trash' == $status ) {
610                        wp_die( time() );
611                }
612                $r = wp_trash_comment( $comment );
613        } elseif ( isset( $_POST['untrash'] ) && 1 == $_POST['untrash'] ) {
614                if ( 'trash' != $status ) {
615                        wp_die( time() );
616                }
617                $r = wp_untrash_comment( $comment );
618                if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'trash' ) { // undo trash, not in trash
619                        $delta = 1;
620                }
621        } elseif ( isset( $_POST['spam'] ) && 1 == $_POST['spam'] ) {
622                if ( 'spam' == $status ) {
623                        wp_die( time() );
624                }
625                $r = wp_spam_comment( $comment );
626        } elseif ( isset( $_POST['unspam'] ) && 1 == $_POST['unspam'] ) {
627                if ( 'spam' != $status ) {
628                        wp_die( time() );
629                }
630                $r = wp_unspam_comment( $comment );
631                if ( ! isset( $_POST['comment_status'] ) || $_POST['comment_status'] != 'spam' ) { // undo spam, not in spam
632                        $delta = 1;
633                }
634        } elseif ( isset( $_POST['delete'] ) && 1 == $_POST['delete'] ) {
635                $r = wp_delete_comment( $comment );
636        } else {
637                wp_die( -1 );
638        }
639
640        if ( $r ) { // Decide if we need to send back '1' or a more complicated response including page links and comment counts
641                _wp_ajax_delete_comment_response( $comment->comment_ID, $delta );
642        }
643        wp_die( 0 );
644}
645
646/**
647 * Ajax handler for deleting a tag.
648 *
649 * @since 3.1.0
650 */
651function wp_ajax_delete_tag() {
652        $tag_id = (int) $_POST['tag_ID'];
653        check_ajax_referer( "delete-tag_$tag_id" );
654
655        if ( ! current_user_can( 'delete_term', $tag_id ) ) {
656                wp_die( -1 );
657        }
658
659        $taxonomy = ! empty( $_POST['taxonomy'] ) ? $_POST['taxonomy'] : 'post_tag';
660        $tag      = get_term( $tag_id, $taxonomy );
661        if ( ! $tag || is_wp_error( $tag ) ) {
662                wp_die( 1 );
663        }
664
665        if ( wp_delete_term( $tag_id, $taxonomy ) ) {
666                wp_die( 1 );
667        } else {
668                wp_die( 0 );
669        }
670}
671
672/**
673 * Ajax handler for deleting a link.
674 *
675 * @since 3.1.0
676 */
677function wp_ajax_delete_link() {
678        $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
679
680        check_ajax_referer( "delete-bookmark_$id" );
681        if ( ! current_user_can( 'manage_links' ) ) {
682                wp_die( -1 );
683        }
684
685        $link = get_bookmark( $id );
686        if ( ! $link || is_wp_error( $link ) ) {
687                wp_die( 1 );
688        }
689
690        if ( wp_delete_link( $id ) ) {
691                wp_die( 1 );
692        } else {
693                wp_die( 0 );
694        }
695}
696
697/**
698 * Ajax handler for deleting meta.
699 *
700 * @since 3.1.0
701 */
702function wp_ajax_delete_meta() {
703        $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
704
705        check_ajax_referer( "delete-meta_$id" );
706        if ( ! $meta = get_metadata_by_mid( 'post', $id ) ) {
707                wp_die( 1 );
708        }
709
710        if ( is_protected_meta( $meta->meta_key, 'post' ) || ! current_user_can( 'delete_post_meta', $meta->post_id, $meta->meta_key ) ) {
711                wp_die( -1 );
712        }
713        if ( delete_meta( $meta->meta_id ) ) {
714                wp_die( 1 );
715        }
716        wp_die( 0 );
717}
718
719/**
720 * Ajax handler for deleting a post.
721 *
722 * @since 3.1.0
723 *
724 * @param string $action Action to perform.
725 */
726function wp_ajax_delete_post( $action ) {
727        if ( empty( $action ) ) {
728                $action = 'delete-post';
729        }
730        $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
731
732        check_ajax_referer( "{$action}_$id" );
733        if ( ! current_user_can( 'delete_post', $id ) ) {
734                wp_die( -1 );
735        }
736
737        if ( ! get_post( $id ) ) {
738                wp_die( 1 );
739        }
740
741        if ( wp_delete_post( $id ) ) {
742                wp_die( 1 );
743        } else {
744                wp_die( 0 );
745        }
746}
747
748/**
749 * Ajax handler for sending a post to the trash.
750 *
751 * @since 3.1.0
752 *
753 * @param string $action Action to perform.
754 */
755function wp_ajax_trash_post( $action ) {
756        if ( empty( $action ) ) {
757                $action = 'trash-post';
758        }
759        $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
760
761        check_ajax_referer( "{$action}_$id" );
762        if ( ! current_user_can( 'delete_post', $id ) ) {
763                wp_die( -1 );
764        }
765
766        if ( ! get_post( $id ) ) {
767                wp_die( 1 );
768        }
769
770        if ( 'trash-post' == $action ) {
771                $done = wp_trash_post( $id );
772        } else {
773                $done = wp_untrash_post( $id );
774        }
775
776        if ( $done ) {
777                wp_die( 1 );
778        }
779
780        wp_die( 0 );
781}
782
783/**
784 * Ajax handler to restore a post from the trash.
785 *
786 * @since 3.1.0
787 *
788 * @param string $action Action to perform.
789 */
790function wp_ajax_untrash_post( $action ) {
791        if ( empty( $action ) ) {
792                $action = 'untrash-post';
793        }
794        wp_ajax_trash_post( $action );
795}
796
797/**
798 * @since 3.1.0
799 *
800 * @param string $action
801 */
802function wp_ajax_delete_page( $action ) {
803        if ( empty( $action ) ) {
804                $action = 'delete-page';
805        }
806        $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
807
808        check_ajax_referer( "{$action}_$id" );
809        if ( ! current_user_can( 'delete_page', $id ) ) {
810                wp_die( -1 );
811        }
812
813        if ( ! get_post( $id ) ) {
814                wp_die( 1 );
815        }
816
817        if ( wp_delete_post( $id ) ) {
818                wp_die( 1 );
819        } else {
820                wp_die( 0 );
821        }
822}
823
824/**
825 * Ajax handler to dim a comment.
826 *
827 * @since 3.1.0
828 */
829function wp_ajax_dim_comment() {
830        $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
831
832        if ( ! $comment = get_comment( $id ) ) {
833                $x = new WP_Ajax_Response(
834                        array(
835                                'what' => 'comment',
836                                'id'   => new WP_Error( 'invalid_comment', sprintf( __( 'Comment %d does not exist' ), $id ) ),
837                        )
838                );
839                $x->send();
840        }
841
842        if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && ! current_user_can( 'moderate_comments' ) ) {
843                wp_die( -1 );
844        }
845
846        $current = wp_get_comment_status( $comment );
847        if ( isset( $_POST['new'] ) && $_POST['new'] == $current ) {
848                wp_die( time() );
849        }
850
851        check_ajax_referer( "approve-comment_$id" );
852        if ( in_array( $current, array( 'unapproved', 'spam' ) ) ) {
853                $result = wp_set_comment_status( $comment, 'approve', true );
854        } else {
855                $result = wp_set_comment_status( $comment, 'hold', true );
856        }
857
858        if ( is_wp_error( $result ) ) {
859                $x = new WP_Ajax_Response(
860                        array(
861                                'what' => 'comment',
862                                'id'   => $result,
863                        )
864                );
865                $x->send();
866        }
867
868        // Decide if we need to send back '1' or a more complicated response including page links and comment counts
869        _wp_ajax_delete_comment_response( $comment->comment_ID );
870        wp_die( 0 );
871}
872
873/**
874 * Ajax handler for adding a link category.
875 *
876 * @since 3.1.0
877 *
878 * @param string $action Action to perform.
879 */
880function wp_ajax_add_link_category( $action ) {
881        if ( empty( $action ) ) {
882                $action = 'add-link-category';
883        }
884        check_ajax_referer( $action );
885        $tax = get_taxonomy( 'link_category' );
886        if ( ! current_user_can( $tax->cap->manage_terms ) ) {
887                wp_die( -1 );
888        }
889        $names = explode( ',', wp_unslash( $_POST['newcat'] ) );
890        $x     = new WP_Ajax_Response();
891        foreach ( $names as $cat_name ) {
892                $cat_name = trim( $cat_name );
893                $slug     = sanitize_title( $cat_name );
894                if ( '' === $slug ) {
895                        continue;
896                }
897
898                $cat_id = wp_insert_term( $cat_name, 'link_category' );
899                if ( ! $cat_id || is_wp_error( $cat_id ) ) {
900                        continue;
901                } else {
902                        $cat_id = $cat_id['term_id'];
903                }
904                $cat_name = esc_html( $cat_name );
905                $x->add(
906                        array(
907                                'what'     => 'link-category',
908                                'id'       => $cat_id,
909                                'data'     => "<li id='link-category-$cat_id'><label for='in-link-category-$cat_id' class='selectit'><input value='" . esc_attr( $cat_id ) . "' type='checkbox' checked='checked' name='link_category[]' id='in-link-category-$cat_id'/> $cat_name</label></li>",
910                                'position' => -1,
911                        )
912                );
913        }
914        $x->send();
915}
916
917/**
918 * Ajax handler to add a tag.
919 *
920 * @since 3.1.0
921 */
922function wp_ajax_add_tag() {
923        check_ajax_referer( 'add-tag', '_wpnonce_add-tag' );
924        $taxonomy = ! empty( $_POST['taxonomy'] ) ? $_POST['taxonomy'] : 'post_tag';
925        $tax      = get_taxonomy( $taxonomy );
926
927        if ( ! current_user_can( $tax->cap->edit_terms ) ) {
928                wp_die( -1 );
929        }
930
931        $x = new WP_Ajax_Response();
932
933        $tag = wp_insert_term( $_POST['tag-name'], $taxonomy, $_POST );
934
935        if ( ! $tag || is_wp_error( $tag ) || ( ! $tag = get_term( $tag['term_id'], $taxonomy ) ) ) {
936                $message = __( 'An error has occurred. Please reload the page and try again.' );
937                if ( is_wp_error( $tag ) && $tag->get_error_message() ) {
938                        $message = $tag->get_error_message();
939                }
940
941                $x->add(
942                        array(
943                                'what' => 'taxonomy',
944                                'data' => new WP_Error( 'error', $message ),
945                        )
946                );
947                $x->send();
948        }
949
950        $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => $_POST['screen'] ) );
951
952        $level = 0;
953        if ( is_taxonomy_hierarchical( $taxonomy ) ) {
954                $level = count( get_ancestors( $tag->term_id, $taxonomy, 'taxonomy' ) );
955                ob_start();
956                $wp_list_table->single_row( $tag, $level );
957                $noparents = ob_get_clean();
958        }
959
960        ob_start();
961        $wp_list_table->single_row( $tag );
962        $parents = ob_get_clean();
963
964        $x->add(
965                array(
966                        'what'         => 'taxonomy',
967                        'supplemental' => compact( 'parents', 'noparents' ),
968                )
969        );
970        $x->add(
971                array(
972                        'what'         => 'term',
973                        'position'     => $level,
974                        'supplemental' => (array) $tag,
975                )
976        );
977        $x->send();
978}
979
980/**
981 * Ajax handler for getting a tagcloud.
982 *
983 * @since 3.1.0
984 */
985function wp_ajax_get_tagcloud() {
986        if ( ! isset( $_POST['tax'] ) ) {
987                wp_die( 0 );
988        }
989
990        $taxonomy = sanitize_key( $_POST['tax'] );
991        $tax      = get_taxonomy( $taxonomy );
992        if ( ! $tax ) {
993                wp_die( 0 );
994        }
995
996        if ( ! current_user_can( $tax->cap->assign_terms ) ) {
997                wp_die( -1 );
998        }
999
1000        $tags = get_terms(
1001                $taxonomy, array(
1002                        'number'  => 45,
1003                        'orderby' => 'count',
1004                        'order'   => 'DESC',
1005                )
1006        );
1007
1008        if ( empty( $tags ) ) {
1009                wp_die( $tax->labels->not_found );
1010        }
1011
1012        if ( is_wp_error( $tags ) ) {
1013                wp_die( $tags->get_error_message() );
1014        }
1015
1016        foreach ( $tags as $key => $tag ) {
1017                $tags[ $key ]->link = '#';
1018                $tags[ $key ]->id   = $tag->term_id;
1019        }
1020
1021        // We need raw tag names here, so don't filter the output
1022        $return = wp_generate_tag_cloud(
1023                $tags, array(
1024                        'filter' => 0,
1025                        'format' => 'list',
1026                )
1027        );
1028
1029        if ( empty( $return ) ) {
1030                wp_die( 0 );
1031        }
1032
1033        echo $return;
1034
1035        wp_die();
1036}
1037
1038/**
1039 * Ajax handler for getting comments.
1040 *
1041 * @since 3.1.0
1042 *
1043 * @global int           $post_id
1044 *
1045 * @param string $action Action to perform.
1046 */
1047function wp_ajax_get_comments( $action ) {
1048        global $post_id;
1049        if ( empty( $action ) ) {
1050                $action = 'get-comments';
1051        }
1052        check_ajax_referer( $action );
1053
1054        if ( empty( $post_id ) && ! empty( $_REQUEST['p'] ) ) {
1055                $id = absint( $_REQUEST['p'] );
1056                if ( ! empty( $id ) ) {
1057                        $post_id = $id;
1058                }
1059        }
1060
1061        if ( empty( $post_id ) ) {
1062                wp_die( -1 );
1063        }
1064
1065        $wp_list_table = _get_list_table( 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1066
1067        if ( ! current_user_can( 'edit_post', $post_id ) ) {
1068                wp_die( -1 );
1069        }
1070
1071        $wp_list_table->prepare_items();
1072
1073        if ( ! $wp_list_table->has_items() ) {
1074                wp_die( 1 );
1075        }
1076
1077        $x = new WP_Ajax_Response();
1078        ob_start();
1079        foreach ( $wp_list_table->items as $comment ) {
1080                if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && 0 === $comment->comment_approved ) {
1081                        continue;
1082                }
1083                get_comment( $comment );
1084                $wp_list_table->single_row( $comment );
1085        }
1086        $comment_list_item = ob_get_clean();
1087
1088        $x->add(
1089                array(
1090                        'what' => 'comments',
1091                        'data' => $comment_list_item,
1092                )
1093        );
1094        $x->send();
1095}
1096
1097/**
1098 * Ajax handler for replying to a comment.
1099 *
1100 * @since 3.1.0
1101 *
1102 * @param string $action Action to perform.
1103 */
1104function wp_ajax_replyto_comment( $action ) {
1105        if ( empty( $action ) ) {
1106                $action = 'replyto-comment';
1107        }
1108
1109        check_ajax_referer( $action, '_ajax_nonce-replyto-comment' );
1110
1111        $comment_post_ID = (int) $_POST['comment_post_ID'];
1112        $post            = get_post( $comment_post_ID );
1113        if ( ! $post ) {
1114                wp_die( -1 );
1115        }
1116
1117        if ( ! current_user_can( 'edit_post', $comment_post_ID ) ) {
1118                wp_die( -1 );
1119        }
1120
1121        if ( empty( $post->post_status ) ) {
1122                wp_die( 1 );
1123        } elseif ( in_array( $post->post_status, array( 'draft', 'pending', 'trash' ) ) ) {
1124                wp_die( __( 'ERROR: you are replying to a comment on a draft post.' ) );
1125        }
1126
1127        $user = wp_get_current_user();
1128        if ( $user->exists() ) {
1129                $user_ID              = $user->ID;
1130                $comment_author       = wp_slash( $user->display_name );
1131                $comment_author_email = wp_slash( $user->user_email );
1132                $comment_author_url   = wp_slash( $user->user_url );
1133                $comment_content      = trim( $_POST['content'] );
1134                $comment_type         = isset( $_POST['comment_type'] ) ? trim( $_POST['comment_type'] ) : '';
1135                if ( current_user_can( 'unfiltered_html' ) ) {
1136                        if ( ! isset( $_POST['_wp_unfiltered_html_comment'] ) ) {
1137                                $_POST['_wp_unfiltered_html_comment'] = '';
1138                        }
1139
1140                        if ( wp_create_nonce( 'unfiltered-html-comment' ) != $_POST['_wp_unfiltered_html_comment'] ) {
1141                                kses_remove_filters(); // start with a clean slate
1142                                kses_init_filters(); // set up the filters
1143                        }
1144                }
1145        } else {
1146                wp_die( __( 'Sorry, you must be logged in to reply to a comment.' ) );
1147        }
1148
1149        if ( '' == $comment_content ) {
1150                wp_die( __( 'ERROR: please type a comment.' ) );
1151        }
1152
1153        $comment_parent = 0;
1154        if ( isset( $_POST['comment_ID'] ) ) {
1155                $comment_parent = absint( $_POST['comment_ID'] );
1156        }
1157        $comment_auto_approved = false;
1158        $commentdata           = compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_type', 'comment_parent', 'user_ID' );
1159
1160        // Automatically approve parent comment.
1161        if ( ! empty( $_POST['approve_parent'] ) ) {
1162                $parent = get_comment( $comment_parent );
1163
1164                if ( $parent && $parent->comment_approved === '0' && $parent->comment_post_ID == $comment_post_ID ) {
1165                        if ( ! current_user_can( 'edit_comment', $parent->comment_ID ) ) {
1166                                wp_die( -1 );
1167                        }
1168
1169                        if ( wp_set_comment_status( $parent, 'approve' ) ) {
1170                                $comment_auto_approved = true;
1171                        }
1172                }
1173        }
1174
1175        $comment_id = wp_new_comment( $commentdata );
1176
1177        if ( is_wp_error( $comment_id ) ) {
1178                wp_die( $comment_id->get_error_message() );
1179        }
1180
1181        $comment = get_comment( $comment_id );
1182        if ( ! $comment ) {
1183                wp_die( 1 );
1184        }
1185
1186        $position = ( isset( $_POST['position'] ) && (int) $_POST['position'] ) ? (int) $_POST['position'] : '-1';
1187
1188        ob_start();
1189        if ( isset( $_REQUEST['mode'] ) && 'dashboard' == $_REQUEST['mode'] ) {
1190                require_once( ABSPATH . 'wp-admin/includes/dashboard.php' );
1191                _wp_dashboard_recent_comments_row( $comment );
1192        } else {
1193                if ( isset( $_REQUEST['mode'] ) && 'single' == $_REQUEST['mode'] ) {
1194                        $wp_list_table = _get_list_table( 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1195                } else {
1196                        $wp_list_table = _get_list_table( 'WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1197                }
1198                $wp_list_table->single_row( $comment );
1199        }
1200        $comment_list_item = ob_get_clean();
1201
1202        $response = array(
1203                'what'     => 'comment',
1204                'id'       => $comment->comment_ID,
1205                'data'     => $comment_list_item,
1206                'position' => $position,
1207        );
1208
1209        $counts                   = wp_count_comments();
1210        $response['supplemental'] = array(
1211                'in_moderation'        => $counts->moderated,
1212                'i18n_comments_text'   => sprintf(
1213                        _n( '%s Comment', '%s Comments', $counts->approved ),
1214                        number_format_i18n( $counts->approved )
1215                ),
1216                'i18n_moderation_text' => sprintf(
1217                        _nx( '%s in moderation', '%s in moderation', $counts->moderated, 'comments' ),
1218                        number_format_i18n( $counts->moderated )
1219                ),
1220        );
1221
1222        if ( $comment_auto_approved ) {
1223                $response['supplemental']['parent_approved'] = $parent->comment_ID;
1224                $response['supplemental']['parent_post_id']  = $parent->comment_post_ID;
1225        }
1226
1227        $x = new WP_Ajax_Response();
1228        $x->add( $response );
1229        $x->send();
1230}
1231
1232/**
1233 * Ajax handler for editing a comment.
1234 *
1235 * @since 3.1.0
1236 */
1237function wp_ajax_edit_comment() {
1238        check_ajax_referer( 'replyto-comment', '_ajax_nonce-replyto-comment' );
1239
1240        $comment_id = (int) $_POST['comment_ID'];
1241        if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
1242                wp_die( -1 );
1243        }
1244
1245        if ( '' == $_POST['content'] ) {
1246                wp_die( __( 'ERROR: please type a comment.' ) );
1247        }
1248
1249        if ( isset( $_POST['status'] ) ) {
1250                $_POST['comment_status'] = $_POST['status'];
1251        }
1252        edit_comment();
1253
1254        $position      = ( isset( $_POST['position'] ) && (int) $_POST['position'] ) ? (int) $_POST['position'] : '-1';
1255        $checkbox      = ( isset( $_POST['checkbox'] ) && true == $_POST['checkbox'] ) ? 1 : 0;
1256        $wp_list_table = _get_list_table( $checkbox ? 'WP_Comments_List_Table' : 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1257
1258        $comment = get_comment( $comment_id );
1259        if ( empty( $comment->comment_ID ) ) {
1260                wp_die( -1 );
1261        }
1262
1263        ob_start();
1264        $wp_list_table->single_row( $comment );
1265        $comment_list_item = ob_get_clean();
1266
1267        $x = new WP_Ajax_Response();
1268
1269        $x->add(
1270                array(
1271                        'what'     => 'edit_comment',
1272                        'id'       => $comment->comment_ID,
1273                        'data'     => $comment_list_item,
1274                        'position' => $position,
1275                )
1276        );
1277
1278        $x->send();
1279}
1280
1281/**
1282 * Ajax handler for adding a menu item.
1283 *
1284 * @since 3.1.0
1285 */
1286function wp_ajax_add_menu_item() {
1287        check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1288
1289        if ( ! current_user_can( 'edit_theme_options' ) ) {
1290                wp_die( -1 );
1291        }
1292
1293        require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1294
1295        // For performance reasons, we omit some object properties from the checklist.
1296        // The following is a hacky way to restore them when adding non-custom items.
1297
1298        $menu_items_data = array();
1299        foreach ( (array) $_POST['menu-item'] as $menu_item_data ) {
1300                if (
1301                        ! empty( $menu_item_data['menu-item-type'] ) &&
1302                        'custom' != $menu_item_data['menu-item-type'] &&
1303                        ! empty( $menu_item_data['menu-item-object-id'] )
1304                ) {
1305                        switch ( $menu_item_data['menu-item-type'] ) {
1306                                case 'post_type':
1307                                        $_object = get_post( $menu_item_data['menu-item-object-id'] );
1308                                        break;
1309
1310                                case 'post_type_archive':
1311                                        $_object = get_post_type_object( $menu_item_data['menu-item-object'] );
1312                                        break;
1313
1314                                case 'taxonomy':
1315                                        $_object = get_term( $menu_item_data['menu-item-object-id'], $menu_item_data['menu-item-object'] );
1316                                        break;
1317                        }
1318
1319                        $_menu_items = array_map( 'wp_setup_nav_menu_item', array( $_object ) );
1320                        $_menu_item  = reset( $_menu_items );
1321
1322                        // Restore the missing menu item properties
1323                        $menu_item_data['menu-item-description'] = $_menu_item->description;
1324                }
1325
1326                $menu_items_data[] = $menu_item_data;
1327        }
1328
1329        $item_ids = wp_save_nav_menu_items( 0, $menu_items_data );
1330        if ( is_wp_error( $item_ids ) ) {
1331                wp_die( 0 );
1332        }
1333
1334        $menu_items = array();
1335
1336        foreach ( (array) $item_ids as $menu_item_id ) {
1337                $menu_obj = get_post( $menu_item_id );
1338                if ( ! empty( $menu_obj->ID ) ) {
1339                        $menu_obj        = wp_setup_nav_menu_item( $menu_obj );
1340                        $menu_obj->label = $menu_obj->title; // don't show "(pending)" in ajax-added items
1341                        $menu_items[]    = $menu_obj;
1342                }
1343        }
1344
1345        /** This filter is documented in wp-admin/includes/nav-menu.php */
1346        $walker_class_name = apply_filters( 'wp_edit_nav_menu_walker', 'Walker_Nav_Menu_Edit', $_POST['menu'] );
1347
1348        if ( ! class_exists( $walker_class_name ) ) {
1349                wp_die( 0 );
1350        }
1351
1352        if ( ! empty( $menu_items ) ) {
1353                $args = array(
1354                        'after'       => '',
1355                        'before'      => '',
1356                        'link_after'  => '',
1357                        'link_before' => '',
1358                        'walker'      => new $walker_class_name,
1359                );
1360                echo walk_nav_menu_tree( $menu_items, 0, (object) $args );
1361        }
1362        wp_die();
1363}
1364
1365/**
1366 * Ajax handler for adding meta.
1367 *
1368 * @since 3.1.0
1369 */
1370function wp_ajax_add_meta() {
1371        check_ajax_referer( 'add-meta', '_ajax_nonce-add-meta' );
1372        $c    = 0;
1373        $pid  = (int) $_POST['post_id'];
1374        $post = get_post( $pid );
1375
1376        if ( isset( $_POST['metakeyselect'] ) || isset( $_POST['metakeyinput'] ) ) {
1377                if ( ! current_user_can( 'edit_post', $pid ) ) {
1378                        wp_die( -1 );
1379                }
1380                if ( isset( $_POST['metakeyselect'] ) && '#NONE#' == $_POST['metakeyselect'] && empty( $_POST['metakeyinput'] ) ) {
1381                        wp_die( 1 );
1382                }
1383
1384                // If the post is an autodraft, save the post as a draft and then attempt to save the meta.
1385                if ( $post->post_status == 'auto-draft' ) {
1386                        $post_data                = array();
1387                        $post_data['action']      = 'draft'; // Warning fix
1388                        $post_data['post_ID']     = $pid;
1389                        $post_data['post_type']   = $post->post_type;
1390                        $post_data['post_status'] = 'draft';
1391                        $now                      = current_time( 'timestamp', 1 );
1392                        /* translators: 1: Post creation date, 2: Post creation time */
1393                        $post_data['post_title'] = sprintf( __( 'Draft created on %1$s at %2$s' ), date( __( 'F j, Y' ), $now ), date( __( 'g:i a' ), $now ) );
1394
1395                        $pid = edit_post( $post_data );
1396                        if ( $pid ) {
1397                                if ( is_wp_error( $pid ) ) {
1398                                        $x = new WP_Ajax_Response(
1399                                                array(
1400                                                        'what' => 'meta',
1401                                                        'data' => $pid,
1402                                                )
1403                                        );
1404                                        $x->send();
1405                                }
1406
1407                                if ( ! $mid = add_meta( $pid ) ) {
1408                                        wp_die( __( 'Please provide a custom field value.' ) );
1409                                }
1410                        } else {
1411                                wp_die( 0 );
1412                        }
1413                } elseif ( ! $mid = add_meta( $pid ) ) {
1414                        wp_die( __( 'Please provide a custom field value.' ) );
1415                }
1416
1417                $meta = get_metadata_by_mid( 'post', $mid );
1418                $pid  = (int) $meta->post_id;
1419                $meta = get_object_vars( $meta );
1420                $x    = new WP_Ajax_Response(
1421                        array(
1422                                'what'         => 'meta',
1423                                'id'           => $mid,
1424                                'data'         => _list_meta_row( $meta, $c ),
1425                                'position'     => 1,
1426                                'supplemental' => array( 'postid' => $pid ),
1427                        )
1428                );
1429        } else { // Update?
1430                $mid   = (int) key( $_POST['meta'] );
1431                $key   = wp_unslash( $_POST['meta'][ $mid ]['key'] );
1432                $value = wp_unslash( $_POST['meta'][ $mid ]['value'] );
1433                if ( '' == trim( $key ) ) {
1434                        wp_die( __( 'Please provide a custom field name.' ) );
1435                }
1436                if ( '' == trim( $value ) ) {
1437                        wp_die( __( 'Please provide a custom field value.' ) );
1438                }
1439                if ( ! $meta = get_metadata_by_mid( 'post', $mid ) ) {
1440                        wp_die( 0 ); // if meta doesn't exist
1441                }
1442                if ( is_protected_meta( $meta->meta_key, 'post' ) || is_protected_meta( $key, 'post' ) ||
1443                        ! current_user_can( 'edit_post_meta', $meta->post_id, $meta->meta_key ) ||
1444                        ! current_user_can( 'edit_post_meta', $meta->post_id, $key ) ) {
1445                        wp_die( -1 );
1446                }
1447                if ( $meta->meta_value != $value || $meta->meta_key != $key ) {
1448                        if ( ! $u = update_metadata_by_mid( 'post', $mid, $value, $key ) ) {
1449                                wp_die( 0 ); // We know meta exists; we also know it's unchanged (or DB error, in which case there are bigger problems).
1450                        }
1451                }
1452
1453                $x = new WP_Ajax_Response(
1454                        array(
1455                                'what'         => 'meta',
1456                                'id'           => $mid,
1457                                'old_id'       => $mid,
1458                                'data'         => _list_meta_row(
1459                                        array(
1460                                                'meta_key'   => $key,
1461                                                'meta_value' => $value,
1462                                                'meta_id'    => $mid,
1463                                        ), $c
1464                                ),
1465                                'position'     => 0,
1466                                'supplemental' => array( 'postid' => $meta->post_id ),
1467                        )
1468                );
1469        }
1470        $x->send();
1471}
1472
1473/**
1474 * Ajax handler for adding a user.
1475 *
1476 * @since 3.1.0
1477 *
1478 * @param string $action Action to perform.
1479 */
1480function wp_ajax_add_user( $action ) {
1481        if ( empty( $action ) ) {
1482                $action = 'add-user';
1483        }
1484
1485        check_ajax_referer( $action );
1486        if ( ! current_user_can( 'create_users' ) ) {
1487                wp_die( -1 );
1488        }
1489        if ( ! $user_id = edit_user() ) {
1490                wp_die( 0 );
1491        } elseif ( is_wp_error( $user_id ) ) {
1492                $x = new WP_Ajax_Response(
1493                        array(
1494                                'what' => 'user',
1495                                'id'   => $user_id,
1496                        )
1497                );
1498                $x->send();
1499        }
1500        $user_object = get_userdata( $user_id );
1501
1502        $wp_list_table = _get_list_table( 'WP_Users_List_Table' );
1503
1504        $role = current( $user_object->roles );
1505
1506        $x = new WP_Ajax_Response(
1507                array(
1508                        'what'         => 'user',
1509                        'id'           => $user_id,
1510                        'data'         => $wp_list_table->single_row( $user_object, '', $role ),
1511                        'supplemental' => array(
1512                                'show-link' => sprintf(
1513                                        /* translators: %s: the new user */
1514                                        __( 'User %s added' ),
1515                                        '<a href="#user-' . $user_id . '">' . $user_object->user_login . '</a>'
1516                                ),
1517                                'role'      => $role,
1518                        ),
1519                )
1520        );
1521        $x->send();
1522}
1523
1524/**
1525 * Ajax handler for closed post boxes.
1526 *
1527 * @since 3.1.0
1528 */
1529function wp_ajax_closed_postboxes() {
1530        check_ajax_referer( 'closedpostboxes', 'closedpostboxesnonce' );
1531        $closed = isset( $_POST['closed'] ) ? explode( ',', $_POST['closed'] ) : array();
1532        $closed = array_filter( $closed );
1533
1534        $hidden = isset( $_POST['hidden'] ) ? explode( ',', $_POST['hidden'] ) : array();
1535        $hidden = array_filter( $hidden );
1536
1537        $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1538
1539        if ( $page != sanitize_key( $page ) ) {
1540                wp_die( 0 );
1541        }
1542
1543        if ( ! $user = wp_get_current_user() ) {
1544                wp_die( -1 );
1545        }
1546
1547        if ( is_array( $closed ) ) {
1548                update_user_option( $user->ID, "closedpostboxes_$page", $closed, true );
1549        }
1550
1551        if ( is_array( $hidden ) ) {
1552                $hidden = array_diff( $hidden, array( 'submitdiv', 'linksubmitdiv', 'manage-menu', 'create-menu' ) ); // postboxes that are always shown
1553                update_user_option( $user->ID, "metaboxhidden_$page", $hidden, true );
1554        }
1555
1556        wp_die( 1 );
1557}
1558
1559/**
1560 * Ajax handler for hidden columns.
1561 *
1562 * @since 3.1.0
1563 */
1564function wp_ajax_hidden_columns() {
1565        check_ajax_referer( 'screen-options-nonce', 'screenoptionnonce' );
1566        $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1567
1568        if ( $page != sanitize_key( $page ) ) {
1569                wp_die( 0 );
1570        }
1571
1572        if ( ! $user = wp_get_current_user() ) {
1573                wp_die( -1 );
1574        }
1575
1576        $hidden = ! empty( $_POST['hidden'] ) ? explode( ',', $_POST['hidden'] ) : array();
1577        update_user_option( $user->ID, "manage{$page}columnshidden", $hidden, true );
1578
1579        wp_die( 1 );
1580}
1581
1582/**
1583 * Ajax handler for updating whether to display the welcome panel.
1584 *
1585 * @since 3.1.0
1586 */
1587function wp_ajax_update_welcome_panel() {
1588        check_ajax_referer( 'welcome-panel-nonce', 'welcomepanelnonce' );
1589
1590        if ( ! current_user_can( 'edit_theme_options' ) ) {
1591                wp_die( -1 );
1592        }
1593
1594        update_user_meta( get_current_user_id(), 'show_welcome_panel', empty( $_POST['visible'] ) ? 0 : 1 );
1595
1596        wp_die( 1 );
1597}
1598
1599/**
1600 * Ajax handler for retrieving menu meta boxes.
1601 *
1602 * @since 3.1.0
1603 */
1604function wp_ajax_menu_get_metabox() {
1605        if ( ! current_user_can( 'edit_theme_options' ) ) {
1606                wp_die( -1 );
1607        }
1608
1609        require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1610
1611        if ( isset( $_POST['item-type'] ) && 'post_type' == $_POST['item-type'] ) {
1612                $type     = 'posttype';
1613                $callback = 'wp_nav_menu_item_post_type_meta_box';
1614                $items    = (array) get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
1615        } elseif ( isset( $_POST['item-type'] ) && 'taxonomy' == $_POST['item-type'] ) {
1616                $type     = 'taxonomy';
1617                $callback = 'wp_nav_menu_item_taxonomy_meta_box';
1618                $items    = (array) get_taxonomies( array( 'show_ui' => true ), 'object' );
1619        }
1620
1621        if ( ! empty( $_POST['item-object'] ) && isset( $items[ $_POST['item-object'] ] ) ) {
1622                $menus_meta_box_object = $items[ $_POST['item-object'] ];
1623
1624                /** This filter is documented in wp-admin/includes/nav-menu.php */
1625                $item = apply_filters( 'nav_menu_meta_box_object', $menus_meta_box_object );
1626                ob_start();
1627                call_user_func_array(
1628                        $callback, array(
1629                                null,
1630                                array(
1631                                        'id'       => 'add-' . $item->name,
1632                                        'title'    => $item->labels->name,
1633                                        'callback' => $callback,
1634                                        'args'     => $item,
1635                                ),
1636                        )
1637                );
1638
1639                $markup = ob_get_clean();
1640
1641                echo wp_json_encode(
1642                        array(
1643                                'replace-id' => $type . '-' . $item->name,
1644                                'markup'     => $markup,
1645                        )
1646                );
1647        }
1648
1649        wp_die();
1650}
1651
1652/**
1653 * Ajax handler for internal linking.
1654 *
1655 * @since 3.1.0
1656 */
1657function wp_ajax_wp_link_ajax() {
1658        check_ajax_referer( 'internal-linking', '_ajax_linking_nonce' );
1659
1660        $args = array();
1661
1662        if ( isset( $_POST['search'] ) ) {
1663                $args['s'] = wp_unslash( $_POST['search'] );
1664        }
1665
1666        if ( isset( $_POST['term'] ) ) {
1667                $args['s'] = wp_unslash( $_POST['term'] );
1668        }
1669
1670        $args['pagenum'] = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
1671
1672        if ( ! class_exists( '_WP_Editors', false ) ) {
1673                require( ABSPATH . WPINC . '/class-wp-editor.php' );
1674        }
1675
1676        $results = _WP_Editors::wp_link_query( $args );
1677
1678        if ( ! isset( $results ) ) {
1679                wp_die( 0 );
1680        }
1681
1682        echo wp_json_encode( $results );
1683        echo "\n";
1684
1685        wp_die();
1686}
1687
1688/**
1689 * Ajax handler for menu locations save.
1690 *
1691 * @since 3.1.0
1692 */
1693function wp_ajax_menu_locations_save() {
1694        if ( ! current_user_can( 'edit_theme_options' ) ) {
1695                wp_die( -1 );
1696        }
1697        check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1698        if ( ! isset( $_POST['menu-locations'] ) ) {
1699                wp_die( 0 );
1700        }
1701        set_theme_mod( 'nav_menu_locations', array_map( 'absint', $_POST['menu-locations'] ) );
1702        wp_die( 1 );
1703}
1704
1705/**
1706 * Ajax handler for saving the meta box order.
1707 *
1708 * @since 3.1.0
1709 */
1710function wp_ajax_meta_box_order() {
1711        check_ajax_referer( 'meta-box-order' );
1712        $order        = isset( $_POST['order'] ) ? (array) $_POST['order'] : false;
1713        $page_columns = isset( $_POST['page_columns'] ) ? $_POST['page_columns'] : 'auto';
1714
1715        if ( $page_columns != 'auto' ) {
1716                $page_columns = (int) $page_columns;
1717        }
1718
1719        $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1720
1721        if ( $page != sanitize_key( $page ) ) {
1722                wp_die( 0 );
1723        }
1724
1725        if ( ! $user = wp_get_current_user() ) {
1726                wp_die( -1 );
1727        }
1728
1729        if ( $order ) {
1730                update_user_option( $user->ID, "meta-box-order_$page", $order, true );
1731        }
1732
1733        if ( $page_columns ) {
1734                update_user_option( $user->ID, "screen_layout_$page", $page_columns, true );
1735        }
1736
1737        wp_die( 1 );
1738}
1739
1740/**
1741 * Ajax handler for menu quick searching.
1742 *
1743 * @since 3.1.0
1744 */
1745function wp_ajax_menu_quick_search() {
1746        if ( ! current_user_can( 'edit_theme_options' ) ) {
1747                wp_die( -1 );
1748        }
1749
1750        require_once ABSPATH . 'wp-admin/includes/nav-menu.php';
1751
1752        _wp_ajax_menu_quick_search( $_POST );
1753
1754        wp_die();
1755}
1756
1757/**
1758 * Ajax handler to retrieve a permalink.
1759 *
1760 * @since 3.1.0
1761 */
1762function wp_ajax_get_permalink() {
1763        check_ajax_referer( 'getpermalink', 'getpermalinknonce' );
1764        $post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0;
1765        wp_die( get_preview_post_link( $post_id ) );
1766}
1767
1768/**
1769 * Ajax handler to retrieve a sample permalink.
1770 *
1771 * @since 3.1.0
1772 */
1773function wp_ajax_sample_permalink() {
1774        check_ajax_referer( 'samplepermalink', 'samplepermalinknonce' );
1775        $post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0;
1776        $title   = isset( $_POST['new_title'] ) ? $_POST['new_title'] : '';
1777        $slug    = isset( $_POST['new_slug'] ) ? $_POST['new_slug'] : null;
1778        wp_die( get_sample_permalink_html( $post_id, $title, $slug ) );
1779}
1780
1781/**
1782 * Ajax handler for Quick Edit saving a post from a list table.
1783 *
1784 * @since 3.1.0
1785 *
1786 * @global string $mode List table view mode.
1787 */
1788function wp_ajax_inline_save() {
1789        global $mode;
1790
1791        check_ajax_referer( 'inlineeditnonce', '_inline_edit' );
1792
1793        if ( ! isset( $_POST['post_ID'] ) || ! ( $post_ID = (int) $_POST['post_ID'] ) ) {
1794                wp_die();
1795        }
1796
1797        if ( 'page' == $_POST['post_type'] ) {
1798                if ( ! current_user_can( 'edit_page', $post_ID ) ) {
1799                        wp_die( __( 'Sorry, you are not allowed to edit this page.' ) );
1800                }
1801        } else {
1802                if ( ! current_user_can( 'edit_post', $post_ID ) ) {
1803                        wp_die( __( 'Sorry, you are not allowed to edit this post.' ) );
1804                }
1805        }
1806
1807        if ( $last = wp_check_post_lock( $post_ID ) ) {
1808                $last_user      = get_userdata( $last );
1809                $last_user_name = $last_user ? $last_user->display_name : __( 'Someone' );
1810                printf( $_POST['post_type'] == 'page' ? __( 'Saving is disabled: %s is currently editing this page.' ) : __( 'Saving is disabled: %s is currently editing this post.' ), esc_html( $last_user_name ) );
1811                wp_die();
1812        }
1813
1814        $data = &$_POST;
1815
1816        $post = get_post( $post_ID, ARRAY_A );
1817
1818        // Since it's coming from the database.
1819        $post = wp_slash( $post );
1820
1821        $data['content'] = $post['post_content'];
1822        $data['excerpt'] = $post['post_excerpt'];
1823
1824        // Rename.
1825        $data['user_ID'] = get_current_user_id();
1826
1827        if ( isset( $data['post_parent'] ) ) {
1828                $data['parent_id'] = $data['post_parent'];
1829        }
1830
1831        // Status.
1832        if ( isset( $data['keep_private'] ) && 'private' == $data['keep_private'] ) {
1833                $data['visibility']  = 'private';
1834                $data['post_status'] = 'private';
1835        } else {
1836                $data['post_status'] = $data['_status'];
1837        }
1838
1839        if ( empty( $data['comment_status'] ) ) {
1840                $data['comment_status'] = 'closed';
1841        }
1842        if ( empty( $data['ping_status'] ) ) {
1843                $data['ping_status'] = 'closed';
1844        }
1845
1846        // Exclude terms from taxonomies that are not supposed to appear in Quick Edit.
1847        if ( ! empty( $data['tax_input'] ) ) {
1848                foreach ( $data['tax_input'] as $taxonomy => $terms ) {
1849                        $tax_object = get_taxonomy( $taxonomy );
1850                        /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */
1851                        if ( ! apply_filters( 'quick_edit_show_taxonomy', $tax_object->show_in_quick_edit, $taxonomy, $post['post_type'] ) ) {
1852                                unset( $data['tax_input'][ $taxonomy ] );
1853                        }
1854                }
1855        }
1856
1857        // Hack: wp_unique_post_slug() doesn't work for drafts, so we will fake that our post is published.
1858        if ( ! empty( $data['post_name'] ) && in_array( $post['post_status'], array( 'draft', 'pending' ) ) ) {
1859                $post['post_status'] = 'publish';
1860                $data['post_name']   = wp_unique_post_slug( $data['post_name'], $post['ID'], $post['post_status'], $post['post_type'], $post['post_parent'] );
1861        }
1862
1863        // Update the post.
1864        edit_post();
1865
1866        $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
1867
1868        $mode = $_POST['post_view'] === 'excerpt' ? 'excerpt' : 'list';
1869
1870        $level = 0;
1871        if ( is_post_type_hierarchical( $wp_list_table->screen->post_type ) ) {
1872                $request_post = array( get_post( $_POST['post_ID'] ) );
1873                $parent       = $request_post[0]->post_parent;
1874
1875                while ( $parent > 0 ) {
1876                        $parent_post = get_post( $parent );
1877                        $parent      = $parent_post->post_parent;
1878                        $level++;
1879                }
1880        }
1881
1882        $wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level );
1883
1884        wp_die();
1885}
1886
1887/**
1888 * Ajax handler for quick edit saving for a term.
1889 *
1890 * @since 3.1.0
1891 */
1892function wp_ajax_inline_save_tax() {
1893        check_ajax_referer( 'taxinlineeditnonce', '_inline_edit' );
1894
1895        $taxonomy = sanitize_key( $_POST['taxonomy'] );
1896        $tax      = get_taxonomy( $taxonomy );
1897        if ( ! $tax ) {
1898                wp_die( 0 );
1899        }
1900
1901        if ( ! isset( $_POST['tax_ID'] ) || ! ( $id = (int) $_POST['tax_ID'] ) ) {
1902                wp_die( -1 );
1903        }
1904
1905        if ( ! current_user_can( 'edit_term', $id ) ) {
1906                wp_die( -1 );
1907        }
1908
1909        $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => 'edit-' . $taxonomy ) );
1910
1911        $tag                  = get_term( $id, $taxonomy );
1912        $_POST['description'] = $tag->description;
1913
1914        $updated = wp_update_term( $id, $taxonomy, $_POST );
1915        if ( $updated && ! is_wp_error( $updated ) ) {
1916                $tag = get_term( $updated['term_id'], $taxonomy );
1917                if ( ! $tag || is_wp_error( $tag ) ) {
1918                        if ( is_wp_error( $tag ) && $tag->get_error_message() ) {
1919                                wp_die( $tag->get_error_message() );
1920                        }
1921                        wp_die( __( 'Item not updated.' ) );
1922                }
1923        } else {
1924                if ( is_wp_error( $updated ) && $updated->get_error_message() ) {
1925                        wp_die( $updated->get_error_message() );
1926                }
1927                wp_die( __( 'Item not updated.' ) );
1928        }
1929        $level  = 0;
1930        $parent = $tag->parent;
1931        while ( $parent > 0 ) {
1932                $parent_tag = get_term( $parent, $taxonomy );
1933                $parent     = $parent_tag->parent;
1934                $level++;
1935        }
1936        $wp_list_table->single_row( $tag, $level );
1937        wp_die();
1938}
1939
1940/**
1941 * Ajax handler for querying posts for the Find Posts modal.
1942 *
1943 * @see window.findPosts
1944 *
1945 * @since 3.1.0
1946 */
1947function wp_ajax_find_posts() {
1948        check_ajax_referer( 'find-posts' );
1949
1950        $post_types = get_post_types( array( 'public' => true ), 'objects' );
1951        unset( $post_types['attachment'] );
1952
1953        $s    = wp_unslash( $_POST['ps'] );
1954        $args = array(
1955                'post_type'      => array_keys( $post_types ),
1956                'post_status'    => 'any',
1957                'posts_per_page' => 50,
1958        );
1959        if ( '' !== $s ) {
1960                $args['s'] = $s;
1961        }
1962
1963        $posts = get_posts( $args );
1964
1965        if ( ! $posts ) {
1966                wp_send_json_error( __( 'No items found.' ) );
1967        }
1968
1969        $html = '<table class="widefat"><thead><tr><th class="found-radio"><br /></th><th>' . __( 'Title' ) . '</th><th class="no-break">' . __( 'Type' ) . '</th><th class="no-break">' . __( 'Date' ) . '</th><th class="no-break">' . __( 'Status' ) . '</th></tr></thead><tbody>';
1970        $alt  = '';
1971        foreach ( $posts as $post ) {
1972                $title = trim( $post->post_title ) ? $post->post_title : __( '(no title)' );
1973                $alt   = ( 'alternate' == $alt ) ? '' : 'alternate';
1974
1975                switch ( $post->post_status ) {
1976                        case 'publish':
1977                        case 'private':
1978                                $stat = __( 'Published' );
1979                                break;
1980                        case 'future':
1981                                $stat = __( 'Scheduled' );
1982                                break;
1983                        case 'pending':
1984                                $stat = __( 'Pending Review' );
1985                                break;
1986                        case 'draft':
1987                                $stat = __( 'Draft' );
1988                                break;
1989                }
1990
1991                if ( '0000-00-00 00:00:00' == $post->post_date ) {
1992                        $time = '';
1993                } else {
1994                        /* translators: date format in table columns, see https://secure.php.net/date */
1995                        $time = mysql2date( __( 'Y/m/d' ), $post->post_date );
1996                }
1997
1998                $html .= '<tr class="' . trim( 'found-posts ' . $alt ) . '"><td class="found-radio"><input type="radio" id="found-' . $post->ID . '" name="found_post_id" value="' . esc_attr( $post->ID ) . '"></td>';
1999                $html .= '<td><label for="found-' . $post->ID . '">' . esc_html( $title ) . '</label></td><td class="no-break">' . esc_html( $post_types[ $post->post_type ]->labels->singular_name ) . '</td><td class="no-break">' . esc_html( $time ) . '</td><td class="no-break">' . esc_html( $stat ) . ' </td></tr>' . "\n\n";
2000        }
2001
2002        $html .= '</tbody></table>';
2003
2004        wp_send_json_success( $html );
2005}
2006
2007/**
2008 * Ajax handler for saving the widgets order.
2009 *
2010 * @since 3.1.0
2011 */
2012function wp_ajax_widgets_order() {
2013        check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
2014
2015        if ( ! current_user_can( 'edit_theme_options' ) ) {
2016                wp_die( -1 );
2017        }
2018
2019        unset( $_POST['savewidgets'], $_POST['action'] );
2020
2021        // Save widgets order for all sidebars.
2022        if ( is_array( $_POST['sidebars'] ) ) {
2023                $sidebars = array();
2024                foreach ( wp_unslash( $_POST['sidebars'] ) as $key => $val ) {
2025                        $sb = array();
2026                        if ( ! empty( $val ) ) {
2027                                $val = explode( ',', $val );
2028                                foreach ( $val as $k => $v ) {
2029                                        if ( strpos( $v, 'widget-' ) === false ) {
2030                                                continue;
2031                                        }
2032
2033                                        $sb[ $k ] = substr( $v, strpos( $v, '_' ) + 1 );
2034                                }
2035                        }
2036                        $sidebars[ $key ] = $sb;
2037                }
2038                wp_set_sidebars_widgets( $sidebars );
2039                wp_die( 1 );
2040        }
2041
2042        wp_die( -1 );
2043}
2044
2045/**
2046 * Ajax handler for saving a widget.
2047 *
2048 * @since 3.1.0
2049 *
2050 * @global array $wp_registered_widgets
2051 * @global array $wp_registered_widget_controls
2052 * @global array $wp_registered_widget_updates
2053 */
2054function wp_ajax_save_widget() {
2055        global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates;
2056
2057        check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
2058
2059        if ( ! current_user_can( 'edit_theme_options' ) || ! isset( $_POST['id_base'] ) ) {
2060                wp_die( -1 );
2061        }
2062
2063        unset( $_POST['savewidgets'], $_POST['action'] );
2064
2065        /**
2066         * Fires early when editing the widgets displayed in sidebars.
2067         *
2068         * @since 2.8.0
2069         */
2070        do_action( 'load-widgets.php' );
2071
2072        /**
2073         * Fires early when editing the widgets displayed in sidebars.
2074         *
2075         * @since 2.8.0
2076         */
2077        do_action( 'widgets.php' );
2078
2079        /** This action is documented in wp-admin/widgets.php */
2080        do_action( 'sidebar_admin_setup' );
2081
2082        $id_base      = wp_unslash( $_POST['id_base'] );
2083        $widget_id    = wp_unslash( $_POST['widget-id'] );
2084        $sidebar_id   = $_POST['sidebar'];
2085        $multi_number = ! empty( $_POST['multi_number'] ) ? (int) $_POST['multi_number'] : 0;
2086        $settings     = isset( $_POST[ 'widget-' . $id_base ] ) && is_array( $_POST[ 'widget-' . $id_base ] ) ? $_POST[ 'widget-' . $id_base ] : false;
2087        $error        = '<p>' . __( 'An error has occurred. Please reload the page and try again.' ) . '</p>';
2088
2089        $sidebars = wp_get_sidebars_widgets();
2090        $sidebar  = isset( $sidebars[ $sidebar_id ] ) ? $sidebars[ $sidebar_id ] : array();
2091
2092        // Delete.
2093        if ( isset( $_POST['delete_widget'] ) && $_POST['delete_widget'] ) {
2094
2095                if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) {
2096                        wp_die( $error );
2097                }
2098
2099                $sidebar = array_diff( $sidebar, array( $widget_id ) );
2100                $_POST   = array(
2101                        'sidebar'            => $sidebar_id,
2102                        'widget-' . $id_base => array(),
2103                        'the-widget-id'      => $widget_id,
2104                        'delete_widget'      => '1',
2105                );
2106
2107                /** This action is documented in wp-admin/widgets.php */
2108                do_action( 'delete_widget', $widget_id, $sidebar_id, $id_base );
2109
2110        } elseif ( $settings && preg_match( '/__i__|%i%/', key( $settings ) ) ) {
2111                if ( ! $multi_number ) {
2112                        wp_die( $error );
2113                }
2114
2115                $_POST[ 'widget-' . $id_base ] = array( $multi_number => reset( $settings ) );
2116                $widget_id                     = $id_base . '-' . $multi_number;
2117                $sidebar[]                     = $widget_id;
2118        }
2119        $_POST['widget-id'] = $sidebar;
2120
2121        foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
2122
2123                if ( $name == $id_base ) {
2124                        if ( ! is_callable( $control['callback'] ) ) {
2125                                continue;
2126                        }
2127
2128                        ob_start();
2129                                call_user_func_array( $control['callback'], $control['params'] );
2130                        ob_end_clean();
2131                        break;
2132                }
2133        }
2134
2135        if ( isset( $_POST['delete_widget'] ) && $_POST['delete_widget'] ) {
2136                $sidebars[ $sidebar_id ] = $sidebar;
2137                wp_set_sidebars_widgets( $sidebars );
2138                echo "deleted:$widget_id";
2139                wp_die();
2140        }
2141
2142        if ( ! empty( $_POST['add_new'] ) ) {
2143                wp_die();
2144        }
2145
2146        if ( $form = $wp_registered_widget_controls[ $widget_id ] ) {
2147                call_user_func_array( $form['callback'], $form['params'] );
2148        }
2149
2150        wp_die();
2151}
2152
2153/**
2154 * Ajax handler for updating a widget.
2155 *
2156 * @since 3.9.0
2157 *
2158 * @global WP_Customize_Manager $wp_customize
2159 */
2160function wp_ajax_update_widget() {
2161        global $wp_customize;
2162        $wp_customize->widgets->wp_ajax_update_widget();
2163}
2164
2165/**
2166 * Ajax handler for removing inactive widgets.
2167 *
2168 * @since 4.4.0
2169 */
2170function wp_ajax_delete_inactive_widgets() {
2171        check_ajax_referer( 'remove-inactive-widgets', 'removeinactivewidgets' );
2172
2173        if ( ! current_user_can( 'edit_theme_options' ) ) {
2174                wp_die( -1 );
2175        }
2176
2177        unset( $_POST['removeinactivewidgets'], $_POST['action'] );
2178        /** This action is documented in wp-admin/includes/ajax-actions.php */
2179        do_action( 'load-widgets.php' );
2180        /** This action is documented in wp-admin/includes/ajax-actions.php */
2181        do_action( 'widgets.php' );
2182        /** This action is documented in wp-admin/widgets.php */
2183        do_action( 'sidebar_admin_setup' );
2184
2185        $sidebars_widgets = wp_get_sidebars_widgets();
2186
2187        foreach ( $sidebars_widgets['wp_inactive_widgets'] as $key => $widget_id ) {
2188                $pieces       = explode( '-', $widget_id );
2189                $multi_number = array_pop( $pieces );
2190                $id_base      = implode( '-', $pieces );
2191                $widget       = get_option( 'widget_' . $id_base );
2192                unset( $widget[ $multi_number ] );
2193                update_option( 'widget_' . $id_base, $widget );
2194                unset( $sidebars_widgets['wp_inactive_widgets'][ $key ] );
2195        }
2196
2197        wp_set_sidebars_widgets( $sidebars_widgets );
2198
2199        wp_die();
2200}
2201
2202/**
2203 * Ajax handler for uploading attachments
2204 *
2205 * @since 3.3.0
2206 */
2207function wp_ajax_upload_attachment() {
2208        check_ajax_referer( 'media-form' );
2209        /*
2210         * This function does not use wp_send_json_success() / wp_send_json_error()
2211         * as the html4 Plupload handler requires a text/html content-type for older IE.
2212         * See https://core.trac.wordpress.org/ticket/31037
2213         */
2214
2215        if ( ! current_user_can( 'upload_files' ) ) {
2216                echo wp_json_encode(
2217                        array(
2218                                'success' => false,
2219                                'data'    => array(
2220                                        'message'  => __( 'Sorry, you are not allowed to upload files.' ),
2221                                        'filename' => $_FILES['async-upload']['name'],
2222                                ),
2223                        )
2224                );
2225
2226                wp_die();
2227        }
2228
2229        if ( isset( $_REQUEST['post_id'] ) ) {
2230                $post_id = $_REQUEST['post_id'];
2231                if ( ! current_user_can( 'edit_post', $post_id ) ) {
2232                        echo wp_json_encode(
2233                                array(
2234                                        'success' => false,
2235                                        'data'    => array(
2236                                                'message'  => __( 'Sorry, you are not allowed to attach files to this post.' ),
2237                                                'filename' => $_FILES['async-upload']['name'],
2238                                        ),
2239                                )
2240                        );
2241
2242                        wp_die();
2243                }
2244        } else {
2245                $post_id = null;
2246        }
2247
2248        $post_data = isset( $_REQUEST['post_data'] ) ? $_REQUEST['post_data'] : array();
2249
2250        // If the context is custom header or background, make sure the uploaded file is an image.
2251        if ( isset( $post_data['context'] ) && in_array( $post_data['context'], array( 'custom-header', 'custom-background' ) ) ) {
2252                $wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['name'] );
2253                if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
2254                        echo wp_json_encode(
2255                                array(
2256                                        'success' => false,
2257                                        'data'    => array(
2258                                                'message'  => __( 'The uploaded file is not a valid image. Please try again.' ),
2259                                                'filename' => $_FILES['async-upload']['name'],
2260                                        ),
2261                                )
2262                        );
2263
2264                        wp_die();
2265                }
2266        }
2267
2268        $attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data );
2269
2270        if ( is_wp_error( $attachment_id ) ) {
2271                echo wp_json_encode(
2272                        array(
2273                                'success' => false,
2274                                'data'    => array(
2275                                        'message'  => $attachment_id->get_error_message(),
2276                                        'filename' => $_FILES['async-upload']['name'],
2277                                ),
2278                        )
2279                );
2280
2281                wp_die();
2282        }
2283
2284        if ( isset( $post_data['context'] ) && isset( $post_data['theme'] ) ) {
2285                if ( 'custom-background' === $post_data['context'] ) {
2286                        update_post_meta( $attachment_id, '_wp_attachment_is_custom_background', $post_data['theme'] );
2287                }
2288
2289                if ( 'custom-header' === $post_data['context'] ) {
2290                        update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', $post_data['theme'] );
2291                }
2292        }
2293
2294        if ( ! $attachment = wp_prepare_attachment_for_js( $attachment_id ) ) {
2295                wp_die();
2296        }
2297
2298        echo wp_json_encode(
2299                array(
2300                        'success' => true,
2301                        'data'    => $attachment,
2302                )
2303        );
2304
2305        wp_die();
2306}
2307
2308/**
2309 * Ajax handler for image editing.
2310 *
2311 * @since 3.1.0
2312 */
2313function wp_ajax_image_editor() {
2314        $attachment_id = intval( $_POST['postid'] );
2315        if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
2316                wp_die( -1 );
2317        }
2318
2319        check_ajax_referer( "image_editor-$attachment_id" );
2320        include_once( ABSPATH . 'wp-admin/includes/image-edit.php' );
2321
2322        $msg = false;
2323        switch ( $_POST['do'] ) {
2324                case 'save':
2325                        $msg = wp_save_image( $attachment_id );
2326                        $msg = wp_json_encode( $msg );
2327                        wp_die( $msg );
2328                        break;
2329                case 'scale':
2330                        $msg = wp_save_image( $attachment_id );
2331                        break;
2332                case 'restore':
2333                        $msg = wp_restore_image( $attachment_id );
2334                        break;
2335        }
2336
2337        wp_image_editor( $attachment_id, $msg );
2338        wp_die();
2339}
2340
2341/**
2342 * Ajax handler for setting the featured image.
2343 *
2344 * @since 3.1.0
2345 */
2346function wp_ajax_set_post_thumbnail() {
2347        $json = ! empty( $_REQUEST['json'] ); // New-style request
2348
2349        $post_ID = intval( $_POST['post_id'] );
2350        if ( ! current_user_can( 'edit_post', $post_ID ) ) {
2351                wp_die( -1 );
2352        }
2353
2354        $thumbnail_id = intval( $_POST['thumbnail_id'] );
2355
2356        if ( $json ) {
2357                check_ajax_referer( "update-post_$post_ID" );
2358        } else {
2359                check_ajax_referer( "set_post_thumbnail-$post_ID" );
2360        }
2361
2362        if ( $thumbnail_id == '-1' ) {
2363                if ( delete_post_thumbnail( $post_ID ) ) {
2364                        $return = _wp_post_thumbnail_html( null, $post_ID );
2365                        $json ? wp_send_json_success( $return ) : wp_die( $return );
2366                } else {
2367                        wp_die( 0 );
2368                }
2369        }
2370
2371        if ( set_post_thumbnail( $post_ID, $thumbnail_id ) ) {
2372                $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
2373                $json ? wp_send_json_success( $return ) : wp_die( $return );
2374        }
2375
2376        wp_die( 0 );
2377}
2378
2379/**
2380 * Ajax handler for retrieving HTML for the featured image.
2381 *
2382 * @since 4.6.0
2383 */
2384function wp_ajax_get_post_thumbnail_html() {
2385        $post_ID = intval( $_POST['post_id'] );
2386
2387        check_ajax_referer( "update-post_$post_ID" );
2388
2389        if ( ! current_user_can( 'edit_post', $post_ID ) ) {
2390                wp_die( -1 );
2391        }
2392
2393        $thumbnail_id = intval( $_POST['thumbnail_id'] );
2394
2395        // For backward compatibility, -1 refers to no featured image.
2396        if ( -1 === $thumbnail_id ) {
2397                $thumbnail_id = null;
2398        }
2399
2400        $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
2401        wp_send_json_success( $return );
2402}
2403
2404/**
2405 * Ajax handler for setting the featured image for an attachment.
2406 *
2407 * @since 4.0.0
2408 *
2409 * @see set_post_thumbnail()
2410 */
2411function wp_ajax_set_attachment_thumbnail() {
2412        if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) {
2413                wp_send_json_error();
2414        }
2415
2416        $thumbnail_id = (int) $_POST['thumbnail_id'];
2417        if ( empty( $thumbnail_id ) ) {
2418                wp_send_json_error();
2419        }
2420
2421        $post_ids = array();
2422        // For each URL, try to find its corresponding post ID.
2423        foreach ( $_POST['urls'] as $url ) {
2424                $post_id = attachment_url_to_postid( $url );
2425                if ( ! empty( $post_id ) ) {
2426                        $post_ids[] = $post_id;
2427                }
2428        }
2429
2430        if ( empty( $post_ids ) ) {
2431                wp_send_json_error();
2432        }
2433
2434        $success = 0;
2435        // For each found attachment, set its thumbnail.
2436        foreach ( $post_ids as $post_id ) {
2437                if ( ! current_user_can( 'edit_post', $post_id ) ) {
2438                        continue;
2439                }
2440
2441                if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2442                        $success++;
2443                }
2444        }
2445
2446        if ( 0 === $success ) {
2447                wp_send_json_error();
2448        } else {
2449                wp_send_json_success();
2450        }
2451
2452        wp_send_json_error();
2453}
2454
2455/**
2456 * Ajax handler for date formatting.
2457 *
2458 * @since 3.1.0
2459 */
2460function wp_ajax_date_format() {
2461        wp_die( date_i18n( sanitize_option( 'date_format', wp_unslash( $_POST['date'] ) ) ) );
2462}
2463
2464/**
2465 * Ajax handler for time formatting.
2466 *
2467 * @since 3.1.0
2468 */
2469function wp_ajax_time_format() {
2470        wp_die( date_i18n( sanitize_option( 'time_format', wp_unslash( $_POST['date'] ) ) ) );
2471}
2472
2473/**
2474 * Ajax handler for saving posts from the fullscreen editor.
2475 *
2476 * @since 3.1.0
2477 * @deprecated 4.3.0
2478 */
2479function wp_ajax_wp_fullscreen_save_post() {
2480        $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
2481
2482        $post = null;
2483
2484        if ( $post_id ) {
2485                $post = get_post( $post_id );
2486        }
2487
2488        check_ajax_referer( 'update-post_' . $post_id, '_wpnonce' );
2489
2490        $post_id = edit_post();
2491
2492        if ( is_wp_error( $post_id ) ) {
2493                wp_send_json_error();
2494        }
2495
2496        if ( $post ) {
2497                $last_date = mysql2date( __( 'F j, Y' ), $post->post_modified );
2498                $last_time = mysql2date( __( 'g:i a' ), $post->post_modified );
2499        } else {
2500                $last_date = date_i18n( __( 'F j, Y' ) );
2501                $last_time = date_i18n( __( 'g:i a' ) );
2502        }
2503
2504        if ( $last_id = get_post_meta( $post_id, '_edit_last', true ) ) {
2505                $last_user   = get_userdata( $last_id );
2506                $last_edited = sprintf( __( 'Last edited by %1$s on %2$s at %3$s' ), esc_html( $last_user->display_name ), $last_date, $last_time );
2507        } else {
2508                $last_edited = sprintf( __( 'Last edited on %1$s at %2$s' ), $last_date, $last_time );
2509        }
2510
2511        wp_send_json_success( array( 'last_edited' => $last_edited ) );
2512}
2513
2514/**
2515 * Ajax handler for removing a post lock.
2516 *
2517 * @since 3.1.0
2518 */
2519function wp_ajax_wp_remove_post_lock() {
2520        if ( empty( $_POST['post_ID'] ) || empty( $_POST['active_post_lock'] ) ) {
2521                wp_die( 0 );
2522        }
2523        $post_id = (int) $_POST['post_ID'];
2524        if ( ! $post = get_post( $post_id ) ) {
2525                wp_die( 0 );
2526        }
2527
2528        check_ajax_referer( 'update-post_' . $post_id );
2529
2530        if ( ! current_user_can( 'edit_post', $post_id ) ) {
2531                wp_die( -1 );
2532        }
2533
2534        $active_lock = array_map( 'absint', explode( ':', $_POST['active_post_lock'] ) );
2535        if ( $active_lock[1] != get_current_user_id() ) {
2536                wp_die( 0 );
2537        }
2538
2539        /**
2540         * Filters the post lock window duration.
2541         *
2542         * @since 3.3.0
2543         *
2544         * @param int $interval The interval in seconds the post lock duration
2545         *                      should last, plus 5 seconds. Default 150.
2546         */
2547        $new_lock = ( time() - apply_filters( 'wp_check_post_lock_window', 150 ) + 5 ) . ':' . $active_lock[1];
2548        update_post_meta( $post_id, '_edit_lock', $new_lock, implode( ':', $active_lock ) );
2549        wp_die( 1 );
2550}
2551
2552/**
2553 * Ajax handler for dismissing a WordPress pointer.
2554 *
2555 * @since 3.1.0
2556 */
2557function wp_ajax_dismiss_wp_pointer() {
2558        $pointer = $_POST['pointer'];
2559        if ( $pointer != sanitize_key( $pointer ) ) {
2560                wp_die( 0 );
2561        }
2562
2563        //  check_ajax_referer( 'dismiss-pointer_' . $pointer );
2564
2565        $dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
2566
2567        if ( in_array( $pointer, $dismissed ) ) {
2568                wp_die( 0 );
2569        }
2570
2571        $dismissed[] = $pointer;
2572        $dismissed   = implode( ',', $dismissed );
2573
2574        update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
2575        wp_die( 1 );
2576}
2577
2578/**
2579 * Ajax handler for getting an attachment.
2580 *
2581 * @since 3.5.0
2582 */
2583function wp_ajax_get_attachment() {
2584        if ( ! isset( $_REQUEST['id'] ) ) {
2585                wp_send_json_error();
2586        }
2587
2588        if ( ! $id = absint( $_REQUEST['id'] ) ) {
2589                wp_send_json_error();
2590        }
2591
2592        if ( ! $post = get_post( $id ) ) {
2593                wp_send_json_error();
2594        }
2595
2596        if ( 'attachment' != $post->post_type ) {
2597                wp_send_json_error();
2598        }
2599
2600        if ( ! current_user_can( 'upload_files' ) ) {
2601                wp_send_json_error();
2602        }
2603
2604        if ( ! $attachment = wp_prepare_attachment_for_js( $id ) ) {
2605                wp_send_json_error();
2606        }
2607
2608        wp_send_json_success( $attachment );
2609}
2610
2611/**
2612 * Ajax handler for querying attachments.
2613 *
2614 * @since 3.5.0
2615 */
2616function wp_ajax_query_attachments() {
2617        if ( ! current_user_can( 'upload_files' ) ) {
2618                wp_send_json_error();
2619        }
2620
2621        $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
2622        $keys  = array(
2623                's',
2624                'order',
2625                'orderby',
2626                'posts_per_page',
2627                'paged',
2628                'post_mime_type',
2629                'post_parent',
2630                'author',
2631                'post__in',
2632                'post__not_in',
2633                'year',
2634                'monthnum',
2635        );
2636        foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) {
2637                if ( $t->query_var && isset( $query[ $t->query_var ] ) ) {
2638                        $keys[] = $t->query_var;
2639                }
2640        }
2641
2642        $query              = array_intersect_key( $query, array_flip( $keys ) );
2643        $query['post_type'] = 'attachment';
2644        if ( MEDIA_TRASH
2645                && ! empty( $_REQUEST['query']['post_status'] )
2646                && 'trash' === $_REQUEST['query']['post_status'] ) {
2647                $query['post_status'] = 'trash';
2648        } else {
2649                $query['post_status'] = 'inherit';
2650        }
2651
2652        if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) ) {
2653                $query['post_status'] .= ',private';
2654        }
2655
2656        // Filter query clauses to include filenames.
2657        if ( isset( $query['s'] ) ) {
2658                add_filter( 'posts_clauses', '_filter_query_attachment_filenames' );
2659        }
2660
2661        /**
2662         * Filters the arguments passed to WP_Query during an Ajax
2663         * call for querying attachments.
2664         *
2665         * @since 3.7.0
2666         *
2667         * @see WP_Query::parse_query()
2668         *
2669         * @param array $query An array of query variables.
2670         */
2671        $query = apply_filters( 'ajax_query_attachments_args', $query );
2672        $query = new WP_Query( $query );
2673
2674        $posts = array_map( 'wp_prepare_attachment_for_js', $query->posts );
2675        $posts = array_filter( $posts );
2676
2677        wp_send_json_success( $posts );
2678}
2679
2680/**
2681 * Ajax handler for updating attachment attributes.
2682 *
2683 * @since 3.5.0
2684 */
2685function wp_ajax_save_attachment() {
2686        if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) ) {
2687                wp_send_json_error();
2688        }
2689
2690        if ( ! $id = absint( $_REQUEST['id'] ) ) {
2691                wp_send_json_error();
2692        }
2693
2694        check_ajax_referer( 'update-post_' . $id, 'nonce' );
2695
2696        if ( ! current_user_can( 'edit_post', $id ) ) {
2697                wp_send_json_error();
2698        }
2699
2700        $changes = $_REQUEST['changes'];
2701        $post    = get_post( $id, ARRAY_A );
2702
2703        if ( 'attachment' != $post['post_type'] ) {
2704                wp_send_json_error();
2705        }
2706
2707        if ( isset( $changes['parent'] ) ) {
2708                $post['post_parent'] = $changes['parent'];
2709        }
2710
2711        if ( isset( $changes['title'] ) ) {
2712                $post['post_title'] = $changes['title'];
2713        }
2714
2715        if ( isset( $changes['caption'] ) ) {
2716                $post['post_excerpt'] = $changes['caption'];
2717        }
2718
2719        if ( isset( $changes['description'] ) ) {
2720                $post['post_content'] = $changes['description'];
2721        }
2722
2723        if ( MEDIA_TRASH && isset( $changes['status'] ) ) {
2724                $post['post_status'] = $changes['status'];
2725        }
2726
2727        if ( isset( $changes['alt'] ) ) {
2728                $alt = wp_unslash( $changes['alt'] );
2729                if ( $alt != get_post_meta( $id, '_wp_attachment_image_alt', true ) ) {
2730                        $alt = wp_strip_all_tags( $alt, true );
2731                        update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) );
2732                }
2733        }
2734
2735        if ( wp_attachment_is( 'audio', $post['ID'] ) ) {
2736                $changed = false;
2737                $id3data = wp_get_attachment_metadata( $post['ID'] );
2738                if ( ! is_array( $id3data ) ) {
2739                        $changed = true;
2740                        $id3data = array();
2741                }
2742                foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
2743                        if ( isset( $changes[ $key ] ) ) {
2744                                $changed         = true;
2745                                $id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
2746                        }
2747                }
2748
2749                if ( $changed ) {
2750                        wp_update_attachment_metadata( $id, $id3data );
2751                }
2752        }
2753
2754        if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
2755                wp_delete_post( $id );
2756        } else {
2757                wp_update_post( $post );
2758        }
2759
2760        wp_send_json_success();
2761}
2762
2763/**
2764 * Ajax handler for saving backward compatible attachment attributes.
2765 *
2766 * @since 3.5.0
2767 */
2768function wp_ajax_save_attachment_compat() {
2769        if ( ! isset( $_REQUEST['id'] ) ) {
2770                wp_send_json_error();
2771        }
2772
2773        if ( ! $id = absint( $_REQUEST['id'] ) ) {
2774                wp_send_json_error();
2775        }
2776
2777        if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) ) {
2778                wp_send_json_error();
2779        }
2780        $attachment_data = $_REQUEST['attachments'][ $id ];
2781
2782        check_ajax_referer( 'update-post_' . $id, 'nonce' );
2783
2784        if ( ! current_user_can( 'edit_post', $id ) ) {
2785                wp_send_json_error();
2786        }
2787
2788        $post = get_post( $id, ARRAY_A );
2789
2790        if ( 'attachment' != $post['post_type'] ) {
2791                wp_send_json_error();
2792        }
2793
2794        /** This filter is documented in wp-admin/includes/media.php */
2795        $post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data );
2796
2797        if ( isset( $post['errors'] ) ) {
2798                $errors = $post['errors']; // @todo return me and display me!
2799                unset( $post['errors'] );
2800        }
2801
2802        wp_update_post( $post );
2803
2804        foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
2805                if ( isset( $attachment_data[ $taxonomy ] ) ) {
2806                        wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false );
2807                }
2808        }
2809
2810        if ( ! $attachment = wp_prepare_attachment_for_js( $id ) ) {
2811                wp_send_json_error();
2812        }
2813
2814        wp_send_json_success( $attachment );
2815}
2816
2817/**
2818 * Ajax handler for saving the attachment order.
2819 *
2820 * @since 3.5.0
2821 */
2822function wp_ajax_save_attachment_order() {
2823        if ( ! isset( $_REQUEST['post_id'] ) ) {
2824                wp_send_json_error();
2825        }
2826
2827        if ( ! $post_id = absint( $_REQUEST['post_id'] ) ) {
2828                wp_send_json_error();
2829        }
2830
2831        if ( empty( $_REQUEST['attachments'] ) ) {
2832                wp_send_json_error();
2833        }
2834
2835        check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
2836
2837        $attachments = $_REQUEST['attachments'];
2838
2839        if ( ! current_user_can( 'edit_post', $post_id ) ) {
2840                wp_send_json_error();
2841        }
2842
2843        foreach ( $attachments as $attachment_id => $menu_order ) {
2844                if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
2845                        continue;
2846                }
2847                if ( ! $attachment = get_post( $attachment_id ) ) {
2848                        continue;
2849                }
2850                if ( 'attachment' != $attachment->post_type ) {
2851                        continue;
2852                }
2853
2854                wp_update_post(
2855                        array(
2856                                'ID'         => $attachment_id,
2857                                'menu_order' => $menu_order,
2858                        )
2859                );
2860        }
2861
2862        wp_send_json_success();
2863}
2864
2865/**
2866 * Ajax handler for sending an attachment to the editor.
2867 *
2868 * Generates the HTML to send an attachment to the editor.
2869 * Backward compatible with the {@see 'media_send_to_editor'} filter
2870 * and the chain of filters that follow.
2871 *
2872 * @since 3.5.0
2873 */
2874function wp_ajax_send_attachment_to_editor() {
2875        check_ajax_referer( 'media-send-to-editor', 'nonce' );
2876
2877        $attachment = wp_unslash( $_POST['attachment'] );
2878
2879        $id = intval( $attachment['id'] );
2880
2881        if ( ! $post = get_post( $id ) ) {
2882                wp_send_json_error();
2883        }
2884
2885        if ( 'attachment' != $post->post_type ) {
2886                wp_send_json_error();
2887        }
2888
2889        if ( current_user_can( 'edit_post', $id ) ) {
2890                // If this attachment is unattached, attach it. Primarily a back compat thing.
2891                if ( 0 == $post->post_parent && $insert_into_post_id = intval( $_POST['post_id'] ) ) {
2892                        wp_update_post(
2893                                array(
2894                                        'ID'          => $id,
2895                                        'post_parent' => $insert_into_post_id,
2896                                )
2897                        );
2898                }
2899        }
2900
2901        $url = empty( $attachment['url'] ) ? '' : $attachment['url'];
2902        $rel = ( strpos( $url, 'attachment_id' ) || get_attachment_link( $id ) == $url );
2903
2904        remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
2905
2906        if ( 'image' === substr( $post->post_mime_type, 0, 5 ) ) {
2907                $align = isset( $attachment['align'] ) ? $attachment['align'] : 'none';
2908                $size  = isset( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium';
2909                $alt   = isset( $attachment['image_alt'] ) ? $attachment['image_alt'] : '';
2910
2911                // No whitespace-only captions.
2912                $caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : '';
2913                if ( '' === trim( $caption ) ) {
2914                        $caption = '';
2915                }
2916
2917                $title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
2918                $html  = get_image_send_to_editor( $id, $caption, $title, $align, $url, $rel, $size, $alt );
2919        } elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post ) ) {
2920                $html = stripslashes_deep( $_POST['html'] );
2921        } else {
2922                $html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : '';
2923                $rel  = $rel ? ' rel="attachment wp-att-' . $id . '"' : ''; // Hard-coded string, $id is already sanitized
2924
2925                if ( ! empty( $url ) ) {
2926                        $html = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
2927                }
2928        }
2929
2930        /** This filter is documented in wp-admin/includes/media.php */
2931        $html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
2932
2933        wp_send_json_success( $html );
2934}
2935
2936/**
2937 * Ajax handler for sending a link to the editor.
2938 *
2939 * Generates the HTML to send a non-image embed link to the editor.
2940 *
2941 * Backward compatible with the following filters:
2942 * - file_send_to_editor_url
2943 * - audio_send_to_editor_url
2944 * - video_send_to_editor_url
2945 *
2946 * @since 3.5.0
2947 *
2948 * @global WP_Post  $post
2949 * @global WP_Embed $wp_embed
2950 */
2951function wp_ajax_send_link_to_editor() {
2952        global $post, $wp_embed;
2953
2954        check_ajax_referer( 'media-send-to-editor', 'nonce' );
2955
2956        if ( ! $src = wp_unslash( $_POST['src'] ) ) {
2957                wp_send_json_error();
2958        }
2959
2960        if ( ! strpos( $src, '://' ) ) {
2961                $src = 'http://' . $src;
2962        }
2963
2964        if ( ! $src = esc_url_raw( $src ) ) {
2965                wp_send_json_error();
2966        }
2967
2968        if ( ! $link_text = trim( wp_unslash( $_POST['link_text'] ) ) ) {
2969                $link_text = wp_basename( $src );
2970        }
2971
2972        $post = get_post( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
2973
2974        // Ping WordPress for an embed.
2975        $check_embed = $wp_embed->run_shortcode( '[embed]' . $src . '[/embed]' );
2976
2977        // Fallback that WordPress creates when no oEmbed was found.
2978        $fallback = $wp_embed->maybe_make_link( $src );
2979
2980        if ( $check_embed !== $fallback ) {
2981                // TinyMCE view for [embed] will parse this
2982                $html = '[embed]' . $src . '[/embed]';
2983        } elseif ( $link_text ) {
2984                $html = '<a href="' . esc_url( $src ) . '">' . $link_text . '</a>';
2985        } else {
2986                $html = '';
2987        }
2988
2989        // Figure out what filter to run:
2990        $type = 'file';
2991        if ( ( $ext = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src ) ) && ( $ext_type = wp_ext2type( $ext ) )
2992                && ( 'audio' == $ext_type || 'video' == $ext_type ) ) {
2993                        $type = $ext_type;
2994        }
2995
2996        /** This filter is documented in wp-admin/includes/media.php */
2997        $html = apply_filters( "{$type}_send_to_editor_url", $html, $src, $link_text );
2998
2999        wp_send_json_success( $html );
3000}
3001
3002/**
3003 * Ajax handler for the Heartbeat API.
3004 *
3005 * Runs when the user is logged in.
3006 *
3007 * @since 3.6.0
3008 */
3009function wp_ajax_heartbeat() {
3010        if ( empty( $_POST['_nonce'] ) ) {
3011                wp_send_json_error();
3012        }
3013
3014        $response    = $data = array();
3015        $nonce_state = wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' );
3016
3017        // screen_id is the same as $current_screen->id and the JS global 'pagenow'.
3018        if ( ! empty( $_POST['screen_id'] ) ) {
3019                $screen_id = sanitize_key( $_POST['screen_id'] );
3020        } else {
3021                $screen_id = 'front';
3022        }
3023
3024        if ( ! empty( $_POST['data'] ) ) {
3025                $data = wp_unslash( (array) $_POST['data'] );
3026        }
3027
3028        if ( 1 !== $nonce_state ) {
3029                /**
3030                 * Filters the nonces to send to the New/Edit Post screen.
3031                 *
3032                 * @since 4.3.0
3033                 *
3034                 * @param array  $response  The Heartbeat response.
3035                 * @param array  $data      The $_POST data sent.
3036                 * @param string $screen_id The screen id.
3037                 */
3038                $response = apply_filters( 'wp_refresh_nonces', $response, $data, $screen_id );
3039
3040                if ( false === $nonce_state ) {
3041                        // User is logged in but nonces have expired.
3042                        $response['nonces_expired'] = true;
3043                        wp_send_json( $response );
3044                }
3045        }
3046
3047        if ( ! empty( $data ) ) {
3048                /**
3049                 * Filters the Heartbeat response received.
3050                 *
3051                 * @since 3.6.0
3052                 *
3053                 * @param array  $response  The Heartbeat response.
3054                 * @param array  $data      The $_POST data sent.
3055                 * @param string $screen_id The screen id.
3056                 */
3057                $response = apply_filters( 'heartbeat_received', $response, $data, $screen_id );
3058        }
3059
3060        /**
3061         * Filters the Heartbeat response sent.
3062         *
3063         * @since 3.6.0
3064         *
3065         * @param array  $response  The Heartbeat response.
3066         * @param string $screen_id The screen id.
3067         */
3068        $response = apply_filters( 'heartbeat_send', $response, $screen_id );
3069
3070        /**
3071         * Fires when Heartbeat ticks in logged-in environments.
3072         *
3073         * Allows the transport to be easily replaced with long-polling.
3074         *
3075         * @since 3.6.0
3076         *
3077         * @param array  $response  The Heartbeat response.
3078         * @param string $screen_id The screen id.
3079         */
3080        do_action( 'heartbeat_tick', $response, $screen_id );
3081
3082        // Send the current time according to the server
3083        $response['server_time'] = time();
3084
3085        wp_send_json( $response );
3086}
3087
3088/**
3089 * Ajax handler for getting revision diffs.
3090 *
3091 * @since 3.6.0
3092 */
3093function wp_ajax_get_revision_diffs() {
3094        require ABSPATH . 'wp-admin/includes/revision.php';
3095
3096        if ( ! $post = get_post( (int) $_REQUEST['post_id'] ) ) {
3097                wp_send_json_error();
3098        }
3099
3100        if ( ! current_user_can( 'edit_post', $post->ID ) ) {
3101                wp_send_json_error();
3102        }
3103
3104        // Really just pre-loading the cache here.
3105        if ( ! $revisions = wp_get_post_revisions( $post->ID, array( 'check_enabled' => false ) ) ) {
3106                wp_send_json_error();
3107        }
3108
3109        $return = array();
3110        @set_time_limit( 0 );
3111
3112        foreach ( $_REQUEST['compare'] as $compare_key ) {
3113                list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
3114
3115                $return[] = array(
3116                        'id'     => $compare_key,
3117                        'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
3118                );
3119        }
3120        wp_send_json_success( $return );
3121}
3122
3123/**
3124 * Ajax handler for auto-saving the selected color scheme for
3125 * a user's own profile.
3126 *
3127 * @since 3.8.0
3128 *
3129 * @global array $_wp_admin_css_colors
3130 */
3131function wp_ajax_save_user_color_scheme() {
3132        global $_wp_admin_css_colors;
3133
3134        check_ajax_referer( 'save-color-scheme', 'nonce' );
3135
3136        $color_scheme = sanitize_key( $_POST['color_scheme'] );
3137
3138        if ( ! isset( $_wp_admin_css_colors[ $color_scheme ] ) ) {
3139                wp_send_json_error();
3140        }
3141
3142        $previous_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true );
3143        update_user_meta( get_current_user_id(), 'admin_color', $color_scheme );
3144
3145        wp_send_json_success(
3146                array(
3147                        'previousScheme' => 'admin-color-' . $previous_color_scheme,
3148                        'currentScheme'  => 'admin-color-' . $color_scheme,
3149                )
3150        );
3151}
3152
3153/**
3154 * Ajax handler for getting themes from themes_api().
3155 *
3156 * @since 3.9.0
3157 *
3158 * @global array $themes_allowedtags
3159 * @global array $theme_field_defaults
3160 */
3161function wp_ajax_query_themes() {
3162        global $themes_allowedtags, $theme_field_defaults;
3163
3164        if ( ! current_user_can( 'install_themes' ) ) {
3165                wp_send_json_error();
3166        }
3167
3168        $args = wp_parse_args(
3169                wp_unslash( $_REQUEST['request'] ), array(
3170                        'per_page' => 20,
3171                        'fields'   => $theme_field_defaults,
3172                )
3173        );
3174
3175        if ( isset( $args['browse'] ) && 'favorites' === $args['browse'] && ! isset( $args['user'] ) ) {
3176                $user = get_user_option( 'wporg_favorites' );
3177                if ( $user ) {
3178                        $args['user'] = $user;
3179                }
3180        }
3181
3182        $old_filter = isset( $args['browse'] ) ? $args['browse'] : 'search';
3183
3184        /** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */
3185        $args = apply_filters( 'install_themes_table_api_args_' . $old_filter, $args );
3186
3187        $api = themes_api( 'query_themes', $args );
3188
3189        if ( is_wp_error( $api ) ) {
3190                wp_send_json_error();
3191        }
3192
3193        $update_php = network_admin_url( 'update.php?action=install-theme' );
3194        foreach ( $api->themes as &$theme ) {
3195                $theme->install_url = add_query_arg(
3196                        array(
3197                                'theme'    => $theme->slug,
3198                                '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ),
3199                        ), $update_php
3200                );
3201
3202                if ( current_user_can( 'switch_themes' ) ) {
3203                        if ( is_multisite() ) {
3204                                $theme->activate_url = add_query_arg(
3205                                        array(
3206                                                'action'   => 'enable',
3207                                                '_wpnonce' => wp_create_nonce( 'enable-theme_' . $theme->slug ),
3208                                                'theme'    => $theme->slug,
3209                                        ), network_admin_url( 'themes.php' )
3210                                );
3211                        } else {
3212                                $theme->activate_url = add_query_arg(
3213                                        array(
3214                                                'action'     => 'activate',
3215                                                '_wpnonce'   => wp_create_nonce( 'switch-theme_' . $theme->slug ),
3216                                                'stylesheet' => $theme->slug,
3217                                        ), admin_url( 'themes.php' )
3218                                );
3219                        }
3220                }
3221
3222                if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
3223                        $theme->customize_url = add_query_arg(
3224                                array(
3225                                        'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
3226                                ), wp_customize_url( $theme->slug )
3227                        );
3228                }
3229
3230                $theme->name        = wp_kses( $theme->name, $themes_allowedtags );
3231                $theme->author      = wp_kses( $theme->author['display_name'], $themes_allowedtags );
3232                $theme->version     = wp_kses( $theme->version, $themes_allowedtags );
3233                $theme->description = wp_kses( $theme->description, $themes_allowedtags );
3234                $theme->stars       = wp_star_rating(
3235                        array(
3236                                'rating' => $theme->rating,
3237                                'type'   => 'percent',
3238                                'number' => $theme->num_ratings,
3239                                'echo'   => false,
3240                        )
3241                );
3242                $theme->num_ratings = number_format_i18n( $theme->num_ratings );
3243                $theme->preview_url = set_url_scheme( $theme->preview_url );
3244        }
3245
3246        wp_send_json_success( $api );
3247}
3248
3249/**
3250 * Apply [embed] Ajax handlers to a string.
3251 *
3252 * @since 4.0.0
3253 *
3254 * @global WP_Post    $post       Global $post.
3255 * @global WP_Embed   $wp_embed   Embed API instance.
3256 * @global WP_Scripts $wp_scripts
3257 * @global int        $content_width
3258 */
3259function wp_ajax_parse_embed() {
3260        global $post, $wp_embed, $content_width;
3261
3262        if ( empty( $_POST['shortcode'] ) ) {
3263                wp_send_json_error();
3264        }
3265        $post_id = isset( $_POST['post_ID'] ) ? intval( $_POST['post_ID'] ) : 0;
3266        if ( $post_id > 0 ) {
3267                $post = get_post( $post_id );
3268                if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3269                        wp_send_json_error();
3270                }
3271                setup_postdata( $post );
3272        } elseif ( ! current_user_can( 'edit_posts' ) ) { // See WP_oEmbed_Controller::get_proxy_item_permissions_check().
3273                wp_send_json_error();
3274        }
3275
3276        $shortcode = wp_unslash( $_POST['shortcode'] );
3277
3278        preg_match( '/' . get_shortcode_regex() . '/s', $shortcode, $matches );
3279        $atts = shortcode_parse_atts( $matches[3] );
3280        if ( ! empty( $matches[5] ) ) {
3281                $url = $matches[5];
3282        } elseif ( ! empty( $atts['src'] ) ) {
3283                $url = $atts['src'];
3284        } else {
3285                $url = '';
3286        }
3287
3288        $parsed                         = false;
3289        $wp_embed->return_false_on_fail = true;
3290
3291        if ( 0 === $post_id ) {
3292                /*
3293                 * Refresh oEmbeds cached outside of posts that are past their TTL.
3294                 * Posts are excluded because they have separate logic for refreshing
3295                 * their post meta caches. See WP_Embed::cache_oembed().
3296                 */
3297                $wp_embed->usecache = false;
3298        }
3299
3300        if ( is_ssl() && 0 === strpos( $url, 'http://' ) ) {
3301                // Admin is ssl and the user pasted non-ssl URL.
3302                // Check if the provider supports ssl embeds and use that for the preview.
3303                $ssl_shortcode = preg_replace( '%^(\\[embed[^\\]]*\\])http://%i', '$1https://', $shortcode );
3304                $parsed        = $wp_embed->run_shortcode( $ssl_shortcode );
3305
3306                if ( ! $parsed ) {
3307                        $no_ssl_support = true;
3308                }
3309        }
3310
3311        // Set $content_width so any embeds fit in the destination iframe.
3312        if ( isset( $_POST['maxwidth'] ) && is_numeric( $_POST['maxwidth'] ) && $_POST['maxwidth'] > 0 ) {
3313                if ( ! isset( $content_width ) ) {
3314                        $content_width = intval( $_POST['maxwidth'] );
3315                } else {
3316                        $content_width = min( $content_width, intval( $_POST['maxwidth'] ) );
3317                }
3318        }
3319
3320        if ( $url && ! $parsed ) {
3321                $parsed = $wp_embed->run_shortcode( $shortcode );
3322        }
3323
3324        if ( ! $parsed ) {
3325                wp_send_json_error(
3326                        array(
3327                                'type'    => 'not-embeddable',
3328                                'message' => sprintf( __( '%s failed to embed.' ), '<code>' . esc_html( $url ) . '</code>' ),
3329                        )
3330                );
3331        }
3332
3333        if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
3334                $styles     = '';
3335                $mce_styles = wpview_media_sandbox_styles();
3336                foreach ( $mce_styles as $style ) {
3337                        $styles .= sprintf( '<link rel="stylesheet" href="%s"/>', $style );
3338                }
3339
3340                $html = do_shortcode( $parsed );
3341
3342                global $wp_scripts;
3343                if ( ! empty( $wp_scripts ) ) {
3344                        $wp_scripts->done = array();
3345                }
3346                ob_start();
3347                wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
3348                $scripts = ob_get_clean();
3349
3350                $parsed = $styles . $html . $scripts;
3351        }
3352
3353        if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
3354                preg_match( '%<link [^>]*href="http://%', $parsed ) ) ) ) {
3355                // Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
3356                wp_send_json_error(
3357                        array(
3358                                'type'    => 'not-ssl',
3359                                'message' => __( 'This preview is unavailable in the editor.' ),
3360                        )
3361                );
3362        }
3363
3364        $return = array(
3365                'body' => $parsed,
3366                'attr' => $wp_embed->last_attr,
3367        );
3368
3369        if ( strpos( $parsed, 'class="wp-embedded-content' ) ) {
3370                if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
3371                        $script_src = includes_url( 'js/wp-embed.js' );
3372                } else {
3373                        $script_src = includes_url( 'js/wp-embed.min.js' );
3374                }
3375
3376                $return['head']    = '<script src="' . $script_src . '"></script>';
3377                $return['sandbox'] = true;
3378        }
3379
3380        wp_send_json_success( $return );
3381}
3382
3383/**
3384 * @since 4.0.0
3385 *
3386 * @global WP_Post    $post
3387 * @global WP_Scripts $wp_scripts
3388 */
3389function wp_ajax_parse_media_shortcode() {
3390        global $post, $wp_scripts;
3391
3392        if ( empty( $_POST['shortcode'] ) ) {
3393                wp_send_json_error();
3394        }
3395
3396        $shortcode = wp_unslash( $_POST['shortcode'] );
3397
3398        if ( ! empty( $_POST['post_ID'] ) ) {
3399                $post = get_post( (int) $_POST['post_ID'] );
3400        }
3401
3402        // the embed shortcode requires a post
3403        if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3404                if ( 'embed' === $shortcode ) {
3405                        wp_send_json_error();
3406                }
3407        } else {
3408                setup_postdata( $post );
3409        }
3410
3411        $parsed = do_shortcode( $shortcode );
3412
3413        if ( empty( $parsed ) ) {
3414                wp_send_json_error(
3415                        array(
3416                                'type'    => 'no-items',
3417                                'message' => __( 'No items found.' ),
3418                        )
3419                );
3420        }
3421
3422        $head   = '';
3423        $styles = wpview_media_sandbox_styles();
3424
3425        foreach ( $styles as $style ) {
3426                $head .= '<link type="text/css" rel="stylesheet" href="' . $style . '">';
3427        }
3428
3429        if ( ! empty( $wp_scripts ) ) {
3430                $wp_scripts->done = array();
3431        }
3432
3433        ob_start();
3434
3435        echo $parsed;
3436
3437        if ( 'playlist' === $_REQUEST['type'] ) {
3438                wp_underscore_playlist_templates();
3439
3440                wp_print_scripts( 'wp-playlist' );
3441        } else {
3442                wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
3443        }
3444
3445        wp_send_json_success(
3446                array(
3447                        'head' => $head,
3448                        'body' => ob_get_clean(),
3449                )
3450        );
3451}
3452
3453/**
3454 * Ajax handler for destroying multiple open sessions for a user.
3455 *
3456 * @since 4.1.0
3457 */
3458function wp_ajax_destroy_sessions() {
3459        $user = get_userdata( (int) $_POST['user_id'] );
3460        if ( $user ) {
3461                if ( ! current_user_can( 'edit_user', $user->ID ) ) {
3462                        $user = false;
3463                } elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
3464                        $user = false;
3465                }
3466        }
3467
3468        if ( ! $user ) {
3469                wp_send_json_error(
3470                        array(
3471                                'message' => __( 'Could not log out user sessions. Please try again.' ),
3472                        )
3473                );
3474        }
3475
3476        $sessions = WP_Session_Tokens::get_instance( $user->ID );
3477
3478        if ( $user->ID === get_current_user_id() ) {
3479                $sessions->destroy_others( wp_get_session_token() );
3480                $message = __( 'You are now logged out everywhere else.' );
3481        } else {
3482                $sessions->destroy_all();
3483                /* translators: %s: User's display name. */
3484                $message = sprintf( __( '%s has been logged out.' ), $user->display_name );
3485        }
3486
3487        wp_send_json_success( array( 'message' => $message ) );
3488}
3489
3490/**
3491 * Ajax handler for cropping an image.
3492 *
3493 * @since 4.3.0
3494 */
3495function wp_ajax_crop_image() {
3496        $attachment_id = absint( $_POST['id'] );
3497
3498        check_ajax_referer( 'image_editor-' . $attachment_id, 'nonce' );
3499        if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
3500                wp_send_json_error();
3501        }
3502
3503        $context = str_replace( '_', '-', $_POST['context'] );
3504        $data    = array_map( 'absint', $_POST['cropDetails'] );
3505        $cropped = wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
3506
3507        if ( ! $cropped || is_wp_error( $cropped ) ) {
3508                wp_send_json_error( array( 'message' => __( 'Image could not be processed.' ) ) );
3509        }
3510
3511        switch ( $context ) {
3512                case 'site-icon':
3513                        require_once ABSPATH . '/wp-admin/includes/class-wp-site-icon.php';
3514                        $wp_site_icon = new WP_Site_Icon();
3515
3516                        // Skip creating a new attachment if the attachment is a Site Icon.
3517                        if ( get_post_meta( $attachment_id, '_wp_attachment_context', true ) == $context ) {
3518
3519                                // Delete the temporary cropped file, we don't need it.
3520                                wp_delete_file( $cropped );
3521
3522                                // Additional sizes in wp_prepare_attachment_for_js().
3523                                add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
3524                                break;
3525                        }
3526
3527                        /** This filter is documented in wp-admin/custom-header.php */
3528                        $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
3529                        $object  = $wp_site_icon->create_attachment_object( $cropped, $attachment_id );
3530                        unset( $object['ID'] );
3531
3532                        // Update the attachment.
3533                        add_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
3534                        $attachment_id = $wp_site_icon->insert_attachment( $object, $cropped );
3535                        remove_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
3536
3537                        // Additional sizes in wp_prepare_attachment_for_js().
3538                        add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
3539                        break;
3540
3541                default:
3542                        /**
3543                         * Fires before a cropped image is saved.
3544                         *
3545                         * Allows to add filters to modify the way a cropped image is saved.
3546                         *
3547                         * @since 4.3.0
3548                         *
3549                         * @param string $context       The Customizer control requesting the cropped image.
3550                         * @param int    $attachment_id The attachment ID of the original image.
3551                         * @param string $cropped       Path to the cropped image file.
3552                         */
3553                        do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
3554
3555                        /** This filter is documented in wp-admin/custom-header.php */
3556                        $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
3557
3558                        $parent_url = wp_get_attachment_url( $attachment_id );
3559                        $url        = str_replace( basename( $parent_url ), basename( $cropped ), $parent_url );
3560
3561                        $size       = @getimagesize( $cropped );
3562                        $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
3563
3564                        $object = array(
3565                                'post_title'     => basename( $cropped ),
3566                                'post_content'   => $url,
3567                                'post_mime_type' => $image_type,
3568                                'guid'           => $url,
3569                                'context'        => $context,
3570                        );
3571
3572                        $attachment_id = wp_insert_attachment( $object, $cropped );
3573                        $metadata      = wp_generate_attachment_metadata( $attachment_id, $cropped );
3574
3575                        /**
3576                         * Filters the cropped image attachment metadata.
3577                         *
3578                         * @since 4.3.0
3579                         *
3580                         * @see wp_generate_attachment_metadata()
3581                         *
3582                         * @param array $metadata Attachment metadata.
3583                         */
3584                        $metadata = apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
3585                        wp_update_attachment_metadata( $attachment_id, $metadata );
3586
3587                        /**
3588                         * Filters the attachment ID for a cropped image.
3589                         *
3590                         * @since 4.3.0
3591                         *
3592                         * @param int    $attachment_id The attachment ID of the cropped image.
3593                         * @param string $context       The Customizer control requesting the cropped image.
3594                         */
3595                        $attachment_id = apply_filters( 'wp_ajax_cropped_attachment_id', $attachment_id, $context );
3596        }
3597
3598        wp_send_json_success( wp_prepare_attachment_for_js( $attachment_id ) );
3599}
3600
3601/**
3602 * Ajax handler for generating a password.
3603 *
3604 * @since 4.4.0
3605 */
3606function wp_ajax_generate_password() {
3607        wp_send_json_success( wp_generate_password( 24 ) );
3608}
3609
3610/**
3611 * Ajax handler for saving the user's WordPress.org username.
3612 *
3613 * @since 4.4.0
3614 */
3615function wp_ajax_save_wporg_username() {
3616        if ( ! current_user_can( 'install_themes' ) && ! current_user_can( 'install_plugins' ) ) {
3617                wp_send_json_error();
3618        }
3619
3620        check_ajax_referer( 'save_wporg_username_' . get_current_user_id() );
3621
3622        $username = isset( $_REQUEST['username'] ) ? wp_unslash( $_REQUEST['username'] ) : false;
3623
3624        if ( ! $username ) {
3625                wp_send_json_error();
3626        }
3627
3628        wp_send_json_success( update_user_meta( get_current_user_id(), 'wporg_favorites', $username ) );
3629}
3630
3631/**
3632 * Ajax handler for installing a theme.
3633 *
3634 * @since 4.6.0
3635 *
3636 * @see Theme_Upgrader
3637 *
3638 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
3639 */
3640function wp_ajax_install_theme() {
3641        check_ajax_referer( 'updates' );
3642
3643        if ( empty( $_POST['slug'] ) ) {
3644                wp_send_json_error(
3645                        array(
3646                                'slug'         => '',
3647                                'errorCode'    => 'no_theme_specified',
3648                                'errorMessage' => __( 'No theme specified.' ),
3649                        )
3650                );
3651        }
3652
3653        $slug = sanitize_key( wp_unslash( $_POST['slug'] ) );
3654
3655        $status = array(
3656                'install' => 'theme',
3657                'slug'    => $slug,
3658        );
3659
3660        if ( ! current_user_can( 'install_themes' ) ) {
3661                $status['errorMessage'] = __( 'Sorry, you are not allowed to install themes on this site.' );
3662                wp_send_json_error( $status );
3663        }
3664
3665        include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3666        include_once( ABSPATH . 'wp-admin/includes/theme.php' );
3667
3668        $api = themes_api(
3669                'theme_information', array(
3670                        'slug'   => $slug,
3671                        'fields' => array( 'sections' => false ),
3672                )
3673        );
3674
3675        if ( is_wp_error( $api ) ) {
3676                $status['errorMessage'] = $api->get_error_message();
3677                wp_send_json_error( $status );
3678        }
3679
3680        $skin     = new WP_Ajax_Upgrader_Skin();
3681        $upgrader = new Theme_Upgrader( $skin );
3682        $result   = $upgrader->install( $api->download_link );
3683
3684        if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3685                $status['debug'] = $skin->get_upgrade_messages();
3686        }
3687
3688        if ( is_wp_error( $result ) ) {
3689                $status['errorCode']    = $result->get_error_code();
3690                $status['errorMessage'] = $result->get_error_message();
3691                wp_send_json_error( $status );
3692        } elseif ( is_wp_error( $skin->result ) ) {
3693                $status['errorCode']    = $skin->result->get_error_code();
3694                $status['errorMessage'] = $skin->result->get_error_message();
3695                wp_send_json_error( $status );
3696        } elseif ( $skin->get_errors()->has_errors() ) {
3697                $status['errorMessage'] = $skin->get_error_messages();
3698                wp_send_json_error( $status );
3699        } elseif ( is_null( $result ) ) {
3700                global $wp_filesystem;
3701
3702                $status['errorCode']    = 'unable_to_connect_to_filesystem';
3703                $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3704
3705                // Pass through the error from WP_Filesystem if one was raised.
3706                if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
3707                        $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3708                }
3709
3710                wp_send_json_error( $status );
3711        }
3712
3713        $status['themeName'] = wp_get_theme( $slug )->get( 'Name' );
3714
3715        if ( current_user_can( 'switch_themes' ) ) {
3716                if ( is_multisite() ) {
3717                        $status['activateUrl'] = add_query_arg(
3718                                array(
3719                                        'action'   => 'enable',
3720                                        '_wpnonce' => wp_create_nonce( 'enable-theme_' . $slug ),
3721                                        'theme'    => $slug,
3722                                ), network_admin_url( 'themes.php' )
3723                        );
3724                } else {
3725                        $status['activateUrl'] = add_query_arg(
3726                                array(
3727                                        'action'     => 'activate',
3728                                        '_wpnonce'   => wp_create_nonce( 'switch-theme_' . $slug ),
3729                                        'stylesheet' => $slug,
3730                                ), admin_url( 'themes.php' )
3731                        );
3732                }
3733        }
3734
3735        if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
3736                $status['customizeUrl'] = add_query_arg(
3737                        array(
3738                                'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
3739                        ), wp_customize_url( $slug )
3740                );
3741        }
3742
3743        /*
3744         * See WP_Theme_Install_List_Table::_get_theme_status() if we wanted to check
3745         * on post-installation status.
3746         */
3747        wp_send_json_success( $status );
3748}
3749
3750/**
3751 * Ajax handler for updating a theme.
3752 *
3753 * @since 4.6.0
3754 *
3755 * @see Theme_Upgrader
3756 *
3757 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
3758 */
3759function wp_ajax_update_theme() {
3760        check_ajax_referer( 'updates' );
3761
3762        if ( empty( $_POST['slug'] ) ) {
3763                wp_send_json_error(
3764                        array(
3765                                'slug'         => '',
3766                                'errorCode'    => 'no_theme_specified',
3767                                'errorMessage' => __( 'No theme specified.' ),
3768                        )
3769                );
3770        }
3771
3772        $stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
3773        $status     = array(
3774                'update'     => 'theme',
3775                'slug'       => $stylesheet,
3776                'oldVersion' => '',
3777                'newVersion' => '',
3778        );
3779
3780        if ( ! current_user_can( 'update_themes' ) ) {
3781                $status['errorMessage'] = __( 'Sorry, you are not allowed to update themes for this site.' );
3782                wp_send_json_error( $status );
3783        }
3784
3785        $theme = wp_get_theme( $stylesheet );
3786        if ( $theme->exists() ) {
3787                $status['oldVersion'] = $theme->get( 'Version' );
3788        }
3789
3790        include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3791
3792        $current = get_site_transient( 'update_themes' );
3793        if ( empty( $current ) ) {
3794                wp_update_themes();
3795        }
3796
3797        $skin     = new WP_Ajax_Upgrader_Skin();
3798        $upgrader = new Theme_Upgrader( $skin );
3799        $result   = $upgrader->bulk_upgrade( array( $stylesheet ) );
3800
3801        if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3802                $status['debug'] = $skin->get_upgrade_messages();
3803        }
3804
3805        if ( is_wp_error( $skin->result ) ) {
3806                $status['errorCode']    = $skin->result->get_error_code();
3807                $status['errorMessage'] = $skin->result->get_error_message();
3808                wp_send_json_error( $status );
3809        } elseif ( $skin->get_errors()->has_errors() ) {
3810                $status['errorMessage'] = $skin->get_error_messages();
3811                wp_send_json_error( $status );
3812        } elseif ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) {
3813
3814                // Theme is already at the latest version.
3815                if ( true === $result[ $stylesheet ] ) {
3816                        $status['errorMessage'] = $upgrader->strings['up_to_date'];
3817                        wp_send_json_error( $status );
3818                }
3819
3820                $theme = wp_get_theme( $stylesheet );
3821                if ( $theme->exists() ) {
3822                        $status['newVersion'] = $theme->get( 'Version' );
3823                }
3824
3825                wp_send_json_success( $status );
3826        } elseif ( false === $result ) {
3827                global $wp_filesystem;
3828
3829                $status['errorCode']    = 'unable_to_connect_to_filesystem';
3830                $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3831
3832                // Pass through the error from WP_Filesystem if one was raised.
3833                if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
3834                        $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3835                }
3836
3837                wp_send_json_error( $status );
3838        }
3839
3840        // An unhandled error occurred.
3841        $status['errorMessage'] = __( 'Update failed.' );
3842        wp_send_json_error( $status );
3843}
3844
3845/**
3846 * Ajax handler for deleting a theme.
3847 *
3848 * @since 4.6.0
3849 *
3850 * @see delete_theme()
3851 *
3852 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
3853 */
3854function wp_ajax_delete_theme() {
3855        check_ajax_referer( 'updates' );
3856
3857        if ( empty( $_POST['slug'] ) ) {
3858                wp_send_json_error(
3859                        array(
3860                                'slug'         => '',
3861                                'errorCode'    => 'no_theme_specified',
3862                                'errorMessage' => __( 'No theme specified.' ),
3863                        )
3864                );
3865        }
3866
3867        $stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
3868        $status     = array(
3869                'delete' => 'theme',
3870                'slug'   => $stylesheet,
3871        );
3872
3873        if ( ! current_user_can( 'delete_themes' ) ) {
3874                $status['errorMessage'] = __( 'Sorry, you are not allowed to delete themes on this site.' );
3875                wp_send_json_error( $status );
3876        }
3877
3878        if ( ! wp_get_theme( $stylesheet )->exists() ) {
3879                $status['errorMessage'] = __( 'The requested theme does not exist.' );
3880                wp_send_json_error( $status );
3881        }
3882
3883        // Check filesystem credentials. `delete_theme()` will bail otherwise.
3884        $url = wp_nonce_url( 'themes.php?action=delete&stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet );
3885        ob_start();
3886        $credentials = request_filesystem_credentials( $url );
3887        ob_end_clean();
3888        if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
3889                global $wp_filesystem;
3890
3891                $status['errorCode']    = 'unable_to_connect_to_filesystem';
3892                $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3893
3894                // Pass through the error from WP_Filesystem if one was raised.
3895                if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
3896                        $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3897                }
3898
3899                wp_send_json_error( $status );
3900        }
3901
3902        include_once( ABSPATH . 'wp-admin/includes/theme.php' );
3903
3904        $result = delete_theme( $stylesheet );
3905
3906        if ( is_wp_error( $result ) ) {
3907                $status['errorMessage'] = $result->get_error_message();
3908                wp_send_json_error( $status );
3909        } elseif ( false === $result ) {
3910                $status['errorMessage'] = __( 'Theme could not be deleted.' );
3911                wp_send_json_error( $status );
3912        }
3913
3914        wp_send_json_success( $status );
3915}
3916
3917/**
3918 * Ajax handler for installing a plugin.
3919 *
3920 * @since 4.6.0
3921 *
3922 * @see Plugin_Upgrader
3923 *
3924 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
3925 */
3926function wp_ajax_install_plugin() {
3927        check_ajax_referer( 'updates' );
3928
3929        if ( empty( $_POST['slug'] ) ) {
3930                wp_send_json_error(
3931                        array(
3932                                'slug'         => '',
3933                                'errorCode'    => 'no_plugin_specified',
3934                                'errorMessage' => __( 'No plugin specified.' ),
3935                        )
3936                );
3937        }
3938
3939        $status = array(
3940                'install' => 'plugin',
3941                'slug'    => sanitize_key( wp_unslash( $_POST['slug'] ) ),
3942        );
3943
3944        if ( ! current_user_can( 'install_plugins' ) ) {
3945                $status['errorMessage'] = __( 'Sorry, you are not allowed to install plugins on this site.' );
3946                wp_send_json_error( $status );
3947        }
3948
3949        include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
3950        include_once( ABSPATH . 'wp-admin/includes/plugin-install.php' );
3951
3952        $api = plugins_api(
3953                'plugin_information', array(
3954                        'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
3955                        'fields' => array(
3956                                'sections' => false,
3957                        ),
3958                )
3959        );
3960
3961        if ( is_wp_error( $api ) ) {
3962                $status['errorMessage'] = $api->get_error_message();
3963                wp_send_json_error( $status );
3964        }
3965
3966        $status['pluginName'] = $api->name;
3967
3968        $skin     = new WP_Ajax_Upgrader_Skin();
3969        $upgrader = new Plugin_Upgrader( $skin );
3970        $result   = $upgrader->install( $api->download_link );
3971
3972        if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
3973                $status['debug'] = $skin->get_upgrade_messages();
3974        }
3975
3976        if ( is_wp_error( $result ) ) {
3977                $status['errorCode']    = $result->get_error_code();
3978                $status['errorMessage'] = $result->get_error_message();
3979                wp_send_json_error( $status );
3980        } elseif ( is_wp_error( $skin->result ) ) {
3981                $status['errorCode']    = $skin->result->get_error_code();
3982                $status['errorMessage'] = $skin->result->get_error_message();
3983                wp_send_json_error( $status );
3984        } elseif ( $skin->get_errors()->has_errors() ) {
3985                $status['errorMessage'] = $skin->get_error_messages();
3986                wp_send_json_error( $status );
3987        } elseif ( is_null( $result ) ) {
3988                global $wp_filesystem;
3989
3990                $status['errorCode']    = 'unable_to_connect_to_filesystem';
3991                $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
3992
3993                // Pass through the error from WP_Filesystem if one was raised.
3994                if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
3995                        $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
3996                }
3997
3998                wp_send_json_error( $status );
3999        }
4000
4001        $install_status = install_plugin_install_status( $api );
4002        $pagenow        = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
4003
4004        // If installation request is coming from import page, do not return network activation link.
4005        $plugins_url = ( 'import' === $pagenow ) ? admin_url( 'plugins.php' ) : network_admin_url( 'plugins.php' );
4006
4007        if ( current_user_can( 'activate_plugin', $install_status['file'] ) && is_plugin_inactive( $install_status['file'] ) ) {
4008                $status['activateUrl'] = add_query_arg(
4009                        array(
4010                                '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ),
4011                                'action'   => 'activate',
4012                                'plugin'   => $install_status['file'],
4013                        ), $plugins_url
4014                );
4015        }
4016
4017        if ( is_multisite() && current_user_can( 'manage_network_plugins' ) && 'import' !== $pagenow ) {
4018                $status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] );
4019        }
4020
4021        wp_send_json_success( $status );
4022}
4023
4024/**
4025 * Ajax handler for updating a plugin.
4026 *
4027 * @since 4.2.0
4028 *
4029 * @see Plugin_Upgrader
4030 *
4031 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4032 */
4033function wp_ajax_update_plugin() {
4034        check_ajax_referer( 'updates' );
4035
4036        if ( empty( $_POST['plugin'] ) || empty( $_POST['slug'] ) ) {
4037                wp_send_json_error(
4038                        array(
4039                                'slug'         => '',
4040                                'errorCode'    => 'no_plugin_specified',
4041                                'errorMessage' => __( 'No plugin specified.' ),
4042                        )
4043                );
4044        }
4045
4046        $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
4047
4048        $status = array(
4049                'update'     => 'plugin',
4050                'slug'       => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4051                'oldVersion' => '',
4052                'newVersion' => '',
4053        );
4054
4055        if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) {
4056                $status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
4057                wp_send_json_error( $status );
4058        }
4059
4060        $plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4061        $status['plugin']     = $plugin;
4062        $status['pluginName'] = $plugin_data['Name'];
4063
4064        if ( $plugin_data['Version'] ) {
4065                /* translators: %s: Plugin version */
4066                $status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
4067        }
4068
4069        include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' );
4070
4071        wp_update_plugins();
4072
4073        $skin     = new WP_Ajax_Upgrader_Skin();
4074        $upgrader = new Plugin_Upgrader( $skin );
4075        $result   = $upgrader->bulk_upgrade( array( $plugin ) );
4076
4077        if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4078                $status['debug'] = $skin->get_upgrade_messages();
4079        }
4080
4081        if ( is_wp_error( $skin->result ) ) {
4082                $status['errorCode']    = $skin->result->get_error_code();
4083                $status['errorMessage'] = $skin->result->get_error_message();
4084                wp_send_json_error( $status );
4085        } elseif ( $skin->get_errors()->has_errors() ) {
4086                $status['errorMessage'] = $skin->get_error_messages();
4087                wp_send_json_error( $status );
4088        } elseif ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) {
4089                $plugin_update_data = current( $result );
4090
4091                /*
4092                 * If the `update_plugins` site transient is empty (e.g. when you update
4093                 * two plugins in quick succession before the transient repopulates),
4094                 * this may be the return.
4095                 *
4096                 * Preferably something can be done to ensure `update_plugins` isn't empty.
4097                 * For now, surface some sort of error here.
4098                 */
4099                if ( true === $plugin_update_data ) {
4100                        $status['errorMessage'] = __( 'Plugin update failed.' );
4101                        wp_send_json_error( $status );
4102                }
4103
4104                $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
4105                $plugin_data = reset( $plugin_data );
4106
4107                if ( $plugin_data['Version'] ) {
4108                        /* translators: %s: Plugin version */
4109                        $status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
4110                }
4111                wp_send_json_success( $status );
4112        } elseif ( false === $result ) {
4113                global $wp_filesystem;
4114
4115                $status['errorCode']    = 'unable_to_connect_to_filesystem';
4116                $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4117
4118                // Pass through the error from WP_Filesystem if one was raised.
4119                if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4120                        $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4121                }
4122
4123                wp_send_json_error( $status );
4124        }
4125
4126        // An unhandled error occurred.
4127        $status['errorMessage'] = __( 'Plugin update failed.' );
4128        wp_send_json_error( $status );
4129}
4130
4131/**
4132 * Ajax handler for deleting a plugin.
4133 *
4134 * @since 4.6.0
4135 *
4136 * @see delete_plugins()
4137 *
4138 * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4139 */
4140function wp_ajax_delete_plugin() {
4141        check_ajax_referer( 'updates' );
4142
4143        if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
4144                wp_send_json_error(
4145                        array(
4146                                'slug'         => '',
4147                                'errorCode'    => 'no_plugin_specified',
4148                                'errorMessage' => __( 'No plugin specified.' ),
4149                        )
4150                );
4151        }
4152
4153        $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
4154
4155        $status = array(
4156                'delete' => 'plugin',
4157                'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4158        );
4159
4160        if ( ! current_user_can( 'delete_plugins' ) || 0 !== validate_file( $plugin ) ) {
4161                $status['errorMessage'] = __( 'Sorry, you are not allowed to delete plugins for this site.' );
4162                wp_send_json_error( $status );
4163        }
4164
4165        $plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4166        $status['plugin']     = $plugin;
4167        $status['pluginName'] = $plugin_data['Name'];
4168
4169        if ( is_plugin_active( $plugin ) ) {
4170                $status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' );
4171                wp_send_json_error( $status );
4172        }
4173
4174        // Check filesystem credentials. `delete_plugins()` will bail otherwise.
4175        $url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin, 'bulk-plugins' );
4176        ob_start();
4177        $credentials = request_filesystem_credentials( $url );
4178        ob_end_clean();
4179        if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
4180                global $wp_filesystem;
4181
4182                $status['errorCode']    = 'unable_to_connect_to_filesystem';
4183                $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4184
4185                // Pass through the error from WP_Filesystem if one was raised.
4186                if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4187                        $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4188                }
4189
4190                wp_send_json_error( $status );
4191        }
4192
4193        $result = delete_plugins( array( $plugin ) );
4194
4195        if ( is_wp_error( $result ) ) {
4196                $status['errorMessage'] = $result->get_error_message();
4197                wp_send_json_error( $status );
4198        } elseif ( false === $result ) {
4199                $status['errorMessage'] = __( 'Plugin could not be deleted.' );
4200                wp_send_json_error( $status );
4201        }
4202
4203        wp_send_json_success( $status );
4204}
4205
4206/**
4207 * Ajax handler for searching plugins.
4208 *
4209 * @since 4.6.0
4210 *
4211 * @global string $s Search term.
4212 */
4213function wp_ajax_search_plugins() {
4214        check_ajax_referer( 'updates' );
4215
4216        $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
4217        if ( 'plugins-network' === $pagenow || 'plugins' === $pagenow ) {
4218                set_current_screen( $pagenow );
4219        }
4220
4221        /** @var WP_Plugins_List_Table $wp_list_table */
4222        $wp_list_table = _get_list_table(
4223                'WP_Plugins_List_Table', array(
4224                        'screen' => get_current_screen(),
4225                )
4226        );
4227
4228        $status = array();
4229
4230        if ( ! $wp_list_table->ajax_user_can() ) {
4231                $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
4232                wp_send_json_error( $status );
4233        }
4234
4235        // Set the correct requester, so pagination works.
4236        $_SERVER['REQUEST_URI'] = add_query_arg(
4237                array_diff_key(
4238                        $_POST, array(
4239                                '_ajax_nonce' => null,
4240                                'action'      => null,
4241                        )
4242                ), network_admin_url( 'plugins.php', 'relative' )
4243        );
4244
4245        $GLOBALS['s'] = wp_unslash( $_POST['s'] );
4246
4247        $wp_list_table->prepare_items();
4248
4249        ob_start();
4250        $wp_list_table->display();
4251        $status['count'] = count( $wp_list_table->items );
4252        $status['items'] = ob_get_clean();
4253
4254        wp_send_json_success( $status );
4255}
4256
4257/**
4258 * Ajax handler for searching plugins to install.
4259 *
4260 * @since 4.6.0
4261 */
4262function wp_ajax_search_install_plugins() {
4263        check_ajax_referer( 'updates' );
4264
4265        $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
4266        if ( 'plugin-install-network' === $pagenow || 'plugin-install' === $pagenow ) {
4267                set_current_screen( $pagenow );
4268        }
4269
4270        /** @var WP_Plugin_Install_List_Table $wp_list_table */
4271        $wp_list_table = _get_list_table(
4272                'WP_Plugin_Install_List_Table', array(
4273                        'screen' => get_current_screen(),
4274                )
4275        );
4276
4277        $status = array();
4278
4279        if ( ! $wp_list_table->ajax_user_can() ) {
4280                $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
4281                wp_send_json_error( $status );
4282        }
4283
4284        // Set the correct requester, so pagination works.
4285        $_SERVER['REQUEST_URI'] = add_query_arg(
4286                array_diff_key(
4287                        $_POST, array(
4288                                '_ajax_nonce' => null,
4289                                'action'      => null,
4290                        )
4291                ), network_admin_url( 'plugin-install.php', 'relative' )
4292        );
4293
4294        $wp_list_table->prepare_items();
4295
4296        ob_start();
4297        $wp_list_table->display();
4298        $status['count'] = (int) $wp_list_table->get_pagination_arg( 'total_items' );
4299        $status['items'] = ob_get_clean();
4300
4301        wp_send_json_success( $status );
4302}
4303
4304/**
4305 * Ajax handler for editing a theme or plugin file.
4306 *
4307 * @since 4.9.0
4308 * @see wp_edit_theme_plugin_file()
4309 */
4310function wp_ajax_edit_theme_plugin_file() {
4311        $r = wp_edit_theme_plugin_file( wp_unslash( $_POST ) ); // Validation of args is done in wp_edit_theme_plugin_file().
4312        if ( is_wp_error( $r ) ) {
4313                wp_send_json_error(
4314                        array_merge(
4315                                array(
4316                                        'code'    => $r->get_error_code(),
4317                                        'message' => $r->get_error_message(),
4318                                ),
4319                                (array) $r->get_error_data()
4320                        )
4321                );
4322        } else {
4323                wp_send_json_success(
4324                        array(
4325                                'message' => __( 'File edited successfully.' ),
4326                        )
4327                );
4328        }
4329}
4330
4331/**
4332 * Ajax handler for exporting a user's personal data.
4333 *
4334 * @since 4.9.6
4335 */
4336function wp_ajax_wp_privacy_export_personal_data() {
4337
4338        if ( empty( $_POST['id'] ) ) {
4339                wp_send_json_error( __( 'Missing request ID.' ) );
4340        }
4341        $request_id = (int) $_POST['id'];
4342
4343        if ( $request_id < 1 ) {
4344                wp_send_json_error( __( 'Invalid request ID.' ) );
4345        }
4346
4347        if ( ! current_user_can( 'export_others_personal_data' ) ) {
4348                wp_send_json_error( __( 'Invalid request.' ) );
4349        }
4350
4351        check_ajax_referer( 'wp-privacy-export-personal-data-' . $request_id, 'security' );
4352
4353        // Get the request data.
4354        $request = wp_get_user_request_data( $request_id );
4355
4356        if ( ! $request || 'export_personal_data' !== $request->action_name ) {
4357                wp_send_json_error( __( 'Invalid request type.' ) );
4358        }
4359
4360        $email_address = $request->email;
4361        if ( ! is_email( $email_address ) ) {
4362                wp_send_json_error( __( 'A valid email address must be given.' ) );
4363        }
4364
4365        if ( ! isset( $_POST['exporter'] ) ) {
4366                wp_send_json_error( __( 'Missing exporter index.' ) );
4367        }
4368        $exporter_index = (int) $_POST['exporter'];
4369
4370        if ( ! isset( $_POST['page'] ) ) {
4371                wp_send_json_error( __( 'Missing page index.' ) );
4372        }
4373        $page = (int) $_POST['page'];
4374
4375        $send_as_email = isset( $_POST['sendAsEmail'] ) ? ( 'true' === $_POST['sendAsEmail'] ) : false;
4376
4377        /**
4378         * Filters the array of exporter callbacks.
4379         *
4380         * @since 4.9.6
4381         *
4382         * @param array $args {
4383         *     An array of callable exporters of personal data. Default empty array.
4384         *
4385         *     @type array {
4386         *         Array of personal data exporters.
4387         *
4388         *         @type string $callback               Callable exporter function that accepts an
4389         *                                              email address and a page and returns an array
4390         *                                              of name => value pairs of personal data.
4391         *         @type string $exporter_friendly_name Translated user facing friendly name for the
4392         *                                              exporter.
4393         *     }
4394         * }
4395         */
4396        $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
4397
4398        if ( ! is_array( $exporters ) ) {
4399                wp_send_json_error( __( 'An exporter has improperly used the registration filter.' ) );
4400        }
4401
4402        // Do we have any registered exporters?
4403        if ( 0 < count( $exporters ) ) {
4404                if ( $exporter_index < 1 ) {
4405                        wp_send_json_error( __( 'Exporter index cannot be negative.' ) );
4406                }
4407
4408                if ( $exporter_index > count( $exporters ) ) {
4409                        wp_send_json_error( __( 'Exporter index out of range.' ) );
4410                }
4411
4412                if ( $page < 1 ) {
4413                        wp_send_json_error( __( 'Page index cannot be less than one.' ) );
4414                }
4415
4416                $exporter_keys = array_keys( $exporters );
4417                $exporter_key  = $exporter_keys[ $exporter_index - 1 ];
4418                $exporter      = $exporters[ $exporter_key ];
4419
4420                if ( ! is_array( $exporter ) ) {
4421                        wp_send_json_error(
4422                                /* translators: %s: array index */
4423                                sprintf( __( 'Expected an array describing the exporter at index %s.' ), $exporter_key )
4424                        );
4425                }
4426                if ( ! array_key_exists( 'exporter_friendly_name', $exporter ) ) {
4427                        wp_send_json_error(
4428                                /* translators: %s: array index */
4429                                sprintf( __( 'Exporter array at index %s does not include a friendly name.' ), $exporter_key )
4430                        );
4431                }
4432                if ( ! array_key_exists( 'callback', $exporter ) ) {
4433                        wp_send_json_error(
4434                                /* translators: %s: exporter friendly name */
4435                                sprintf( __( 'Exporter does not include a callback: %s.' ), esc_html( $exporter['exporter_friendly_name'] ) )
4436                        );
4437                }
4438                if ( ! is_callable( $exporter['callback'] ) ) {
4439                        wp_send_json_error(
4440                                /* translators: %s: exporter friendly name */
4441                                sprintf( __( 'Exporter callback is not a valid callback: %s.' ), esc_html( $exporter['exporter_friendly_name'] ) )
4442                        );
4443                }
4444
4445                $callback               = $exporter['callback'];
4446                $exporter_friendly_name = $exporter['exporter_friendly_name'];
4447
4448                $response = call_user_func( $callback, $email_address, $page );
4449                if ( is_wp_error( $response ) ) {
4450                        wp_send_json_error( $response );
4451                }
4452
4453                if ( ! is_array( $response ) ) {
4454                        wp_send_json_error(
4455                                /* translators: %s: exporter friendly name */
4456                                sprintf( __( 'Expected response as an array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
4457                        );
4458                }
4459                if ( ! array_key_exists( 'data', $response ) ) {
4460                        wp_send_json_error(
4461                                /* translators: %s: exporter friendly name */
4462                                sprintf( __( 'Expected data in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
4463                        );
4464                }
4465                if ( ! is_array( $response['data'] ) ) {
4466                        wp_send_json_error(
4467                                /* translators: %s: exporter friendly name */
4468                                sprintf( __( 'Expected data array in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
4469                        );
4470                }
4471                if ( ! array_key_exists( 'done', $response ) ) {
4472                        wp_send_json_error(
4473                                /* translators: %s: exporter friendly name */
4474                                sprintf( __( 'Expected done (boolean) in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
4475                        );
4476                }
4477        } else {
4478                // No exporters, so we're done.
4479                $exporter_key = '';
4480
4481                $response = array(
4482                        'data' => array(),
4483                        'done' => true,
4484                );
4485        }
4486
4487        /**
4488         * Filters a page of personal data exporter data. Used to build the export report.
4489         *
4490         * Allows the export response to be consumed by destinations in addition to Ajax.
4491         *
4492         * @since 4.9.6
4493         *
4494         * @param array  $response        The personal data for the given exporter and page.
4495         * @param int    $exporter_index  The index of the exporter that provided this data.
4496         * @param string $email_address   The email address associated with this personal data.
4497         * @param int    $page            The page for this response.
4498         * @param int    $request_id      The privacy request post ID associated with this request.
4499         * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
4500         * @param string $exporter_key    The key (slug) of the exporter that provided this data.
4501         */
4502        $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key );
4503
4504        if ( is_wp_error( $response ) ) {
4505                wp_send_json_error( $response );
4506        }
4507
4508        wp_send_json_success( $response );
4509}
4510
4511/**
4512 * Ajax handler for erasing personal data.
4513 *
4514 * @since 4.9.6
4515 */
4516function wp_ajax_wp_privacy_erase_personal_data() {
4517
4518        if ( empty( $_POST['id'] ) ) {
4519                wp_send_json_error( __( 'Missing request ID.' ) );
4520        }
4521
4522        $request_id = (int) $_POST['id'];
4523
4524        if ( $request_id < 1 ) {
4525                wp_send_json_error( __( 'Invalid request ID.' ) );
4526        }
4527
4528        // Both capabilities are required to avoid confusion, see `_wp_personal_data_removal_page()`.
4529        if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) {
4530                wp_send_json_error( __( 'Invalid request.' ) );
4531        }
4532
4533        check_ajax_referer( 'wp-privacy-erase-personal-data-' . $request_id, 'security' );
4534
4535        // Get the request data.
4536        $request = wp_get_user_request_data( $request_id );
4537
4538        if ( ! $request || 'remove_personal_data' !== $request->action_name ) {
4539                wp_send_json_error( __( 'Invalid request ID.' ) );
4540        }
4541
4542        $email_address = $request->email;
4543
4544        if ( ! is_email( $email_address ) ) {
4545                wp_send_json_error( __( 'Invalid email address in request.' ) );
4546        }
4547
4548        if ( ! isset( $_POST['eraser'] ) ) {
4549                wp_send_json_error( __( 'Missing eraser index.' ) );
4550        }
4551
4552        $eraser_index = (int) $_POST['eraser'];
4553
4554        if ( ! isset( $_POST['page'] ) ) {
4555                wp_send_json_error( __( 'Missing page index.' ) );
4556        }
4557
4558        $page = (int) $_POST['page'];
4559
4560        /**
4561         * Filters the array of personal data eraser callbacks.
4562         *
4563         * @since 4.9.6
4564         *
4565         * @param array $args {
4566         *     An array of callable erasers of personal data. Default empty array.
4567         *
4568         *     @type array {
4569         *         Array of personal data exporters.
4570         *
4571         *         @type string $callback               Callable eraser that accepts an email address and
4572         *                                              a page and returns an array with boolean values for
4573         *                                              whether items were removed or retained and any messages
4574         *                                              from the eraser, as well as if additional pages are
4575         *                                              available.
4576         *         @type string $exporter_friendly_name Translated user facing friendly name for the eraser.
4577         *     }
4578         * }
4579         */
4580        $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() );
4581
4582        // Do we have any registered erasers?
4583        if ( 0 < count( $erasers ) ) {
4584
4585                if ( $eraser_index < 1 ) {
4586                        wp_send_json_error( __( 'Eraser index cannot be less than one.' ) );
4587                }
4588
4589                if ( $eraser_index > count( $erasers ) ) {
4590                        wp_send_json_error( __( 'Eraser index is out of range.' ) );
4591                }
4592
4593                if ( $page < 1 ) {
4594                        wp_send_json_error( __( 'Page index cannot be less than one.' ) );
4595                }
4596
4597                $eraser_keys = array_keys( $erasers );
4598                $eraser_key  = $eraser_keys[ $eraser_index - 1 ];
4599                $eraser      = $erasers[ $eraser_key ];
4600
4601                if ( ! is_array( $eraser ) ) {
4602                        /* translators: %d: array index */
4603                        wp_send_json_error( sprintf( __( 'Expected an array describing the eraser at index %d.' ), $eraser_index ) );
4604                }
4605
4606                if ( ! array_key_exists( 'callback', $eraser ) ) {
4607                        /* translators: %d: array index */
4608                        wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a callback.' ), $eraser_index ) );
4609                }
4610
4611                if ( ! is_callable( $eraser['callback'] ) ) {
4612                        /* translators: %d: array index */
4613                        wp_send_json_error( sprintf( __( 'Eraser callback at index %d is not a valid callback.' ), $eraser_index ) );
4614                }
4615
4616                if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) {
4617                        /* translators: %d: array index */
4618                        wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) );
4619                }
4620
4621                $callback             = $eraser['callback'];
4622                $eraser_friendly_name = $eraser['eraser_friendly_name'];
4623
4624                $response = call_user_func( $callback, $email_address, $page );
4625
4626                if ( is_wp_error( $response ) ) {
4627                        wp_send_json_error( $response );
4628                }
4629
4630                if ( ! is_array( $response ) ) {
4631                        wp_send_json_error(
4632                                sprintf(
4633                                        /* translators: 1: eraser friendly name, 2: array index */
4634                                        __( 'Did not receive array from %1$s eraser (index %2$d).' ),
4635                                        esc_html( $eraser_friendly_name ),
4636                                        $eraser_index
4637                                )
4638                        );
4639                }
4640
4641                if ( ! array_key_exists( 'items_removed', $response ) ) {
4642                        wp_send_json_error(
4643                                sprintf(
4644                                        /* translators: 1: eraser friendly name, 2: array index */
4645                                        __( 'Expected items_removed key in response array from %1$s eraser (index %2$d).' ),
4646                                        esc_html( $eraser_friendly_name ),
4647                                        $eraser_index
4648                                )
4649                        );
4650                }
4651
4652                if ( ! array_key_exists( 'items_retained', $response ) ) {
4653                        wp_send_json_error(
4654                                sprintf(
4655                                        /* translators: 1: eraser friendly name, 2: array index */
4656                                        __( 'Expected items_retained key in response array from %1$s eraser (index %2$d).' ),
4657                                        esc_html( $eraser_friendly_name ),
4658                                        $eraser_index
4659                                )
4660                        );
4661                }
4662
4663                if ( ! array_key_exists( 'messages', $response ) ) {
4664                        wp_send_json_error(
4665                                sprintf(
4666                                        /* translators: 1: eraser friendly name, 2: array index */
4667                                        __( 'Expected messages key in response array from %1$s eraser (index %2$d).' ),
4668                                        esc_html( $eraser_friendly_name ),
4669                                        $eraser_index
4670                                )
4671                        );
4672                }
4673
4674                if ( ! is_array( $response['messages'] ) ) {
4675                        wp_send_json_error(
4676                                sprintf(
4677                                        /* translators: 1: eraser friendly name, 2: array index */
4678                                        __( 'Expected messages key to reference an array in response array from %1$s eraser (index %2$d).' ),
4679                                        esc_html( $eraser_friendly_name ),
4680                                        $eraser_index
4681                                )
4682                        );
4683                }
4684
4685                if ( ! array_key_exists( 'done', $response ) ) {
4686                        wp_send_json_error(
4687                                sprintf(
4688                                        /* translators: 1: eraser friendly name, 2: array index */
4689                                        __( 'Expected done flag in response array from %1$s eraser (index %2$d).' ),
4690                                        esc_html( $eraser_friendly_name ),
4691                                        $eraser_index
4692                                )
4693                        );
4694                }
4695        } else {
4696                // No erasers, so we're done.
4697                $eraser_key = '';
4698
4699                $response = array(
4700                        'items_removed'  => false,
4701                        'items_retained' => false,
4702                        'messages'       => array(),
4703                        'done'           => true,
4704                );
4705        }
4706
4707        /**
4708         * Filters a page of personal data eraser data.
4709         *
4710         * Allows the erasure response to be consumed by destinations in addition to Ajax.
4711         *
4712         * @since 4.9.6
4713         *
4714         * @param array  $response        The personal data for the given exporter and page.
4715         * @param int    $eraser_index    The index of the eraser that provided this data.
4716         * @param string $email_address   The email address associated with this personal data.
4717         * @param int    $page            The page for this response.
4718         * @param int    $request_id      The privacy request post ID associated with this request.
4719         * @param string $eraser_key      The key (slug) of the eraser that provided this data.
4720         */
4721        $response = apply_filters( 'wp_privacy_personal_data_erasure_page', $response, $eraser_index, $email_address, $page, $request_id, $eraser_key );
4722
4723        if ( is_wp_error( $response ) ) {
4724                wp_send_json_error( $response );
4725        }
4726
4727        wp_send_json_success( $response );
4728}