[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

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