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