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