[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

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

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