[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

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