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


Generated : Sat Dec 13 08:20:01 2025 Cross-referenced by PHPXref