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


Generated : Fri Jul 25 08:20:01 2025 Cross-referenced by PHPXref