[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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