[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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