[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/includes/ -> ajax-actions.php (source)

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