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