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