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