[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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