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