[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

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