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