[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

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


Generated: Sat Apr 27 01:00:02 2024 Cross-referenced by PHPXref 0.7.1