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