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