[ 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, 'yes' );
 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, 'yes' );
 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, 'yes' );
 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 && $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      $checkbox      = ( isset( $_POST['checkbox'] ) && true == $_POST['checkbox'] ) ? 1 : 0;
1487      $wp_list_table = _get_list_table( $checkbox ? 'WP_Comments_List_Table' : 'WP_Post_Comments_List_Table', array( 'screen' => 'edit-comments' ) );
1488  
1489      $comment = get_comment( $comment_id );
1490  
1491      if ( empty( $comment->comment_ID ) ) {
1492          wp_die( -1 );
1493      }
1494  
1495      ob_start();
1496      $wp_list_table->single_row( $comment );
1497      $comment_list_item = ob_get_clean();
1498  
1499      $x = new WP_Ajax_Response();
1500  
1501      $x->add(
1502          array(
1503              'what'     => 'edit_comment',
1504              'id'       => $comment->comment_ID,
1505              'data'     => $comment_list_item,
1506              'position' => $position,
1507          )
1508      );
1509  
1510      $x->send();
1511  }
1512  
1513  /**
1514   * Handles adding a menu item via AJAX.
1515   *
1516   * @since 3.1.0
1517   */
1518  function wp_ajax_add_menu_item() {
1519      check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1520  
1521      if ( ! current_user_can( 'edit_theme_options' ) ) {
1522          wp_die( -1 );
1523      }
1524  
1525      require_once  ABSPATH . 'wp-admin/includes/nav-menu.php';
1526  
1527      /*
1528       * For performance reasons, we omit some object properties from the checklist.
1529       * The following is a hacky way to restore them when adding non-custom items.
1530       */
1531      $menu_items_data = array();
1532  
1533      foreach ( (array) $_POST['menu-item'] as $menu_item_data ) {
1534          if (
1535              ! empty( $menu_item_data['menu-item-type'] ) &&
1536              'custom' !== $menu_item_data['menu-item-type'] &&
1537              ! empty( $menu_item_data['menu-item-object-id'] )
1538          ) {
1539              switch ( $menu_item_data['menu-item-type'] ) {
1540                  case 'post_type':
1541                      $_object = get_post( $menu_item_data['menu-item-object-id'] );
1542                      break;
1543  
1544                  case 'post_type_archive':
1545                      $_object = get_post_type_object( $menu_item_data['menu-item-object'] );
1546                      break;
1547  
1548                  case 'taxonomy':
1549                      $_object = get_term( $menu_item_data['menu-item-object-id'], $menu_item_data['menu-item-object'] );
1550                      break;
1551              }
1552  
1553              $_menu_items = array_map( 'wp_setup_nav_menu_item', array( $_object ) );
1554              $_menu_item  = reset( $_menu_items );
1555  
1556              // Restore the missing menu item properties.
1557              $menu_item_data['menu-item-description'] = $_menu_item->description;
1558          }
1559  
1560          $menu_items_data[] = $menu_item_data;
1561      }
1562  
1563      $item_ids = wp_save_nav_menu_items( 0, $menu_items_data );
1564      if ( is_wp_error( $item_ids ) ) {
1565          wp_die( 0 );
1566      }
1567  
1568      $menu_items = array();
1569  
1570      foreach ( (array) $item_ids as $menu_item_id ) {
1571          $menu_obj = get_post( $menu_item_id );
1572  
1573          if ( ! empty( $menu_obj->ID ) ) {
1574              $menu_obj        = wp_setup_nav_menu_item( $menu_obj );
1575              $menu_obj->title = empty( $menu_obj->title ) ? __( 'Menu Item' ) : $menu_obj->title;
1576              $menu_obj->label = $menu_obj->title; // Don't show "(pending)" in ajax-added items.
1577              $menu_items[]    = $menu_obj;
1578          }
1579      }
1580  
1581      /** This filter is documented in wp-admin/includes/nav-menu.php */
1582      $walker_class_name = apply_filters( 'wp_edit_nav_menu_walker', 'Walker_Nav_Menu_Edit', $_POST['menu'] );
1583  
1584      if ( ! class_exists( $walker_class_name ) ) {
1585          wp_die( 0 );
1586      }
1587  
1588      if ( ! empty( $menu_items ) ) {
1589          $args = array(
1590              'after'       => '',
1591              'before'      => '',
1592              'link_after'  => '',
1593              'link_before' => '',
1594              'walker'      => new $walker_class_name(),
1595          );
1596  
1597          echo walk_nav_menu_tree( $menu_items, 0, (object) $args );
1598      }
1599  
1600      wp_die();
1601  }
1602  
1603  /**
1604   * Handles adding meta via AJAX.
1605   *
1606   * @since 3.1.0
1607   */
1608  function wp_ajax_add_meta() {
1609      check_ajax_referer( 'add-meta', '_ajax_nonce-add-meta' );
1610      $c    = 0;
1611      $pid  = (int) $_POST['post_id'];
1612      $post = get_post( $pid );
1613  
1614      if ( isset( $_POST['metakeyselect'] ) || isset( $_POST['metakeyinput'] ) ) {
1615          if ( ! current_user_can( 'edit_post', $pid ) ) {
1616              wp_die( -1 );
1617          }
1618  
1619          if ( isset( $_POST['metakeyselect'] ) && '#NONE#' === $_POST['metakeyselect'] && empty( $_POST['metakeyinput'] ) ) {
1620              wp_die( 1 );
1621          }
1622  
1623          // If the post is an autodraft, save the post as a draft and then attempt to save the meta.
1624          if ( 'auto-draft' === $post->post_status ) {
1625              $post_data                = array();
1626              $post_data['action']      = 'draft'; // Warning fix.
1627              $post_data['post_ID']     = $pid;
1628              $post_data['post_type']   = $post->post_type;
1629              $post_data['post_status'] = 'draft';
1630              $now                      = time();
1631  
1632              $post_data['post_title'] = sprintf(
1633                  /* translators: 1: Post creation date, 2: Post creation time. */
1634                  __( 'Draft created on %1$s at %2$s' ),
1635                  gmdate( __( 'F j, Y' ), $now ),
1636                  gmdate( __( 'g:i a' ), $now )
1637              );
1638  
1639              $pid = edit_post( $post_data );
1640  
1641              if ( $pid ) {
1642                  if ( is_wp_error( $pid ) ) {
1643                      $x = new WP_Ajax_Response(
1644                          array(
1645                              'what' => 'meta',
1646                              'data' => $pid,
1647                          )
1648                      );
1649                      $x->send();
1650                  }
1651  
1652                  $mid = add_meta( $pid );
1653                  if ( ! $mid ) {
1654                      wp_die( __( 'Please provide a custom field value.' ) );
1655                  }
1656              } else {
1657                  wp_die( 0 );
1658              }
1659          } else {
1660              $mid = add_meta( $pid );
1661              if ( ! $mid ) {
1662                  wp_die( __( 'Please provide a custom field value.' ) );
1663              }
1664          }
1665  
1666          $meta = get_metadata_by_mid( 'post', $mid );
1667          $pid  = (int) $meta->post_id;
1668          $meta = get_object_vars( $meta );
1669  
1670          $x = new WP_Ajax_Response(
1671              array(
1672                  'what'         => 'meta',
1673                  'id'           => $mid,
1674                  'data'         => _list_meta_row( $meta, $c ),
1675                  'position'     => 1,
1676                  'supplemental' => array( 'postid' => $pid ),
1677              )
1678          );
1679      } else { // Update?
1680          $mid   = (int) key( $_POST['meta'] );
1681          $key   = wp_unslash( $_POST['meta'][ $mid ]['key'] );
1682          $value = wp_unslash( $_POST['meta'][ $mid ]['value'] );
1683  
1684          if ( '' === trim( $key ) ) {
1685              wp_die( __( 'Please provide a custom field name.' ) );
1686          }
1687  
1688          $meta = get_metadata_by_mid( 'post', $mid );
1689  
1690          if ( ! $meta ) {
1691              wp_die( 0 ); // If meta doesn't exist.
1692          }
1693  
1694          if (
1695              is_protected_meta( $meta->meta_key, 'post' ) || is_protected_meta( $key, 'post' ) ||
1696              ! current_user_can( 'edit_post_meta', $meta->post_id, $meta->meta_key ) ||
1697              ! current_user_can( 'edit_post_meta', $meta->post_id, $key )
1698          ) {
1699              wp_die( -1 );
1700          }
1701  
1702          if ( $meta->meta_value != $value || $meta->meta_key != $key ) {
1703              $u = update_metadata_by_mid( 'post', $mid, $value, $key );
1704              if ( ! $u ) {
1705                  wp_die( 0 ); // We know meta exists; we also know it's unchanged (or DB error, in which case there are bigger problems).
1706              }
1707          }
1708  
1709          $x = new WP_Ajax_Response(
1710              array(
1711                  'what'         => 'meta',
1712                  'id'           => $mid,
1713                  'old_id'       => $mid,
1714                  'data'         => _list_meta_row(
1715                      array(
1716                          'meta_key'   => $key,
1717                          'meta_value' => $value,
1718                          'meta_id'    => $mid,
1719                      ),
1720                      $c
1721                  ),
1722                  'position'     => 0,
1723                  'supplemental' => array( 'postid' => $meta->post_id ),
1724              )
1725          );
1726      }
1727      $x->send();
1728  }
1729  
1730  /**
1731   * Handles adding a user via AJAX.
1732   *
1733   * @since 3.1.0
1734   *
1735   * @param string $action Action to perform.
1736   */
1737  function wp_ajax_add_user( $action ) {
1738      if ( empty( $action ) ) {
1739          $action = 'add-user';
1740      }
1741  
1742      check_ajax_referer( $action );
1743  
1744      if ( ! current_user_can( 'create_users' ) ) {
1745          wp_die( -1 );
1746      }
1747  
1748      $user_id = edit_user();
1749  
1750      if ( ! $user_id ) {
1751          wp_die( 0 );
1752      } elseif ( is_wp_error( $user_id ) ) {
1753          $x = new WP_Ajax_Response(
1754              array(
1755                  'what' => 'user',
1756                  'id'   => $user_id,
1757              )
1758          );
1759          $x->send();
1760      }
1761  
1762      $user_object   = get_userdata( $user_id );
1763      $wp_list_table = _get_list_table( 'WP_Users_List_Table' );
1764  
1765      $role = current( $user_object->roles );
1766  
1767      $x = new WP_Ajax_Response(
1768          array(
1769              'what'         => 'user',
1770              'id'           => $user_id,
1771              'data'         => $wp_list_table->single_row( $user_object, '', $role ),
1772              'supplemental' => array(
1773                  'show-link' => sprintf(
1774                      /* translators: %s: The new user. */
1775                      __( 'User %s added' ),
1776                      '<a href="#user-' . $user_id . '">' . $user_object->user_login . '</a>'
1777                  ),
1778                  'role'      => $role,
1779              ),
1780          )
1781      );
1782      $x->send();
1783  }
1784  
1785  /**
1786   * Handles closed post boxes via AJAX.
1787   *
1788   * @since 3.1.0
1789   */
1790  function wp_ajax_closed_postboxes() {
1791      check_ajax_referer( 'closedpostboxes', 'closedpostboxesnonce' );
1792      $closed = isset( $_POST['closed'] ) ? explode( ',', $_POST['closed'] ) : array();
1793      $closed = array_filter( $closed );
1794  
1795      $hidden = isset( $_POST['hidden'] ) ? explode( ',', $_POST['hidden'] ) : array();
1796      $hidden = array_filter( $hidden );
1797  
1798      $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1799  
1800      if ( sanitize_key( $page ) != $page ) {
1801          wp_die( 0 );
1802      }
1803  
1804      $user = wp_get_current_user();
1805      if ( ! $user ) {
1806          wp_die( -1 );
1807      }
1808  
1809      if ( is_array( $closed ) ) {
1810          update_user_meta( $user->ID, "closedpostboxes_$page", $closed );
1811      }
1812  
1813      if ( is_array( $hidden ) ) {
1814          // Postboxes that are always shown.
1815          $hidden = array_diff( $hidden, array( 'submitdiv', 'linksubmitdiv', 'manage-menu', 'create-menu' ) );
1816          update_user_meta( $user->ID, "metaboxhidden_$page", $hidden );
1817      }
1818  
1819      wp_die( 1 );
1820  }
1821  
1822  /**
1823   * Handles hidden columns via AJAX.
1824   *
1825   * @since 3.1.0
1826   */
1827  function wp_ajax_hidden_columns() {
1828      check_ajax_referer( 'screen-options-nonce', 'screenoptionnonce' );
1829      $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1830  
1831      if ( sanitize_key( $page ) != $page ) {
1832          wp_die( 0 );
1833      }
1834  
1835      $user = wp_get_current_user();
1836      if ( ! $user ) {
1837          wp_die( -1 );
1838      }
1839  
1840      $hidden = ! empty( $_POST['hidden'] ) ? explode( ',', $_POST['hidden'] ) : array();
1841      update_user_meta( $user->ID, "manage{$page}columnshidden", $hidden );
1842  
1843      wp_die( 1 );
1844  }
1845  
1846  /**
1847   * Handles updating whether to display the welcome panel via AJAX.
1848   *
1849   * @since 3.1.0
1850   */
1851  function wp_ajax_update_welcome_panel() {
1852      check_ajax_referer( 'welcome-panel-nonce', 'welcomepanelnonce' );
1853  
1854      if ( ! current_user_can( 'edit_theme_options' ) ) {
1855          wp_die( -1 );
1856      }
1857  
1858      update_user_meta( get_current_user_id(), 'show_welcome_panel', empty( $_POST['visible'] ) ? 0 : 1 );
1859  
1860      wp_die( 1 );
1861  }
1862  
1863  /**
1864   * Handles for retrieving menu meta boxes via AJAX.
1865   *
1866   * @since 3.1.0
1867   */
1868  function wp_ajax_menu_get_metabox() {
1869      if ( ! current_user_can( 'edit_theme_options' ) ) {
1870          wp_die( -1 );
1871      }
1872  
1873      require_once  ABSPATH . 'wp-admin/includes/nav-menu.php';
1874  
1875      if ( isset( $_POST['item-type'] ) && 'post_type' === $_POST['item-type'] ) {
1876          $type     = 'posttype';
1877          $callback = 'wp_nav_menu_item_post_type_meta_box';
1878          $items    = (array) get_post_types( array( 'show_in_nav_menus' => true ), 'object' );
1879      } elseif ( isset( $_POST['item-type'] ) && 'taxonomy' === $_POST['item-type'] ) {
1880          $type     = 'taxonomy';
1881          $callback = 'wp_nav_menu_item_taxonomy_meta_box';
1882          $items    = (array) get_taxonomies( array( 'show_ui' => true ), 'object' );
1883      }
1884  
1885      if ( ! empty( $_POST['item-object'] ) && isset( $items[ $_POST['item-object'] ] ) ) {
1886          $menus_meta_box_object = $items[ $_POST['item-object'] ];
1887  
1888          /** This filter is documented in wp-admin/includes/nav-menu.php */
1889          $item = apply_filters( 'nav_menu_meta_box_object', $menus_meta_box_object );
1890  
1891          $box_args = array(
1892              'id'       => 'add-' . $item->name,
1893              'title'    => $item->labels->name,
1894              'callback' => $callback,
1895              'args'     => $item,
1896          );
1897  
1898          ob_start();
1899          $callback( null, $box_args );
1900  
1901          $markup = ob_get_clean();
1902  
1903          echo wp_json_encode(
1904              array(
1905                  'replace-id' => $type . '-' . $item->name,
1906                  'markup'     => $markup,
1907              )
1908          );
1909      }
1910  
1911      wp_die();
1912  }
1913  
1914  /**
1915   * Handles internal linking via AJAX.
1916   *
1917   * @since 3.1.0
1918   */
1919  function wp_ajax_wp_link_ajax() {
1920      check_ajax_referer( 'internal-linking', '_ajax_linking_nonce' );
1921  
1922      $args = array();
1923  
1924      if ( isset( $_POST['search'] ) ) {
1925          $args['s'] = wp_unslash( $_POST['search'] );
1926      }
1927  
1928      if ( isset( $_POST['term'] ) ) {
1929          $args['s'] = wp_unslash( $_POST['term'] );
1930      }
1931  
1932      $args['pagenum'] = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 1;
1933  
1934      if ( ! class_exists( '_WP_Editors', false ) ) {
1935          require  ABSPATH . WPINC . '/class-wp-editor.php';
1936      }
1937  
1938      $results = _WP_Editors::wp_link_query( $args );
1939  
1940      if ( ! isset( $results ) ) {
1941          wp_die( 0 );
1942      }
1943  
1944      echo wp_json_encode( $results );
1945      echo "\n";
1946  
1947      wp_die();
1948  }
1949  
1950  /**
1951   * Handles saving menu locations via AJAX.
1952   *
1953   * @since 3.1.0
1954   */
1955  function wp_ajax_menu_locations_save() {
1956      if ( ! current_user_can( 'edit_theme_options' ) ) {
1957          wp_die( -1 );
1958      }
1959  
1960      check_ajax_referer( 'add-menu_item', 'menu-settings-column-nonce' );
1961  
1962      if ( ! isset( $_POST['menu-locations'] ) ) {
1963          wp_die( 0 );
1964      }
1965  
1966      set_theme_mod( 'nav_menu_locations', array_map( 'absint', $_POST['menu-locations'] ) );
1967      wp_die( 1 );
1968  }
1969  
1970  /**
1971   * Handles saving the meta box order via AJAX.
1972   *
1973   * @since 3.1.0
1974   */
1975  function wp_ajax_meta_box_order() {
1976      check_ajax_referer( 'meta-box-order' );
1977      $order        = isset( $_POST['order'] ) ? (array) $_POST['order'] : false;
1978      $page_columns = isset( $_POST['page_columns'] ) ? $_POST['page_columns'] : 'auto';
1979  
1980      if ( 'auto' !== $page_columns ) {
1981          $page_columns = (int) $page_columns;
1982      }
1983  
1984      $page = isset( $_POST['page'] ) ? $_POST['page'] : '';
1985  
1986      if ( sanitize_key( $page ) != $page ) {
1987          wp_die( 0 );
1988      }
1989  
1990      $user = wp_get_current_user();
1991      if ( ! $user ) {
1992          wp_die( -1 );
1993      }
1994  
1995      if ( $order ) {
1996          update_user_meta( $user->ID, "meta-box-order_$page", $order );
1997      }
1998  
1999      if ( $page_columns ) {
2000          update_user_meta( $user->ID, "screen_layout_$page", $page_columns );
2001      }
2002  
2003      wp_send_json_success();
2004  }
2005  
2006  /**
2007   * Handles menu quick searching via AJAX.
2008   *
2009   * @since 3.1.0
2010   */
2011  function wp_ajax_menu_quick_search() {
2012      if ( ! current_user_can( 'edit_theme_options' ) ) {
2013          wp_die( -1 );
2014      }
2015  
2016      require_once  ABSPATH . 'wp-admin/includes/nav-menu.php';
2017  
2018      _wp_ajax_menu_quick_search( $_POST );
2019  
2020      wp_die();
2021  }
2022  
2023  /**
2024   * Handles retrieving a permalink via AJAX.
2025   *
2026   * @since 3.1.0
2027   */
2028  function wp_ajax_get_permalink() {
2029      check_ajax_referer( 'getpermalink', 'getpermalinknonce' );
2030      $post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
2031      wp_die( get_preview_post_link( $post_id ) );
2032  }
2033  
2034  /**
2035   * Handles retrieving a sample permalink via AJAX.
2036   *
2037   * @since 3.1.0
2038   */
2039  function wp_ajax_sample_permalink() {
2040      check_ajax_referer( 'samplepermalink', 'samplepermalinknonce' );
2041      $post_id = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
2042      $title   = isset( $_POST['new_title'] ) ? $_POST['new_title'] : '';
2043      $slug    = isset( $_POST['new_slug'] ) ? $_POST['new_slug'] : null;
2044      wp_die( get_sample_permalink_html( $post_id, $title, $slug ) );
2045  }
2046  
2047  /**
2048   * Handles Quick Edit saving a post from a list table via AJAX.
2049   *
2050   * @since 3.1.0
2051   *
2052   * @global string $mode List table view mode.
2053   */
2054  function wp_ajax_inline_save() {
2055      global $mode;
2056  
2057      check_ajax_referer( 'inlineeditnonce', '_inline_edit' );
2058  
2059      if ( ! isset( $_POST['post_ID'] ) || ! (int) $_POST['post_ID'] ) {
2060          wp_die();
2061      }
2062  
2063      $post_id = (int) $_POST['post_ID'];
2064  
2065      if ( 'page' === $_POST['post_type'] ) {
2066          if ( ! current_user_can( 'edit_page', $post_id ) ) {
2067              wp_die( __( 'Sorry, you are not allowed to edit this page.' ) );
2068          }
2069      } else {
2070          if ( ! current_user_can( 'edit_post', $post_id ) ) {
2071              wp_die( __( 'Sorry, you are not allowed to edit this post.' ) );
2072          }
2073      }
2074  
2075      $last = wp_check_post_lock( $post_id );
2076      if ( $last ) {
2077          $last_user      = get_userdata( $last );
2078          $last_user_name = $last_user ? $last_user->display_name : __( 'Someone' );
2079  
2080          /* translators: %s: User's display name. */
2081          $msg_template = __( 'Saving is disabled: %s is currently editing this post.' );
2082  
2083          if ( 'page' === $_POST['post_type'] ) {
2084              /* translators: %s: User's display name. */
2085              $msg_template = __( 'Saving is disabled: %s is currently editing this page.' );
2086          }
2087  
2088          printf( $msg_template, esc_html( $last_user_name ) );
2089          wp_die();
2090      }
2091  
2092      $data = &$_POST;
2093  
2094      $post = get_post( $post_id, ARRAY_A );
2095  
2096      // Since it's coming from the database.
2097      $post = wp_slash( $post );
2098  
2099      $data['content'] = $post['post_content'];
2100      $data['excerpt'] = $post['post_excerpt'];
2101  
2102      // Rename.
2103      $data['user_ID'] = get_current_user_id();
2104  
2105      if ( isset( $data['post_parent'] ) ) {
2106          $data['parent_id'] = $data['post_parent'];
2107      }
2108  
2109      // Status.
2110      if ( isset( $data['keep_private'] ) && 'private' === $data['keep_private'] ) {
2111          $data['visibility']  = 'private';
2112          $data['post_status'] = 'private';
2113      } else {
2114          $data['post_status'] = $data['_status'];
2115      }
2116  
2117      if ( empty( $data['comment_status'] ) ) {
2118          $data['comment_status'] = 'closed';
2119      }
2120  
2121      if ( empty( $data['ping_status'] ) ) {
2122          $data['ping_status'] = 'closed';
2123      }
2124  
2125      // Exclude terms from taxonomies that are not supposed to appear in Quick Edit.
2126      if ( ! empty( $data['tax_input'] ) ) {
2127          foreach ( $data['tax_input'] as $taxonomy => $terms ) {
2128              $tax_object = get_taxonomy( $taxonomy );
2129              /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */
2130              if ( ! apply_filters( 'quick_edit_show_taxonomy', $tax_object->show_in_quick_edit, $taxonomy, $post['post_type'] ) ) {
2131                  unset( $data['tax_input'][ $taxonomy ] );
2132              }
2133          }
2134      }
2135  
2136      // Hack: wp_unique_post_slug() doesn't work for drafts, so we will fake that our post is published.
2137      if ( ! empty( $data['post_name'] ) && in_array( $post['post_status'], array( 'draft', 'pending' ), true ) ) {
2138          $post['post_status'] = 'publish';
2139          $data['post_name']   = wp_unique_post_slug( $data['post_name'], $post['ID'], $post['post_status'], $post['post_type'], $post['post_parent'] );
2140      }
2141  
2142      // Update the post.
2143      edit_post();
2144  
2145      $wp_list_table = _get_list_table( 'WP_Posts_List_Table', array( 'screen' => $_POST['screen'] ) );
2146  
2147      $mode = 'excerpt' === $_POST['post_view'] ? 'excerpt' : 'list';
2148  
2149      $level = 0;
2150      if ( is_post_type_hierarchical( $wp_list_table->screen->post_type ) ) {
2151          $request_post = array( get_post( $_POST['post_ID'] ) );
2152          $parent       = $request_post[0]->post_parent;
2153  
2154          while ( $parent > 0 ) {
2155              $parent_post = get_post( $parent );
2156              $parent      = $parent_post->post_parent;
2157              ++$level;
2158          }
2159      }
2160  
2161      $wp_list_table->display_rows( array( get_post( $_POST['post_ID'] ) ), $level );
2162  
2163      wp_die();
2164  }
2165  
2166  /**
2167   * Handles Quick Edit saving for a term via AJAX.
2168   *
2169   * @since 3.1.0
2170   */
2171  function wp_ajax_inline_save_tax() {
2172      check_ajax_referer( 'taxinlineeditnonce', '_inline_edit' );
2173  
2174      $taxonomy        = sanitize_key( $_POST['taxonomy'] );
2175      $taxonomy_object = get_taxonomy( $taxonomy );
2176  
2177      if ( ! $taxonomy_object ) {
2178          wp_die( 0 );
2179      }
2180  
2181      if ( ! isset( $_POST['tax_ID'] ) || ! (int) $_POST['tax_ID'] ) {
2182          wp_die( -1 );
2183      }
2184  
2185      $id = (int) $_POST['tax_ID'];
2186  
2187      if ( ! current_user_can( 'edit_term', $id ) ) {
2188          wp_die( -1 );
2189      }
2190  
2191      $wp_list_table = _get_list_table( 'WP_Terms_List_Table', array( 'screen' => 'edit-' . $taxonomy ) );
2192  
2193      $tag                  = get_term( $id, $taxonomy );
2194      $_POST['description'] = $tag->description;
2195  
2196      $updated = wp_update_term( $id, $taxonomy, $_POST );
2197  
2198      if ( $updated && ! is_wp_error( $updated ) ) {
2199          $tag = get_term( $updated['term_id'], $taxonomy );
2200          if ( ! $tag || is_wp_error( $tag ) ) {
2201              if ( is_wp_error( $tag ) && $tag->get_error_message() ) {
2202                  wp_die( $tag->get_error_message() );
2203              }
2204              wp_die( __( 'Item not updated.' ) );
2205          }
2206      } else {
2207          if ( is_wp_error( $updated ) && $updated->get_error_message() ) {
2208              wp_die( $updated->get_error_message() );
2209          }
2210          wp_die( __( 'Item not updated.' ) );
2211      }
2212  
2213      $level  = 0;
2214      $parent = $tag->parent;
2215  
2216      while ( $parent > 0 ) {
2217          $parent_tag = get_term( $parent, $taxonomy );
2218          $parent     = $parent_tag->parent;
2219          ++$level;
2220      }
2221  
2222      $wp_list_table->single_row( $tag, $level );
2223      wp_die();
2224  }
2225  
2226  /**
2227   * Handles querying posts for the Find Posts modal via AJAX.
2228   *
2229   * @see window.findPosts
2230   *
2231   * @since 3.1.0
2232   */
2233  function wp_ajax_find_posts() {
2234      check_ajax_referer( 'find-posts' );
2235  
2236      $post_types = get_post_types( array( 'public' => true ), 'objects' );
2237      unset( $post_types['attachment'] );
2238  
2239      $args = array(
2240          'post_type'      => array_keys( $post_types ),
2241          'post_status'    => 'any',
2242          'posts_per_page' => 50,
2243      );
2244  
2245      $search = wp_unslash( $_POST['ps'] );
2246  
2247      if ( '' !== $search ) {
2248          $args['s'] = $search;
2249      }
2250  
2251      $posts = get_posts( $args );
2252  
2253      if ( ! $posts ) {
2254          wp_send_json_error( __( 'No items found.' ) );
2255      }
2256  
2257      $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>';
2258      $alt  = '';
2259      foreach ( $posts as $post ) {
2260          $title = trim( $post->post_title ) ? $post->post_title : __( '(no title)' );
2261          $alt   = ( 'alternate' === $alt ) ? '' : 'alternate';
2262  
2263          switch ( $post->post_status ) {
2264              case 'publish':
2265              case 'private':
2266                  $stat = __( 'Published' );
2267                  break;
2268              case 'future':
2269                  $stat = __( 'Scheduled' );
2270                  break;
2271              case 'pending':
2272                  $stat = __( 'Pending Review' );
2273                  break;
2274              case 'draft':
2275                  $stat = __( 'Draft' );
2276                  break;
2277          }
2278  
2279          if ( '0000-00-00 00:00:00' === $post->post_date ) {
2280              $time = '';
2281          } else {
2282              /* translators: Date format in table columns, see https://www.php.net/manual/datetime.format.php */
2283              $time = mysql2date( __( 'Y/m/d' ), $post->post_date );
2284          }
2285  
2286          $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>';
2287          $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";
2288      }
2289  
2290      $html .= '</tbody></table>';
2291  
2292      wp_send_json_success( $html );
2293  }
2294  
2295  /**
2296   * Handles saving the widgets order via AJAX.
2297   *
2298   * @since 3.1.0
2299   */
2300  function wp_ajax_widgets_order() {
2301      check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
2302  
2303      if ( ! current_user_can( 'edit_theme_options' ) ) {
2304          wp_die( -1 );
2305      }
2306  
2307      unset( $_POST['savewidgets'], $_POST['action'] );
2308  
2309      // Save widgets order for all sidebars.
2310      if ( is_array( $_POST['sidebars'] ) ) {
2311          $sidebars = array();
2312  
2313          foreach ( wp_unslash( $_POST['sidebars'] ) as $key => $val ) {
2314              $sb = array();
2315  
2316              if ( ! empty( $val ) ) {
2317                  $val = explode( ',', $val );
2318  
2319                  foreach ( $val as $k => $v ) {
2320                      if ( ! str_contains( $v, 'widget-' ) ) {
2321                          continue;
2322                      }
2323  
2324                      $sb[ $k ] = substr( $v, strpos( $v, '_' ) + 1 );
2325                  }
2326              }
2327              $sidebars[ $key ] = $sb;
2328          }
2329  
2330          wp_set_sidebars_widgets( $sidebars );
2331          wp_die( 1 );
2332      }
2333  
2334      wp_die( -1 );
2335  }
2336  
2337  /**
2338   * Handles saving a widget via AJAX.
2339   *
2340   * @since 3.1.0
2341   *
2342   * @global array $wp_registered_widgets
2343   * @global array $wp_registered_widget_controls
2344   * @global array $wp_registered_widget_updates
2345   */
2346  function wp_ajax_save_widget() {
2347      global $wp_registered_widgets, $wp_registered_widget_controls, $wp_registered_widget_updates;
2348  
2349      check_ajax_referer( 'save-sidebar-widgets', 'savewidgets' );
2350  
2351      if ( ! current_user_can( 'edit_theme_options' ) || ! isset( $_POST['id_base'] ) ) {
2352          wp_die( -1 );
2353      }
2354  
2355      unset( $_POST['savewidgets'], $_POST['action'] );
2356  
2357      /**
2358       * Fires early when editing the widgets displayed in sidebars.
2359       *
2360       * @since 2.8.0
2361       */
2362      do_action( 'load-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
2363  
2364      /**
2365       * Fires early when editing the widgets displayed in sidebars.
2366       *
2367       * @since 2.8.0
2368       */
2369      do_action( 'widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
2370  
2371      /** This action is documented in wp-admin/widgets.php */
2372      do_action( 'sidebar_admin_setup' );
2373  
2374      $id_base      = wp_unslash( $_POST['id_base'] );
2375      $widget_id    = wp_unslash( $_POST['widget-id'] );
2376      $sidebar_id   = $_POST['sidebar'];
2377      $multi_number = ! empty( $_POST['multi_number'] ) ? (int) $_POST['multi_number'] : 0;
2378      $settings     = isset( $_POST[ 'widget-' . $id_base ] ) && is_array( $_POST[ 'widget-' . $id_base ] ) ? $_POST[ 'widget-' . $id_base ] : false;
2379      $error        = '<p>' . __( 'An error has occurred. Please reload the page and try again.' ) . '</p>';
2380  
2381      $sidebars = wp_get_sidebars_widgets();
2382      $sidebar  = isset( $sidebars[ $sidebar_id ] ) ? $sidebars[ $sidebar_id ] : array();
2383  
2384      // Delete.
2385      if ( isset( $_POST['delete_widget'] ) && $_POST['delete_widget'] ) {
2386  
2387          if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) {
2388              wp_die( $error );
2389          }
2390  
2391          $sidebar = array_diff( $sidebar, array( $widget_id ) );
2392          $_POST   = array(
2393              'sidebar'            => $sidebar_id,
2394              'widget-' . $id_base => array(),
2395              'the-widget-id'      => $widget_id,
2396              'delete_widget'      => '1',
2397          );
2398  
2399          /** This action is documented in wp-admin/widgets.php */
2400          do_action( 'delete_widget', $widget_id, $sidebar_id, $id_base );
2401  
2402      } elseif ( $settings && preg_match( '/__i__|%i%/', key( $settings ) ) ) {
2403          if ( ! $multi_number ) {
2404              wp_die( $error );
2405          }
2406  
2407          $_POST[ 'widget-' . $id_base ] = array( $multi_number => reset( $settings ) );
2408          $widget_id                     = $id_base . '-' . $multi_number;
2409          $sidebar[]                     = $widget_id;
2410      }
2411      $_POST['widget-id'] = $sidebar;
2412  
2413      foreach ( (array) $wp_registered_widget_updates as $name => $control ) {
2414  
2415          if ( $name == $id_base ) {
2416              if ( ! is_callable( $control['callback'] ) ) {
2417                  continue;
2418              }
2419  
2420              ob_start();
2421                  call_user_func_array( $control['callback'], $control['params'] );
2422              ob_end_clean();
2423              break;
2424          }
2425      }
2426  
2427      if ( isset( $_POST['delete_widget'] ) && $_POST['delete_widget'] ) {
2428          $sidebars[ $sidebar_id ] = $sidebar;
2429          wp_set_sidebars_widgets( $sidebars );
2430          echo "deleted:$widget_id";
2431          wp_die();
2432      }
2433  
2434      if ( ! empty( $_POST['add_new'] ) ) {
2435          wp_die();
2436      }
2437  
2438      $form = $wp_registered_widget_controls[ $widget_id ];
2439      if ( $form ) {
2440          call_user_func_array( $form['callback'], $form['params'] );
2441      }
2442  
2443      wp_die();
2444  }
2445  
2446  /**
2447   * Handles updating a widget via AJAX.
2448   *
2449   * @since 3.9.0
2450   *
2451   * @global WP_Customize_Manager $wp_customize
2452   */
2453  function wp_ajax_update_widget() {
2454      global $wp_customize;
2455      $wp_customize->widgets->wp_ajax_update_widget();
2456  }
2457  
2458  /**
2459   * Handles removing inactive widgets via AJAX.
2460   *
2461   * @since 4.4.0
2462   */
2463  function wp_ajax_delete_inactive_widgets() {
2464      check_ajax_referer( 'remove-inactive-widgets', 'removeinactivewidgets' );
2465  
2466      if ( ! current_user_can( 'edit_theme_options' ) ) {
2467          wp_die( -1 );
2468      }
2469  
2470      unset( $_POST['removeinactivewidgets'], $_POST['action'] );
2471      /** This action is documented in wp-admin/includes/ajax-actions.php */
2472      do_action( 'load-widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
2473      /** This action is documented in wp-admin/includes/ajax-actions.php */
2474      do_action( 'widgets.php' ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
2475      /** This action is documented in wp-admin/widgets.php */
2476      do_action( 'sidebar_admin_setup' );
2477  
2478      $sidebars_widgets = wp_get_sidebars_widgets();
2479  
2480      foreach ( $sidebars_widgets['wp_inactive_widgets'] as $key => $widget_id ) {
2481          $pieces       = explode( '-', $widget_id );
2482          $multi_number = array_pop( $pieces );
2483          $id_base      = implode( '-', $pieces );
2484          $widget       = get_option( 'widget_' . $id_base );
2485          unset( $widget[ $multi_number ] );
2486          update_option( 'widget_' . $id_base, $widget );
2487          unset( $sidebars_widgets['wp_inactive_widgets'][ $key ] );
2488      }
2489  
2490      wp_set_sidebars_widgets( $sidebars_widgets );
2491  
2492      wp_die();
2493  }
2494  
2495  /**
2496   * Handles creating missing image sub-sizes for just uploaded images via AJAX.
2497   *
2498   * @since 5.3.0
2499   */
2500  function wp_ajax_media_create_image_subsizes() {
2501      check_ajax_referer( 'media-form' );
2502  
2503      if ( ! current_user_can( 'upload_files' ) ) {
2504          wp_send_json_error( array( 'message' => __( 'Sorry, you are not allowed to upload files.' ) ) );
2505      }
2506  
2507      if ( empty( $_POST['attachment_id'] ) ) {
2508          wp_send_json_error( array( 'message' => __( 'Upload failed. Please reload and try again.' ) ) );
2509      }
2510  
2511      $attachment_id = (int) $_POST['attachment_id'];
2512  
2513      if ( ! empty( $_POST['_wp_upload_failed_cleanup'] ) ) {
2514          // Upload failed. Cleanup.
2515          if ( wp_attachment_is_image( $attachment_id ) && current_user_can( 'delete_post', $attachment_id ) ) {
2516              $attachment = get_post( $attachment_id );
2517  
2518              // Created at most 10 min ago.
2519              if ( $attachment && ( time() - strtotime( $attachment->post_date_gmt ) < 600 ) ) {
2520                  wp_delete_attachment( $attachment_id, true );
2521                  wp_send_json_success();
2522              }
2523          }
2524      }
2525  
2526      /*
2527       * Set a custom header with the attachment_id.
2528       * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
2529       */
2530      if ( ! headers_sent() ) {
2531          header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
2532      }
2533  
2534      /*
2535       * This can still be pretty slow and cause timeout or out of memory errors.
2536       * The js that handles the response would need to also handle HTTP 500 errors.
2537       */
2538      wp_update_image_subsizes( $attachment_id );
2539  
2540      if ( ! empty( $_POST['_legacy_support'] ) ) {
2541          // The old (inline) uploader. Only needs the attachment_id.
2542          $response = array( 'id' => $attachment_id );
2543      } else {
2544          // Media modal and Media Library grid view.
2545          $response = wp_prepare_attachment_for_js( $attachment_id );
2546  
2547          if ( ! $response ) {
2548              wp_send_json_error( array( 'message' => __( 'Upload failed.' ) ) );
2549          }
2550      }
2551  
2552      // At this point the image has been uploaded successfully.
2553      wp_send_json_success( $response );
2554  }
2555  
2556  /**
2557   * Handles uploading attachments via AJAX.
2558   *
2559   * @since 3.3.0
2560   */
2561  function wp_ajax_upload_attachment() {
2562      check_ajax_referer( 'media-form' );
2563      /*
2564       * This function does not use wp_send_json_success() / wp_send_json_error()
2565       * as the html4 Plupload handler requires a text/html Content-Type for older IE.
2566       * See https://core.trac.wordpress.org/ticket/31037
2567       */
2568  
2569      if ( ! current_user_can( 'upload_files' ) ) {
2570          echo wp_json_encode(
2571              array(
2572                  'success' => false,
2573                  'data'    => array(
2574                      'message'  => __( 'Sorry, you are not allowed to upload files.' ),
2575                      'filename' => esc_html( $_FILES['async-upload']['name'] ),
2576                  ),
2577              )
2578          );
2579  
2580          wp_die();
2581      }
2582  
2583      if ( isset( $_REQUEST['post_id'] ) ) {
2584          $post_id = $_REQUEST['post_id'];
2585  
2586          if ( ! current_user_can( 'edit_post', $post_id ) ) {
2587              echo wp_json_encode(
2588                  array(
2589                      'success' => false,
2590                      'data'    => array(
2591                          'message'  => __( 'Sorry, you are not allowed to attach files to this post.' ),
2592                          'filename' => esc_html( $_FILES['async-upload']['name'] ),
2593                      ),
2594                  )
2595              );
2596  
2597              wp_die();
2598          }
2599      } else {
2600          $post_id = null;
2601      }
2602  
2603      $post_data = ! empty( $_REQUEST['post_data'] ) ? _wp_get_allowed_postdata( _wp_translate_postdata( false, (array) $_REQUEST['post_data'] ) ) : array();
2604  
2605      if ( is_wp_error( $post_data ) ) {
2606          wp_die( $post_data->get_error_message() );
2607      }
2608  
2609      // If the context is custom header or background, make sure the uploaded file is an image.
2610      if ( isset( $post_data['context'] ) && in_array( $post_data['context'], array( 'custom-header', 'custom-background' ), true ) ) {
2611          $wp_filetype = wp_check_filetype_and_ext( $_FILES['async-upload']['tmp_name'], $_FILES['async-upload']['name'] );
2612  
2613          if ( ! wp_match_mime_types( 'image', $wp_filetype['type'] ) ) {
2614              echo wp_json_encode(
2615                  array(
2616                      'success' => false,
2617                      'data'    => array(
2618                          'message'  => __( 'The uploaded file is not a valid image. Please try again.' ),
2619                          'filename' => esc_html( $_FILES['async-upload']['name'] ),
2620                      ),
2621                  )
2622              );
2623  
2624              wp_die();
2625          }
2626      }
2627  
2628      $attachment_id = media_handle_upload( 'async-upload', $post_id, $post_data );
2629  
2630      if ( is_wp_error( $attachment_id ) ) {
2631          echo wp_json_encode(
2632              array(
2633                  'success' => false,
2634                  'data'    => array(
2635                      'message'  => $attachment_id->get_error_message(),
2636                      'filename' => esc_html( $_FILES['async-upload']['name'] ),
2637                  ),
2638              )
2639          );
2640  
2641          wp_die();
2642      }
2643  
2644      if ( isset( $post_data['context'] ) && isset( $post_data['theme'] ) ) {
2645          if ( 'custom-background' === $post_data['context'] ) {
2646              update_post_meta( $attachment_id, '_wp_attachment_is_custom_background', $post_data['theme'] );
2647          }
2648  
2649          if ( 'custom-header' === $post_data['context'] ) {
2650              update_post_meta( $attachment_id, '_wp_attachment_is_custom_header', $post_data['theme'] );
2651          }
2652      }
2653  
2654      $attachment = wp_prepare_attachment_for_js( $attachment_id );
2655      if ( ! $attachment ) {
2656          wp_die();
2657      }
2658  
2659      echo wp_json_encode(
2660          array(
2661              'success' => true,
2662              'data'    => $attachment,
2663          )
2664      );
2665  
2666      wp_die();
2667  }
2668  
2669  /**
2670   * Handles image editing via AJAX.
2671   *
2672   * @since 3.1.0
2673   */
2674  function wp_ajax_image_editor() {
2675      $attachment_id = (int) $_POST['postid'];
2676  
2677      if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
2678          wp_die( -1 );
2679      }
2680  
2681      check_ajax_referer( "image_editor-$attachment_id" );
2682      require_once  ABSPATH . 'wp-admin/includes/image-edit.php';
2683  
2684      $msg = false;
2685  
2686      switch ( $_POST['do'] ) {
2687          case 'save':
2688              $msg = wp_save_image( $attachment_id );
2689              if ( ! empty( $msg->error ) ) {
2690                  wp_send_json_error( $msg );
2691              }
2692  
2693              wp_send_json_success( $msg );
2694              break;
2695          case 'scale':
2696              $msg = wp_save_image( $attachment_id );
2697              break;
2698          case 'restore':
2699              $msg = wp_restore_image( $attachment_id );
2700              break;
2701      }
2702  
2703      ob_start();
2704      wp_image_editor( $attachment_id, $msg );
2705      $html = ob_get_clean();
2706  
2707      if ( ! empty( $msg->error ) ) {
2708          wp_send_json_error(
2709              array(
2710                  'message' => $msg,
2711                  'html'    => $html,
2712              )
2713          );
2714      }
2715  
2716      wp_send_json_success(
2717          array(
2718              'message' => $msg,
2719              'html'    => $html,
2720          )
2721      );
2722  }
2723  
2724  /**
2725   * Handles setting the featured image via AJAX.
2726   *
2727   * @since 3.1.0
2728   */
2729  function wp_ajax_set_post_thumbnail() {
2730      $json = ! empty( $_REQUEST['json'] ); // New-style request.
2731  
2732      $post_id = (int) $_POST['post_id'];
2733      if ( ! current_user_can( 'edit_post', $post_id ) ) {
2734          wp_die( -1 );
2735      }
2736  
2737      $thumbnail_id = (int) $_POST['thumbnail_id'];
2738  
2739      if ( $json ) {
2740          check_ajax_referer( "update-post_$post_id" );
2741      } else {
2742          check_ajax_referer( "set_post_thumbnail-$post_id" );
2743      }
2744  
2745      if ( '-1' == $thumbnail_id ) {
2746          if ( delete_post_thumbnail( $post_id ) ) {
2747              $return = _wp_post_thumbnail_html( null, $post_id );
2748              $json ? wp_send_json_success( $return ) : wp_die( $return );
2749          } else {
2750              wp_die( 0 );
2751          }
2752      }
2753  
2754      if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2755          $return = _wp_post_thumbnail_html( $thumbnail_id, $post_id );
2756          $json ? wp_send_json_success( $return ) : wp_die( $return );
2757      }
2758  
2759      wp_die( 0 );
2760  }
2761  
2762  /**
2763   * Handles retrieving HTML for the featured image via AJAX.
2764   *
2765   * @since 4.6.0
2766   */
2767  function wp_ajax_get_post_thumbnail_html() {
2768      $post_id = (int) $_POST['post_id'];
2769  
2770      check_ajax_referer( "update-post_$post_id" );
2771  
2772      if ( ! current_user_can( 'edit_post', $post_id ) ) {
2773          wp_die( -1 );
2774      }
2775  
2776      $thumbnail_id = (int) $_POST['thumbnail_id'];
2777  
2778      // For backward compatibility, -1 refers to no featured image.
2779      if ( -1 === $thumbnail_id ) {
2780          $thumbnail_id = null;
2781      }
2782  
2783      $return = _wp_post_thumbnail_html( $thumbnail_id, $post_id );
2784      wp_send_json_success( $return );
2785  }
2786  
2787  /**
2788   * Handles setting the featured image for an attachment via AJAX.
2789   *
2790   * @since 4.0.0
2791   *
2792   * @see set_post_thumbnail()
2793   */
2794  function wp_ajax_set_attachment_thumbnail() {
2795      if ( empty( $_POST['urls'] ) || ! is_array( $_POST['urls'] ) ) {
2796          wp_send_json_error();
2797      }
2798  
2799      $thumbnail_id = (int) $_POST['thumbnail_id'];
2800      if ( empty( $thumbnail_id ) ) {
2801          wp_send_json_error();
2802      }
2803  
2804      if ( false === check_ajax_referer( 'set-attachment-thumbnail', '_ajax_nonce', false ) ) {
2805          wp_send_json_error();
2806      }
2807  
2808      $post_ids = array();
2809      // For each URL, try to find its corresponding post ID.
2810      foreach ( $_POST['urls'] as $url ) {
2811          $post_id = attachment_url_to_postid( $url );
2812          if ( ! empty( $post_id ) ) {
2813              $post_ids[] = $post_id;
2814          }
2815      }
2816  
2817      if ( empty( $post_ids ) ) {
2818          wp_send_json_error();
2819      }
2820  
2821      $success = 0;
2822      // For each found attachment, set its thumbnail.
2823      foreach ( $post_ids as $post_id ) {
2824          if ( ! current_user_can( 'edit_post', $post_id ) ) {
2825              continue;
2826          }
2827  
2828          if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
2829              ++$success;
2830          }
2831      }
2832  
2833      if ( 0 === $success ) {
2834          wp_send_json_error();
2835      } else {
2836          wp_send_json_success();
2837      }
2838  
2839      wp_send_json_error();
2840  }
2841  
2842  /**
2843   * Handles formatting a date via AJAX.
2844   *
2845   * @since 3.1.0
2846   */
2847  function wp_ajax_date_format() {
2848      wp_die( date_i18n( sanitize_option( 'date_format', wp_unslash( $_POST['date'] ) ) ) );
2849  }
2850  
2851  /**
2852   * Handles formatting a time via AJAX.
2853   *
2854   * @since 3.1.0
2855   */
2856  function wp_ajax_time_format() {
2857      wp_die( date_i18n( sanitize_option( 'time_format', wp_unslash( $_POST['date'] ) ) ) );
2858  }
2859  
2860  /**
2861   * Handles saving posts from the fullscreen editor via AJAX.
2862   *
2863   * @since 3.1.0
2864   * @deprecated 4.3.0
2865   */
2866  function wp_ajax_wp_fullscreen_save_post() {
2867      $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
2868  
2869      $post = null;
2870  
2871      if ( $post_id ) {
2872          $post = get_post( $post_id );
2873      }
2874  
2875      check_ajax_referer( 'update-post_' . $post_id, '_wpnonce' );
2876  
2877      $post_id = edit_post();
2878  
2879      if ( is_wp_error( $post_id ) ) {
2880          wp_send_json_error();
2881      }
2882  
2883      if ( $post ) {
2884          $last_date = mysql2date( __( 'F j, Y' ), $post->post_modified );
2885          $last_time = mysql2date( __( 'g:i a' ), $post->post_modified );
2886      } else {
2887          $last_date = date_i18n( __( 'F j, Y' ) );
2888          $last_time = date_i18n( __( 'g:i a' ) );
2889      }
2890  
2891      $last_id = get_post_meta( $post_id, '_edit_last', true );
2892      if ( $last_id ) {
2893          $last_user = get_userdata( $last_id );
2894          /* translators: 1: User's display name, 2: Date of last edit, 3: Time of last edit. */
2895          $last_edited = sprintf( __( 'Last edited by %1$s on %2$s at %3$s' ), esc_html( $last_user->display_name ), $last_date, $last_time );
2896      } else {
2897          /* translators: 1: Date of last edit, 2: Time of last edit. */
2898          $last_edited = sprintf( __( 'Last edited on %1$s at %2$s' ), $last_date, $last_time );
2899      }
2900  
2901      wp_send_json_success( array( 'last_edited' => $last_edited ) );
2902  }
2903  
2904  /**
2905   * Handles removing a post lock via AJAX.
2906   *
2907   * @since 3.1.0
2908   */
2909  function wp_ajax_wp_remove_post_lock() {
2910      if ( empty( $_POST['post_ID'] ) || empty( $_POST['active_post_lock'] ) ) {
2911          wp_die( 0 );
2912      }
2913  
2914      $post_id = (int) $_POST['post_ID'];
2915      $post    = get_post( $post_id );
2916  
2917      if ( ! $post ) {
2918          wp_die( 0 );
2919      }
2920  
2921      check_ajax_referer( 'update-post_' . $post_id );
2922  
2923      if ( ! current_user_can( 'edit_post', $post_id ) ) {
2924          wp_die( -1 );
2925      }
2926  
2927      $active_lock = array_map( 'absint', explode( ':', $_POST['active_post_lock'] ) );
2928  
2929      if ( get_current_user_id() != $active_lock[1] ) {
2930          wp_die( 0 );
2931      }
2932  
2933      /**
2934       * Filters the post lock window duration.
2935       *
2936       * @since 3.3.0
2937       *
2938       * @param int $interval The interval in seconds the post lock duration
2939       *                      should last, plus 5 seconds. Default 150.
2940       */
2941      $new_lock = ( time() - apply_filters( 'wp_check_post_lock_window', 150 ) + 5 ) . ':' . $active_lock[1];
2942      update_post_meta( $post_id, '_edit_lock', $new_lock, implode( ':', $active_lock ) );
2943      wp_die( 1 );
2944  }
2945  
2946  /**
2947   * Handles dismissing a WordPress pointer via AJAX.
2948   *
2949   * @since 3.1.0
2950   */
2951  function wp_ajax_dismiss_wp_pointer() {
2952      $pointer = $_POST['pointer'];
2953  
2954      if ( sanitize_key( $pointer ) != $pointer ) {
2955          wp_die( 0 );
2956      }
2957  
2958      //  check_ajax_referer( 'dismiss-pointer_' . $pointer );
2959  
2960      $dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) );
2961  
2962      if ( in_array( $pointer, $dismissed, true ) ) {
2963          wp_die( 0 );
2964      }
2965  
2966      $dismissed[] = $pointer;
2967      $dismissed   = implode( ',', $dismissed );
2968  
2969      update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed );
2970      wp_die( 1 );
2971  }
2972  
2973  /**
2974   * Handles getting an attachment via AJAX.
2975   *
2976   * @since 3.5.0
2977   */
2978  function wp_ajax_get_attachment() {
2979      if ( ! isset( $_REQUEST['id'] ) ) {
2980          wp_send_json_error();
2981      }
2982  
2983      $id = absint( $_REQUEST['id'] );
2984      if ( ! $id ) {
2985          wp_send_json_error();
2986      }
2987  
2988      $post = get_post( $id );
2989      if ( ! $post ) {
2990          wp_send_json_error();
2991      }
2992  
2993      if ( 'attachment' !== $post->post_type ) {
2994          wp_send_json_error();
2995      }
2996  
2997      if ( ! current_user_can( 'upload_files' ) ) {
2998          wp_send_json_error();
2999      }
3000  
3001      $attachment = wp_prepare_attachment_for_js( $id );
3002      if ( ! $attachment ) {
3003          wp_send_json_error();
3004      }
3005  
3006      wp_send_json_success( $attachment );
3007  }
3008  
3009  /**
3010   * Handles querying attachments via AJAX.
3011   *
3012   * @since 3.5.0
3013   */
3014  function wp_ajax_query_attachments() {
3015      if ( ! current_user_can( 'upload_files' ) ) {
3016          wp_send_json_error();
3017      }
3018  
3019      $query = isset( $_REQUEST['query'] ) ? (array) $_REQUEST['query'] : array();
3020      $keys  = array(
3021          's',
3022          'order',
3023          'orderby',
3024          'posts_per_page',
3025          'paged',
3026          'post_mime_type',
3027          'post_parent',
3028          'author',
3029          'post__in',
3030          'post__not_in',
3031          'year',
3032          'monthnum',
3033      );
3034  
3035      foreach ( get_taxonomies_for_attachments( 'objects' ) as $t ) {
3036          if ( $t->query_var && isset( $query[ $t->query_var ] ) ) {
3037              $keys[] = $t->query_var;
3038          }
3039      }
3040  
3041      $query              = array_intersect_key( $query, array_flip( $keys ) );
3042      $query['post_type'] = 'attachment';
3043  
3044      if (
3045          MEDIA_TRASH &&
3046          ! empty( $_REQUEST['query']['post_status'] ) &&
3047          'trash' === $_REQUEST['query']['post_status']
3048      ) {
3049          $query['post_status'] = 'trash';
3050      } else {
3051          $query['post_status'] = 'inherit';
3052      }
3053  
3054      if ( current_user_can( get_post_type_object( 'attachment' )->cap->read_private_posts ) ) {
3055          $query['post_status'] .= ',private';
3056      }
3057  
3058      // Filter query clauses to include filenames.
3059      if ( isset( $query['s'] ) ) {
3060          add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' );
3061      }
3062  
3063      /**
3064       * Filters the arguments passed to WP_Query during an Ajax
3065       * call for querying attachments.
3066       *
3067       * @since 3.7.0
3068       *
3069       * @see WP_Query::parse_query()
3070       *
3071       * @param array $query An array of query variables.
3072       */
3073      $query             = apply_filters( 'ajax_query_attachments_args', $query );
3074      $attachments_query = new WP_Query( $query );
3075      update_post_parent_caches( $attachments_query->posts );
3076  
3077      $posts       = array_map( 'wp_prepare_attachment_for_js', $attachments_query->posts );
3078      $posts       = array_filter( $posts );
3079      $total_posts = $attachments_query->found_posts;
3080  
3081      if ( $total_posts < 1 ) {
3082          // Out-of-bounds, run the query again without LIMIT for total count.
3083          unset( $query['paged'] );
3084  
3085          $count_query = new WP_Query();
3086          $count_query->query( $query );
3087          $total_posts = $count_query->found_posts;
3088      }
3089  
3090      $posts_per_page = (int) $attachments_query->get( 'posts_per_page' );
3091  
3092      $max_pages = $posts_per_page ? (int) ceil( $total_posts / $posts_per_page ) : 0;
3093  
3094      header( 'X-WP-Total: ' . (int) $total_posts );
3095      header( 'X-WP-TotalPages: ' . $max_pages );
3096  
3097      wp_send_json_success( $posts );
3098  }
3099  
3100  /**
3101   * Handles updating attachment attributes via AJAX.
3102   *
3103   * @since 3.5.0
3104   */
3105  function wp_ajax_save_attachment() {
3106      if ( ! isset( $_REQUEST['id'] ) || ! isset( $_REQUEST['changes'] ) ) {
3107          wp_send_json_error();
3108      }
3109  
3110      $id = absint( $_REQUEST['id'] );
3111      if ( ! $id ) {
3112          wp_send_json_error();
3113      }
3114  
3115      check_ajax_referer( 'update-post_' . $id, 'nonce' );
3116  
3117      if ( ! current_user_can( 'edit_post', $id ) ) {
3118          wp_send_json_error();
3119      }
3120  
3121      $changes = $_REQUEST['changes'];
3122      $post    = get_post( $id, ARRAY_A );
3123  
3124      if ( 'attachment' !== $post['post_type'] ) {
3125          wp_send_json_error();
3126      }
3127  
3128      if ( isset( $changes['parent'] ) ) {
3129          $post['post_parent'] = $changes['parent'];
3130      }
3131  
3132      if ( isset( $changes['title'] ) ) {
3133          $post['post_title'] = $changes['title'];
3134      }
3135  
3136      if ( isset( $changes['caption'] ) ) {
3137          $post['post_excerpt'] = $changes['caption'];
3138      }
3139  
3140      if ( isset( $changes['description'] ) ) {
3141          $post['post_content'] = $changes['description'];
3142      }
3143  
3144      if ( MEDIA_TRASH && isset( $changes['status'] ) ) {
3145          $post['post_status'] = $changes['status'];
3146      }
3147  
3148      if ( isset( $changes['alt'] ) ) {
3149          $alt = wp_unslash( $changes['alt'] );
3150          if ( get_post_meta( $id, '_wp_attachment_image_alt', true ) !== $alt ) {
3151              $alt = wp_strip_all_tags( $alt, true );
3152              update_post_meta( $id, '_wp_attachment_image_alt', wp_slash( $alt ) );
3153          }
3154      }
3155  
3156      if ( wp_attachment_is( 'audio', $post['ID'] ) ) {
3157          $changed = false;
3158          $id3data = wp_get_attachment_metadata( $post['ID'] );
3159  
3160          if ( ! is_array( $id3data ) ) {
3161              $changed = true;
3162              $id3data = array();
3163          }
3164  
3165          foreach ( wp_get_attachment_id3_keys( (object) $post, 'edit' ) as $key => $label ) {
3166              if ( isset( $changes[ $key ] ) ) {
3167                  $changed         = true;
3168                  $id3data[ $key ] = sanitize_text_field( wp_unslash( $changes[ $key ] ) );
3169              }
3170          }
3171  
3172          if ( $changed ) {
3173              wp_update_attachment_metadata( $id, $id3data );
3174          }
3175      }
3176  
3177      if ( MEDIA_TRASH && isset( $changes['status'] ) && 'trash' === $changes['status'] ) {
3178          wp_delete_post( $id );
3179      } else {
3180          wp_update_post( $post );
3181      }
3182  
3183      wp_send_json_success();
3184  }
3185  
3186  /**
3187   * Handles saving backward compatible attachment attributes via AJAX.
3188   *
3189   * @since 3.5.0
3190   */
3191  function wp_ajax_save_attachment_compat() {
3192      if ( ! isset( $_REQUEST['id'] ) ) {
3193          wp_send_json_error();
3194      }
3195  
3196      $id = absint( $_REQUEST['id'] );
3197      if ( ! $id ) {
3198          wp_send_json_error();
3199      }
3200  
3201      if ( empty( $_REQUEST['attachments'] ) || empty( $_REQUEST['attachments'][ $id ] ) ) {
3202          wp_send_json_error();
3203      }
3204  
3205      $attachment_data = $_REQUEST['attachments'][ $id ];
3206  
3207      check_ajax_referer( 'update-post_' . $id, 'nonce' );
3208  
3209      if ( ! current_user_can( 'edit_post', $id ) ) {
3210          wp_send_json_error();
3211      }
3212  
3213      $post = get_post( $id, ARRAY_A );
3214  
3215      if ( 'attachment' !== $post['post_type'] ) {
3216          wp_send_json_error();
3217      }
3218  
3219      /** This filter is documented in wp-admin/includes/media.php */
3220      $post = apply_filters( 'attachment_fields_to_save', $post, $attachment_data );
3221  
3222      if ( isset( $post['errors'] ) ) {
3223          $errors = $post['errors']; // @todo return me and display me!
3224          unset( $post['errors'] );
3225      }
3226  
3227      wp_update_post( $post );
3228  
3229      foreach ( get_attachment_taxonomies( $post ) as $taxonomy ) {
3230          if ( isset( $attachment_data[ $taxonomy ] ) ) {
3231              wp_set_object_terms( $id, array_map( 'trim', preg_split( '/,+/', $attachment_data[ $taxonomy ] ) ), $taxonomy, false );
3232          }
3233      }
3234  
3235      $attachment = wp_prepare_attachment_for_js( $id );
3236  
3237      if ( ! $attachment ) {
3238          wp_send_json_error();
3239      }
3240  
3241      wp_send_json_success( $attachment );
3242  }
3243  
3244  /**
3245   * Handles saving the attachment order via AJAX.
3246   *
3247   * @since 3.5.0
3248   */
3249  function wp_ajax_save_attachment_order() {
3250      if ( ! isset( $_REQUEST['post_id'] ) ) {
3251          wp_send_json_error();
3252      }
3253  
3254      $post_id = absint( $_REQUEST['post_id'] );
3255      if ( ! $post_id ) {
3256          wp_send_json_error();
3257      }
3258  
3259      if ( empty( $_REQUEST['attachments'] ) ) {
3260          wp_send_json_error();
3261      }
3262  
3263      check_ajax_referer( 'update-post_' . $post_id, 'nonce' );
3264  
3265      $attachments = $_REQUEST['attachments'];
3266  
3267      if ( ! current_user_can( 'edit_post', $post_id ) ) {
3268          wp_send_json_error();
3269      }
3270  
3271      foreach ( $attachments as $attachment_id => $menu_order ) {
3272          if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
3273              continue;
3274          }
3275  
3276          $attachment = get_post( $attachment_id );
3277  
3278          if ( ! $attachment ) {
3279              continue;
3280          }
3281  
3282          if ( 'attachment' !== $attachment->post_type ) {
3283              continue;
3284          }
3285  
3286          wp_update_post(
3287              array(
3288                  'ID'         => $attachment_id,
3289                  'menu_order' => $menu_order,
3290              )
3291          );
3292      }
3293  
3294      wp_send_json_success();
3295  }
3296  
3297  /**
3298   * Handles sending an attachment to the editor via AJAX.
3299   *
3300   * Generates the HTML to send an attachment to the editor.
3301   * Backward compatible with the {@see 'media_send_to_editor'} filter
3302   * and the chain of filters that follow.
3303   *
3304   * @since 3.5.0
3305   */
3306  function wp_ajax_send_attachment_to_editor() {
3307      check_ajax_referer( 'media-send-to-editor', 'nonce' );
3308  
3309      $attachment = wp_unslash( $_POST['attachment'] );
3310  
3311      $id = (int) $attachment['id'];
3312  
3313      $post = get_post( $id );
3314      if ( ! $post ) {
3315          wp_send_json_error();
3316      }
3317  
3318      if ( 'attachment' !== $post->post_type ) {
3319          wp_send_json_error();
3320      }
3321  
3322      if ( current_user_can( 'edit_post', $id ) ) {
3323          // If this attachment is unattached, attach it. Primarily a back compat thing.
3324          $insert_into_post_id = (int) $_POST['post_id'];
3325  
3326          if ( 0 == $post->post_parent && $insert_into_post_id ) {
3327              wp_update_post(
3328                  array(
3329                      'ID'          => $id,
3330                      'post_parent' => $insert_into_post_id,
3331                  )
3332              );
3333          }
3334      }
3335  
3336      $url = empty( $attachment['url'] ) ? '' : $attachment['url'];
3337      $rel = ( str_contains( $url, 'attachment_id' ) || get_attachment_link( $id ) === $url );
3338  
3339      remove_filter( 'media_send_to_editor', 'image_media_send_to_editor' );
3340  
3341      if ( str_starts_with( $post->post_mime_type, 'image' ) ) {
3342          $align = isset( $attachment['align'] ) ? $attachment['align'] : 'none';
3343          $size  = isset( $attachment['image-size'] ) ? $attachment['image-size'] : 'medium';
3344          $alt   = isset( $attachment['image_alt'] ) ? $attachment['image_alt'] : '';
3345  
3346          // No whitespace-only captions.
3347          $caption = isset( $attachment['post_excerpt'] ) ? $attachment['post_excerpt'] : '';
3348          if ( '' === trim( $caption ) ) {
3349              $caption = '';
3350          }
3351  
3352          $title = ''; // We no longer insert title tags into <img> tags, as they are redundant.
3353          $html  = get_image_send_to_editor( $id, $caption, $title, $align, $url, $rel, $size, $alt );
3354      } elseif ( wp_attachment_is( 'video', $post ) || wp_attachment_is( 'audio', $post ) ) {
3355          $html = stripslashes_deep( $_POST['html'] );
3356      } else {
3357          $html = isset( $attachment['post_title'] ) ? $attachment['post_title'] : '';
3358          $rel  = $rel ? ' rel="attachment wp-att-' . $id . '"' : ''; // Hard-coded string, $id is already sanitized.
3359  
3360          if ( ! empty( $url ) ) {
3361              $html = '<a href="' . esc_url( $url ) . '"' . $rel . '>' . $html . '</a>';
3362          }
3363      }
3364  
3365      /** This filter is documented in wp-admin/includes/media.php */
3366      $html = apply_filters( 'media_send_to_editor', $html, $id, $attachment );
3367  
3368      wp_send_json_success( $html );
3369  }
3370  
3371  /**
3372   * Handles sending a link to the editor via AJAX.
3373   *
3374   * Generates the HTML to send a non-image embed link to the editor.
3375   *
3376   * Backward compatible with the following filters:
3377   * - file_send_to_editor_url
3378   * - audio_send_to_editor_url
3379   * - video_send_to_editor_url
3380   *
3381   * @since 3.5.0
3382   *
3383   * @global WP_Post  $post     Global post object.
3384   * @global WP_Embed $wp_embed WordPress Embed object.
3385   */
3386  function wp_ajax_send_link_to_editor() {
3387      global $post, $wp_embed;
3388  
3389      check_ajax_referer( 'media-send-to-editor', 'nonce' );
3390  
3391      $src = wp_unslash( $_POST['src'] );
3392      if ( ! $src ) {
3393          wp_send_json_error();
3394      }
3395  
3396      if ( ! strpos( $src, '://' ) ) {
3397          $src = 'http://' . $src;
3398      }
3399  
3400      $src = sanitize_url( $src );
3401      if ( ! $src ) {
3402          wp_send_json_error();
3403      }
3404  
3405      $link_text = trim( wp_unslash( $_POST['link_text'] ) );
3406      if ( ! $link_text ) {
3407          $link_text = wp_basename( $src );
3408      }
3409  
3410      $post = get_post( isset( $_POST['post_id'] ) ? $_POST['post_id'] : 0 );
3411  
3412      // Ping WordPress for an embed.
3413      $check_embed = $wp_embed->run_shortcode( '[embed]' . $src . '[/embed]' );
3414  
3415      // Fallback that WordPress creates when no oEmbed was found.
3416      $fallback = $wp_embed->maybe_make_link( $src );
3417  
3418      if ( $check_embed !== $fallback ) {
3419          // TinyMCE view for [embed] will parse this.
3420          $html = '[embed]' . $src . '[/embed]';
3421      } elseif ( $link_text ) {
3422          $html = '<a href="' . esc_url( $src ) . '">' . $link_text . '</a>';
3423      } else {
3424          $html = '';
3425      }
3426  
3427      // Figure out what filter to run:
3428      $type = 'file';
3429      $ext  = preg_replace( '/^.+?\.([^.]+)$/', '$1', $src );
3430      if ( $ext ) {
3431          $ext_type = wp_ext2type( $ext );
3432          if ( 'audio' === $ext_type || 'video' === $ext_type ) {
3433              $type = $ext_type;
3434          }
3435      }
3436  
3437      /** This filter is documented in wp-admin/includes/media.php */
3438      $html = apply_filters( "{$type}_send_to_editor_url", $html, $src, $link_text );
3439  
3440      wp_send_json_success( $html );
3441  }
3442  
3443  /**
3444   * Handles the Heartbeat API via AJAX.
3445   *
3446   * Runs when the user is logged in.
3447   *
3448   * @since 3.6.0
3449   */
3450  function wp_ajax_heartbeat() {
3451      if ( empty( $_POST['_nonce'] ) ) {
3452          wp_send_json_error();
3453      }
3454  
3455      $response    = array();
3456      $data        = array();
3457      $nonce_state = wp_verify_nonce( $_POST['_nonce'], 'heartbeat-nonce' );
3458  
3459      // 'screen_id' is the same as $current_screen->id and the JS global 'pagenow'.
3460      if ( ! empty( $_POST['screen_id'] ) ) {
3461          $screen_id = sanitize_key( $_POST['screen_id'] );
3462      } else {
3463          $screen_id = 'front';
3464      }
3465  
3466      if ( ! empty( $_POST['data'] ) ) {
3467          $data = wp_unslash( (array) $_POST['data'] );
3468      }
3469  
3470      if ( 1 !== $nonce_state ) {
3471          /**
3472           * Filters the nonces to send to the New/Edit Post screen.
3473           *
3474           * @since 4.3.0
3475           *
3476           * @param array  $response  The Heartbeat response.
3477           * @param array  $data      The $_POST data sent.
3478           * @param string $screen_id The screen ID.
3479           */
3480          $response = apply_filters( 'wp_refresh_nonces', $response, $data, $screen_id );
3481  
3482          if ( false === $nonce_state ) {
3483              // User is logged in but nonces have expired.
3484              $response['nonces_expired'] = true;
3485              wp_send_json( $response );
3486          }
3487      }
3488  
3489      if ( ! empty( $data ) ) {
3490          /**
3491           * Filters the Heartbeat response received.
3492           *
3493           * @since 3.6.0
3494           *
3495           * @param array  $response  The Heartbeat response.
3496           * @param array  $data      The $_POST data sent.
3497           * @param string $screen_id The screen ID.
3498           */
3499          $response = apply_filters( 'heartbeat_received', $response, $data, $screen_id );
3500      }
3501  
3502      /**
3503       * Filters the Heartbeat response sent.
3504       *
3505       * @since 3.6.0
3506       *
3507       * @param array  $response  The Heartbeat response.
3508       * @param string $screen_id The screen ID.
3509       */
3510      $response = apply_filters( 'heartbeat_send', $response, $screen_id );
3511  
3512      /**
3513       * Fires when Heartbeat ticks in logged-in environments.
3514       *
3515       * Allows the transport to be easily replaced with long-polling.
3516       *
3517       * @since 3.6.0
3518       *
3519       * @param array  $response  The Heartbeat response.
3520       * @param string $screen_id The screen ID.
3521       */
3522      do_action( 'heartbeat_tick', $response, $screen_id );
3523  
3524      // Send the current time according to the server.
3525      $response['server_time'] = time();
3526  
3527      wp_send_json( $response );
3528  }
3529  
3530  /**
3531   * Handles getting revision diffs via AJAX.
3532   *
3533   * @since 3.6.0
3534   */
3535  function wp_ajax_get_revision_diffs() {
3536      require  ABSPATH . 'wp-admin/includes/revision.php';
3537  
3538      $post = get_post( (int) $_REQUEST['post_id'] );
3539      if ( ! $post ) {
3540          wp_send_json_error();
3541      }
3542  
3543      if ( ! current_user_can( 'edit_post', $post->ID ) ) {
3544          wp_send_json_error();
3545      }
3546  
3547      // Really just pre-loading the cache here.
3548      $revisions = wp_get_post_revisions( $post->ID, array( 'check_enabled' => false ) );
3549      if ( ! $revisions ) {
3550          wp_send_json_error();
3551      }
3552  
3553      $return = array();
3554  
3555      if ( function_exists( 'set_time_limit' ) ) {
3556          set_time_limit( 0 );
3557      }
3558  
3559      foreach ( $_REQUEST['compare'] as $compare_key ) {
3560          list( $compare_from, $compare_to ) = explode( ':', $compare_key ); // from:to
3561  
3562          $return[] = array(
3563              'id'     => $compare_key,
3564              'fields' => wp_get_revision_ui_diff( $post, $compare_from, $compare_to ),
3565          );
3566      }
3567      wp_send_json_success( $return );
3568  }
3569  
3570  /**
3571   * Handles auto-saving the selected color scheme for
3572   * a user's own profile via AJAX.
3573   *
3574   * @since 3.8.0
3575   *
3576   * @global array $_wp_admin_css_colors
3577   */
3578  function wp_ajax_save_user_color_scheme() {
3579      global $_wp_admin_css_colors;
3580  
3581      check_ajax_referer( 'save-color-scheme', 'nonce' );
3582  
3583      $color_scheme = sanitize_key( $_POST['color_scheme'] );
3584  
3585      if ( ! isset( $_wp_admin_css_colors[ $color_scheme ] ) ) {
3586          wp_send_json_error();
3587      }
3588  
3589      $previous_color_scheme = get_user_meta( get_current_user_id(), 'admin_color', true );
3590      update_user_meta( get_current_user_id(), 'admin_color', $color_scheme );
3591  
3592      wp_send_json_success(
3593          array(
3594              'previousScheme' => 'admin-color-' . $previous_color_scheme,
3595              'currentScheme'  => 'admin-color-' . $color_scheme,
3596          )
3597      );
3598  }
3599  
3600  /**
3601   * Handles getting themes from themes_api() via AJAX.
3602   *
3603   * @since 3.9.0
3604   *
3605   * @global array $themes_allowedtags
3606   * @global array $theme_field_defaults
3607   */
3608  function wp_ajax_query_themes() {
3609      global $themes_allowedtags, $theme_field_defaults;
3610  
3611      if ( ! current_user_can( 'install_themes' ) ) {
3612          wp_send_json_error();
3613      }
3614  
3615      $args = wp_parse_args(
3616          wp_unslash( $_REQUEST['request'] ),
3617          array(
3618              'per_page' => 20,
3619              'fields'   => array_merge(
3620                  (array) $theme_field_defaults,
3621                  array(
3622                      'reviews_url' => true, // Explicitly request the reviews URL to be linked from the Add Themes screen.
3623                  )
3624              ),
3625          )
3626      );
3627  
3628      if ( isset( $args['browse'] ) && 'favorites' === $args['browse'] && ! isset( $args['user'] ) ) {
3629          $user = get_user_option( 'wporg_favorites' );
3630          if ( $user ) {
3631              $args['user'] = $user;
3632          }
3633      }
3634  
3635      $old_filter = isset( $args['browse'] ) ? $args['browse'] : 'search';
3636  
3637      /** This filter is documented in wp-admin/includes/class-wp-theme-install-list-table.php */
3638      $args = apply_filters( 'install_themes_table_api_args_' . $old_filter, $args );
3639  
3640      $api = themes_api( 'query_themes', $args );
3641  
3642      if ( is_wp_error( $api ) ) {
3643          wp_send_json_error();
3644      }
3645  
3646      $update_php = network_admin_url( 'update.php?action=install-theme' );
3647  
3648      $installed_themes = search_theme_directories();
3649  
3650      if ( false === $installed_themes ) {
3651          $installed_themes = array();
3652      }
3653  
3654      foreach ( $installed_themes as $theme_slug => $theme_data ) {
3655          // Ignore child themes.
3656          if ( str_contains( $theme_slug, '/' ) ) {
3657              unset( $installed_themes[ $theme_slug ] );
3658          }
3659      }
3660  
3661      foreach ( $api->themes as &$theme ) {
3662          $theme->install_url = add_query_arg(
3663              array(
3664                  'theme'    => $theme->slug,
3665                  '_wpnonce' => wp_create_nonce( 'install-theme_' . $theme->slug ),
3666              ),
3667              $update_php
3668          );
3669  
3670          if ( current_user_can( 'switch_themes' ) ) {
3671              if ( is_multisite() ) {
3672                  $theme->activate_url = add_query_arg(
3673                      array(
3674                          'action'   => 'enable',
3675                          '_wpnonce' => wp_create_nonce( 'enable-theme_' . $theme->slug ),
3676                          'theme'    => $theme->slug,
3677                      ),
3678                      network_admin_url( 'themes.php' )
3679                  );
3680              } else {
3681                  $theme->activate_url = add_query_arg(
3682                      array(
3683                          'action'     => 'activate',
3684                          '_wpnonce'   => wp_create_nonce( 'switch-theme_' . $theme->slug ),
3685                          'stylesheet' => $theme->slug,
3686                      ),
3687                      admin_url( 'themes.php' )
3688                  );
3689              }
3690          }
3691  
3692          $is_theme_installed = array_key_exists( $theme->slug, $installed_themes );
3693  
3694          // We only care about installed themes.
3695          $theme->block_theme = $is_theme_installed && wp_get_theme( $theme->slug )->is_block_theme();
3696  
3697          if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
3698              $customize_url = $theme->block_theme ? admin_url( 'site-editor.php' ) : wp_customize_url( $theme->slug );
3699  
3700              $theme->customize_url = add_query_arg(
3701                  array(
3702                      'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
3703                  ),
3704                  $customize_url
3705              );
3706          }
3707  
3708          $theme->name        = wp_kses( $theme->name, $themes_allowedtags );
3709          $theme->author      = wp_kses( $theme->author['display_name'], $themes_allowedtags );
3710          $theme->version     = wp_kses( $theme->version, $themes_allowedtags );
3711          $theme->description = wp_kses( $theme->description, $themes_allowedtags );
3712  
3713          $theme->stars = wp_star_rating(
3714              array(
3715                  'rating' => $theme->rating,
3716                  'type'   => 'percent',
3717                  'number' => $theme->num_ratings,
3718                  'echo'   => false,
3719              )
3720          );
3721  
3722          $theme->num_ratings    = number_format_i18n( $theme->num_ratings );
3723          $theme->preview_url    = set_url_scheme( $theme->preview_url );
3724          $theme->compatible_wp  = is_wp_version_compatible( $theme->requires );
3725          $theme->compatible_php = is_php_version_compatible( $theme->requires_php );
3726      }
3727  
3728      wp_send_json_success( $api );
3729  }
3730  
3731  /**
3732   * Applies [embed] Ajax handlers to a string.
3733   *
3734   * @since 4.0.0
3735   *
3736   * @global WP_Post    $post          Global post object.
3737   * @global WP_Embed   $wp_embed      WordPress Embed object.
3738   * @global WP_Scripts $wp_scripts
3739   * @global int        $content_width
3740   */
3741  function wp_ajax_parse_embed() {
3742      global $post, $wp_embed, $content_width;
3743  
3744      if ( empty( $_POST['shortcode'] ) ) {
3745          wp_send_json_error();
3746      }
3747  
3748      $post_id = isset( $_POST['post_ID'] ) ? (int) $_POST['post_ID'] : 0;
3749  
3750      if ( $post_id > 0 ) {
3751          $post = get_post( $post_id );
3752  
3753          if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3754              wp_send_json_error();
3755          }
3756          setup_postdata( $post );
3757      } elseif ( ! current_user_can( 'edit_posts' ) ) { // See WP_oEmbed_Controller::get_proxy_item_permissions_check().
3758          wp_send_json_error();
3759      }
3760  
3761      $shortcode = wp_unslash( $_POST['shortcode'] );
3762  
3763      preg_match( '/' . get_shortcode_regex() . '/s', $shortcode, $matches );
3764      $atts = shortcode_parse_atts( $matches[3] );
3765  
3766      if ( ! empty( $matches[5] ) ) {
3767          $url = $matches[5];
3768      } elseif ( ! empty( $atts['src'] ) ) {
3769          $url = $atts['src'];
3770      } else {
3771          $url = '';
3772      }
3773  
3774      $parsed                         = false;
3775      $wp_embed->return_false_on_fail = true;
3776  
3777      if ( 0 === $post_id ) {
3778          /*
3779           * Refresh oEmbeds cached outside of posts that are past their TTL.
3780           * Posts are excluded because they have separate logic for refreshing
3781           * their post meta caches. See WP_Embed::cache_oembed().
3782           */
3783          $wp_embed->usecache = false;
3784      }
3785  
3786      if ( is_ssl() && str_starts_with( $url, 'http://' ) ) {
3787          /*
3788           * Admin is ssl and the user pasted non-ssl URL.
3789           * Check if the provider supports ssl embeds and use that for the preview.
3790           */
3791          $ssl_shortcode = preg_replace( '%^(\\[embed[^\\]]*\\])http://%i', '$1https://', $shortcode );
3792          $parsed        = $wp_embed->run_shortcode( $ssl_shortcode );
3793  
3794          if ( ! $parsed ) {
3795              $no_ssl_support = true;
3796          }
3797      }
3798  
3799      // Set $content_width so any embeds fit in the destination iframe.
3800      if ( isset( $_POST['maxwidth'] ) && is_numeric( $_POST['maxwidth'] ) && $_POST['maxwidth'] > 0 ) {
3801          if ( ! isset( $content_width ) ) {
3802              $content_width = (int) $_POST['maxwidth'];
3803          } else {
3804              $content_width = min( $content_width, (int) $_POST['maxwidth'] );
3805          }
3806      }
3807  
3808      if ( $url && ! $parsed ) {
3809          $parsed = $wp_embed->run_shortcode( $shortcode );
3810      }
3811  
3812      if ( ! $parsed ) {
3813          wp_send_json_error(
3814              array(
3815                  'type'    => 'not-embeddable',
3816                  /* translators: %s: URL that could not be embedded. */
3817                  'message' => sprintf( __( '%s failed to embed.' ), '<code>' . esc_html( $url ) . '</code>' ),
3818              )
3819          );
3820      }
3821  
3822      if ( has_shortcode( $parsed, 'audio' ) || has_shortcode( $parsed, 'video' ) ) {
3823          $styles     = '';
3824          $mce_styles = wpview_media_sandbox_styles();
3825  
3826          foreach ( $mce_styles as $style ) {
3827              $styles .= sprintf( '<link rel="stylesheet" href="%s" />', $style );
3828          }
3829  
3830          $html = do_shortcode( $parsed );
3831  
3832          global $wp_scripts;
3833  
3834          if ( ! empty( $wp_scripts ) ) {
3835              $wp_scripts->done = array();
3836          }
3837  
3838          ob_start();
3839          wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
3840          $scripts = ob_get_clean();
3841  
3842          $parsed = $styles . $html . $scripts;
3843      }
3844  
3845      if ( ! empty( $no_ssl_support ) || ( is_ssl() && ( preg_match( '%<(iframe|script|embed) [^>]*src="http://%', $parsed ) ||
3846          preg_match( '%<link [^>]*href="http://%', $parsed ) ) ) ) {
3847          // Admin is ssl and the embed is not. Iframes, scripts, and other "active content" will be blocked.
3848          wp_send_json_error(
3849              array(
3850                  'type'    => 'not-ssl',
3851                  'message' => __( 'This preview is unavailable in the editor.' ),
3852              )
3853          );
3854      }
3855  
3856      $return = array(
3857          'body' => $parsed,
3858          'attr' => $wp_embed->last_attr,
3859      );
3860  
3861      if ( str_contains( $parsed, 'class="wp-embedded-content' ) ) {
3862          if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
3863              $script_src = includes_url( 'js/wp-embed.js' );
3864          } else {
3865              $script_src = includes_url( 'js/wp-embed.min.js' );
3866          }
3867  
3868          $return['head']    = '<script src="' . $script_src . '"></script>';
3869          $return['sandbox'] = true;
3870      }
3871  
3872      wp_send_json_success( $return );
3873  }
3874  
3875  /**
3876   * @since 4.0.0
3877   *
3878   * @global WP_Post    $post       Global post object.
3879   * @global WP_Scripts $wp_scripts
3880   */
3881  function wp_ajax_parse_media_shortcode() {
3882      global $post, $wp_scripts;
3883  
3884      if ( empty( $_POST['shortcode'] ) ) {
3885          wp_send_json_error();
3886      }
3887  
3888      $shortcode = wp_unslash( $_POST['shortcode'] );
3889  
3890      // Only process previews for media related shortcodes:
3891      $found_shortcodes = get_shortcode_tags_in_content( $shortcode );
3892      $media_shortcodes = array(
3893          'audio',
3894          'embed',
3895          'playlist',
3896          'video',
3897          'gallery',
3898      );
3899  
3900      $other_shortcodes = array_diff( $found_shortcodes, $media_shortcodes );
3901  
3902      if ( ! empty( $other_shortcodes ) ) {
3903          wp_send_json_error();
3904      }
3905  
3906      if ( ! empty( $_POST['post_ID'] ) ) {
3907          $post = get_post( (int) $_POST['post_ID'] );
3908      }
3909  
3910      // The embed shortcode requires a post.
3911      if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
3912          if ( in_array( 'embed', $found_shortcodes, true ) ) {
3913              wp_send_json_error();
3914          }
3915      } else {
3916          setup_postdata( $post );
3917      }
3918  
3919      $parsed = do_shortcode( $shortcode );
3920  
3921      if ( empty( $parsed ) ) {
3922          wp_send_json_error(
3923              array(
3924                  'type'    => 'no-items',
3925                  'message' => __( 'No items found.' ),
3926              )
3927          );
3928      }
3929  
3930      $head   = '';
3931      $styles = wpview_media_sandbox_styles();
3932  
3933      foreach ( $styles as $style ) {
3934          $head .= '<link type="text/css" rel="stylesheet" href="' . $style . '">';
3935      }
3936  
3937      if ( ! empty( $wp_scripts ) ) {
3938          $wp_scripts->done = array();
3939      }
3940  
3941      ob_start();
3942  
3943      echo $parsed;
3944  
3945      if ( 'playlist' === $_REQUEST['type'] ) {
3946          wp_underscore_playlist_templates();
3947  
3948          wp_print_scripts( 'wp-playlist' );
3949      } else {
3950          wp_print_scripts( array( 'mediaelement-vimeo', 'wp-mediaelement' ) );
3951      }
3952  
3953      wp_send_json_success(
3954          array(
3955              'head' => $head,
3956              'body' => ob_get_clean(),
3957          )
3958      );
3959  }
3960  
3961  /**
3962   * Handles destroying multiple open sessions for a user via AJAX.
3963   *
3964   * @since 4.1.0
3965   */
3966  function wp_ajax_destroy_sessions() {
3967      $user = get_userdata( (int) $_POST['user_id'] );
3968  
3969      if ( $user ) {
3970          if ( ! current_user_can( 'edit_user', $user->ID ) ) {
3971              $user = false;
3972          } elseif ( ! wp_verify_nonce( $_POST['nonce'], 'update-user_' . $user->ID ) ) {
3973              $user = false;
3974          }
3975      }
3976  
3977      if ( ! $user ) {
3978          wp_send_json_error(
3979              array(
3980                  'message' => __( 'Could not log out user sessions. Please try again.' ),
3981              )
3982          );
3983      }
3984  
3985      $sessions = WP_Session_Tokens::get_instance( $user->ID );
3986  
3987      if ( get_current_user_id() === $user->ID ) {
3988          $sessions->destroy_others( wp_get_session_token() );
3989          $message = __( 'You are now logged out everywhere else.' );
3990      } else {
3991          $sessions->destroy_all();
3992          /* translators: %s: User's display name. */
3993          $message = sprintf( __( '%s has been logged out.' ), $user->display_name );
3994      }
3995  
3996      wp_send_json_success( array( 'message' => $message ) );
3997  }
3998  
3999  /**
4000   * Handles cropping an image via AJAX.
4001   *
4002   * @since 4.3.0
4003   */
4004  function wp_ajax_crop_image() {
4005      $attachment_id = absint( $_POST['id'] );
4006  
4007      check_ajax_referer( 'image_editor-' . $attachment_id, 'nonce' );
4008  
4009      if ( empty( $attachment_id ) || ! current_user_can( 'edit_post', $attachment_id ) ) {
4010          wp_send_json_error();
4011      }
4012  
4013      $context = str_replace( '_', '-', $_POST['context'] );
4014      $data    = array_map( 'absint', $_POST['cropDetails'] );
4015      $cropped = wp_crop_image( $attachment_id, $data['x1'], $data['y1'], $data['width'], $data['height'], $data['dst_width'], $data['dst_height'] );
4016  
4017      if ( ! $cropped || is_wp_error( $cropped ) ) {
4018          wp_send_json_error( array( 'message' => __( 'Image could not be processed.' ) ) );
4019      }
4020  
4021      switch ( $context ) {
4022          case 'site-icon':
4023              require_once  ABSPATH . 'wp-admin/includes/class-wp-site-icon.php';
4024              $wp_site_icon = new WP_Site_Icon();
4025  
4026              // Skip creating a new attachment if the attachment is a Site Icon.
4027              if ( get_post_meta( $attachment_id, '_wp_attachment_context', true ) == $context ) {
4028  
4029                  // Delete the temporary cropped file, we don't need it.
4030                  wp_delete_file( $cropped );
4031  
4032                  // Additional sizes in wp_prepare_attachment_for_js().
4033                  add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
4034                  break;
4035              }
4036  
4037              /** This filter is documented in wp-admin/includes/class-custom-image-header.php */
4038              $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
4039  
4040              // Copy attachment properties.
4041              $attachment = wp_copy_parent_attachment_properties( $cropped, $attachment_id, $context );
4042  
4043              // Update the attachment.
4044              add_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
4045              $attachment_id = $wp_site_icon->insert_attachment( $attachment, $cropped );
4046              remove_filter( 'intermediate_image_sizes_advanced', array( $wp_site_icon, 'additional_sizes' ) );
4047  
4048              // Additional sizes in wp_prepare_attachment_for_js().
4049              add_filter( 'image_size_names_choose', array( $wp_site_icon, 'additional_sizes' ) );
4050              break;
4051  
4052          default:
4053              /**
4054               * Fires before a cropped image is saved.
4055               *
4056               * Allows to add filters to modify the way a cropped image is saved.
4057               *
4058               * @since 4.3.0
4059               *
4060               * @param string $context       The Customizer control requesting the cropped image.
4061               * @param int    $attachment_id The attachment ID of the original image.
4062               * @param string $cropped       Path to the cropped image file.
4063               */
4064              do_action( 'wp_ajax_crop_image_pre_save', $context, $attachment_id, $cropped );
4065  
4066              /** This filter is documented in wp-admin/includes/class-custom-image-header.php */
4067              $cropped = apply_filters( 'wp_create_file_in_uploads', $cropped, $attachment_id ); // For replication.
4068  
4069              // Copy attachment properties.
4070              $attachment = wp_copy_parent_attachment_properties( $cropped, $attachment_id, $context );
4071  
4072              $attachment_id = wp_insert_attachment( $attachment, $cropped );
4073              $metadata      = wp_generate_attachment_metadata( $attachment_id, $cropped );
4074  
4075              /**
4076               * Filters the cropped image attachment metadata.
4077               *
4078               * @since 4.3.0
4079               *
4080               * @see wp_generate_attachment_metadata()
4081               *
4082               * @param array $metadata Attachment metadata.
4083               */
4084              $metadata = apply_filters( 'wp_ajax_cropped_attachment_metadata', $metadata );
4085              wp_update_attachment_metadata( $attachment_id, $metadata );
4086  
4087              /**
4088               * Filters the attachment ID for a cropped image.
4089               *
4090               * @since 4.3.0
4091               *
4092               * @param int    $attachment_id The attachment ID of the cropped image.
4093               * @param string $context       The Customizer control requesting the cropped image.
4094               */
4095              $attachment_id = apply_filters( 'wp_ajax_cropped_attachment_id', $attachment_id, $context );
4096      }
4097  
4098      wp_send_json_success( wp_prepare_attachment_for_js( $attachment_id ) );
4099  }
4100  
4101  /**
4102   * Handles generating a password via AJAX.
4103   *
4104   * @since 4.4.0
4105   */
4106  function wp_ajax_generate_password() {
4107      wp_send_json_success( wp_generate_password( 24 ) );
4108  }
4109  
4110  /**
4111   * Handles generating a password in the no-privilege context via AJAX.
4112   *
4113   * @since 5.7.0
4114   */
4115  function wp_ajax_nopriv_generate_password() {
4116      wp_send_json_success( wp_generate_password( 24 ) );
4117  }
4118  
4119  /**
4120   * Handles saving the user's WordPress.org username via AJAX.
4121   *
4122   * @since 4.4.0
4123   */
4124  function wp_ajax_save_wporg_username() {
4125      if ( ! current_user_can( 'install_themes' ) && ! current_user_can( 'install_plugins' ) ) {
4126          wp_send_json_error();
4127      }
4128  
4129      check_ajax_referer( 'save_wporg_username_' . get_current_user_id() );
4130  
4131      $username = isset( $_REQUEST['username'] ) ? wp_unslash( $_REQUEST['username'] ) : false;
4132  
4133      if ( ! $username ) {
4134          wp_send_json_error();
4135      }
4136  
4137      wp_send_json_success( update_user_meta( get_current_user_id(), 'wporg_favorites', $username ) );
4138  }
4139  
4140  /**
4141   * Handles installing a theme via AJAX.
4142   *
4143   * @since 4.6.0
4144   *
4145   * @see Theme_Upgrader
4146   *
4147   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4148   */
4149  function wp_ajax_install_theme() {
4150      check_ajax_referer( 'updates' );
4151  
4152      if ( empty( $_POST['slug'] ) ) {
4153          wp_send_json_error(
4154              array(
4155                  'slug'         => '',
4156                  'errorCode'    => 'no_theme_specified',
4157                  'errorMessage' => __( 'No theme specified.' ),
4158              )
4159          );
4160      }
4161  
4162      $slug = sanitize_key( wp_unslash( $_POST['slug'] ) );
4163  
4164      $status = array(
4165          'install' => 'theme',
4166          'slug'    => $slug,
4167      );
4168  
4169      if ( ! current_user_can( 'install_themes' ) ) {
4170          $status['errorMessage'] = __( 'Sorry, you are not allowed to install themes on this site.' );
4171          wp_send_json_error( $status );
4172      }
4173  
4174      require_once  ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4175      require_once  ABSPATH . 'wp-admin/includes/theme.php';
4176  
4177      $api = themes_api(
4178          'theme_information',
4179          array(
4180              'slug'   => $slug,
4181              'fields' => array( 'sections' => false ),
4182          )
4183      );
4184  
4185      if ( is_wp_error( $api ) ) {
4186          $status['errorMessage'] = $api->get_error_message();
4187          wp_send_json_error( $status );
4188      }
4189  
4190      $skin     = new WP_Ajax_Upgrader_Skin();
4191      $upgrader = new Theme_Upgrader( $skin );
4192      $result   = $upgrader->install( $api->download_link );
4193  
4194      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4195          $status['debug'] = $skin->get_upgrade_messages();
4196      }
4197  
4198      if ( is_wp_error( $result ) ) {
4199          $status['errorCode']    = $result->get_error_code();
4200          $status['errorMessage'] = $result->get_error_message();
4201          wp_send_json_error( $status );
4202      } elseif ( is_wp_error( $skin->result ) ) {
4203          $status['errorCode']    = $skin->result->get_error_code();
4204          $status['errorMessage'] = $skin->result->get_error_message();
4205          wp_send_json_error( $status );
4206      } elseif ( $skin->get_errors()->has_errors() ) {
4207          $status['errorMessage'] = $skin->get_error_messages();
4208          wp_send_json_error( $status );
4209      } elseif ( is_null( $result ) ) {
4210          global $wp_filesystem;
4211  
4212          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4213          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4214  
4215          // Pass through the error from WP_Filesystem if one was raised.
4216          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4217              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4218          }
4219  
4220          wp_send_json_error( $status );
4221      }
4222  
4223      $status['themeName'] = wp_get_theme( $slug )->get( 'Name' );
4224  
4225      if ( current_user_can( 'switch_themes' ) ) {
4226          if ( is_multisite() ) {
4227              $status['activateUrl'] = add_query_arg(
4228                  array(
4229                      'action'   => 'enable',
4230                      '_wpnonce' => wp_create_nonce( 'enable-theme_' . $slug ),
4231                      'theme'    => $slug,
4232                  ),
4233                  network_admin_url( 'themes.php' )
4234              );
4235          } else {
4236              $status['activateUrl'] = add_query_arg(
4237                  array(
4238                      'action'     => 'activate',
4239                      '_wpnonce'   => wp_create_nonce( 'switch-theme_' . $slug ),
4240                      'stylesheet' => $slug,
4241                  ),
4242                  admin_url( 'themes.php' )
4243              );
4244          }
4245      }
4246  
4247      $theme                = wp_get_theme( $slug );
4248      $status['blockTheme'] = $theme->is_block_theme();
4249  
4250      if ( ! is_multisite() && current_user_can( 'edit_theme_options' ) && current_user_can( 'customize' ) ) {
4251          $status['customizeUrl'] = add_query_arg(
4252              array(
4253                  'return' => urlencode( network_admin_url( 'theme-install.php', 'relative' ) ),
4254              ),
4255              wp_customize_url( $slug )
4256          );
4257      }
4258  
4259      /*
4260       * See WP_Theme_Install_List_Table::_get_theme_status() if we wanted to check
4261       * on post-installation status.
4262       */
4263      wp_send_json_success( $status );
4264  }
4265  
4266  /**
4267   * Handles updating a theme via AJAX.
4268   *
4269   * @since 4.6.0
4270   *
4271   * @see Theme_Upgrader
4272   *
4273   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4274   */
4275  function wp_ajax_update_theme() {
4276      check_ajax_referer( 'updates' );
4277  
4278      if ( empty( $_POST['slug'] ) ) {
4279          wp_send_json_error(
4280              array(
4281                  'slug'         => '',
4282                  'errorCode'    => 'no_theme_specified',
4283                  'errorMessage' => __( 'No theme specified.' ),
4284              )
4285          );
4286      }
4287  
4288      $stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
4289      $status     = array(
4290          'update'     => 'theme',
4291          'slug'       => $stylesheet,
4292          'oldVersion' => '',
4293          'newVersion' => '',
4294      );
4295  
4296      if ( ! current_user_can( 'update_themes' ) ) {
4297          $status['errorMessage'] = __( 'Sorry, you are not allowed to update themes for this site.' );
4298          wp_send_json_error( $status );
4299      }
4300  
4301      $theme = wp_get_theme( $stylesheet );
4302      if ( $theme->exists() ) {
4303          $status['oldVersion'] = $theme->get( 'Version' );
4304      }
4305  
4306      require_once  ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4307  
4308      $current = get_site_transient( 'update_themes' );
4309      if ( empty( $current ) ) {
4310          wp_update_themes();
4311      }
4312  
4313      $skin     = new WP_Ajax_Upgrader_Skin();
4314      $upgrader = new Theme_Upgrader( $skin );
4315      $result   = $upgrader->bulk_upgrade( array( $stylesheet ) );
4316  
4317      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4318          $status['debug'] = $skin->get_upgrade_messages();
4319      }
4320  
4321      if ( is_wp_error( $skin->result ) ) {
4322          $status['errorCode']    = $skin->result->get_error_code();
4323          $status['errorMessage'] = $skin->result->get_error_message();
4324          wp_send_json_error( $status );
4325      } elseif ( $skin->get_errors()->has_errors() ) {
4326          $status['errorMessage'] = $skin->get_error_messages();
4327          wp_send_json_error( $status );
4328      } elseif ( is_array( $result ) && ! empty( $result[ $stylesheet ] ) ) {
4329  
4330          // Theme is already at the latest version.
4331          if ( true === $result[ $stylesheet ] ) {
4332              $status['errorMessage'] = $upgrader->strings['up_to_date'];
4333              wp_send_json_error( $status );
4334          }
4335  
4336          $theme = wp_get_theme( $stylesheet );
4337          if ( $theme->exists() ) {
4338              $status['newVersion'] = $theme->get( 'Version' );
4339          }
4340  
4341          wp_send_json_success( $status );
4342      } elseif ( false === $result ) {
4343          global $wp_filesystem;
4344  
4345          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4346          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4347  
4348          // Pass through the error from WP_Filesystem if one was raised.
4349          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4350              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4351          }
4352  
4353          wp_send_json_error( $status );
4354      }
4355  
4356      // An unhandled error occurred.
4357      $status['errorMessage'] = __( 'Theme update failed.' );
4358      wp_send_json_error( $status );
4359  }
4360  
4361  /**
4362   * Handles deleting a theme via AJAX.
4363   *
4364   * @since 4.6.0
4365   *
4366   * @see delete_theme()
4367   *
4368   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4369   */
4370  function wp_ajax_delete_theme() {
4371      check_ajax_referer( 'updates' );
4372  
4373      if ( empty( $_POST['slug'] ) ) {
4374          wp_send_json_error(
4375              array(
4376                  'slug'         => '',
4377                  'errorCode'    => 'no_theme_specified',
4378                  'errorMessage' => __( 'No theme specified.' ),
4379              )
4380          );
4381      }
4382  
4383      $stylesheet = preg_replace( '/[^A-z0-9_\-]/', '', wp_unslash( $_POST['slug'] ) );
4384      $status     = array(
4385          'delete' => 'theme',
4386          'slug'   => $stylesheet,
4387      );
4388  
4389      if ( ! current_user_can( 'delete_themes' ) ) {
4390          $status['errorMessage'] = __( 'Sorry, you are not allowed to delete themes on this site.' );
4391          wp_send_json_error( $status );
4392      }
4393  
4394      if ( ! wp_get_theme( $stylesheet )->exists() ) {
4395          $status['errorMessage'] = __( 'The requested theme does not exist.' );
4396          wp_send_json_error( $status );
4397      }
4398  
4399      // Check filesystem credentials. `delete_theme()` will bail otherwise.
4400      $url = wp_nonce_url( 'themes.php?action=delete&stylesheet=' . urlencode( $stylesheet ), 'delete-theme_' . $stylesheet );
4401  
4402      ob_start();
4403      $credentials = request_filesystem_credentials( $url );
4404      ob_end_clean();
4405  
4406      if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
4407          global $wp_filesystem;
4408  
4409          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4410          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4411  
4412          // Pass through the error from WP_Filesystem if one was raised.
4413          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4414              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4415          }
4416  
4417          wp_send_json_error( $status );
4418      }
4419  
4420      require_once  ABSPATH . 'wp-admin/includes/theme.php';
4421  
4422      $result = delete_theme( $stylesheet );
4423  
4424      if ( is_wp_error( $result ) ) {
4425          $status['errorMessage'] = $result->get_error_message();
4426          wp_send_json_error( $status );
4427      } elseif ( false === $result ) {
4428          $status['errorMessage'] = __( 'Theme could not be deleted.' );
4429          wp_send_json_error( $status );
4430      }
4431  
4432      wp_send_json_success( $status );
4433  }
4434  
4435  /**
4436   * Handles installing a plugin via AJAX.
4437   *
4438   * @since 4.6.0
4439   *
4440   * @see Plugin_Upgrader
4441   *
4442   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4443   */
4444  function wp_ajax_install_plugin() {
4445      check_ajax_referer( 'updates' );
4446  
4447      if ( empty( $_POST['slug'] ) ) {
4448          wp_send_json_error(
4449              array(
4450                  'slug'         => '',
4451                  'errorCode'    => 'no_plugin_specified',
4452                  'errorMessage' => __( 'No plugin specified.' ),
4453              )
4454          );
4455      }
4456  
4457      $status = array(
4458          'install' => 'plugin',
4459          'slug'    => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4460      );
4461  
4462      if ( ! current_user_can( 'install_plugins' ) ) {
4463          $status['errorMessage'] = __( 'Sorry, you are not allowed to install plugins on this site.' );
4464          wp_send_json_error( $status );
4465      }
4466  
4467      require_once  ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4468      require_once  ABSPATH . 'wp-admin/includes/plugin-install.php';
4469  
4470      $api = plugins_api(
4471          'plugin_information',
4472          array(
4473              'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4474              'fields' => array(
4475                  'sections' => false,
4476              ),
4477          )
4478      );
4479  
4480      if ( is_wp_error( $api ) ) {
4481          $status['errorMessage'] = $api->get_error_message();
4482          wp_send_json_error( $status );
4483      }
4484  
4485      $status['pluginName'] = $api->name;
4486  
4487      $skin     = new WP_Ajax_Upgrader_Skin();
4488      $upgrader = new Plugin_Upgrader( $skin );
4489      $result   = $upgrader->install( $api->download_link );
4490  
4491      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4492          $status['debug'] = $skin->get_upgrade_messages();
4493      }
4494  
4495      if ( is_wp_error( $result ) ) {
4496          $status['errorCode']    = $result->get_error_code();
4497          $status['errorMessage'] = $result->get_error_message();
4498          wp_send_json_error( $status );
4499      } elseif ( is_wp_error( $skin->result ) ) {
4500          $status['errorCode']    = $skin->result->get_error_code();
4501          $status['errorMessage'] = $skin->result->get_error_message();
4502          wp_send_json_error( $status );
4503      } elseif ( $skin->get_errors()->has_errors() ) {
4504          $status['errorMessage'] = $skin->get_error_messages();
4505          wp_send_json_error( $status );
4506      } elseif ( is_null( $result ) ) {
4507          global $wp_filesystem;
4508  
4509          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4510          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4511  
4512          // Pass through the error from WP_Filesystem if one was raised.
4513          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4514              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4515          }
4516  
4517          wp_send_json_error( $status );
4518      }
4519  
4520      $install_status = install_plugin_install_status( $api );
4521      $pagenow        = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
4522  
4523      // If installation request is coming from import page, do not return network activation link.
4524      $plugins_url = ( 'import' === $pagenow ) ? admin_url( 'plugins.php' ) : network_admin_url( 'plugins.php' );
4525  
4526      if ( current_user_can( 'activate_plugin', $install_status['file'] ) && is_plugin_inactive( $install_status['file'] ) ) {
4527          $status['activateUrl'] = add_query_arg(
4528              array(
4529                  '_wpnonce' => wp_create_nonce( 'activate-plugin_' . $install_status['file'] ),
4530                  'action'   => 'activate',
4531                  'plugin'   => $install_status['file'],
4532              ),
4533              $plugins_url
4534          );
4535      }
4536  
4537      if ( is_multisite() && current_user_can( 'manage_network_plugins' ) && 'import' !== $pagenow ) {
4538          $status['activateUrl'] = add_query_arg( array( 'networkwide' => 1 ), $status['activateUrl'] );
4539      }
4540  
4541      wp_send_json_success( $status );
4542  }
4543  
4544  /**
4545   * Handles activating a plugin via AJAX.
4546   *
4547   * @since 6.5.0
4548   */
4549  function wp_ajax_activate_plugin() {
4550      check_ajax_referer( 'updates' );
4551  
4552      if ( empty( $_POST['name'] ) || empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
4553          wp_send_json_error(
4554              array(
4555                  'slug'         => '',
4556                  'pluginName'   => '',
4557                  'plugin'       => '',
4558                  'errorCode'    => 'no_plugin_specified',
4559                  'errorMessage' => __( 'No plugin specified.' ),
4560              )
4561          );
4562      }
4563  
4564      $status = array(
4565          'activate'   => 'plugin',
4566          'slug'       => wp_unslash( $_POST['slug'] ),
4567          'pluginName' => wp_unslash( $_POST['name'] ),
4568          'plugin'     => wp_unslash( $_POST['plugin'] ),
4569      );
4570  
4571      if ( ! current_user_can( 'activate_plugin', $status['plugin'] ) ) {
4572          $status['errorMessage'] = __( 'Sorry, you are not allowed to activate plugins on this site.' );
4573          wp_send_json_error( $status );
4574      }
4575  
4576      if ( is_plugin_active( $status['plugin'] ) ) {
4577          $status['errorMessage'] = sprintf(
4578              /* translators: %s: Plugin name. */
4579              __( '%s is already active.' ),
4580              $status['pluginName']
4581          );
4582      }
4583  
4584      $activated = activate_plugin( $status['plugin'] );
4585  
4586      if ( is_wp_error( $activated ) ) {
4587          $status['errorMessage'] = $activated->get_error_message();
4588          wp_send_json_error( $status );
4589      }
4590  
4591      wp_send_json_success( $status );
4592  }
4593  
4594  /**
4595   * Handles updating a plugin via AJAX.
4596   *
4597   * @since 4.2.0
4598   *
4599   * @see Plugin_Upgrader
4600   *
4601   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4602   */
4603  function wp_ajax_update_plugin() {
4604      check_ajax_referer( 'updates' );
4605  
4606      if ( empty( $_POST['plugin'] ) || empty( $_POST['slug'] ) ) {
4607          wp_send_json_error(
4608              array(
4609                  'slug'         => '',
4610                  'errorCode'    => 'no_plugin_specified',
4611                  'errorMessage' => __( 'No plugin specified.' ),
4612              )
4613          );
4614      }
4615  
4616      $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
4617  
4618      $status = array(
4619          'update'     => 'plugin',
4620          'slug'       => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4621          'oldVersion' => '',
4622          'newVersion' => '',
4623      );
4624  
4625      if ( ! current_user_can( 'update_plugins' ) || 0 !== validate_file( $plugin ) ) {
4626          $status['errorMessage'] = __( 'Sorry, you are not allowed to update plugins for this site.' );
4627          wp_send_json_error( $status );
4628      }
4629  
4630      $plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4631      $status['plugin']     = $plugin;
4632      $status['pluginName'] = $plugin_data['Name'];
4633  
4634      if ( $plugin_data['Version'] ) {
4635          /* translators: %s: Plugin version. */
4636          $status['oldVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
4637      }
4638  
4639      require_once  ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
4640  
4641      wp_update_plugins();
4642  
4643      $skin     = new WP_Ajax_Upgrader_Skin();
4644      $upgrader = new Plugin_Upgrader( $skin );
4645      $result   = $upgrader->bulk_upgrade( array( $plugin ) );
4646  
4647      if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
4648          $status['debug'] = $skin->get_upgrade_messages();
4649      }
4650  
4651      if ( is_wp_error( $skin->result ) ) {
4652          $status['errorCode']    = $skin->result->get_error_code();
4653          $status['errorMessage'] = $skin->result->get_error_message();
4654          wp_send_json_error( $status );
4655      } elseif ( $skin->get_errors()->has_errors() ) {
4656          $status['errorMessage'] = $skin->get_error_messages();
4657          wp_send_json_error( $status );
4658      } elseif ( is_array( $result ) && ! empty( $result[ $plugin ] ) ) {
4659  
4660          /*
4661           * Plugin is already at the latest version.
4662           *
4663           * This may also be the return value if the `update_plugins` site transient is empty,
4664           * e.g. when you update two plugins in quick succession before the transient repopulates.
4665           *
4666           * Preferably something can be done to ensure `update_plugins` isn't empty.
4667           * For now, surface some sort of error here.
4668           */
4669          if ( true === $result[ $plugin ] ) {
4670              $status['errorMessage'] = $upgrader->strings['up_to_date'];
4671              wp_send_json_error( $status );
4672          }
4673  
4674          $plugin_data = get_plugins( '/' . $result[ $plugin ]['destination_name'] );
4675          $plugin_data = reset( $plugin_data );
4676  
4677          if ( $plugin_data['Version'] ) {
4678              /* translators: %s: Plugin version. */
4679              $status['newVersion'] = sprintf( __( 'Version %s' ), $plugin_data['Version'] );
4680          }
4681  
4682          wp_send_json_success( $status );
4683      } elseif ( false === $result ) {
4684          global $wp_filesystem;
4685  
4686          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4687          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4688  
4689          // Pass through the error from WP_Filesystem if one was raised.
4690          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4691              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4692          }
4693  
4694          wp_send_json_error( $status );
4695      }
4696  
4697      // An unhandled error occurred.
4698      $status['errorMessage'] = __( 'Plugin update failed.' );
4699      wp_send_json_error( $status );
4700  }
4701  
4702  /**
4703   * Handles deleting a plugin via AJAX.
4704   *
4705   * @since 4.6.0
4706   *
4707   * @see delete_plugins()
4708   *
4709   * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
4710   */
4711  function wp_ajax_delete_plugin() {
4712      check_ajax_referer( 'updates' );
4713  
4714      if ( empty( $_POST['slug'] ) || empty( $_POST['plugin'] ) ) {
4715          wp_send_json_error(
4716              array(
4717                  'slug'         => '',
4718                  'errorCode'    => 'no_plugin_specified',
4719                  'errorMessage' => __( 'No plugin specified.' ),
4720              )
4721          );
4722      }
4723  
4724      $plugin = plugin_basename( sanitize_text_field( wp_unslash( $_POST['plugin'] ) ) );
4725  
4726      $status = array(
4727          'delete' => 'plugin',
4728          'slug'   => sanitize_key( wp_unslash( $_POST['slug'] ) ),
4729      );
4730  
4731      if ( ! current_user_can( 'delete_plugins' ) || 0 !== validate_file( $plugin ) ) {
4732          $status['errorMessage'] = __( 'Sorry, you are not allowed to delete plugins for this site.' );
4733          wp_send_json_error( $status );
4734      }
4735  
4736      $plugin_data          = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );
4737      $status['plugin']     = $plugin;
4738      $status['pluginName'] = $plugin_data['Name'];
4739  
4740      if ( is_plugin_active( $plugin ) ) {
4741          $status['errorMessage'] = __( 'You cannot delete a plugin while it is active on the main site.' );
4742          wp_send_json_error( $status );
4743      }
4744  
4745      // Check filesystem credentials. `delete_plugins()` will bail otherwise.
4746      $url = wp_nonce_url( 'plugins.php?action=delete-selected&verify-delete=1&checked[]=' . $plugin, 'bulk-plugins' );
4747  
4748      ob_start();
4749      $credentials = request_filesystem_credentials( $url );
4750      ob_end_clean();
4751  
4752      if ( false === $credentials || ! WP_Filesystem( $credentials ) ) {
4753          global $wp_filesystem;
4754  
4755          $status['errorCode']    = 'unable_to_connect_to_filesystem';
4756          $status['errorMessage'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
4757  
4758          // Pass through the error from WP_Filesystem if one was raised.
4759          if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
4760              $status['errorMessage'] = esc_html( $wp_filesystem->errors->get_error_message() );
4761          }
4762  
4763          wp_send_json_error( $status );
4764      }
4765  
4766      $result = delete_plugins( array( $plugin ) );
4767  
4768      if ( is_wp_error( $result ) ) {
4769          $status['errorMessage'] = $result->get_error_message();
4770          wp_send_json_error( $status );
4771      } elseif ( false === $result ) {
4772          $status['errorMessage'] = __( 'Plugin could not be deleted.' );
4773          wp_send_json_error( $status );
4774      }
4775  
4776      wp_send_json_success( $status );
4777  }
4778  
4779  /**
4780   * Handles searching plugins via AJAX.
4781   *
4782   * @since 4.6.0
4783   *
4784   * @global string $s Search term.
4785   */
4786  function wp_ajax_search_plugins() {
4787      check_ajax_referer( 'updates' );
4788  
4789      // Ensure after_plugin_row_{$plugin_file} gets hooked.
4790      wp_plugin_update_rows();
4791  
4792      $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
4793      if ( 'plugins-network' === $pagenow || 'plugins' === $pagenow ) {
4794          set_current_screen( $pagenow );
4795      }
4796  
4797      /** @var WP_Plugins_List_Table $wp_list_table */
4798      $wp_list_table = _get_list_table(
4799          'WP_Plugins_List_Table',
4800          array(
4801              'screen' => get_current_screen(),
4802          )
4803      );
4804  
4805      $status = array();
4806  
4807      if ( ! $wp_list_table->ajax_user_can() ) {
4808          $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
4809          wp_send_json_error( $status );
4810      }
4811  
4812      // Set the correct requester, so pagination works.
4813      $_SERVER['REQUEST_URI'] = add_query_arg(
4814          array_diff_key(
4815              $_POST,
4816              array(
4817                  '_ajax_nonce' => null,
4818                  'action'      => null,
4819              )
4820          ),
4821          network_admin_url( 'plugins.php', 'relative' )
4822      );
4823  
4824      $GLOBALS['s'] = wp_unslash( $_POST['s'] );
4825  
4826      $wp_list_table->prepare_items();
4827  
4828      ob_start();
4829      $wp_list_table->display();
4830      $status['count'] = count( $wp_list_table->items );
4831      $status['items'] = ob_get_clean();
4832  
4833      wp_send_json_success( $status );
4834  }
4835  
4836  /**
4837   * Handles searching plugins to install via AJAX.
4838   *
4839   * @since 4.6.0
4840   */
4841  function wp_ajax_search_install_plugins() {
4842      check_ajax_referer( 'updates' );
4843  
4844      $pagenow = isset( $_POST['pagenow'] ) ? sanitize_key( $_POST['pagenow'] ) : '';
4845      if ( 'plugin-install-network' === $pagenow || 'plugin-install' === $pagenow ) {
4846          set_current_screen( $pagenow );
4847      }
4848  
4849      /** @var WP_Plugin_Install_List_Table $wp_list_table */
4850      $wp_list_table = _get_list_table(
4851          'WP_Plugin_Install_List_Table',
4852          array(
4853              'screen' => get_current_screen(),
4854          )
4855      );
4856  
4857      $status = array();
4858  
4859      if ( ! $wp_list_table->ajax_user_can() ) {
4860          $status['errorMessage'] = __( 'Sorry, you are not allowed to manage plugins for this site.' );
4861          wp_send_json_error( $status );
4862      }
4863  
4864      // Set the correct requester, so pagination works.
4865      $_SERVER['REQUEST_URI'] = add_query_arg(
4866          array_diff_key(
4867              $_POST,
4868              array(
4869                  '_ajax_nonce' => null,
4870                  'action'      => null,
4871              )
4872          ),
4873          network_admin_url( 'plugin-install.php', 'relative' )
4874      );
4875  
4876      $wp_list_table->prepare_items();
4877  
4878      ob_start();
4879      $wp_list_table->display();
4880      $status['count'] = (int) $wp_list_table->get_pagination_arg( 'total_items' );
4881      $status['items'] = ob_get_clean();
4882  
4883      wp_send_json_success( $status );
4884  }
4885  
4886  /**
4887   * Handles editing a theme or plugin file via AJAX.
4888   *
4889   * @since 4.9.0
4890   *
4891   * @see wp_edit_theme_plugin_file()
4892   */
4893  function wp_ajax_edit_theme_plugin_file() {
4894      $r = wp_edit_theme_plugin_file( wp_unslash( $_POST ) ); // Validation of args is done in wp_edit_theme_plugin_file().
4895  
4896      if ( is_wp_error( $r ) ) {
4897          wp_send_json_error(
4898              array_merge(
4899                  array(
4900                      'code'    => $r->get_error_code(),
4901                      'message' => $r->get_error_message(),
4902                  ),
4903                  (array) $r->get_error_data()
4904              )
4905          );
4906      } else {
4907          wp_send_json_success(
4908              array(
4909                  'message' => __( 'File edited successfully.' ),
4910              )
4911          );
4912      }
4913  }
4914  
4915  /**
4916   * Handles exporting a user's personal data via AJAX.
4917   *
4918   * @since 4.9.6
4919   */
4920  function wp_ajax_wp_privacy_export_personal_data() {
4921  
4922      if ( empty( $_POST['id'] ) ) {
4923          wp_send_json_error( __( 'Missing request ID.' ) );
4924      }
4925  
4926      $request_id = (int) $_POST['id'];
4927  
4928      if ( $request_id < 1 ) {
4929          wp_send_json_error( __( 'Invalid request ID.' ) );
4930      }
4931  
4932      if ( ! current_user_can( 'export_others_personal_data' ) ) {
4933          wp_send_json_error( __( 'Sorry, you are not allowed to perform this action.' ) );
4934      }
4935  
4936      check_ajax_referer( 'wp-privacy-export-personal-data-' . $request_id, 'security' );
4937  
4938      // Get the request.
4939      $request = wp_get_user_request( $request_id );
4940  
4941      if ( ! $request || 'export_personal_data' !== $request->action_name ) {
4942          wp_send_json_error( __( 'Invalid request type.' ) );
4943      }
4944  
4945      $email_address = $request->email;
4946      if ( ! is_email( $email_address ) ) {
4947          wp_send_json_error( __( 'A valid email address must be given.' ) );
4948      }
4949  
4950      if ( ! isset( $_POST['exporter'] ) ) {
4951          wp_send_json_error( __( 'Missing exporter index.' ) );
4952      }
4953  
4954      $exporter_index = (int) $_POST['exporter'];
4955  
4956      if ( ! isset( $_POST['page'] ) ) {
4957          wp_send_json_error( __( 'Missing page index.' ) );
4958      }
4959  
4960      $page = (int) $_POST['page'];
4961  
4962      $send_as_email = isset( $_POST['sendAsEmail'] ) ? ( 'true' === $_POST['sendAsEmail'] ) : false;
4963  
4964      /**
4965       * Filters the array of exporter callbacks.
4966       *
4967       * @since 4.9.6
4968       *
4969       * @param array $args {
4970       *     An array of callable exporters of personal data. Default empty array.
4971       *
4972       *     @type array ...$0 {
4973       *         Array of personal data exporters.
4974       *
4975       *         @type callable $callback               Callable exporter function that accepts an
4976       *                                                email address and a page number and returns an
4977       *                                                array of name => value pairs of personal data.
4978       *         @type string   $exporter_friendly_name Translated user facing friendly name for the
4979       *                                                exporter.
4980       *     }
4981       * }
4982       */
4983      $exporters = apply_filters( 'wp_privacy_personal_data_exporters', array() );
4984  
4985      if ( ! is_array( $exporters ) ) {
4986          wp_send_json_error( __( 'An exporter has improperly used the registration filter.' ) );
4987      }
4988  
4989      // Do we have any registered exporters?
4990      if ( 0 < count( $exporters ) ) {
4991          if ( $exporter_index < 1 ) {
4992              wp_send_json_error( __( 'Exporter index cannot be negative.' ) );
4993          }
4994  
4995          if ( $exporter_index > count( $exporters ) ) {
4996              wp_send_json_error( __( 'Exporter index is out of range.' ) );
4997          }
4998  
4999          if ( $page < 1 ) {
5000              wp_send_json_error( __( 'Page index cannot be less than one.' ) );
5001          }
5002  
5003          $exporter_keys = array_keys( $exporters );
5004          $exporter_key  = $exporter_keys[ $exporter_index - 1 ];
5005          $exporter      = $exporters[ $exporter_key ];
5006  
5007          if ( ! is_array( $exporter ) ) {
5008              wp_send_json_error(
5009                  /* translators: %s: Exporter array index. */
5010                  sprintf( __( 'Expected an array describing the exporter at index %s.' ), $exporter_key )
5011              );
5012          }
5013  
5014          if ( ! array_key_exists( 'exporter_friendly_name', $exporter ) ) {
5015              wp_send_json_error(
5016                  /* translators: %s: Exporter array index. */
5017                  sprintf( __( 'Exporter array at index %s does not include a friendly name.' ), $exporter_key )
5018              );
5019          }
5020  
5021          $exporter_friendly_name = $exporter['exporter_friendly_name'];
5022  
5023          if ( ! array_key_exists( 'callback', $exporter ) ) {
5024              wp_send_json_error(
5025                  /* translators: %s: Exporter friendly name. */
5026                  sprintf( __( 'Exporter does not include a callback: %s.' ), esc_html( $exporter_friendly_name ) )
5027              );
5028          }
5029  
5030          if ( ! is_callable( $exporter['callback'] ) ) {
5031              wp_send_json_error(
5032                  /* translators: %s: Exporter friendly name. */
5033                  sprintf( __( 'Exporter callback is not a valid callback: %s.' ), esc_html( $exporter_friendly_name ) )
5034              );
5035          }
5036  
5037          $callback = $exporter['callback'];
5038          $response = call_user_func( $callback, $email_address, $page );
5039  
5040          if ( is_wp_error( $response ) ) {
5041              wp_send_json_error( $response );
5042          }
5043  
5044          if ( ! is_array( $response ) ) {
5045              wp_send_json_error(
5046                  /* translators: %s: Exporter friendly name. */
5047                  sprintf( __( 'Expected response as an array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
5048              );
5049          }
5050  
5051          if ( ! array_key_exists( 'data', $response ) ) {
5052              wp_send_json_error(
5053                  /* translators: %s: Exporter friendly name. */
5054                  sprintf( __( 'Expected data in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
5055              );
5056          }
5057  
5058          if ( ! is_array( $response['data'] ) ) {
5059              wp_send_json_error(
5060                  /* translators: %s: Exporter friendly name. */
5061                  sprintf( __( 'Expected data array in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
5062              );
5063          }
5064  
5065          if ( ! array_key_exists( 'done', $response ) ) {
5066              wp_send_json_error(
5067                  /* translators: %s: Exporter friendly name. */
5068                  sprintf( __( 'Expected done (boolean) in response array from exporter: %s.' ), esc_html( $exporter_friendly_name ) )
5069              );
5070          }
5071      } else {
5072          // No exporters, so we're done.
5073          $exporter_key = '';
5074  
5075          $response = array(
5076              'data' => array(),
5077              'done' => true,
5078          );
5079      }
5080  
5081      /**
5082       * Filters a page of personal data exporter data. Used to build the export report.
5083       *
5084       * Allows the export response to be consumed by destinations in addition to Ajax.
5085       *
5086       * @since 4.9.6
5087       *
5088       * @param array  $response        The personal data for the given exporter and page number.
5089       * @param int    $exporter_index  The index of the exporter that provided this data.
5090       * @param string $email_address   The email address associated with this personal data.
5091       * @param int    $page            The page number for this response.
5092       * @param int    $request_id      The privacy request post ID associated with this request.
5093       * @param bool   $send_as_email   Whether the final results of the export should be emailed to the user.
5094       * @param string $exporter_key    The key (slug) of the exporter that provided this data.
5095       */
5096      $response = apply_filters( 'wp_privacy_personal_data_export_page', $response, $exporter_index, $email_address, $page, $request_id, $send_as_email, $exporter_key );
5097  
5098      if ( is_wp_error( $response ) ) {
5099          wp_send_json_error( $response );
5100      }
5101  
5102      wp_send_json_success( $response );
5103  }
5104  
5105  /**
5106   * Handles erasing personal data via AJAX.
5107   *
5108   * @since 4.9.6
5109   */
5110  function wp_ajax_wp_privacy_erase_personal_data() {
5111  
5112      if ( empty( $_POST['id'] ) ) {
5113          wp_send_json_error( __( 'Missing request ID.' ) );
5114      }
5115  
5116      $request_id = (int) $_POST['id'];
5117  
5118      if ( $request_id < 1 ) {
5119          wp_send_json_error( __( 'Invalid request ID.' ) );
5120      }
5121  
5122      // Both capabilities are required to avoid confusion, see `_wp_personal_data_removal_page()`.
5123      if ( ! current_user_can( 'erase_others_personal_data' ) || ! current_user_can( 'delete_users' ) ) {
5124          wp_send_json_error( __( 'Sorry, you are not allowed to perform this action.' ) );
5125      }
5126  
5127      check_ajax_referer( 'wp-privacy-erase-personal-data-' . $request_id, 'security' );
5128  
5129      // Get the request.
5130      $request = wp_get_user_request( $request_id );
5131  
5132      if ( ! $request || 'remove_personal_data' !== $request->action_name ) {
5133          wp_send_json_error( __( 'Invalid request type.' ) );
5134      }
5135  
5136      $email_address = $request->email;
5137  
5138      if ( ! is_email( $email_address ) ) {
5139          wp_send_json_error( __( 'Invalid email address in request.' ) );
5140      }
5141  
5142      if ( ! isset( $_POST['eraser'] ) ) {
5143          wp_send_json_error( __( 'Missing eraser index.' ) );
5144      }
5145  
5146      $eraser_index = (int) $_POST['eraser'];
5147  
5148      if ( ! isset( $_POST['page'] ) ) {
5149          wp_send_json_error( __( 'Missing page index.' ) );
5150      }
5151  
5152      $page = (int) $_POST['page'];
5153  
5154      /**
5155       * Filters the array of personal data eraser callbacks.
5156       *
5157       * @since 4.9.6
5158       *
5159       * @param array $args {
5160       *     An array of callable erasers of personal data. Default empty array.
5161       *
5162       *     @type array ...$0 {
5163       *         Array of personal data exporters.
5164       *
5165       *         @type callable $callback               Callable eraser that accepts an email address and a page
5166       *                                                number, and returns an array with boolean values for
5167       *                                                whether items were removed or retained and any messages
5168       *                                                from the eraser, as well as if additional pages are
5169       *                                                available.
5170       *         @type string   $exporter_friendly_name Translated user facing friendly name for the eraser.
5171       *     }
5172       * }
5173       */
5174      $erasers = apply_filters( 'wp_privacy_personal_data_erasers', array() );
5175  
5176      // Do we have any registered erasers?
5177      if ( 0 < count( $erasers ) ) {
5178  
5179          if ( $eraser_index < 1 ) {
5180              wp_send_json_error( __( 'Eraser index cannot be less than one.' ) );
5181          }
5182  
5183          if ( $eraser_index > count( $erasers ) ) {
5184              wp_send_json_error( __( 'Eraser index is out of range.' ) );
5185          }
5186  
5187          if ( $page < 1 ) {
5188              wp_send_json_error( __( 'Page index cannot be less than one.' ) );
5189          }
5190  
5191          $eraser_keys = array_keys( $erasers );
5192          $eraser_key  = $eraser_keys[ $eraser_index - 1 ];
5193          $eraser      = $erasers[ $eraser_key ];
5194  
5195          if ( ! is_array( $eraser ) ) {
5196              /* translators: %d: Eraser array index. */
5197              wp_send_json_error( sprintf( __( 'Expected an array describing the eraser at index %d.' ), $eraser_index ) );
5198          }
5199  
5200          if ( ! array_key_exists( 'eraser_friendly_name', $eraser ) ) {
5201              /* translators: %d: Eraser array index. */
5202              wp_send_json_error( sprintf( __( 'Eraser array at index %d does not include a friendly name.' ), $eraser_index ) );
5203          }
5204  
5205          $eraser_friendly_name = $eraser['eraser_friendly_name'];
5206  
5207          if ( ! array_key_exists( 'callback', $eraser ) ) {
5208              wp_send_json_error(
5209                  sprintf(
5210                      /* translators: %s: Eraser friendly name. */
5211                      __( 'Eraser does not include a callback: %s.' ),
5212                      esc_html( $eraser_friendly_name )
5213                  )
5214              );
5215          }
5216  
5217          if ( ! is_callable( $eraser['callback'] ) ) {
5218              wp_send_json_error(
5219                  sprintf(
5220                      /* translators: %s: Eraser friendly name. */
5221                      __( 'Eraser callback is not valid: %s.' ),
5222                      esc_html( $eraser_friendly_name )
5223                  )
5224              );
5225          }
5226  
5227          $callback = $eraser['callback'];
5228          $response = call_user_func( $callback, $email_address, $page );
5229  
5230          if ( is_wp_error( $response ) ) {
5231              wp_send_json_error( $response );
5232          }
5233  
5234          if ( ! is_array( $response ) ) {
5235              wp_send_json_error(
5236                  sprintf(
5237                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5238                      __( 'Did not receive array from %1$s eraser (index %2$d).' ),
5239                      esc_html( $eraser_friendly_name ),
5240                      $eraser_index
5241                  )
5242              );
5243          }
5244  
5245          if ( ! array_key_exists( 'items_removed', $response ) ) {
5246              wp_send_json_error(
5247                  sprintf(
5248                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5249                      __( 'Expected items_removed key in response array from %1$s eraser (index %2$d).' ),
5250                      esc_html( $eraser_friendly_name ),
5251                      $eraser_index
5252                  )
5253              );
5254          }
5255  
5256          if ( ! array_key_exists( 'items_retained', $response ) ) {
5257              wp_send_json_error(
5258                  sprintf(
5259                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5260                      __( 'Expected items_retained key in response array from %1$s eraser (index %2$d).' ),
5261                      esc_html( $eraser_friendly_name ),
5262                      $eraser_index
5263                  )
5264              );
5265          }
5266  
5267          if ( ! array_key_exists( 'messages', $response ) ) {
5268              wp_send_json_error(
5269                  sprintf(
5270                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5271                      __( 'Expected messages key in response array from %1$s eraser (index %2$d).' ),
5272                      esc_html( $eraser_friendly_name ),
5273                      $eraser_index
5274                  )
5275              );
5276          }
5277  
5278          if ( ! is_array( $response['messages'] ) ) {
5279              wp_send_json_error(
5280                  sprintf(
5281                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5282                      __( 'Expected messages key to reference an array in response array from %1$s eraser (index %2$d).' ),
5283                      esc_html( $eraser_friendly_name ),
5284                      $eraser_index
5285                  )
5286              );
5287          }
5288  
5289          if ( ! array_key_exists( 'done', $response ) ) {
5290              wp_send_json_error(
5291                  sprintf(
5292                      /* translators: 1: Eraser friendly name, 2: Eraser array index. */
5293                      __( 'Expected done flag in response array from %1$s eraser (index %2$d).' ),
5294                      esc_html( $eraser_friendly_name ),
5295                      $eraser_index
5296                  )
5297              );
5298          }
5299      } else {
5300          // No erasers, so we're done.
5301          $eraser_key = '';
5302  
5303          $response = array(
5304              'items_removed'  => false,
5305              'items_retained' => false,
5306              'messages'       => array(),
5307              'done'           => true,
5308          );
5309      }
5310  
5311      /**
5312       * Filters a page of personal data eraser data.
5313       *
5314       * Allows the erasure response to be consumed by destinations in addition to Ajax.
5315       *
5316       * @since 4.9.6
5317       *
5318       * @param array  $response        {
5319       *     The personal data for the given exporter and page number.
5320       *
5321       *     @type bool     $items_removed  Whether items were actually removed or not.
5322       *     @type bool     $items_retained Whether items were retained or not.
5323       *     @type string[] $messages       An array of messages to add to the personal data export file.
5324       *     @type bool     $done           Whether the eraser is finished or not.
5325       * }
5326       * @param int    $eraser_index    The index of the eraser that provided this data.
5327       * @param string $email_address   The email address associated with this personal data.
5328       * @param int    $page            The page number for this response.
5329       * @param int    $request_id      The privacy request post ID associated with this request.
5330       * @param string $eraser_key      The key (slug) of the eraser that provided this data.
5331       */
5332      $response = apply_filters( 'wp_privacy_personal_data_erasure_page', $response, $eraser_index, $email_address, $page, $request_id, $eraser_key );
5333  
5334      if ( is_wp_error( $response ) ) {
5335          wp_send_json_error( $response );
5336      }
5337  
5338      wp_send_json_success( $response );
5339  }
5340  
5341  /**
5342   * Handles site health checks on server communication via AJAX.
5343   *
5344   * @since 5.2.0
5345   * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_dotorg_communication()
5346   * @see WP_REST_Site_Health_Controller::test_dotorg_communication()
5347   */
5348  function wp_ajax_health_check_dotorg_communication() {
5349      _doing_it_wrong(
5350          'wp_ajax_health_check_dotorg_communication',
5351          sprintf(
5352          // translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
5353              __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
5354              'wp_ajax_health_check_dotorg_communication',
5355              'WP_REST_Site_Health_Controller::test_dotorg_communication'
5356          ),
5357          '5.6.0'
5358      );
5359  
5360      check_ajax_referer( 'health-check-site-status' );
5361  
5362      if ( ! current_user_can( 'view_site_health_checks' ) ) {
5363          wp_send_json_error();
5364      }
5365  
5366      if ( ! class_exists( 'WP_Site_Health' ) ) {
5367          require_once  ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
5368      }
5369  
5370      $site_health = WP_Site_Health::get_instance();
5371      wp_send_json_success( $site_health->get_test_dotorg_communication() );
5372  }
5373  
5374  /**
5375   * Handles site health checks on background updates via AJAX.
5376   *
5377   * @since 5.2.0
5378   * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_background_updates()
5379   * @see WP_REST_Site_Health_Controller::test_background_updates()
5380   */
5381  function wp_ajax_health_check_background_updates() {
5382      _doing_it_wrong(
5383          'wp_ajax_health_check_background_updates',
5384          sprintf(
5385          // translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
5386              __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
5387              'wp_ajax_health_check_background_updates',
5388              'WP_REST_Site_Health_Controller::test_background_updates'
5389          ),
5390          '5.6.0'
5391      );
5392  
5393      check_ajax_referer( 'health-check-site-status' );
5394  
5395      if ( ! current_user_can( 'view_site_health_checks' ) ) {
5396          wp_send_json_error();
5397      }
5398  
5399      if ( ! class_exists( 'WP_Site_Health' ) ) {
5400          require_once  ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
5401      }
5402  
5403      $site_health = WP_Site_Health::get_instance();
5404      wp_send_json_success( $site_health->get_test_background_updates() );
5405  }
5406  
5407  /**
5408   * Handles site health checks on loopback requests via AJAX.
5409   *
5410   * @since 5.2.0
5411   * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::test_loopback_requests()
5412   * @see WP_REST_Site_Health_Controller::test_loopback_requests()
5413   */
5414  function wp_ajax_health_check_loopback_requests() {
5415      _doing_it_wrong(
5416          'wp_ajax_health_check_loopback_requests',
5417          sprintf(
5418          // translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
5419              __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
5420              'wp_ajax_health_check_loopback_requests',
5421              'WP_REST_Site_Health_Controller::test_loopback_requests'
5422          ),
5423          '5.6.0'
5424      );
5425  
5426      check_ajax_referer( 'health-check-site-status' );
5427  
5428      if ( ! current_user_can( 'view_site_health_checks' ) ) {
5429          wp_send_json_error();
5430      }
5431  
5432      if ( ! class_exists( 'WP_Site_Health' ) ) {
5433          require_once  ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
5434      }
5435  
5436      $site_health = WP_Site_Health::get_instance();
5437      wp_send_json_success( $site_health->get_test_loopback_requests() );
5438  }
5439  
5440  /**
5441   * Handles site health check to update the result status via AJAX.
5442   *
5443   * @since 5.2.0
5444   */
5445  function wp_ajax_health_check_site_status_result() {
5446      check_ajax_referer( 'health-check-site-status-result' );
5447  
5448      if ( ! current_user_can( 'view_site_health_checks' ) ) {
5449          wp_send_json_error();
5450      }
5451  
5452      set_transient( 'health-check-site-status-result', wp_json_encode( $_POST['counts'] ) );
5453  
5454      wp_send_json_success();
5455  }
5456  
5457  /**
5458   * Handles site health check to get directories and database sizes via AJAX.
5459   *
5460   * @since 5.2.0
5461   * @deprecated 5.6.0 Use WP_REST_Site_Health_Controller::get_directory_sizes()
5462   * @see WP_REST_Site_Health_Controller::get_directory_sizes()
5463   */
5464  function wp_ajax_health_check_get_sizes() {
5465      _doing_it_wrong(
5466          'wp_ajax_health_check_get_sizes',
5467          sprintf(
5468          // translators: 1: The Site Health action that is no longer used by core. 2: The new function that replaces it.
5469              __( 'The Site Health check for %1$s has been replaced with %2$s.' ),
5470              'wp_ajax_health_check_get_sizes',
5471              'WP_REST_Site_Health_Controller::get_directory_sizes'
5472          ),
5473          '5.6.0'
5474      );
5475  
5476      check_ajax_referer( 'health-check-site-status-result' );
5477  
5478      if ( ! current_user_can( 'view_site_health_checks' ) || is_multisite() ) {
5479          wp_send_json_error();
5480      }
5481  
5482      if ( ! class_exists( 'WP_Debug_Data' ) ) {
5483          require_once  ABSPATH . 'wp-admin/includes/class-wp-debug-data.php';
5484      }
5485  
5486      $sizes_data = WP_Debug_Data::get_sizes();
5487      $all_sizes  = array( 'raw' => 0 );
5488  
5489      foreach ( $sizes_data as $name => $value ) {
5490          $name = sanitize_text_field( $name );
5491          $data = array();
5492  
5493          if ( isset( $value['size'] ) ) {
5494              if ( is_string( $value['size'] ) ) {
5495                  $data['size'] = sanitize_text_field( $value['size'] );
5496              } else {
5497                  $data['size'] = (int) $value['size'];
5498              }
5499          }
5500  
5501          if ( isset( $value['debug'] ) ) {
5502              if ( is_string( $value['debug'] ) ) {
5503                  $data['debug'] = sanitize_text_field( $value['debug'] );
5504              } else {
5505                  $data['debug'] = (int) $value['debug'];
5506              }
5507          }
5508  
5509          if ( ! empty( $value['raw'] ) ) {
5510              $data['raw'] = (int) $value['raw'];
5511          }
5512  
5513          $all_sizes[ $name ] = $data;
5514      }
5515  
5516      if ( isset( $all_sizes['total_size']['debug'] ) && 'not available' === $all_sizes['total_size']['debug'] ) {
5517          wp_send_json_error( $all_sizes );
5518      }
5519  
5520      wp_send_json_success( $all_sizes );
5521  }
5522  
5523  /**
5524   * Handles renewing the REST API nonce via AJAX.
5525   *
5526   * @since 5.3.0
5527   */
5528  function wp_ajax_rest_nonce() {
5529      exit( wp_create_nonce( 'wp_rest' ) );
5530  }
5531  
5532  /**
5533   * Handles enabling or disable plugin and theme auto-updates via AJAX.
5534   *
5535   * @since 5.5.0
5536   */
5537  function wp_ajax_toggle_auto_updates() {
5538      check_ajax_referer( 'updates' );
5539  
5540      if ( empty( $_POST['type'] ) || empty( $_POST['asset'] ) || empty( $_POST['state'] ) ) {
5541          wp_send_json_error( array( 'error' => __( 'Invalid data. No selected item.' ) ) );
5542      }
5543  
5544      $asset = sanitize_text_field( urldecode( $_POST['asset'] ) );
5545  
5546      if ( 'enable' !== $_POST['state'] && 'disable' !== $_POST['state'] ) {
5547          wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown state.' ) ) );
5548      }
5549      $state = $_POST['state'];
5550  
5551      if ( 'plugin' !== $_POST['type'] && 'theme' !== $_POST['type'] ) {
5552          wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown type.' ) ) );
5553      }
5554      $type = $_POST['type'];
5555  
5556      switch ( $type ) {
5557          case 'plugin':
5558              if ( ! current_user_can( 'update_plugins' ) ) {
5559                  $error_message = __( 'Sorry, you are not allowed to modify plugins.' );
5560                  wp_send_json_error( array( 'error' => $error_message ) );
5561              }
5562  
5563              $option = 'auto_update_plugins';
5564              /** This filter is documented in wp-admin/includes/class-wp-plugins-list-table.php */
5565              $all_items = apply_filters( 'all_plugins', get_plugins() );
5566              break;
5567          case 'theme':
5568              if ( ! current_user_can( 'update_themes' ) ) {
5569                  $error_message = __( 'Sorry, you are not allowed to modify themes.' );
5570                  wp_send_json_error( array( 'error' => $error_message ) );
5571              }
5572  
5573              $option    = 'auto_update_themes';
5574              $all_items = wp_get_themes();
5575              break;
5576          default:
5577              wp_send_json_error( array( 'error' => __( 'Invalid data. Unknown type.' ) ) );
5578      }
5579  
5580      if ( ! array_key_exists( $asset, $all_items ) ) {
5581          $error_message = __( 'Invalid data. The item does not exist.' );
5582          wp_send_json_error( array( 'error' => $error_message ) );
5583      }
5584  
5585      $auto_updates = (array) get_site_option( $option, array() );
5586  
5587      if ( 'disable' === $state ) {
5588          $auto_updates = array_diff( $auto_updates, array( $asset ) );
5589      } else {
5590          $auto_updates[] = $asset;
5591          $auto_updates   = array_unique( $auto_updates );
5592      }
5593  
5594      // Remove items that have been deleted since the site option was last updated.
5595      $auto_updates = array_intersect( $auto_updates, array_keys( $all_items ) );
5596  
5597      update_site_option( $option, $auto_updates );
5598  
5599      wp_send_json_success();
5600  }
5601  
5602  /**
5603   * Handles sending a password reset link via AJAX.
5604   *
5605   * @since 5.7.0
5606   */
5607  function wp_ajax_send_password_reset() {
5608  
5609      // Validate the nonce for this action.
5610      $user_id = isset( $_POST['user_id'] ) ? (int) $_POST['user_id'] : 0;
5611      check_ajax_referer( 'reset-password-for-' . $user_id, 'nonce' );
5612  
5613      // Verify user capabilities.
5614      if ( ! current_user_can( 'edit_user', $user_id ) ) {
5615          wp_send_json_error( __( 'Cannot send password reset, permission denied.' ) );
5616      }
5617  
5618      // Send the password reset link.
5619      $user    = get_userdata( $user_id );
5620      $results = retrieve_password( $user->user_login );
5621  
5622      if ( true === $results ) {
5623          wp_send_json_success(
5624              /* translators: %s: User's display name. */
5625              sprintf( __( 'A password reset link was emailed to %s.' ), $user->display_name )
5626          );
5627      } else {
5628          wp_send_json_error( $results->get_error_message() );
5629      }
5630  }


Generated : Tue Apr 23 08:20:01 2024 Cross-referenced by PHPXref