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