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