[ 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 Comment ID.
 458   * @param int $delta      Optional. Change in the number of total comments. Default -1.
 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 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 ( ! $post || ! 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 = $_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 = $_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 = $_POST['page_columns'] ?? 'auto';
1992  
1993      if ( 'auto' !== $page_columns ) {
1994          $page_columns = (int) $page_columns;
1995      }
1996  
1997      $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   = $_POST['new_title'] ?? '';
2056      $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      if ( ! $post ) {
2110          wp_die();
2111      }
2112  
2113      // Since it's coming from the database.
2114      $post = wp_slash( $post );
2115  
2116      $data['content'] = $post['post_content'];
2117      $data['excerpt'] = $post['post_excerpt'];
2118  
2119      // Rename.
2120      $data['user_ID'] = get_current_user_id();
2121  
2122      if ( isset( $data['post_parent'] ) ) {
2123          $data['parent_id'] = $data['post_parent'];
2124      }
2125  
2126      // Status.
2127      if ( isset( $data['keep_private'] ) && 'private' === $data['keep_private'] ) {
2128          $data['visibility']  = 'private';
2129          $data['post_status'] = 'private';
2130      } elseif ( isset( $data['_status'] ) ) {
2131          $data['post_status'] = $data['_status'];
2132      }
2133  
2134      if ( empty( $data['comment_status'] ) ) {
2135          $data['comment_status'] = 'closed';
2136      }
2137  
2138      if ( empty( $data['ping_status'] ) ) {
2139          $data['ping_status'] = 'closed';
2140      }
2141  
2142      // Exclude terms from taxonomies that are not supposed to appear in Quick Edit.
2143      if ( ! empty( $data['tax_input'] ) ) {
2144          foreach ( $data['tax_input'] as $taxonomy => $terms ) {
2145              $tax_object = get_taxonomy( $taxonomy );
2146              /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */
2147              if ( ! apply_filters( 'quick_edit_show_taxonomy', $tax_object->show_in_quick_edit, $taxonomy, $post['post_type'] ) ) {
2148                  unset( $data['tax_input'][ $taxonomy ] );
2149              }
2150          }
2151      }
2152  
2153      // Hack: wp_unique_post_slug() doesn't work for drafts, so we will fake that our post is published.
2154      if ( ! empty( $data['post_name'] ) && in_array( $post['post_status'], array( 'draft', 'pending' ), true ) ) {
2155          $post['post_status'] = 'publish';
2156          $data['post_name']   = wp_unique_post_slug( $data['post_name'], $post['ID'], $post['post_status'], $post['post_type'], $post['post_parent'] );
2157      }
2158  
2159      // Update the post.
2160      edit_post();
2161  
2162      $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
2163  
2164      $mode = 'excerpt' === $_POST['post_view'] ? 'excerpt' : 'list';
2165  
2166      $level = 0;
2167      if ( is_post_type_hierarchical( $wp_list_table->screen->post_type ) ) {
2168          $request_post = array( get_post( $_POST['post_ID'] ) );
2169          $parent       = $request_post[0]->post_parent;
2170  
2171          while ( $parent > 0 ) {
2172              $parent_post = get_post( $parent );
2173              $parent      = $parent_post->post_parent;
2174              ++$level;
2175          }
2176      }
2177  
2178      $wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level );
2179  
2180      wp_die();
2181  }
2182  
2183  /**
2184   * Handles Quick Edit saving for a term via AJAX.
2185   *
2186   * @since 3.1.0
2187   */
2188  function wp_ajax_inline_save_tax() {
2189      check_ajax_referer( 'taxinlineeditnonce', '_inline_edit' );
2190  
2191      $taxonomy        = sanitize_key( $_POST['taxonomy'] );
2192      $taxonomy_object = get_taxonomy( $taxonomy );
2193  
2194      if ( ! $taxonomy_object ) {
2195          wp_die( 0 );
2196      }
2197  
2198      if ( ! isset( $_POST['tax_ID'] ) || ! (int) $_POST['tax_ID'] ) {
2199          wp_die( -1 );
2200      }
2201  
2202      $id = (int) $_POST['tax_ID'];
2203  
2204      if ( ! current_user_can( 'edit_term', $id ) ) {
2205          wp_die( -1 );
2206      }
2207  
2208      $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => 'edit-' . $taxonomy ) );
2209  
2210      $tag                  = get_term( $id, $taxonomy );
2211      $_POST['description'] = $tag->description;
2212  
2213      $updated = wp_update_term( $id, $taxonomy, $_POST );
2214  
2215      if ( $updated && ! is_wp_error( $updated ) ) {
2216          $tag = get_term( $updated['term_id'], $taxonomy );
2217          if ( ! $tag || is_wp_error( $tag ) ) {
2218              if ( is_wp_error( $tag ) && $tag->get_error_message() ) {
2219                  wp_die( $tag->get_error_message() );
2220              }
2221              wp_die( __( 'Item not updated.' ) );
2222          }
2223      } else {
2224          if ( is_wp_error( $updated ) && $updated->get_error_message() ) {
2225              wp_die( $updated->get_error_message() );
2226          }
2227          wp_die( __( 'Item not updated.' ) );
2228      }
2229  
2230      $level  = 0;
2231      $parent = $tag->parent;
2232  
2233      while ( $parent > 0 ) {
2234          $parent_tag = get_term( $parent, $taxonomy );
2235          $parent     = $parent_tag->parent;
2236          ++$level;
2237      }
2238  
2239      $wp_list_table->single_row( $tag, $level );
2240      wp_die();
2241  }
2242  
2243  /**
2244   * Handles querying posts for the Find Posts modal via AJAX.
2245   *
2246   * @see window.findPosts
2247   *
2248   * @since 3.1.0
2249   */
2250  function wp_ajax_find_posts() {
2251      check_ajax_referer( 'find-posts' );
2252  
2253      $post_types = get_post_types( array( 'public' => true ), 'objects' );
2254      unset( $post_types['attachment'] );
2255  
2256      $args = array(
2257          'post_type'      => array_keys( $post_types ),
2258          'post_status'    => 'any',
2259          'posts_per_page' => 50,
2260      );
2261  
2262      $search = wp_unslash( $_POST['ps'] );
2263  
2264      if ( '' !== $search ) {
2265          $args['s'] = $search;
2266      }
2267  
2268      $posts = get_posts( $args );
2269  
2270      if ( ! $posts ) {
2271          wp_send_json_error( __( 'No items found.' ) );
2272      }
2273  
2274      $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>';
2275      $alternate = '';
2276      foreach ( $posts as $post ) {
2277          $title     = trim( $post->post_title ) ? $post->post_title : __( '(no title)' );
2278          $alternate = ( 'alternate' === $alternate ) ? '' : 'alternate';
2279  
2280          switch ( $post->post_status ) {
2281              case 'publish':
2282              case 'private':
2283                  $stat = __( 'Published' );
2284                  break;
2285              case 'future':
2286                  $stat = __( 'Scheduled' );
2287                  break;
2288              case 'pending':
2289                  $stat = __( 'Pending Review' );
2290                  break;
2291              case 'draft':
2292                  $stat = __( 'Draft' );
2293                  break;
2294          }
2295  
2296          if ( '0000-00-00 00:00:00' === $post->post_date ) {
2297              $time = '';
2298          } else {
2299              /* translators: Date format in table columns, see https://www.php.net/manual/datetime.format.php */
2300              $time = mysql2date( __( 'Y/m/d' ), $post->post_date );
2301          }
2302  
2303          $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>';
2304          $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";
2305      }
2306  
2307      $html .= '</tbody></table>';
2308  
2309      wp_send_json_success( $html );
2310  }
2311  
2312  /**
2313   * Handles saving the widgets order via AJAX.
2314   *
2315   * @since 3.1.0
2316   */
2317  function wp_ajax_widgets_order() {
2318      check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
2319  
2320      if ( ! current_user_can( 'edit_theme_options' ) ) {
2321          wp_die( -1 );
2322      }
2323  
2324      unset( $_POST['savewidgets'], $_POST['action'] );
2325  
2326      // Save widgets order for all sidebars.
2327      if ( is_array( $_POST['sidebars'] ) ) {
2328          $sidebars = array();
2329  
2330          foreach ( wp_unslash( $_POST['sidebars'] ) as $key => $val ) {
2331              $sidebar = array();
2332  
2333              if ( ! empty( $val ) ) {
2334                  $val = explode( ',', $val );
2335  
2336                  foreach ( $val as $k => $v ) {
2337                      if ( ! str_contains( $v, 'widget-' ) ) {
2338                          continue;
2339                      }
2340  
2341                      $sidebar[ $k ] = substr( $v, strpos( $v, '_' ) + 1 );
2342                  }
2343              }
2344              $sidebars[ $key ] = $sidebar;
2345          }
2346  
2347          wp_set_sidebars_widgets( $sidebars );
2348          wp_die( 1 );
2349      }
2350  
2351      wp_die( -1 );
2352  }
2353  
2354  /**
2355   * Handles saving a widget via AJAX.
2356   *
2357   * @since 3.1.0
2358   *
2359   * @global array $wp_registered_widgets         Registered widgets.
2360   * @global array $wp_registered_widget_controls Registered widget controls.
2361   * @global array $wp_registered_widget_updates  Registered widget updates.
2362   */
2363  function wp_ajax_save_widget() {
2364      global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates;
2365  
2366      check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
2367  
2368      if ( ! current_user_can( 'edit_theme_options' ) || ! isset( $_POST['id_base'] ) ) {
2369          wp_die( -1 );
2370      }
2371  
2372      unset( $_POST['savewidgets'], $_POST['action'] );
2373  
2374      /**
2375       * Fires early when editing the widgets displayed in sidebars.
2376       *
2377       * @since 2.8.0
2378       */
2379      do_action( 'load-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
2380  
2381      /**
2382       * Fires early when editing the widgets displayed in sidebars.
2383       *
2384       * @since 2.8.0
2385       */
2386      do_action( 'widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
2387  
2388      /** This action is documented in wp-admin/widgets-form.php */
2389      do_action( 'sidebar_admin_setup' );
2390  
2391      $id_base      = wp_unslash( $_POST['id_base'] );
2392      $widget_id    = wp_unslash( $_POST['widget-id'] );
2393      $sidebar_id   = $_POST['sidebar'];
2394      $multi_number = ! empty( $_POST['multi_number'] ) ? (int) $_POST['multi_number'] : 0;
2395      $settings     = isset( $_POST[ 'widget-' . $id_base ] ) && is_array( $_POST[ 'widget-' . $id_base ] ) ? $_POST[ 'widget-' . $id_base ] : false;
2396      $error        = '<p>' . __( 'An error has occurred. Please reload the page and try again.' ) . '</p>';
2397  
2398      $sidebars = wp_get_sidebars_widgets();
2399      $sidebar  = $sidebars[ $sidebar_id ] ?? array();
2400  
2401      // Delete.
2402      if ( isset( $_POST['delete_widget'] ) && $_POST['delete_widget'] ) {
2403  
2404          if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) {
2405              wp_die( $error );
2406          }
2407  
2408          $sidebar = array_diff( $sidebar, array( $widget_id ) );
2409          $_POST   = array(
2410              'sidebar'            => $sidebar_id,
2411              'widget-' . $id_base => array(),
2412              'the-widget-id'      => $widget_id,
2413              'delete_widget'      => '1',
2414          );
2415  
2416          /** This action is documented in wp-admin/widgets-form.php */
2417          do_action( 'delete_widget', $widget_id, $sidebar_id, $id_base );
2418  
2419      } elseif ( $settings && preg_match( '/__i__|%i%/', key( $settings ) ) ) {
2420          if ( ! $multi_number ) {
2421              wp_die( $error );
2422          }
2423  
2424          $_POST[ 'widget-' . $id_base ] = array( $multi_number => reset( $settings ) );
2425          $widget_id                     = $id_base . '-' . $multi_number;
2426          $sidebar[]                     = $widget_id;
2427      }
2428      $_POST['widget-id'] = $sidebar;
2429  
2430      foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
2431  
2432          if ( $name === $id_base ) {
2433              if ( ! is_callable( $control['callback'] ) ) {
2434                  continue;
2435              }
2436  
2437              ob_start();
2438                  call_user_func_array( $control['callback'], $control['params'] );
2439              ob_end_clean();
2440              break;
2441          }
2442      }
2443  
2444      if ( isset( $_POST['delete_widget'] ) && $_POST['delete_widget'] ) {
2445          $sidebars[ $sidebar_id ] = $sidebar;
2446          wp_set_sidebars_widgets( $sidebars );
2447          echo "deleted:$widget_id";
2448          wp_die();
2449      }
2450  
2451      if ( ! empty( $_POST['add_new'] ) ) {
2452          wp_die();
2453      }
2454  
2455      $form = $wp_registered_widget_controls[ $widget_id ];
2456      if ( $form ) {
2457          call_user_func_array( $form['callback'], $form['params'] );
2458      }
2459  
2460      wp_die();
2461  }
2462  
2463  /**
2464   * Handles updating a widget via AJAX.
2465   *
2466   * @since 3.9.0
2467   *
2468   * @global WP_Customize_Manager $wp_customize Customizer manager object.
2469   */
2470  function wp_ajax_update_widget() {
2471      global $wp_customize;
2472      $wp_customize->widgets->wp_ajax_update_widget();
2473  }
2474  
2475  /**
2476   * Handles removing inactive widgets via AJAX.
2477   *
2478   * @since 4.4.0
2479   */
2480  function wp_ajax_delete_inactive_widgets() {
2481      check_ajax_referer( 'remove-inactive-widgets', 'removeinactivewidgets' );
2482  
2483      if ( ! current_user_can( 'edit_theme_options' ) ) {
2484          wp_die( -1 );
2485      }
2486  
2487      unset( $_POST['removeinactivewidgets'], $_POST['action'] );
2488      /** This action is documented in wp-admin/includes/ajax-actions.php */
2489      do_action( 'load-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
2490      /** This action is documented in wp-admin/includes/ajax-actions.php */
2491      do_action( 'widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
2492      /** This action is documented in wp-admin/widgets-form.php */
2493      do_action( 'sidebar_admin_setup' );
2494  
2495      $sidebars_widgets = wp_get_sidebars_widgets();
2496  
2497      foreach ( $sidebars_widgets['wp_inactive_widgets'] as $key => $widget_id ) {
2498          $pieces       = explode( '-', $widget_id );
2499          $multi_number = array_pop( $pieces );
2500          $id_base      = implode( '-', $pieces );
2501          $widget       = get_option( 'widget_' . $id_base );
2502          unset( $widget[ $multi_number ] );
2503          update_option( 'widget_' . $id_base, $widget );
2504          unset( $sidebars_widgets['wp_inactive_widgets'][ $key ] );
2505      }
2506  
2507      wp_set_sidebars_widgets( $sidebars_widgets );
2508  
2509      wp_die();
2510  }
2511  
2512  /**
2513   * Handles creating missing image sub-sizes for just uploaded images via AJAX.
2514   *
2515   * @since 5.3.0
2516   */
2517  function wp_ajax_media_create_image_subsizes() {
2518      check_ajax_referer( 'media-form' );
2519  
2520      if ( ! current_user_can( 'upload_files' ) ) {
2521          wp_send_json_error( array( 'message' => __( 'Sorry, you are not allowed to upload files.' ) ) );
2522      }
2523  
2524      if ( empty( $_POST['attachment_id'] ) ) {
2525          wp_send_json_error( array( 'message' => __( 'Upload failed. Please reload and try again.' ) ) );
2526      }
2527  
2528      $attachment_id = (int) $_POST['attachment_id'];
2529  
2530      if ( ! empty( $_POST['_wp_upload_failed_cleanup'] ) ) {
2531          // Upload failed. Cleanup.
2532          if ( wp_attachment_is_image( $attachment_id ) && current_user_can( 'delete_post', $attachment_id ) ) {
2533              $attachment = get_post( $attachment_id );
2534  
2535              // Created at most 10 min ago.
2536              if ( $attachment && ( time() - strtotime( $attachment->post_date_gmt ) < 600 ) ) {
2537                  wp_delete_attachment( $attachment_id, true );
2538                  wp_send_json_success();
2539              }
2540          }
2541      }
2542  
2543      /*
2544       * Set a custom header with the attachment_id.
2545       * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
2546       */
2547      if ( ! headers_sent() ) {
2548          header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
2549      }
2550  
2551      /*
2552       * This can still be pretty slow and cause timeout or out of memory errors.
2553       * The js that handles the response would need to also handle HTTP 500 errors.
2554       */
2555      wp_update_image_subsizes( $attachment_id );
2556  
2557      if ( ! empty( $_POST['_legacy_support'] ) ) {
2558          // The old (inline) uploader. Only needs the attachment_id.
2559          $response = array( 'id' => $attachment_id );
2560      } else {
2561          // Media modal and Media Library grid view.
2562          $response = wp_prepare_attachment_for_js( $attachment_id );
2563  
2564          if ( ! $response ) {
2565              wp_send_json_error( array( 'message' => __( 'Upload failed.' ) ) );
2566          }
2567      }
2568  
2569      // At this point the image has been uploaded successfully.
2570      wp_send_json_success( $response );
2571  }
2572  
2573  /**
2574   * Handles uploading attachments via AJAX.
2575   *
2576   * @since 3.3.0
2577   */
2578  function wp_ajax_upload_attachment() {
2579      check_ajax_referer( 'media-form' );
2580      /*
2581       * This function does not use wp_send_json_success() / wp_send_json_error()
2582       * as the html4 Plupload handler requires a text/html Content-Type for older IE.
2583       * See https://core.trac.wordpress.org/ticket/31037
2584       */
2585  
2586      if ( ! current_user_can( 'upload_files' ) ) {
2587          echo wp_json_encode(
2588              array(
2589                  'success' => false,
2590                  'data'    => array(
2591                      'message'  => __( 'Sorry, you are not allowed to upload files.' ),
2592                      'filename' => esc_html( $_FILES['async-upload']['name'] ),
2593                  ),
2594              )
2595          );
2596  
2597          wp_die();
2598      }
2599  
2600      if ( isset( $_REQUEST['post_id'] ) ) {
2601          $post_id = $_REQUEST['post_id'];
2602  
2603          if ( ! current_user_can( 'edit_post', $post_id ) ) {
2604              echo wp_json_encode(
2605                  array(
2606                      'success' => false,
2607                      'data'    => array(
2608                          'message'  => __( 'Sorry, you are not allowed to attach files to this post.' ),
2609                          'filename' => esc_html( $_FILES['async-upload']['name'] ),
2610                      ),
2611                  )
2612              );
2613  
2614              wp_die();
2615          }
2616      } else {
2617          $post_id = null;
2618      }
2619  
2620      $post_data = ! empty( $_REQUEST['post_data'] ) ? _wp_get_allowed_postdata( _wp_translate_postdata( false, (array) $_REQUEST['post_data'] ) ) : array();
2621  
2622      if ( is_wp_error( $post_data ) ) {
2623          wp_die( $post_data->get_error_message() );
2624      }
2625  
2626      // If the context is custom header or background, make sure the uploaded file is an image.
2627      if ( isset( $post_data['context'] ) && in_array( $post_data['context'], array( 'custom-header', 'custom-background' ), true ) ) {
2628          $wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['name'] );
2629  
2630          if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
2631              echo wp_json_encode(
2632                  array(
2633                      'success' => false,
2634                      'data'    => array(
2635                          'message'  => __( 'The uploaded file is not a valid image. Please try again.' ),
2636                          'filename' => esc_html( $_FILES['async-upload']['name'] ),
2637                      ),
2638                  )
2639              );
2640  
2641              wp_die();
2642          }
2643      }
2644  
2645      $attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data );
2646  
2647      if ( is_wp_error( $attachment_id ) ) {
2648          echo wp_json_encode(
2649              array(
2650                  'success' => false,
2651                  'data'    => array(
2652                      'message'  => $attachment_id->get_error_message(),
2653                      'filename' => esc_html( $_FILES['async-upload']['name'] ),
2654                  ),
2655              )
2656          );
2657  
2658          wp_die();
2659      }
2660  
2661      if ( isset( $post_data['context'] ) && isset( $post_data['theme'] ) ) {
2662          if ( 'custom-background' === $post_data['context'] ) {
2663              update_post_meta( $attachment_id, '_wp_attachment_is_custom_background', $post_data['theme'] );
2664          }
2665  
2666          if ( 'custom-header' === $post_data['context'] ) {
2667              update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', $post_data['theme'] );
2668          }
2669      }
2670  
2671      $attachment = wp_prepare_attachment_for_js( $attachment_id );
2672      if ( ! $attachment ) {
2673          wp_die();
2674      }
2675  
2676      echo wp_json_encode(
2677          array(
2678              'success' => true,
2679              'data'    => $attachment,
2680          )
2681      );
2682  
2683      wp_die();
2684  }
2685  
2686  /**
2687   * Handles image editing via AJAX.
2688   *
2689   * @since 3.1.0
2690   */
2691  function wp_ajax_image_editor() {
2692      $attachment_id = (int) $_POST['postid'];
2693  
2694      if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
2695          wp_die( -1 );
2696      }
2697  
2698      check_ajax_referer( "image_editor-$attachment_id" );
2699      require_once  ABSPATH . 'wp-admin/includes/image-edit.php';
2700  
2701      $message = false;
2702  
2703      switch ( $_POST['do'] ) {
2704          case 'save':
2705              $message = wp_save_image( $attachment_id );
2706              if ( ! empty( $message->error ) ) {
2707                  wp_send_json_error( $message );
2708              }
2709  
2710              wp_send_json_success( $message );
2711              break;
2712          case 'scale':
2713              $message = wp_save_image( $attachment_id );
2714              break;
2715          case 'restore':
2716              $message = wp_restore_image( $attachment_id );
2717              break;
2718      }
2719  
2720      ob_start();
2721      wp_image_editor( $attachment_id, $message );
2722      $html = ob_get_clean();
2723  
2724      if ( ! empty( $message->error ) ) {
2725          wp_send_json_error(
2726              array(
2727                  'message' => $message,
2728                  'html'    => $html,
2729              )
2730          );
2731      }
2732  
2733      wp_send_json_success(
2734          array(
2735              'message' => $message,
2736              'html'    => $html,
2737          )
2738      );
2739  }
2740  
2741  /**
2742   * Handles setting the featured image via AJAX.
2743   *
2744   * @since 3.1.0
2745   */
2746  function wp_ajax_set_post_thumbnail() {
2747      $json = ! empty( $_REQUEST['json'] ); // New-style request.
2748  
2749      $post_id = (int) $_POST['post_id'];
2750      if ( ! current_user_can( 'edit_post', $post_id ) ) {
2751          wp_die( -1 );
2752      }
2753  
2754      $thumbnail_id = (int) $_POST['thumbnail_id'];
2755  
2756      if ( $json ) {
2757          check_ajax_referer( "update-post_$post_id" );
2758      } else {
2759          check_ajax_referer( "set_post_thumbnail-$post_id" );
2760      }
2761  
2762      if ( -1 === $thumbnail_id ) {
2763          if ( delete_post_thumbnail( $post_id ) ) {
2764              $return = _wp_post_thumbnail_html( null, $post_id );
2765              $json ? wp_send_json_success( $return ) : wp_die( $return );
2766          } else {
2767              wp_die( 0 );
2768          }
2769      }
2770  
2771      if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2772          $return = _wp_post_thumbnail_html( $thumbnail_id, $post_id );
2773          $json ? wp_send_json_success( $return ) : wp_die( $return );
2774      }
2775  
2776      wp_die( 0 );
2777  }
2778  
2779  /**
2780   * Handles retrieving HTML for the featured image via AJAX.
2781   *
2782   * @since 4.6.0
2783   */
2784  function wp_ajax_get_post_thumbnail_html() {
2785      $post_id = (int) $_POST['post_id'];
2786  
2787      check_ajax_referer( "update-post_$post_id" );
2788  
2789      if ( ! current_user_can( 'edit_post', $post_id ) ) {
2790          wp_die( -1 );
2791      }
2792  
2793      $thumbnail_id = (int) $_POST['thumbnail_id'];
2794  
2795      // For backward compatibility, -1 refers to no featured image.
2796      if ( -1 === $thumbnail_id ) {
2797          $thumbnail_id = null;
2798      }
2799  
2800      $return = _wp_post_thumbnail_html( $thumbnail_id, $post_id );
2801      wp_send_json_success( $return );
2802  }
2803  
2804  /**
2805   * Handles setting the featured image for an attachment via AJAX.
2806   *
2807   * @since 4.0.0
2808   *
2809   * @see set_post_thumbnail()
2810   */
2811  function wp_ajax_set_attachment_thumbnail() {
2812      if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) {
2813          wp_send_json_error();
2814      }
2815  
2816      $thumbnail_id = (int) $_POST['thumbnail_id'];
2817      if ( empty( $thumbnail_id ) ) {
2818          wp_send_json_error();
2819      }
2820  
2821      if ( false === check_ajax_referer( 'set-attachment-thumbnail', '_ajax_nonce', false ) ) {
2822          wp_send_json_error();
2823      }
2824  
2825      $post_ids = array();
2826      // For each URL, try to find its corresponding post ID.
2827      foreach ( $_POST['urls'] as $url ) {
2828          $post_id = attachment_url_to_postid( $url );
2829          if ( ! empty( $post_id ) ) {
2830              $post_ids[] = $post_id;
2831          }
2832      }
2833  
2834      if ( empty( $post_ids ) ) {
2835          wp_send_json_error();
2836      }
2837  
2838      $success = 0;
2839      // For each found attachment, set its thumbnail.
2840      foreach ( $post_ids as $post_id ) {
2841          if ( ! current_user_can( 'edit_post', $post_id ) ) {
2842              continue;
2843          }
2844  
2845          if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2846              ++$success;
2847          }
2848      }
2849  
2850      if ( 0 === $success ) {
2851          wp_send_json_error();
2852      } else {
2853          wp_send_json_success();
2854      }
2855  
2856      wp_send_json_error();
2857  }
2858  
2859  /**
2860   * Handles formatting a date via AJAX.
2861   *
2862   * @since 3.1.0
2863   */
2864  function wp_ajax_date_format() {
2865      wp_die( date_i18n( sanitize_option( 'date_format', wp_unslash( $_POST['date'] ) ) ) );
2866  }
2867  
2868  /**
2869   * Handles formatting a time via AJAX.
2870   *
2871   * @since 3.1.0
2872   */
2873  function wp_ajax_time_format() {
2874      wp_die( date_i18n( sanitize_option( 'time_format', wp_unslash( $_POST['date'] ) ) ) );
2875  }
2876  
2877  /**
2878   * Handles saving posts from the fullscreen editor via AJAX.
2879   *
2880   * @since 3.1.0
2881   * @deprecated 4.3.0
2882   */
2883  function wp_ajax_wp_fullscreen_save_post() {
2884      $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
2885  
2886      $post = null;
2887  
2888      if ( $post_id ) {
2889          $post = get_post( $post_id );
2890      }
2891  
2892      check_ajax_referer( 'update-post_' . $post_id, '_wpnonce' );
2893  
2894      $post_id = edit_post();
2895  
2896      if ( is_wp_error( $post_id ) ) {
2897          wp_send_json_error();
2898      }
2899  
2900      if ( $post ) {
2901          $last_date = mysql2date( __( 'F j, Y' ), $post->post_modified );
2902          $last_time = mysql2date( __( 'g:i a' ), $post->post_modified );
2903      } else {
2904          $last_date = date_i18n( __( 'F j, Y' ) );
2905          $last_time = date_i18n( __( 'g:i a' ) );
2906      }
2907  
2908      $last_id = get_post_meta( $post_id, '_edit_last', true );
2909      if ( $last_id ) {
2910          $last_user = get_userdata( $last_id );
2911          /* translators: 1: User's display name, 2: Date of last edit, 3: Time of last edit. */
2912          $last_edited = sprintf( __( 'Last edited by %1$s on %2$s at %3$s' ), esc_html( $last_user->display_name ), $last_date, $last_time );
2913      } else {
2914          /* translators: 1: Date of last edit, 2: Time of last edit. */
2915          $last_edited = sprintf( __( 'Last edited on %1$s at %2$s' ), $last_date, $last_time );
2916      }
2917  
2918      wp_send_json_success( array( 'last_edited' => $last_edited ) );
2919  }
2920  
2921  /**
2922   * Handles removing a post lock via AJAX.
2923   *
2924   * @since 3.1.0
2925   */
2926  function wp_ajax_wp_remove_post_lock() {
2927      if ( empty( $_POST['post_ID'] ) || empty( $_POST['active_post_lock'] ) ) {
2928          wp_die( 0 );
2929      }
2930  
2931      $post_id = (int) $_POST['post_ID'];
2932      $post    = get_post( $post_id );
2933  
2934      if ( ! $post ) {
2935          wp_die( 0 );
2936      }
2937  
2938      check_ajax_referer( 'update-post_' . $post_id );
2939  
2940      if ( ! current_user_can( 'edit_post', $post_id ) ) {
2941          wp_die( -1 );
2942      }
2943  
2944      $active_lock = array_map( 'absint', explode( ':', $_POST['active_post_lock'] ) );
2945  
2946      if ( get_current_user_id() !== $active_lock[1] ) {
2947          wp_die( 0 );
2948      }
2949  
2950      /**
2951       * Filters the post lock window duration.
2952       *
2953       * @since 3.3.0
2954       *
2955       * @param int $interval The interval in seconds the post lock duration
2956       *                      should last, plus 5 seconds. Default 150.
2957       */
2958      $new_lock = ( time() - apply_filters( 'wp_check_post_lock_window', 150 ) + 5 ) . ':' . $active_lock[1];
2959      update_post_meta( $post_id, '_edit_lock', $new_lock, implode( ':', $active_lock ) );
2960      wp_die( 1 );
2961  }
2962  
2963  /**
2964   * Handles dismissing a WordPress pointer via AJAX.
2965   *
2966   * @since 3.1.0
2967   */
2968  function wp_ajax_dismiss_wp_pointer() {
2969      $pointer = $_POST['pointer'];
2970  
2971      if ( sanitize_key( $pointer ) !== $pointer ) {
2972          wp_die( 0 );
2973      }
2974  
2975      //  check_ajax_referer( 'dismiss-pointer_' . $pointer );
2976  
2977      $dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
2978  
2979      if ( in_array( $pointer, $dismissed, true ) ) {
2980          wp_die( 0 );
2981      }
2982  
2983      $dismissed[] = $pointer;
2984      $dismissed   = implode( ',', $dismissed );
2985  
2986      update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
2987      wp_die( 1 );
2988  }
2989  
2990  /**
2991   * Handles getting an attachment via AJAX.
2992   *
2993   * @since 3.5.0
2994   */
2995  function wp_ajax_get_attachment() {
2996      if ( ! isset( $_REQUEST['id'] ) ) {
2997          wp_send_json_error();
2998      }
2999  
3000      $id = absint( $_REQUEST['id'] );
3001      if ( ! $id ) {
3002          wp_send_json_error();
3003      }
3004  
3005      $post = get_post( $id );
3006      if ( ! $post ) {
3007          wp_send_json_error();
3008      }
3009  
3010      if ( 'attachment' !== $post->post_type ) {
3011          wp_send_json_error();
3012      }
3013  
3014      if ( ! current_user_can( 'upload_files' ) ) {
3015          wp_send_json_error();
3016      }
3017  
3018      $attachment = wp_prepare_attachment_for_js( $id );
3019      if ( ! $attachment ) {
3020          wp_send_json_error();
3021      }
3022  
3023      wp_send_json_success( $attachment );
3024  }
3025  
3026  /**
3027   * Handles querying attachments via AJAX.
3028   *
3029   * @since 3.5.0
3030   */
3031  function wp_ajax_query_attachments() {
3032      if ( ! current_user_can( 'upload_files' ) ) {
3033          wp_send_json_error();
3034      }
3035  
3036      $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
3037      $keys  = array(
3038          's',
3039          'order',
3040          'orderby',
3041          'posts_per_page',
3042          'paged',
3043          'post_mime_type',
3044          'post_parent',
3045          'author',
3046          'post__in',
3047          'post__not_in',
3048          'year',
3049          'monthnum',
3050      );
3051  
3052      foreach ( get_taxonomies_for_attachments( 'objects' ) as $taxonomy ) {
3053          if ( $taxonomy->query_var && isset( $query[ $taxonomy->query_var ] ) ) {
3054              $keys[] = $taxonomy->query_var;
3055          }
3056      }
3057  
3058      $query              = array_intersect_key( $query, array_flip( $keys ) );
3059      $query['post_type'] = 'attachment';
3060  
3061      if (
3062          MEDIA_TRASH &&
3063          ! empty( $_REQUEST['query']['post_status'] ) &&
3064          'trash' === $_REQUEST['query']['post_status']
3065      ) {
3066          $query['post_status'] = 'trash';
3067      } else {
3068          $query['post_status'] = 'inherit';
3069      }
3070  
3071      if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) ) {
3072          $query['post_status'] .= ',private';
3073      }
3074  
3075      // Filter query clauses to include filenames.
3076      if ( isset( $query['s'] ) ) {
3077          add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' );
3078      }
3079  
3080      /**
3081       * Filters the arguments passed to WP_Query during an Ajax
3082       * call for querying attachments.
3083       *
3084       * @since 3.7.0
3085       *
3086       * @see WP_Query::parse_query()
3087       *
3088       * @param array $query An array of query variables.
3089       */
3090      $query             = apply_filters( 'ajax_query_attachments_args', $query );
3091      $attachments_query = new WP_Query( $query );
3092      update_post_parent_caches( $attachments_query->posts );
3093  
3094      $posts       = array_map( 'wp_prepare_attachment_for_js', $attachments_query->posts );
3095      $posts       = array_filter( $posts );
3096      $total_posts = $attachments_query->found_posts;
3097  
3098      if ( $total_posts < 1 ) {
3099          // Out-of-bounds, run the query again without LIMIT for total count.
3100          unset( $query['paged'] );
3101  
3102          $count_query = new WP_Query();
3103          $count_query->query( $query );
3104          $total_posts = $count_query->found_posts;
3105      }
3106  
3107      $posts_per_page = (int) $attachments_query->get( 'posts_per_page' );
3108  
3109      $max_pages = $posts_per_page ? (int) ceil( $total_posts / $posts_per_page ) : 0;
3110  
3111      header( 'X-WP-Total: ' . (int) $total_posts );
3112      header( 'X-WP-TotalPages: ' . $max_pages );
3113  
3114      wp_send_json_success( $posts );
3115  }
3116  
3117  /**
3118   * Handles updating attachment attributes via AJAX.
3119   *
3120   * @since 3.5.0
3121   */
3122  function wp_ajax_save_attachment() {
3123      if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) ) {
3124          wp_send_json_error();
3125      }
3126  
3127      $id = absint( $_REQUEST['id'] );
3128      if ( ! $id ) {
3129          wp_send_json_error();
3130      }
3131  
3132      check_ajax_referer( 'update-post_' . $id, 'nonce' );
3133  
3134      if ( ! current_user_can( 'edit_post', $id ) ) {
3135          wp_send_json_error();
3136      }
3137  
3138      $changes = $_REQUEST['changes'];
3139      $post    = get_post( $id, ARRAY_A );
3140      if ( ! $post ) {
3141          wp_send_json_error();
3142      }
3143  
3144      if ( 'attachment' !== $post['post_type'] ) {
3145          wp_send_json_error();
3146      }
3147  
3148      if ( isset( $changes['parent'] ) ) {
3149          $post['post_parent'] = $changes['parent'];
3150      }
3151  
3152      if ( isset( $changes['title'] ) ) {
3153          $post['post_title'] = $changes['title'];
3154      }
3155  
3156      if ( isset( $changes['caption'] ) ) {
3157          $post['post_excerpt'] = $changes['caption'];
3158      }
3159  
3160      if ( isset( $changes['description'] ) ) {
3161          $post['post_content'] = $changes['description'];
3162      }
3163  
3164      if ( MEDIA_TRASH && isset( $changes['status'] ) ) {
3165          $post['post_status'] = $changes['status'];
3166      }
3167  
3168      if ( isset( $changes['alt'] ) ) {
3169          $alt = wp_unslash( $changes['alt'] );
3170          if ( get_post_meta( $id, '_wp_attachment_image_alt', true ) !== $alt ) {
3171              $alt = wp_strip_all_tags( $alt, true );
3172              update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) );
3173          }
3174      }
3175  
3176      if ( wp_attachment_is( 'audio', $post['ID'] ) ) {
3177          $changed  = false;
3178          $id3_data = wp_get_attachment_metadata( $post['ID'] );
3179  
3180          if ( ! is_array( $id3_data ) ) {
3181              $changed  = true;
3182              $id3_data = array();
3183          }
3184  
3185          foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
3186              if ( isset( $changes[ $key ] ) ) {
3187                  $changed          = true;
3188                  $id3_data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
3189              }
3190          }
3191  
3192          if ( $changed ) {
3193              wp_update_attachment_metadata( $id, $id3_data );
3194          }
3195      }
3196  
3197      if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
3198          wp_delete_post( $id );
3199      } else {
3200          wp_update_post( $post );
3201      }
3202  
3203      wp_send_json_success();
3204  }
3205  
3206  /**
3207   * Handles saving backward compatible attachment attributes via AJAX.
3208   *
3209   * @since 3.5.0
3210   */
3211  function wp_ajax_save_attachment_compat() {
3212      if ( ! isset( $_REQUEST['id'] ) ) {
3213          wp_send_json_error();
3214      }
3215  
3216      $id = absint( $_REQUEST['id'] );
3217      if ( ! $id ) {
3218          wp_send_json_error();
3219      }
3220  
3221      if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) ) {
3222          wp_send_json_error();
3223      }
3224  
3225      $attachment_data = $_REQUEST['attachments'][ $id ];
3226  
3227      check_ajax_referer( 'update-post_' . $id, 'nonce' );
3228  
3229      if ( ! current_user_can( 'edit_post', $id ) ) {
3230          wp_send_json_error();
3231      }
3232  
3233      $post = get_post( $id, ARRAY_A );
3234      if ( ! $post ) {
3235          wp_send_json_error();
3236      }
3237  
3238      if ( 'attachment' !== $post['post_type'] ) {
3239          wp_send_json_error();
3240      }
3241  
3242      /** This filter is documented in wp-admin/includes/media.php */
3243      $post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data );
3244  
3245      if ( isset( $post['errors'] ) ) {
3246          $errors = $post['errors']; // @todo return me and display me!
3247          unset( $post['errors'] );
3248      }
3249  
3250      wp_update_post( $post );
3251  
3252      foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
3253          if ( isset( $attachment_data[ $taxonomy ] ) ) {
3254              wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false );
3255          }
3256      }
3257  
3258      $attachment = wp_prepare_attachment_for_js( $id );
3259  
3260      if ( ! $attachment ) {
3261          wp_send_json_error();
3262      }
3263  
3264      wp_send_json_success( $attachment );
3265  }
3266  
3267  /**
3268   * Handles saving the attachment order via AJAX.
3269   *
3270   * @since 3.5.0
3271   */
3272  function wp_ajax_save_attachment_order() {
3273      if ( ! isset( $_REQUEST['post_id'] ) ) {
3274          wp_send_json_error();
3275      }
3276  
3277      $post_id = absint( $_REQUEST['post_id'] );
3278      if ( ! $post_id ) {
3279          wp_send_json_error();
3280      }
3281  
3282      if ( empty( $_REQUEST['attachments'] ) ) {
3283          wp_send_json_error();
3284      }
3285  
3286      check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
3287  
3288      $attachments = $_REQUEST['attachments'];
3289  
3290      if ( ! current_user_can( 'edit_post', $post_id ) ) {
3291          wp_send_json_error();
3292      }
3293  
3294      foreach ( $attachments as $attachment_id => $menu_order ) {
3295          if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
3296              continue;
3297          }
3298  
3299          $attachment = get_post( $attachment_id );
3300  
3301          if ( ! $attachment ) {
3302              continue;
3303          }
3304  
3305          if ( 'attachment' !== $attachment->post_type ) {
3306              continue;
3307          }
3308  
3309          wp_update_post(
3310              array(
3311                  'ID'         => $attachment_id,
3312                  'menu_order' => $menu_order,
3313              )
3314          );
3315      }
3316  
3317      wp_send_json_success();
3318  }
3319  
3320  /**
3321   * Handles sending an attachment to the editor via AJAX.
3322   *
3323   * Generates the HTML to send an attachment to the editor.
3324   * Backward compatible with the {@see 'media_send_to_editor'} filter
3325   * and the chain of filters that follow.
3326   *
3327   * @since 3.5.0
3328   */
3329  function wp_ajax_send_attachment_to_editor() {
3330      check_ajax_referer( 'media-send-to-editor', 'nonce' );
3331  
3332      $attachment = wp_unslash( $_POST['attachment'] );
3333  
3334      $id = (int) $attachment['id'];
3335  
3336      $post = get_post( $id );
3337      if ( ! $post ) {
3338          wp_send_json_error();
3339      }
3340  
3341      if ( 'attachment' !== $post->post_type ) {
3342          wp_send_json_error();
3343      }
3344  
3345      if ( current_user_can( 'edit_post', $id ) ) {
3346          // If this attachment is unattached, attach it. Primarily a back compat thing.
3347          $insert_into_post_id = (int) $_POST['post_id'];
3348  
3349          if ( 0 === $post->post_parent && $insert_into_post_id ) {
3350              wp_update_post(
3351                  array(
3352                      'ID'          => $id,
3353                      'post_parent' => $insert_into_post_id,
3354                  )
3355              );
3356          }
3357      }
3358  
3359      $url = empty( $attachment['url'] ) ? '' : $attachment['url'];
3360      $rel = ( str_contains( $url, 'attachment_id' ) || get_attachment_link( $id ) === $url );
3361  
3362      remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
3363  
3364      if ( str_starts_with( $post->post_mime_type, 'image' ) ) {
3365          $align = $attachment['align'] ?? 'none';
3366          $size  = $attachment['image-size'] ?? 'medium';
3367          $alt   = $attachment['image_alt'] ?? '';
3368  
3369          // No whitespace-only captions.
3370          $caption = $attachment['post_excerpt'] ?? '';
3371          if ( '' === trim( $caption ) ) {
3372              $caption = '';
3373          }
3374  
3375          $title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
3376          $html  = get_image_send_to_editor( $id, $caption, $title, $align, $url, $rel, $size, $alt );
3377      } elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post ) ) {
3378          $html = stripslashes_deep( $_POST['html'] );
3379      } else {
3380          $html = $attachment['post_title'] ?? '';
3381          $rel  = $rel ? ' rel="attachment wp-att-' . $id . '"' : ''; // Hard-coded string, $id is already sanitized.
3382  
3383          if ( ! empty( $url ) ) {
3384              $html = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
3385          }
3386      }
3387  
3388      /** This filter is documented in wp-admin/includes/media.php */
3389      $html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
3390  
3391      wp_send_json_success( $html );
3392  }
3393  
3394  /**
3395   * Handles sending a link to the editor via AJAX.
3396   *
3397   * Generates the HTML to send a non-image embed link to the editor.
3398   *
3399   * Backward compatible with the following filters:
3400   * - file_send_to_editor_url
3401   * - audio_send_to_editor_url
3402   * - video_send_to_editor_url
3403   *
3404   * @since 3.5.0
3405   *
3406   * @global WP_Post  $post     Global post object.
3407   * @global WP_Embed $wp_embed WordPress Embed object.
3408   */
3409  function wp_ajax_send_link_to_editor() {
3410      global $post, $wp_embed;
3411  
3412      check_ajax_referer( 'media-send-to-editor', 'nonce' );
3413  
3414      $src = wp_unslash( $_POST['src'] );
3415      if ( ! $src ) {
3416          wp_send_json_error();
3417      }
3418  
3419      if ( ! strpos( $src, '://' ) ) {
3420          $src = 'http://' . $src;
3421      }
3422  
3423      $src = sanitize_url( $src );
3424      if ( ! $src ) {
3425          wp_send_json_error();
3426      }
3427  
3428      $link_text = trim( wp_unslash( $_POST['link_text'] ) );
3429      if ( ! $link_text ) {
3430          $link_text = wp_basename( $src );
3431      }
3432  
3433      $post = get_post( $_POST['post_id'] ?? 0 );
3434  
3435      // Ping WordPress for an embed.
3436      $check_embed = $wp_embed->run_shortcode( '[embed]' . $src . '[/embed]' );
3437  
3438      // Fallback that WordPress creates when no oEmbed was found.
3439      $fallback = $wp_embed->maybe_make_link( $src );
3440  
3441      if ( $check_embed !== $fallback ) {
3442          // TinyMCE view for [embed] will parse this.
3443          $html = '[embed]' . $src . '[/embed]';
3444      } elseif ( $link_text ) {
3445          $html = '<a href="' . esc_url( $src ) . '">' . $link_text . '</a>';
3446      } else {
3447          $html = '';
3448      }
3449  
3450      // Figure out what filter to run:
3451      $type      = 'file';
3452      $extension = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src );
3453      if ( $extension ) {
3454          $extension_type = wp_ext2type( $extension );
3455          if ( 'audio' === $extension_type || 'video' === $extension_type ) {
3456              $type = $extension_type;
3457          }
3458      }
3459  
3460      /** This filter is documented in wp-admin/includes/media.php */
3461      $html = apply_filters( "{$type}_send_to_editor_url", $html, $src, $link_text );
3462  
3463      wp_send_json_success( $html );
3464  }
3465  
3466  /**
3467   * Handles the Heartbeat API via AJAX.
3468   *
3469   * Runs when the user is logged in.
3470   *
3471   * @since 3.6.0
3472   */
3473  function wp_ajax_heartbeat() {
3474      if ( empty( $_POST['_nonce'] ) ) {
3475          wp_send_json_error();
3476      }
3477  
3478      $response    = array();
3479      $data        = array();
3480      $nonce_state = wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' );
3481  
3482      // 'screen_id' is the same as $current_screen->id and the JS global 'pagenow'.
3483      if ( ! empty( $_POST['screen_id'] ) ) {
3484          $screen_id = sanitize_key( $_POST['screen_id'] );
3485      } else {
3486          $screen_id = 'front';
3487      }
3488  
3489      if ( ! empty( $_POST['data'] ) ) {
3490          $data = wp_unslash( (array) $_POST['data'] );
3491      }
3492  
3493      if ( 1 !== $nonce_state ) {
3494          /**
3495           * Filters the nonces to send to the New/Edit Post screen.
3496           *
3497           * @since 4.3.0
3498           *
3499           * @param array  $response  The Heartbeat response.
3500           * @param array  $data      The $_POST data sent.
3501           * @param string $screen_id The screen ID.
3502           */
3503          $response = apply_filters( 'wp_refresh_nonces', $response, $data, $screen_id );
3504  
3505          if ( false === $nonce_state ) {
3506              // User is logged in but nonces have expired.
3507              $response['nonces_expired'] = true;
3508              wp_send_json( $response );
3509          }
3510      }
3511  
3512      if ( ! empty( $data ) ) {
3513          /**
3514           * Filters the Heartbeat response received.
3515           *
3516           * @since 3.6.0
3517           *
3518           * @param array  $response  The Heartbeat response.
3519           * @param array  $data      The $_POST data sent.
3520           * @param string $screen_id The screen ID.
3521           */
3522          $response = apply_filters( 'heartbeat_received', $response, $data, $screen_id );
3523      }
3524  
3525      /**
3526       * Filters the Heartbeat response sent.
3527       *
3528       * @since 3.6.0
3529       *
3530       * @param array  $response  The Heartbeat response.
3531       * @param string $screen_id The screen ID.
3532       */
3533      $response = apply_filters( 'heartbeat_send', $response, $screen_id );
3534  
3535      /**
3536       * Fires when Heartbeat ticks in logged-in environments.
3537       *
3538       * Allows the transport to be easily replaced with long-polling.
3539       *
3540       * @since 3.6.0
3541       *
3542       * @param array  $response  The Heartbeat response.
3543       * @param string $screen_id The screen ID.
3544       */
3545      do_action( 'heartbeat_tick', $response, $screen_id );
3546  
3547      // Send the current time according to the server.
3548      $response['server_time'] = time();
3549  
3550      wp_send_json( $response );
3551  }
3552  
3553  /**
3554   * Handles getting revision diffs via AJAX.
3555   *
3556   * @since 3.6.0
3557   */
3558  function wp_ajax_get_revision_diffs() {
3559      require  ABSPATH . 'wp-admin/includes/revision.php';
3560  
3561      $post = get_post( (int) $_REQUEST['post_id'] );
3562      if ( ! $post ) {
3563          wp_send_json_error();
3564      }
3565  
3566      if ( ! current_user_can( 'edit_post', $post->ID ) ) {
3567          wp_send_json_error();
3568      }
3569  
3570      // Really just pre-loading the cache here.
3571      $revisions = wp_get_post_revisions( $post->ID, array( 'check_enabled' => false ) );
3572      if ( ! $revisions ) {
3573          wp_send_json_error();
3574      }
3575  
3576      $return = array();
3577  
3578      // Increase the script timeout limit to allow ample time for diff UI setup.
3579      if ( function_exists( 'set_time_limit' ) ) {
3580          set_time_limit( 5 * MINUTE_IN_SECONDS );
3581      }
3582  
3583      foreach ( $_REQUEST['compare'] as $compare_key ) {
3584          list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
3585  
3586          $return[] = array(
3587              'id'     => $compare_key,
3588              'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
3589          );
3590      }
3591      wp_send_json_success( $return );
3592  }
3593  
3594  /**
3595   * Handles auto-saving the selected color scheme for
3596   * a user's own profile via AJAX.
3597   *
3598   * @since 3.8.0
3599   *
3600   * @global array $_wp_admin_css_colors Registered admin CSS color schemes.
3601   */
3602  function wp_ajax_save_user_color_scheme() {
3603      global $_wp_admin_css_colors;
3604  
3605      check_ajax_referer( 'save-color-scheme', 'nonce' );
3606  
3607      $color_scheme = sanitize_key( $_POST['color_scheme'] );
3608  
3609      if ( ! isset( $_wp_admin_css_colors[ $color_scheme ] ) ) {
3610          wp_send_json_error();
3611      }
3612  
3613      $previous_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true );
3614      update_user_meta( get_current_user_id(), 'admin_color', $color_scheme );
3615  
3616      wp_send_json_success(
3617          array(
3618              'previousScheme' => 'admin-color-' . $previous_color_scheme,
3619              'currentScheme'  => 'admin-color-' . $color_scheme,
3620          )
3621      );
3622  }
3623  
3624  /**
3625   * Handles getting themes from themes_api() via AJAX.
3626   *
3627   * @since 3.9.0
3628   *
3629   * @global array $themes_allowedtags   Allowed HTML tags for theme descriptions.
3630   * @global array $theme_field_defaults Default theme fields.
3631   */
3632  function wp_ajax_query_themes() {
3633      global $themes_allowedtags, $theme_field_defaults;
3634  
3635      if ( ! current_user_can( 'install_themes' ) ) {
3636          wp_send_json_error();
3637      }
3638  
3639      $args = wp_parse_args(
3640          wp_unslash( $_REQUEST['request'] ),
3641          array(
3642              'per_page' => 20,
3643              'fields'   => array_merge(
3644                  (array) $theme_field_defaults,
3645                  array(
3646                      'reviews_url' => true, // Explicitly request the reviews URL to be linked from the Add Themes screen.
3647                  )
3648              ),
3649          )
3650      );
3651  
3652      if ( isset( $args['browse'] ) && 'favorites' === $args['browse'] && ! isset( $args['user'] ) ) {
3653          $user = get_user_option( 'wporg_favorites' );
3654          if ( $user ) {
3655              $args['user'] = $user;
3656          }
3657      }
3658  
3659      $old_filter = $args['browse'] ?? 'search';
3660  
3661      /** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */
3662      $args = apply_filters( 'install_themes_table_api_args_' . $old_filter, $args );
3663  
3664      $api = themes_api( 'query_themes', $args );
3665  
3666      if ( is_wp_error( $api ) ) {
3667          wp_send_json_error();
3668      }
3669  
3670      $update_php = network_admin_url( 'update.php?action=install-theme' );
3671  
3672      $installed_themes = search_theme_directories();
3673  
3674      if ( false === $installed_themes ) {
3675          $installed_themes = array();
3676      }
3677  
3678      foreach ( $installed_themes as $theme_slug => $theme_data ) {
3679          // Ignore child themes.
3680          if ( str_contains( $theme_slug, '/' ) ) {
3681              unset( $installed_themes[ $theme_slug ] );
3682          }
3683      }
3684  
3685      foreach ( $api->themes as &$theme ) {
3686          $theme->install_url = add_query_arg(
3687              array(
3688                  'theme'    => $theme->slug,
3689                  '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ),
3690              ),
3691              $update_php
3692          );
3693  
3694          if ( current_user_can( 'switch_themes' ) ) {
3695              if ( is_multisite() ) {
3696                  $theme->activate_url = add_query_arg(
3697                      array(
3698                          'action'   => 'enable',
3699                          '_wpnonce' => wp_create_nonce( 'enable-theme_' . $theme->slug ),
3700                          'theme'    => $theme->slug,
3701                      ),
3702                      network_admin_url( 'themes.php' )
3703                  );
3704              } else {
3705                  $theme->activate_url = add_query_arg(
3706                      array(
3707                          'action'     => 'activate',
3708                          '_wpnonce'   => wp_create_nonce( 'switch-theme_' . $theme->slug ),
3709                          'stylesheet' => $theme->slug,
3710                      ),
3711                      admin_url( 'themes.php' )
3712                  );
3713              }
3714          }
3715  
3716          $is_theme_installed = array_key_exists( $theme->slug, $installed_themes );
3717  
3718          // We only care about installed themes.
3719          $theme->block_theme = $is_theme_installed && wp_get_theme( $theme->slug )->is_block_theme();
3720  
3721          if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
3722              $customize_url = $theme->block_theme ? admin_url( 'site-editor.php' ) : wp_customize_url( $theme->slug );
3723  
3724              $theme->customize_url = add_query_arg(
3725                  array(
3726                      'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
3727                  ),
3728                  $customize_url
3729              );
3730          }
3731  
3732          $theme->name        = wp_kses( $theme->name, $themes_allowedtags );
3733          $theme->author      = wp_kses( $theme->author['display_name'], $themes_allowedtags );
3734          $theme->version     = wp_kses( $theme->version, $themes_allowedtags );
3735          $theme->description = wp_kses( $theme->description, $themes_allowedtags );
3736  
3737          $theme->stars = wp_star_rating(
3738              array(
3739                  'rating' => $theme->rating,
3740                  'type'   => 'percent',
3741                  'number' => $theme->num_ratings,
3742                  'echo'   => false,
3743              )
3744          );
3745  
3746          $theme->num_ratings    = number_format_i18n( $theme->num_ratings );
3747          $theme->preview_url    = set_url_scheme( $theme->preview_url );
3748          $theme->compatible_wp  = is_wp_version_compatible( $theme->requires );
3749          $theme->compatible_php = is_php_version_compatible( $theme->requires_php );
3750      }
3751  
3752      wp_send_json_success( $api );
3753  }
3754  
3755  /**
3756   * Applies [embed] Ajax handlers to a string.
3757   *
3758   * @since 4.0.0
3759   *
3760   * @global WP_Post    $post          Global post object.
3761   * @global WP_Embed   $wp_embed      WordPress Embed object.
3762   * @global WP_Scripts $wp_scripts    Script dependencies object.
3763   * @global int        $content_width Shared post content width.
3764   */
3765  function wp_ajax_parse_embed() {
3766      global $post, $wp_embed, $content_width;
3767  
3768      if ( empty( $_POST['shortcode'] ) ) {
3769          wp_send_json_error();
3770      }
3771  
3772      $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
3773  
3774      if ( $post_id > 0 ) {
3775          $post = get_post( $post_id );
3776  
3777          if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3778              wp_send_json_error();
3779          }
3780          setup_postdata( $post );
3781      } elseif ( ! current_user_can( 'edit_posts' ) ) { // See WP_oEmbed_Controller::get_proxy_item_permissions_check().
3782          wp_send_json_error();
3783      }
3784  
3785      $shortcode = wp_unslash( $_POST['shortcode'] );
3786  
3787      preg_match( '/' . get_shortcode_regex() . '/s', $shortcode, $matches );
3788      $atts = shortcode_parse_atts( $matches[3] );
3789  
3790      if ( ! empty( $matches[5] ) ) {
3791          $url = $matches[5];
3792      } elseif ( ! empty( $atts['src'] ) ) {
3793          $url = $atts['src'];
3794      } else {
3795          $url = '';
3796      }
3797  
3798      $parsed                         = false;
3799      $wp_embed->return_false_on_fail = true;
3800  
3801      if ( 0 === $post_id ) {
3802          /*
3803           * Refresh oEmbeds cached outside of posts that are past their TTL.
3804           * Posts are excluded because they have separate logic for refreshing
3805           * their post meta caches. See WP_Embed::cache_oembed().
3806           */
3807          $wp_embed->usecache = false;
3808      }
3809  
3810      if ( is_ssl() && str_starts_with( $url, 'http://' ) ) {
3811          /*
3812           * Admin is ssl and the user pasted non-ssl URL.
3813           * Check if the provider supports ssl embeds and use that for the preview.
3814           */
3815          $ssl_shortcode = preg_replace( '%^(\\[embed[^\\]]*\\])http://%i', '$1https://', $shortcode );
3816          $parsed        = $wp_embed->run_shortcode( $ssl_shortcode );
3817  
3818          if ( ! $parsed ) {
3819              $no_ssl_support = true;
3820          }
3821      }
3822  
3823      // Set $content_width so any embeds fit in the destination iframe.
3824      if ( isset( $_POST['maxwidth'] ) && is_numeric( $_POST['maxwidth'] ) && $_POST['maxwidth'] > 0 ) {
3825          if ( ! isset( $content_width ) ) {
3826              $content_width = (int) $_POST['maxwidth'];
3827          } else {
3828              $content_width = min( $content_width, (int) $_POST['maxwidth'] );
3829          }
3830      }
3831  
3832      if ( $url && ! $parsed ) {
3833          $parsed = $wp_embed->run_shortcode( $shortcode );
3834      }
3835  
3836      if ( ! $parsed ) {
3837          wp_send_json_error(
3838              array(
3839                  'type'    => 'not-embeddable',
3840                  /* translators: %s: URL that could not be embedded. */
3841                  'message' => sprintf( __( '%s failed to embed.' ), '<code>' . esc_html( $url ) . '</code>' ),
3842              )
3843          );
3844      }
3845  
3846      if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
3847          $styles     = '';
3848          $mce_styles = wpview_media_sandbox_styles();
3849  
3850          foreach ( $mce_styles as $style ) {
3851              $styles .= sprintf( '<link rel="stylesheet" href="%s" />', $style );
3852          }
3853  
3854          $html = do_shortcode( $parsed );
3855  
3856          global $wp_scripts;
3857  
3858          if ( ! empty( $wp_scripts ) ) {
3859              $wp_scripts->done = array();
3860          }
3861  
3862          ob_start();
3863          wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
3864          $scripts = ob_get_clean();
3865  
3866          $parsed = $styles . $html . $scripts;
3867      }
3868  
3869      if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
3870          preg_match( '%<link [^>]*href="http://%', $parsed ) ) ) ) {
3871          // Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
3872          wp_send_json_error(
3873              array(
3874                  'type'    => 'not-ssl',
3875                  'message' => __( 'This preview is unavailable in the editor.' ),
3876              )
3877          );
3878      }
3879  
3880      $return = array(
3881          'body' => $parsed,
3882          'attr' => $wp_embed->last_attr,
3883      );
3884  
3885      if ( str_contains( $parsed, 'class="wp-embedded-content' ) ) {
3886          if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
3887              $script_src = includes_url( 'js/wp-embed.js' );
3888          } else {
3889              $script_src = includes_url( 'js/wp-embed.min.js' );
3890          }
3891  
3892          $return['head']    = '<script src="' . $script_src . '"></script>';
3893          $return['sandbox'] = true;
3894      }
3895  
3896      wp_send_json_success( $return );
3897  }
3898  
3899  /**
3900   * @since 4.0.0
3901   *
3902   * @global WP_Post    $post       Global post object.
3903   * @global WP_Scripts $wp_scripts Script dependencies object.
3904   */
3905  function wp_ajax_parse_media_shortcode() {
3906      global $post, $wp_scripts;
3907  
3908      if ( empty( $_POST['shortcode'] ) ) {
3909          wp_send_json_error();
3910      }
3911  
3912      $shortcode = wp_unslash( $_POST['shortcode'] );
3913  
3914      // Only process previews for media related shortcodes:
3915      $found_shortcodes = get_shortcode_tags_in_content( $shortcode );
3916      $media_shortcodes = array(
3917          'audio',
3918          'embed',
3919          'playlist',
3920          'video',
3921          'gallery',
3922      );
3923  
3924      $other_shortcodes = array_diff( $found_shortcodes, $media_shortcodes );
3925  
3926      if ( ! empty( $other_shortcodes ) ) {
3927          wp_send_json_error();
3928      }
3929  
3930      if ( ! empty( $_POST['post_ID'] ) ) {
3931          $post = get_post( (int) $_POST['post_ID'] );
3932      }
3933  
3934      // The embed shortcode requires a post.
3935      if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3936          if ( in_array( 'embed', $found_shortcodes, true ) ) {
3937              wp_send_json_error();
3938          }
3939      } else {
3940          setup_postdata( $post );
3941      }
3942  
3943      $parsed = do_shortcode( $shortcode );
3944  
3945      if ( empty( $parsed ) ) {
3946          wp_send_json_error(
3947              array(
3948                  'type'    => 'no-items',
3949                  'message' => __( 'No items found.' ),
3950              )
3951          );
3952      }
3953  
3954      $head   = '';
3955      $styles = wpview_media_sandbox_styles();
3956  
3957      foreach ( $styles as $style ) {
3958          $head .= '<link rel="stylesheet" href="' . $style . '">';
3959      }
3960  
3961      if ( ! empty( $wp_scripts ) ) {
3962          $wp_scripts->done = array();
3963      }
3964  
3965      ob_start();
3966  
3967      echo $parsed;
3968  
3969      if ( 'playlist' === $_REQUEST['type'] ) {
3970          wp_underscore_playlist_templates();
3971  
3972          wp_print_scripts( 'wp-playlist' );
3973      } else {
3974          wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
3975      }
3976  
3977      wp_send_json_success(
3978          array(
3979              'head' => $head,
3980              'body' => ob_get_clean(),
3981          )
3982      );
3983  }
3984  
3985  /**
3986   * Handles destroying multiple open sessions for a user via AJAX.
3987   *
3988   * @since 4.1.0
3989   */
3990  function wp_ajax_destroy_sessions() {
3991      $user = get_userdata( (int) $_POST['user_id'] );
3992  
3993      if ( $user ) {
3994          if ( ! current_user_can( 'edit_user', $user->ID ) ) {
3995              $user = false;
3996          } elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
3997              $user = false;
3998          }
3999      }
4000  
4001      if ( ! $user ) {
4002          wp_send_json_error(
4003              array(
4004                  'message' => __( 'Could not log out user sessions. Please try again.' ),
4005              )
4006          );
4007      }
4008  
4009      $sessions = WP_Session_Tokens::get_instance( $user->ID );
4010  
4011      if ( get_current_user_id() === $user->ID ) {
4012          $sessions->destroy_others( wp_get_session_token() );
4013          $message = __( 'You are now logged out everywhere else.' );
4014      } else {
4015          $sessions->destroy_all();
4016          /* translators: %s: User's display name. */
4017          $message = sprintf( __( '%s has been logged out.' ), $user->display_name );
4018      }
4019  
4020      wp_send_json_success( array( 'message' => $message ) );
4021  }
4022  
4023  /**
4024   * Handles cropping an image via AJAX.
4025   *
4026   * @since 4.3.0
4027   */
4028  function wp_ajax_crop_image() {
4029      $attachment_id = absint( $_POST['id'] );
4030  
4031      check_ajax_referer( 'image_editor-' . $attachment_id, 'nonce' );
4032  
4033      if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
4034          wp_send_json_error();
4035      }
4036  
4037      $context = str_replace( '_', '-', $_POST['context'] );
4038      $data    = array_map( 'absint', $_POST['cropDetails'] );
4039      $cropped = wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
4040  
4041      if ( ! $cropped || is_wp_error( $cropped ) ) {
4042          wp_send_json_error( array( 'message' => __( 'Image could not be processed.' ) ) );
4043      }
4044  
4045      switch ( $context ) {
4046          case 'site-icon':
4047              require_once  ABSPATH . 'wp-admin/includes/class-wp-site-icon.php';
4048              $wp_site_icon = new WP_Site_Icon();
4049  
4050              // Skip creating a new attachment if the attachment is a Site Icon.
4051              if ( get_post_meta( $attachment_id, '_wp_attachment_context', true ) === $context ) {
4052  
4053                  // Delete the temporary cropped file, we don't need it.
4054                  wp_delete_file( $cropped );
4055  
4056                  // Additional sizes in wp_prepare_attachment_for_js().
4057                  add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
4058                  break;
4059              }
4060  
4061              /** This filter is documented in wp-admin/includes/class-custom-image-header.php */
4062              $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
4063  
4064              // Copy attachment properties.
4065              $attachment = wp_copy_parent_attachment_properties( $cropped, $attachment_id, $context );
4066  
4067              // Update the attachment.
4068              add_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
4069              $attachment_id = $wp_site_icon->insert_attachment( $attachment, $cropped );
4070              remove_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
4071  
4072              // Additional sizes in wp_prepare_attachment_for_js().
4073              add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
4074              break;
4075  
4076          default:
4077              /**
4078               * Fires before a cropped image is saved.
4079               *
4080               * Allows to add filters to modify the way a cropped image is saved.
4081               *
4082               * @since 4.3.0
4083               *
4084               * @param string $context       The Customizer control requesting the cropped image.
4085               * @param int    $attachment_id The attachment ID of the original image.
4086               * @param string $cropped       Path to the cropped image file.
4087               */
4088              do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
4089  
4090              /** This filter is documented in wp-admin/includes/class-custom-image-header.php */
4091              $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
4092  
4093              // Copy attachment properties.
4094              $attachment = wp_copy_parent_attachment_properties( $cropped, $attachment_id, $context );
4095  
4096              $attachment_id = wp_insert_attachment( $attachment, $cropped );
4097              $metadata      = wp_generate_attachment_metadata( $attachment_id, $cropped );
4098  
4099              /**
4100               * Filters the cropped image attachment metadata.
4101               *
4102               * @since 4.3.0
4103               *
4104               * @see wp_generate_attachment_metadata()
4105               *
4106               * @param array $metadata Attachment metadata.
4107               */
4108              $metadata = apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
4109              wp_update_attachment_metadata( $attachment_id, $metadata );
4110  
4111              /**
4112               * Filters the attachment ID for a cropped image.
4113               *
4114               * @since 4.3.0
4115               *
4116               * @param int    $attachment_id The attachment ID of the cropped image.
4117               * @param string $context       The Customizer control requesting the cropped image.
4118               */
4119              $attachment_id = apply_filters( 'wp_ajax_cropped_attachment_id', $attachment_id, $context );
4120      }
4121  
4122      wp_send_json_success( wp_prepare_attachment_for_js( $attachment_id ) );
4123  }
4124  
4125  /**
4126   * Handles generating a password via AJAX.
4127   *
4128   * @since 4.4.0
4129   */
4130  function wp_ajax_generate_password() {
4131      wp_send_json_success( wp_generate_password( 24 ) );
4132  }
4133  
4134  /**
4135   * Handles generating a password in the no-privilege context via AJAX.
4136   *
4137   * @since 5.7.0
4138   */
4139  function wp_ajax_nopriv_generate_password() {
4140      wp_send_json_success( wp_generate_password( 24 ) );
4141  }
4142  
4143  /**
4144   * Handles saving the user's WordPress.org username via AJAX.
4145   *
4146   * @since 4.4.0
4147   */
4148  function wp_ajax_save_wporg_username() {
4149      if ( ! current_user_can( 'install_themes' ) && ! current_user_can( 'install_plugins' ) ) {
4150          wp_send_json_error();
4151      }
4152  
4153      check_ajax_referer( 'save_wporg_username_' . get_current_user_id() );
4154  
4155      $username = isset( $_REQUEST['username'] ) ? wp_unslash( $_REQUEST['username'] ) : false;
4156  
4157      if ( ! $username ) {
4158          wp_send_json_error();
4159      }
4160  
4161      wp_send_json_success( update_user_meta( get_current_user_id(), 'wporg_favorites', $username ) );
4162  }
4163  
4164  /**
4165   * Handles installing a theme via AJAX.
4166   *
4167   * @since 4.6.0
4168   *
4169   * @see Theme_Upgrader
4170   *
4171   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4172   */
4173  function wp_ajax_install_theme() {
4174      check_ajax_referer( 'updates' );
4175  
4176      if ( empty( $_POST['slug'] ) ) {
4177          wp_send_json_error(
4178              array(
4179                  'slug'         => '',
4180                  'errorCode'    => 'no_theme_specified',
4181                  'errorMessage' => __( 'No theme specified.' ),
4182              )
4183          );
4184      }
4185  
4186      $slug = sanitize_key( wp_unslash( $_POST['slug'] ) );
4187  
4188      $status = array(
4189          'install' => 'theme',
4190          'slug'    => $slug,
4191      );
4192  
4193      if ( ! current_user_can( 'install_themes' ) ) {
4194          $status['errorMessage'] = __( 'Sorry, you are not allowed to install themes on this site.' );
4195          wp_send_json_error( $status );
4196      }
4197  
4198      require_once  ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4199      require_once  ABSPATH . 'wp-admin/includes/theme.php';
4200  
4201      $api = themes_api(
4202          'theme_information',
4203          array(
4204              'slug'   => $slug,
4205              'fields' => array( 'sections' => false ),
4206          )
4207      );
4208  
4209      if ( is_wp_error( $api ) ) {
4210          $status['errorMessage'] = $api->get_error_message();
4211          wp_send_json_error( $status );
4212      }
4213  
4214      $skin     = new WP_Ajax_Upgrader_Skin();
4215      $upgrader = new Theme_Upgrader( $skin );
4216      $result   = $upgrader->install( $api->download_link );
4217  
4218      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4219          $status['debug'] = $skin->get_upgrade_messages();
4220      }
4221  
4222      if ( is_wp_error( $result ) ) {
4223          $status['errorCode']    = $result->get_error_code();
4224          $status['errorMessage'] = $result->get_error_message();
4225          wp_send_json_error( $status );
4226      } elseif ( is_wp_error( $skin->result ) ) {
4227          $status['errorCode']    = $skin->result->get_error_code();
4228          $status['errorMessage'] = $skin->result->get_error_message();
4229          wp_send_json_error( $status );
4230      } elseif ( $skin->get_errors()->has_errors() ) {
4231          $status['errorMessage'] = $skin->get_error_messages();
4232          wp_send_json_error( $status );
4233      } elseif ( is_null( $result ) ) {
4234          global $wp_filesystem;
4235  
4236          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4237          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4238  
4239          // Pass through the error from WP_Filesystem if one was raised.
4240          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4241              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4242          }
4243  
4244          wp_send_json_error( $status );
4245      }
4246  
4247      $status['themeName'] = wp_get_theme( $slug )->get( 'Name' );
4248  
4249      if ( current_user_can( 'switch_themes' ) ) {
4250          if ( is_multisite() ) {
4251              $status['activateUrl'] = add_query_arg(
4252                  array(
4253                      'action'   => 'enable',
4254                      '_wpnonce' => wp_create_nonce( 'enable-theme_' . $slug ),
4255                      'theme'    => $slug,
4256                  ),
4257                  network_admin_url( 'themes.php' )
4258              );
4259          } else {
4260              $status['activateUrl'] = add_query_arg(
4261                  array(
4262                      'action'     => 'activate',
4263                      '_wpnonce'   => wp_create_nonce( 'switch-theme_' . $slug ),
4264                      'stylesheet' => $slug,
4265                  ),
4266                  admin_url( 'themes.php' )
4267              );
4268          }
4269      }
4270  
4271      $theme                = wp_get_theme( $slug );
4272      $status['blockTheme'] = $theme->is_block_theme();
4273  
4274      if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
4275          $status['customizeUrl'] = add_query_arg(
4276              array(
4277                  'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
4278              ),
4279              wp_customize_url( $slug )
4280          );
4281      }
4282  
4283      /*
4284       * See WP_Theme_Install_List_Table::_get_theme_status() if we wanted to check
4285       * on post-installation status.
4286       */
4287      wp_send_json_success( $status );
4288  }
4289  
4290  /**
4291   * Handles updating a theme via AJAX.
4292   *
4293   * @since 4.6.0
4294   *
4295   * @see Theme_Upgrader
4296   *
4297   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4298   */
4299  function wp_ajax_update_theme() {
4300      check_ajax_referer( 'updates' );
4301  
4302      if ( empty( $_POST['slug'] ) ) {
4303          wp_send_json_error(
4304              array(
4305                  'slug'         => '',
4306                  'errorCode'    => 'no_theme_specified',
4307                  'errorMessage' => __( 'No theme specified.' ),
4308              )
4309          );
4310      }
4311  
4312      $stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
4313      $status     = array(
4314          'update'     => 'theme',
4315          'slug'       => $stylesheet,
4316          'oldVersion' => '',
4317          'newVersion' => '',
4318      );
4319  
4320      if ( ! current_user_can( 'update_themes' ) ) {
4321          $status['errorMessage'] = __( 'Sorry, you are not allowed to update themes for this site.' );
4322          wp_send_json_error( $status );
4323      }
4324  
4325      $theme = wp_get_theme( $stylesheet );
4326      if ( $theme->exists() ) {
4327          $status['oldVersion'] = $theme->get( 'Version' );
4328      }
4329  
4330      require_once  ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4331  
4332      $current = get_site_transient( 'update_themes' );
4333      if ( empty( $current ) ) {
4334          wp_update_themes();
4335      }
4336  
4337      $skin     = new WP_Ajax_Upgrader_Skin();
4338      $upgrader = new Theme_Upgrader( $skin );
4339      $result   = $upgrader->bulk_upgrade( array( $stylesheet ) );
4340  
4341      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4342          $status['debug'] = $skin->get_upgrade_messages();
4343      }
4344  
4345      if ( is_wp_error( $skin->result ) ) {
4346          $status['errorCode']    = $skin->result->get_error_code();
4347          $status['errorMessage'] = $skin->result->get_error_message();
4348          wp_send_json_error( $status );
4349      } elseif ( $skin->get_errors()->has_errors() ) {
4350          $status['errorMessage'] = $skin->get_error_messages();
4351          wp_send_json_error( $status );
4352      } elseif ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) {
4353  
4354          // Theme is already at the latest version.
4355          if ( true === $result[ $stylesheet ] ) {
4356              $status['errorMessage'] = $upgrader->strings['up_to_date'];
4357              wp_send_json_error( $status );
4358          }
4359  
4360          $theme = wp_get_theme( $stylesheet );
4361          if ( $theme->exists() ) {
4362              $status['newVersion'] = $theme->get( 'Version' );
4363          }
4364  
4365          wp_send_json_success( $status );
4366      } elseif ( false === $result ) {
4367          global $wp_filesystem;
4368  
4369          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4370          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4371  
4372          // Pass through the error from WP_Filesystem if one was raised.
4373          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4374              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4375          }
4376  
4377          wp_send_json_error( $status );
4378      }
4379  
4380      // An unhandled error occurred.
4381      $status['errorMessage'] = __( 'Theme update failed.' );
4382      wp_send_json_error( $status );
4383  }
4384  
4385  /**
4386   * Handles deleting a theme via AJAX.
4387   *
4388   * @since 4.6.0
4389   *
4390   * @see delete_theme()
4391   *
4392   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4393   */
4394  function wp_ajax_delete_theme() {
4395      check_ajax_referer( 'updates' );
4396  
4397      if ( empty( $_POST['slug'] ) ) {
4398          wp_send_json_error(
4399              array(
4400                  'slug'         => '',
4401                  'errorCode'    => 'no_theme_specified',
4402                  'errorMessage' => __( 'No theme specified.' ),
4403              )
4404          );
4405      }
4406  
4407      $stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
4408      $status     = array(
4409          'delete' => 'theme',
4410          'slug'   => $stylesheet,
4411      );
4412  
4413      if ( ! current_user_can( 'delete_themes' ) ) {
4414          $status['errorMessage'] = __( 'Sorry, you are not allowed to delete themes on this site.' );
4415          wp_send_json_error( $status );
4416      }
4417  
4418      if ( ! wp_get_theme( $stylesheet )->exists() ) {
4419          $status['errorMessage'] = __( 'The requested theme does not exist.' );
4420          wp_send_json_error( $status );
4421      }
4422  
4423      // Check filesystem credentials. `delete_theme()` will bail otherwise.
4424      $url = wp_nonce_url( 'themes.php?action=delete&stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet );
4425  
4426      ob_start();
4427      $credentials = request_filesystem_credentials( $url );
4428      ob_end_clean();
4429  
4430      if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
4431          global $wp_filesystem;
4432  
4433          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4434          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4435  
4436          // Pass through the error from WP_Filesystem if one was raised.
4437          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4438              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4439          }
4440  
4441          wp_send_json_error( $status );
4442      }
4443  
4444      require_once  ABSPATH . 'wp-admin/includes/theme.php';
4445  
4446      $result = delete_theme( $stylesheet );
4447  
4448      if ( is_wp_error( $result ) ) {
4449          $status['errorMessage'] = $result->get_error_message();
4450          wp_send_json_error( $status );
4451      } elseif ( false === $result ) {
4452          $status['errorMessage'] = __( 'Theme could not be deleted.' );
4453          wp_send_json_error( $status );
4454      }
4455  
4456      wp_send_json_success( $status );
4457  }
4458  
4459  /**
4460   * Handles installing a plugin via AJAX.
4461   *
4462   * @since 4.6.0
4463   *
4464   * @see Plugin_Upgrader
4465   *
4466   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4467   */
4468  function wp_ajax_install_plugin() {
4469      check_ajax_referer( 'updates' );
4470  
4471      if ( empty( $_POST['slug'] ) ) {
4472          wp_send_json_error(
4473              array(
4474                  'slug'         => '',
4475                  'errorCode'    => 'no_plugin_specified',
4476                  'errorMessage' => __( 'No plugin specified.' ),
4477              )
4478          );
4479      }
4480  
4481      $status = array(
4482          'install' => 'plugin',
4483          'slug'    => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4484      );
4485  
4486      if ( ! current_user_can( 'install_plugins' ) ) {
4487          $status['errorMessage'] = __( 'Sorry, you are not allowed to install plugins on this site.' );
4488          wp_send_json_error( $status );
4489      }
4490  
4491      require_once  ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4492      require_once  ABSPATH . 'wp-admin/includes/plugin-install.php';
4493  
4494      $api = plugins_api(
4495          'plugin_information',
4496          array(
4497              'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4498              'fields' => array(
4499                  'sections' => false,
4500              ),
4501          )
4502      );
4503  
4504      if ( is_wp_error( $api ) ) {
4505          $status['errorMessage'] = $api->get_error_message();
4506          wp_send_json_error( $status );
4507      }
4508  
4509      $status['pluginName'] = $api->name;
4510  
4511      $skin     = new WP_Ajax_Upgrader_Skin();
4512      $upgrader = new Plugin_Upgrader( $skin );
4513      $result   = $upgrader->install( $api->download_link );
4514  
4515      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4516          $status['debug'] = $skin->get_upgrade_messages();
4517      }
4518  
4519      if ( is_wp_error( $result ) ) {
4520          $status['errorCode']    = $result->get_error_code();
4521          $status['errorMessage'] = $result->get_error_message();
4522          wp_send_json_error( $status );
4523      } elseif ( is_wp_error( $skin->result ) ) {
4524          $status['errorCode']    = $skin->result->get_error_code();
4525          $status['errorMessage'] = $skin->result->get_error_message();
4526          wp_send_json_error( $status );
4527      } elseif ( $skin->get_errors()->has_errors() ) {
4528          $status['errorMessage'] = $skin->get_error_messages();
4529          wp_send_json_error( $status );
4530      } elseif ( is_null( $result ) ) {
4531          global $wp_filesystem;
4532  
4533          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4534          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4535  
4536          // Pass through the error from WP_Filesystem if one was raised.
4537          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4538              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4539          }
4540  
4541          wp_send_json_error( $status );
4542      }
4543  
4544      $install_status = install_plugin_install_status( $api );
4545      $pagenow        = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
4546  
4547      // If installation request is coming from import page, do not return network activation link.
4548      $plugins_url = ( 'import' === $pagenow ) ? admin_url( 'plugins.php' ) : network_admin_url( 'plugins.php' );
4549  
4550      if ( current_user_can( 'activate_plugin', $install_status['file'] ) && is_plugin_inactive( $install_status['file'] ) ) {
4551          $status['activateUrl'] = add_query_arg(
4552              array(
4553                  '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ),
4554                  'action'   => 'activate',
4555                  'plugin'   => $install_status['file'],
4556              ),
4557              $plugins_url
4558          );
4559      }
4560  
4561      if ( is_multisite() && current_user_can( 'manage_network_plugins' ) && 'import' !== $pagenow ) {
4562          $status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] );
4563      }
4564  
4565      wp_send_json_success( $status );
4566  }
4567  
4568  /**
4569   * Handles activating a plugin via AJAX.
4570   *
4571   * @since 6.5.0
4572   */
4573  function wp_ajax_activate_plugin() {
4574      check_ajax_referer( 'updates' );
4575  
4576      if ( empty( $_POST['name'] ) || empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
4577          wp_send_json_error(
4578              array(
4579                  'slug'         => '',
4580                  'pluginName'   => '',
4581                  'plugin'       => '',
4582                  'errorCode'    => 'no_plugin_specified',
4583                  'errorMessage' => __( 'No plugin specified.' ),
4584              )
4585          );
4586      }
4587  
4588      $status = array(
4589          'activate'   => 'plugin',
4590          'slug'       => wp_unslash( $_POST['slug'] ),
4591          'pluginName' => wp_unslash( $_POST['name'] ),
4592          'plugin'     => wp_unslash( $_POST['plugin'] ),
4593      );
4594  
4595      if ( ! current_user_can( 'activate_plugin', $status['plugin'] ) ) {
4596          $status['errorMessage'] = __( 'Sorry, you are not allowed to activate plugins on this site.' );
4597          wp_send_json_error( $status );
4598      }
4599  
4600      if ( is_plugin_active( $status['plugin'] ) ) {
4601          $status['errorMessage'] = sprintf(
4602              /* translators: %s: Plugin name. */
4603              __( '%s is already active.' ),
4604              $status['pluginName']
4605          );
4606      }
4607  
4608      $activated = activate_plugin( $status['plugin'] );
4609  
4610      if ( is_wp_error( $activated ) ) {
4611          $status['errorMessage'] = $activated->get_error_message();
4612          wp_send_json_error( $status );
4613      }
4614  
4615      wp_send_json_success( $status );
4616  }
4617  
4618  /**
4619   * Handles updating a plugin via AJAX.
4620   *
4621   * @since 4.2.0
4622   *
4623   * @see Plugin_Upgrader
4624   *
4625   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4626   */
4627  function wp_ajax_update_plugin() {
4628      check_ajax_referer( 'updates' );
4629  
4630      if ( empty( $_POST['plugin'] ) || empty( $_POST['slug'] ) ) {
4631          wp_send_json_error(
4632              array(
4633                  'slug'         => '',
4634                  'errorCode'    => 'no_plugin_specified',
4635                  'errorMessage' => __( 'No plugin specified.' ),
4636              )
4637          );
4638      }
4639  
4640      $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
4641  
4642      $status = array(
4643          'update'     => 'plugin',
4644          'slug'       => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4645          'oldVersion' => '',
4646          'newVersion' => '',
4647      );
4648  
4649      if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) {
4650          $status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
4651          wp_send_json_error( $status );
4652      }
4653  
4654      $plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4655      $status['plugin']     = $plugin;
4656      $status['pluginName'] = $plugin_data['Name'];
4657  
4658      if ( $plugin_data['Version'] ) {
4659          /* translators: %s: Plugin version. */
4660          $status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
4661      }
4662  
4663      require_once  ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4664  
4665      wp_update_plugins();
4666  
4667      $skin     = new WP_Ajax_Upgrader_Skin();
4668      $upgrader = new Plugin_Upgrader( $skin );
4669      $result   = $upgrader->bulk_upgrade( array( $plugin ) );
4670  
4671      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4672          $status['debug'] = $skin->get_upgrade_messages();
4673      }
4674  
4675      if ( is_wp_error( $skin->result ) ) {
4676          $status['errorCode']    = $skin->result->get_error_code();
4677          $status['errorMessage'] = $skin->result->get_error_message();
4678          wp_send_json_error( $status );
4679      } elseif ( $skin->get_errors()->has_errors() ) {
4680          $status['errorMessage'] = $skin->get_error_messages();
4681          wp_send_json_error( $status );
4682      } elseif ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) {
4683  
4684          /*
4685           * Plugin is already at the latest version.
4686           *
4687           * This may also be the return value if the `update_plugins` site transient is empty,
4688           * e.g. when you update two plugins in quick succession before the transient repopulates.
4689           *
4690           * Preferably something can be done to ensure `update_plugins` isn't empty.
4691           * For now, surface some sort of error here.
4692           */
4693          if ( true === $result[ $plugin ] ) {
4694              $status['errorMessage'] = $upgrader->strings['up_to_date'];
4695              wp_send_json_error( $status );
4696          }
4697  
4698          $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
4699          $plugin_data = reset( $plugin_data );
4700  
4701          if ( $plugin_data['Version'] ) {
4702              /* translators: %s: Plugin version. */
4703              $status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
4704          }
4705  
4706          wp_send_json_success( $status );
4707      } elseif ( false === $result ) {
4708          global $wp_filesystem;
4709  
4710          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4711          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4712  
4713          // Pass through the error from WP_Filesystem if one was raised.
4714          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4715              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4716          }
4717  
4718          wp_send_json_error( $status );
4719      }
4720  
4721      // An unhandled error occurred.
4722      $status['errorMessage'] = __( 'Plugin update failed.' );
4723      wp_send_json_error( $status );
4724  }
4725  
4726  /**
4727   * Handles deleting a plugin via AJAX.
4728   *
4729   * @since 4.6.0
4730   *
4731   * @see delete_plugins()
4732   *
4733   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4734   */
4735  function wp_ajax_delete_plugin() {
4736      check_ajax_referer( 'updates' );
4737  
4738      if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
4739          wp_send_json_error(
4740              array(
4741                  'slug'         => '',
4742                  'errorCode'    => 'no_plugin_specified',
4743                  'errorMessage' => __( 'No plugin specified.' ),
4744              )
4745          );
4746      }
4747  
4748      $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
4749  
4750      $status = array(
4751          'delete' => 'plugin',
4752          'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4753      );
4754  
4755      if ( ! current_user_can( 'delete_plugins' ) || 0 !== validate_file( $plugin ) ) {
4756          $status['errorMessage'] = __( 'Sorry, you are not allowed to delete plugins for this site.' );
4757          wp_send_json_error( $status );
4758      }
4759  
4760      $plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4761      $status['plugin']     = $plugin;
4762      $status['pluginName'] = $plugin_data['Name'];
4763  
4764      if ( is_plugin_active( $plugin ) ) {
4765          $status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' );
4766          wp_send_json_error( $status );
4767      }
4768  
4769      // Check filesystem credentials. `delete_plugins()` will bail otherwise.
4770      $url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin, 'bulk-plugins' );
4771  
4772      ob_start();
4773      $credentials = request_filesystem_credentials( $url );
4774      ob_end_clean();
4775  
4776      if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
4777          global $wp_filesystem;
4778  
4779          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4780          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4781  
4782          // Pass through the error from WP_Filesystem if one was raised.
4783          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4784              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4785          }
4786  
4787          wp_send_json_error( $status );
4788      }
4789  
4790      $result = delete_plugins( array( $plugin ) );
4791  
4792      if ( is_wp_error( $result ) ) {
4793          $status['errorMessage'] = $result->get_error_message();
4794          wp_send_json_error( $status );
4795      } elseif ( false === $result ) {
4796          $status['errorMessage'] = __( 'Plugin could not be deleted.' );
4797          wp_send_json_error( $status );
4798      }
4799  
4800      wp_send_json_success( $status );
4801  }
4802  
4803  /**
4804   * Handles searching plugins via AJAX.
4805   *
4806   * @since 4.6.0
4807   *
4808   * @global string $s Search term.
4809   */
4810  function wp_ajax_search_plugins() {
4811      check_ajax_referer( 'updates' );
4812  
4813      // Ensure after_plugin_row_{$plugin_file} gets hooked.
4814      wp_plugin_update_rows();
4815  
4816      WP_Plugin_Dependencies::initialize();
4817  
4818      $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
4819      if ( 'plugins-network' === $pagenow || 'plugins' === $pagenow ) {
4820          set_current_screen( $pagenow );
4821      }
4822  
4823      /** @var WP_Plugins_List_Table $wp_list_table */
4824      $wp_list_table = _get_list_table(
4825          'WP_Plugins_List_Table',
4826          array(
4827              'screen' => get_current_screen(),
4828          )
4829      );
4830  
4831      $status = array();
4832  
4833      if ( ! $wp_list_table->ajax_user_can() ) {
4834          $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
4835          wp_send_json_error( $status );
4836      }
4837  
4838      // Set the correct requester, so pagination works.
4839      $_SERVER['REQUEST_URI'] = add_query_arg(
4840          array_diff_key(
4841              $_POST,
4842              array(
4843                  '_ajax_nonce' => null,
4844                  'action'      => null,
4845              )
4846          ),
4847          network_admin_url( 'plugins.php', 'relative' )
4848      );
4849  
4850      $GLOBALS['s'] = wp_unslash( $_POST['s'] );
4851  
4852      $wp_list_table->prepare_items();
4853  
4854      ob_start();
4855      $wp_list_table->display();
4856      $status['count'] = count( $wp_list_table->items );
4857      $status['items'] = ob_get_clean();
4858  
4859      wp_send_json_success( $status );
4860  }
4861  
4862  /**
4863   * Handles searching plugins to install via AJAX.
4864   *
4865   * @since 4.6.0
4866   */
4867  function wp_ajax_search_install_plugins() {
4868      check_ajax_referer( 'updates' );
4869  
4870      $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
4871      if ( 'plugin-install-network' === $pagenow || 'plugin-install' === $pagenow ) {
4872          set_current_screen( $pagenow );
4873      }
4874  
4875      /** @var WP_Plugin_Install_List_Table $wp_list_table */
4876      $wp_list_table = _get_list_table(
4877          'WP_Plugin_Install_List_Table',
4878          array(
4879              'screen' => get_current_screen(),
4880          )
4881      );
4882  
4883      $status = array();
4884  
4885      if ( ! $wp_list_table->ajax_user_can() ) {
4886          $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
4887          wp_send_json_error( $status );
4888      }
4889  
4890      // Set the correct requester, so pagination works.
4891      $_SERVER['REQUEST_URI'] = add_query_arg(
4892          array_diff_key(
4893              $_POST,
4894              array(
4895                  '_ajax_nonce' => null,
4896                  'action'      => null,
4897              )
4898          ),
4899          network_admin_url( 'plugin-install.php', 'relative' )
4900      );
4901  
4902      $wp_list_table->prepare_items();
4903  
4904      ob_start();
4905      $wp_list_table->display();
4906      $status['count'] = (int) $wp_list_table->get_pagination_arg( 'total_items' );
4907      $status['items'] = ob_get_clean();
4908  
4909      wp_send_json_success( $status );
4910  }
4911  
4912  /**
4913   * Handles editing a theme or plugin file via AJAX.
4914   *
4915   * @since 4.9.0
4916   *
4917   * @see wp_edit_theme_plugin_file()
4918   */
4919  function wp_ajax_edit_theme_plugin_file() {
4920      $edit_result = wp_edit_theme_plugin_file( wp_unslash( $_POST ) ); // Validation of args is done in wp_edit_theme_plugin_file().
4921  
4922      if ( is_wp_error( $edit_result ) ) {
4923          wp_send_json_error(
4924              array_merge(
4925                  array(
4926                      'code'    => $edit_result->get_error_code(),
4927                      'message' => $edit_result->get_error_message(),
4928                  ),
4929                  (array) $edit_result->get_error_data()
4930              )
4931          );
4932      } else {
4933          wp_send_json_success(
4934              array(
4935                  'message' => __( 'File edited successfully.' ),
4936              )
4937          );
4938      }
4939  }
4940  
4941  /**
4942   * Handles exporting a user's personal data via AJAX.
4943   *
4944   * @since 4.9.6
4945   */
4946  function wp_ajax_wp_privacy_export_personal_data() {
4947  
4948      if ( empty( $_POST['id'] ) ) {
4949          wp_send_json_error( __( 'Missing request ID.' ) );
4950      }
4951  
4952      $request_id = (int) $_POST['id'];
4953  
4954      if ( $request_id < 1 ) {
4955          wp_send_json_error( __( 'Invalid request ID.' ) );
4956      }
4957  
4958      if ( ! current_user_can( 'export_others_personal_data' ) ) {
4959          wp_send_json_error( __( 'Sorry, you are not allowed to perform this action.' ) );
4960      }
4961  
4962      check_ajax_referer( 'wp-privacy-export-personal-data-' . $request_id, 'security' );
4963  
4964      // Get the request.
4965      $request = wp_get_user_request( $request_id );
4966  
4967      if ( ! $request || 'export_personal_data' !== $request->action_name ) {
4968          wp_send_json_error( __( 'Invalid request type.' ) );
4969      }
4970  
4971      $email_address = $request->email;
4972      if ( ! is_email( $email_address ) ) {
4973          wp_send_json_error( __( 'A valid email address must be given.' ) );
4974      }
4975  
4976      if ( ! isset( $_POST['exporter'] ) ) {
4977          wp_send_json_error( __( 'Missing exporter index.' ) );
4978      }
4979  
4980      $exporter_index = (int) $_POST['exporter'];
4981  
4982      if ( ! isset( $_POST['page'] ) ) {
4983          wp_send_json_error( __( 'Missing page index.' ) );
4984      }
4985  
4986      $page = (int) $_POST['page'];
4987  
4988      $send_as_email = isset( $_POST['sendAsEmail'] ) ? ( 'true' === $_POST['sendAsEmail'] ) : false;
4989  
4990      /**
4991       * Filters the array of exporter callbacks.
4992       *
4993       * @since 4.9.6
4994       *
4995       * @param array $args {
4996       *     An array of callable exporters of personal data. Default empty array.
4997       *
4998       *     @type array ...$0 {
4999       *         Array of personal data exporters.
5000       *
5001       *         @type callable $callback               Callable exporter function that accepts an
5002       *                                                email address and a page number and returns an
5003       *                                                array of name => value pairs of personal data.
5004       *         @type string   $exporter_friendly_name Translated user facing friendly name for the
5005       *                                                exporter.
5006       *     }
5007       * }
5008       */
5009      $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
5010  
5011      if ( ! is_array( $exporters ) ) {
5012          wp_send_json_error( __( 'An exporter has improperly used the registration filter.' ) );
5013      }
5014  
5015      // Do we have any registered exporters?
5016      if ( 0 < count( $exporters ) ) {
5017          if ( $exporter_index < 1 ) {
5018              wp_send_json_error( __( 'Exporter index cannot be negative.' ) );
5019          }
5020  
5021          if ( $exporter_index > count( $exporters ) ) {
5022              wp_send_json_error( __( 'Exporter index is out of range.' ) );
5023          }
5024  
5025          if ( $page < 1 ) {
5026              wp_send_json_error( __( 'Page index cannot be less than one.' ) );
5027          }
5028  
5029          $exporter_keys = array_keys( $exporters );
5030          $exporter_key  = $exporter_keys[ $exporter_index - 1 ];
5031          $exporter      = $exporters[ $exporter_key ];
5032  
5033          if ( ! is_array( $exporter ) ) {
5034              wp_send_json_error(
5035                  /* translators: %s: Exporter array index. */
5036                  sprintf( __( 'Expected an array describing the exporter at index %s.' ), $exporter_key )
5037              );
5038          }
5039  
5040          if ( ! array_key_exists( 'exporter_friendly_name', $exporter ) ) {
5041              wp_send_json_error(
5042                  /* translators: %s: Exporter array index. */
5043                  sprintf( __( 'Exporter array at index %s does not include a friendly name.' ), $exporter_key )
5044              );
5045          }
5046  
5047          $exporter_friendly_name = $exporter['exporter_friendly_name'];
5048  
5049          if ( ! array_key_exists( 'callback', $exporter ) ) {
5050              wp_send_json_error(
5051                  /* translators: %s: Exporter friendly name. */
5052                  sprintf( __( 'Exporter does not include a callback: %s.' ), esc_html( $exporter_friendly_name ) )
5053              );
5054          }
5055  
5056          if ( ! is_callable( $exporter['callback'] ) ) {
5057              wp_send_json_error(
5058                  /* translators: %s: Exporter friendly name. */
5059                  sprintf( __( 'Exporter callback is not a valid callback: %s.' ), esc_html( $exporter_friendly_name ) )
5060              );
5061          }
5062  
5063          $callback = $exporter['callback'];
5064          $response = call_user_func( $callback, $email_address, $page );
5065  
5066          if ( is_wp_error( $response ) ) {
5067              wp_send_json_error( $response );
5068          }
5069  
5070          if ( ! is_array( $response ) ) {
5071              wp_send_json_error(
5072                  /* translators: %s: Exporter friendly name. */
5073                  sprintf( __( 'Expected response as an array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
5074              );
5075          }
5076  
5077          if ( ! array_key_exists( 'data', $response ) ) {
5078              wp_send_json_error(
5079                  /* translators: %s: Exporter friendly name. */
5080                  sprintf( __( 'Expected data in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
5081              );
5082          }
5083  
5084          if ( ! is_array( $response['data'] ) ) {
5085              wp_send_json_error(
5086                  /* translators: %s: Exporter friendly name. */
5087                  sprintf( __( 'Expected data array in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
5088              );
5089          }
5090  
5091          if ( ! array_key_exists( 'done', $response ) ) {
5092              wp_send_json_error(
5093                  /* translators: %s: Exporter friendly name. */
5094                  sprintf( __( 'Expected done (boolean) in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
5095              );
5096          }
5097      } else {
5098          // No exporters, so we're done.
5099          $exporter_key = '';
5100  
5101          $response = array(
5102              'data' => array(),
5103              'done' => true,
5104          );
5105      }
5106  
5107      /**
5108       * Filters a page of personal data exporter data. Used to build the export report.
5109       *
5110       * Allows the export response to be consumed by destinations in addition to Ajax.
5111       *
5112       * @since 4.9.6
5113       *
5114       * @param array  $response        The personal data for the given exporter and page number.
5115       * @param int    $exporter_index  The index of the exporter that provided this data.
5116       * @param string $email_address   The email address associated with this personal data.
5117       * @param int    $page            The page number for this response.
5118       * @param int    $request_id      The privacy request post ID associated with this request.
5119       * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
5120       * @param string $exporter_key    The key (slug) of the exporter that provided this data.
5121       */
5122      $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key );
5123  
5124      if ( is_wp_error( $response ) ) {
5125          wp_send_json_error( $response );
5126      }
5127  
5128      wp_send_json_success( $response );
5129  }
5130  
5131  /**
5132   * Handles erasing personal data via AJAX.
5133   *
5134   * @since 4.9.6
5135   */
5136  function wp_ajax_wp_privacy_erase_personal_data() {
5137  
5138      if ( empty( $_POST['id'] ) ) {
5139          wp_send_json_error( __( 'Missing request ID.' ) );
5140      }
5141  
5142      $request_id = (int) $_POST['id'];
5143  
5144      if ( $request_id < 1 ) {
5145          wp_send_json_error( __( 'Invalid request ID.' ) );
5146      }
5147  
5148      // Both capabilities are required to avoid confusion, see `_wp_personal_data_removal_page()`.
5149      if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) {
5150          wp_send_json_error( __( 'Sorry, you are not allowed to perform this action.' ) );
5151      }
5152  
5153      check_ajax_referer( 'wp-privacy-erase-personal-data-' . $request_id, 'security' );
5154  
5155      // Get the request.
5156      $request = wp_get_user_request( $request_id );
5157  
5158      if ( ! $request || 'remove_personal_data' !== $request->action_name ) {
5159          wp_send_json_error( __( 'Invalid request type.' ) );
5160      }
5161  
5162      $email_address = $request->email;
5163  
5164      if ( ! is_email( $email_address ) ) {
5165          wp_send_json_error( __( 'Invalid email address in request.' ) );
5166      }
5167  
5168      if ( ! isset( $_POST['eraser'] ) ) {
5169          wp_send_json_error( __( 'Missing eraser index.' ) );
5170      }
5171  
5172      $eraser_index = (int) $_POST['eraser'];
5173  
5174      if ( ! isset( $_POST['page'] ) ) {
5175          wp_send_json_error( __( 'Missing page index.' ) );
5176      }
5177  
5178      $page = (int) $_POST['page'];
5179  
5180      /**
5181       * Filters the array of personal data eraser callbacks.
5182       *
5183       * @since 4.9.6
5184       *
5185       * @param array $args {
5186       *     An array of callable erasers of personal data. Default empty array.
5187       *
5188       *     @type array ...$0 {
5189       *         Array of personal data exporters.
5190       *
5191       *         @type callable $callback               Callable eraser that accepts an email address and a page
5192       *                                                number, and returns an array with boolean values for
5193       *                                                whether items were removed or retained and any messages
5194       *                                                from the eraser, as well as if additional pages are
5195       *                                                available.
5196       *         @type string   $exporter_friendly_name Translated user facing friendly name for the eraser.
5197       *     }
5198       * }
5199       */
5200      $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() );
5201  
5202      // Do we have any registered erasers?
5203      if ( 0 < count( $erasers ) ) {
5204  
5205          if ( $eraser_index < 1 ) {
5206              wp_send_json_error( __( 'Eraser index cannot be less than one.' ) );
5207          }
5208  
5209          if ( $eraser_index > count( $erasers ) ) {
5210              wp_send_json_error( __( 'Eraser index is out of range.' ) );
5211          }
5212  
5213          if ( $page < 1 ) {
5214              wp_send_json_error( __( 'Page index cannot be less than one.' ) );
5215          }
5216  
5217          $eraser_keys = array_keys( $erasers );
5218          $eraser_key  = $eraser_keys[ $eraser_index - 1 ];
5219          $eraser      = $erasers[ $eraser_key ];
5220  
5221          if ( ! is_array( $eraser ) ) {
5222              /* translators: %d: Eraser array index. */
5223              wp_send_json_error( sprintf( __( 'Expected an array describing the eraser at index %d.' ), $eraser_index ) );
5224          }
5225  
5226          if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) {
5227              /* translators: %d: Eraser array index. */
5228              wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) );
5229          }
5230  
5231          $eraser_friendly_name = $eraser['eraser_friendly_name'];
5232  
5233          if ( ! array_key_exists( 'callback', $eraser ) ) {
5234              wp_send_json_error(
5235                  sprintf(
5236                      /* translators: %s: Eraser friendly name. */
5237                      __( 'Eraser does not include a callback: %s.' ),
5238                      esc_html( $eraser_friendly_name )
5239                  )
5240              );
5241          }
5242  
5243          if ( ! is_callable( $eraser['callback'] ) ) {
5244              wp_send_json_error(
5245                  sprintf(
5246                      /* translators: %s: Eraser friendly name. */
5247                      __( 'Eraser callback is not valid: %s.' ),
5248                      esc_html( $eraser_friendly_name )
5249                  )
5250              );
5251          }
5252  
5253          $callback = $eraser['callback'];
5254          $response = call_user_func( $callback, $email_address, $page );
5255  
5256          if ( is_wp_error( $response ) ) {
5257              wp_send_json_error( $response );
5258          }
5259  
5260          if ( ! is_array( $response ) ) {
5261              wp_send_json_error(
5262                  sprintf(
5263                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5264                      __( 'Did not receive array from %1$s eraser (index %2$d).' ),
5265                      esc_html( $eraser_friendly_name ),
5266                      $eraser_index
5267                  )
5268              );
5269          }
5270  
5271          if ( ! array_key_exists( 'items_removed', $response ) ) {
5272              wp_send_json_error(
5273                  sprintf(
5274                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5275                      __( 'Expected items_removed key in response array from %1$s eraser (index %2$d).' ),
5276                      esc_html( $eraser_friendly_name ),
5277                      $eraser_index
5278                  )
5279              );
5280          }
5281  
5282          if ( ! array_key_exists( 'items_retained', $response ) ) {
5283              wp_send_json_error(
5284                  sprintf(
5285                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5286                      __( 'Expected items_retained key in response array from %1$s eraser (index %2$d).' ),
5287                      esc_html( $eraser_friendly_name ),
5288                      $eraser_index
5289                  )
5290              );
5291          }
5292  
5293          if ( ! array_key_exists( 'messages', $response ) ) {
5294              wp_send_json_error(
5295                  sprintf(
5296                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5297                      __( 'Expected messages key in response array from %1$s eraser (index %2$d).' ),
5298                      esc_html( $eraser_friendly_name ),
5299                      $eraser_index
5300                  )
5301              );
5302          }
5303  
5304          if ( ! is_array( $response['messages'] ) ) {
5305              wp_send_json_error(
5306                  sprintf(
5307                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5308                      __( 'Expected messages key to reference an array in response array from %1$s eraser (index %2$d).' ),
5309                      esc_html( $eraser_friendly_name ),
5310                      $eraser_index
5311                  )
5312              );
5313          }
5314  
5315          if ( ! array_key_exists( 'done', $response ) ) {
5316              wp_send_json_error(
5317                  sprintf(
5318                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5319                      __( 'Expected done flag in response array from %1$s eraser (index %2$d).' ),
5320                      esc_html( $eraser_friendly_name ),
5321                      $eraser_index
5322                  )
5323              );
5324          }
5325      } else {
5326          // No erasers, so we're done.
5327          $eraser_key = '';
5328  
5329          $response = array(
5330              'items_removed'  => false,
5331              'items_retained' => false,
5332              'messages'       => array(),
5333              'done'           => true,
5334          );
5335      }
5336  
5337      /**
5338       * Filters a page of personal data eraser data.
5339       *
5340       * Allows the erasure response to be consumed by destinations in addition to Ajax.
5341       *
5342       * @since 4.9.6
5343       *
5344       * @param array  $response        {
5345       *     The personal data for the given exporter and page number.
5346       *
5347       *     @type bool     $items_removed  Whether items were actually removed or not.
5348       *     @type bool     $items_retained Whether items were retained or not.
5349       *     @type string[] $messages       An array of messages to add to the personal data export file.
5350       *     @type bool     $done           Whether the eraser is finished or not.
5351       * }
5352       * @param int    $eraser_index    The index of the eraser that provided this data.
5353       * @param string $email_address   The email address associated with this personal data.
5354       * @param int    $page            The page number for this response.
5355       * @param int    $request_id      The privacy request post ID associated with this request.
5356       * @param string $eraser_key      The key (slug) of the eraser that provided this data.
5357       */
5358      $response = apply_filters( 'wp_privacy_personal_data_erasure_page', $response, $eraser_index, $email_address, $page, $request_id, $eraser_key );
5359  
5360      if ( is_wp_error( $response ) ) {
5361          wp_send_json_error( $response );
5362      }
5363  
5364      wp_send_json_success( $response );
5365  }
5366  
5367  /**
5368   * Handles site health checks on server communication via AJAX.
5369   *
5370   * @since 5.2.0
5371   * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_dotorg_communication()
5372   * @see WP_REST_Site_Health_Controller::test_dotorg_communication()
5373   */
5374  function wp_ajax_health_check_dotorg_communication() {
5375      _doing_it_wrong(
5376          'wp_ajax_health_check_dotorg_communication',
5377          sprintf(
5378              /* translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it. */
5379              __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
5380              'wp_ajax_health_check_dotorg_communication',
5381              'WP_REST_Site_Health_Controller::test_dotorg_communication'
5382          ),
5383          '5.6.0'
5384      );
5385  
5386      check_ajax_referer( 'health-check-site-status' );
5387  
5388      if ( ! current_user_can( 'view_site_health_checks' ) ) {
5389          wp_send_json_error();
5390      }
5391  
5392      if ( ! class_exists( 'WP_Site_Health' ) ) {
5393          require_once  ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
5394      }
5395  
5396      $site_health = WP_Site_Health::get_instance();
5397      wp_send_json_success( $site_health->get_test_dotorg_communication() );
5398  }
5399  
5400  /**
5401   * Handles site health checks on background updates via AJAX.
5402   *
5403   * @since 5.2.0
5404   * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_background_updates()
5405   * @see WP_REST_Site_Health_Controller::test_background_updates()
5406   */
5407  function wp_ajax_health_check_background_updates() {
5408      _doing_it_wrong(
5409          'wp_ajax_health_check_background_updates',
5410          sprintf(
5411              /* translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it. */
5412              __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
5413              'wp_ajax_health_check_background_updates',
5414              'WP_REST_Site_Health_Controller::test_background_updates'
5415          ),
5416          '5.6.0'
5417      );
5418  
5419      check_ajax_referer( 'health-check-site-status' );
5420  
5421      if ( ! current_user_can( 'view_site_health_checks' ) ) {
5422          wp_send_json_error();
5423      }
5424  
5425      if ( ! class_exists( 'WP_Site_Health' ) ) {
5426          require_once  ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
5427      }
5428  
5429      $site_health = WP_Site_Health::get_instance();
5430      wp_send_json_success( $site_health->get_test_background_updates() );
5431  }
5432  
5433  /**
5434   * Handles site health checks on loopback requests via AJAX.
5435   *
5436   * @since 5.2.0
5437   * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_loopback_requests()
5438   * @see WP_REST_Site_Health_Controller::test_loopback_requests()
5439   */
5440  function wp_ajax_health_check_loopback_requests() {
5441      _doing_it_wrong(
5442          'wp_ajax_health_check_loopback_requests',
5443          sprintf(
5444              /* translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it. */
5445              __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
5446              'wp_ajax_health_check_loopback_requests',
5447              'WP_REST_Site_Health_Controller::test_loopback_requests'
5448          ),
5449          '5.6.0'
5450      );
5451  
5452      check_ajax_referer( 'health-check-site-status' );
5453  
5454      if ( ! current_user_can( 'view_site_health_checks' ) ) {
5455          wp_send_json_error();
5456      }
5457  
5458      if ( ! class_exists( 'WP_Site_Health' ) ) {
5459          require_once  ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
5460      }
5461  
5462      $site_health = WP_Site_Health::get_instance();
5463      wp_send_json_success( $site_health->get_test_loopback_requests() );
5464  }
5465  
5466  /**
5467   * Handles site health check to update the result status via AJAX.
5468   *
5469   * @since 5.2.0
5470   */
5471  function wp_ajax_health_check_site_status_result() {
5472      check_ajax_referer( 'health-check-site-status-result' );
5473  
5474      if ( ! current_user_can( 'view_site_health_checks' ) ) {
5475          wp_send_json_error();
5476      }
5477  
5478      set_transient( 'health-check-site-status-result', wp_json_encode( $_POST['counts'] ) );
5479  
5480      wp_send_json_success();
5481  }
5482  
5483  /**
5484   * Handles site health check to get directories and database sizes via AJAX.
5485   *
5486   * @since 5.2.0
5487   * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::get_directory_sizes()
5488   * @see WP_REST_Site_Health_Controller::get_directory_sizes()
5489   */
5490  function wp_ajax_health_check_get_sizes() {
5491      _doing_it_wrong(
5492          'wp_ajax_health_check_get_sizes',
5493          sprintf(
5494              /* translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it. */
5495              __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
5496              'wp_ajax_health_check_get_sizes',
5497              'WP_REST_Site_Health_Controller::get_directory_sizes'
5498          ),
5499          '5.6.0'
5500      );
5501  
5502      check_ajax_referer( 'health-check-site-status-result' );
5503  
5504      if ( ! current_user_can( 'view_site_health_checks' ) || is_multisite() ) {
5505          wp_send_json_error();
5506      }
5507  
5508      if ( ! class_exists( 'WP_Debug_Data' ) ) {
5509          require_once  ABSPATH . 'wp-admin/includes/class-wp-debug-data.php';
5510      }
5511  
5512      $sizes_data = WP_Debug_Data::get_sizes();
5513      $all_sizes  = array( 'raw' => 0 );
5514  
5515      foreach ( $sizes_data as $name => $value ) {
5516          $name = sanitize_text_field( $name );
5517          $data = array();
5518  
5519          if ( isset( $value['size'] ) ) {
5520              if ( is_string( $value['size'] ) ) {
5521                  $data['size'] = sanitize_text_field( $value['size'] );
5522              } else {
5523                  $data['size'] = (int) $value['size'];
5524              }
5525          }
5526  
5527          if ( isset( $value['debug'] ) ) {
5528              if ( is_string( $value['debug'] ) ) {
5529                  $data['debug'] = sanitize_text_field( $value['debug'] );
5530              } else {
5531                  $data['debug'] = (int) $value['debug'];
5532              }
5533          }
5534  
5535          if ( ! empty( $value['raw'] ) ) {
5536              $data['raw'] = (int) $value['raw'];
5537          }
5538  
5539          $all_sizes[ $name ] = $data;
5540      }
5541  
5542      if ( isset( $all_sizes['total_size']['debug'] ) && 'not available' === $all_sizes['total_size']['debug'] ) {
5543          wp_send_json_error( $all_sizes );
5544      }
5545  
5546      wp_send_json_success( $all_sizes );
5547  }
5548  
5549  /**
5550   * Handles renewing the REST API nonce via AJAX.
5551   *
5552   * @since 5.3.0
5553   */
5554  function wp_ajax_rest_nonce() {
5555      exit( wp_create_nonce( 'wp_rest' ) );
5556  }
5557  
5558  /**
5559   * Handles enabling or disable plugin and theme auto-updates via AJAX.
5560   *
5561   * @since 5.5.0
5562   */
5563  function wp_ajax_toggle_auto_updates() {
5564      check_ajax_referer( 'updates' );
5565  
5566      if ( empty( $_POST['type'] ) || empty( $_POST['asset'] ) || empty( $_POST['state'] ) ) {
5567          wp_send_json_error( array( 'error' => __( 'Invalid data. No selected item.' ) ) );
5568      }
5569  
5570      $asset = sanitize_text_field( urldecode( $_POST['asset'] ) );
5571  
5572      if ( 'enable' !== $_POST['state'] && 'disable' !== $_POST['state'] ) {
5573          wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown state.' ) ) );
5574      }
5575      $state = $_POST['state'];
5576  
5577      if ( 'plugin' !== $_POST['type'] && 'theme' !== $_POST['type'] ) {
5578          wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown type.' ) ) );
5579      }
5580      $type = $_POST['type'];
5581  
5582      switch ( $type ) {
5583          case 'plugin':
5584              if ( ! current_user_can( 'update_plugins' ) ) {
5585                  $error_message = __( 'Sorry, you are not allowed to modify plugins.' );
5586                  wp_send_json_error( array( 'error' => $error_message ) );
5587              }
5588  
5589              $option = 'auto_update_plugins';
5590              /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
5591              $all_items = apply_filters( 'all_plugins', get_plugins() );
5592              break;
5593          case 'theme':
5594              if ( ! current_user_can( 'update_themes' ) ) {
5595                  $error_message = __( 'Sorry, you are not allowed to modify themes.' );
5596                  wp_send_json_error( array( 'error' => $error_message ) );
5597              }
5598  
5599              $option    = 'auto_update_themes';
5600              $all_items = wp_get_themes();
5601              break;
5602          default:
5603              wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown type.' ) ) );
5604      }
5605  
5606      if ( ! array_key_exists( $asset, $all_items ) ) {
5607          $error_message = __( 'Invalid data. The item does not exist.' );
5608          wp_send_json_error( array( 'error' => $error_message ) );
5609      }
5610  
5611      $auto_updates = (array) get_site_option( $option, array() );
5612  
5613      if ( 'disable' === $state ) {
5614          $auto_updates = array_diff( $auto_updates, array( $asset ) );
5615      } else {
5616          $auto_updates[] = $asset;
5617          $auto_updates   = array_unique( $auto_updates );
5618      }
5619  
5620      // Remove items that have been deleted since the site option was last updated.
5621      $auto_updates = array_intersect( $auto_updates, array_keys( $all_items ) );
5622  
5623      update_site_option( $option, $auto_updates );
5624  
5625      wp_send_json_success();
5626  }
5627  
5628  /**
5629   * Handles sending a password reset link via AJAX.
5630   *
5631   * @since 5.7.0
5632   */
5633  function wp_ajax_send_password_reset() {
5634  
5635      // Validate the nonce for this action.
5636      $user_id = isset( $_POST['user_id'] ) ? (int) $_POST['user_id'] : 0;
5637      check_ajax_referer( 'reset-password-for-' . $user_id, 'nonce' );
5638  
5639      // Verify user capabilities.
5640      if ( ! current_user_can( 'edit_user', $user_id ) ) {
5641          wp_send_json_error( __( 'Cannot send password reset, permission denied.' ) );
5642      }
5643  
5644      // Send the password reset link.
5645      $user    = get_userdata( $user_id );
5646      $results = retrieve_password( $user->user_login );
5647  
5648      if ( true === $results ) {
5649          wp_send_json_success(
5650              /* translators: %s: User's display name. */
5651              sprintf( __( 'A password reset link was emailed to %s.' ), $user->display_name )
5652          );
5653      } else {
5654          wp_send_json_error( $results->get_error_message() );
5655      }
5656  }


Generated : Wed Jul 15 08:20:16 2026 Cross-referenced by PHPXref