[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/includes/ -> ajax-actions.php (source)

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


Generated : Tue Oct 6 08:20:33 2026 Cross-referenced by PHPXref