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