[ Index ]

PHP Cross Reference of WordPress

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