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