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