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