[ 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   * Handles the Heartbeat API in the no-privilege context via AJAX .
  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   * Handles fetching a list table via AJAX.
  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   * Handles tag search via AJAX.
 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 ( str_contains( $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   * Handles compression testing via AJAX.
 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          // Use `update_option()` on single site to mark the option for autoloading.
 192          if ( is_multisite() ) {
 193              update_site_option( 'can_compress_scripts', 0 );
 194          } else {
 195              update_option( 'can_compress_scripts', 0, true );
 196          }
 197          wp_die( 0 );
 198      }
 199  
 200      if ( isset( $_GET['test'] ) ) {
 201          header( 'Expires: Wed, 11 Jan 1984 05:00:00 GMT' );
 202          header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s' ) . ' GMT' );
 203          header( 'Cache-Control: no-cache, must-revalidate, max-age=0' );
 204          header( 'Content-Type: application/javascript; charset=UTF-8' );
 205          $force_gzip = ( defined( 'ENFORCE_GZIP' ) && ENFORCE_GZIP );
 206          $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."';
 207  
 208          if ( '1' === $_GET['test'] ) {
 209              echo $test_str;
 210              wp_die();
 211          } elseif ( '2' === $_GET['test'] ) {
 212              if ( ! isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
 213                  wp_die( -1 );
 214              }
 215  
 216              if ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate' ) && function_exists( 'gzdeflate' ) && ! $force_gzip ) {
 217                  header( 'Content-Encoding: deflate' );
 218                  $out = gzdeflate( $test_str, 1 );
 219              } elseif ( false !== stripos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip' ) && function_exists( 'gzencode' ) ) {
 220                  header( 'Content-Encoding: gzip' );
 221                  $out = gzencode( $test_str, 1 );
 222              } else {
 223                  wp_die( -1 );
 224              }
 225  
 226              echo $out;
 227              wp_die();
 228          } elseif ( 'no' === $_GET['test'] ) {
 229              check_ajax_referer( 'update_can_compress_scripts' );
 230              // Use `update_option()` on single site to mark the option for autoloading.
 231              if ( is_multisite() ) {
 232                  update_site_option( 'can_compress_scripts', 0 );
 233              } else {
 234                  update_option( 'can_compress_scripts', 0, true );
 235              }
 236          } elseif ( 'yes' === $_GET['test'] ) {
 237              check_ajax_referer( 'update_can_compress_scripts' );
 238              // Use `update_option()` on single site to mark the option for autoloading.
 239              if ( is_multisite() ) {
 240                  update_site_option( 'can_compress_scripts', 1 );
 241              } else {
 242                  update_option( 'can_compress_scripts', 1, true );
 243              }
 244          }
 245      }
 246  
 247      wp_die( 0 );
 248  }
 249  
 250  /**
 251   * Handles image editor previews via AJAX.
 252   *
 253   * @since 3.1.0
 254   */
 255  function wp_ajax_imgedit_preview() {
 256      $post_id = (int) $_GET['postid'];
 257      if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) {
 258          wp_die( -1 );
 259      }
 260  
 261      check_ajax_referer( "image_editor-$post_id" );
 262  
 263      require_once  ABSPATH . 'wp-admin/includes/image-edit.php';
 264  
 265      if ( ! stream_preview_image( $post_id ) ) {
 266          wp_die( -1 );
 267      }
 268  
 269      wp_die();
 270  }
 271  
 272  /**
 273   * Handles oEmbed caching via AJAX.
 274   *
 275   * @since 3.1.0
 276   *
 277   * @global WP_Embed $wp_embed WordPress Embed object.
 278   */
 279  function wp_ajax_oembed_cache() {
 280      $GLOBALS['wp_embed']->cache_oembed( $_GET['post'] );
 281      wp_die( 0 );
 282  }
 283  
 284  /**
 285   * Handles user autocomplete via AJAX.
 286   *
 287   * @since 3.4.0
 288   */
 289  function wp_ajax_autocomplete_user() {
 290      if ( ! is_multisite() || ! current_user_can( 'promote_users' ) || wp_is_large_network( 'users' ) ) {
 291          wp_die( -1 );
 292      }
 293  
 294      /** This filter is documented in wp-admin/user-new.php */
 295      if ( ! current_user_can( 'manage_network_users' ) && ! apply_filters( 'autocomplete_users_for_site_admins', false ) ) {
 296          wp_die( -1 );
 297      }
 298  
 299      $return = array();
 300  
 301      /*
 302       * Check the type of request.
 303       * Current allowed values are `add` and `search`.
 304       */
 305      if ( isset( $_REQUEST['autocomplete_type'] ) && 'search' === $_REQUEST['autocomplete_type'] ) {
 306          $type = $_REQUEST['autocomplete_type'];
 307      } else {
 308          $type = 'add';
 309      }
 310  
 311      /*
 312       * Check the desired field for value.
 313       * Current allowed values are `user_email` and `user_login`.
 314       */
 315      if ( isset( $_REQUEST['autocomplete_field'] ) && 'user_email' === $_REQUEST['autocomplete_field'] ) {
 316          $field = $_REQUEST['autocomplete_field'];
 317      } else {
 318          $field = 'user_login';
 319      }
 320  
 321      // Exclude current users of this blog.
 322      if ( isset( $_REQUEST['site_id'] ) ) {
 323          $id = absint( $_REQUEST['site_id'] );
 324      } else {
 325          $id = get_current_blog_id();
 326      }
 327  
 328      $include_blog_users = ( 'search' === $type ? get_users(
 329          array(
 330              'blog_id' => $id,
 331              'fields'  => 'ID',
 332          )
 333      ) : array() );
 334  
 335      $exclude_blog_users = ( 'add' === $type ? get_users(
 336          array(
 337              'blog_id' => $id,
 338              'fields'  => 'ID',
 339          )
 340      ) : array() );
 341  
 342      $users = get_users(
 343          array(
 344              'blog_id'        => false,
 345              'search'         => '*' . $_REQUEST['term'] . '*',
 346              'include'        => $include_blog_users,
 347              'exclude'        => $exclude_blog_users,
 348              'search_columns' => array( 'user_login', 'user_nicename', 'user_email' ),
 349          )
 350      );
 351  
 352      foreach ( $users as $user ) {
 353          $return[] = array(
 354              /* translators: 1: User login, 2: User email address. */
 355              'label' => sprintf( _x( '%1$s (%2$s)', 'user autocomplete result' ), $user->user_login, $user->user_email ),
 356              'value' => $user->$field,
 357          );
 358      }
 359  
 360      wp_die( wp_json_encode( $return ) );
 361  }
 362  
 363  /**
 364   * Handles Ajax requests for community events
 365   *
 366   * @since 4.8.0
 367   */
 368  function wp_ajax_get_community_events() {
 369      require_once  ABSPATH . 'wp-admin/includes/class-wp-community-events.php';
 370  
 371      check_ajax_referer( 'community_events' );
 372  
 373      $search         = isset( $_POST['location'] ) ? wp_unslash( $_POST['location'] ) : '';
 374      $timezone       = isset( $_POST['timezone'] ) ? wp_unslash( $_POST['timezone'] ) : '';
 375      $user_id        = get_current_user_id();
 376      $saved_location = get_user_option( 'community-events-location', $user_id );
 377      $events_client  = new WP_Community_Events( $user_id, $saved_location );
 378      $events         = $events_client->get_events( $search, $timezone );
 379      $ip_changed     = false;
 380  
 381      if ( is_wp_error( $events ) ) {
 382          wp_send_json_error(
 383              array(
 384                  'error' => $events->get_error_message(),
 385              )
 386          );
 387      } else {
 388          if ( empty( $saved_location['ip'] ) && ! empty( $events['location']['ip'] ) ) {
 389              $ip_changed = true;
 390          } elseif ( isset( $saved_location['ip'] ) && ! empty( $events['location']['ip'] ) && $saved_location['ip'] !== $events['location']['ip'] ) {
 391              $ip_changed = true;
 392          }
 393  
 394          /*
 395           * The location should only be updated when it changes. The API doesn't always return
 396           * a full location; sometimes it's missing the description or country. The location
 397           * that was saved during the initial request is known to be good and complete, though.
 398           * It should be left intact until the user explicitly changes it (either by manually
 399           * searching for a new location, or by changing their IP address).
 400           *
 401           * If the location was updated with an incomplete response from the API, then it could
 402           * break assumptions that the UI makes (e.g., that there will always be a description
 403           * that corresponds to a latitude/longitude location).
 404           *
 405           * The location is stored network-wide, so that the user doesn't have to set it on each site.
 406           */
 407          if ( $ip_changed || $search ) {
 408              update_user_meta( $user_id, 'community-events-location', $events['location'] );
 409          }
 410  
 411          wp_send_json_success( $events );
 412      }
 413  }
 414  
 415  /**
 416   * Handles dashboard widgets via AJAX.
 417   *
 418   * @since 3.4.0
 419   */
 420  function wp_ajax_dashboard_widgets() {
 421      require_once  ABSPATH . 'wp-admin/includes/dashboard.php';
 422  
 423      $pagenow = $_GET['pagenow'];
 424      if ( 'dashboard-user' === $pagenow || 'dashboard-network' === $pagenow || 'dashboard' === $pagenow ) {
 425          set_current_screen( $pagenow );
 426      }
 427  
 428      switch ( $_GET['widget'] ) {
 429          case 'dashboard_primary':
 430              wp_dashboard_primary();
 431              break;
 432      }
 433      wp_die();
 434  }
 435  
 436  /**
 437   * Handles Customizer preview logged-in status via AJAX.
 438   *
 439   * @since 3.4.0
 440   */
 441  function wp_ajax_logged_in() {
 442      wp_die( 1 );
 443  }
 444  
 445  //
 446  // Ajax helpers.
 447  //
 448  
 449  /**
 450   * Sends back current comment total and new page links if they need to be updated.
 451   *
 452   * Contrary to normal success Ajax response ("1"), die with time() on success.
 453   *
 454   * @since 2.7.0
 455   * @access private
 456   *
 457   * @param int $comment_id
 458   * @param int $delta
 459   */
 460  function _wp_ajax_delete_comment_response( $comment_id, $delta = -1 ) {
 461      $total    = isset( $_POST['_total'] ) ? (int) $_POST['_total'] : 0;
 462      $per_page = isset( $_POST['_per_page'] ) ? (int) $_POST['_per_page'] : 0;
 463      $page     = isset( $_POST['_page'] ) ? (int) $_POST['_page'] : 0;
 464      $url      = isset( $_POST['_url'] ) ? sanitize_url( $_POST['_url'] ) : '';
 465  
 466      // JS didn't send us everything we need to know. Just die with success message.
 467      if ( ! $total || ! $per_page || ! $page || ! $url ) {
 468          $time           = time();
 469          $comment        = get_comment( $comment_id );
 470          $comment_status = '';
 471          $comment_link   = '';
 472  
 473          if ( $comment ) {
 474              $comment_status = $comment->comment_approved;
 475          }
 476  
 477          if ( 1 === (int) $comment_status ) {
 478              $comment_link = get_comment_link( $comment );
 479          }
 480  
 481          $counts = wp_count_comments();
 482  
 483          $x = new WP_Ajax_Response(
 484              array(
 485                  'what'         => 'comment',
 486                  // Here for completeness - not used.
 487                  'id'           => $comment_id,
 488                  'supplemental' => array(
 489                      'status'               => $comment_status,
 490                      'postId'               => $comment ? $comment->comment_post_ID : '',
 491                      'time'                 => $time,
 492                      'in_moderation'        => $counts->moderated,
 493                      'i18n_comments_text'   => sprintf(
 494                          /* translators: %s: Number of comments. */
 495                          _n( '%s Comment', '%s Comments', $counts->approved ),
 496                          number_format_i18n( $counts->approved )
 497                      ),
 498                      'i18n_moderation_text' => sprintf(
 499                          /* translators: %s: Number of comments. */
 500                          _n( '%s Comment in moderation', '%s Comments in moderation', $counts->moderated ),
 501                          number_format_i18n( $counts->moderated )
 502                      ),
 503                      'comment_link'         => $comment_link,
 504                  ),
 505              )
 506          );
 507          $x->send();
 508      }
 509  
 510      $total += $delta;
 511      if ( $total < 0 ) {
 512          $total = 0;
 513      }
 514  
 515      // Only do the expensive stuff on a page-break, and about 1 other time per page.
 516      if ( 0 === $total % $per_page || 1 === mt_rand( 1, $per_page ) ) {
 517          $post_id = 0;
 518          // What type of comment count are we looking for?
 519          $status = 'all';
 520          $parsed = parse_url( $url );
 521  
 522          if ( isset( $parsed['query'] ) ) {
 523              parse_str( $parsed['query'], $query_vars );
 524  
 525              if ( ! empty( $query_vars['comment_status'] ) ) {
 526                  $status = $query_vars['comment_status'];
 527              }
 528  
 529              if ( ! empty( $query_vars['p'] ) ) {
 530                  $post_id = (int) $query_vars['p'];
 531              }
 532  
 533              if ( ! empty( $query_vars['comment_type'] ) ) {
 534                  $type = $query_vars['comment_type'];
 535              }
 536          }
 537  
 538          if ( empty( $type ) ) {
 539              // Only use the comment count if not filtering by a comment_type.
 540              $comment_count = wp_count_comments( $post_id );
 541  
 542              // We're looking for a known type of comment count.
 543              if ( isset( $comment_count->$status ) ) {
 544                  $total = $comment_count->$status;
 545              }
 546          }
 547          // Else use the decremented value from above.
 548      }
 549  
 550      // The time since the last comment count.
 551      $time    = time();
 552      $comment = get_comment( $comment_id );
 553      $counts  = wp_count_comments();
 554  
 555      $x = new WP_Ajax_Response(
 556          array(
 557              'what'         => 'comment',
 558              'id'           => $comment_id,
 559              'supplemental' => array(
 560                  'status'               => $comment ? $comment->comment_approved : '',
 561                  'postId'               => $comment ? $comment->comment_post_ID : '',
 562                  /* translators: %s: Number of comments. */
 563                  'total_items_i18n'     => sprintf( _n( '%s item', '%s items', $total ), number_format_i18n( $total ) ),
 564                  'total_pages'          => (int) ceil( $total / $per_page ),
 565                  'total_pages_i18n'     => number_format_i18n( (int) ceil( $total / $per_page ) ),
 566                  'total'                => $total,
 567                  'time'                 => $time,
 568                  'in_moderation'        => $counts->moderated,
 569                  'i18n_moderation_text' => sprintf(
 570                      /* translators: %s: Number of comments. */
 571                      _n( '%s Comment in moderation', '%s Comments in moderation', $counts->moderated ),
 572                      number_format_i18n( $counts->moderated )
 573                  ),
 574              ),
 575          )
 576      );
 577      $x->send();
 578  }
 579  
 580  //
 581  // POST-based Ajax handlers.
 582  //
 583  
 584  /**
 585   * Handles adding a hierarchical term via AJAX.
 586   *
 587   * @since 3.1.0
 588   * @access private
 589   */
 590  function _wp_ajax_add_hierarchical_term() {
 591      $action   = $_POST['action'];
 592      $taxonomy = get_taxonomy( substr( $action, 4 ) );
 593      check_ajax_referer( $action, '_ajax_nonce-add-' . $taxonomy->name );
 594  
 595      if ( ! current_user_can( $taxonomy->cap->edit_terms ) ) {
 596          wp_die( -1 );
 597      }
 598  
 599      $names  = explode( ',', $_POST[ 'new' . $taxonomy->name ] );
 600      $parent = isset( $_POST[ 'new' . $taxonomy->name . '_parent' ] ) ? (int) $_POST[ 'new' . $taxonomy->name . '_parent' ] : 0;
 601  
 602      if ( 0 > $parent ) {
 603          $parent = 0;
 604      }
 605  
 606      if ( 'category' === $taxonomy->name ) {
 607          $post_category = isset( $_POST['post_category'] ) ? (array) $_POST['post_category'] : array();
 608      } else {
 609          $post_category = ( isset( $_POST['tax_input'] ) && isset( $_POST['tax_input'][ $taxonomy->name ] ) ) ? (array) $_POST['tax_input'][ $taxonomy->name ] : array();
 610      }
 611  
 612      $checked_categories = array_map( 'absint', (array) $post_category );
 613      $popular_ids        = wp_popular_terms_checklist( $taxonomy->name, 0, 10, false );
 614  
 615      foreach ( $names as $cat_name ) {
 616          $cat_name          = trim( $cat_name );
 617          $category_nicename = sanitize_title( $cat_name );
 618  
 619          if ( '' === $category_nicename ) {
 620              continue;
 621          }
 622  
 623          $cat_id = wp_insert_term( $cat_name, $taxonomy->name, array( 'parent' => $parent ) );
 624  
 625          if ( ! $cat_id || is_wp_error( $cat_id ) ) {
 626              continue;
 627          } else {
 628              $cat_id = $cat_id['term_id'];
 629          }
 630  
 631          $checked_categories[] = $cat_id;
 632  
 633          if ( $parent ) { // Do these all at once in a second.
 634              continue;
 635          }
 636  
 637          ob_start();
 638  
 639          wp_terms_checklist(
 640              0,
 641              array(
 642                  'taxonomy'             => $taxonomy->name,
 643                  'descendants_and_self' => $cat_id,
 644                  'selected_cats'        => $checked_categories,
 645                  'popular_cats'         => $popular_ids,
 646              )
 647          );
 648  
 649          $data = ob_get_clean();
 650  
 651          $add = array(
 652              'what'     => $taxonomy->name,
 653              'id'       => $cat_id,
 654              'data'     => str_replace( array( "\n", "\t" ), '', $data ),
 655              'position' => -1,
 656          );
 657      }
 658  
 659      if ( $parent ) { // Foncy - replace the parent and all its children.
 660          $parent  = get_term( $parent, $taxonomy->name );
 661          $term_id = $parent->term_id;
 662  
 663          while ( $parent->parent ) { // Get the top parent.
 664              $parent = get_term( $parent->parent, $taxonomy->name );
 665              if ( is_wp_error( $parent ) ) {
 666                  break;
 667              }
 668              $term_id = $parent->term_id;
 669          }
 670  
 671          ob_start();
 672  
 673          wp_terms_checklist(
 674              0,
 675              array(
 676                  'taxonomy'             => $taxonomy->name,
 677                  'descendants_and_self' => $term_id,
 678                  'selected_cats'        => $checked_categories,
 679                  'popular_cats'         => $popular_ids,
 680              )
 681          );
 682  
 683          $data = ob_get_clean();
 684  
 685          $add = array(
 686              'what'     => $taxonomy->name,
 687              'id'       => $term_id,
 688              'data'     => str_replace( array( "\n", "\t" ), '', $data ),
 689              'position' => -1,
 690          );
 691      }
 692  
 693      ob_start();
 694  
 695      wp_dropdown_categories(
 696          array(
 697              'taxonomy'         => $taxonomy->name,
 698              'hide_empty'       => 0,
 699              'name'             => 'new' . $taxonomy->name . '_parent',
 700              'orderby'          => 'name',
 701              'hierarchical'     => 1,
 702              'show_option_none' => '&mdash; ' . $taxonomy->labels->parent_item . ' &mdash;',
 703          )
 704      );
 705  
 706      $sup = ob_get_clean();
 707  
 708      $add['supplemental'] = array( 'newcat_parent' => $sup );
 709  
 710      $x = new WP_Ajax_Response( $add );
 711      $x->send();
 712  }
 713  
 714  /**
 715   * Handles deleting a comment via AJAX.
 716   *
 717   * @since 3.1.0
 718   */
 719  function wp_ajax_delete_comment() {
 720      $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
 721  
 722      $comment = get_comment( $id );
 723  
 724      if ( ! $comment ) {
 725          wp_die( time() );
 726      }
 727  
 728      if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) ) {
 729          wp_die( -1 );
 730      }
 731  
 732      check_ajax_referer( "delete-comment_$id" );
 733      $status = wp_get_comment_status( $comment );
 734      $delta  = -1;
 735  
 736      if ( isset( $_POST['trash'] ) && '1' === $_POST['trash'] ) {
 737          if ( 'trash' === $status ) {
 738              wp_die( time() );
 739          }
 740  
 741          $r = wp_trash_comment( $comment );
 742      } elseif ( isset( $_POST['untrash'] ) && '1' === $_POST['untrash'] ) {
 743          if ( 'trash' !== $status ) {
 744              wp_die( time() );
 745          }
 746  
 747          $r = wp_untrash_comment( $comment );
 748  
 749          // Undo trash, not in Trash.
 750          if ( ! isset( $_POST['comment_status'] ) || 'trash' !== $_POST['comment_status'] ) {
 751              $delta = 1;
 752          }
 753      } elseif ( isset( $_POST['spam'] ) && '1' === $_POST['spam'] ) {
 754          if ( 'spam' === $status ) {
 755              wp_die( time() );
 756          }
 757  
 758          $r = wp_spam_comment( $comment );
 759      } elseif ( isset( $_POST['unspam'] ) && '1' === $_POST['unspam'] ) {
 760          if ( 'spam' !== $status ) {
 761              wp_die( time() );
 762          }
 763  
 764          $r = wp_unspam_comment( $comment );
 765  
 766          // Undo spam, not in spam.
 767          if ( ! isset( $_POST['comment_status'] ) || 'spam' !== $_POST['comment_status'] ) {
 768              $delta = 1;
 769          }
 770      } elseif ( isset( $_POST['delete'] ) && '1' === $_POST['delete'] ) {
 771          $r = wp_delete_comment( $comment );
 772      } else {
 773          wp_die( -1 );
 774      }
 775  
 776      if ( $r ) {
 777          // Decide if we need to send back '1' or a more complicated response including page links and comment counts.
 778          _wp_ajax_delete_comment_response( $comment->comment_ID, $delta );
 779      }
 780  
 781      wp_die( 0 );
 782  }
 783  
 784  /**
 785   * Handles deleting a tag via AJAX.
 786   *
 787   * @since 3.1.0
 788   */
 789  function wp_ajax_delete_tag() {
 790      $tag_id = (int) $_POST['tag_ID'];
 791      check_ajax_referer( "delete-tag_$tag_id" );
 792  
 793      if ( ! current_user_can( 'delete_term', $tag_id ) ) {
 794          wp_die( -1 );
 795      }
 796  
 797      $taxonomy = ! empty( $_POST['taxonomy'] ) ? $_POST['taxonomy'] : 'post_tag';
 798      $tag      = get_term( $tag_id, $taxonomy );
 799  
 800      if ( ! $tag || is_wp_error( $tag ) ) {
 801          wp_die( 1 );
 802      }
 803  
 804      if ( wp_delete_term( $tag_id, $taxonomy ) ) {
 805          wp_die( 1 );
 806      } else {
 807          wp_die( 0 );
 808      }
 809  }
 810  
 811  /**
 812   * Handles deleting a link via AJAX.
 813   *
 814   * @since 3.1.0
 815   */
 816  function wp_ajax_delete_link() {
 817      $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
 818  
 819      check_ajax_referer( "delete-bookmark_$id" );
 820  
 821      if ( ! current_user_can( 'manage_links' ) ) {
 822          wp_die( -1 );
 823      }
 824  
 825      $link = get_bookmark( $id );
 826      if ( ! $link || is_wp_error( $link ) ) {
 827          wp_die( 1 );
 828      }
 829  
 830      if ( wp_delete_link( $id ) ) {
 831          wp_die( 1 );
 832      } else {
 833          wp_die( 0 );
 834      }
 835  }
 836  
 837  /**
 838   * Handles deleting meta via AJAX.
 839   *
 840   * @since 3.1.0
 841   */
 842  function wp_ajax_delete_meta() {
 843      $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
 844  
 845      check_ajax_referer( "delete-meta_$id" );
 846      $meta = get_metadata_by_mid( 'post', $id );
 847  
 848      if ( ! $meta ) {
 849          wp_die( 1 );
 850      }
 851  
 852      if ( is_protected_meta( $meta->meta_key, 'post' ) || ! current_user_can( 'delete_post_meta', $meta->post_id, $meta->meta_key ) ) {
 853          wp_die( -1 );
 854      }
 855  
 856      if ( delete_meta( $meta->meta_id ) ) {
 857          wp_die( 1 );
 858      }
 859  
 860      wp_die( 0 );
 861  }
 862  
 863  /**
 864   * Handles deleting a post via AJAX.
 865   *
 866   * @since 3.1.0
 867   *
 868   * @param string $action Action to perform.
 869   */
 870  function wp_ajax_delete_post( $action ) {
 871      if ( empty( $action ) ) {
 872          $action = 'delete-post';
 873      }
 874  
 875      $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
 876      check_ajax_referer( "{$action}_$id" );
 877  
 878      if ( ! current_user_can( 'delete_post', $id ) ) {
 879          wp_die( -1 );
 880      }
 881  
 882      if ( ! get_post( $id ) ) {
 883          wp_die( 1 );
 884      }
 885  
 886      if ( wp_delete_post( $id ) ) {
 887          wp_die( 1 );
 888      } else {
 889          wp_die( 0 );
 890      }
 891  }
 892  
 893  /**
 894   * Handles sending a post to the Trash via AJAX.
 895   *
 896   * @since 3.1.0
 897   *
 898   * @param string $action Action to perform.
 899   */
 900  function wp_ajax_trash_post( $action ) {
 901      if ( empty( $action ) ) {
 902          $action = 'trash-post';
 903      }
 904  
 905      $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
 906      check_ajax_referer( "{$action}_$id" );
 907  
 908      if ( ! current_user_can( 'delete_post', $id ) ) {
 909          wp_die( -1 );
 910      }
 911  
 912      if ( ! get_post( $id ) ) {
 913          wp_die( 1 );
 914      }
 915  
 916      if ( 'trash-post' === $action ) {
 917          $done = wp_trash_post( $id );
 918      } else {
 919          $done = wp_untrash_post( $id );
 920      }
 921  
 922      if ( $done ) {
 923          wp_die( 1 );
 924      }
 925  
 926      wp_die( 0 );
 927  }
 928  
 929  /**
 930   * Handles restoring a post from the Trash via AJAX.
 931   *
 932   * @since 3.1.0
 933   *
 934   * @param string $action Action to perform.
 935   */
 936  function wp_ajax_untrash_post( $action ) {
 937      if ( empty( $action ) ) {
 938          $action = 'untrash-post';
 939      }
 940  
 941      wp_ajax_trash_post( $action );
 942  }
 943  
 944  /**
 945   * Handles deleting a page via AJAX.
 946   *
 947   * @since 3.1.0
 948   *
 949   * @param string $action Action to perform.
 950   */
 951  function wp_ajax_delete_page( $action ) {
 952      if ( empty( $action ) ) {
 953          $action = 'delete-page';
 954      }
 955  
 956      $id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
 957      check_ajax_referer( "{$action}_$id" );
 958  
 959      if ( ! current_user_can( 'delete_page', $id ) ) {
 960          wp_die( -1 );
 961      }
 962  
 963      if ( ! get_post( $id ) ) {
 964          wp_die( 1 );
 965      }
 966  
 967      if ( wp_delete_post( $id ) ) {
 968          wp_die( 1 );
 969      } else {
 970          wp_die( 0 );
 971      }
 972  }
 973  
 974  /**
 975   * Handles dimming a comment via AJAX.
 976   *
 977   * @since 3.1.0
 978   */
 979  function wp_ajax_dim_comment() {
 980      $id      = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
 981      $comment = get_comment( $id );
 982  
 983      if ( ! $comment ) {
 984          $x = new WP_Ajax_Response(
 985              array(
 986                  'what' => 'comment',
 987                  'id'   => new WP_Error(
 988                      'invalid_comment',
 989                      /* translators: %d: Comment ID. */
 990                      sprintf( __( 'Comment %d does not exist' ), $id )
 991                  ),
 992              )
 993          );
 994          $x->send();
 995      }
 996  
 997      if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && ! current_user_can( 'moderate_comments' ) ) {
 998          wp_die( -1 );
 999      }
1000  
1001      $current = wp_get_comment_status( $comment );
1002  
1003      if ( isset( $_POST['new'] ) && $_POST['new'] === $current ) {
1004          wp_die( time() );
1005      }
1006  
1007      check_ajax_referer( "approve-comment_$id" );
1008  
1009      if ( in_array( $current, array( 'unapproved', 'spam' ), true ) ) {
1010          $result = wp_set_comment_status( $comment, 'approve', true );
1011      } else {
1012          $result = wp_set_comment_status( $comment, 'hold', true );
1013      }
1014  
1015      if ( is_wp_error( $result ) ) {
1016          $x = new WP_Ajax_Response(
1017              array(
1018                  'what' => 'comment',
1019                  'id'   => $result,
1020              )
1021          );
1022          $x->send();
1023      }
1024  
1025      // Decide if we need to send back '1' or a more complicated response including page links and comment counts.
1026      _wp_ajax_delete_comment_response( $comment->comment_ID );
1027      wp_die( 0 );
1028  }
1029  
1030  /**
1031   * Handles adding a link category via AJAX.
1032   *
1033   * @since 3.1.0
1034   *
1035   * @param string $action Action to perform.
1036   */
1037  function wp_ajax_add_link_category( $action ) {
1038      if ( empty( $action ) ) {
1039          $action = 'add-link-category';
1040      }
1041  
1042      check_ajax_referer( $action );
1043  
1044      $taxonomy_object = get_taxonomy( 'link_category' );
1045  
1046      if ( ! current_user_can( $taxonomy_object->cap->manage_terms ) ) {
1047          wp_die( -1 );
1048      }
1049  
1050      $names = explode( ',', wp_unslash( $_POST['newcat'] ) );
1051      $x     = new WP_Ajax_Response();
1052  
1053      foreach ( $names as $cat_name ) {
1054          $cat_name = trim( $cat_name );
1055          $slug     = sanitize_title( $cat_name );
1056  
1057          if ( '' === $slug ) {
1058              continue;
1059          }
1060  
1061          $cat_id = wp_insert_term( $cat_name, 'link_category' );
1062  
1063          if ( ! $cat_id || is_wp_error( $cat_id ) ) {
1064              continue;
1065          } else {
1066              $cat_id = $cat_id['term_id'];
1067          }
1068  
1069          $cat_name = esc_html( $cat_name );
1070  
1071          $x->add(
1072              array(
1073                  'what'     => 'link-category',
1074                  'id'       => $cat_id,
1075                  '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>",
1076                  'position' => -1,
1077              )
1078          );
1079      }
1080      $x->send();
1081  }
1082  
1083  /**
1084   * Handles adding a tag via AJAX.
1085   *
1086   * @since 3.1.0
1087   */
1088  function wp_ajax_add_tag() {
1089      check_ajax_referer( 'add-tag', '_wpnonce_add-tag' );
1090  
1091      $taxonomy        = ! empty( $_POST['taxonomy'] ) ? $_POST['taxonomy'] : 'post_tag';
1092      $taxonomy_object = get_taxonomy( $taxonomy );
1093  
1094      if ( ! current_user_can( $taxonomy_object->cap->edit_terms ) ) {
1095          wp_die( -1 );
1096      }
1097  
1098      $x = new WP_Ajax_Response();
1099  
1100      $tag = wp_insert_term( $_POST['tag-name'], $taxonomy, $_POST );
1101  
1102      if ( $tag && ! is_wp_error( $tag ) ) {
1103          $tag = get_term( $tag['term_id'], $taxonomy );
1104      }
1105  
1106      if ( ! $tag || is_wp_error( $tag ) ) {
1107          $message    = __( 'An error has occurred. Please reload the page and try again.' );
1108          $error_code = 'error';
1109  
1110          if ( is_wp_error( $tag ) && $tag->get_error_message() ) {
1111              $message = $tag->get_error_message();
1112          }
1113  
1114          if ( is_wp_error( $tag ) && $tag->get_error_code() ) {
1115              $error_code = $tag->get_error_code();
1116          }
1117  
1118          $x->add(
1119              array(
1120                  'what' => 'taxonomy',
1121                  'data' => new WP_Error( $error_code, $message ),
1122              )
1123          );
1124          $x->send();
1125      }
1126  
1127      $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => $_POST['screen'] ) );
1128  
1129      $level     = 0;
1130      $noparents = '';
1131  
1132      if ( is_taxonomy_hierarchical( $taxonomy ) ) {
1133          $level = count( get_ancestors( $tag->term_id, $taxonomy, 'taxonomy' ) );
1134          ob_start();
1135          $wp_list_table->single_row( $tag, $level );
1136          $noparents = ob_get_clean();
1137      }
1138  
1139      ob_start();
1140      $wp_list_table->single_row( $tag );
1141      $parents = ob_get_clean();
1142  
1143      require  ABSPATH . 'wp-admin/includes/edit-tag-messages.php';
1144  
1145      $message = '';
1146      if ( isset( $messages[ $taxonomy_object->name ][1] ) ) {
1147          $message = $messages[ $taxonomy_object->name ][1];
1148      } elseif ( isset( $messages['_item'][1] ) ) {
1149          $message = $messages['_item'][1];
1150      }
1151  
1152      $x->add(
1153          array(
1154              'what'         => 'taxonomy',
1155              'data'         => $message,
1156              'supplemental' => array(
1157                  'parents'   => $parents,
1158                  'noparents' => $noparents,
1159                  'notice'    => $message,
1160              ),
1161          )
1162      );
1163  
1164      $x->add(
1165          array(
1166              'what'         => 'term',
1167              'position'     => $level,
1168              'supplemental' => (array) $tag,
1169          )
1170      );
1171  
1172      $x->send();
1173  }
1174  
1175  /**
1176   * Handles getting a tagcloud via AJAX.
1177   *
1178   * @since 3.1.0
1179   */
1180  function wp_ajax_get_tagcloud() {
1181      if ( ! isset( $_POST['tax'] ) ) {
1182          wp_die( 0 );
1183      }
1184  
1185      $taxonomy        = sanitize_key( $_POST['tax'] );
1186      $taxonomy_object = get_taxonomy( $taxonomy );
1187  
1188      if ( ! $taxonomy_object ) {
1189          wp_die( 0 );
1190      }
1191  
1192      if ( ! current_user_can( $taxonomy_object->cap->assign_terms ) ) {
1193          wp_die( -1 );
1194      }
1195  
1196      $tags = get_terms(
1197          array(
1198              'taxonomy' => $taxonomy,
1199              'number'   => 45,
1200              'orderby'  => 'count',
1201              'order'    => 'DESC',
1202          )
1203      );
1204  
1205      if ( empty( $tags ) ) {
1206          wp_die( $taxonomy_object->labels->not_found );
1207      }
1208  
1209      if ( is_wp_error( $tags ) ) {
1210          wp_die( $tags->get_error_message() );
1211      }
1212  
1213      foreach ( $tags as $key => $tag ) {
1214          $tags[ $key ]->link = '#';
1215          $tags[ $key ]->id   = $tag->term_id;
1216      }
1217  
1218      // We need raw tag names here, so don't filter the output.
1219      $return = wp_generate_tag_cloud(
1220          $tags,
1221          array(
1222              'filter' => 0,
1223              'format' => 'list',
1224          )
1225      );
1226  
1227      if ( empty( $return ) ) {
1228          wp_die( 0 );
1229      }
1230  
1231      echo $return;
1232      wp_die();
1233  }
1234  
1235  /**
1236   * Handles getting comments via AJAX.
1237   *
1238   * @since 3.1.0
1239   *
1240   * @global int $post_id
1241   *
1242   * @param string $action Action to perform.
1243   */
1244  function wp_ajax_get_comments( $action ) {
1245      global $post_id;
1246  
1247      if ( empty( $action ) ) {
1248          $action = 'get-comments';
1249      }
1250  
1251      check_ajax_referer( $action );
1252  
1253      if ( empty( $post_id ) && ! empty( $_REQUEST['p'] ) ) {
1254          $id = absint( $_REQUEST['p'] );
1255          if ( ! empty( $id ) ) {
1256              $post_id = $id;
1257          }
1258      }
1259  
1260      if ( empty( $post_id ) ) {
1261          wp_die( -1 );
1262      }
1263  
1264      $wp_list_table = _get_list_table( 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1265  
1266      if ( ! current_user_can( 'edit_post', $post_id ) ) {
1267          wp_die( -1 );
1268      }
1269  
1270      $wp_list_table->prepare_items();
1271  
1272      if ( ! $wp_list_table->has_items() ) {
1273          wp_die( 1 );
1274      }
1275  
1276      $x = new WP_Ajax_Response();
1277  
1278      ob_start();
1279      foreach ( $wp_list_table->items as $comment ) {
1280          if ( ! current_user_can( 'edit_comment', $comment->comment_ID ) && 0 === $comment->comment_approved ) {
1281              continue;
1282          }
1283          get_comment( $comment );
1284          $wp_list_table->single_row( $comment );
1285      }
1286      $comment_list_item = ob_get_clean();
1287  
1288      $x->add(
1289          array(
1290              'what' => 'comments',
1291              'data' => $comment_list_item,
1292          )
1293      );
1294  
1295      $x->send();
1296  }
1297  
1298  /**
1299   * Handles replying to a comment via AJAX.
1300   *
1301   * @since 3.1.0
1302   *
1303   * @param string $action Action to perform.
1304   */
1305  function wp_ajax_replyto_comment( $action ) {
1306      if ( empty( $action ) ) {
1307          $action = 'replyto-comment';
1308      }
1309  
1310      check_ajax_referer( $action, '_ajax_nonce-replyto-comment' );
1311  
1312      $comment_post_id = (int) $_POST['comment_post_ID'];
1313      $post            = get_post( $comment_post_id );
1314  
1315      if ( ! $post ) {
1316          wp_die( -1 );
1317      }
1318  
1319      if ( ! current_user_can( 'edit_post', $comment_post_id ) ) {
1320          wp_die( -1 );
1321      }
1322  
1323      if ( empty( $post->post_status ) ) {
1324          wp_die( 1 );
1325      } elseif ( in_array( $post->post_status, array( 'draft', 'pending', 'trash' ), true ) ) {
1326          wp_die( __( 'You cannot reply to a comment on a draft post.' ) );
1327      }
1328  
1329      $user = wp_get_current_user();
1330  
1331      if ( $user->exists() ) {
1332          $comment_author       = wp_slash( $user->display_name );
1333          $comment_author_email = wp_slash( $user->user_email );
1334          $comment_author_url   = wp_slash( $user->user_url );
1335          $user_id              = $user->ID;
1336  
1337          if ( current_user_can( 'unfiltered_html' ) ) {
1338              if ( ! isset( $_POST['_wp_unfiltered_html_comment'] ) ) {
1339                  $_POST['_wp_unfiltered_html_comment'] = '';
1340              }
1341  
1342              if ( wp_create_nonce( 'unfiltered-html-comment' ) !== $_POST['_wp_unfiltered_html_comment'] ) {
1343                  kses_remove_filters(); // Start with a clean slate.
1344                  kses_init_filters();   // Set up the filters.
1345                  remove_filter( 'pre_comment_content', 'wp_filter_post_kses' );
1346                  add_filter( 'pre_comment_content', 'wp_filter_kses' );
1347              }
1348          }
1349      } else {
1350          wp_die( __( 'Sorry, you must be logged in to reply to a comment.' ) );
1351      }
1352  
1353      $comment_content = trim( $_POST['content'] );
1354  
1355      if ( '' === $comment_content ) {
1356          wp_die( __( 'Please type your comment text.' ) );
1357      }
1358  
1359      $comment_type = isset( $_POST['comment_type'] ) ? trim( $_POST['comment_type'] ) : 'comment';
1360  
1361      $comment_parent = 0;
1362  
1363      if ( isset( $_POST['comment_ID'] ) ) {
1364          $comment_parent = absint( $_POST['comment_ID'] );
1365      }
1366  
1367      $comment_auto_approved = false;
1368  
1369      $commentdata = array(
1370          'comment_post_ID' => $comment_post_id,
1371      );
1372  
1373      $commentdata += compact(
1374          'comment_author',
1375          'comment_author_email',
1376          'comment_author_url',
1377          'comment_content',
1378          'comment_type',
1379          'comment_parent',
1380          'user_id'
1381      );
1382  
1383      // Automatically approve parent comment.
1384      if ( ! empty( $_POST['approve_parent'] ) ) {
1385          $parent = get_comment( $comment_parent );
1386  
1387          if ( $parent && '0' === $parent->comment_approved && (int) $parent->comment_post_ID === $comment_post_id ) {
1388              if ( ! current_user_can( 'edit_comment', $parent->comment_ID ) ) {
1389                  wp_die( -1 );
1390              }
1391  
1392              if ( wp_set_comment_status( $parent, 'approve' ) ) {
1393                  $comment_auto_approved = true;
1394              }
1395          }
1396      }
1397  
1398      $comment_id = wp_new_comment( $commentdata );
1399  
1400      if ( is_wp_error( $comment_id ) ) {
1401          wp_die( $comment_id->get_error_message() );
1402      }
1403  
1404      $comment = get_comment( $comment_id );
1405  
1406      if ( ! $comment ) {
1407          wp_die( 1 );
1408      }
1409  
1410      $position = ( isset( $_POST['position'] ) && (int) $_POST['position'] ) ? (int) $_POST['position'] : '-1';
1411  
1412      ob_start();
1413      if ( isset( $_REQUEST['mode'] ) && 'dashboard' === $_REQUEST['mode'] ) {
1414          require_once  ABSPATH . 'wp-admin/includes/dashboard.php';
1415          _wp_dashboard_recent_comments_row( $comment );
1416      } else {
1417          if ( isset( $_REQUEST['mode'] ) && 'single' === $_REQUEST['mode'] ) {
1418              $wp_list_table = _get_list_table( 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1419          } else {
1420              $wp_list_table = _get_list_table( 'WP_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1421          }
1422          $wp_list_table->single_row( $comment );
1423      }
1424      $comment_list_item = ob_get_clean();
1425  
1426      $response = array(
1427          'what'     => 'comment',
1428          'id'       => $comment->comment_ID,
1429          'data'     => $comment_list_item,
1430          'position' => $position,
1431      );
1432  
1433      $counts                   = wp_count_comments();
1434      $response['supplemental'] = array(
1435          'in_moderation'        => $counts->moderated,
1436          'i18n_comments_text'   => sprintf(
1437              /* translators: %s: Number of comments. */
1438              _n( '%s Comment', '%s Comments', $counts->approved ),
1439              number_format_i18n( $counts->approved )
1440          ),
1441          'i18n_moderation_text' => sprintf(
1442              /* translators: %s: Number of comments. */
1443              _n( '%s Comment in moderation', '%s Comments in moderation', $counts->moderated ),
1444              number_format_i18n( $counts->moderated )
1445          ),
1446      );
1447  
1448      if ( $comment_auto_approved ) {
1449          $response['supplemental']['parent_approved'] = $parent->comment_ID;
1450          $response['supplemental']['parent_post_id']  = $parent->comment_post_ID;
1451      }
1452  
1453      $x = new WP_Ajax_Response();
1454      $x->add( $response );
1455      $x->send();
1456  }
1457  
1458  /**
1459   * Handles editing a comment via AJAX.
1460   *
1461   * @since 3.1.0
1462   */
1463  function wp_ajax_edit_comment() {
1464      check_ajax_referer( 'replyto-comment', '_ajax_nonce-replyto-comment' );
1465  
1466      $comment_id = (int) $_POST['comment_ID'];
1467  
1468      if ( ! current_user_can( 'edit_comment', $comment_id ) ) {
1469          wp_die( -1 );
1470      }
1471  
1472      if ( '' === $_POST['content'] ) {
1473          wp_die( __( 'Please type your comment text.' ) );
1474      }
1475  
1476      if ( isset( $_POST['status'] ) ) {
1477          $_POST['comment_status'] = $_POST['status'];
1478      }
1479  
1480      $updated = edit_comment();
1481      if ( is_wp_error( $updated ) ) {
1482          wp_die( $updated->get_error_message() );
1483      }
1484  
1485      $position = ( isset( $_POST['position'] ) && (int) $_POST['position'] ) ? (int) $_POST['position'] : '-1';
1486      /*
1487       * Checkbox is used to differentiate between the Edit Comments screen (1)
1488       * and the Comments section on the Edit Post screen (0).
1489       */
1490      $checkbox      = ( isset( $_POST['checkbox'] ) && '1' === $_POST['checkbox'] ) ? 1 : 0;
1491      $wp_list_table = _get_list_table( $checkbox ? 'WP_Comments_List_Table' : 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1492  
1493      $comment = get_comment( $comment_id );
1494  
1495      if ( empty( $comment->comment_ID ) ) {
1496          wp_die( -1 );
1497      }
1498  
1499      ob_start();
1500      $wp_list_table->single_row( $comment );
1501      $comment_list_item = ob_get_clean();
1502  
1503      $x = new WP_Ajax_Response();
1504  
1505      $x->add(
1506          array(
1507              'what'     => 'edit_comment',
1508              'id'       => $comment->comment_ID,
1509              'data'     => $comment_list_item,
1510              'position' => $position,
1511          )
1512      );
1513  
1514      $x->send();
1515  }
1516  
1517  /**
1518   * Handles adding a menu item via AJAX.
1519   *
1520   * @since 3.1.0
1521   */
1522  function wp_ajax_add_menu_item() {
1523      check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1524  
1525      if ( ! current_user_can( 'edit_theme_options' ) ) {
1526          wp_die( -1 );
1527      }
1528  
1529      require_once  ABSPATH . 'wp-admin/includes/nav-menu.php';
1530  
1531      /*
1532       * For performance reasons, we omit some object properties from the checklist.
1533       * The following is a hacky way to restore them when adding non-custom items.
1534       */
1535      $menu_items_data = array();
1536  
1537      foreach ( (array) $_POST['menu-item'] as $menu_item_data ) {
1538          if (
1539              ! empty( $menu_item_data['menu-item-type'] ) &&
1540              'custom' !== $menu_item_data['menu-item-type'] &&
1541              ! empty( $menu_item_data['menu-item-object-id'] )
1542          ) {
1543              switch ( $menu_item_data['menu-item-type'] ) {
1544                  case 'post_type':
1545                      $_object = get_post( $menu_item_data['menu-item-object-id'] );
1546                      break;
1547  
1548                  case 'post_type_archive':
1549                      $_object = get_post_type_object( $menu_item_data['menu-item-object'] );
1550                      break;
1551  
1552                  case 'taxonomy':
1553                      $_object = get_term( $menu_item_data['menu-item-object-id'], $menu_item_data['menu-item-object'] );
1554                      break;
1555              }
1556  
1557              $_menu_items = array_map( 'wp_setup_nav_menu_item', array( $_object ) );
1558              $_menu_item  = reset( $_menu_items );
1559  
1560              // Restore the missing menu item properties.
1561              $menu_item_data['menu-item-description'] = $_menu_item->description;
1562          }
1563  
1564          $menu_items_data[] = $menu_item_data;
1565      }
1566  
1567      $item_ids = wp_save_nav_menu_items( 0, $menu_items_data );
1568      if ( is_wp_error( $item_ids ) ) {
1569          wp_die( 0 );
1570      }
1571  
1572      $menu_items = array();
1573  
1574      foreach ( (array) $item_ids as $menu_item_id ) {
1575          $menu_obj = get_post( $menu_item_id );
1576  
1577          if ( ! empty( $menu_obj->ID ) ) {
1578              $menu_obj        = wp_setup_nav_menu_item( $menu_obj );
1579              $menu_obj->title = empty( $menu_obj->title ) ? __( 'Menu Item' ) : $menu_obj->title;
1580              $menu_obj->label = $menu_obj->title; // Don't show "(pending)" in ajax-added items.
1581              $menu_items[]    = $menu_obj;
1582          }
1583      }
1584  
1585      /** This filter is documented in wp-admin/includes/nav-menu.php */
1586      $walker_class_name = apply_filters( 'wp_edit_nav_menu_walker', 'Walker_Nav_Menu_Edit', $_POST['menu'] );
1587  
1588      if ( ! class_exists( $walker_class_name ) ) {
1589          wp_die( 0 );
1590      }
1591  
1592      if ( ! empty( $menu_items ) ) {
1593          $args = array(
1594              'after'       => '',
1595              'before'      => '',
1596              'link_after'  => '',
1597              'link_before' => '',
1598              'walker'      => new $walker_class_name(),
1599          );
1600  
1601          echo walk_nav_menu_tree( $menu_items, 0, (object) $args );
1602      }
1603  
1604      wp_die();
1605  }
1606  
1607  /**
1608   * Handles adding meta via AJAX.
1609   *
1610   * @since 3.1.0
1611   */
1612  function wp_ajax_add_meta() {
1613      check_ajax_referer( 'add-meta', '_ajax_nonce-add-meta' );
1614      $c    = 0;
1615      $pid  = (int) $_POST['post_id'];
1616      $post = get_post( $pid );
1617  
1618      if ( isset( $_POST['metakeyselect'] ) || isset( $_POST['metakeyinput'] ) ) {
1619          if ( ! current_user_can( 'edit_post', $pid ) ) {
1620              wp_die( -1 );
1621          }
1622  
1623          if ( isset( $_POST['metakeyselect'] ) && '#NONE#' === $_POST['metakeyselect'] && empty( $_POST['metakeyinput'] ) ) {
1624              wp_die( 1 );
1625          }
1626  
1627          // If the post is an autodraft, save the post as a draft and then attempt to save the meta.
1628          if ( 'auto-draft' === $post->post_status ) {
1629              $post_data                = array();
1630              $post_data['action']      = 'draft'; // Warning fix.
1631              $post_data['post_ID']     = $pid;
1632              $post_data['post_type']   = $post->post_type;
1633              $post_data['post_status'] = 'draft';
1634              $now                      = time();
1635  
1636              $post_data['post_title'] = sprintf(
1637                  /* translators: 1: Post creation date, 2: Post creation time. */
1638                  __( 'Draft created on %1$s at %2$s' ),
1639                  gmdate( __( 'F j, Y' ), $now ),
1640                  gmdate( __( 'g:i a' ), $now )
1641              );
1642  
1643              $pid = edit_post( $post_data );
1644  
1645              if ( $pid ) {
1646                  if ( is_wp_error( $pid ) ) {
1647                      $x = new WP_Ajax_Response(
1648                          array(
1649                              'what' => 'meta',
1650                              'data' => $pid,
1651                          )
1652                      );
1653                      $x->send();
1654                  }
1655  
1656                  $mid = add_meta( $pid );
1657                  if ( ! $mid ) {
1658                      wp_die( __( 'Please provide a custom field value.' ) );
1659                  }
1660              } else {
1661                  wp_die( 0 );
1662              }
1663          } else {
1664              $mid = add_meta( $pid );
1665              if ( ! $mid ) {
1666                  wp_die( __( 'Please provide a custom field value.' ) );
1667              }
1668          }
1669  
1670          $meta = get_metadata_by_mid( 'post', $mid );
1671          $pid  = (int) $meta->post_id;
1672          $meta = get_object_vars( $meta );
1673  
1674          $x = new WP_Ajax_Response(
1675              array(
1676                  'what'         => 'meta',
1677                  'id'           => $mid,
1678                  'data'         => _list_meta_row( $meta, $c ),
1679                  'position'     => 1,
1680                  'supplemental' => array( 'postid' => $pid ),
1681              )
1682          );
1683      } else { // Update?
1684          $mid   = (int) key( $_POST['meta'] );
1685          $key   = wp_unslash( $_POST['meta'][ $mid ]['key'] );
1686          $value = wp_unslash( $_POST['meta'][ $mid ]['value'] );
1687  
1688          if ( '' === trim( $key ) ) {
1689              wp_die( __( 'Please provide a custom field name.' ) );
1690          }
1691  
1692          $meta = get_metadata_by_mid( 'post', $mid );
1693  
1694          if ( ! $meta ) {
1695              wp_die( 0 ); // If meta doesn't exist.
1696          }
1697  
1698          if (
1699              is_protected_meta( $meta->meta_key, 'post' ) || is_protected_meta( $key, 'post' ) ||
1700              ! current_user_can( 'edit_post_meta', $meta->post_id, $meta->meta_key ) ||
1701              ! current_user_can( 'edit_post_meta', $meta->post_id, $key )
1702          ) {
1703              wp_die( -1 );
1704          }
1705  
1706          if ( $meta->meta_value !== $value || $meta->meta_key !== $key ) {
1707              $u = update_metadata_by_mid( 'post', $mid, $value, $key );
1708              if ( ! $u ) {
1709                  wp_die( 0 ); // We know meta exists; we also know it's unchanged (or DB error, in which case there are bigger problems).
1710              }
1711          }
1712  
1713          $x = new WP_Ajax_Response(
1714              array(
1715                  'what'         => 'meta',
1716                  'id'           => $mid,
1717                  'old_id'       => $mid,
1718                  'data'         => _list_meta_row(
1719                      array(
1720                          'meta_key'   => $key,
1721                          'meta_value' => $value,
1722                          'meta_id'    => $mid,
1723                      ),
1724                      $c
1725                  ),
1726                  'position'     => 0,
1727                  'supplemental' => array( 'postid' => $meta->post_id ),
1728              )
1729          );
1730      }
1731      $x->send();
1732  }
1733  
1734  /**
1735   * Handles adding a user via AJAX.
1736   *
1737   * @since 3.1.0
1738   *
1739   * @param string $action Action to perform.
1740   */
1741  function wp_ajax_add_user( $action ) {
1742      if ( empty( $action ) ) {
1743          $action = 'add-user';
1744      }
1745  
1746      check_ajax_referer( $action );
1747  
1748      if ( ! current_user_can( 'create_users' ) ) {
1749          wp_die( -1 );
1750      }
1751  
1752      $user_id = edit_user();
1753  
1754      if ( ! $user_id ) {
1755          wp_die( 0 );
1756      } elseif ( is_wp_error( $user_id ) ) {
1757          $x = new WP_Ajax_Response(
1758              array(
1759                  'what' => 'user',
1760                  'id'   => $user_id,
1761              )
1762          );
1763          $x->send();
1764      }
1765  
1766      $user_object   = get_userdata( $user_id );
1767      $wp_list_table = _get_list_table( 'WP_Users_List_Table' );
1768  
1769      $role = current( $user_object->roles );
1770  
1771      $x = new WP_Ajax_Response(
1772          array(
1773              'what'         => 'user',
1774              'id'           => $user_id,
1775              'data'         => $wp_list_table->single_row( $user_object, '', $role ),
1776              'supplemental' => array(
1777                  'show-link' => sprintf(
1778                      /* translators: %s: The new user. */
1779                      __( 'User %s added' ),
1780                      '<a href="#user-' . $user_id . '">' . $user_object->user_login . '</a>'
1781                  ),
1782                  'role'      => $role,
1783              ),
1784          )
1785      );
1786      $x->send();
1787  }
1788  
1789  /**
1790   * Handles closed post boxes via AJAX.
1791   *
1792   * @since 3.1.0
1793   */
1794  function wp_ajax_closed_postboxes() {
1795      check_ajax_referer( 'closedpostboxes', 'closedpostboxesnonce' );
1796      $closed = isset( $_POST['closed'] ) ? explode( ',', $_POST['closed'] ) : array();
1797      $closed = array_filter( $closed );
1798  
1799      $hidden = isset( $_POST['hidden'] ) ? explode( ',', $_POST['hidden'] ) : array();
1800      $hidden = array_filter( $hidden );
1801  
1802      $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1803  
1804      if ( sanitize_key( $page ) !== $page ) {
1805          wp_die( 0 );
1806      }
1807  
1808      $user = wp_get_current_user();
1809      if ( ! $user ) {
1810          wp_die( -1 );
1811      }
1812  
1813      if ( is_array( $closed ) ) {
1814          update_user_meta( $user->ID, "closedpostboxes_$page", $closed );
1815      }
1816  
1817      if ( is_array( $hidden ) ) {
1818          // Postboxes that are always shown.
1819          $hidden = array_diff( $hidden, array( 'submitdiv', 'linksubmitdiv', 'manage-menu', 'create-menu' ) );
1820          update_user_meta( $user->ID, "metaboxhidden_$page", $hidden );
1821      }
1822  
1823      wp_die( 1 );
1824  }
1825  
1826  /**
1827   * Handles hidden columns via AJAX.
1828   *
1829   * @since 3.1.0
1830   */
1831  function wp_ajax_hidden_columns() {
1832      check_ajax_referer( 'screen-options-nonce', 'screenoptionnonce' );
1833      $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1834  
1835      if ( sanitize_key( $page ) !== $page ) {
1836          wp_die( 0 );
1837      }
1838  
1839      $user = wp_get_current_user();
1840      if ( ! $user ) {
1841          wp_die( -1 );
1842      }
1843  
1844      $hidden = ! empty( $_POST['hidden'] ) ? explode( ',', $_POST['hidden'] ) : array();
1845      update_user_meta( $user->ID, "manage{$page}columnshidden", $hidden );
1846  
1847      wp_die( 1 );
1848  }
1849  
1850  /**
1851   * Handles updating whether to display the welcome panel via AJAX.
1852   *
1853   * @since 3.1.0
1854   */
1855  function wp_ajax_update_welcome_panel() {
1856      check_ajax_referer( 'welcome-panel-nonce', 'welcomepanelnonce' );
1857  
1858      if ( ! current_user_can( 'edit_theme_options' ) ) {
1859          wp_die( -1 );
1860      }
1861  
1862      update_user_meta( get_current_user_id(), 'show_welcome_panel', empty( $_POST['visible'] ) ? 0 : 1 );
1863  
1864      wp_die( 1 );
1865  }
1866  
1867  /**
1868   * Handles for retrieving menu meta boxes via AJAX.
1869   *
1870   * @since 3.1.0
1871   */
1872  function wp_ajax_menu_get_metabox() {
1873      if ( ! current_user_can( 'edit_theme_options' ) ) {
1874          wp_die( -1 );
1875      }
1876  
1877      require_once  ABSPATH . 'wp-admin/includes/nav-menu.php';
1878  
1879      if ( isset( $_POST['item-type'] ) && 'post_type' === $_POST['item-type'] ) {
1880          $type     = 'posttype';
1881          $callback = 'wp_nav_menu_item_post_type_meta_box';
1882          $items    = (array) get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
1883      } elseif ( isset( $_POST['item-type'] ) && 'taxonomy' === $_POST['item-type'] ) {
1884          $type     = 'taxonomy';
1885          $callback = 'wp_nav_menu_item_taxonomy_meta_box';
1886          $items    = (array) get_taxonomies( array( 'show_ui' => true ), 'object' );
1887      }
1888  
1889      if ( ! empty( $_POST['item-object'] ) && isset( $items[ $_POST['item-object'] ] ) ) {
1890          $menus_meta_box_object = $items[ $_POST['item-object'] ];
1891  
1892          /** This filter is documented in wp-admin/includes/nav-menu.php */
1893          $item = apply_filters( 'nav_menu_meta_box_object', $menus_meta_box_object );
1894  
1895          $box_args = array(
1896              'id'       => 'add-' . $item->name,
1897              'title'    => $item->labels->name,
1898              'callback' => $callback,
1899              'args'     => $item,
1900          );
1901  
1902          ob_start();
1903          $callback( null, $box_args );
1904  
1905          $markup = ob_get_clean();
1906  
1907          echo wp_json_encode(
1908              array(
1909                  'replace-id' => $type . '-' . $item->name,
1910                  'markup'     => $markup,
1911              )
1912          );
1913      }
1914  
1915      wp_die();
1916  }
1917  
1918  /**
1919   * Handles internal linking via AJAX.
1920   *
1921   * @since 3.1.0
1922   */
1923  function wp_ajax_wp_link_ajax() {
1924      check_ajax_referer( 'internal-linking', '_ajax_linking_nonce' );
1925  
1926      $args = array();
1927  
1928      if ( isset( $_POST['search'] ) ) {
1929          $args['s'] = wp_unslash( $_POST['search'] );
1930      }
1931  
1932      if ( isset( $_POST['term'] ) ) {
1933          $args['s'] = wp_unslash( $_POST['term'] );
1934      }
1935  
1936      $args['pagenum'] = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
1937  
1938      if ( ! class_exists( '_WP_Editors', false ) ) {
1939          require  ABSPATH . WPINC . '/class-wp-editor.php';
1940      }
1941  
1942      $results = _WP_Editors::wp_link_query( $args );
1943  
1944      if ( ! isset( $results ) ) {
1945          wp_die( 0 );
1946      }
1947  
1948      echo wp_json_encode( $results );
1949      echo "\n";
1950  
1951      wp_die();
1952  }
1953  
1954  /**
1955   * Handles saving menu locations via AJAX.
1956   *
1957   * @since 3.1.0
1958   */
1959  function wp_ajax_menu_locations_save() {
1960      if ( ! current_user_can( 'edit_theme_options' ) ) {
1961          wp_die( -1 );
1962      }
1963  
1964      check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1965  
1966      if ( ! isset( $_POST['menu-locations'] ) ) {
1967          wp_die( 0 );
1968      }
1969  
1970      set_theme_mod( 'nav_menu_locations', array_map( 'absint', $_POST['menu-locations'] ) );
1971      wp_die( 1 );
1972  }
1973  
1974  /**
1975   * Handles saving the meta box order via AJAX.
1976   *
1977   * @since 3.1.0
1978   */
1979  function wp_ajax_meta_box_order() {
1980      check_ajax_referer( 'meta-box-order' );
1981      $order        = isset( $_POST['order'] ) ? (array) $_POST['order'] : false;
1982      $page_columns = isset( $_POST['page_columns'] ) ? $_POST['page_columns'] : 'auto';
1983  
1984      if ( 'auto' !== $page_columns ) {
1985          $page_columns = (int) $page_columns;
1986      }
1987  
1988      $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1989  
1990      if ( sanitize_key( $page ) !== $page ) {
1991          wp_die( 0 );
1992      }
1993  
1994      $user = wp_get_current_user();
1995      if ( ! $user ) {
1996          wp_die( -1 );
1997      }
1998  
1999      if ( $order ) {
2000          update_user_meta( $user->ID, "meta-box-order_$page", $order );
2001      }
2002  
2003      if ( $page_columns ) {
2004          update_user_meta( $user->ID, "screen_layout_$page", $page_columns );
2005      }
2006  
2007      wp_send_json_success();
2008  }
2009  
2010  /**
2011   * Handles menu quick searching via AJAX.
2012   *
2013   * @since 3.1.0
2014   */
2015  function wp_ajax_menu_quick_search() {
2016      if ( ! current_user_can( 'edit_theme_options' ) ) {
2017          wp_die( -1 );
2018      }
2019  
2020      require_once  ABSPATH . 'wp-admin/includes/nav-menu.php';
2021  
2022      _wp_ajax_menu_quick_search( $_POST );
2023  
2024      wp_die();
2025  }
2026  
2027  /**
2028   * Handles retrieving a permalink via AJAX.
2029   *
2030   * @since 3.1.0
2031   */
2032  function wp_ajax_get_permalink() {
2033      check_ajax_referer( 'getpermalink', 'getpermalinknonce' );
2034      $post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
2035      wp_die( get_preview_post_link( $post_id ) );
2036  }
2037  
2038  /**
2039   * Handles retrieving a sample permalink via AJAX.
2040   *
2041   * @since 3.1.0
2042   */
2043  function wp_ajax_sample_permalink() {
2044      check_ajax_referer( 'samplepermalink', 'samplepermalinknonce' );
2045      $post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
2046      $title   = isset( $_POST['new_title'] ) ? $_POST['new_title'] : '';
2047      $slug    = isset( $_POST['new_slug'] ) ? $_POST['new_slug'] : null;
2048      wp_die( get_sample_permalink_html( $post_id, $title, $slug ) );
2049  }
2050  
2051  /**
2052   * Handles Quick Edit saving a post from a list table via AJAX.
2053   *
2054   * @since 3.1.0
2055   *
2056   * @global string $mode List table view mode.
2057   */
2058  function wp_ajax_inline_save() {
2059      global $mode;
2060  
2061      check_ajax_referer( 'inlineeditnonce', '_inline_edit' );
2062  
2063      if ( ! isset( $_POST['post_ID'] ) || ! (int) $_POST['post_ID'] ) {
2064          wp_die();
2065      }
2066  
2067      $post_id = (int) $_POST['post_ID'];
2068  
2069      if ( 'page' === $_POST['post_type'] ) {
2070          if ( ! current_user_can( 'edit_page', $post_id ) ) {
2071              wp_die( __( 'Sorry, you are not allowed to edit this page.' ) );
2072          }
2073      } else {
2074          if ( ! current_user_can( 'edit_post', $post_id ) ) {
2075              wp_die( __( 'Sorry, you are not allowed to edit this post.' ) );
2076          }
2077      }
2078  
2079      $last = wp_check_post_lock( $post_id );
2080      if ( $last ) {
2081          $last_user      = get_userdata( $last );
2082          $last_user_name = $last_user ? $last_user->display_name : __( 'Someone' );
2083  
2084          /* translators: %s: User's display name. */
2085          $msg_template = __( 'Saving is disabled: %s is currently editing this post.' );
2086  
2087          if ( 'page' === $_POST['post_type'] ) {
2088              /* translators: %s: User's display name. */
2089              $msg_template = __( 'Saving is disabled: %s is currently editing this page.' );
2090          }
2091  
2092          printf( $msg_template, esc_html( $last_user_name ) );
2093          wp_die();
2094      }
2095  
2096      $data = &$_POST;
2097  
2098      $post = get_post( $post_id, ARRAY_A );
2099  
2100      // Since it's coming from the database.
2101      $post = wp_slash( $post );
2102  
2103      $data['content'] = $post['post_content'];
2104      $data['excerpt'] = $post['post_excerpt'];
2105  
2106      // Rename.
2107      $data['user_ID'] = get_current_user_id();
2108  
2109      if ( isset( $data['post_parent'] ) ) {
2110          $data['parent_id'] = $data['post_parent'];
2111      }
2112  
2113      // Status.
2114      if ( isset( $data['keep_private'] ) && 'private' === $data['keep_private'] ) {
2115          $data['visibility']  = 'private';
2116          $data['post_status'] = 'private';
2117      } else {
2118          $data['post_status'] = $data['_status'];
2119      }
2120  
2121      if ( empty( $data['comment_status'] ) ) {
2122          $data['comment_status'] = 'closed';
2123      }
2124  
2125      if ( empty( $data['ping_status'] ) ) {
2126          $data['ping_status'] = 'closed';
2127      }
2128  
2129      // Exclude terms from taxonomies that are not supposed to appear in Quick Edit.
2130      if ( ! empty( $data['tax_input'] ) ) {
2131          foreach ( $data['tax_input'] as $taxonomy => $terms ) {
2132              $tax_object = get_taxonomy( $taxonomy );
2133              /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */
2134              if ( ! apply_filters( 'quick_edit_show_taxonomy', $tax_object->show_in_quick_edit, $taxonomy, $post['post_type'] ) ) {
2135                  unset( $data['tax_input'][ $taxonomy ] );
2136              }
2137          }
2138      }
2139  
2140      // Hack: wp_unique_post_slug() doesn't work for drafts, so we will fake that our post is published.
2141      if ( ! empty( $data['post_name'] ) && in_array( $post['post_status'], array( 'draft', 'pending' ), true ) ) {
2142          $post['post_status'] = 'publish';
2143          $data['post_name']   = wp_unique_post_slug( $data['post_name'], $post['ID'], $post['post_status'], $post['post_type'], $post['post_parent'] );
2144      }
2145  
2146      // Update the post.
2147      edit_post();
2148  
2149      $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
2150  
2151      $mode = 'excerpt' === $_POST['post_view'] ? 'excerpt' : 'list';
2152  
2153      $level = 0;
2154      if ( is_post_type_hierarchical( $wp_list_table->screen->post_type ) ) {
2155          $request_post = array( get_post( $_POST['post_ID'] ) );
2156          $parent       = $request_post[0]->post_parent;
2157  
2158          while ( $parent > 0 ) {
2159              $parent_post = get_post( $parent );
2160              $parent      = $parent_post->post_parent;
2161              ++$level;
2162          }
2163      }
2164  
2165      $wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level );
2166  
2167      wp_die();
2168  }
2169  
2170  /**
2171   * Handles Quick Edit saving for a term via AJAX.
2172   *
2173   * @since 3.1.0
2174   */
2175  function wp_ajax_inline_save_tax() {
2176      check_ajax_referer( 'taxinlineeditnonce', '_inline_edit' );
2177  
2178      $taxonomy        = sanitize_key( $_POST['taxonomy'] );
2179      $taxonomy_object = get_taxonomy( $taxonomy );
2180  
2181      if ( ! $taxonomy_object ) {
2182          wp_die( 0 );
2183      }
2184  
2185      if ( ! isset( $_POST['tax_ID'] ) || ! (int) $_POST['tax_ID'] ) {
2186          wp_die( -1 );
2187      }
2188  
2189      $id = (int) $_POST['tax_ID'];
2190  
2191      if ( ! current_user_can( 'edit_term', $id ) ) {
2192          wp_die( -1 );
2193      }
2194  
2195      $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => 'edit-' . $taxonomy ) );
2196  
2197      $tag                  = get_term( $id, $taxonomy );
2198      $_POST['description'] = $tag->description;
2199  
2200      $updated = wp_update_term( $id, $taxonomy, $_POST );
2201  
2202      if ( $updated && ! is_wp_error( $updated ) ) {
2203          $tag = get_term( $updated['term_id'], $taxonomy );
2204          if ( ! $tag || is_wp_error( $tag ) ) {
2205              if ( is_wp_error( $tag ) && $tag->get_error_message() ) {
2206                  wp_die( $tag->get_error_message() );
2207              }
2208              wp_die( __( 'Item not updated.' ) );
2209          }
2210      } else {
2211          if ( is_wp_error( $updated ) && $updated->get_error_message() ) {
2212              wp_die( $updated->get_error_message() );
2213          }
2214          wp_die( __( 'Item not updated.' ) );
2215      }
2216  
2217      $level  = 0;
2218      $parent = $tag->parent;
2219  
2220      while ( $parent > 0 ) {
2221          $parent_tag = get_term( $parent, $taxonomy );
2222          $parent     = $parent_tag->parent;
2223          ++$level;
2224      }
2225  
2226      $wp_list_table->single_row( $tag, $level );
2227      wp_die();
2228  }
2229  
2230  /**
2231   * Handles querying posts for the Find Posts modal via AJAX.
2232   *
2233   * @see window.findPosts
2234   *
2235   * @since 3.1.0
2236   */
2237  function wp_ajax_find_posts() {
2238      check_ajax_referer( 'find-posts' );
2239  
2240      $post_types = get_post_types( array( 'public' => true ), 'objects' );
2241      unset( $post_types['attachment'] );
2242  
2243      $args = array(
2244          'post_type'      => array_keys( $post_types ),
2245          'post_status'    => 'any',
2246          'posts_per_page' => 50,
2247      );
2248  
2249      $search = wp_unslash( $_POST['ps'] );
2250  
2251      if ( '' !== $search ) {
2252          $args['s'] = $search;
2253      }
2254  
2255      $posts = get_posts( $args );
2256  
2257      if ( ! $posts ) {
2258          wp_send_json_error( __( 'No items found.' ) );
2259      }
2260  
2261      $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>';
2262      $alt  = '';
2263      foreach ( $posts as $post ) {
2264          $title = trim( $post->post_title ) ? $post->post_title : __( '(no title)' );
2265          $alt   = ( 'alternate' === $alt ) ? '' : 'alternate';
2266  
2267          switch ( $post->post_status ) {
2268              case 'publish':
2269              case 'private':
2270                  $stat = __( 'Published' );
2271                  break;
2272              case 'future':
2273                  $stat = __( 'Scheduled' );
2274                  break;
2275              case 'pending':
2276                  $stat = __( 'Pending Review' );
2277                  break;
2278              case 'draft':
2279                  $stat = __( 'Draft' );
2280                  break;
2281          }
2282  
2283          if ( '0000-00-00 00:00:00' === $post->post_date ) {
2284              $time = '';
2285          } else {
2286              /* translators: Date format in table columns, see https://www.php.net/manual/datetime.format.php */
2287              $time = mysql2date( __( 'Y/m/d' ), $post->post_date );
2288          }
2289  
2290          $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>';
2291          $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";
2292      }
2293  
2294      $html .= '</tbody></table>';
2295  
2296      wp_send_json_success( $html );
2297  }
2298  
2299  /**
2300   * Handles saving the widgets order via AJAX.
2301   *
2302   * @since 3.1.0
2303   */
2304  function wp_ajax_widgets_order() {
2305      check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
2306  
2307      if ( ! current_user_can( 'edit_theme_options' ) ) {
2308          wp_die( -1 );
2309      }
2310  
2311      unset( $_POST['savewidgets'], $_POST['action'] );
2312  
2313      // Save widgets order for all sidebars.
2314      if ( is_array( $_POST['sidebars'] ) ) {
2315          $sidebars = array();
2316  
2317          foreach ( wp_unslash( $_POST['sidebars'] ) as $key => $val ) {
2318              $sb = array();
2319  
2320              if ( ! empty( $val ) ) {
2321                  $val = explode( ',', $val );
2322  
2323                  foreach ( $val as $k => $v ) {
2324                      if ( ! str_contains( $v, 'widget-' ) ) {
2325                          continue;
2326                      }
2327  
2328                      $sb[ $k ] = substr( $v, strpos( $v, '_' ) + 1 );
2329                  }
2330              }
2331              $sidebars[ $key ] = $sb;
2332          }
2333  
2334          wp_set_sidebars_widgets( $sidebars );
2335          wp_die( 1 );
2336      }
2337  
2338      wp_die( -1 );
2339  }
2340  
2341  /**
2342   * Handles saving a widget via AJAX.
2343   *
2344   * @since 3.1.0
2345   *
2346   * @global array $wp_registered_widgets
2347   * @global array $wp_registered_widget_controls
2348   * @global array $wp_registered_widget_updates
2349   */
2350  function wp_ajax_save_widget() {
2351      global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates;
2352  
2353      check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
2354  
2355      if ( ! current_user_can( 'edit_theme_options' ) || ! isset( $_POST['id_base'] ) ) {
2356          wp_die( -1 );
2357      }
2358  
2359      unset( $_POST['savewidgets'], $_POST['action'] );
2360  
2361      /**
2362       * Fires early when editing the widgets displayed in sidebars.
2363       *
2364       * @since 2.8.0
2365       */
2366      do_action( 'load-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
2367  
2368      /**
2369       * Fires early when editing the widgets displayed in sidebars.
2370       *
2371       * @since 2.8.0
2372       */
2373      do_action( 'widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
2374  
2375      /** This action is documented in wp-admin/widgets.php */
2376      do_action( 'sidebar_admin_setup' );
2377  
2378      $id_base      = wp_unslash( $_POST['id_base'] );
2379      $widget_id    = wp_unslash( $_POST['widget-id'] );
2380      $sidebar_id   = $_POST['sidebar'];
2381      $multi_number = ! empty( $_POST['multi_number'] ) ? (int) $_POST['multi_number'] : 0;
2382      $settings     = isset( $_POST[ 'widget-' . $id_base ] ) && is_array( $_POST[ 'widget-' . $id_base ] ) ? $_POST[ 'widget-' . $id_base ] : false;
2383      $error        = '<p>' . __( 'An error has occurred. Please reload the page and try again.' ) . '</p>';
2384  
2385      $sidebars = wp_get_sidebars_widgets();
2386      $sidebar  = isset( $sidebars[ $sidebar_id ] ) ? $sidebars[ $sidebar_id ] : array();
2387  
2388      // Delete.
2389      if ( isset( $_POST['delete_widget'] ) && $_POST['delete_widget'] ) {
2390  
2391          if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) {
2392              wp_die( $error );
2393          }
2394  
2395          $sidebar = array_diff( $sidebar, array( $widget_id ) );
2396          $_POST   = array(
2397              'sidebar'            => $sidebar_id,
2398              'widget-' . $id_base => array(),
2399              'the-widget-id'      => $widget_id,
2400              'delete_widget'      => '1',
2401          );
2402  
2403          /** This action is documented in wp-admin/widgets.php */
2404          do_action( 'delete_widget', $widget_id, $sidebar_id, $id_base );
2405  
2406      } elseif ( $settings && preg_match( '/__i__|%i%/', key( $settings ) ) ) {
2407          if ( ! $multi_number ) {
2408              wp_die( $error );
2409          }
2410  
2411          $_POST[ 'widget-' . $id_base ] = array( $multi_number => reset( $settings ) );
2412          $widget_id                     = $id_base . '-' . $multi_number;
2413          $sidebar[]                     = $widget_id;
2414      }
2415      $_POST['widget-id'] = $sidebar;
2416  
2417      foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
2418  
2419          if ( $name === $id_base ) {
2420              if ( ! is_callable( $control['callback'] ) ) {
2421                  continue;
2422              }
2423  
2424              ob_start();
2425                  call_user_func_array( $control['callback'], $control['params'] );
2426              ob_end_clean();
2427              break;
2428          }
2429      }
2430  
2431      if ( isset( $_POST['delete_widget'] ) && $_POST['delete_widget'] ) {
2432          $sidebars[ $sidebar_id ] = $sidebar;
2433          wp_set_sidebars_widgets( $sidebars );
2434          echo "deleted:$widget_id";
2435          wp_die();
2436      }
2437  
2438      if ( ! empty( $_POST['add_new'] ) ) {
2439          wp_die();
2440      }
2441  
2442      $form = $wp_registered_widget_controls[ $widget_id ];
2443      if ( $form ) {
2444          call_user_func_array( $form['callback'], $form['params'] );
2445      }
2446  
2447      wp_die();
2448  }
2449  
2450  /**
2451   * Handles updating a widget via AJAX.
2452   *
2453   * @since 3.9.0
2454   *
2455   * @global WP_Customize_Manager $wp_customize
2456   */
2457  function wp_ajax_update_widget() {
2458      global $wp_customize;
2459      $wp_customize->widgets->wp_ajax_update_widget();
2460  }
2461  
2462  /**
2463   * Handles removing inactive widgets via AJAX.
2464   *
2465   * @since 4.4.0
2466   */
2467  function wp_ajax_delete_inactive_widgets() {
2468      check_ajax_referer( 'remove-inactive-widgets', 'removeinactivewidgets' );
2469  
2470      if ( ! current_user_can( 'edit_theme_options' ) ) {
2471          wp_die( -1 );
2472      }
2473  
2474      unset( $_POST['removeinactivewidgets'], $_POST['action'] );
2475      /** This action is documented in wp-admin/includes/ajax-actions.php */
2476      do_action( 'load-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
2477      /** This action is documented in wp-admin/includes/ajax-actions.php */
2478      do_action( 'widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
2479      /** This action is documented in wp-admin/widgets.php */
2480      do_action( 'sidebar_admin_setup' );
2481  
2482      $sidebars_widgets = wp_get_sidebars_widgets();
2483  
2484      foreach ( $sidebars_widgets['wp_inactive_widgets'] as $key => $widget_id ) {
2485          $pieces       = explode( '-', $widget_id );
2486          $multi_number = array_pop( $pieces );
2487          $id_base      = implode( '-', $pieces );
2488          $widget       = get_option( 'widget_' . $id_base );
2489          unset( $widget[ $multi_number ] );
2490          update_option( 'widget_' . $id_base, $widget );
2491          unset( $sidebars_widgets['wp_inactive_widgets'][ $key ] );
2492      }
2493  
2494      wp_set_sidebars_widgets( $sidebars_widgets );
2495  
2496      wp_die();
2497  }
2498  
2499  /**
2500   * Handles creating missing image sub-sizes for just uploaded images via AJAX.
2501   *
2502   * @since 5.3.0
2503   */
2504  function wp_ajax_media_create_image_subsizes() {
2505      check_ajax_referer( 'media-form' );
2506  
2507      if ( ! current_user_can( 'upload_files' ) ) {
2508          wp_send_json_error( array( 'message' => __( 'Sorry, you are not allowed to upload files.' ) ) );
2509      }
2510  
2511      if ( empty( $_POST['attachment_id'] ) ) {
2512          wp_send_json_error( array( 'message' => __( 'Upload failed. Please reload and try again.' ) ) );
2513      }
2514  
2515      $attachment_id = (int) $_POST['attachment_id'];
2516  
2517      if ( ! empty( $_POST['_wp_upload_failed_cleanup'] ) ) {
2518          // Upload failed. Cleanup.
2519          if ( wp_attachment_is_image( $attachment_id ) && current_user_can( 'delete_post', $attachment_id ) ) {
2520              $attachment = get_post( $attachment_id );
2521  
2522              // Created at most 10 min ago.
2523              if ( $attachment && ( time() - strtotime( $attachment->post_date_gmt ) < 600 ) ) {
2524                  wp_delete_attachment( $attachment_id, true );
2525                  wp_send_json_success();
2526              }
2527          }
2528      }
2529  
2530      /*
2531       * Set a custom header with the attachment_id.
2532       * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
2533       */
2534      if ( ! headers_sent() ) {
2535          header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
2536      }
2537  
2538      /*
2539       * This can still be pretty slow and cause timeout or out of memory errors.
2540       * The js that handles the response would need to also handle HTTP 500 errors.
2541       */
2542      wp_update_image_subsizes( $attachment_id );
2543  
2544      if ( ! empty( $_POST['_legacy_support'] ) ) {
2545          // The old (inline) uploader. Only needs the attachment_id.
2546          $response = array( 'id' => $attachment_id );
2547      } else {
2548          // Media modal and Media Library grid view.
2549          $response = wp_prepare_attachment_for_js( $attachment_id );
2550  
2551          if ( ! $response ) {
2552              wp_send_json_error( array( 'message' => __( 'Upload failed.' ) ) );
2553          }
2554      }
2555  
2556      // At this point the image has been uploaded successfully.
2557      wp_send_json_success( $response );
2558  }
2559  
2560  /**
2561   * Handles uploading attachments via AJAX.
2562   *
2563   * @since 3.3.0
2564   */
2565  function wp_ajax_upload_attachment() {
2566      check_ajax_referer( 'media-form' );
2567      /*
2568       * This function does not use wp_send_json_success() / wp_send_json_error()
2569       * as the html4 Plupload handler requires a text/html Content-Type for older IE.
2570       * See https://core.trac.wordpress.org/ticket/31037
2571       */
2572  
2573      if ( ! current_user_can( 'upload_files' ) ) {
2574          echo wp_json_encode(
2575              array(
2576                  'success' => false,
2577                  'data'    => array(
2578                      'message'  => __( 'Sorry, you are not allowed to upload files.' ),
2579                      'filename' => esc_html( $_FILES['async-upload']['name'] ),
2580                  ),
2581              )
2582          );
2583  
2584          wp_die();
2585      }
2586  
2587      if ( isset( $_REQUEST['post_id'] ) ) {
2588          $post_id = $_REQUEST['post_id'];
2589  
2590          if ( ! current_user_can( 'edit_post', $post_id ) ) {
2591              echo wp_json_encode(
2592                  array(
2593                      'success' => false,
2594                      'data'    => array(
2595                          'message'  => __( 'Sorry, you are not allowed to attach files to this post.' ),
2596                          'filename' => esc_html( $_FILES['async-upload']['name'] ),
2597                      ),
2598                  )
2599              );
2600  
2601              wp_die();
2602          }
2603      } else {
2604          $post_id = null;
2605      }
2606  
2607      $post_data = ! empty( $_REQUEST['post_data'] ) ? _wp_get_allowed_postdata( _wp_translate_postdata( false, (array) $_REQUEST['post_data'] ) ) : array();
2608  
2609      if ( is_wp_error( $post_data ) ) {
2610          wp_die( $post_data->get_error_message() );
2611      }
2612  
2613      // If the context is custom header or background, make sure the uploaded file is an image.
2614      if ( isset( $post_data['context'] ) && in_array( $post_data['context'], array( 'custom-header', 'custom-background' ), true ) ) {
2615          $wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['name'] );
2616  
2617          if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
2618              echo wp_json_encode(
2619                  array(
2620                      'success' => false,
2621                      'data'    => array(
2622                          'message'  => __( 'The uploaded file is not a valid image. Please try again.' ),
2623                          'filename' => esc_html( $_FILES['async-upload']['name'] ),
2624                      ),
2625                  )
2626              );
2627  
2628              wp_die();
2629          }
2630      }
2631  
2632      $attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data );
2633  
2634      if ( is_wp_error( $attachment_id ) ) {
2635          echo wp_json_encode(
2636              array(
2637                  'success' => false,
2638                  'data'    => array(
2639                      'message'  => $attachment_id->get_error_message(),
2640                      'filename' => esc_html( $_FILES['async-upload']['name'] ),
2641                  ),
2642              )
2643          );
2644  
2645          wp_die();
2646      }
2647  
2648      if ( isset( $post_data['context'] ) && isset( $post_data['theme'] ) ) {
2649          if ( 'custom-background' === $post_data['context'] ) {
2650              update_post_meta( $attachment_id, '_wp_attachment_is_custom_background', $post_data['theme'] );
2651          }
2652  
2653          if ( 'custom-header' === $post_data['context'] ) {
2654              update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', $post_data['theme'] );
2655          }
2656      }
2657  
2658      $attachment = wp_prepare_attachment_for_js( $attachment_id );
2659      if ( ! $attachment ) {
2660          wp_die();
2661      }
2662  
2663      echo wp_json_encode(
2664          array(
2665              'success' => true,
2666              'data'    => $attachment,
2667          )
2668      );
2669  
2670      wp_die();
2671  }
2672  
2673  /**
2674   * Handles image editing via AJAX.
2675   *
2676   * @since 3.1.0
2677   */
2678  function wp_ajax_image_editor() {
2679      $attachment_id = (int) $_POST['postid'];
2680  
2681      if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
2682          wp_die( -1 );
2683      }
2684  
2685      check_ajax_referer( "image_editor-$attachment_id" );
2686      require_once  ABSPATH . 'wp-admin/includes/image-edit.php';
2687  
2688      $msg = false;
2689  
2690      switch ( $_POST['do'] ) {
2691          case 'save':
2692              $msg = wp_save_image( $attachment_id );
2693              if ( ! empty( $msg->error ) ) {
2694                  wp_send_json_error( $msg );
2695              }
2696  
2697              wp_send_json_success( $msg );
2698              break;
2699          case 'scale':
2700              $msg = wp_save_image( $attachment_id );
2701              break;
2702          case 'restore':
2703              $msg = wp_restore_image( $attachment_id );
2704              break;
2705      }
2706  
2707      ob_start();
2708      wp_image_editor( $attachment_id, $msg );
2709      $html = ob_get_clean();
2710  
2711      if ( ! empty( $msg->error ) ) {
2712          wp_send_json_error(
2713              array(
2714                  'message' => $msg,
2715                  'html'    => $html,
2716              )
2717          );
2718      }
2719  
2720      wp_send_json_success(
2721          array(
2722              'message' => $msg,
2723              'html'    => $html,
2724          )
2725      );
2726  }
2727  
2728  /**
2729   * Handles setting the featured image via AJAX.
2730   *
2731   * @since 3.1.0
2732   */
2733  function wp_ajax_set_post_thumbnail() {
2734      $json = ! empty( $_REQUEST['json'] ); // New-style request.
2735  
2736      $post_id = (int) $_POST['post_id'];
2737      if ( ! current_user_can( 'edit_post', $post_id ) ) {
2738          wp_die( -1 );
2739      }
2740  
2741      $thumbnail_id = (int) $_POST['thumbnail_id'];
2742  
2743      if ( $json ) {
2744          check_ajax_referer( "update-post_$post_id" );
2745      } else {
2746          check_ajax_referer( "set_post_thumbnail-$post_id" );
2747      }
2748  
2749      if ( -1 === $thumbnail_id ) {
2750          if ( delete_post_thumbnail( $post_id ) ) {
2751              $return = _wp_post_thumbnail_html( null, $post_id );
2752              $json ? wp_send_json_success( $return ) : wp_die( $return );
2753          } else {
2754              wp_die( 0 );
2755          }
2756      }
2757  
2758      if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2759          $return = _wp_post_thumbnail_html( $thumbnail_id, $post_id );
2760          $json ? wp_send_json_success( $return ) : wp_die( $return );
2761      }
2762  
2763      wp_die( 0 );
2764  }
2765  
2766  /**
2767   * Handles retrieving HTML for the featured image via AJAX.
2768   *
2769   * @since 4.6.0
2770   */
2771  function wp_ajax_get_post_thumbnail_html() {
2772      $post_id = (int) $_POST['post_id'];
2773  
2774      check_ajax_referer( "update-post_$post_id" );
2775  
2776      if ( ! current_user_can( 'edit_post', $post_id ) ) {
2777          wp_die( -1 );
2778      }
2779  
2780      $thumbnail_id = (int) $_POST['thumbnail_id'];
2781  
2782      // For backward compatibility, -1 refers to no featured image.
2783      if ( -1 === $thumbnail_id ) {
2784          $thumbnail_id = null;
2785      }
2786  
2787      $return = _wp_post_thumbnail_html( $thumbnail_id, $post_id );
2788      wp_send_json_success( $return );
2789  }
2790  
2791  /**
2792   * Handles setting the featured image for an attachment via AJAX.
2793   *
2794   * @since 4.0.0
2795   *
2796   * @see set_post_thumbnail()
2797   */
2798  function wp_ajax_set_attachment_thumbnail() {
2799      if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) {
2800          wp_send_json_error();
2801      }
2802  
2803      $thumbnail_id = (int) $_POST['thumbnail_id'];
2804      if ( empty( $thumbnail_id ) ) {
2805          wp_send_json_error();
2806      }
2807  
2808      if ( false === check_ajax_referer( 'set-attachment-thumbnail', '_ajax_nonce', false ) ) {
2809          wp_send_json_error();
2810      }
2811  
2812      $post_ids = array();
2813      // For each URL, try to find its corresponding post ID.
2814      foreach ( $_POST['urls'] as $url ) {
2815          $post_id = attachment_url_to_postid( $url );
2816          if ( ! empty( $post_id ) ) {
2817              $post_ids[] = $post_id;
2818          }
2819      }
2820  
2821      if ( empty( $post_ids ) ) {
2822          wp_send_json_error();
2823      }
2824  
2825      $success = 0;
2826      // For each found attachment, set its thumbnail.
2827      foreach ( $post_ids as $post_id ) {
2828          if ( ! current_user_can( 'edit_post', $post_id ) ) {
2829              continue;
2830          }
2831  
2832          if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2833              ++$success;
2834          }
2835      }
2836  
2837      if ( 0 === $success ) {
2838          wp_send_json_error();
2839      } else {
2840          wp_send_json_success();
2841      }
2842  
2843      wp_send_json_error();
2844  }
2845  
2846  /**
2847   * Handles formatting a date via AJAX.
2848   *
2849   * @since 3.1.0
2850   */
2851  function wp_ajax_date_format() {
2852      wp_die( date_i18n( sanitize_option( 'date_format', wp_unslash( $_POST['date'] ) ) ) );
2853  }
2854  
2855  /**
2856   * Handles formatting a time via AJAX.
2857   *
2858   * @since 3.1.0
2859   */
2860  function wp_ajax_time_format() {
2861      wp_die( date_i18n( sanitize_option( 'time_format', wp_unslash( $_POST['date'] ) ) ) );
2862  }
2863  
2864  /**
2865   * Handles saving posts from the fullscreen editor via AJAX.
2866   *
2867   * @since 3.1.0
2868   * @deprecated 4.3.0
2869   */
2870  function wp_ajax_wp_fullscreen_save_post() {
2871      $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
2872  
2873      $post = null;
2874  
2875      if ( $post_id ) {
2876          $post = get_post( $post_id );
2877      }
2878  
2879      check_ajax_referer( 'update-post_' . $post_id, '_wpnonce' );
2880  
2881      $post_id = edit_post();
2882  
2883      if ( is_wp_error( $post_id ) ) {
2884          wp_send_json_error();
2885      }
2886  
2887      if ( $post ) {
2888          $last_date = mysql2date( __( 'F j, Y' ), $post->post_modified );
2889          $last_time = mysql2date( __( 'g:i a' ), $post->post_modified );
2890      } else {
2891          $last_date = date_i18n( __( 'F j, Y' ) );
2892          $last_time = date_i18n( __( 'g:i a' ) );
2893      }
2894  
2895      $last_id = get_post_meta( $post_id, '_edit_last', true );
2896      if ( $last_id ) {
2897          $last_user = get_userdata( $last_id );
2898          /* translators: 1: User's display name, 2: Date of last edit, 3: Time of last edit. */
2899          $last_edited = sprintf( __( 'Last edited by %1$s on %2$s at %3$s' ), esc_html( $last_user->display_name ), $last_date, $last_time );
2900      } else {
2901          /* translators: 1: Date of last edit, 2: Time of last edit. */
2902          $last_edited = sprintf( __( 'Last edited on %1$s at %2$s' ), $last_date, $last_time );
2903      }
2904  
2905      wp_send_json_success( array( 'last_edited' => $last_edited ) );
2906  }
2907  
2908  /**
2909   * Handles removing a post lock via AJAX.
2910   *
2911   * @since 3.1.0
2912   */
2913  function wp_ajax_wp_remove_post_lock() {
2914      if ( empty( $_POST['post_ID'] ) || empty( $_POST['active_post_lock'] ) ) {
2915          wp_die( 0 );
2916      }
2917  
2918      $post_id = (int) $_POST['post_ID'];
2919      $post    = get_post( $post_id );
2920  
2921      if ( ! $post ) {
2922          wp_die( 0 );
2923      }
2924  
2925      check_ajax_referer( 'update-post_' . $post_id );
2926  
2927      if ( ! current_user_can( 'edit_post', $post_id ) ) {
2928          wp_die( -1 );
2929      }
2930  
2931      $active_lock = array_map( 'absint', explode( ':', $_POST['active_post_lock'] ) );
2932  
2933      if ( get_current_user_id() !== $active_lock[1] ) {
2934          wp_die( 0 );
2935      }
2936  
2937      /**
2938       * Filters the post lock window duration.
2939       *
2940       * @since 3.3.0
2941       *
2942       * @param int $interval The interval in seconds the post lock duration
2943       *                      should last, plus 5 seconds. Default 150.
2944       */
2945      $new_lock = ( time() - apply_filters( 'wp_check_post_lock_window', 150 ) + 5 ) . ':' . $active_lock[1];
2946      update_post_meta( $post_id, '_edit_lock', $new_lock, implode( ':', $active_lock ) );
2947      wp_die( 1 );
2948  }
2949  
2950  /**
2951   * Handles dismissing a WordPress pointer via AJAX.
2952   *
2953   * @since 3.1.0
2954   */
2955  function wp_ajax_dismiss_wp_pointer() {
2956      $pointer = $_POST['pointer'];
2957  
2958      if ( sanitize_key( $pointer ) !== $pointer ) {
2959          wp_die( 0 );
2960      }
2961  
2962      //  check_ajax_referer( 'dismiss-pointer_' . $pointer );
2963  
2964      $dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
2965  
2966      if ( in_array( $pointer, $dismissed, true ) ) {
2967          wp_die( 0 );
2968      }
2969  
2970      $dismissed[] = $pointer;
2971      $dismissed   = implode( ',', $dismissed );
2972  
2973      update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
2974      wp_die( 1 );
2975  }
2976  
2977  /**
2978   * Handles getting an attachment via AJAX.
2979   *
2980   * @since 3.5.0
2981   */
2982  function wp_ajax_get_attachment() {
2983      if ( ! isset( $_REQUEST['id'] ) ) {
2984          wp_send_json_error();
2985      }
2986  
2987      $id = absint( $_REQUEST['id'] );
2988      if ( ! $id ) {
2989          wp_send_json_error();
2990      }
2991  
2992      $post = get_post( $id );
2993      if ( ! $post ) {
2994          wp_send_json_error();
2995      }
2996  
2997      if ( 'attachment' !== $post->post_type ) {
2998          wp_send_json_error();
2999      }
3000  
3001      if ( ! current_user_can( 'upload_files' ) ) {
3002          wp_send_json_error();
3003      }
3004  
3005      $attachment = wp_prepare_attachment_for_js( $id );
3006      if ( ! $attachment ) {
3007          wp_send_json_error();
3008      }
3009  
3010      wp_send_json_success( $attachment );
3011  }
3012  
3013  /**
3014   * Handles querying attachments via AJAX.
3015   *
3016   * @since 3.5.0
3017   */
3018  function wp_ajax_query_attachments() {
3019      if ( ! current_user_can( 'upload_files' ) ) {
3020          wp_send_json_error();
3021      }
3022  
3023      $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
3024      $keys  = array(
3025          's',
3026          'order',
3027          'orderby',
3028          'posts_per_page',
3029          'paged',
3030          'post_mime_type',
3031          'post_parent',
3032          'author',
3033          'post__in',
3034          'post__not_in',
3035          'year',
3036          'monthnum',
3037      );
3038  
3039      foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) {
3040          if ( $t->query_var && isset( $query[ $t->query_var ] ) ) {
3041              $keys[] = $t->query_var;
3042          }
3043      }
3044  
3045      $query              = array_intersect_key( $query, array_flip( $keys ) );
3046      $query['post_type'] = 'attachment';
3047  
3048      if (
3049          MEDIA_TRASH &&
3050          ! empty( $_REQUEST['query']['post_status'] ) &&
3051          'trash' === $_REQUEST['query']['post_status']
3052      ) {
3053          $query['post_status'] = 'trash';
3054      } else {
3055          $query['post_status'] = 'inherit';
3056      }
3057  
3058      if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) ) {
3059          $query['post_status'] .= ',private';
3060      }
3061  
3062      // Filter query clauses to include filenames.
3063      if ( isset( $query['s'] ) ) {
3064          add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' );
3065      }
3066  
3067      /**
3068       * Filters the arguments passed to WP_Query during an Ajax
3069       * call for querying attachments.
3070       *
3071       * @since 3.7.0
3072       *
3073       * @see WP_Query::parse_query()
3074       *
3075       * @param array $query An array of query variables.
3076       */
3077      $query             = apply_filters( 'ajax_query_attachments_args', $query );
3078      $attachments_query = new WP_Query( $query );
3079      update_post_parent_caches( $attachments_query->posts );
3080  
3081      $posts       = array_map( 'wp_prepare_attachment_for_js', $attachments_query->posts );
3082      $posts       = array_filter( $posts );
3083      $total_posts = $attachments_query->found_posts;
3084  
3085      if ( $total_posts < 1 ) {
3086          // Out-of-bounds, run the query again without LIMIT for total count.
3087          unset( $query['paged'] );
3088  
3089          $count_query = new WP_Query();
3090          $count_query->query( $query );
3091          $total_posts = $count_query->found_posts;
3092      }
3093  
3094      $posts_per_page = (int) $attachments_query->get( 'posts_per_page' );
3095  
3096      $max_pages = $posts_per_page ? (int) ceil( $total_posts / $posts_per_page ) : 0;
3097  
3098      header( 'X-WP-Total: ' . (int) $total_posts );
3099      header( 'X-WP-TotalPages: ' . $max_pages );
3100  
3101      wp_send_json_success( $posts );
3102  }
3103  
3104  /**
3105   * Handles updating attachment attributes via AJAX.
3106   *
3107   * @since 3.5.0
3108   */
3109  function wp_ajax_save_attachment() {
3110      if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) ) {
3111          wp_send_json_error();
3112      }
3113  
3114      $id = absint( $_REQUEST['id'] );
3115      if ( ! $id ) {
3116          wp_send_json_error();
3117      }
3118  
3119      check_ajax_referer( 'update-post_' . $id, 'nonce' );
3120  
3121      if ( ! current_user_can( 'edit_post', $id ) ) {
3122          wp_send_json_error();
3123      }
3124  
3125      $changes = $_REQUEST['changes'];
3126      $post    = get_post( $id, ARRAY_A );
3127  
3128      if ( 'attachment' !== $post['post_type'] ) {
3129          wp_send_json_error();
3130      }
3131  
3132      if ( isset( $changes['parent'] ) ) {
3133          $post['post_parent'] = $changes['parent'];
3134      }
3135  
3136      if ( isset( $changes['title'] ) ) {
3137          $post['post_title'] = $changes['title'];
3138      }
3139  
3140      if ( isset( $changes['caption'] ) ) {
3141          $post['post_excerpt'] = $changes['caption'];
3142      }
3143  
3144      if ( isset( $changes['description'] ) ) {
3145          $post['post_content'] = $changes['description'];
3146      }
3147  
3148      if ( MEDIA_TRASH && isset( $changes['status'] ) ) {
3149          $post['post_status'] = $changes['status'];
3150      }
3151  
3152      if ( isset( $changes['alt'] ) ) {
3153          $alt = wp_unslash( $changes['alt'] );
3154          if ( get_post_meta( $id, '_wp_attachment_image_alt', true ) !== $alt ) {
3155              $alt = wp_strip_all_tags( $alt, true );
3156              update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) );
3157          }
3158      }
3159  
3160      if ( wp_attachment_is( 'audio', $post['ID'] ) ) {
3161          $changed = false;
3162          $id3data = wp_get_attachment_metadata( $post['ID'] );
3163  
3164          if ( ! is_array( $id3data ) ) {
3165              $changed = true;
3166              $id3data = array();
3167          }
3168  
3169          foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
3170              if ( isset( $changes[ $key ] ) ) {
3171                  $changed         = true;
3172                  $id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
3173              }
3174          }
3175  
3176          if ( $changed ) {
3177              wp_update_attachment_metadata( $id, $id3data );
3178          }
3179      }
3180  
3181      if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
3182          wp_delete_post( $id );
3183      } else {
3184          wp_update_post( $post );
3185      }
3186  
3187      wp_send_json_success();
3188  }
3189  
3190  /**
3191   * Handles saving backward compatible attachment attributes via AJAX.
3192   *
3193   * @since 3.5.0
3194   */
3195  function wp_ajax_save_attachment_compat() {
3196      if ( ! isset( $_REQUEST['id'] ) ) {
3197          wp_send_json_error();
3198      }
3199  
3200      $id = absint( $_REQUEST['id'] );
3201      if ( ! $id ) {
3202          wp_send_json_error();
3203      }
3204  
3205      if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) ) {
3206          wp_send_json_error();
3207      }
3208  
3209      $attachment_data = $_REQUEST['attachments'][ $id ];
3210  
3211      check_ajax_referer( 'update-post_' . $id, 'nonce' );
3212  
3213      if ( ! current_user_can( 'edit_post', $id ) ) {
3214          wp_send_json_error();
3215      }
3216  
3217      $post = get_post( $id, ARRAY_A );
3218  
3219      if ( 'attachment' !== $post['post_type'] ) {
3220          wp_send_json_error();
3221      }
3222  
3223      /** This filter is documented in wp-admin/includes/media.php */
3224      $post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data );
3225  
3226      if ( isset( $post['errors'] ) ) {
3227          $errors = $post['errors']; // @todo return me and display me!
3228          unset( $post['errors'] );
3229      }
3230  
3231      wp_update_post( $post );
3232  
3233      foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
3234          if ( isset( $attachment_data[ $taxonomy ] ) ) {
3235              wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false );
3236          }
3237      }
3238  
3239      $attachment = wp_prepare_attachment_for_js( $id );
3240  
3241      if ( ! $attachment ) {
3242          wp_send_json_error();
3243      }
3244  
3245      wp_send_json_success( $attachment );
3246  }
3247  
3248  /**
3249   * Handles saving the attachment order via AJAX.
3250   *
3251   * @since 3.5.0
3252   */
3253  function wp_ajax_save_attachment_order() {
3254      if ( ! isset( $_REQUEST['post_id'] ) ) {
3255          wp_send_json_error();
3256      }
3257  
3258      $post_id = absint( $_REQUEST['post_id'] );
3259      if ( ! $post_id ) {
3260          wp_send_json_error();
3261      }
3262  
3263      if ( empty( $_REQUEST['attachments'] ) ) {
3264          wp_send_json_error();
3265      }
3266  
3267      check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
3268  
3269      $attachments = $_REQUEST['attachments'];
3270  
3271      if ( ! current_user_can( 'edit_post', $post_id ) ) {
3272          wp_send_json_error();
3273      }
3274  
3275      foreach ( $attachments as $attachment_id => $menu_order ) {
3276          if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
3277              continue;
3278          }
3279  
3280          $attachment = get_post( $attachment_id );
3281  
3282          if ( ! $attachment ) {
3283              continue;
3284          }
3285  
3286          if ( 'attachment' !== $attachment->post_type ) {
3287              continue;
3288          }
3289  
3290          wp_update_post(
3291              array(
3292                  'ID'         => $attachment_id,
3293                  'menu_order' => $menu_order,
3294              )
3295          );
3296      }
3297  
3298      wp_send_json_success();
3299  }
3300  
3301  /**
3302   * Handles sending an attachment to the editor via AJAX.
3303   *
3304   * Generates the HTML to send an attachment to the editor.
3305   * Backward compatible with the {@see 'media_send_to_editor'} filter
3306   * and the chain of filters that follow.
3307   *
3308   * @since 3.5.0
3309   */
3310  function wp_ajax_send_attachment_to_editor() {
3311      check_ajax_referer( 'media-send-to-editor', 'nonce' );
3312  
3313      $attachment = wp_unslash( $_POST['attachment'] );
3314  
3315      $id = (int) $attachment['id'];
3316  
3317      $post = get_post( $id );
3318      if ( ! $post ) {
3319          wp_send_json_error();
3320      }
3321  
3322      if ( 'attachment' !== $post->post_type ) {
3323          wp_send_json_error();
3324      }
3325  
3326      if ( current_user_can( 'edit_post', $id ) ) {
3327          // If this attachment is unattached, attach it. Primarily a back compat thing.
3328          $insert_into_post_id = (int) $_POST['post_id'];
3329  
3330          if ( 0 === $post->post_parent && $insert_into_post_id ) {
3331              wp_update_post(
3332                  array(
3333                      'ID'          => $id,
3334                      'post_parent' => $insert_into_post_id,
3335                  )
3336              );
3337          }
3338      }
3339  
3340      $url = empty( $attachment['url'] ) ? '' : $attachment['url'];
3341      $rel = ( str_contains( $url, 'attachment_id' ) || get_attachment_link( $id ) === $url );
3342  
3343      remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
3344  
3345      if ( str_starts_with( $post->post_mime_type, 'image' ) ) {
3346          $align = isset( $attachment['align'] ) ? $attachment['align'] : 'none';
3347          $size  = isset( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium';
3348          $alt   = isset( $attachment['image_alt'] ) ? $attachment['image_alt'] : '';
3349  
3350          // No whitespace-only captions.
3351          $caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : '';
3352          if ( '' === trim( $caption ) ) {
3353              $caption = '';
3354          }
3355  
3356          $title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
3357          $html  = get_image_send_to_editor( $id, $caption, $title, $align, $url, $rel, $size, $alt );
3358      } elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post ) ) {
3359          $html = stripslashes_deep( $_POST['html'] );
3360      } else {
3361          $html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : '';
3362          $rel  = $rel ? ' rel="attachment wp-att-' . $id . '"' : ''; // Hard-coded string, $id is already sanitized.
3363  
3364          if ( ! empty( $url ) ) {
3365              $html = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
3366          }
3367      }
3368  
3369      /** This filter is documented in wp-admin/includes/media.php */
3370      $html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
3371  
3372      wp_send_json_success( $html );
3373  }
3374  
3375  /**
3376   * Handles sending a link to the editor via AJAX.
3377   *
3378   * Generates the HTML to send a non-image embed link to the editor.
3379   *
3380   * Backward compatible with the following filters:
3381   * - file_send_to_editor_url
3382   * - audio_send_to_editor_url
3383   * - video_send_to_editor_url
3384   *
3385   * @since 3.5.0
3386   *
3387   * @global WP_Post  $post     Global post object.
3388   * @global WP_Embed $wp_embed WordPress Embed object.
3389   */
3390  function wp_ajax_send_link_to_editor() {
3391      global $post, $wp_embed;
3392  
3393      check_ajax_referer( 'media-send-to-editor', 'nonce' );
3394  
3395      $src = wp_unslash( $_POST['src'] );
3396      if ( ! $src ) {
3397          wp_send_json_error();
3398      }
3399  
3400      if ( ! strpos( $src, '://' ) ) {
3401          $src = 'http://' . $src;
3402      }
3403  
3404      $src = sanitize_url( $src );
3405      if ( ! $src ) {
3406          wp_send_json_error();
3407      }
3408  
3409      $link_text = trim( wp_unslash( $_POST['link_text'] ) );
3410      if ( ! $link_text ) {
3411          $link_text = wp_basename( $src );
3412      }
3413  
3414      $post = get_post( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
3415  
3416      // Ping WordPress for an embed.
3417      $check_embed = $wp_embed->run_shortcode( '[embed]' . $src . '[/embed]' );
3418  
3419      // Fallback that WordPress creates when no oEmbed was found.
3420      $fallback = $wp_embed->maybe_make_link( $src );
3421  
3422      if ( $check_embed !== $fallback ) {
3423          // TinyMCE view for [embed] will parse this.
3424          $html = '[embed]' . $src . '[/embed]';
3425      } elseif ( $link_text ) {
3426          $html = '<a href="' . esc_url( $src ) . '">' . $link_text . '</a>';
3427      } else {
3428          $html = '';
3429      }
3430  
3431      // Figure out what filter to run:
3432      $type = 'file';
3433      $ext  = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src );
3434      if ( $ext ) {
3435          $ext_type = wp_ext2type( $ext );
3436          if ( 'audio' === $ext_type || 'video' === $ext_type ) {
3437              $type = $ext_type;
3438          }
3439      }
3440  
3441      /** This filter is documented in wp-admin/includes/media.php */
3442      $html = apply_filters( "{$type}_send_to_editor_url", $html, $src, $link_text );
3443  
3444      wp_send_json_success( $html );
3445  }
3446  
3447  /**
3448   * Handles the Heartbeat API via AJAX.
3449   *
3450   * Runs when the user is logged in.
3451   *
3452   * @since 3.6.0
3453   */
3454  function wp_ajax_heartbeat() {
3455      if ( empty( $_POST['_nonce'] ) ) {
3456          wp_send_json_error();
3457      }
3458  
3459      $response    = array();
3460      $data        = array();
3461      $nonce_state = wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' );
3462  
3463      // 'screen_id' is the same as $current_screen->id and the JS global 'pagenow'.
3464      if ( ! empty( $_POST['screen_id'] ) ) {
3465          $screen_id = sanitize_key( $_POST['screen_id'] );
3466      } else {
3467          $screen_id = 'front';
3468      }
3469  
3470      if ( ! empty( $_POST['data'] ) ) {
3471          $data = wp_unslash( (array) $_POST['data'] );
3472      }
3473  
3474      if ( 1 !== $nonce_state ) {
3475          /**
3476           * Filters the nonces to send to the New/Edit Post screen.
3477           *
3478           * @since 4.3.0
3479           *
3480           * @param array  $response  The Heartbeat response.
3481           * @param array  $data      The $_POST data sent.
3482           * @param string $screen_id The screen ID.
3483           */
3484          $response = apply_filters( 'wp_refresh_nonces', $response, $data, $screen_id );
3485  
3486          if ( false === $nonce_state ) {
3487              // User is logged in but nonces have expired.
3488              $response['nonces_expired'] = true;
3489              wp_send_json( $response );
3490          }
3491      }
3492  
3493      if ( ! empty( $data ) ) {
3494          /**
3495           * Filters the Heartbeat response received.
3496           *
3497           * @since 3.6.0
3498           *
3499           * @param array  $response  The Heartbeat response.
3500           * @param array  $data      The $_POST data sent.
3501           * @param string $screen_id The screen ID.
3502           */
3503          $response = apply_filters( 'heartbeat_received', $response, $data, $screen_id );
3504      }
3505  
3506      /**
3507       * Filters the Heartbeat response sent.
3508       *
3509       * @since 3.6.0
3510       *
3511       * @param array  $response  The Heartbeat response.
3512       * @param string $screen_id The screen ID.
3513       */
3514      $response = apply_filters( 'heartbeat_send', $response, $screen_id );
3515  
3516      /**
3517       * Fires when Heartbeat ticks in logged-in environments.
3518       *
3519       * Allows the transport to be easily replaced with long-polling.
3520       *
3521       * @since 3.6.0
3522       *
3523       * @param array  $response  The Heartbeat response.
3524       * @param string $screen_id The screen ID.
3525       */
3526      do_action( 'heartbeat_tick', $response, $screen_id );
3527  
3528      // Send the current time according to the server.
3529      $response['server_time'] = time();
3530  
3531      wp_send_json( $response );
3532  }
3533  
3534  /**
3535   * Handles getting revision diffs via AJAX.
3536   *
3537   * @since 3.6.0
3538   */
3539  function wp_ajax_get_revision_diffs() {
3540      require  ABSPATH . 'wp-admin/includes/revision.php';
3541  
3542      $post = get_post( (int) $_REQUEST['post_id'] );
3543      if ( ! $post ) {
3544          wp_send_json_error();
3545      }
3546  
3547      if ( ! current_user_can( 'edit_post', $post->ID ) ) {
3548          wp_send_json_error();
3549      }
3550  
3551      // Really just pre-loading the cache here.
3552      $revisions = wp_get_post_revisions( $post->ID, array( 'check_enabled' => false ) );
3553      if ( ! $revisions ) {
3554          wp_send_json_error();
3555      }
3556  
3557      $return = array();
3558  
3559      // Increase the script timeout limit to allow ample time for diff UI setup.
3560      if ( function_exists( 'set_time_limit' ) ) {
3561          set_time_limit( 5 * MINUTE_IN_SECONDS );
3562      }
3563  
3564      foreach ( $_REQUEST['compare'] as $compare_key ) {
3565          list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
3566  
3567          $return[] = array(
3568              'id'     => $compare_key,
3569              'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
3570          );
3571      }
3572      wp_send_json_success( $return );
3573  }
3574  
3575  /**
3576   * Handles auto-saving the selected color scheme for
3577   * a user's own profile via AJAX.
3578   *
3579   * @since 3.8.0
3580   *
3581   * @global array $_wp_admin_css_colors
3582   */
3583  function wp_ajax_save_user_color_scheme() {
3584      global $_wp_admin_css_colors;
3585  
3586      check_ajax_referer( 'save-color-scheme', 'nonce' );
3587  
3588      $color_scheme = sanitize_key( $_POST['color_scheme'] );
3589  
3590      if ( ! isset( $_wp_admin_css_colors[ $color_scheme ] ) ) {
3591          wp_send_json_error();
3592      }
3593  
3594      $previous_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true );
3595      update_user_meta( get_current_user_id(), 'admin_color', $color_scheme );
3596  
3597      wp_send_json_success(
3598          array(
3599              'previousScheme' => 'admin-color-' . $previous_color_scheme,
3600              'currentScheme'  => 'admin-color-' . $color_scheme,
3601          )
3602      );
3603  }
3604  
3605  /**
3606   * Handles getting themes from themes_api() via AJAX.
3607   *
3608   * @since 3.9.0
3609   *
3610   * @global array $themes_allowedtags
3611   * @global array $theme_field_defaults
3612   */
3613  function wp_ajax_query_themes() {
3614      global $themes_allowedtags, $theme_field_defaults;
3615  
3616      if ( ! current_user_can( 'install_themes' ) ) {
3617          wp_send_json_error();
3618      }
3619  
3620      $args = wp_parse_args(
3621          wp_unslash( $_REQUEST['request'] ),
3622          array(
3623              'per_page' => 20,
3624              'fields'   => array_merge(
3625                  (array) $theme_field_defaults,
3626                  array(
3627                      'reviews_url' => true, // Explicitly request the reviews URL to be linked from the Add Themes screen.
3628                  )
3629              ),
3630          )
3631      );
3632  
3633      if ( isset( $args['browse'] ) && 'favorites' === $args['browse'] && ! isset( $args['user'] ) ) {
3634          $user = get_user_option( 'wporg_favorites' );
3635          if ( $user ) {
3636              $args['user'] = $user;
3637          }
3638      }
3639  
3640      $old_filter = isset( $args['browse'] ) ? $args['browse'] : 'search';
3641  
3642      /** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */
3643      $args = apply_filters( 'install_themes_table_api_args_' . $old_filter, $args );
3644  
3645      $api = themes_api( 'query_themes', $args );
3646  
3647      if ( is_wp_error( $api ) ) {
3648          wp_send_json_error();
3649      }
3650  
3651      $update_php = network_admin_url( 'update.php?action=install-theme' );
3652  
3653      $installed_themes = search_theme_directories();
3654  
3655      if ( false === $installed_themes ) {
3656          $installed_themes = array();
3657      }
3658  
3659      foreach ( $installed_themes as $theme_slug => $theme_data ) {
3660          // Ignore child themes.
3661          if ( str_contains( $theme_slug, '/' ) ) {
3662              unset( $installed_themes[ $theme_slug ] );
3663          }
3664      }
3665  
3666      foreach ( $api->themes as &$theme ) {
3667          $theme->install_url = add_query_arg(
3668              array(
3669                  'theme'    => $theme->slug,
3670                  '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ),
3671              ),
3672              $update_php
3673          );
3674  
3675          if ( current_user_can( 'switch_themes' ) ) {
3676              if ( is_multisite() ) {
3677                  $theme->activate_url = add_query_arg(
3678                      array(
3679                          'action'   => 'enable',
3680                          '_wpnonce' => wp_create_nonce( 'enable-theme_' . $theme->slug ),
3681                          'theme'    => $theme->slug,
3682                      ),
3683                      network_admin_url( 'themes.php' )
3684                  );
3685              } else {
3686                  $theme->activate_url = add_query_arg(
3687                      array(
3688                          'action'     => 'activate',
3689                          '_wpnonce'   => wp_create_nonce( 'switch-theme_' . $theme->slug ),
3690                          'stylesheet' => $theme->slug,
3691                      ),
3692                      admin_url( 'themes.php' )
3693                  );
3694              }
3695          }
3696  
3697          $is_theme_installed = array_key_exists( $theme->slug, $installed_themes );
3698  
3699          // We only care about installed themes.
3700          $theme->block_theme = $is_theme_installed && wp_get_theme( $theme->slug )->is_block_theme();
3701  
3702          if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
3703              $customize_url = $theme->block_theme ? admin_url( 'site-editor.php' ) : wp_customize_url( $theme->slug );
3704  
3705              $theme->customize_url = add_query_arg(
3706                  array(
3707                      'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
3708                  ),
3709                  $customize_url
3710              );
3711          }
3712  
3713          $theme->name        = wp_kses( $theme->name, $themes_allowedtags );
3714          $theme->author      = wp_kses( $theme->author['display_name'], $themes_allowedtags );
3715          $theme->version     = wp_kses( $theme->version, $themes_allowedtags );
3716          $theme->description = wp_kses( $theme->description, $themes_allowedtags );
3717  
3718          $theme->stars = wp_star_rating(
3719              array(
3720                  'rating' => $theme->rating,
3721                  'type'   => 'percent',
3722                  'number' => $theme->num_ratings,
3723                  'echo'   => false,
3724              )
3725          );
3726  
3727          $theme->num_ratings    = number_format_i18n( $theme->num_ratings );
3728          $theme->preview_url    = set_url_scheme( $theme->preview_url );
3729          $theme->compatible_wp  = is_wp_version_compatible( $theme->requires );
3730          $theme->compatible_php = is_php_version_compatible( $theme->requires_php );
3731      }
3732  
3733      wp_send_json_success( $api );
3734  }
3735  
3736  /**
3737   * Applies [embed] Ajax handlers to a string.
3738   *
3739   * @since 4.0.0
3740   *
3741   * @global WP_Post    $post          Global post object.
3742   * @global WP_Embed   $wp_embed      WordPress Embed object.
3743   * @global WP_Scripts $wp_scripts
3744   * @global int        $content_width
3745   */
3746  function wp_ajax_parse_embed() {
3747      global $post, $wp_embed, $content_width;
3748  
3749      if ( empty( $_POST['shortcode'] ) ) {
3750          wp_send_json_error();
3751      }
3752  
3753      $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
3754  
3755      if ( $post_id > 0 ) {
3756          $post = get_post( $post_id );
3757  
3758          if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3759              wp_send_json_error();
3760          }
3761          setup_postdata( $post );
3762      } elseif ( ! current_user_can( 'edit_posts' ) ) { // See WP_oEmbed_Controller::get_proxy_item_permissions_check().
3763          wp_send_json_error();
3764      }
3765  
3766      $shortcode = wp_unslash( $_POST['shortcode'] );
3767  
3768      preg_match( '/' . get_shortcode_regex() . '/s', $shortcode, $matches );
3769      $atts = shortcode_parse_atts( $matches[3] );
3770  
3771      if ( ! empty( $matches[5] ) ) {
3772          $url = $matches[5];
3773      } elseif ( ! empty( $atts['src'] ) ) {
3774          $url = $atts['src'];
3775      } else {
3776          $url = '';
3777      }
3778  
3779      $parsed                         = false;
3780      $wp_embed->return_false_on_fail = true;
3781  
3782      if ( 0 === $post_id ) {
3783          /*
3784           * Refresh oEmbeds cached outside of posts that are past their TTL.
3785           * Posts are excluded because they have separate logic for refreshing
3786           * their post meta caches. See WP_Embed::cache_oembed().
3787           */
3788          $wp_embed->usecache = false;
3789      }
3790  
3791      if ( is_ssl() && str_starts_with( $url, 'http://' ) ) {
3792          /*
3793           * Admin is ssl and the user pasted non-ssl URL.
3794           * Check if the provider supports ssl embeds and use that for the preview.
3795           */
3796          $ssl_shortcode = preg_replace( '%^(\\[embed[^\\]]*\\])http://%i', '$1https://', $shortcode );
3797          $parsed        = $wp_embed->run_shortcode( $ssl_shortcode );
3798  
3799          if ( ! $parsed ) {
3800              $no_ssl_support = true;
3801          }
3802      }
3803  
3804      // Set $content_width so any embeds fit in the destination iframe.
3805      if ( isset( $_POST['maxwidth'] ) && is_numeric( $_POST['maxwidth'] ) && $_POST['maxwidth'] > 0 ) {
3806          if ( ! isset( $content_width ) ) {
3807              $content_width = (int) $_POST['maxwidth'];
3808          } else {
3809              $content_width = min( $content_width, (int) $_POST['maxwidth'] );
3810          }
3811      }
3812  
3813      if ( $url && ! $parsed ) {
3814          $parsed = $wp_embed->run_shortcode( $shortcode );
3815      }
3816  
3817      if ( ! $parsed ) {
3818          wp_send_json_error(
3819              array(
3820                  'type'    => 'not-embeddable',
3821                  /* translators: %s: URL that could not be embedded. */
3822                  'message' => sprintf( __( '%s failed to embed.' ), '<code>' . esc_html( $url ) . '</code>' ),
3823              )
3824          );
3825      }
3826  
3827      if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
3828          $styles     = '';
3829          $mce_styles = wpview_media_sandbox_styles();
3830  
3831          foreach ( $mce_styles as $style ) {
3832              $styles .= sprintf( '<link rel="stylesheet" href="%s" />', $style );
3833          }
3834  
3835          $html = do_shortcode( $parsed );
3836  
3837          global $wp_scripts;
3838  
3839          if ( ! empty( $wp_scripts ) ) {
3840              $wp_scripts->done = array();
3841          }
3842  
3843          ob_start();
3844          wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
3845          $scripts = ob_get_clean();
3846  
3847          $parsed = $styles . $html . $scripts;
3848      }
3849  
3850      if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
3851          preg_match( '%<link [^>]*href="http://%', $parsed ) ) ) ) {
3852          // Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
3853          wp_send_json_error(
3854              array(
3855                  'type'    => 'not-ssl',
3856                  'message' => __( 'This preview is unavailable in the editor.' ),
3857              )
3858          );
3859      }
3860  
3861      $return = array(
3862          'body' => $parsed,
3863          'attr' => $wp_embed->last_attr,
3864      );
3865  
3866      if ( str_contains( $parsed, 'class="wp-embedded-content' ) ) {
3867          if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
3868              $script_src = includes_url( 'js/wp-embed.js' );
3869          } else {
3870              $script_src = includes_url( 'js/wp-embed.min.js' );
3871          }
3872  
3873          $return['head']    = '<script src="' . $script_src . '"></script>';
3874          $return['sandbox'] = true;
3875      }
3876  
3877      wp_send_json_success( $return );
3878  }
3879  
3880  /**
3881   * @since 4.0.0
3882   *
3883   * @global WP_Post    $post       Global post object.
3884   * @global WP_Scripts $wp_scripts
3885   */
3886  function wp_ajax_parse_media_shortcode() {
3887      global $post, $wp_scripts;
3888  
3889      if ( empty( $_POST['shortcode'] ) ) {
3890          wp_send_json_error();
3891      }
3892  
3893      $shortcode = wp_unslash( $_POST['shortcode'] );
3894  
3895      // Only process previews for media related shortcodes:
3896      $found_shortcodes = get_shortcode_tags_in_content( $shortcode );
3897      $media_shortcodes = array(
3898          'audio',
3899          'embed',
3900          'playlist',
3901          'video',
3902          'gallery',
3903      );
3904  
3905      $other_shortcodes = array_diff( $found_shortcodes, $media_shortcodes );
3906  
3907      if ( ! empty( $other_shortcodes ) ) {
3908          wp_send_json_error();
3909      }
3910  
3911      if ( ! empty( $_POST['post_ID'] ) ) {
3912          $post = get_post( (int) $_POST['post_ID'] );
3913      }
3914  
3915      // The embed shortcode requires a post.
3916      if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3917          if ( in_array( 'embed', $found_shortcodes, true ) ) {
3918              wp_send_json_error();
3919          }
3920      } else {
3921          setup_postdata( $post );
3922      }
3923  
3924      $parsed = do_shortcode( $shortcode );
3925  
3926      if ( empty( $parsed ) ) {
3927          wp_send_json_error(
3928              array(
3929                  'type'    => 'no-items',
3930                  'message' => __( 'No items found.' ),
3931              )
3932          );
3933      }
3934  
3935      $head   = '';
3936      $styles = wpview_media_sandbox_styles();
3937  
3938      foreach ( $styles as $style ) {
3939          $head .= '<link type="text/css" rel="stylesheet" href="' . $style . '">';
3940      }
3941  
3942      if ( ! empty( $wp_scripts ) ) {
3943          $wp_scripts->done = array();
3944      }
3945  
3946      ob_start();
3947  
3948      echo $parsed;
3949  
3950      if ( 'playlist' === $_REQUEST['type'] ) {
3951          wp_underscore_playlist_templates();
3952  
3953          wp_print_scripts( 'wp-playlist' );
3954      } else {
3955          wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
3956      }
3957  
3958      wp_send_json_success(
3959          array(
3960              'head' => $head,
3961              'body' => ob_get_clean(),
3962          )
3963      );
3964  }
3965  
3966  /**
3967   * Handles destroying multiple open sessions for a user via AJAX.
3968   *
3969   * @since 4.1.0
3970   */
3971  function wp_ajax_destroy_sessions() {
3972      $user = get_userdata( (int) $_POST['user_id'] );
3973  
3974      if ( $user ) {
3975          if ( ! current_user_can( 'edit_user', $user->ID ) ) {
3976              $user = false;
3977          } elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
3978              $user = false;
3979          }
3980      }
3981  
3982      if ( ! $user ) {
3983          wp_send_json_error(
3984              array(
3985                  'message' => __( 'Could not log out user sessions. Please try again.' ),
3986              )
3987          );
3988      }
3989  
3990      $sessions = WP_Session_Tokens::get_instance( $user->ID );
3991  
3992      if ( get_current_user_id() === $user->ID ) {
3993          $sessions->destroy_others( wp_get_session_token() );
3994          $message = __( 'You are now logged out everywhere else.' );
3995      } else {
3996          $sessions->destroy_all();
3997          /* translators: %s: User's display name. */
3998          $message = sprintf( __( '%s has been logged out.' ), $user->display_name );
3999      }
4000  
4001      wp_send_json_success( array( 'message' => $message ) );
4002  }
4003  
4004  /**
4005   * Handles cropping an image via AJAX.
4006   *
4007   * @since 4.3.0
4008   */
4009  function wp_ajax_crop_image() {
4010      $attachment_id = absint( $_POST['id'] );
4011  
4012      check_ajax_referer( 'image_editor-' . $attachment_id, 'nonce' );
4013  
4014      if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
4015          wp_send_json_error();
4016      }
4017  
4018      $context = str_replace( '_', '-', $_POST['context'] );
4019      $data    = array_map( 'absint', $_POST['cropDetails'] );
4020      $cropped = wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
4021  
4022      if ( ! $cropped || is_wp_error( $cropped ) ) {
4023          wp_send_json_error( array( 'message' => __( 'Image could not be processed.' ) ) );
4024      }
4025  
4026      switch ( $context ) {
4027          case 'site-icon':
4028              require_once  ABSPATH . 'wp-admin/includes/class-wp-site-icon.php';
4029              $wp_site_icon = new WP_Site_Icon();
4030  
4031              // Skip creating a new attachment if the attachment is a Site Icon.
4032              if ( get_post_meta( $attachment_id, '_wp_attachment_context', true ) === $context ) {
4033  
4034                  // Delete the temporary cropped file, we don't need it.
4035                  wp_delete_file( $cropped );
4036  
4037                  // Additional sizes in wp_prepare_attachment_for_js().
4038                  add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
4039                  break;
4040              }
4041  
4042              /** This filter is documented in wp-admin/includes/class-custom-image-header.php */
4043              $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
4044  
4045              // Copy attachment properties.
4046              $attachment = wp_copy_parent_attachment_properties( $cropped, $attachment_id, $context );
4047  
4048              // Update the attachment.
4049              add_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
4050              $attachment_id = $wp_site_icon->insert_attachment( $attachment, $cropped );
4051              remove_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
4052  
4053              // Additional sizes in wp_prepare_attachment_for_js().
4054              add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
4055              break;
4056  
4057          default:
4058              /**
4059               * Fires before a cropped image is saved.
4060               *
4061               * Allows to add filters to modify the way a cropped image is saved.
4062               *
4063               * @since 4.3.0
4064               *
4065               * @param string $context       The Customizer control requesting the cropped image.
4066               * @param int    $attachment_id The attachment ID of the original image.
4067               * @param string $cropped       Path to the cropped image file.
4068               */
4069              do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
4070  
4071              /** This filter is documented in wp-admin/includes/class-custom-image-header.php */
4072              $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
4073  
4074              // Copy attachment properties.
4075              $attachment = wp_copy_parent_attachment_properties( $cropped, $attachment_id, $context );
4076  
4077              $attachment_id = wp_insert_attachment( $attachment, $cropped );
4078              $metadata      = wp_generate_attachment_metadata( $attachment_id, $cropped );
4079  
4080              /**
4081               * Filters the cropped image attachment metadata.
4082               *
4083               * @since 4.3.0
4084               *
4085               * @see wp_generate_attachment_metadata()
4086               *
4087               * @param array $metadata Attachment metadata.
4088               */
4089              $metadata = apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
4090              wp_update_attachment_metadata( $attachment_id, $metadata );
4091  
4092              /**
4093               * Filters the attachment ID for a cropped image.
4094               *
4095               * @since 4.3.0
4096               *
4097               * @param int    $attachment_id The attachment ID of the cropped image.
4098               * @param string $context       The Customizer control requesting the cropped image.
4099               */
4100              $attachment_id = apply_filters( 'wp_ajax_cropped_attachment_id', $attachment_id, $context );
4101      }
4102  
4103      wp_send_json_success( wp_prepare_attachment_for_js( $attachment_id ) );
4104  }
4105  
4106  /**
4107   * Handles generating a password via AJAX.
4108   *
4109   * @since 4.4.0
4110   */
4111  function wp_ajax_generate_password() {
4112      wp_send_json_success( wp_generate_password( 24 ) );
4113  }
4114  
4115  /**
4116   * Handles generating a password in the no-privilege context via AJAX.
4117   *
4118   * @since 5.7.0
4119   */
4120  function wp_ajax_nopriv_generate_password() {
4121      wp_send_json_success( wp_generate_password( 24 ) );
4122  }
4123  
4124  /**
4125   * Handles saving the user's WordPress.org username via AJAX.
4126   *
4127   * @since 4.4.0
4128   */
4129  function wp_ajax_save_wporg_username() {
4130      if ( ! current_user_can( 'install_themes' ) && ! current_user_can( 'install_plugins' ) ) {
4131          wp_send_json_error();
4132      }
4133  
4134      check_ajax_referer( 'save_wporg_username_' . get_current_user_id() );
4135  
4136      $username = isset( $_REQUEST['username'] ) ? wp_unslash( $_REQUEST['username'] ) : false;
4137  
4138      if ( ! $username ) {
4139          wp_send_json_error();
4140      }
4141  
4142      wp_send_json_success( update_user_meta( get_current_user_id(), 'wporg_favorites', $username ) );
4143  }
4144  
4145  /**
4146   * Handles installing a theme via AJAX.
4147   *
4148   * @since 4.6.0
4149   *
4150   * @see Theme_Upgrader
4151   *
4152   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4153   */
4154  function wp_ajax_install_theme() {
4155      check_ajax_referer( 'updates' );
4156  
4157      if ( empty( $_POST['slug'] ) ) {
4158          wp_send_json_error(
4159              array(
4160                  'slug'         => '',
4161                  'errorCode'    => 'no_theme_specified',
4162                  'errorMessage' => __( 'No theme specified.' ),
4163              )
4164          );
4165      }
4166  
4167      $slug = sanitize_key( wp_unslash( $_POST['slug'] ) );
4168  
4169      $status = array(
4170          'install' => 'theme',
4171          'slug'    => $slug,
4172      );
4173  
4174      if ( ! current_user_can( 'install_themes' ) ) {
4175          $status['errorMessage'] = __( 'Sorry, you are not allowed to install themes on this site.' );
4176          wp_send_json_error( $status );
4177      }
4178  
4179      require_once  ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4180      require_once  ABSPATH . 'wp-admin/includes/theme.php';
4181  
4182      $api = themes_api(
4183          'theme_information',
4184          array(
4185              'slug'   => $slug,
4186              'fields' => array( 'sections' => false ),
4187          )
4188      );
4189  
4190      if ( is_wp_error( $api ) ) {
4191          $status['errorMessage'] = $api->get_error_message();
4192          wp_send_json_error( $status );
4193      }
4194  
4195      $skin     = new WP_Ajax_Upgrader_Skin();
4196      $upgrader = new Theme_Upgrader( $skin );
4197      $result   = $upgrader->install( $api->download_link );
4198  
4199      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4200          $status['debug'] = $skin->get_upgrade_messages();
4201      }
4202  
4203      if ( is_wp_error( $result ) ) {
4204          $status['errorCode']    = $result->get_error_code();
4205          $status['errorMessage'] = $result->get_error_message();
4206          wp_send_json_error( $status );
4207      } elseif ( is_wp_error( $skin->result ) ) {
4208          $status['errorCode']    = $skin->result->get_error_code();
4209          $status['errorMessage'] = $skin->result->get_error_message();
4210          wp_send_json_error( $status );
4211      } elseif ( $skin->get_errors()->has_errors() ) {
4212          $status['errorMessage'] = $skin->get_error_messages();
4213          wp_send_json_error( $status );
4214      } elseif ( is_null( $result ) ) {
4215          global $wp_filesystem;
4216  
4217          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4218          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4219  
4220          // Pass through the error from WP_Filesystem if one was raised.
4221          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4222              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4223          }
4224  
4225          wp_send_json_error( $status );
4226      }
4227  
4228      $status['themeName'] = wp_get_theme( $slug )->get( 'Name' );
4229  
4230      if ( current_user_can( 'switch_themes' ) ) {
4231          if ( is_multisite() ) {
4232              $status['activateUrl'] = add_query_arg(
4233                  array(
4234                      'action'   => 'enable',
4235                      '_wpnonce' => wp_create_nonce( 'enable-theme_' . $slug ),
4236                      'theme'    => $slug,
4237                  ),
4238                  network_admin_url( 'themes.php' )
4239              );
4240          } else {
4241              $status['activateUrl'] = add_query_arg(
4242                  array(
4243                      'action'     => 'activate',
4244                      '_wpnonce'   => wp_create_nonce( 'switch-theme_' . $slug ),
4245                      'stylesheet' => $slug,
4246                  ),
4247                  admin_url( 'themes.php' )
4248              );
4249          }
4250      }
4251  
4252      $theme                = wp_get_theme( $slug );
4253      $status['blockTheme'] = $theme->is_block_theme();
4254  
4255      if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
4256          $status['customizeUrl'] = add_query_arg(
4257              array(
4258                  'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
4259              ),
4260              wp_customize_url( $slug )
4261          );
4262      }
4263  
4264      /*
4265       * See WP_Theme_Install_List_Table::_get_theme_status() if we wanted to check
4266       * on post-installation status.
4267       */
4268      wp_send_json_success( $status );
4269  }
4270  
4271  /**
4272   * Handles updating a theme via AJAX.
4273   *
4274   * @since 4.6.0
4275   *
4276   * @see Theme_Upgrader
4277   *
4278   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4279   */
4280  function wp_ajax_update_theme() {
4281      check_ajax_referer( 'updates' );
4282  
4283      if ( empty( $_POST['slug'] ) ) {
4284          wp_send_json_error(
4285              array(
4286                  'slug'         => '',
4287                  'errorCode'    => 'no_theme_specified',
4288                  'errorMessage' => __( 'No theme specified.' ),
4289              )
4290          );
4291      }
4292  
4293      $stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
4294      $status     = array(
4295          'update'     => 'theme',
4296          'slug'       => $stylesheet,
4297          'oldVersion' => '',
4298          'newVersion' => '',
4299      );
4300  
4301      if ( ! current_user_can( 'update_themes' ) ) {
4302          $status['errorMessage'] = __( 'Sorry, you are not allowed to update themes for this site.' );
4303          wp_send_json_error( $status );
4304      }
4305  
4306      $theme = wp_get_theme( $stylesheet );
4307      if ( $theme->exists() ) {
4308          $status['oldVersion'] = $theme->get( 'Version' );
4309      }
4310  
4311      require_once  ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4312  
4313      $current = get_site_transient( 'update_themes' );
4314      if ( empty( $current ) ) {
4315          wp_update_themes();
4316      }
4317  
4318      $skin     = new WP_Ajax_Upgrader_Skin();
4319      $upgrader = new Theme_Upgrader( $skin );
4320      $result   = $upgrader->bulk_upgrade( array( $stylesheet ) );
4321  
4322      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4323          $status['debug'] = $skin->get_upgrade_messages();
4324      }
4325  
4326      if ( is_wp_error( $skin->result ) ) {
4327          $status['errorCode']    = $skin->result->get_error_code();
4328          $status['errorMessage'] = $skin->result->get_error_message();
4329          wp_send_json_error( $status );
4330      } elseif ( $skin->get_errors()->has_errors() ) {
4331          $status['errorMessage'] = $skin->get_error_messages();
4332          wp_send_json_error( $status );
4333      } elseif ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) {
4334  
4335          // Theme is already at the latest version.
4336          if ( true === $result[ $stylesheet ] ) {
4337              $status['errorMessage'] = $upgrader->strings['up_to_date'];
4338              wp_send_json_error( $status );
4339          }
4340  
4341          $theme = wp_get_theme( $stylesheet );
4342          if ( $theme->exists() ) {
4343              $status['newVersion'] = $theme->get( 'Version' );
4344          }
4345  
4346          wp_send_json_success( $status );
4347      } elseif ( false === $result ) {
4348          global $wp_filesystem;
4349  
4350          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4351          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4352  
4353          // Pass through the error from WP_Filesystem if one was raised.
4354          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4355              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4356          }
4357  
4358          wp_send_json_error( $status );
4359      }
4360  
4361      // An unhandled error occurred.
4362      $status['errorMessage'] = __( 'Theme update failed.' );
4363      wp_send_json_error( $status );
4364  }
4365  
4366  /**
4367   * Handles deleting a theme via AJAX.
4368   *
4369   * @since 4.6.0
4370   *
4371   * @see delete_theme()
4372   *
4373   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4374   */
4375  function wp_ajax_delete_theme() {
4376      check_ajax_referer( 'updates' );
4377  
4378      if ( empty( $_POST['slug'] ) ) {
4379          wp_send_json_error(
4380              array(
4381                  'slug'         => '',
4382                  'errorCode'    => 'no_theme_specified',
4383                  'errorMessage' => __( 'No theme specified.' ),
4384              )
4385          );
4386      }
4387  
4388      $stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
4389      $status     = array(
4390          'delete' => 'theme',
4391          'slug'   => $stylesheet,
4392      );
4393  
4394      if ( ! current_user_can( 'delete_themes' ) ) {
4395          $status['errorMessage'] = __( 'Sorry, you are not allowed to delete themes on this site.' );
4396          wp_send_json_error( $status );
4397      }
4398  
4399      if ( ! wp_get_theme( $stylesheet )->exists() ) {
4400          $status['errorMessage'] = __( 'The requested theme does not exist.' );
4401          wp_send_json_error( $status );
4402      }
4403  
4404      // Check filesystem credentials. `delete_theme()` will bail otherwise.
4405      $url = wp_nonce_url( 'themes.php?action=delete&stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet );
4406  
4407      ob_start();
4408      $credentials = request_filesystem_credentials( $url );
4409      ob_end_clean();
4410  
4411      if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
4412          global $wp_filesystem;
4413  
4414          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4415          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4416  
4417          // Pass through the error from WP_Filesystem if one was raised.
4418          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4419              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4420          }
4421  
4422          wp_send_json_error( $status );
4423      }
4424  
4425      require_once  ABSPATH . 'wp-admin/includes/theme.php';
4426  
4427      $result = delete_theme( $stylesheet );
4428  
4429      if ( is_wp_error( $result ) ) {
4430          $status['errorMessage'] = $result->get_error_message();
4431          wp_send_json_error( $status );
4432      } elseif ( false === $result ) {
4433          $status['errorMessage'] = __( 'Theme could not be deleted.' );
4434          wp_send_json_error( $status );
4435      }
4436  
4437      wp_send_json_success( $status );
4438  }
4439  
4440  /**
4441   * Handles installing a plugin via AJAX.
4442   *
4443   * @since 4.6.0
4444   *
4445   * @see Plugin_Upgrader
4446   *
4447   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4448   */
4449  function wp_ajax_install_plugin() {
4450      check_ajax_referer( 'updates' );
4451  
4452      if ( empty( $_POST['slug'] ) ) {
4453          wp_send_json_error(
4454              array(
4455                  'slug'         => '',
4456                  'errorCode'    => 'no_plugin_specified',
4457                  'errorMessage' => __( 'No plugin specified.' ),
4458              )
4459          );
4460      }
4461  
4462      $status = array(
4463          'install' => 'plugin',
4464          'slug'    => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4465      );
4466  
4467      if ( ! current_user_can( 'install_plugins' ) ) {
4468          $status['errorMessage'] = __( 'Sorry, you are not allowed to install plugins on this site.' );
4469          wp_send_json_error( $status );
4470      }
4471  
4472      require_once  ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4473      require_once  ABSPATH . 'wp-admin/includes/plugin-install.php';
4474  
4475      $api = plugins_api(
4476          'plugin_information',
4477          array(
4478              'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4479              'fields' => array(
4480                  'sections' => false,
4481              ),
4482          )
4483      );
4484  
4485      if ( is_wp_error( $api ) ) {
4486          $status['errorMessage'] = $api->get_error_message();
4487          wp_send_json_error( $status );
4488      }
4489  
4490      $status['pluginName'] = $api->name;
4491  
4492      $skin     = new WP_Ajax_Upgrader_Skin();
4493      $upgrader = new Plugin_Upgrader( $skin );
4494      $result   = $upgrader->install( $api->download_link );
4495  
4496      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4497          $status['debug'] = $skin->get_upgrade_messages();
4498      }
4499  
4500      if ( is_wp_error( $result ) ) {
4501          $status['errorCode']    = $result->get_error_code();
4502          $status['errorMessage'] = $result->get_error_message();
4503          wp_send_json_error( $status );
4504      } elseif ( is_wp_error( $skin->result ) ) {
4505          $status['errorCode']    = $skin->result->get_error_code();
4506          $status['errorMessage'] = $skin->result->get_error_message();
4507          wp_send_json_error( $status );
4508      } elseif ( $skin->get_errors()->has_errors() ) {
4509          $status['errorMessage'] = $skin->get_error_messages();
4510          wp_send_json_error( $status );
4511      } elseif ( is_null( $result ) ) {
4512          global $wp_filesystem;
4513  
4514          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4515          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4516  
4517          // Pass through the error from WP_Filesystem if one was raised.
4518          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4519              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4520          }
4521  
4522          wp_send_json_error( $status );
4523      }
4524  
4525      $install_status = install_plugin_install_status( $api );
4526      $pagenow        = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
4527  
4528      // If installation request is coming from import page, do not return network activation link.
4529      $plugins_url = ( 'import' === $pagenow ) ? admin_url( 'plugins.php' ) : network_admin_url( 'plugins.php' );
4530  
4531      if ( current_user_can( 'activate_plugin', $install_status['file'] ) && is_plugin_inactive( $install_status['file'] ) ) {
4532          $status['activateUrl'] = add_query_arg(
4533              array(
4534                  '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ),
4535                  'action'   => 'activate',
4536                  'plugin'   => $install_status['file'],
4537              ),
4538              $plugins_url
4539          );
4540      }
4541  
4542      if ( is_multisite() && current_user_can( 'manage_network_plugins' ) && 'import' !== $pagenow ) {
4543          $status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] );
4544      }
4545  
4546      wp_send_json_success( $status );
4547  }
4548  
4549  /**
4550   * Handles activating a plugin via AJAX.
4551   *
4552   * @since 6.5.0
4553   */
4554  function wp_ajax_activate_plugin() {
4555      check_ajax_referer( 'updates' );
4556  
4557      if ( empty( $_POST['name'] ) || empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
4558          wp_send_json_error(
4559              array(
4560                  'slug'         => '',
4561                  'pluginName'   => '',
4562                  'plugin'       => '',
4563                  'errorCode'    => 'no_plugin_specified',
4564                  'errorMessage' => __( 'No plugin specified.' ),
4565              )
4566          );
4567      }
4568  
4569      $status = array(
4570          'activate'   => 'plugin',
4571          'slug'       => wp_unslash( $_POST['slug'] ),
4572          'pluginName' => wp_unslash( $_POST['name'] ),
4573          'plugin'     => wp_unslash( $_POST['plugin'] ),
4574      );
4575  
4576      if ( ! current_user_can( 'activate_plugin', $status['plugin'] ) ) {
4577          $status['errorMessage'] = __( 'Sorry, you are not allowed to activate plugins on this site.' );
4578          wp_send_json_error( $status );
4579      }
4580  
4581      if ( is_plugin_active( $status['plugin'] ) ) {
4582          $status['errorMessage'] = sprintf(
4583              /* translators: %s: Plugin name. */
4584              __( '%s is already active.' ),
4585              $status['pluginName']
4586          );
4587      }
4588  
4589      $activated = activate_plugin( $status['plugin'] );
4590  
4591      if ( is_wp_error( $activated ) ) {
4592          $status['errorMessage'] = $activated->get_error_message();
4593          wp_send_json_error( $status );
4594      }
4595  
4596      wp_send_json_success( $status );
4597  }
4598  
4599  /**
4600   * Handles updating a plugin via AJAX.
4601   *
4602   * @since 4.2.0
4603   *
4604   * @see Plugin_Upgrader
4605   *
4606   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4607   */
4608  function wp_ajax_update_plugin() {
4609      check_ajax_referer( 'updates' );
4610  
4611      if ( empty( $_POST['plugin'] ) || empty( $_POST['slug'] ) ) {
4612          wp_send_json_error(
4613              array(
4614                  'slug'         => '',
4615                  'errorCode'    => 'no_plugin_specified',
4616                  'errorMessage' => __( 'No plugin specified.' ),
4617              )
4618          );
4619      }
4620  
4621      $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
4622  
4623      $status = array(
4624          'update'     => 'plugin',
4625          'slug'       => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4626          'oldVersion' => '',
4627          'newVersion' => '',
4628      );
4629  
4630      if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) {
4631          $status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
4632          wp_send_json_error( $status );
4633      }
4634  
4635      $plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4636      $status['plugin']     = $plugin;
4637      $status['pluginName'] = $plugin_data['Name'];
4638  
4639      if ( $plugin_data['Version'] ) {
4640          /* translators: %s: Plugin version. */
4641          $status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
4642      }
4643  
4644      require_once  ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4645  
4646      wp_update_plugins();
4647  
4648      $skin     = new WP_Ajax_Upgrader_Skin();
4649      $upgrader = new Plugin_Upgrader( $skin );
4650      $result   = $upgrader->bulk_upgrade( array( $plugin ) );
4651  
4652      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4653          $status['debug'] = $skin->get_upgrade_messages();
4654      }
4655  
4656      if ( is_wp_error( $skin->result ) ) {
4657          $status['errorCode']    = $skin->result->get_error_code();
4658          $status['errorMessage'] = $skin->result->get_error_message();
4659          wp_send_json_error( $status );
4660      } elseif ( $skin->get_errors()->has_errors() ) {
4661          $status['errorMessage'] = $skin->get_error_messages();
4662          wp_send_json_error( $status );
4663      } elseif ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) {
4664  
4665          /*
4666           * Plugin is already at the latest version.
4667           *
4668           * This may also be the return value if the `update_plugins` site transient is empty,
4669           * e.g. when you update two plugins in quick succession before the transient repopulates.
4670           *
4671           * Preferably something can be done to ensure `update_plugins` isn't empty.
4672           * For now, surface some sort of error here.
4673           */
4674          if ( true === $result[ $plugin ] ) {
4675              $status['errorMessage'] = $upgrader->strings['up_to_date'];
4676              wp_send_json_error( $status );
4677          }
4678  
4679          $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
4680          $plugin_data = reset( $plugin_data );
4681  
4682          if ( $plugin_data['Version'] ) {
4683              /* translators: %s: Plugin version. */
4684              $status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
4685          }
4686  
4687          wp_send_json_success( $status );
4688      } elseif ( false === $result ) {
4689          global $wp_filesystem;
4690  
4691          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4692          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4693  
4694          // Pass through the error from WP_Filesystem if one was raised.
4695          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4696              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4697          }
4698  
4699          wp_send_json_error( $status );
4700      }
4701  
4702      // An unhandled error occurred.
4703      $status['errorMessage'] = __( 'Plugin update failed.' );
4704      wp_send_json_error( $status );
4705  }
4706  
4707  /**
4708   * Handles deleting a plugin via AJAX.
4709   *
4710   * @since 4.6.0
4711   *
4712   * @see delete_plugins()
4713   *
4714   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4715   */
4716  function wp_ajax_delete_plugin() {
4717      check_ajax_referer( 'updates' );
4718  
4719      if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
4720          wp_send_json_error(
4721              array(
4722                  'slug'         => '',
4723                  'errorCode'    => 'no_plugin_specified',
4724                  'errorMessage' => __( 'No plugin specified.' ),
4725              )
4726          );
4727      }
4728  
4729      $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
4730  
4731      $status = array(
4732          'delete' => 'plugin',
4733          'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4734      );
4735  
4736      if ( ! current_user_can( 'delete_plugins' ) || 0 !== validate_file( $plugin ) ) {
4737          $status['errorMessage'] = __( 'Sorry, you are not allowed to delete plugins for this site.' );
4738          wp_send_json_error( $status );
4739      }
4740  
4741      $plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4742      $status['plugin']     = $plugin;
4743      $status['pluginName'] = $plugin_data['Name'];
4744  
4745      if ( is_plugin_active( $plugin ) ) {
4746          $status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' );
4747          wp_send_json_error( $status );
4748      }
4749  
4750      // Check filesystem credentials. `delete_plugins()` will bail otherwise.
4751      $url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin, 'bulk-plugins' );
4752  
4753      ob_start();
4754      $credentials = request_filesystem_credentials( $url );
4755      ob_end_clean();
4756  
4757      if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
4758          global $wp_filesystem;
4759  
4760          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4761          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4762  
4763          // Pass through the error from WP_Filesystem if one was raised.
4764          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4765              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4766          }
4767  
4768          wp_send_json_error( $status );
4769      }
4770  
4771      $result = delete_plugins( array( $plugin ) );
4772  
4773      if ( is_wp_error( $result ) ) {
4774          $status['errorMessage'] = $result->get_error_message();
4775          wp_send_json_error( $status );
4776      } elseif ( false === $result ) {
4777          $status['errorMessage'] = __( 'Plugin could not be deleted.' );
4778          wp_send_json_error( $status );
4779      }
4780  
4781      wp_send_json_success( $status );
4782  }
4783  
4784  /**
4785   * Handles searching plugins via AJAX.
4786   *
4787   * @since 4.6.0
4788   *
4789   * @global string $s Search term.
4790   */
4791  function wp_ajax_search_plugins() {
4792      check_ajax_referer( 'updates' );
4793  
4794      // Ensure after_plugin_row_{$plugin_file} gets hooked.
4795      wp_plugin_update_rows();
4796  
4797      $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
4798      if ( 'plugins-network' === $pagenow || 'plugins' === $pagenow ) {
4799          set_current_screen( $pagenow );
4800      }
4801  
4802      /** @var WP_Plugins_List_Table $wp_list_table */
4803      $wp_list_table = _get_list_table(
4804          'WP_Plugins_List_Table',
4805          array(
4806              'screen' => get_current_screen(),
4807          )
4808      );
4809  
4810      $status = array();
4811  
4812      if ( ! $wp_list_table->ajax_user_can() ) {
4813          $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
4814          wp_send_json_error( $status );
4815      }
4816  
4817      // Set the correct requester, so pagination works.
4818      $_SERVER['REQUEST_URI'] = add_query_arg(
4819          array_diff_key(
4820              $_POST,
4821              array(
4822                  '_ajax_nonce' => null,
4823                  'action'      => null,
4824              )
4825          ),
4826          network_admin_url( 'plugins.php', 'relative' )
4827      );
4828  
4829      $GLOBALS['s'] = wp_unslash( $_POST['s'] );
4830  
4831      $wp_list_table->prepare_items();
4832  
4833      ob_start();
4834      $wp_list_table->display();
4835      $status['count'] = count( $wp_list_table->items );
4836      $status['items'] = ob_get_clean();
4837  
4838      wp_send_json_success( $status );
4839  }
4840  
4841  /**
4842   * Handles searching plugins to install via AJAX.
4843   *
4844   * @since 4.6.0
4845   */
4846  function wp_ajax_search_install_plugins() {
4847      check_ajax_referer( 'updates' );
4848  
4849      $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
4850      if ( 'plugin-install-network' === $pagenow || 'plugin-install' === $pagenow ) {
4851          set_current_screen( $pagenow );
4852      }
4853  
4854      /** @var WP_Plugin_Install_List_Table $wp_list_table */
4855      $wp_list_table = _get_list_table(
4856          'WP_Plugin_Install_List_Table',
4857          array(
4858              'screen' => get_current_screen(),
4859          )
4860      );
4861  
4862      $status = array();
4863  
4864      if ( ! $wp_list_table->ajax_user_can() ) {
4865          $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
4866          wp_send_json_error( $status );
4867      }
4868  
4869      // Set the correct requester, so pagination works.
4870      $_SERVER['REQUEST_URI'] = add_query_arg(
4871          array_diff_key(
4872              $_POST,
4873              array(
4874                  '_ajax_nonce' => null,
4875                  'action'      => null,
4876              )
4877          ),
4878          network_admin_url( 'plugin-install.php', 'relative' )
4879      );
4880  
4881      $wp_list_table->prepare_items();
4882  
4883      ob_start();
4884      $wp_list_table->display();
4885      $status['count'] = (int) $wp_list_table->get_pagination_arg( 'total_items' );
4886      $status['items'] = ob_get_clean();
4887  
4888      wp_send_json_success( $status );
4889  }
4890  
4891  /**
4892   * Handles editing a theme or plugin file via AJAX.
4893   *
4894   * @since 4.9.0
4895   *
4896   * @see wp_edit_theme_plugin_file()
4897   */
4898  function wp_ajax_edit_theme_plugin_file() {
4899      $r = wp_edit_theme_plugin_file( wp_unslash( $_POST ) ); // Validation of args is done in wp_edit_theme_plugin_file().
4900  
4901      if ( is_wp_error( $r ) ) {
4902          wp_send_json_error(
4903              array_merge(
4904                  array(
4905                      'code'    => $r->get_error_code(),
4906                      'message' => $r->get_error_message(),
4907                  ),
4908                  (array) $r->get_error_data()
4909              )
4910          );
4911      } else {
4912          wp_send_json_success(
4913              array(
4914                  'message' => __( 'File edited successfully.' ),
4915              )
4916          );
4917      }
4918  }
4919  
4920  /**
4921   * Handles exporting a user's personal data via AJAX.
4922   *
4923   * @since 4.9.6
4924   */
4925  function wp_ajax_wp_privacy_export_personal_data() {
4926  
4927      if ( empty( $_POST['id'] ) ) {
4928          wp_send_json_error( __( 'Missing request ID.' ) );
4929      }
4930  
4931      $request_id = (int) $_POST['id'];
4932  
4933      if ( $request_id < 1 ) {
4934          wp_send_json_error( __( 'Invalid request ID.' ) );
4935      }
4936  
4937      if ( ! current_user_can( 'export_others_personal_data' ) ) {
4938          wp_send_json_error( __( 'Sorry, you are not allowed to perform this action.' ) );
4939      }
4940  
4941      check_ajax_referer( 'wp-privacy-export-personal-data-' . $request_id, 'security' );
4942  
4943      // Get the request.
4944      $request = wp_get_user_request( $request_id );
4945  
4946      if ( ! $request || 'export_personal_data' !== $request->action_name ) {
4947          wp_send_json_error( __( 'Invalid request type.' ) );
4948      }
4949  
4950      $email_address = $request->email;
4951      if ( ! is_email( $email_address ) ) {
4952          wp_send_json_error( __( 'A valid email address must be given.' ) );
4953      }
4954  
4955      if ( ! isset( $_POST['exporter'] ) ) {
4956          wp_send_json_error( __( 'Missing exporter index.' ) );
4957      }
4958  
4959      $exporter_index = (int) $_POST['exporter'];
4960  
4961      if ( ! isset( $_POST['page'] ) ) {
4962          wp_send_json_error( __( 'Missing page index.' ) );
4963      }
4964  
4965      $page = (int) $_POST['page'];
4966  
4967      $send_as_email = isset( $_POST['sendAsEmail'] ) ? ( 'true' === $_POST['sendAsEmail'] ) : false;
4968  
4969      /**
4970       * Filters the array of exporter callbacks.
4971       *
4972       * @since 4.9.6
4973       *
4974       * @param array $args {
4975       *     An array of callable exporters of personal data. Default empty array.
4976       *
4977       *     @type array ...$0 {
4978       *         Array of personal data exporters.
4979       *
4980       *         @type callable $callback               Callable exporter function that accepts an
4981       *                                                email address and a page number and returns an
4982       *                                                array of name => value pairs of personal data.
4983       *         @type string   $exporter_friendly_name Translated user facing friendly name for the
4984       *                                                exporter.
4985       *     }
4986       * }
4987       */
4988      $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
4989  
4990      if ( ! is_array( $exporters ) ) {
4991          wp_send_json_error( __( 'An exporter has improperly used the registration filter.' ) );
4992      }
4993  
4994      // Do we have any registered exporters?
4995      if ( 0 < count( $exporters ) ) {
4996          if ( $exporter_index < 1 ) {
4997              wp_send_json_error( __( 'Exporter index cannot be negative.' ) );
4998          }
4999  
5000          if ( $exporter_index > count( $exporters ) ) {
5001              wp_send_json_error( __( 'Exporter index is out of range.' ) );
5002          }
5003  
5004          if ( $page < 1 ) {
5005              wp_send_json_error( __( 'Page index cannot be less than one.' ) );
5006          }
5007  
5008          $exporter_keys = array_keys( $exporters );
5009          $exporter_key  = $exporter_keys[ $exporter_index - 1 ];
5010          $exporter      = $exporters[ $exporter_key ];
5011  
5012          if ( ! is_array( $exporter ) ) {
5013              wp_send_json_error(
5014                  /* translators: %s: Exporter array index. */
5015                  sprintf( __( 'Expected an array describing the exporter at index %s.' ), $exporter_key )
5016              );
5017          }
5018  
5019          if ( ! array_key_exists( 'exporter_friendly_name', $exporter ) ) {
5020              wp_send_json_error(
5021                  /* translators: %s: Exporter array index. */
5022                  sprintf( __( 'Exporter array at index %s does not include a friendly name.' ), $exporter_key )
5023              );
5024          }
5025  
5026          $exporter_friendly_name = $exporter['exporter_friendly_name'];
5027  
5028          if ( ! array_key_exists( 'callback', $exporter ) ) {
5029              wp_send_json_error(
5030                  /* translators: %s: Exporter friendly name. */
5031                  sprintf( __( 'Exporter does not include a callback: %s.' ), esc_html( $exporter_friendly_name ) )
5032              );
5033          }
5034  
5035          if ( ! is_callable( $exporter['callback'] ) ) {
5036              wp_send_json_error(
5037                  /* translators: %s: Exporter friendly name. */
5038                  sprintf( __( 'Exporter callback is not a valid callback: %s.' ), esc_html( $exporter_friendly_name ) )
5039              );
5040          }
5041  
5042          $callback = $exporter['callback'];
5043          $response = call_user_func( $callback, $email_address, $page );
5044  
5045          if ( is_wp_error( $response ) ) {
5046              wp_send_json_error( $response );
5047          }
5048  
5049          if ( ! is_array( $response ) ) {
5050              wp_send_json_error(
5051                  /* translators: %s: Exporter friendly name. */
5052                  sprintf( __( 'Expected response as an array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
5053              );
5054          }
5055  
5056          if ( ! array_key_exists( 'data', $response ) ) {
5057              wp_send_json_error(
5058                  /* translators: %s: Exporter friendly name. */
5059                  sprintf( __( 'Expected data in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
5060              );
5061          }
5062  
5063          if ( ! is_array( $response['data'] ) ) {
5064              wp_send_json_error(
5065                  /* translators: %s: Exporter friendly name. */
5066                  sprintf( __( 'Expected data array in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
5067              );
5068          }
5069  
5070          if ( ! array_key_exists( 'done', $response ) ) {
5071              wp_send_json_error(
5072                  /* translators: %s: Exporter friendly name. */
5073                  sprintf( __( 'Expected done (boolean) in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
5074              );
5075          }
5076      } else {
5077          // No exporters, so we're done.
5078          $exporter_key = '';
5079  
5080          $response = array(
5081              'data' => array(),
5082              'done' => true,
5083          );
5084      }
5085  
5086      /**
5087       * Filters a page of personal data exporter data. Used to build the export report.
5088       *
5089       * Allows the export response to be consumed by destinations in addition to Ajax.
5090       *
5091       * @since 4.9.6
5092       *
5093       * @param array  $response        The personal data for the given exporter and page number.
5094       * @param int    $exporter_index  The index of the exporter that provided this data.
5095       * @param string $email_address   The email address associated with this personal data.
5096       * @param int    $page            The page number for this response.
5097       * @param int    $request_id      The privacy request post ID associated with this request.
5098       * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
5099       * @param string $exporter_key    The key (slug) of the exporter that provided this data.
5100       */
5101      $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key );
5102  
5103      if ( is_wp_error( $response ) ) {
5104          wp_send_json_error( $response );
5105      }
5106  
5107      wp_send_json_success( $response );
5108  }
5109  
5110  /**
5111   * Handles erasing personal data via AJAX.
5112   *
5113   * @since 4.9.6
5114   */
5115  function wp_ajax_wp_privacy_erase_personal_data() {
5116  
5117      if ( empty( $_POST['id'] ) ) {
5118          wp_send_json_error( __( 'Missing request ID.' ) );
5119      }
5120  
5121      $request_id = (int) $_POST['id'];
5122  
5123      if ( $request_id < 1 ) {
5124          wp_send_json_error( __( 'Invalid request ID.' ) );
5125      }
5126  
5127      // Both capabilities are required to avoid confusion, see `_wp_personal_data_removal_page()`.
5128      if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) {
5129          wp_send_json_error( __( 'Sorry, you are not allowed to perform this action.' ) );
5130      }
5131  
5132      check_ajax_referer( 'wp-privacy-erase-personal-data-' . $request_id, 'security' );
5133  
5134      // Get the request.
5135      $request = wp_get_user_request( $request_id );
5136  
5137      if ( ! $request || 'remove_personal_data' !== $request->action_name ) {
5138          wp_send_json_error( __( 'Invalid request type.' ) );
5139      }
5140  
5141      $email_address = $request->email;
5142  
5143      if ( ! is_email( $email_address ) ) {
5144          wp_send_json_error( __( 'Invalid email address in request.' ) );
5145      }
5146  
5147      if ( ! isset( $_POST['eraser'] ) ) {
5148          wp_send_json_error( __( 'Missing eraser index.' ) );
5149      }
5150  
5151      $eraser_index = (int) $_POST['eraser'];
5152  
5153      if ( ! isset( $_POST['page'] ) ) {
5154          wp_send_json_error( __( 'Missing page index.' ) );
5155      }
5156  
5157      $page = (int) $_POST['page'];
5158  
5159      /**
5160       * Filters the array of personal data eraser callbacks.
5161       *
5162       * @since 4.9.6
5163       *
5164       * @param array $args {
5165       *     An array of callable erasers of personal data. Default empty array.
5166       *
5167       *     @type array ...$0 {
5168       *         Array of personal data exporters.
5169       *
5170       *         @type callable $callback               Callable eraser that accepts an email address and a page
5171       *                                                number, and returns an array with boolean values for
5172       *                                                whether items were removed or retained and any messages
5173       *                                                from the eraser, as well as if additional pages are
5174       *                                                available.
5175       *         @type string   $exporter_friendly_name Translated user facing friendly name for the eraser.
5176       *     }
5177       * }
5178       */
5179      $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() );
5180  
5181      // Do we have any registered erasers?
5182      if ( 0 < count( $erasers ) ) {
5183  
5184          if ( $eraser_index < 1 ) {
5185              wp_send_json_error( __( 'Eraser index cannot be less than one.' ) );
5186          }
5187  
5188          if ( $eraser_index > count( $erasers ) ) {
5189              wp_send_json_error( __( 'Eraser index is out of range.' ) );
5190          }
5191  
5192          if ( $page < 1 ) {
5193              wp_send_json_error( __( 'Page index cannot be less than one.' ) );
5194          }
5195  
5196          $eraser_keys = array_keys( $erasers );
5197          $eraser_key  = $eraser_keys[ $eraser_index - 1 ];
5198          $eraser      = $erasers[ $eraser_key ];
5199  
5200          if ( ! is_array( $eraser ) ) {
5201              /* translators: %d: Eraser array index. */
5202              wp_send_json_error( sprintf( __( 'Expected an array describing the eraser at index %d.' ), $eraser_index ) );
5203          }
5204  
5205          if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) {
5206              /* translators: %d: Eraser array index. */
5207              wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) );
5208          }
5209  
5210          $eraser_friendly_name = $eraser['eraser_friendly_name'];
5211  
5212          if ( ! array_key_exists( 'callback', $eraser ) ) {
5213              wp_send_json_error(
5214                  sprintf(
5215                      /* translators: %s: Eraser friendly name. */
5216                      __( 'Eraser does not include a callback: %s.' ),
5217                      esc_html( $eraser_friendly_name )
5218                  )
5219              );
5220          }
5221  
5222          if ( ! is_callable( $eraser['callback'] ) ) {
5223              wp_send_json_error(
5224                  sprintf(
5225                      /* translators: %s: Eraser friendly name. */
5226                      __( 'Eraser callback is not valid: %s.' ),
5227                      esc_html( $eraser_friendly_name )
5228                  )
5229              );
5230          }
5231  
5232          $callback = $eraser['callback'];
5233          $response = call_user_func( $callback, $email_address, $page );
5234  
5235          if ( is_wp_error( $response ) ) {
5236              wp_send_json_error( $response );
5237          }
5238  
5239          if ( ! is_array( $response ) ) {
5240              wp_send_json_error(
5241                  sprintf(
5242                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5243                      __( 'Did not receive array from %1$s eraser (index %2$d).' ),
5244                      esc_html( $eraser_friendly_name ),
5245                      $eraser_index
5246                  )
5247              );
5248          }
5249  
5250          if ( ! array_key_exists( 'items_removed', $response ) ) {
5251              wp_send_json_error(
5252                  sprintf(
5253                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5254                      __( 'Expected items_removed key in response array from %1$s eraser (index %2$d).' ),
5255                      esc_html( $eraser_friendly_name ),
5256                      $eraser_index
5257                  )
5258              );
5259          }
5260  
5261          if ( ! array_key_exists( 'items_retained', $response ) ) {
5262              wp_send_json_error(
5263                  sprintf(
5264                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5265                      __( 'Expected items_retained key in response array from %1$s eraser (index %2$d).' ),
5266                      esc_html( $eraser_friendly_name ),
5267                      $eraser_index
5268                  )
5269              );
5270          }
5271  
5272          if ( ! array_key_exists( 'messages', $response ) ) {
5273              wp_send_json_error(
5274                  sprintf(
5275                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5276                      __( 'Expected messages key in response array from %1$s eraser (index %2$d).' ),
5277                      esc_html( $eraser_friendly_name ),
5278                      $eraser_index
5279                  )
5280              );
5281          }
5282  
5283          if ( ! is_array( $response['messages'] ) ) {
5284              wp_send_json_error(
5285                  sprintf(
5286                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5287                      __( 'Expected messages key to reference an array in response array from %1$s eraser (index %2$d).' ),
5288                      esc_html( $eraser_friendly_name ),
5289                      $eraser_index
5290                  )
5291              );
5292          }
5293  
5294          if ( ! array_key_exists( 'done', $response ) ) {
5295              wp_send_json_error(
5296                  sprintf(
5297                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5298                      __( 'Expected done flag in response array from %1$s eraser (index %2$d).' ),
5299                      esc_html( $eraser_friendly_name ),
5300                      $eraser_index
5301                  )
5302              );
5303          }
5304      } else {
5305          // No erasers, so we're done.
5306          $eraser_key = '';
5307  
5308          $response = array(
5309              'items_removed'  => false,
5310              'items_retained' => false,
5311              'messages'       => array(),
5312              'done'           => true,
5313          );
5314      }
5315  
5316      /**
5317       * Filters a page of personal data eraser data.
5318       *
5319       * Allows the erasure response to be consumed by destinations in addition to Ajax.
5320       *
5321       * @since 4.9.6
5322       *
5323       * @param array  $response        {
5324       *     The personal data for the given exporter and page number.
5325       *
5326       *     @type bool     $items_removed  Whether items were actually removed or not.
5327       *     @type bool     $items_retained Whether items were retained or not.
5328       *     @type string[] $messages       An array of messages to add to the personal data export file.
5329       *     @type bool     $done           Whether the eraser is finished or not.
5330       * }
5331       * @param int    $eraser_index    The index of the eraser that provided this data.
5332       * @param string $email_address   The email address associated with this personal data.
5333       * @param int    $page            The page number for this response.
5334       * @param int    $request_id      The privacy request post ID associated with this request.
5335       * @param string $eraser_key      The key (slug) of the eraser that provided this data.
5336       */
5337      $response = apply_filters( 'wp_privacy_personal_data_erasure_page', $response, $eraser_index, $email_address, $page, $request_id, $eraser_key );
5338  
5339      if ( is_wp_error( $response ) ) {
5340          wp_send_json_error( $response );
5341      }
5342  
5343      wp_send_json_success( $response );
5344  }
5345  
5346  /**
5347   * Handles site health checks on server communication via AJAX.
5348   *
5349   * @since 5.2.0
5350   * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_dotorg_communication()
5351   * @see WP_REST_Site_Health_Controller::test_dotorg_communication()
5352   */
5353  function wp_ajax_health_check_dotorg_communication() {
5354      _doing_it_wrong(
5355          'wp_ajax_health_check_dotorg_communication',
5356          sprintf(
5357          // translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
5358              __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
5359              'wp_ajax_health_check_dotorg_communication',
5360              'WP_REST_Site_Health_Controller::test_dotorg_communication'
5361          ),
5362          '5.6.0'
5363      );
5364  
5365      check_ajax_referer( 'health-check-site-status' );
5366  
5367      if ( ! current_user_can( 'view_site_health_checks' ) ) {
5368          wp_send_json_error();
5369      }
5370  
5371      if ( ! class_exists( 'WP_Site_Health' ) ) {
5372          require_once  ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
5373      }
5374  
5375      $site_health = WP_Site_Health::get_instance();
5376      wp_send_json_success( $site_health->get_test_dotorg_communication() );
5377  }
5378  
5379  /**
5380   * Handles site health checks on background updates via AJAX.
5381   *
5382   * @since 5.2.0
5383   * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_background_updates()
5384   * @see WP_REST_Site_Health_Controller::test_background_updates()
5385   */
5386  function wp_ajax_health_check_background_updates() {
5387      _doing_it_wrong(
5388          'wp_ajax_health_check_background_updates',
5389          sprintf(
5390          // translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
5391              __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
5392              'wp_ajax_health_check_background_updates',
5393              'WP_REST_Site_Health_Controller::test_background_updates'
5394          ),
5395          '5.6.0'
5396      );
5397  
5398      check_ajax_referer( 'health-check-site-status' );
5399  
5400      if ( ! current_user_can( 'view_site_health_checks' ) ) {
5401          wp_send_json_error();
5402      }
5403  
5404      if ( ! class_exists( 'WP_Site_Health' ) ) {
5405          require_once  ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
5406      }
5407  
5408      $site_health = WP_Site_Health::get_instance();
5409      wp_send_json_success( $site_health->get_test_background_updates() );
5410  }
5411  
5412  /**
5413   * Handles site health checks on loopback requests via AJAX.
5414   *
5415   * @since 5.2.0
5416   * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_loopback_requests()
5417   * @see WP_REST_Site_Health_Controller::test_loopback_requests()
5418   */
5419  function wp_ajax_health_check_loopback_requests() {
5420      _doing_it_wrong(
5421          'wp_ajax_health_check_loopback_requests',
5422          sprintf(
5423          // translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
5424              __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
5425              'wp_ajax_health_check_loopback_requests',
5426              'WP_REST_Site_Health_Controller::test_loopback_requests'
5427          ),
5428          '5.6.0'
5429      );
5430  
5431      check_ajax_referer( 'health-check-site-status' );
5432  
5433      if ( ! current_user_can( 'view_site_health_checks' ) ) {
5434          wp_send_json_error();
5435      }
5436  
5437      if ( ! class_exists( 'WP_Site_Health' ) ) {
5438          require_once  ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
5439      }
5440  
5441      $site_health = WP_Site_Health::get_instance();
5442      wp_send_json_success( $site_health->get_test_loopback_requests() );
5443  }
5444  
5445  /**
5446   * Handles site health check to update the result status via AJAX.
5447   *
5448   * @since 5.2.0
5449   */
5450  function wp_ajax_health_check_site_status_result() {
5451      check_ajax_referer( 'health-check-site-status-result' );
5452  
5453      if ( ! current_user_can( 'view_site_health_checks' ) ) {
5454          wp_send_json_error();
5455      }
5456  
5457      set_transient( 'health-check-site-status-result', wp_json_encode( $_POST['counts'] ) );
5458  
5459      wp_send_json_success();
5460  }
5461  
5462  /**
5463   * Handles site health check to get directories and database sizes via AJAX.
5464   *
5465   * @since 5.2.0
5466   * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::get_directory_sizes()
5467   * @see WP_REST_Site_Health_Controller::get_directory_sizes()
5468   */
5469  function wp_ajax_health_check_get_sizes() {
5470      _doing_it_wrong(
5471          'wp_ajax_health_check_get_sizes',
5472          sprintf(
5473          // translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
5474              __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
5475              'wp_ajax_health_check_get_sizes',
5476              'WP_REST_Site_Health_Controller::get_directory_sizes'
5477          ),
5478          '5.6.0'
5479      );
5480  
5481      check_ajax_referer( 'health-check-site-status-result' );
5482  
5483      if ( ! current_user_can( 'view_site_health_checks' ) || is_multisite() ) {
5484          wp_send_json_error();
5485      }
5486  
5487      if ( ! class_exists( 'WP_Debug_Data' ) ) {
5488          require_once  ABSPATH . 'wp-admin/includes/class-wp-debug-data.php';
5489      }
5490  
5491      $sizes_data = WP_Debug_Data::get_sizes();
5492      $all_sizes  = array( 'raw' => 0 );
5493  
5494      foreach ( $sizes_data as $name => $value ) {
5495          $name = sanitize_text_field( $name );
5496          $data = array();
5497  
5498          if ( isset( $value['size'] ) ) {
5499              if ( is_string( $value['size'] ) ) {
5500                  $data['size'] = sanitize_text_field( $value['size'] );
5501              } else {
5502                  $data['size'] = (int) $value['size'];
5503              }
5504          }
5505  
5506          if ( isset( $value['debug'] ) ) {
5507              if ( is_string( $value['debug'] ) ) {
5508                  $data['debug'] = sanitize_text_field( $value['debug'] );
5509              } else {
5510                  $data['debug'] = (int) $value['debug'];
5511              }
5512          }
5513  
5514          if ( ! empty( $value['raw'] ) ) {
5515              $data['raw'] = (int) $value['raw'];
5516          }
5517  
5518          $all_sizes[ $name ] = $data;
5519      }
5520  
5521      if ( isset( $all_sizes['total_size']['debug'] ) && 'not available' === $all_sizes['total_size']['debug'] ) {
5522          wp_send_json_error( $all_sizes );
5523      }
5524  
5525      wp_send_json_success( $all_sizes );
5526  }
5527  
5528  /**
5529   * Handles renewing the REST API nonce via AJAX.
5530   *
5531   * @since 5.3.0
5532   */
5533  function wp_ajax_rest_nonce() {
5534      exit( wp_create_nonce( 'wp_rest' ) );
5535  }
5536  
5537  /**
5538   * Handles enabling or disable plugin and theme auto-updates via AJAX.
5539   *
5540   * @since 5.5.0
5541   */
5542  function wp_ajax_toggle_auto_updates() {
5543      check_ajax_referer( 'updates' );
5544  
5545      if ( empty( $_POST['type'] ) || empty( $_POST['asset'] ) || empty( $_POST['state'] ) ) {
5546          wp_send_json_error( array( 'error' => __( 'Invalid data. No selected item.' ) ) );
5547      }
5548  
5549      $asset = sanitize_text_field( urldecode( $_POST['asset'] ) );
5550  
5551      if ( 'enable' !== $_POST['state'] && 'disable' !== $_POST['state'] ) {
5552          wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown state.' ) ) );
5553      }
5554      $state = $_POST['state'];
5555  
5556      if ( 'plugin' !== $_POST['type'] && 'theme' !== $_POST['type'] ) {
5557          wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown type.' ) ) );
5558      }
5559      $type = $_POST['type'];
5560  
5561      switch ( $type ) {
5562          case 'plugin':
5563              if ( ! current_user_can( 'update_plugins' ) ) {
5564                  $error_message = __( 'Sorry, you are not allowed to modify plugins.' );
5565                  wp_send_json_error( array( 'error' => $error_message ) );
5566              }
5567  
5568              $option = 'auto_update_plugins';
5569              /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
5570              $all_items = apply_filters( 'all_plugins', get_plugins() );
5571              break;
5572          case 'theme':
5573              if ( ! current_user_can( 'update_themes' ) ) {
5574                  $error_message = __( 'Sorry, you are not allowed to modify themes.' );
5575                  wp_send_json_error( array( 'error' => $error_message ) );
5576              }
5577  
5578              $option    = 'auto_update_themes';
5579              $all_items = wp_get_themes();
5580              break;
5581          default:
5582              wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown type.' ) ) );
5583      }
5584  
5585      if ( ! array_key_exists( $asset, $all_items ) ) {
5586          $error_message = __( 'Invalid data. The item does not exist.' );
5587          wp_send_json_error( array( 'error' => $error_message ) );
5588      }
5589  
5590      $auto_updates = (array) get_site_option( $option, array() );
5591  
5592      if ( 'disable' === $state ) {
5593          $auto_updates = array_diff( $auto_updates, array( $asset ) );
5594      } else {
5595          $auto_updates[] = $asset;
5596          $auto_updates   = array_unique( $auto_updates );
5597      }
5598  
5599      // Remove items that have been deleted since the site option was last updated.
5600      $auto_updates = array_intersect( $auto_updates, array_keys( $all_items ) );
5601  
5602      update_site_option( $option, $auto_updates );
5603  
5604      wp_send_json_success();
5605  }
5606  
5607  /**
5608   * Handles sending a password reset link via AJAX.
5609   *
5610   * @since 5.7.0
5611   */
5612  function wp_ajax_send_password_reset() {
5613  
5614      // Validate the nonce for this action.
5615      $user_id = isset( $_POST['user_id'] ) ? (int) $_POST['user_id'] : 0;
5616      check_ajax_referer( 'reset-password-for-' . $user_id, 'nonce' );
5617  
5618      // Verify user capabilities.
5619      if ( ! current_user_can( 'edit_user', $user_id ) ) {
5620          wp_send_json_error( __( 'Cannot send password reset, permission denied.' ) );
5621      }
5622  
5623      // Send the password reset link.
5624      $user    = get_userdata( $user_id );
5625      $results = retrieve_password( $user->user_login );
5626  
5627      if ( true === $results ) {
5628          wp_send_json_success(
5629              /* translators: %s: User's display name. */
5630              sprintf( __( 'A password reset link was emailed to %s.' ), $user->display_name )
5631          );
5632      } else {
5633          wp_send_json_error( $results->get_error_message() );
5634      }
5635  }


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref