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