[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

   1  <?php
   2  /**
   3   * Administration API: Core Ajax handlers
   4   *
   5   * @package WordPress
   6   * @subpackage Administration
   7   * @since 2.1.0
   8   */
   9  
  10  //
  11  // No-privilege Ajax handlers.
  12  //
  13  
  14  /**
  15   * Ajax handler for the Heartbeat API in
  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/manual/datetime.format.php */
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  
2608      switch ( $_POST['do'] ) {
2609          case 'save':
2610              $msg = wp_save_image( $attachment_id );
2611              if ( ! empty( $msg->error ) ) {
2612                  wp_send_json_error( $msg );
2613              }
2614  
2615              wp_send_json_success( $msg );
2616              break;
2617          case 'scale':
2618              $msg = wp_save_image( $attachment_id );
2619              break;
2620          case 'restore':
2621              $msg = wp_restore_image( $attachment_id );
2622              break;
2623      }
2624  
2625      ob_start();
2626      wp_image_editor( $attachment_id, $msg );
2627      $html = ob_get_clean();
2628  
2629      if ( ! empty( $msg->error ) ) {
2630          wp_send_json_error(
2631              array(
2632                  'message' => $msg,
2633                  'html'    => $html,
2634              )
2635          );
2636      }
2637  
2638      wp_send_json_success(
2639          array(
2640              'message' => $msg,
2641              'html'    => $html,
2642          )
2643      );
2644  }
2645  
2646  /**
2647   * Ajax handler for setting the featured image.
2648   *
2649   * @since 3.1.0
2650   */
2651  function wp_ajax_set_post_thumbnail() {
2652      $json = ! empty( $_REQUEST['json'] ); // New-style request.
2653  
2654      $post_ID = intval( $_POST['post_id'] );
2655      if ( ! current_user_can( 'edit_post', $post_ID ) ) {
2656          wp_die( -1 );
2657      }
2658  
2659      $thumbnail_id = intval( $_POST['thumbnail_id'] );
2660  
2661      if ( $json ) {
2662          check_ajax_referer( "update-post_$post_ID" );
2663      } else {
2664          check_ajax_referer( "set_post_thumbnail-$post_ID" );
2665      }
2666  
2667      if ( '-1' == $thumbnail_id ) {
2668          if ( delete_post_thumbnail( $post_ID ) ) {
2669              $return = _wp_post_thumbnail_html( null, $post_ID );
2670              $json ? wp_send_json_success( $return ) : wp_die( $return );
2671          } else {
2672              wp_die( 0 );
2673          }
2674      }
2675  
2676      if ( set_post_thumbnail( $post_ID, $thumbnail_id ) ) {
2677          $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
2678          $json ? wp_send_json_success( $return ) : wp_die( $return );
2679      }
2680  
2681      wp_die( 0 );
2682  }
2683  
2684  /**
2685   * Ajax handler for retrieving HTML for the featured image.
2686   *
2687   * @since 4.6.0
2688   */
2689  function wp_ajax_get_post_thumbnail_html() {
2690      $post_ID = intval( $_POST['post_id'] );
2691  
2692      check_ajax_referer( "update-post_$post_ID" );
2693  
2694      if ( ! current_user_can( 'edit_post', $post_ID ) ) {
2695          wp_die( -1 );
2696      }
2697  
2698      $thumbnail_id = intval( $_POST['thumbnail_id'] );
2699  
2700      // For backward compatibility, -1 refers to no featured image.
2701      if ( -1 === $thumbnail_id ) {
2702          $thumbnail_id = null;
2703      }
2704  
2705      $return = _wp_post_thumbnail_html( $thumbnail_id, $post_ID );
2706      wp_send_json_success( $return );
2707  }
2708  
2709  /**
2710   * Ajax handler for setting the featured image for an attachment.
2711   *
2712   * @since 4.0.0
2713   *
2714   * @see set_post_thumbnail()
2715   */
2716  function wp_ajax_set_attachment_thumbnail() {
2717      if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) {
2718          wp_send_json_error();
2719      }
2720  
2721      $thumbnail_id = (int) $_POST['thumbnail_id'];
2722      if ( empty( $thumbnail_id ) ) {
2723          wp_send_json_error();
2724      }
2725  
2726      $post_ids = array();
2727      // For each URL, try to find its corresponding post ID.
2728      foreach ( $_POST['urls'] as $url ) {
2729          $post_id = attachment_url_to_postid( $url );
2730          if ( ! empty( $post_id ) ) {
2731              $post_ids[] = $post_id;
2732          }
2733      }
2734  
2735      if ( empty( $post_ids ) ) {
2736          wp_send_json_error();
2737      }
2738  
2739      $success = 0;
2740      // For each found attachment, set its thumbnail.
2741      foreach ( $post_ids as $post_id ) {
2742          if ( ! current_user_can( 'edit_post', $post_id ) ) {
2743              continue;
2744          }
2745  
2746          if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2747              $success++;
2748          }
2749      }
2750  
2751      if ( 0 === $success ) {
2752          wp_send_json_error();
2753      } else {
2754          wp_send_json_success();
2755      }
2756  
2757      wp_send_json_error();
2758  }
2759  
2760  /**
2761   * Ajax handler for date formatting.
2762   *
2763   * @since 3.1.0
2764   */
2765  function wp_ajax_date_format() {
2766      wp_die( date_i18n( sanitize_option( 'date_format', wp_unslash( $_POST['date'] ) ) ) );
2767  }
2768  
2769  /**
2770   * Ajax handler for time formatting.
2771   *
2772   * @since 3.1.0
2773   */
2774  function wp_ajax_time_format() {
2775      wp_die( date_i18n( sanitize_option( 'time_format', wp_unslash( $_POST['date'] ) ) ) );
2776  }
2777  
2778  /**
2779   * Ajax handler for saving posts from the fullscreen editor.
2780   *
2781   * @since 3.1.0
2782   * @deprecated 4.3.0
2783   */
2784  function wp_ajax_wp_fullscreen_save_post() {
2785      $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
2786  
2787      $post = null;
2788  
2789      if ( $post_id ) {
2790          $post = get_post( $post_id );
2791      }
2792  
2793      check_ajax_referer( 'update-post_' . $post_id, '_wpnonce' );
2794  
2795      $post_id = edit_post();
2796  
2797      if ( is_wp_error( $post_id ) ) {
2798          wp_send_json_error();
2799      }
2800  
2801      if ( $post ) {
2802          $last_date = mysql2date( __( 'F j, Y' ), $post->post_modified );
2803          $last_time = mysql2date( __( 'g:i a' ), $post->post_modified );
2804      } else {
2805          $last_date = date_i18n( __( 'F j, Y' ) );
2806          $last_time = date_i18n( __( 'g:i a' ) );
2807      }
2808  
2809      $last_id = get_post_meta( $post_id, '_edit_last', true );
2810      if ( $last_id ) {
2811          $last_user = get_userdata( $last_id );
2812          /* translators: 1: User's display name, 2: Date of last edit, 3: Time of last edit. */
2813          $last_edited = sprintf( __( 'Last edited by %1$s on %2$s at %3$s' ), esc_html( $last_user->display_name ), $last_date, $last_time );
2814      } else {
2815          /* translators: 1: Date of last edit, 2: Time of last edit. */
2816          $last_edited = sprintf( __( 'Last edited on %1$s at %2$s' ), $last_date, $last_time );
2817      }
2818  
2819      wp_send_json_success( array( 'last_edited' => $last_edited ) );
2820  }
2821  
2822  /**
2823   * Ajax handler for removing a post lock.
2824   *
2825   * @since 3.1.0
2826   */
2827  function wp_ajax_wp_remove_post_lock() {
2828      if ( empty( $_POST['post_ID'] ) || empty( $_POST['active_post_lock'] ) ) {
2829          wp_die( 0 );
2830      }
2831  
2832      $post_id = (int) $_POST['post_ID'];
2833      $post    = get_post( $post_id );
2834  
2835      if ( ! $post ) {
2836          wp_die( 0 );
2837      }
2838  
2839      check_ajax_referer( 'update-post_' . $post_id );
2840  
2841      if ( ! current_user_can( 'edit_post', $post_id ) ) {
2842          wp_die( -1 );
2843      }
2844  
2845      $active_lock = array_map( 'absint', explode( ':', $_POST['active_post_lock'] ) );
2846  
2847      if ( get_current_user_id() != $active_lock[1] ) {
2848          wp_die( 0 );
2849      }
2850  
2851      /**
2852       * Filters the post lock window duration.
2853       *
2854       * @since 3.3.0
2855       *
2856       * @param int $interval The interval in seconds the post lock duration
2857       *                      should last, plus 5 seconds. Default 150.
2858       */
2859      $new_lock = ( time() - apply_filters( 'wp_check_post_lock_window', 150 ) + 5 ) . ':' . $active_lock[1];
2860      update_post_meta( $post_id, '_edit_lock', $new_lock, implode( ':', $active_lock ) );
2861      wp_die( 1 );
2862  }
2863  
2864  /**
2865   * Ajax handler for dismissing a WordPress pointer.
2866   *
2867   * @since 3.1.0
2868   */
2869  function wp_ajax_dismiss_wp_pointer() {
2870      $pointer = $_POST['pointer'];
2871  
2872      if ( sanitize_key( $pointer ) != $pointer ) {
2873          wp_die( 0 );
2874      }
2875  
2876      //  check_ajax_referer( 'dismiss-pointer_' . $pointer );
2877  
2878      $dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
2879  
2880      if ( in_array( $pointer, $dismissed, true ) ) {
2881          wp_die( 0 );
2882      }
2883  
2884      $dismissed[] = $pointer;
2885      $dismissed   = implode( ',', $dismissed );
2886  
2887      update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
2888      wp_die( 1 );
2889  }
2890  
2891  /**
2892   * Ajax handler for getting an attachment.
2893   *
2894   * @since 3.5.0
2895   */
2896  function wp_ajax_get_attachment() {
2897      if ( ! isset( $_REQUEST['id'] ) ) {
2898          wp_send_json_error();
2899      }
2900  
2901      $id = absint( $_REQUEST['id'] );
2902      if ( ! $id ) {
2903          wp_send_json_error();
2904      }
2905  
2906      $post = get_post( $id );
2907      if ( ! $post ) {
2908          wp_send_json_error();
2909      }
2910  
2911      if ( 'attachment' !== $post->post_type ) {
2912          wp_send_json_error();
2913      }
2914  
2915      if ( ! current_user_can( 'upload_files' ) ) {
2916          wp_send_json_error();
2917      }
2918  
2919      $attachment = wp_prepare_attachment_for_js( $id );
2920      if ( ! $attachment ) {
2921          wp_send_json_error();
2922      }
2923  
2924      wp_send_json_success( $attachment );
2925  }
2926  
2927  /**
2928   * Ajax handler for querying attachments.
2929   *
2930   * @since 3.5.0
2931   */
2932  function wp_ajax_query_attachments() {
2933      if ( ! current_user_can( 'upload_files' ) ) {
2934          wp_send_json_error();
2935      }
2936  
2937      $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
2938      $keys  = array(
2939          's',
2940          'order',
2941          'orderby',
2942          'posts_per_page',
2943          'paged',
2944          'post_mime_type',
2945          'post_parent',
2946          'author',
2947          'post__in',
2948          'post__not_in',
2949          'year',
2950          'monthnum',
2951      );
2952  
2953      foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) {
2954          if ( $t->query_var && isset( $query[ $t->query_var ] ) ) {
2955              $keys[] = $t->query_var;
2956          }
2957      }
2958  
2959      $query              = array_intersect_key( $query, array_flip( $keys ) );
2960      $query['post_type'] = 'attachment';
2961  
2962      if (
2963          MEDIA_TRASH &&
2964          ! empty( $_REQUEST['query']['post_status'] ) &&
2965          'trash' === $_REQUEST['query']['post_status']
2966      ) {
2967          $query['post_status'] = 'trash';
2968      } else {
2969          $query['post_status'] = 'inherit';
2970      }
2971  
2972      if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) ) {
2973          $query['post_status'] .= ',private';
2974      }
2975  
2976      // Filter query clauses to include filenames.
2977      if ( isset( $query['s'] ) ) {
2978          add_filter( 'posts_clauses', '_filter_query_attachment_filenames' );
2979      }
2980  
2981      /**
2982       * Filters the arguments passed to WP_Query during an Ajax
2983       * call for querying attachments.
2984       *
2985       * @since 3.7.0
2986       *
2987       * @see WP_Query::parse_query()
2988       *
2989       * @param array $query An array of query variables.
2990       */
2991      $query = apply_filters( 'ajax_query_attachments_args', $query );
2992      $query = new WP_Query( $query );
2993  
2994      $posts = array_map( 'wp_prepare_attachment_for_js', $query->posts );
2995      $posts = array_filter( $posts );
2996  
2997      wp_send_json_success( $posts );
2998  }
2999  
3000  /**
3001   * Ajax handler for updating attachment attributes.
3002   *
3003   * @since 3.5.0
3004   */
3005  function wp_ajax_save_attachment() {
3006      if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) ) {
3007          wp_send_json_error();
3008      }
3009  
3010      $id = absint( $_REQUEST['id'] );
3011      if ( ! $id ) {
3012          wp_send_json_error();
3013      }
3014  
3015      check_ajax_referer( 'update-post_' . $id, 'nonce' );
3016  
3017      if ( ! current_user_can( 'edit_post', $id ) ) {
3018          wp_send_json_error();
3019      }
3020  
3021      $changes = $_REQUEST['changes'];
3022      $post    = get_post( $id, ARRAY_A );
3023  
3024      if ( 'attachment' !== $post['post_type'] ) {
3025          wp_send_json_error();
3026      }
3027  
3028      if ( isset( $changes['parent'] ) ) {
3029          $post['post_parent'] = $changes['parent'];
3030      }
3031  
3032      if ( isset( $changes['title'] ) ) {
3033          $post['post_title'] = $changes['title'];
3034      }
3035  
3036      if ( isset( $changes['caption'] ) ) {
3037          $post['post_excerpt'] = $changes['caption'];
3038      }
3039  
3040      if ( isset( $changes['description'] ) ) {
3041          $post['post_content'] = $changes['description'];
3042      }
3043  
3044      if ( MEDIA_TRASH && isset( $changes['status'] ) ) {
3045          $post['post_status'] = $changes['status'];
3046      }
3047  
3048      if ( isset( $changes['alt'] ) ) {
3049          $alt = wp_unslash( $changes['alt'] );
3050          if ( get_post_meta( $id, '_wp_attachment_image_alt', true ) !== $alt ) {
3051              $alt = wp_strip_all_tags( $alt, true );
3052              update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) );
3053          }
3054      }
3055  
3056      if ( wp_attachment_is( 'audio', $post['ID'] ) ) {
3057          $changed = false;
3058          $id3data = wp_get_attachment_metadata( $post['ID'] );
3059  
3060          if ( ! is_array( $id3data ) ) {
3061              $changed = true;
3062              $id3data = array();
3063          }
3064  
3065          foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
3066              if ( isset( $changes[ $key ] ) ) {
3067                  $changed         = true;
3068                  $id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
3069              }
3070          }
3071  
3072          if ( $changed ) {
3073              wp_update_attachment_metadata( $id, $id3data );
3074          }
3075      }
3076  
3077      if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
3078          wp_delete_post( $id );
3079      } else {
3080          wp_update_post( $post );
3081      }
3082  
3083      wp_send_json_success();
3084  }
3085  
3086  /**
3087   * Ajax handler for saving backward compatible attachment attributes.
3088   *
3089   * @since 3.5.0
3090   */
3091  function wp_ajax_save_attachment_compat() {
3092      if ( ! isset( $_REQUEST['id'] ) ) {
3093          wp_send_json_error();
3094      }
3095  
3096      $id = absint( $_REQUEST['id'] );
3097      if ( ! $id ) {
3098          wp_send_json_error();
3099      }
3100  
3101      if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) ) {
3102          wp_send_json_error();
3103      }
3104  
3105      $attachment_data = $_REQUEST['attachments'][ $id ];
3106  
3107      check_ajax_referer( 'update-post_' . $id, 'nonce' );
3108  
3109      if ( ! current_user_can( 'edit_post', $id ) ) {
3110          wp_send_json_error();
3111      }
3112  
3113      $post = get_post( $id, ARRAY_A );
3114  
3115      if ( 'attachment' !== $post['post_type'] ) {
3116          wp_send_json_error();
3117      }
3118  
3119      /** This filter is documented in wp-admin/includes/media.php */
3120      $post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data );
3121  
3122      if ( isset( $post['errors'] ) ) {
3123          $errors = $post['errors']; // @todo return me and display me!
3124          unset( $post['errors'] );
3125      }
3126  
3127      wp_update_post( $post );
3128  
3129      foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
3130          if ( isset( $attachment_data[ $taxonomy ] ) ) {
3131              wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false );
3132          }
3133      }
3134  
3135      $attachment = wp_prepare_attachment_for_js( $id );
3136  
3137      if ( ! $attachment ) {
3138          wp_send_json_error();
3139      }
3140  
3141      wp_send_json_success( $attachment );
3142  }
3143  
3144  /**
3145   * Ajax handler for saving the attachment order.
3146   *
3147   * @since 3.5.0
3148   */
3149  function wp_ajax_save_attachment_order() {
3150      if ( ! isset( $_REQUEST['post_id'] ) ) {
3151          wp_send_json_error();
3152      }
3153  
3154      $post_id = absint( $_REQUEST['post_id'] );
3155      if ( ! $post_id ) {
3156          wp_send_json_error();
3157      }
3158  
3159      if ( empty( $_REQUEST['attachments'] ) ) {
3160          wp_send_json_error();
3161      }
3162  
3163      check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
3164  
3165      $attachments = $_REQUEST['attachments'];
3166  
3167      if ( ! current_user_can( 'edit_post', $post_id ) ) {
3168          wp_send_json_error();
3169      }
3170  
3171      foreach ( $attachments as $attachment_id => $menu_order ) {
3172          if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
3173              continue;
3174          }
3175  
3176          $attachment = get_post( $attachment_id );
3177  
3178          if ( ! $attachment ) {
3179              continue;
3180          }
3181  
3182          if ( 'attachment' !== $attachment->post_type ) {
3183              continue;
3184          }
3185  
3186          wp_update_post(
3187              array(
3188                  'ID'         => $attachment_id,
3189                  'menu_order' => $menu_order,
3190              )
3191          );
3192      }
3193  
3194      wp_send_json_success();
3195  }
3196  
3197  /**
3198   * Ajax handler for sending an attachment to the editor.
3199   *
3200   * Generates the HTML to send an attachment to the editor.
3201   * Backward compatible with the {@see 'media_send_to_editor'} filter
3202   * and the chain of filters that follow.
3203   *
3204   * @since 3.5.0
3205   */
3206  function wp_ajax_send_attachment_to_editor() {
3207      check_ajax_referer( 'media-send-to-editor', 'nonce' );
3208  
3209      $attachment = wp_unslash( $_POST['attachment'] );
3210  
3211      $id = intval( $attachment['id'] );
3212  
3213      $post = get_post( $id );
3214      if ( ! $post ) {
3215          wp_send_json_error();
3216      }
3217  
3218      if ( 'attachment' !== $post->post_type ) {
3219          wp_send_json_error();
3220      }
3221  
3222      if ( current_user_can( 'edit_post', $id ) ) {
3223          // If this attachment is unattached, attach it. Primarily a back compat thing.
3224          $insert_into_post_id = intval( $_POST['post_id'] );
3225  
3226          if ( 0 == $post->post_parent && $insert_into_post_id ) {
3227              wp_update_post(
3228                  array(
3229                      'ID'          => $id,
3230                      'post_parent' => $insert_into_post_id,
3231                  )
3232              );
3233          }
3234      }
3235  
3236      $url = empty( $attachment['url'] ) ? '' : $attachment['url'];
3237      $rel = ( strpos( $url, 'attachment_id' ) || get_attachment_link( $id ) == $url );
3238  
3239      remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
3240  
3241      if ( 'image' === substr( $post->post_mime_type, 0, 5 ) ) {
3242          $align = isset( $attachment['align'] ) ? $attachment['align'] : 'none';
3243          $size  = isset( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium';
3244          $alt   = isset( $attachment['image_alt'] ) ? $attachment['image_alt'] : '';
3245  
3246          // No whitespace-only captions.
3247          $caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : '';
3248          if ( '' === trim( $caption ) ) {
3249              $caption = '';
3250          }
3251  
3252          $title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
3253          $html  = get_image_send_to_editor( $id, $caption, $title, $align, $url, $rel, $size, $alt );
3254      } elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post ) ) {
3255          $html = stripslashes_deep( $_POST['html'] );
3256      } else {
3257          $html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : '';
3258          $rel  = $rel ? ' rel="attachment wp-att-' . $id . '"' : ''; // Hard-coded string, $id is already sanitized.
3259  
3260          if ( ! empty( $url ) ) {
3261              $html = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
3262          }
3263      }
3264  
3265      /** This filter is documented in wp-admin/includes/media.php */
3266      $html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
3267  
3268      wp_send_json_success( $html );
3269  }
3270  
3271  /**
3272   * Ajax handler for sending a link to the editor.
3273   *
3274   * Generates the HTML to send a non-image embed link to the editor.
3275   *
3276   * Backward compatible with the following filters:
3277   * - file_send_to_editor_url
3278   * - audio_send_to_editor_url
3279   * - video_send_to_editor_url
3280   *
3281   * @since 3.5.0
3282   *
3283   * @global WP_Post  $post     Global post object.
3284   * @global WP_Embed $wp_embed
3285   */
3286  function wp_ajax_send_link_to_editor() {
3287      global $post, $wp_embed;
3288  
3289      check_ajax_referer( 'media-send-to-editor', 'nonce' );
3290  
3291      $src = wp_unslash( $_POST['src'] );
3292      if ( ! $src ) {
3293          wp_send_json_error();
3294      }
3295  
3296      if ( ! strpos( $src, '://' ) ) {
3297          $src = 'http://' . $src;
3298      }
3299  
3300      $src = esc_url_raw( $src );
3301      if ( ! $src ) {
3302          wp_send_json_error();
3303      }
3304  
3305      $link_text = trim( wp_unslash( $_POST['link_text'] ) );
3306      if ( ! $link_text ) {
3307          $link_text = wp_basename( $src );
3308      }
3309  
3310      $post = get_post( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
3311  
3312      // Ping WordPress for an embed.
3313      $check_embed = $wp_embed->run_shortcode( '[embed]' . $src . '[/embed]' );
3314  
3315      // Fallback that WordPress creates when no oEmbed was found.
3316      $fallback = $wp_embed->maybe_make_link( $src );
3317  
3318      if ( $check_embed !== $fallback ) {
3319          // TinyMCE view for [embed] will parse this.
3320          $html = '[embed]' . $src . '[/embed]';
3321      } elseif ( $link_text ) {
3322          $html = '<a href="' . esc_url( $src ) . '">' . $link_text . '</a>';
3323      } else {
3324          $html = '';
3325      }
3326  
3327      // Figure out what filter to run:
3328      $type = 'file';
3329      $ext  = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src );
3330      if ( $ext ) {
3331          $ext_type = wp_ext2type( $ext );
3332          if ( 'audio' === $ext_type || 'video' === $ext_type ) {
3333              $type = $ext_type;
3334          }
3335      }
3336  
3337      /** This filter is documented in wp-admin/includes/media.php */
3338      $html = apply_filters( "{$type}_send_to_editor_url", $html, $src, $link_text );
3339  
3340      wp_send_json_success( $html );
3341  }
3342  
3343  /**
3344   * Ajax handler for the Heartbeat API.
3345   *
3346   * Runs when the user is logged in.
3347   *
3348   * @since 3.6.0
3349   */
3350  function wp_ajax_heartbeat() {
3351      if ( empty( $_POST['_nonce'] ) ) {
3352          wp_send_json_error();
3353      }
3354  
3355      $response    = array();
3356      $data        = array();
3357      $nonce_state = wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' );
3358  
3359      // 'screen_id' is the same as $current_screen->id and the JS global 'pagenow'.
3360      if ( ! empty( $_POST['screen_id'] ) ) {
3361          $screen_id = sanitize_key( $_POST['screen_id'] );
3362      } else {
3363          $screen_id = 'front';
3364      }
3365  
3366      if ( ! empty( $_POST['data'] ) ) {
3367          $data = wp_unslash( (array) $_POST['data'] );
3368      }
3369  
3370      if ( 1 !== $nonce_state ) {
3371          /**
3372           * Filters the nonces to send to the New/Edit Post screen.
3373           *
3374           * @since 4.3.0
3375           *
3376           * @param array  $response  The Heartbeat response.
3377           * @param array  $data      The $_POST data sent.
3378           * @param string $screen_id The screen ID.
3379           */
3380          $response = apply_filters( 'wp_refresh_nonces', $response, $data, $screen_id );
3381  
3382          if ( false === $nonce_state ) {
3383              // User is logged in but nonces have expired.
3384              $response['nonces_expired'] = true;
3385              wp_send_json( $response );
3386          }
3387      }
3388  
3389      if ( ! empty( $data ) ) {
3390          /**
3391           * Filters the Heartbeat response received.
3392           *
3393           * @since 3.6.0
3394           *
3395           * @param array  $response  The Heartbeat response.
3396           * @param array  $data      The $_POST data sent.
3397           * @param string $screen_id The screen ID.
3398           */
3399          $response = apply_filters( 'heartbeat_received', $response, $data, $screen_id );
3400      }
3401  
3402      /**
3403       * Filters the Heartbeat response sent.
3404       *
3405       * @since 3.6.0
3406       *
3407       * @param array  $response  The Heartbeat response.
3408       * @param string $screen_id The screen ID.
3409       */
3410      $response = apply_filters( 'heartbeat_send', $response, $screen_id );
3411  
3412      /**
3413       * Fires when Heartbeat ticks in logged-in environments.
3414       *
3415       * Allows the transport to be easily replaced with long-polling.
3416       *
3417       * @since 3.6.0
3418       *
3419       * @param array  $response  The Heartbeat response.
3420       * @param string $screen_id The screen ID.
3421       */
3422      do_action( 'heartbeat_tick', $response, $screen_id );
3423  
3424      // Send the current time according to the server.
3425      $response['server_time'] = time();
3426  
3427      wp_send_json( $response );
3428  }
3429  
3430  /**
3431   * Ajax handler for getting revision diffs.
3432   *
3433   * @since 3.6.0
3434   */
3435  function wp_ajax_get_revision_diffs() {
3436      require ABSPATH . 'wp-admin/includes/revision.php';
3437  
3438      $post = get_post( (int) $_REQUEST['post_id'] );
3439      if ( ! $post ) {
3440          wp_send_json_error();
3441      }
3442  
3443      if ( ! current_user_can( 'edit_post', $post->ID ) ) {
3444          wp_send_json_error();
3445      }
3446  
3447      // Really just pre-loading the cache here.
3448      $revisions = wp_get_post_revisions( $post->ID, array( 'check_enabled' => false ) );
3449      if ( ! $revisions ) {
3450          wp_send_json_error();
3451      }
3452  
3453      $return = array();
3454      set_time_limit( 0 );
3455  
3456      foreach ( $_REQUEST['compare'] as $compare_key ) {
3457          list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
3458  
3459          $return[] = array(
3460              'id'     => $compare_key,
3461              'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
3462          );
3463      }
3464      wp_send_json_success( $return );
3465  }
3466  
3467  /**
3468   * Ajax handler for auto-saving the selected color scheme for
3469   * a user's own profile.
3470   *
3471   * @since 3.8.0
3472   *
3473   * @global array $_wp_admin_css_colors
3474   */
3475  function wp_ajax_save_user_color_scheme() {
3476      global $_wp_admin_css_colors;
3477  
3478      check_ajax_referer( 'save-color-scheme', 'nonce' );
3479  
3480      $color_scheme = sanitize_key( $_POST['color_scheme'] );
3481  
3482      if ( ! isset( $_wp_admin_css_colors[ $color_scheme ] ) ) {
3483          wp_send_json_error();
3484      }
3485  
3486      $previous_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true );
3487      update_user_meta( get_current_user_id(), 'admin_color', $color_scheme );
3488  
3489      wp_send_json_success(
3490          array(
3491              'previousScheme' => 'admin-color-' . $previous_color_scheme,
3492              'currentScheme'  => 'admin-color-' . $color_scheme,
3493          )
3494      );
3495  }
3496  
3497  /**
3498   * Ajax handler for getting themes from themes_api().
3499   *
3500   * @since 3.9.0
3501   *
3502   * @global array $themes_allowedtags
3503   * @global array $theme_field_defaults
3504   */
3505  function wp_ajax_query_themes() {
3506      global $themes_allowedtags, $theme_field_defaults;
3507  
3508      if ( ! current_user_can( 'install_themes' ) ) {
3509          wp_send_json_error();
3510      }
3511  
3512      $args = wp_parse_args(
3513          wp_unslash( $_REQUEST['request'] ),
3514          array(
3515              'per_page' => 20,
3516              'fields'   => array_merge(
3517                  (array) $theme_field_defaults,
3518                  array(
3519                      'reviews_url' => true, // Explicitly request the reviews URL to be linked from the Add Themes screen.
3520                  )
3521              ),
3522          )
3523      );
3524  
3525      if ( isset( $args['browse'] ) && 'favorites' === $args['browse'] && ! isset( $args['user'] ) ) {
3526          $user = get_user_option( 'wporg_favorites' );
3527          if ( $user ) {
3528              $args['user'] = $user;
3529          }
3530      }
3531  
3532      $old_filter = isset( $args['browse'] ) ? $args['browse'] : 'search';
3533  
3534      /** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */
3535      $args = apply_filters( 'install_themes_table_api_args_' . $old_filter, $args );
3536  
3537      $api = themes_api( 'query_themes', $args );
3538  
3539      if ( is_wp_error( $api ) ) {
3540          wp_send_json_error();
3541      }
3542  
3543      $update_php = network_admin_url( 'update.php?action=install-theme' );
3544  
3545      foreach ( $api->themes as &$theme ) {
3546          $theme->install_url = add_query_arg(
3547              array(
3548                  'theme'    => $theme->slug,
3549                  '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ),
3550              ),
3551              $update_php
3552          );
3553  
3554          if ( current_user_can( 'switch_themes' ) ) {
3555              if ( is_multisite() ) {
3556                  $theme->activate_url = add_query_arg(
3557                      array(
3558                          'action'   => 'enable',
3559                          '_wpnonce' => wp_create_nonce( 'enable-theme_' . $theme->slug ),
3560                          'theme'    => $theme->slug,
3561                      ),
3562                      network_admin_url( 'themes.php' )
3563                  );
3564              } else {
3565                  $theme->activate_url = add_query_arg(
3566                      array(
3567                          'action'     => 'activate',
3568                          '_wpnonce'   => wp_create_nonce( 'switch-theme_' . $theme->slug ),
3569                          'stylesheet' => $theme->slug,
3570                      ),
3571                      admin_url( 'themes.php' )
3572                  );
3573              }
3574          }
3575  
3576          if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
3577              $theme->customize_url = add_query_arg(
3578                  array(
3579                      'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
3580                  ),
3581                  wp_customize_url( $theme->slug )
3582              );
3583          }
3584  
3585          $theme->name        = wp_kses( $theme->name, $themes_allowedtags );
3586          $theme->author      = wp_kses( $theme->author['display_name'], $themes_allowedtags );
3587          $theme->version     = wp_kses( $theme->version, $themes_allowedtags );
3588          $theme->description = wp_kses( $theme->description, $themes_allowedtags );
3589  
3590          $theme->stars = wp_star_rating(
3591              array(
3592                  'rating' => $theme->rating,
3593                  'type'   => 'percent',
3594                  'number' => $theme->num_ratings,
3595                  'echo'   => false,
3596              )
3597          );
3598  
3599          $theme->num_ratings    = number_format_i18n( $theme->num_ratings );
3600          $theme->preview_url    = set_url_scheme( $theme->preview_url );
3601          $theme->compatible_wp  = is_wp_version_compatible( $theme->requires );
3602          $theme->compatible_php = is_php_version_compatible( $theme->requires_php );
3603      }
3604  
3605      wp_send_json_success( $api );
3606  }
3607  
3608  /**
3609   * Apply [embed] Ajax handlers to a string.
3610   *
3611   * @since 4.0.0
3612   *
3613   * @global WP_Post    $post       Global post object.
3614   * @global WP_Embed   $wp_embed   Embed API instance.
3615   * @global WP_Scripts $wp_scripts
3616   * @global int        $content_width
3617   */
3618  function wp_ajax_parse_embed() {
3619      global $post, $wp_embed, $content_width;
3620  
3621      if ( empty( $_POST['shortcode'] ) ) {
3622          wp_send_json_error();
3623      }
3624  
3625      $post_id = isset( $_POST['post_ID'] ) ? intval( $_POST['post_ID'] ) : 0;
3626  
3627      if ( $post_id > 0 ) {
3628          $post = get_post( $post_id );
3629  
3630          if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3631              wp_send_json_error();
3632          }
3633          setup_postdata( $post );
3634      } elseif ( ! current_user_can( 'edit_posts' ) ) { // See WP_oEmbed_Controller::get_proxy_item_permissions_check().
3635          wp_send_json_error();
3636      }
3637  
3638      $shortcode = wp_unslash( $_POST['shortcode'] );
3639  
3640      preg_match( '/' . get_shortcode_regex() . '/s', $shortcode, $matches );
3641      $atts = shortcode_parse_atts( $matches[3] );
3642  
3643      if ( ! empty( $matches[5] ) ) {
3644          $url = $matches[5];
3645      } elseif ( ! empty( $atts['src'] ) ) {
3646          $url = $atts['src'];
3647      } else {
3648          $url = '';
3649      }
3650  
3651      $parsed                         = false;
3652      $wp_embed->return_false_on_fail = true;
3653  
3654      if ( 0 === $post_id ) {
3655          /*
3656           * Refresh oEmbeds cached outside of posts that are past their TTL.
3657           * Posts are excluded because they have separate logic for refreshing
3658           * their post meta caches. See WP_Embed::cache_oembed().
3659           */
3660          $wp_embed->usecache = false;
3661      }
3662  
3663      if ( is_ssl() && 0 === strpos( $url, 'http://' ) ) {
3664          // Admin is ssl and the user pasted non-ssl URL.
3665          // Check if the provider supports ssl embeds and use that for the preview.
3666          $ssl_shortcode = preg_replace( '%^(\\[embed[^\\]]*\\])http://%i', '$1https://', $shortcode );
3667          $parsed        = $wp_embed->run_shortcode( $ssl_shortcode );
3668  
3669          if ( ! $parsed ) {
3670              $no_ssl_support = true;
3671          }
3672      }
3673  
3674      // Set $content_width so any embeds fit in the destination iframe.
3675      if ( isset( $_POST['maxwidth'] ) && is_numeric( $_POST['maxwidth'] ) && $_POST['maxwidth'] > 0 ) {
3676          if ( ! isset( $content_width ) ) {
3677              $content_width = intval( $_POST['maxwidth'] );
3678          } else {
3679              $content_width = min( $content_width, intval( $_POST['maxwidth'] ) );
3680          }
3681      }
3682  
3683      if ( $url && ! $parsed ) {
3684          $parsed = $wp_embed->run_shortcode( $shortcode );
3685      }
3686  
3687      if ( ! $parsed ) {
3688          wp_send_json_error(
3689              array(
3690                  'type'    => 'not-embeddable',
3691                  /* translators: %s: URL that could not be embedded. */
3692                  'message' => sprintf( __( '%s failed to embed.' ), '<code>' . esc_html( $url ) . '</code>' ),
3693              )
3694          );
3695      }
3696  
3697      if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
3698          $styles     = '';
3699          $mce_styles = wpview_media_sandbox_styles();
3700  
3701          foreach ( $mce_styles as $style ) {
3702              $styles .= sprintf( '<link rel="stylesheet" href="%s"/>', $style );
3703          }
3704  
3705          $html = do_shortcode( $parsed );
3706  
3707          global $wp_scripts;
3708  
3709          if ( ! empty( $wp_scripts ) ) {
3710              $wp_scripts->done = array();
3711          }
3712  
3713          ob_start();
3714          wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
3715          $scripts = ob_get_clean();
3716  
3717          $parsed = $styles . $html . $scripts;
3718      }
3719  
3720      if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
3721          preg_match( '%<link [^>]*href="http://%', $parsed ) ) ) ) {
3722          // Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
3723          wp_send_json_error(
3724              array(
3725                  'type'    => 'not-ssl',
3726                  'message' => __( 'This preview is unavailable in the editor.' ),
3727              )
3728          );
3729      }
3730  
3731      $return = array(
3732          'body' => $parsed,
3733          'attr' => $wp_embed->last_attr,
3734      );
3735  
3736      if ( strpos( $parsed, 'class="wp-embedded-content' ) ) {
3737          if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
3738              $script_src = includes_url( 'js/wp-embed.js' );
3739          } else {
3740              $script_src = includes_url( 'js/wp-embed.min.js' );
3741          }
3742  
3743          $return['head']    = '<script src="' . $script_src . '"></script>';
3744          $return['sandbox'] = true;
3745      }
3746  
3747      wp_send_json_success( $return );
3748  }
3749  
3750  /**
3751   * @since 4.0.0
3752   *
3753   * @global WP_Post    $post       Global post object.
3754   * @global WP_Scripts $wp_scripts
3755   */
3756  function wp_ajax_parse_media_shortcode() {
3757      global $post, $wp_scripts;
3758  
3759      if ( empty( $_POST['shortcode'] ) ) {
3760          wp_send_json_error();
3761      }
3762  
3763      $shortcode = wp_unslash( $_POST['shortcode'] );
3764  
3765      if ( ! empty( $_POST['post_ID'] ) ) {
3766          $post = get_post( (int) $_POST['post_ID'] );
3767      }
3768  
3769      // The embed shortcode requires a post.
3770      if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3771          if ( 'embed' === $shortcode ) {
3772              wp_send_json_error();
3773          }
3774      } else {
3775          setup_postdata( $post );
3776      }
3777  
3778      $parsed = do_shortcode( $shortcode );
3779  
3780      if ( empty( $parsed ) ) {
3781          wp_send_json_error(
3782              array(
3783                  'type'    => 'no-items',
3784                  'message' => __( 'No items found.' ),
3785              )
3786          );
3787      }
3788  
3789      $head   = '';
3790      $styles = wpview_media_sandbox_styles();
3791  
3792      foreach ( $styles as $style ) {
3793          $head .= '<link type="text/css" rel="stylesheet" href="' . $style . '">';
3794      }
3795  
3796      if ( ! empty( $wp_scripts ) ) {
3797          $wp_scripts->done = array();
3798      }
3799  
3800      ob_start();
3801  
3802      echo $parsed;
3803  
3804      if ( 'playlist' === $_REQUEST['type'] ) {
3805          wp_underscore_playlist_templates();
3806  
3807          wp_print_scripts( 'wp-playlist' );
3808      } else {
3809          wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
3810      }
3811  
3812      wp_send_json_success(
3813          array(
3814              'head' => $head,
3815              'body' => ob_get_clean(),
3816          )
3817      );
3818  }
3819  
3820  /**
3821   * Ajax handler for destroying multiple open sessions for a user.
3822   *
3823   * @since 4.1.0
3824   */
3825  function wp_ajax_destroy_sessions() {
3826      $user = get_userdata( (int) $_POST['user_id'] );
3827  
3828      if ( $user ) {
3829          if ( ! current_user_can( 'edit_user', $user->ID ) ) {
3830              $user = false;
3831          } elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
3832              $user = false;
3833          }
3834      }
3835  
3836      if ( ! $user ) {
3837          wp_send_json_error(
3838              array(
3839                  'message' => __( 'Could not log out user sessions. Please try again.' ),
3840              )
3841          );
3842      }
3843  
3844      $sessions = WP_Session_Tokens::get_instance( $user->ID );
3845  
3846      if ( get_current_user_id() === $user->ID ) {
3847          $sessions->destroy_others( wp_get_session_token() );
3848          $message = __( 'You are now logged out everywhere else.' );
3849      } else {
3850          $sessions->destroy_all();
3851          /* translators: %s: User's display name. */
3852          $message = sprintf( __( '%s has been logged out.' ), $user->display_name );
3853      }
3854  
3855      wp_send_json_success( array( 'message' => $message ) );
3856  }
3857  
3858  /**
3859   * Ajax handler for cropping an image.
3860   *
3861   * @since 4.3.0
3862   */
3863  function wp_ajax_crop_image() {
3864      $attachment_id = absint( $_POST['id'] );
3865  
3866      check_ajax_referer( 'image_editor-' . $attachment_id, 'nonce' );
3867  
3868      if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
3869          wp_send_json_error();
3870      }
3871  
3872      $context = str_replace( '_', '-', $_POST['context'] );
3873      $data    = array_map( 'absint', $_POST['cropDetails'] );
3874      $cropped = wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
3875  
3876      if ( ! $cropped || is_wp_error( $cropped ) ) {
3877          wp_send_json_error( array( 'message' => __( 'Image could not be processed.' ) ) );
3878      }
3879  
3880      switch ( $context ) {
3881          case 'site-icon':
3882              require_once ABSPATH . 'wp-admin/includes/class-wp-site-icon.php';
3883              $wp_site_icon = new WP_Site_Icon();
3884  
3885              // Skip creating a new attachment if the attachment is a Site Icon.
3886              if ( get_post_meta( $attachment_id, '_wp_attachment_context', true ) == $context ) {
3887  
3888                  // Delete the temporary cropped file, we don't need it.
3889                  wp_delete_file( $cropped );
3890  
3891                  // Additional sizes in wp_prepare_attachment_for_js().
3892                  add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
3893                  break;
3894              }
3895  
3896              /** This filter is documented in wp-admin/includes/class-custom-image-header.php */
3897              $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
3898              $object  = $wp_site_icon->create_attachment_object( $cropped, $attachment_id );
3899              unset( $object['ID'] );
3900  
3901              // Update the attachment.
3902              add_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
3903              $attachment_id = $wp_site_icon->insert_attachment( $object, $cropped );
3904              remove_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
3905  
3906              // Additional sizes in wp_prepare_attachment_for_js().
3907              add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
3908              break;
3909  
3910          default:
3911              /**
3912               * Fires before a cropped image is saved.
3913               *
3914               * Allows to add filters to modify the way a cropped image is saved.
3915               *
3916               * @since 4.3.0
3917               *
3918               * @param string $context       The Customizer control requesting the cropped image.
3919               * @param int    $attachment_id The attachment ID of the original image.
3920               * @param string $cropped       Path to the cropped image file.
3921               */
3922              do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
3923  
3924              /** This filter is documented in wp-admin/includes/class-custom-image-header.php */
3925              $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
3926  
3927              $parent_url = wp_get_attachment_url( $attachment_id );
3928              $url        = str_replace( wp_basename( $parent_url ), wp_basename( $cropped ), $parent_url );
3929  
3930              $size       = @getimagesize( $cropped );
3931              $image_type = ( $size ) ? $size['mime'] : 'image/jpeg';
3932  
3933              $object = array(
3934                  'post_title'     => wp_basename( $cropped ),
3935                  'post_content'   => $url,
3936                  'post_mime_type' => $image_type,
3937                  'guid'           => $url,
3938                  'context'        => $context,
3939              );
3940  
3941              $attachment_id = wp_insert_attachment( $object, $cropped );
3942              $metadata      = wp_generate_attachment_metadata( $attachment_id, $cropped );
3943  
3944              /**
3945               * Filters the cropped image attachment metadata.
3946               *
3947               * @since 4.3.0
3948               *
3949               * @see wp_generate_attachment_metadata()
3950               *
3951               * @param array $metadata Attachment metadata.
3952               */
3953              $metadata = apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
3954              wp_update_attachment_metadata( $attachment_id, $metadata );
3955  
3956              /**
3957               * Filters the attachment ID for a cropped image.
3958               *
3959               * @since 4.3.0
3960               *
3961               * @param int    $attachment_id The attachment ID of the cropped image.
3962               * @param string $context       The Customizer control requesting the cropped image.
3963               */
3964              $attachment_id = apply_filters( 'wp_ajax_cropped_attachment_id', $attachment_id, $context );
3965      }
3966  
3967      wp_send_json_success( wp_prepare_attachment_for_js( $attachment_id ) );
3968  }
3969  
3970  /**
3971   * Ajax handler for generating a password.
3972   *
3973   * @since 4.4.0
3974   */
3975  function wp_ajax_generate_password() {
3976      wp_send_json_success( wp_generate_password( 24 ) );
3977  }
3978  
3979  /**
3980   * Ajax handler for saving the user's WordPress.org username.
3981   *
3982   * @since 4.4.0
3983   */
3984  function wp_ajax_save_wporg_username() {
3985      if ( ! current_user_can( 'install_themes' ) && ! current_user_can( 'install_plugins' ) ) {
3986          wp_send_json_error();
3987      }
3988  
3989      check_ajax_referer( 'save_wporg_username_' . get_current_user_id() );
3990  
3991      $username = isset( $_REQUEST['username'] ) ? wp_unslash( $_REQUEST['username'] ) : false;
3992  
3993      if ( ! $username ) {
3994          wp_send_json_error();
3995      }
3996  
3997      wp_send_json_success( update_user_meta( get_current_user_id(), 'wporg_favorites', $username ) );
3998  }
3999  
4000  /**
4001   * Ajax handler for installing a theme.
4002   *
4003   * @since 4.6.0
4004   *
4005   * @see Theme_Upgrader
4006   *
4007   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4008   */
4009  function wp_ajax_install_theme() {
4010      check_ajax_referer( 'updates' );
4011  
4012      if ( empty( $_POST['slug'] ) ) {
4013          wp_send_json_error(
4014              array(
4015                  'slug'         => '',
4016                  'errorCode'    => 'no_theme_specified',
4017                  'errorMessage' => __( 'No theme specified.' ),
4018              )
4019          );
4020      }
4021  
4022      $slug = sanitize_key( wp_unslash( $_POST['slug'] ) );
4023  
4024      $status = array(
4025          'install' => 'theme',
4026          'slug'    => $slug,
4027      );
4028  
4029      if ( ! current_user_can( 'install_themes' ) ) {
4030          $status['errorMessage'] = __( 'Sorry, you are not allowed to install themes on this site.' );
4031          wp_send_json_error( $status );
4032      }
4033  
4034      require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4035      include_once ABSPATH . 'wp-admin/includes/theme.php';
4036  
4037      $api = themes_api(
4038          'theme_information',
4039          array(
4040              'slug'   => $slug,
4041              'fields' => array( 'sections' => false ),
4042          )
4043      );
4044  
4045      if ( is_wp_error( $api ) ) {
4046          $status['errorMessage'] = $api->get_error_message();
4047          wp_send_json_error( $status );
4048      }
4049  
4050      $skin     = new WP_Ajax_Upgrader_Skin();
4051      $upgrader = new Theme_Upgrader( $skin );
4052      $result   = $upgrader->install( $api->download_link );
4053  
4054      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4055          $status['debug'] = $skin->get_upgrade_messages();
4056      }
4057  
4058      if ( is_wp_error( $result ) ) {
4059          $status['errorCode']    = $result->get_error_code();
4060          $status['errorMessage'] = $result->get_error_message();
4061          wp_send_json_error( $status );
4062      } elseif ( is_wp_error( $skin->result ) ) {
4063          $status['errorCode']    = $skin->result->get_error_code();
4064          $status['errorMessage'] = $skin->result->get_error_message();
4065          wp_send_json_error( $status );
4066      } elseif ( $skin->get_errors()->has_errors() ) {
4067          $status['errorMessage'] = $skin->get_error_messages();
4068          wp_send_json_error( $status );
4069      } elseif ( is_null( $result ) ) {
4070          global $wp_filesystem;
4071  
4072          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4073          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4074  
4075          // Pass through the error from WP_Filesystem if one was raised.
4076          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4077              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4078          }
4079  
4080          wp_send_json_error( $status );
4081      }
4082  
4083      $status['themeName'] = wp_get_theme( $slug )->get( 'Name' );
4084  
4085      if ( current_user_can( 'switch_themes' ) ) {
4086          if ( is_multisite() ) {
4087              $status['activateUrl'] = add_query_arg(
4088                  array(
4089                      'action'   => 'enable',
4090                      '_wpnonce' => wp_create_nonce( 'enable-theme_' . $slug ),
4091                      'theme'    => $slug,
4092                  ),
4093                  network_admin_url( 'themes.php' )
4094              );
4095          } else {
4096              $status['activateUrl'] = add_query_arg(
4097                  array(
4098                      'action'     => 'activate',
4099                      '_wpnonce'   => wp_create_nonce( 'switch-theme_' . $slug ),
4100                      'stylesheet' => $slug,
4101                  ),
4102                  admin_url( 'themes.php' )
4103              );
4104          }
4105      }
4106  
4107      if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
4108          $status['customizeUrl'] = add_query_arg(
4109              array(
4110                  'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
4111              ),
4112              wp_customize_url( $slug )
4113          );
4114      }
4115  
4116      /*
4117       * See WP_Theme_Install_List_Table::_get_theme_status() if we wanted to check
4118       * on post-installation status.
4119       */
4120      wp_send_json_success( $status );
4121  }
4122  
4123  /**
4124   * Ajax handler for updating a theme.
4125   *
4126   * @since 4.6.0
4127   *
4128   * @see Theme_Upgrader
4129   *
4130   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4131   */
4132  function wp_ajax_update_theme() {
4133      check_ajax_referer( 'updates' );
4134  
4135      if ( empty( $_POST['slug'] ) ) {
4136          wp_send_json_error(
4137              array(
4138                  'slug'         => '',
4139                  'errorCode'    => 'no_theme_specified',
4140                  'errorMessage' => __( 'No theme specified.' ),
4141              )
4142          );
4143      }
4144  
4145      $stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
4146      $status     = array(
4147          'update'     => 'theme',
4148          'slug'       => $stylesheet,
4149          'oldVersion' => '',
4150          'newVersion' => '',
4151      );
4152  
4153      if ( ! current_user_can( 'update_themes' ) ) {
4154          $status['errorMessage'] = __( 'Sorry, you are not allowed to update themes for this site.' );
4155          wp_send_json_error( $status );
4156      }
4157  
4158      $theme = wp_get_theme( $stylesheet );
4159      if ( $theme->exists() ) {
4160          $status['oldVersion'] = $theme->get( 'Version' );
4161      }
4162  
4163      require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4164  
4165      $current = get_site_transient( 'update_themes' );
4166      if ( empty( $current ) ) {
4167          wp_update_themes();
4168      }
4169  
4170      $skin     = new WP_Ajax_Upgrader_Skin();
4171      $upgrader = new Theme_Upgrader( $skin );
4172      $result   = $upgrader->bulk_upgrade( array( $stylesheet ) );
4173  
4174      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4175          $status['debug'] = $skin->get_upgrade_messages();
4176      }
4177  
4178      if ( is_wp_error( $skin->result ) ) {
4179          $status['errorCode']    = $skin->result->get_error_code();
4180          $status['errorMessage'] = $skin->result->get_error_message();
4181          wp_send_json_error( $status );
4182      } elseif ( $skin->get_errors()->has_errors() ) {
4183          $status['errorMessage'] = $skin->get_error_messages();
4184          wp_send_json_error( $status );
4185      } elseif ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) {
4186  
4187          // Theme is already at the latest version.
4188          if ( true === $result[ $stylesheet ] ) {
4189              $status['errorMessage'] = $upgrader->strings['up_to_date'];
4190              wp_send_json_error( $status );
4191          }
4192  
4193          $theme = wp_get_theme( $stylesheet );
4194          if ( $theme->exists() ) {
4195              $status['newVersion'] = $theme->get( 'Version' );
4196          }
4197  
4198          wp_send_json_success( $status );
4199      } elseif ( false === $result ) {
4200          global $wp_filesystem;
4201  
4202          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4203          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4204  
4205          // Pass through the error from WP_Filesystem if one was raised.
4206          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4207              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4208          }
4209  
4210          wp_send_json_error( $status );
4211      }
4212  
4213      // An unhandled error occurred.
4214      $status['errorMessage'] = __( 'Theme update failed.' );
4215      wp_send_json_error( $status );
4216  }
4217  
4218  /**
4219   * Ajax handler for deleting a theme.
4220   *
4221   * @since 4.6.0
4222   *
4223   * @see delete_theme()
4224   *
4225   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4226   */
4227  function wp_ajax_delete_theme() {
4228      check_ajax_referer( 'updates' );
4229  
4230      if ( empty( $_POST['slug'] ) ) {
4231          wp_send_json_error(
4232              array(
4233                  'slug'         => '',
4234                  'errorCode'    => 'no_theme_specified',
4235                  'errorMessage' => __( 'No theme specified.' ),
4236              )
4237          );
4238      }
4239  
4240      $stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
4241      $status     = array(
4242          'delete' => 'theme',
4243          'slug'   => $stylesheet,
4244      );
4245  
4246      if ( ! current_user_can( 'delete_themes' ) ) {
4247          $status['errorMessage'] = __( 'Sorry, you are not allowed to delete themes on this site.' );
4248          wp_send_json_error( $status );
4249      }
4250  
4251      if ( ! wp_get_theme( $stylesheet )->exists() ) {
4252          $status['errorMessage'] = __( 'The requested theme does not exist.' );
4253          wp_send_json_error( $status );
4254      }
4255  
4256      // Check filesystem credentials. `delete_theme()` will bail otherwise.
4257      $url = wp_nonce_url( 'themes.php?action=delete&stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet );
4258  
4259      ob_start();
4260      $credentials = request_filesystem_credentials( $url );
4261      ob_end_clean();
4262  
4263      if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
4264          global $wp_filesystem;
4265  
4266          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4267          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4268  
4269          // Pass through the error from WP_Filesystem if one was raised.
4270          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4271              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4272          }
4273  
4274          wp_send_json_error( $status );
4275      }
4276  
4277      include_once ABSPATH . 'wp-admin/includes/theme.php';
4278  
4279      $result = delete_theme( $stylesheet );
4280  
4281      if ( is_wp_error( $result ) ) {
4282          $status['errorMessage'] = $result->get_error_message();
4283          wp_send_json_error( $status );
4284      } elseif ( false === $result ) {
4285          $status['errorMessage'] = __( 'Theme could not be deleted.' );
4286          wp_send_json_error( $status );
4287      }
4288  
4289      wp_send_json_success( $status );
4290  }
4291  
4292  /**
4293   * Ajax handler for installing a plugin.
4294   *
4295   * @since 4.6.0
4296   *
4297   * @see Plugin_Upgrader
4298   *
4299   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4300   */
4301  function wp_ajax_install_plugin() {
4302      check_ajax_referer( 'updates' );
4303  
4304      if ( empty( $_POST['slug'] ) ) {
4305          wp_send_json_error(
4306              array(
4307                  'slug'         => '',
4308                  'errorCode'    => 'no_plugin_specified',
4309                  'errorMessage' => __( 'No plugin specified.' ),
4310              )
4311          );
4312      }
4313  
4314      $status = array(
4315          'install' => 'plugin',
4316          'slug'    => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4317      );
4318  
4319      if ( ! current_user_can( 'install_plugins' ) ) {
4320          $status['errorMessage'] = __( 'Sorry, you are not allowed to install plugins on this site.' );
4321          wp_send_json_error( $status );
4322      }
4323  
4324      require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4325      include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
4326  
4327      $api = plugins_api(
4328          'plugin_information',
4329          array(
4330              'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4331              'fields' => array(
4332                  'sections' => false,
4333              ),
4334          )
4335      );
4336  
4337      if ( is_wp_error( $api ) ) {
4338          $status['errorMessage'] = $api->get_error_message();
4339          wp_send_json_error( $status );
4340      }
4341  
4342      $status['pluginName'] = $api->name;
4343  
4344      $skin     = new WP_Ajax_Upgrader_Skin();
4345      $upgrader = new Plugin_Upgrader( $skin );
4346      $result   = $upgrader->install( $api->download_link );
4347  
4348      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4349          $status['debug'] = $skin->get_upgrade_messages();
4350      }
4351  
4352      if ( is_wp_error( $result ) ) {
4353          $status['errorCode']    = $result->get_error_code();
4354          $status['errorMessage'] = $result->get_error_message();
4355          wp_send_json_error( $status );
4356      } elseif ( is_wp_error( $skin->result ) ) {
4357          $status['errorCode']    = $skin->result->get_error_code();
4358          $status['errorMessage'] = $skin->result->get_error_message();
4359          wp_send_json_error( $status );
4360      } elseif ( $skin->get_errors()->has_errors() ) {
4361          $status['errorMessage'] = $skin->get_error_messages();
4362          wp_send_json_error( $status );
4363      } elseif ( is_null( $result ) ) {
4364          global $wp_filesystem;
4365  
4366          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4367          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4368  
4369          // Pass through the error from WP_Filesystem if one was raised.
4370          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4371              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4372          }
4373  
4374          wp_send_json_error( $status );
4375      }
4376  
4377      $install_status = install_plugin_install_status( $api );
4378      $pagenow        = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
4379  
4380      // If installation request is coming from import page, do not return network activation link.
4381      $plugins_url = ( 'import' === $pagenow ) ? admin_url( 'plugins.php' ) : network_admin_url( 'plugins.php' );
4382  
4383      if ( current_user_can( 'activate_plugin', $install_status['file'] ) && is_plugin_inactive( $install_status['file'] ) ) {
4384          $status['activateUrl'] = add_query_arg(
4385              array(
4386                  '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ),
4387                  'action'   => 'activate',
4388                  'plugin'   => $install_status['file'],
4389              ),
4390              $plugins_url
4391          );
4392      }
4393  
4394      if ( is_multisite() && current_user_can( 'manage_network_plugins' ) && 'import' !== $pagenow ) {
4395          $status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] );
4396      }
4397  
4398      wp_send_json_success( $status );
4399  }
4400  
4401  /**
4402   * Ajax handler for updating a plugin.
4403   *
4404   * @since 4.2.0
4405   *
4406   * @see Plugin_Upgrader
4407   *
4408   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4409   */
4410  function wp_ajax_update_plugin() {
4411      check_ajax_referer( 'updates' );
4412  
4413      if ( empty( $_POST['plugin'] ) || empty( $_POST['slug'] ) ) {
4414          wp_send_json_error(
4415              array(
4416                  'slug'         => '',
4417                  'errorCode'    => 'no_plugin_specified',
4418                  'errorMessage' => __( 'No plugin specified.' ),
4419              )
4420          );
4421      }
4422  
4423      $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
4424  
4425      $status = array(
4426          'update'     => 'plugin',
4427          'slug'       => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4428          'oldVersion' => '',
4429          'newVersion' => '',
4430      );
4431  
4432      if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) {
4433          $status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
4434          wp_send_json_error( $status );
4435      }
4436  
4437      $plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4438      $status['plugin']     = $plugin;
4439      $status['pluginName'] = $plugin_data['Name'];
4440  
4441      if ( $plugin_data['Version'] ) {
4442          /* translators: %s: Plugin version. */
4443          $status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
4444      }
4445  
4446      require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4447  
4448      wp_update_plugins();
4449  
4450      $skin     = new WP_Ajax_Upgrader_Skin();
4451      $upgrader = new Plugin_Upgrader( $skin );
4452      $result   = $upgrader->bulk_upgrade( array( $plugin ) );
4453  
4454      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4455          $status['debug'] = $skin->get_upgrade_messages();
4456      }
4457  
4458      if ( is_wp_error( $skin->result ) ) {
4459          $status['errorCode']    = $skin->result->get_error_code();
4460          $status['errorMessage'] = $skin->result->get_error_message();
4461          wp_send_json_error( $status );
4462      } elseif ( $skin->get_errors()->has_errors() ) {
4463          $status['errorMessage'] = $skin->get_error_messages();
4464          wp_send_json_error( $status );
4465      } elseif ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) {
4466  
4467          /*
4468           * Plugin is already at the latest version.
4469           *
4470           * This may also be the return value if the `update_plugins` site transient is empty,
4471           * e.g. when you update two plugins in quick succession before the transient repopulates.
4472           *
4473           * Preferably something can be done to ensure `update_plugins` isn't empty.
4474           * For now, surface some sort of error here.
4475           */
4476          if ( true === $result[ $plugin ] ) {
4477              $status['errorMessage'] = $upgrader->strings['up_to_date'];
4478              wp_send_json_error( $status );
4479          }
4480  
4481          $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
4482          $plugin_data = reset( $plugin_data );
4483  
4484          if ( $plugin_data['Version'] ) {
4485              /* translators: %s: Plugin version. */
4486              $status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
4487          }
4488  
4489          wp_send_json_success( $status );
4490      } elseif ( false === $result ) {
4491          global $wp_filesystem;
4492  
4493          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4494          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4495  
4496          // Pass through the error from WP_Filesystem if one was raised.
4497          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4498              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4499          }
4500  
4501          wp_send_json_error( $status );
4502      }
4503  
4504      // An unhandled error occurred.
4505      $status['errorMessage'] = __( 'Plugin update failed.' );
4506      wp_send_json_error( $status );
4507  }
4508  
4509  /**
4510   * Ajax handler for deleting a plugin.
4511   *
4512   * @since 4.6.0
4513   *
4514   * @see delete_plugins()
4515   *
4516   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4517   */
4518  function wp_ajax_delete_plugin() {
4519      check_ajax_referer( 'updates' );
4520  
4521      if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
4522          wp_send_json_error(
4523              array(
4524                  'slug'         => '',
4525                  'errorCode'    => 'no_plugin_specified',
4526                  'errorMessage' => __( 'No plugin specified.' ),
4527              )
4528          );
4529      }
4530  
4531      $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
4532  
4533      $status = array(
4534          'delete' => 'plugin',
4535          'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4536      );
4537  
4538      if ( ! current_user_can( 'delete_plugins' ) || 0 !== validate_file( $plugin ) ) {
4539          $status['errorMessage'] = __( 'Sorry, you are not allowed to delete plugins for this site.' );
4540          wp_send_json_error( $status );
4541      }
4542  
4543      $plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4544      $status['plugin']     = $plugin;
4545      $status['pluginName'] = $plugin_data['Name'];
4546  
4547      if ( is_plugin_active( $plugin ) ) {
4548          $status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' );
4549          wp_send_json_error( $status );
4550      }
4551  
4552      // Check filesystem credentials. `delete_plugins()` will bail otherwise.
4553      $url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin, 'bulk-plugins' );
4554  
4555      ob_start();
4556      $credentials = request_filesystem_credentials( $url );
4557      ob_end_clean();
4558  
4559      if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
4560          global