| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * WordPress Dashboard Widget Administration Screen API 4 * 5 * @package WordPress 6 * @subpackage Administration 7 */ 8 9 /** 10 * Determines whether the site has ever received a comment. 11 * 12 * The result is persisted so the Recent Comments widget remains registered if 13 * all comments are later deleted, preserving its Screen Options and position. 14 * 15 * @since 7.2.0 16 * @access private 17 * 18 * @return bool Whether the site has received a comment. 19 */ 20 function _wp_dashboard_has_received_comments() { 21 if ( get_option( 'dashboard_recent_comments_widget_registered' ) ) { 22 return true; 23 } 24 25 $comment_count = wp_count_comments(); 26 27 if ( empty( $comment_count->all ) ) { 28 return false; 29 } 30 31 update_option( 'dashboard_recent_comments_widget_registered', 1, false ); 32 33 return true; 34 } 35 36 /** 37 * Registers dashboard widgets. 38 * 39 * Handles POST data, sets up filters. 40 * 41 * @since 2.5.0 42 * 43 * @global array $wp_registered_widgets 44 * @global array $wp_registered_widget_controls 45 * @global callable[] $wp_dashboard_control_callbacks 46 */ 47 function wp_dashboard_setup() { 48 global $wp_registered_widgets, $wp_registered_widget_controls, $wp_dashboard_control_callbacks; 49 50 $screen = get_current_screen(); 51 52 /* Register Widgets and Controls */ 53 $wp_dashboard_control_callbacks = array(); 54 55 // Browser version 56 $check_browser = wp_check_browser_version(); 57 58 if ( $check_browser && $check_browser['upgrade'] ) { 59 add_filter( 'postbox_classes_dashboard_dashboard_browser_nag', 'dashboard_browser_nag_class' ); 60 61 if ( $check_browser['insecure'] ) { 62 wp_add_dashboard_widget( 'dashboard_browser_nag', __( 'You are using an insecure browser!' ), 'wp_dashboard_browser_nag' ); 63 } else { 64 wp_add_dashboard_widget( 'dashboard_browser_nag', __( 'Your browser is out of date!' ), 'wp_dashboard_browser_nag' ); 65 } 66 } 67 68 // PHP Version. 69 $check_php = wp_check_php_version(); 70 71 if ( $check_php && current_user_can( 'update_php' ) ) { 72 // If "not acceptable" the widget will be shown. 73 if ( isset( $check_php['is_acceptable'] ) && ! $check_php['is_acceptable'] ) { 74 add_filter( 'postbox_classes_dashboard_dashboard_php_nag', 'dashboard_php_nag_class' ); 75 76 if ( $check_php['is_lower_than_future_minimum'] ) { 77 wp_add_dashboard_widget( 'dashboard_php_nag', __( 'PHP Update Required' ), 'wp_dashboard_php_nag' ); 78 } else { 79 wp_add_dashboard_widget( 'dashboard_php_nag', __( 'PHP Update Recommended' ), 'wp_dashboard_php_nag' ); 80 } 81 } 82 } 83 84 // Site Health. 85 if ( current_user_can( 'view_site_health_checks' ) && ! is_network_admin() ) { 86 if ( ! class_exists( 'WP_Site_Health' ) ) { 87 require_once ABSPATH . 'wp-admin/includes/class-wp-site-health.php'; 88 } 89 90 WP_Site_Health::get_instance(); 91 92 wp_enqueue_style( 'site-health' ); 93 wp_enqueue_script( 'site-health' ); 94 95 wp_add_dashboard_widget( 'dashboard_site_health', __( 'Site Health Status' ), 'wp_dashboard_site_health' ); 96 } 97 98 // Right Now. 99 if ( is_blog_admin() && current_user_can( 'edit_posts' ) ) { 100 wp_add_dashboard_widget( 'dashboard_right_now', __( 'At a Glance' ), 'wp_dashboard_right_now' ); 101 } 102 103 if ( is_network_admin() ) { 104 wp_add_dashboard_widget( 'network_dashboard_right_now', __( 'Right Now' ), 'wp_network_dashboard_right_now' ); 105 } 106 107 // Activity Widget. 108 if ( is_blog_admin() ) { 109 wp_add_dashboard_widget( 'dashboard_activity', __( 'Activity' ), 'wp_dashboard_site_activity' ); 110 } 111 112 // Recent Comments Widget. 113 if ( is_blog_admin() && _wp_dashboard_has_received_comments() ) { 114 wp_add_dashboard_widget( 'dashboard_recent_comments', __( 'Recent Comments' ), 'wp_dashboard_recent_comments_widget' ); 115 } 116 117 // QuickPress Widget. 118 if ( is_blog_admin() && current_user_can( get_post_type_object( 'post' )->cap->create_posts ) ) { 119 $quick_draft_title = sprintf( '<span class="hide-if-no-js">%1$s</span> <span class="hide-if-js">%2$s</span>', __( 'Quick Draft' ), __( 'Your Recent Drafts' ) ); 120 wp_add_dashboard_widget( 'dashboard_quick_press', $quick_draft_title, 'wp_dashboard_quick_press' ); 121 } 122 123 // WordPress Events and News. 124 wp_add_dashboard_widget( 'dashboard_primary', __( 'WordPress Events and News' ), 'wp_dashboard_events_news' ); 125 126 if ( is_network_admin() ) { 127 128 /** 129 * Fires after core widgets for the Network Admin dashboard have been registered. 130 * 131 * @since 3.1.0 132 */ 133 do_action( 'wp_network_dashboard_setup' ); 134 135 /** 136 * Filters the list of widgets to load for the Network Admin dashboard. 137 * 138 * @since 3.1.0 139 * 140 * @param string[] $dashboard_widgets An array of dashboard widget IDs. 141 */ 142 $dashboard_widgets = apply_filters( 'wp_network_dashboard_widgets', array() ); 143 } elseif ( is_user_admin() ) { 144 145 /** 146 * Fires after core widgets for the User Admin dashboard have been registered. 147 * 148 * @since 3.1.0 149 */ 150 do_action( 'wp_user_dashboard_setup' ); 151 152 /** 153 * Filters the list of widgets to load for the User Admin dashboard. 154 * 155 * @since 3.1.0 156 * 157 * @param string[] $dashboard_widgets An array of dashboard widget IDs. 158 */ 159 $dashboard_widgets = apply_filters( 'wp_user_dashboard_widgets', array() ); 160 } else { 161 162 /** 163 * Fires after core widgets for the admin dashboard have been registered. 164 * 165 * @since 2.5.0 166 */ 167 do_action( 'wp_dashboard_setup' ); 168 169 /** 170 * Filters the list of widgets to load for the admin dashboard. 171 * 172 * @since 2.5.0 173 * 174 * @param string[] $dashboard_widgets An array of dashboard widget IDs. 175 */ 176 $dashboard_widgets = apply_filters( 'wp_dashboard_widgets', array() ); 177 } 178 179 foreach ( $dashboard_widgets as $widget_id ) { 180 $name = empty( $wp_registered_widgets[ $widget_id ]['all_link'] ) ? $wp_registered_widgets[ $widget_id ]['name'] : $wp_registered_widgets[ $widget_id ]['name'] . " <a href='{$wp_registered_widgets[$widget_id]['all_link']}' class='edit-box open-box'>" . __( 'View all' ) . '</a>'; 181 wp_add_dashboard_widget( $widget_id, $name, $wp_registered_widgets[ $widget_id ]['callback'], $wp_registered_widget_controls[ $widget_id ]['callback'] ); 182 } 183 184 if ( 'POST' === $_SERVER['REQUEST_METHOD'] && isset( $_POST['widget_id'] ) ) { 185 check_admin_referer( 'edit-dashboard-widget_' . $_POST['widget_id'], 'dashboard-widget-nonce' ); 186 ob_start(); // Hack - but the same hack wp-admin/widgets.php uses. 187 wp_dashboard_trigger_widget_control( $_POST['widget_id'] ); 188 ob_end_clean(); 189 wp_redirect( remove_query_arg( 'edit' ) ); 190 exit; 191 } 192 193 /** This action is documented in wp-admin/includes/meta-boxes.php */ 194 do_action( 'do_meta_boxes', $screen->id, 'normal', '' ); 195 196 /** This action is documented in wp-admin/includes/meta-boxes.php */ 197 do_action( 'do_meta_boxes', $screen->id, 'side', '' ); 198 } 199 200 /** 201 * Adds a new dashboard widget. 202 * 203 * @since 2.7.0 204 * @since 5.6.0 The `$context` and `$priority` parameters were added. 205 * 206 * @global callable[] $wp_dashboard_control_callbacks 207 * 208 * @param string $widget_id Widget ID (used in the 'id' attribute for the widget). 209 * @param string $widget_name Title of the widget. 210 * @param callable $callback Function that fills the widget with the desired content. 211 * The function should echo its output. 212 * @param callable $control_callback Optional. Function that outputs controls for the widget. Default null. 213 * @param array $callback_args Optional. Data that should be set as the $args property of the widget array 214 * (which is the second parameter passed to your callback). Default null. 215 * @param string $context Optional. The context within the screen where the box should display. 216 * Accepts 'normal', 'side', 'column3', or 'column4'. Default 'normal'. 217 * @param string $priority Optional. The priority within the context where the box should show. 218 * Accepts 'high', 'core', 'default', or 'low'. Default 'core'. 219 */ 220 function wp_add_dashboard_widget( $widget_id, $widget_name, $callback, $control_callback = null, $callback_args = null, $context = 'normal', $priority = 'core' ) { 221 global $wp_dashboard_control_callbacks; 222 223 $screen = get_current_screen(); 224 225 $private_callback_args = array( '__widget_basename' => $widget_name ); 226 227 if ( is_null( $callback_args ) ) { 228 $callback_args = $private_callback_args; 229 } elseif ( is_array( $callback_args ) ) { 230 $callback_args = array_merge( $callback_args, $private_callback_args ); 231 } 232 233 if ( $control_callback && is_callable( $control_callback ) && current_user_can( 'edit_dashboard' ) ) { 234 $wp_dashboard_control_callbacks[ $widget_id ] = $control_callback; 235 236 if ( isset( $_GET['edit'] ) && $widget_id === $_GET['edit'] ) { 237 list($url) = explode( '#', add_query_arg( 'edit', false ), 2 ); 238 $widget_name .= ' <span class="postbox-title-action"><a href="' . esc_url( $url ) . '">' . __( 'Cancel' ) . '</a></span>'; 239 $callback = '_wp_dashboard_control_callback'; 240 } else { 241 list($url) = explode( '#', add_query_arg( 'edit', $widget_id ), 2 ); 242 $widget_name .= ' <span class="postbox-title-action"><a href="' . esc_url( "$url#$widget_id" ) . '" class="edit-box open-box">' . __( 'Configure' ) . '</a></span>'; 243 } 244 } 245 246 $side_widgets = array( 'dashboard_quick_press', 'dashboard_primary' ); 247 248 if ( in_array( $widget_id, $side_widgets, true ) ) { 249 $context = 'side'; 250 } 251 252 $high_priority_widgets = array( 'dashboard_browser_nag', 'dashboard_php_nag' ); 253 254 if ( in_array( $widget_id, $high_priority_widgets, true ) ) { 255 $priority = 'high'; 256 } 257 258 if ( empty( $context ) ) { 259 $context = 'normal'; 260 } 261 262 if ( empty( $priority ) ) { 263 $priority = 'core'; 264 } 265 266 add_meta_box( $widget_id, $widget_name, $callback, $screen, $context, $priority, $callback_args ); 267 } 268 269 /** 270 * Outputs controls for the current dashboard widget. 271 * 272 * @access private 273 * @since 2.7.0 274 * 275 * @param mixed $dashboard 276 * @param array $meta_box 277 */ 278 function _wp_dashboard_control_callback( $dashboard, $meta_box ) { 279 echo '<form method="post" class="dashboard-widget-control-form wp-clearfix">'; 280 wp_dashboard_trigger_widget_control( $meta_box['id'] ); 281 wp_nonce_field( 'edit-dashboard-widget_' . $meta_box['id'], 'dashboard-widget-nonce' ); 282 echo '<input type="hidden" name="widget_id" value="' . esc_attr( $meta_box['id'] ) . '" />'; 283 submit_button( __( 'Save Changes' ) ); 284 echo '</form>'; 285 } 286 287 /** 288 * Displays the dashboard. 289 * 290 * @since 2.5.0 291 */ 292 function wp_dashboard() { 293 $screen = get_current_screen(); 294 $columns = absint( $screen->get_columns() ); 295 $columns_css = ''; 296 297 if ( $columns ) { 298 $columns_css = " columns-$columns"; 299 } 300 ?> 301 <div id="dashboard-widgets" class="metabox-holder<?php echo $columns_css; ?>"> 302 <div id="postbox-container-1" class="postbox-container"> 303 <?php do_meta_boxes( $screen->id, 'normal', '' ); ?> 304 </div> 305 <div id="postbox-container-2" class="postbox-container"> 306 <?php do_meta_boxes( $screen->id, 'side', '' ); ?> 307 </div> 308 <div id="postbox-container-3" class="postbox-container"> 309 <?php do_meta_boxes( $screen->id, 'column3', '' ); ?> 310 </div> 311 <div id="postbox-container-4" class="postbox-container"> 312 <?php do_meta_boxes( $screen->id, 'column4', '' ); ?> 313 </div> 314 </div> 315 316 <?php 317 wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false ); 318 wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false ); 319 } 320 321 // 322 // Dashboard Widgets. 323 // 324 325 /** 326 * Dashboard widget that displays some basic stats about the site. 327 * 328 * Formerly 'Right Now'. A streamlined 'At a Glance' as of 3.8. 329 * 330 * @since 2.7.0 331 */ 332 function wp_dashboard_right_now() { 333 ?> 334 <div class="main"> 335 <ul> 336 <?php 337 // Posts and Pages. 338 foreach ( array( 'post', 'page' ) as $post_type ) { 339 $num_posts = wp_count_posts( $post_type ); 340 341 if ( $num_posts && $num_posts->publish ) { 342 if ( 'post' === $post_type ) { 343 /* translators: %s: Number of posts. */ 344 $text = _n( '%s Published post', '%s Published posts', $num_posts->publish ); 345 } else { 346 /* translators: %s: Number of pages. */ 347 $text = _n( '%s Published page', '%s Published pages', $num_posts->publish ); 348 } 349 350 $text = sprintf( $text, number_format_i18n( $num_posts->publish ) ); 351 $post_type_object = get_post_type_object( $post_type ); 352 353 if ( $post_type_object && current_user_can( $post_type_object->cap->edit_posts ) ) { 354 $url = add_query_arg( 355 array( 356 'post_status' => 'publish', 357 'post_type' => $post_type, 358 ), 359 admin_url( 'edit.php' ) 360 ); 361 printf( '<li class="%1$s-count"><a href="%2$s">%3$s</a></li>', $post_type, esc_url( $url ), esc_html( $text ) ); 362 } else { 363 printf( '<li class="%1$s-count"><span>%2$s</span></li>', $post_type, $text ); 364 } 365 } 366 } 367 368 // Comments. 369 $num_comm = wp_count_comments(); 370 371 if ( $num_comm && ( $num_comm->approved || $num_comm->moderated ) ) { 372 /* translators: %s: Number of comments. */ 373 $text = sprintf( _n( '%s Comment', '%s Comments', $num_comm->approved ), number_format_i18n( $num_comm->approved ) ); 374 ?> 375 <li class="comment-count"> 376 <a href="edit-comments.php"><?php echo $text; ?></a> 377 </li> 378 <?php 379 $moderated_comments_count_i18n = number_format_i18n( $num_comm->moderated ); 380 /* translators: %s: Number of comments. */ 381 $text = sprintf( _n( '%s Comment in moderation', '%s Comments in moderation', $num_comm->moderated ), $moderated_comments_count_i18n ); 382 ?> 383 <li class="comment-mod-count<?php echo ! $num_comm->moderated ? ' hidden' : ''; ?>"> 384 <a href="edit-comments.php?comment_status=moderated" class="comments-in-moderation-text"><?php echo $text; ?></a> 385 </li> 386 <?php 387 } 388 389 /** 390 * Filters the array of extra elements to list in the 'At a Glance' 391 * dashboard widget. 392 * 393 * Prior to 3.8.0, the widget was named 'Right Now'. Each element 394 * is wrapped in list-item tags on output. 395 * 396 * @since 3.8.0 397 * 398 * @param string[] $items Array of extra 'At a Glance' widget items. 399 */ 400 $elements = apply_filters( 'dashboard_glance_items', array() ); 401 402 if ( $elements ) { 403 echo '<li>' . implode( "</li>\n<li>", $elements ) . "</li>\n"; 404 } 405 406 ?> 407 </ul> 408 <?php 409 update_right_now_message(); 410 411 // Check if search engines are asked not to index this site. 412 if ( ! is_network_admin() && ! is_user_admin() 413 && current_user_can( 'manage_options' ) && ! get_option( 'blog_public' ) 414 ) { 415 416 /** 417 * Filters the link title attribute for the 'Search engines discouraged' 418 * message displayed in the 'At a Glance' dashboard widget. 419 * 420 * Prior to 3.8.0, the widget was named 'Right Now'. 421 * 422 * @since 3.0.0 423 * @since 4.5.0 The default for `$title` was updated to an empty string. 424 * 425 * @param string $title Default attribute text. 426 */ 427 $title = apply_filters( 'privacy_on_link_title', '' ); 428 429 /** 430 * Filters the link label for the 'Search engines discouraged' message 431 * displayed in the 'At a Glance' dashboard widget. 432 * 433 * Prior to 3.8.0, the widget was named 'Right Now'. 434 * 435 * @since 3.0.0 436 * 437 * @param string $content Default text. 438 */ 439 $content = apply_filters( 'privacy_on_link_text', __( 'Search engines discouraged' ) ); 440 441 $title_attr = '' === $title ? '' : " title='$title'"; 442 443 echo "<p class='search-engines-info'><a href='options-reading.php'$title_attr>$content</a></p>"; 444 } 445 ?> 446 </div> 447 <?php 448 /* 449 * activity_box_end has a core action, but only prints content when multisite. 450 * Using an output buffer is the only way to really check if anything's displayed here. 451 */ 452 ob_start(); 453 454 /** 455 * Fires at the end of the 'At a Glance' dashboard widget. 456 * 457 * Prior to 3.8.0, the widget was named 'Right Now'. 458 * 459 * @since 2.5.0 460 */ 461 do_action( 'rightnow_end' ); 462 463 /** 464 * Fires at the end of the 'At a Glance' dashboard widget. 465 * 466 * Prior to 3.8.0, the widget was named 'Right Now'. 467 * 468 * @since 2.0.0 469 */ 470 do_action( 'activity_box_end' ); 471 472 $actions = ob_get_clean(); 473 474 if ( ! empty( $actions ) ) : 475 ?> 476 <div class="sub"> 477 <?php echo $actions; ?> 478 </div> 479 <?php 480 endif; 481 } 482 483 /** 484 * @since 3.1.0 485 */ 486 function wp_network_dashboard_right_now() { 487 $actions = array(); 488 489 if ( current_user_can( 'create_sites' ) ) { 490 $actions['create-site'] = '<a href="' . network_admin_url( 'site-new.php' ) . '">' . __( 'Create a New Site' ) . '</a>'; 491 } 492 if ( current_user_can( 'create_users' ) ) { 493 $actions['create-user'] = '<a href="' . network_admin_url( 'user-new.php' ) . '">' . __( 'Create a New User' ) . '</a>'; 494 } 495 496 $c_users = get_user_count(); 497 $c_blogs = get_blog_count(); 498 499 /* translators: %s: Number of users on the network. */ 500 $user_text = sprintf( _n( '%s user', '%s users', $c_users ), number_format_i18n( $c_users ) ); 501 /* translators: %s: Number of sites on the network. */ 502 $blog_text = sprintf( _n( '%s site', '%s sites', $c_blogs ), number_format_i18n( $c_blogs ) ); 503 504 /* translators: 1: Text indicating the number of sites on the network, 2: Text indicating the number of users on the network. */ 505 $sentence = sprintf( __( 'You have %1$s and %2$s.' ), $blog_text, $user_text ); 506 507 if ( $actions ) { 508 echo '<ul class="subsubsub">'; 509 foreach ( $actions as $class => $action ) { 510 $actions[ $class ] = "\t<li class='$class'>$action"; 511 } 512 echo implode( " |</li>\n", $actions ) . "</li>\n"; 513 echo '</ul>'; 514 } 515 ?> 516 <br class="clear" /> 517 518 <p class="youhave"><?php echo $sentence; ?></p> 519 520 521 <?php 522 /** 523 * Fires in the Network Admin 'Right Now' dashboard widget 524 * just before the user and site search form fields. 525 * 526 * @since MU (3.0.0) 527 */ 528 do_action( 'wpmuadminresult' ); 529 ?> 530 531 <form action="<?php echo esc_url( network_admin_url( 'users.php' ) ); ?>" method="get"> 532 <p> 533 <label class="screen-reader-text" for="search-users"> 534 <?php 535 /* translators: Hidden accessibility text. */ 536 _e( 'Search Users' ); 537 ?> 538 </label> 539 <input type="search" name="s" value="" size="30" autocomplete="off" id="search-users" /> 540 <?php submit_button( __( 'Search Users' ), '', false, false, array( 'id' => 'submit_users' ) ); ?> 541 </p> 542 </form> 543 544 <form action="<?php echo esc_url( network_admin_url( 'sites.php' ) ); ?>" method="get"> 545 <p> 546 <label class="screen-reader-text" for="search-sites"> 547 <?php 548 /* translators: Hidden accessibility text. */ 549 _e( 'Search Sites' ); 550 ?> 551 </label> 552 <input type="search" name="s" value="" size="30" autocomplete="off" id="search-sites" /> 553 <?php submit_button( __( 'Search Sites' ), '', false, false, array( 'id' => 'submit_sites' ) ); ?> 554 </p> 555 </form> 556 <?php 557 /** 558 * Fires at the end of the 'Right Now' widget in the Network Admin dashboard. 559 * 560 * @since MU (3.0.0) 561 */ 562 do_action( 'mu_rightnow_end' ); 563 564 /** 565 * Fires at the end of the 'Right Now' widget in the Network Admin dashboard. 566 * 567 * @since MU (3.0.0) 568 */ 569 do_action( 'mu_activity_box_end' ); 570 } 571 572 /** 573 * Displays the Quick Draft widget. 574 * 575 * @since 3.8.0 576 * @since 7.1.0 Added $notice_type parameter. 577 * 578 * @global int $post_ID 579 * 580 * @param string|false $message Optional. Error or success message. Default false. 581 * @param string $notice_type Optional. Admin notice type. Default 'error'. 582 */ 583 function wp_dashboard_quick_press( $message = false, $notice_type = 'error' ) { 584 global $post_ID; 585 586 if ( ! current_user_can( 'edit_posts' ) ) { 587 return; 588 } 589 590 // Check if a new auto-draft (= no new post_ID) is needed or if the old can be used. 591 $last_post_id = (int) get_user_option( 'dashboard_quick_press_last_post_id' ); // Get the last post_ID. 592 593 if ( $last_post_id ) { 594 $post = get_post( $last_post_id ); 595 596 if ( empty( $post ) || 'auto-draft' !== $post->post_status ) { // auto-draft doesn't exist anymore. 597 $post = get_default_post_to_edit( 'post', true ); 598 update_user_option( get_current_user_id(), 'dashboard_quick_press_last_post_id', (int) $post->ID ); // Save post_ID. 599 } else { 600 $post->post_title = ''; // Remove the auto draft title. 601 } 602 } else { 603 $post = get_default_post_to_edit( 'post', true ); 604 $user_id = get_current_user_id(); 605 $user_blogs = get_blogs_of_user( $user_id ); 606 $current_blog_id = get_current_blog_id(); 607 608 // Don't create an option if this is a super admin who does not belong to this site. 609 if ( isset( $user_blogs[ $current_blog_id ] ) ) { 610 update_user_option( $user_id, 'dashboard_quick_press_last_post_id', (int) $post->ID ); // Save post_ID. 611 } 612 } 613 614 $post_ID = (int) $post->ID; 615 ?> 616 617 <form name="post" action="<?php echo esc_url( admin_url( 'post.php' ) ); ?>" method="post" id="quick-press" class="hide-if-no-js"> 618 619 <?php 620 if ( $message ) { 621 wp_admin_notice( 622 $message, 623 array( 624 'type' => $notice_type, 625 'attributes' => array( 626 'role' => 'alert', 627 ), 628 ) 629 ); 630 } 631 ?> 632 633 <div class="input-text-wrap" id="title-wrap"> 634 <label for="title"> 635 <?php 636 /** This filter is documented in wp-admin/edit-form-advanced.php */ 637 echo apply_filters( 'enter_title_here', __( 'Title' ), $post ); 638 ?> 639 </label> 640 <input type="text" name="post_title" id="title" autocomplete="off" /> 641 </div> 642 643 <div class="textarea-wrap" id="description-wrap"> 644 <label for="content"><?php _e( 'Content' ); ?></label> 645 <textarea name="content" id="content" placeholder="<?php esc_attr_e( 'What’s on your mind?' ); ?>" class="mceEditor" rows="3" cols="15" autocomplete="off"></textarea> 646 </div> 647 648 <p class="submit"> 649 <input type="hidden" name="action" id="quickpost-action" value="post-quickdraft-save" /> 650 <input type="hidden" name="post_ID" value="<?php echo $post_ID; ?>" /> 651 <input type="hidden" name="post_type" value="post" /> 652 <?php wp_nonce_field( 'add-post' ); ?> 653 <?php submit_button( __( 'Save Draft' ), 'primary', 'save', false, array( 'id' => 'save-post' ) ); ?> 654 <br class="clear" /> 655 </p> 656 657 </form> 658 <?php 659 wp_dashboard_recent_drafts(); 660 } 661 662 /** 663 * Show recent drafts of the user on the dashboard. 664 * 665 * @since 2.7.0 666 * 667 * @param WP_Post[]|false $drafts Optional. Array of posts to display. Default false. 668 */ 669 function wp_dashboard_recent_drafts( $drafts = false ) { 670 if ( ! $drafts ) { 671 $query_args = array( 672 'post_type' => 'post', 673 'post_status' => 'draft', 674 'author' => get_current_user_id(), 675 'posts_per_page' => 4, 676 'orderby' => 'modified', 677 'order' => 'DESC', 678 ); 679 680 /** 681 * Filters the post query arguments for the 'Recent Drafts' dashboard widget. 682 * 683 * @since 4.4.0 684 * 685 * @param array $query_args The query arguments for the 'Recent Drafts' dashboard widget. 686 */ 687 $query_args = apply_filters( 'dashboard_recent_drafts_query_args', $query_args ); 688 689 $drafts = get_posts( $query_args ); 690 if ( ! $drafts ) { 691 echo '<div class="no-drafts">'; 692 echo '<p>' . __( 'There are no recent drafts.' ) . '</p>'; 693 echo '</div>'; 694 return; 695 } 696 } 697 698 echo '<div class="drafts">'; 699 700 if ( count( $drafts ) > 3 ) { 701 printf( 702 '<p class="view-all"><a href="%s">%s</a></p>' . "\n", 703 esc_url( admin_url( 'edit.php?post_status=draft' ) ), 704 __( 'View all drafts' ) 705 ); 706 } 707 708 echo '<h2 class="hide-if-no-js">' . __( 'Your Recent Drafts' ) . "</h2>\n"; 709 echo '<ul>'; 710 711 /* translators: Maximum number of words used in a preview of a draft on the dashboard. */ 712 $draft_length = (int) _x( '10', 'draft_length' ); 713 714 $drafts = array_slice( $drafts, 0, 3 ); 715 foreach ( $drafts as $draft ) { 716 $url = get_edit_post_link( $draft->ID ); 717 $title = _draft_or_post_title( $draft->ID ); 718 719 echo "<li>\n"; 720 printf( 721 '<div class="draft-title"><a href="%s" aria-label="%s">%s</a><time datetime="%s">%s</time></div>', 722 esc_url( $url ), 723 /* translators: %s: Post title. */ 724 esc_attr( sprintf( __( 'Edit “%s”' ), $title ) ), 725 esc_html( $title ), 726 get_the_time( 'c', $draft ), 727 get_the_time( __( 'F j, Y' ), $draft ) 728 ); 729 730 $the_content = wp_trim_words( $draft->post_content, $draft_length ); 731 732 if ( $the_content ) { 733 echo '<p class="draft-content">' . $the_content . '</p>'; 734 } 735 echo "</li>\n"; 736 } 737 738 echo "</ul>\n"; 739 echo '</div>'; 740 } 741 742 /** 743 * Outputs a row for the Recent Comments widget. 744 * 745 * @access private 746 * @since 2.7.0 747 * 748 * @global WP_Comment $comment Global comment object. 749 * 750 * @param WP_Comment $comment The current comment. 751 * @param bool $show_date Optional. Whether to display the date. 752 */ 753 function _wp_dashboard_recent_comments_row( &$comment, $show_date = true ) { 754 $GLOBALS['comment'] = clone $comment; 755 756 if ( $comment->comment_post_ID > 0 ) { 757 $comment_post_title = _draft_or_post_title( $comment->comment_post_ID ); 758 $comment_post_url = get_the_permalink( $comment->comment_post_ID ); 759 $comment_post_link = '<a href="' . esc_url( $comment_post_url ) . '">' . $comment_post_title . '</a>'; 760 } else { 761 $comment_post_link = ''; 762 } 763 764 $actions_string = ''; 765 if ( current_user_can( 'edit_comment', $comment->comment_ID ) ) { 766 // Pre-order it: Approve | Reply | Edit | Spam | Trash. 767 $actions = array( 768 'approve' => '', 769 'unapprove' => '', 770 'reply' => '', 771 'edit' => '', 772 'spam' => '', 773 'trash' => '', 774 'delete' => '', 775 'view' => '', 776 ); 777 778 $approve_nonce = esc_html( '_wpnonce=' . wp_create_nonce( 'approve-comment_' . $comment->comment_ID ) ); 779 $del_nonce = esc_html( '_wpnonce=' . wp_create_nonce( 'delete-comment_' . $comment->comment_ID ) ); 780 781 $action_string = 'comment.php?action=%s&p=' . $comment->comment_post_ID . '&c=' . $comment->comment_ID . '&%s'; 782 783 $approve_url = sprintf( $action_string, 'approvecomment', $approve_nonce ); 784 $unapprove_url = sprintf( $action_string, 'unapprovecomment', $approve_nonce ); 785 $spam_url = sprintf( $action_string, 'spamcomment', $del_nonce ); 786 $trash_url = sprintf( $action_string, 'trashcomment', $del_nonce ); 787 $delete_url = sprintf( $action_string, 'deletecomment', $del_nonce ); 788 789 $actions['approve'] = sprintf( 790 '<a href="%s" data-wp-lists="%s" class="vim-a aria-button-if-js" aria-label="%s">%s</a>', 791 esc_url( $approve_url ), 792 "dim:the-comment-list:comment-{$comment->comment_ID}:unapproved:e7e7d3:e7e7d3:new=approved", 793 esc_attr__( 'Approve this comment' ), 794 __( 'Approve' ) 795 ); 796 797 $actions['unapprove'] = sprintf( 798 '<a href="%s" data-wp-lists="%s" class="vim-u aria-button-if-js" aria-label="%s">%s</a>', 799 esc_url( $unapprove_url ), 800 "dim:the-comment-list:comment-{$comment->comment_ID}:unapproved:e7e7d3:e7e7d3:new=unapproved", 801 esc_attr__( 'Unapprove this comment' ), 802 __( 'Unapprove' ) 803 ); 804 805 $actions['edit'] = sprintf( 806 '<a href="%s" aria-label="%s">%s</a>', 807 "comment.php?action=editcomment&c={$comment->comment_ID}", 808 esc_attr__( 'Edit this comment' ), 809 __( 'Edit' ) 810 ); 811 812 $actions['reply'] = sprintf( 813 '<button type="button" onclick="window.commentReply && commentReply.open(\'%s\',\'%s\');" class="vim-r button-link hide-if-no-js" aria-label="%s">%s</button>', 814 $comment->comment_ID, 815 $comment->comment_post_ID, 816 esc_attr__( 'Reply to this comment' ), 817 /* translators: Comment reply button text. */ 818 _x( 'Reply', 'verb' ) 819 ); 820 821 $actions['spam'] = sprintf( 822 '<a href="%s" data-wp-lists="%s" class="vim-s vim-destructive aria-button-if-js" aria-label="%s">%s</a>', 823 esc_url( $spam_url ), 824 "delete:the-comment-list:comment-{$comment->comment_ID}::spam=1", 825 esc_attr__( 'Mark this comment as spam' ), 826 /* translators: "Mark as spam" link. */ 827 _x( 'Spam', 'verb' ) 828 ); 829 830 if ( ! EMPTY_TRASH_DAYS ) { 831 $actions['delete'] = sprintf( 832 '<a href="%s" data-wp-lists="%s" class="delete vim-d vim-destructive aria-button-if-js" aria-label="%s">%s</a>', 833 esc_url( $delete_url ), 834 "delete:the-comment-list:comment-{$comment->comment_ID}::trash=1", 835 esc_attr__( 'Delete this comment permanently' ), 836 __( 'Delete Permanently' ) 837 ); 838 } else { 839 $actions['trash'] = sprintf( 840 '<a href="%s" data-wp-lists="%s" class="delete vim-d vim-destructive aria-button-if-js" aria-label="%s">%s</a>', 841 esc_url( $trash_url ), 842 "delete:the-comment-list:comment-{$comment->comment_ID}::trash=1", 843 esc_attr__( 'Move this comment to the Trash' ), 844 _x( 'Trash', 'verb' ) 845 ); 846 } 847 848 $actions['view'] = sprintf( 849 '<a class="comment-link" href="%s" aria-label="%s">%s</a>', 850 esc_url( get_comment_link( $comment ) ), 851 esc_attr__( 'View this comment' ), 852 __( 'View' ) 853 ); 854 855 /** This filter is documented in wp-admin/includes/class-wp-comments-list-table.php */ 856 $actions = apply_filters( 'comment_row_actions', array_filter( $actions ), $comment ); 857 858 $i = 0; 859 860 foreach ( $actions as $action => $link ) { 861 ++$i; 862 863 if ( ( ( 'approve' === $action || 'unapprove' === $action ) && 2 === $i ) 864 || 1 === $i 865 ) { 866 $separator = ''; 867 } else { 868 $separator = ' | '; 869 } 870 871 // Reply and quickedit need a hide-if-no-js span. 872 if ( 'reply' === $action || 'quickedit' === $action ) { 873 $action .= ' hide-if-no-js'; 874 } 875 876 if ( 'view' === $action && '1' !== $comment->comment_approved ) { 877 $action .= ' hidden'; 878 } 879 880 $actions_string .= "<span class='$action'>{$separator}{$link}</span>"; 881 } 882 } 883 ?> 884 885 <li id="comment-<?php echo $comment->comment_ID; ?>" <?php comment_class( array( 'comment-item', wp_get_comment_status( $comment ) ), $comment ); ?>> 886 887 <?php 888 $comment_row_class = ''; 889 890 if ( get_option( 'show_avatars' ) ) { 891 echo get_avatar( $comment, 50, 'mystery' ); 892 $comment_row_class .= ' has-avatar'; 893 } 894 ?> 895 896 <?php if ( ! $comment->comment_type || 'comment' === $comment->comment_type ) : ?> 897 898 <div class="dashboard-comment-wrap has-row-actions <?php echo $comment_row_class; ?>"> 899 <p class="comment-meta"> 900 <?php 901 // Comments might not have a post they relate to, e.g. programmatically created ones. 902 if ( $comment_post_link ) { 903 printf( 904 /* translators: 1: Comment author, 2: Post link, 3: Notification if the comment is pending. */ 905 __( 'From %1$s on %2$s %3$s' ), 906 '<cite class="comment-author">' . get_comment_author_link( $comment ) . '</cite>', 907 $comment_post_link, 908 '<span class="approve">' . __( '[Pending]' ) . '</span>' 909 ); 910 } else { 911 printf( 912 /* translators: 1: Comment author, 2: Notification if the comment is pending. */ 913 __( 'From %1$s %2$s' ), 914 '<cite class="comment-author">' . get_comment_author_link( $comment ) . '</cite>', 915 '<span class="approve">' . __( '[Pending]' ) . '</span>' 916 ); 917 } 918 ?> 919 </p> 920 921 <?php 922 else : 923 switch ( $comment->comment_type ) { 924 case 'pingback': 925 $type = __( 'Pingback' ); 926 break; 927 case 'trackback': 928 $type = __( 'Trackback' ); 929 break; 930 default: 931 $type = ucwords( $comment->comment_type ); 932 } 933 $type = esc_html( $type ); 934 ?> 935 <div class="dashboard-comment-wrap has-row-actions"> 936 <p class="comment-meta"> 937 <?php 938 // Pingbacks, Trackbacks or custom comment types might not have a post they relate to, e.g. programmatically created ones. 939 if ( $comment_post_link ) { 940 printf( 941 /* translators: 1: Type of comment, 2: Post link, 3: Notification if the comment is pending. */ 942 _x( '%1$s on %2$s %3$s', 'dashboard' ), 943 "<strong>$type</strong>", 944 $comment_post_link, 945 '<span class="approve">' . __( '[Pending]' ) . '</span>' 946 ); 947 } else { 948 printf( 949 /* translators: 1: Type of comment, 2: Notification if the comment is pending. */ 950 _x( '%1$s %2$s', 'dashboard' ), 951 "<strong>$type</strong>", 952 '<span class="approve">' . __( '[Pending]' ) . '</span>' 953 ); 954 } 955 ?> 956 </p> 957 <p class="comment-author"><?php comment_author_link( $comment ); ?></p> 958 959 <?php endif; // comment_type ?> 960 <blockquote><p><?php comment_excerpt( $comment ); ?></p></blockquote> 961 <?php if ( $actions_string ) : ?> 962 <p class="row-actions"><?php echo $actions_string; ?></p> 963 <?php endif; ?> 964 </div> 965 </li> 966 <?php 967 $GLOBALS['comment'] = null; 968 } 969 970 /** 971 * Outputs the Activity widget. 972 * 973 * Callback function for {@see 'dashboard_activity'}. 974 * 975 * @since 3.8.0 976 * @since 7.2.0 Added the On This Day section and moved Recent Comments to a dedicated widget. 977 */ 978 function wp_dashboard_site_activity() { 979 980 echo '<div id="activity-widget">'; 981 982 $on_this_day_posts = wp_dashboard_on_this_day_get_posts(); 983 $future_posts = wp_dashboard_recent_posts( 984 array( 985 'max' => 5, 986 'status' => 'future', 987 'order' => 'ASC', 988 'title' => __( 'Publishing Soon' ), 989 'id' => 'future-posts', 990 ) 991 ); 992 $recent_posts = wp_dashboard_recent_posts( 993 array( 994 'max' => 5, 995 'status' => 'publish', 996 'order' => 'DESC', 997 'title' => __( 'Recently Published' ), 998 'id' => 'published-posts', 999 'exclude' => wp_list_pluck( $on_this_day_posts, 'ID' ), 1000 ) 1001 ); 1002 1003 $on_this_day_rendered = wp_dashboard_on_this_day( $on_this_day_posts ); 1004 1005 if ( ! $future_posts && ! $recent_posts && ! $on_this_day_rendered ) { 1006 echo '<div class="no-activity">'; 1007 echo '<p>' . __( 'No activity yet!' ) . '</p>'; 1008 echo '</div>'; 1009 } 1010 1011 echo '</div>'; 1012 } 1013 1014 /** 1015 * Outputs the On This Day section of the Activity widget. 1016 * 1017 * @since 7.2.0 1018 * 1019 * @param WP_Post[]|null $posts Optional. Posts to render. Defaults to querying for posts. 1020 * @return bool False if no posts were found. True otherwise. 1021 */ 1022 function wp_dashboard_on_this_day( $posts = null ) { 1023 if ( null === $posts ) { 1024 $posts = wp_dashboard_on_this_day_get_posts(); 1025 } 1026 1027 if ( empty( $posts ) ) { 1028 return false; 1029 } 1030 1031 echo '<div id="on-this-day-posts" class="activity-block">'; 1032 echo '<h3>' . esc_html__( 'Published On This Day' ) . '</h3>'; 1033 echo '<ul>'; 1034 1035 foreach ( $posts as $post ) { 1036 /* translators: Date format for posts published in a previous year. */ 1037 $date = get_the_date( __( 'M jS, Y' ), $post ); 1038 1039 $title = get_the_title( $post ); 1040 1041 if ( '' === trim( $title ) ) { 1042 $title = __( '(no title)' ); 1043 1044 if ( current_user_can( 'read_post', $post->ID ) && ! post_password_required( $post ) ) { 1045 $excerpt = get_the_excerpt( $post ); 1046 1047 if ( is_string( $excerpt ) && '' !== $excerpt ) { 1048 $title .= ' ' . wp_trim_words( $excerpt, 15 ); 1049 } 1050 } 1051 } 1052 1053 $can_edit = current_user_can( 'edit_post', $post->ID ); 1054 $link = $can_edit ? get_edit_post_link( $post->ID ) : get_permalink( $post ); 1055 1056 if ( $can_edit ) { 1057 /* translators: %s: Post title. */ 1058 $aria_label = sprintf( __( 'Edit “%s”' ), $title ); 1059 } else { 1060 /* translators: %s: Post title. */ 1061 $aria_label = sprintf( __( 'View “%s”' ), $title ); 1062 } 1063 1064 $author_id = (int) $post->post_author; 1065 $author_name = $author_id > 0 ? (string) get_the_author_meta( 'display_name', $author_id ) : ''; 1066 1067 printf( 1068 '<li><span>%1$s</span><span><a href="%2$s" aria-label="%3$s">%4$s</a>', 1069 esc_html( $date ), 1070 esc_url( $link ), 1071 esc_attr( $aria_label ), 1072 esc_html( $title ) 1073 ); 1074 1075 if ( '' !== trim( $author_name ) && get_current_user_id() !== $author_id ) { 1076 printf( 1077 ' <span class="wp-on-this-day-post-author">%s</span>', 1078 esc_html( 1079 sprintf( 1080 /* translators: %s: Post author's display name. */ 1081 __( 'by %s' ), 1082 $author_name 1083 ) 1084 ) 1085 ); 1086 } 1087 1088 echo '</span></li>'; 1089 } 1090 1091 echo '</ul>'; 1092 echo '</div>'; 1093 1094 return true; 1095 } 1096 1097 /** 1098 * Retrieves posts published on this calendar day in previous years. 1099 * 1100 * Up to ten posts are returned by default. Use the 1101 * {@see 'wp_dashboard_on_this_day_query_args'} filter to change the limit. 1102 * Results are cached by WP_Query's native query caching. 1103 * 1104 * @since 7.2.0 1105 * 1106 * @return WP_Post[] Array of posts ordered by newest first. 1107 */ 1108 function wp_dashboard_on_this_day_get_posts() { 1109 $today = current_datetime(); 1110 $year = (int) $today->format( 'Y' ); 1111 1112 $args = array( 1113 'post_type' => 'post', 1114 'post_status' => array( 'publish' ), 1115 'posts_per_page' => 10, 1116 'ignore_sticky_posts' => true, 1117 'orderby' => 'date', 1118 'order' => 'DESC', 1119 'no_found_rows' => true, 1120 'update_post_term_cache' => false, 1121 'update_post_meta_cache' => false, 1122 'date_query' => array( 1123 'relation' => 'AND', 1124 array( 1125 'before' => array( 'year' => $year ), 1126 ), 1127 _wp_dashboard_on_this_day_date_query_clause( $today ), 1128 ), 1129 ); 1130 1131 /** 1132 * Filters the arguments used to query posts for the On This Day section. 1133 * 1134 * @since 7.2.0 1135 * 1136 * @param array $args WP_Query arguments. 1137 */ 1138 $args = apply_filters( 'wp_dashboard_on_this_day_query_args', $args ); 1139 1140 $query = new WP_Query( $args ); 1141 1142 return $query->posts; 1143 } 1144 1145 /** 1146 * Builds the date query clause for today's anniversary date. 1147 * 1148 * On February 28 in a non-leap year, February 29 posts are included so 1149 * leap-day anniversaries still appear. 1150 * 1151 * @since 7.2.0 1152 * @access private 1153 * 1154 * @param DateTimeInterface $date Date to build the clause for. 1155 * @return array Date query clause. 1156 */ 1157 function _wp_dashboard_on_this_day_date_query_clause( $date ) { 1158 $month = (int) $date->format( 'm' ); 1159 $day = (int) $date->format( 'd' ); 1160 $clause = array( 1161 'month' => $month, 1162 'day' => $day, 1163 ); 1164 1165 // Display leap day posts on February 28 in non-leap years. 1166 if ( 1167 28 === $day 1168 && 2 === $month 1169 && false === (bool) $date->format( 'L' ) 1170 ) { 1171 $clause = array( 1172 'relation' => 'OR', 1173 $clause, 1174 array( 1175 'month' => 2, 1176 'day' => 29, 1177 ), 1178 ); 1179 } 1180 1181 return $clause; 1182 } 1183 1184 /** 1185 * Generates Publishing Soon and Recently Published sections. 1186 * 1187 * @since 3.8.0 1188 * 1189 * @param array $args { 1190 * An array of query and display arguments. 1191 * 1192 * @type int $max Number of posts to display. 1193 * @type string $status Post status. 1194 * @type string $order Designates ascending ('ASC') or descending ('DESC') order. 1195 * @type string $title Section title. 1196 * @type string $id The container id. 1197 * @type int[] $exclude Optional. Post IDs to exclude. 1198 * } 1199 * @return bool False if no posts were found. True otherwise. 1200 */ 1201 function wp_dashboard_recent_posts( $args ) { 1202 $query_args = array( 1203 'post_type' => 'post', 1204 'post_status' => $args['status'], 1205 'orderby' => 'date', 1206 'order' => $args['order'], 1207 'posts_per_page' => (int) $args['max'], 1208 'no_found_rows' => true, 1209 'cache_results' => true, 1210 'perm' => ( 'future' === $args['status'] ) ? 'editable' : 'readable', 1211 ); 1212 1213 if ( ! empty( $args['exclude'] ) ) { 1214 $query_args['post__not_in'] = array_map( 'intval', $args['exclude'] ); 1215 } 1216 1217 /** 1218 * Filters the query arguments used for the Recent Posts widget. 1219 * 1220 * @since 4.2.0 1221 * 1222 * @param array $query_args The arguments passed to WP_Query to produce the list of posts. 1223 */ 1224 $query_args = apply_filters( 'dashboard_recent_posts_query_args', $query_args ); 1225 1226 $posts = new WP_Query( $query_args ); 1227 1228 if ( $posts->have_posts() ) { 1229 1230 echo '<div id="' . $args['id'] . '" class="activity-block">'; 1231 1232 echo '<h3>' . $args['title'] . '</h3>'; 1233 1234 echo '<ul>'; 1235 1236 $today = current_time( 'Y-m-d' ); 1237 $tomorrow = current_datetime()->modify( '+1 day' )->format( 'Y-m-d' ); 1238 $year = current_time( 'Y' ); 1239 1240 while ( $posts->have_posts() ) { 1241 $posts->the_post(); 1242 1243 $time = get_the_time( 'U' ); 1244 1245 if ( ! is_int( $time ) ) { 1246 /* translators: Date and time format for recent posts on the dashboard, from a different calendar year, see https://www.php.net/manual/datetime.format.php */ 1247 $date = get_the_date( __( 'M jS Y' ) ); 1248 } elseif ( gmdate( 'Y-m-d', $time ) === $today ) { 1249 $date = __( 'Today' ); 1250 } elseif ( gmdate( 'Y-m-d', $time ) === $tomorrow ) { 1251 $date = __( 'Tomorrow' ); 1252 } elseif ( gmdate( 'Y', $time ) !== $year ) { 1253 /* translators: Date and time format for recent posts on the dashboard, from a different calendar year, see https://www.php.net/manual/datetime.format.php */ 1254 $date = date_i18n( __( 'M jS Y' ), $time ); 1255 } else { 1256 /* translators: Date and time format for recent posts on the dashboard, see https://www.php.net/manual/datetime.format.php */ 1257 $date = date_i18n( __( 'M jS' ), $time ); 1258 } 1259 1260 // Use the post edit link for those who can edit, the permalink otherwise. 1261 $recent_post_link = current_user_can( 'edit_post', get_the_ID() ) ? get_edit_post_link() : get_permalink(); 1262 1263 $draft_or_post_title = _draft_or_post_title(); 1264 printf( 1265 '<li><span>%1$s</span> <a href="%2$s" aria-label="%3$s">%4$s</a></li>', 1266 /* translators: 1: Relative date, 2: Time. */ 1267 sprintf( _x( '%1$s, %2$s', 'dashboard' ), $date, get_the_time() ), 1268 $recent_post_link, 1269 /* translators: %s: Post title. */ 1270 esc_attr( sprintf( __( 'Edit “%s”' ), $draft_or_post_title ) ), 1271 $draft_or_post_title 1272 ); 1273 } 1274 1275 echo '</ul>'; 1276 echo '</div>'; 1277 1278 } else { 1279 return false; 1280 } 1281 1282 wp_reset_postdata(); 1283 1284 return true; 1285 } 1286 1287 /** 1288 * Outputs the Recent Comments dashboard widget. 1289 * 1290 * Callback function for {@see 'dashboard_recent_comments'}. 1291 * 1292 * @since 7.2.0 1293 */ 1294 function wp_dashboard_recent_comments_widget() { 1295 if ( ! wp_dashboard_recent_comments( 5, false ) ) { 1296 echo '<p>' . esc_html__( 'No comments to show.' ) . '</p>'; 1297 } 1298 } 1299 1300 /** 1301 * Outputs recent comments. 1302 * 1303 * @since 3.8.0 1304 * @since 7.2.0 Moved from the Activity widget to a dedicated dashboard widget. 1305 * 1306 * @param int $total_items Optional. Number of comments to query. Default 5. 1307 * @param bool $show_title Optional. Whether to display the section title. Default true. 1308 * @return bool False if no comments were found. True otherwise. 1309 */ 1310 function wp_dashboard_recent_comments( $total_items = 5, $show_title = true ) { 1311 // Select all comment types and filter out spam later for better query performance. 1312 $comments = array(); 1313 1314 $comments_query = array( 1315 'number' => $total_items * 5, 1316 'offset' => 0, 1317 ); 1318 1319 if ( ! current_user_can( 'edit_posts' ) ) { 1320 $comments_query['status'] = 'approve'; 1321 } 1322 1323 $comments_count = 0; 1324 do { 1325 $possible = get_comments( $comments_query ); 1326 1327 if ( empty( $possible ) || ! is_array( $possible ) ) { 1328 break; 1329 } 1330 1331 foreach ( $possible as $comment ) { 1332 if ( ! current_user_can( 'edit_post', $comment->comment_post_ID ) 1333 && ( post_password_required( $comment->comment_post_ID ) 1334 || ! current_user_can( 'read_post', $comment->comment_post_ID ) ) 1335 ) { 1336 // The user has no access to the post and thus cannot see the comments. 1337 continue; 1338 } 1339 1340 $comments[] = $comment; 1341 $comments_count = count( $comments ); 1342 1343 if ( $comments_count === $total_items ) { 1344 break 2; 1345 } 1346 } 1347 1348 $comments_query['offset'] += $comments_query['number']; 1349 $comments_query['number'] = $total_items * 10; 1350 } while ( $comments_count < $total_items ); 1351 1352 if ( $comments ) { 1353 echo '<div id="latest-comments" class="activity-block table-view-list">'; 1354 1355 if ( $show_title ) { 1356 echo '<h3>' . esc_html__( 'Recent Comments' ) . '</h3>'; 1357 } 1358 1359 echo '<ul id="the-comment-list" data-wp-lists="list:comment">'; 1360 foreach ( $comments as $comment ) { 1361 _wp_dashboard_recent_comments_row( $comment ); 1362 } 1363 echo '</ul>'; 1364 1365 if ( current_user_can( 'edit_posts' ) ) { 1366 echo '<h3 class="screen-reader-text">' . 1367 /* translators: Hidden accessibility text. */ 1368 __( 'View more comments' ) . 1369 '</h3>'; 1370 _get_list_table( 'WP_Comments_List_Table' )->views(); 1371 } 1372 1373 wp_comment_reply( -1, false, 'dashboard', false ); 1374 wp_comment_trashnotice(); 1375 1376 echo '</div>'; 1377 } else { 1378 return false; 1379 } 1380 return true; 1381 } 1382 1383 /** 1384 * Display generic dashboard RSS widget feed. 1385 * 1386 * @since 2.5.0 1387 * 1388 * @param string $widget_id 1389 */ 1390 function wp_dashboard_rss_output( $widget_id ) { 1391 $widgets = get_option( 'dashboard_widget_options' ); 1392 echo '<div class="rss-widget">'; 1393 wp_widget_rss_output( $widgets[ $widget_id ] ); 1394 echo '</div>'; 1395 } 1396 1397 /** 1398 * Checks to see if all of the feed url in $check_urls are cached. 1399 * 1400 * If $check_urls is empty, look for the rss feed url found in the dashboard 1401 * widget options of $widget_id. If cached, call $callback, a function that 1402 * echoes out output for this widget. If not cache, echo a "Loading..." stub 1403 * which is later replaced by Ajax call (see top of /wp-admin/index.php) 1404 * 1405 * @since 2.5.0 1406 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter 1407 * by adding it to the function signature. 1408 * 1409 * @param string $widget_id The widget ID. 1410 * @param callable $callback The callback function used to display each feed. 1411 * @param array $check_urls RSS feeds. 1412 * @param mixed ...$args Optional additional parameters to pass to the callback function. 1413 * @return bool True on success, false on failure. 1414 */ 1415 function wp_dashboard_cached_rss_widget( $widget_id, $callback, $check_urls = array(), ...$args ) { 1416 $doing_ajax = wp_doing_ajax(); 1417 $loading = '<p class="widget-loading hide-if-no-js">' . __( 'Loading…' ) . '</p>'; 1418 $loading .= wp_get_admin_notice( 1419 __( 'This widget requires JavaScript.' ), 1420 array( 1421 'type' => 'error', 1422 'additional_classes' => array( 'inline', 'hide-if-js' ), 1423 ) 1424 ); 1425 1426 if ( empty( $check_urls ) ) { 1427 $widgets = get_option( 'dashboard_widget_options' ); 1428 1429 if ( empty( $widgets[ $widget_id ]['url'] ) && ! $doing_ajax ) { 1430 echo $loading; 1431 return false; 1432 } 1433 1434 $check_urls = array( $widgets[ $widget_id ]['url'] ); 1435 } 1436 1437 $locale = get_user_locale(); 1438 $cache_key = 'dash_v2_' . md5( $widget_id . '_' . $locale ); 1439 $output = get_transient( $cache_key ); 1440 1441 if ( false !== $output ) { 1442 echo $output; 1443 return true; 1444 } 1445 1446 if ( ! $doing_ajax ) { 1447 echo $loading; 1448 return false; 1449 } 1450 1451 if ( $callback && is_callable( $callback ) ) { 1452 array_unshift( $args, $widget_id, $check_urls ); 1453 ob_start(); 1454 call_user_func_array( $callback, $args ); 1455 // Default lifetime in cache of 12 hours (same as the feeds). 1456 set_transient( $cache_key, ob_get_flush(), 12 * HOUR_IN_SECONDS ); 1457 } 1458 1459 return true; 1460 } 1461 1462 // 1463 // Dashboard Widgets Controls. 1464 // 1465 1466 /** 1467 * Calls widget control callback. 1468 * 1469 * @since 2.5.0 1470 * 1471 * @global callable[] $wp_dashboard_control_callbacks 1472 * 1473 * @param int|false $widget_control_id Optional. Registered widget ID. Default false. 1474 */ 1475 function wp_dashboard_trigger_widget_control( $widget_control_id = false ) { 1476 global $wp_dashboard_control_callbacks; 1477 1478 if ( is_scalar( $widget_control_id ) && $widget_control_id 1479 && isset( $wp_dashboard_control_callbacks[ $widget_control_id ] ) 1480 && is_callable( $wp_dashboard_control_callbacks[ $widget_control_id ] ) 1481 ) { 1482 call_user_func( 1483 $wp_dashboard_control_callbacks[ $widget_control_id ], 1484 '', 1485 array( 1486 'id' => $widget_control_id, 1487 'callback' => $wp_dashboard_control_callbacks[ $widget_control_id ], 1488 ) 1489 ); 1490 } 1491 } 1492 1493 /** 1494 * Sets up the RSS dashboard widget control and $args to be used as input to wp_widget_rss_form(). 1495 * 1496 * Handles POST data from RSS-type widgets. 1497 * 1498 * @since 2.5.0 1499 * 1500 * @param string $widget_id 1501 * @param array $form_inputs 1502 */ 1503 function wp_dashboard_rss_control( $widget_id, $form_inputs = array() ) { 1504 $widget_options = get_option( 'dashboard_widget_options' ); 1505 1506 if ( ! $widget_options ) { 1507 $widget_options = array(); 1508 } 1509 1510 if ( ! isset( $widget_options[ $widget_id ] ) ) { 1511 $widget_options[ $widget_id ] = array(); 1512 } 1513 1514 $number = 1; // Hack to use wp_widget_rss_form(). 1515 1516 $widget_options[ $widget_id ]['number'] = $number; 1517 1518 if ( 'POST' === $_SERVER['REQUEST_METHOD'] && isset( $_POST['widget-rss'][ $number ] ) ) { 1519 $_POST['widget-rss'][ $number ] = wp_unslash( $_POST['widget-rss'][ $number ] ); 1520 $widget_options[ $widget_id ] = wp_widget_rss_process( $_POST['widget-rss'][ $number ] ); 1521 $widget_options[ $widget_id ]['number'] = $number; 1522 1523 // Title is optional. If blank, fill it if possible. 1524 if ( ! $widget_options[ $widget_id ]['title'] && isset( $_POST['widget-rss'][ $number ]['title'] ) ) { 1525 $rss = fetch_feed( $widget_options[ $widget_id ]['url'] ); 1526 if ( is_wp_error( $rss ) ) { 1527 $widget_options[ $widget_id ]['title'] = htmlentities( __( 'Unknown Feed' ) ); 1528 } else { 1529 $widget_options[ $widget_id ]['title'] = htmlentities( strip_tags( $rss->get_title() ) ); 1530 $rss->__destruct(); 1531 unset( $rss ); 1532 } 1533 } 1534 1535 update_option( 'dashboard_widget_options', $widget_options, false ); 1536 1537 $locale = get_user_locale(); 1538 $cache_key = 'dash_v2_' . md5( $widget_id . '_' . $locale ); 1539 delete_transient( $cache_key ); 1540 } 1541 1542 wp_widget_rss_form( $widget_options[ $widget_id ], $form_inputs ); 1543 } 1544 1545 1546 /** 1547 * Renders the Events and News dashboard widget. 1548 * 1549 * @since 4.8.0 1550 */ 1551 function wp_dashboard_events_news() { 1552 wp_print_community_events_markup(); 1553 1554 ?> 1555 1556 <div class="wordpress-news hide-if-no-js"> 1557 <?php wp_dashboard_primary(); ?> 1558 </div> 1559 1560 <p class="community-events-footer"> 1561 <?php 1562 printf( 1563 '<a href="%1$s" target="_blank">%2$s <span class="screen-reader-text"> %3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>', 1564 'https://make.wordpress.org/community/meetups-landing-page', 1565 __( 'Meetups' ), 1566 /* translators: Hidden accessibility text. */ 1567 __( '(opens in a new tab)' ) 1568 ); 1569 ?> 1570 1571 | 1572 1573 <?php 1574 printf( 1575 '<a href="%1$s" target="_blank">%2$s <span class="screen-reader-text"> %3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>', 1576 'https://central.wordcamp.org/schedule/', 1577 __( 'WordCamps' ), 1578 /* translators: Hidden accessibility text. */ 1579 __( '(opens in a new tab)' ) 1580 ); 1581 ?> 1582 1583 | 1584 1585 <?php 1586 printf( 1587 '<a href="%1$s" target="_blank">%2$s <span class="screen-reader-text"> %3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>', 1588 /* translators: If a Rosetta site exists (e.g. https://es.wordpress.org/news/), then use that. Otherwise, leave untranslated. */ 1589 esc_url( _x( 'https://wordpress.org/news/', 'Events and News dashboard widget' ) ), 1590 __( 'News' ), 1591 /* translators: Hidden accessibility text. */ 1592 __( '(opens in a new tab)' ) 1593 ); 1594 ?> 1595 </p> 1596 1597 <?php 1598 } 1599 1600 /** 1601 * Prints the markup for the Community Events section of the Events and News Dashboard widget. 1602 * 1603 * @since 4.8.0 1604 */ 1605 function wp_print_community_events_markup() { 1606 $community_events_notice = '<p class="hide-if-js">' . __( 'The WordPress Events and News feeds require JavaScript.' ) . '</p>'; 1607 $community_events_notice .= '<p class="community-events-error-occurred" aria-hidden="true">' . __( 'An error occurred. Please try again.' ) . '</p>'; 1608 $community_events_notice .= '<p class="community-events-could-not-locate" aria-hidden="true"></p>'; 1609 1610 wp_admin_notice( 1611 $community_events_notice, 1612 array( 1613 'type' => 'error', 1614 'additional_classes' => array( 'community-events-errors', 'inline', 'hide-if-js' ), 1615 'paragraph_wrap' => false, 1616 ) 1617 ); 1618 1619 /* 1620 * Hide the main element when the page first loads, because the content 1621 * won't be ready until wp.communityEvents.renderEventsTemplate() has run. 1622 */ 1623 ?> 1624 <div id="community-events" class="community-events" aria-hidden="true"> 1625 <div class="activity-block"> 1626 <p> 1627 <span id="community-events-location-message"></span> 1628 1629 <button class="button-link community-events-toggle-location" aria-expanded="false"> 1630 <span class="dashicons dashicons-location" aria-hidden="true"></span> 1631 <span class="community-events-location-edit"><?php _e( 'Select location' ); ?></span> 1632 </button> 1633 </p> 1634 1635 <form class="community-events-form" aria-hidden="true" action="<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>" method="post"> 1636 <label for="community-events-location"> 1637 <?php _e( 'City:' ); ?> 1638 </label> 1639 <?php 1640 /* translators: Replace with a city related to your locale. 1641 * Test that it matches the expected location and has upcoming 1642 * events before including it. If no cities related to your 1643 * locale have events, then use a city related to your locale 1644 * that would be recognizable to most users. Use only the city 1645 * name itself, without any region or country. Use the endonym 1646 * (native locale name) instead of the English name if possible. 1647 */ 1648 ?> 1649 <input id="community-events-location" class="regular-text" type="text" name="community-events-location" placeholder="<?php esc_attr_e( 'Cincinnati' ); ?>" /> 1650 1651 <?php submit_button( __( 'Submit' ), 'secondary', 'community-events-submit', false ); ?> 1652 1653 <button class="community-events-cancel button-link" type="button" aria-expanded="false"> 1654 <?php _e( 'Cancel' ); ?> 1655 </button> 1656 1657 <span class="spinner"></span> 1658 </form> 1659 </div> 1660 1661 <ul class="community-events-results activity-block last"></ul> 1662 </div> 1663 1664 <?php 1665 } 1666 1667 /** 1668 * Renders the events templates for the Event and News widget. 1669 * 1670 * @since 4.8.0 1671 */ 1672 function wp_print_community_events_templates() { 1673 ?> 1674 1675 <script id="tmpl-community-events-attend-event-near" type="text/template"> 1676 <?php 1677 printf( 1678 /* translators: %s: The name of a city. */ 1679 __( 'Attend an upcoming event near %s.' ), 1680 '<strong>{{ data.location.description }}</strong>' 1681 ); 1682 ?> 1683 </script> 1684 1685 <script id="tmpl-community-events-could-not-locate" type="text/template"> 1686 <?php 1687 printf( 1688 /* translators: %s is the name of the city we couldn't locate. 1689 * Replace the examples with cities in your locale, but test 1690 * that they match the expected location before including them. 1691 * Use endonyms (native locale names) whenever possible. 1692 */ 1693 __( '%s could not be located. Please try another nearby city. For example: Kansas City; Springfield; Portland.' ), 1694 '<em>{{data.unknownCity}}</em>' 1695 ); 1696 ?> 1697 </script> 1698 1699 <script id="tmpl-community-events-event-list" type="text/template"> 1700 <# _.each( data.events, function( event ) { #> 1701 <li class="event event-{{ event.type }} wp-clearfix"> 1702 <div class="event-info"> 1703 <div class="dashicons event-icon" aria-hidden="true"></div> 1704 <div class="event-info-inner"> 1705 <a class="event-title" href="{{ event.url }}">{{ event.title }}</a> 1706 <# if ( event.type ) { 1707 const titleCaseEventType = event.type.replace( 1708 /\w\S*/g, 1709 function ( type ) { return type.charAt(0).toUpperCase() + type.substr(1).toLowerCase(); } 1710 ); 1711 #> 1712 {{ 'wordcamp' === event.type ? 'WordCamp' : titleCaseEventType }} 1713 <span class="ce-separator" aria-hidden="true"></span> 1714 <# } #> 1715 <span class="event-city">{{ event.location.location }}</span> 1716 </div> 1717 </div> 1718 1719 <div class="event-date-time"> 1720 <span class="event-date">{{ event.user_formatted_date }}</span> 1721 <# if ( 'meetup' === event.type ) { #> 1722 <span class="event-time"> 1723 {{ event.user_formatted_time }} {{ event.timeZoneAbbreviation }} 1724 </span> 1725 <# } #> 1726 </div> 1727 </li> 1728 <# } ) #> 1729 1730 <# if ( data.events.length <= 2 ) { #> 1731 <li class="event-none"> 1732 <?php 1733 printf( 1734 /* translators: %s: Localized meetup organization documentation URL. */ 1735 __( 'Want more events? <a href="%s">Help organize the next one</a>!' ), 1736 __( 'https://make.wordpress.org/community/organize-event-landing-page/' ) 1737 ); 1738 ?> 1739 </li> 1740 <# } #> 1741 1742 </script> 1743 1744 <script id="tmpl-community-events-no-upcoming-events" type="text/template"> 1745 <li class="event-none"> 1746 <# if ( data.location.description ) { #> 1747 <?php 1748 printf( 1749 /* translators: 1: The city the user searched for, 2: Meetup organization documentation URL. */ 1750 __( 'There are no events scheduled near %1$s at the moment. Would you like to <a href="%2$s">organize a WordPress event</a>?' ), 1751 '{{ data.location.description }}', 1752 __( 'https://make.wordpress.org/community/handbook/meetup-organizer/welcome/' ) 1753 ); 1754 ?> 1755 1756 <# } else { #> 1757 <?php 1758 printf( 1759 /* translators: %s: Meetup organization documentation URL. */ 1760 __( 'There are no events scheduled near you at the moment. Would you like to <a href="%s">organize a WordPress event</a>?' ), 1761 __( 'https://make.wordpress.org/community/handbook/meetup-organizer/welcome/' ) 1762 ); 1763 ?> 1764 <# } #> 1765 </li> 1766 </script> 1767 <?php 1768 } 1769 1770 /** 1771 * 'WordPress Events and News' dashboard widget. 1772 * 1773 * @since 2.7.0 1774 * @since 4.8.0 Removed popular plugins feed. 1775 */ 1776 function wp_dashboard_primary() { 1777 $feeds = array( 1778 'news' => array( 1779 1780 /** 1781 * Filters the primary link URL for the 'WordPress Events and News' dashboard widget. 1782 * 1783 * @since 2.5.0 1784 * 1785 * @param string $link The widget's primary link URL. 1786 */ 1787 'link' => apply_filters( 'dashboard_primary_link', __( 'https://wordpress.org/news/' ) ), 1788 1789 /** 1790 * Filters the primary feed URL for the 'WordPress Events and News' dashboard widget. 1791 * 1792 * @since 2.3.0 1793 * 1794 * @param string $url The widget's primary feed URL. 1795 */ 1796 'url' => apply_filters( 'dashboard_primary_feed', __( 'https://wordpress.org/news/feed/' ) ), 1797 1798 /** 1799 * Filters the primary link title for the 'WordPress Events and News' dashboard widget. 1800 * 1801 * @since 2.3.0 1802 * 1803 * @param string $title Title attribute for the widget's primary link. 1804 */ 1805 'title' => apply_filters( 'dashboard_primary_title', __( 'WordPress Blog' ) ), 1806 'items' => 2, 1807 'show_summary' => 0, 1808 'show_author' => 0, 1809 'show_date' => 0, 1810 ), 1811 'planet' => array( 1812 1813 /** 1814 * Filters the secondary link URL for the 'WordPress Events and News' dashboard widget. 1815 * 1816 * @since 2.3.0 1817 * 1818 * @param string $link The widget's secondary link URL. 1819 */ 1820 'link' => apply_filters( 1821 'dashboard_secondary_link', 1822 /* translators: Link to the Planet website of the locale. */ 1823 __( 'https://planet.wordpress.org/' ) 1824 ), 1825 1826 /** 1827 * Filters the secondary feed URL for the 'WordPress Events and News' dashboard widget. 1828 * 1829 * @since 2.3.0 1830 * 1831 * @param string $url The widget's secondary feed URL. 1832 */ 1833 'url' => apply_filters( 1834 'dashboard_secondary_feed', 1835 /* translators: Link to the Planet feed of the locale. */ 1836 __( 'https://planet.wordpress.org/feed/' ) 1837 ), 1838 1839 /** 1840 * Filters the secondary link title for the 'WordPress Events and News' dashboard widget. 1841 * 1842 * @since 2.3.0 1843 * 1844 * @param string $title Title attribute for the widget's secondary link. 1845 */ 1846 'title' => apply_filters( 'dashboard_secondary_title', __( 'Other WordPress News' ) ), 1847 1848 /** 1849 * Filters the number of secondary link items for the 'WordPress Events and News' dashboard widget. 1850 * 1851 * @since 4.4.0 1852 * 1853 * @param string $items How many items to show in the secondary feed. 1854 */ 1855 'items' => apply_filters( 'dashboard_secondary_items', 3 ), 1856 'show_summary' => 0, 1857 'show_author' => 0, 1858 'show_date' => 0, 1859 ), 1860 ); 1861 1862 wp_dashboard_cached_rss_widget( 'dashboard_primary', 'wp_dashboard_primary_output', $feeds ); 1863 } 1864 1865 /** 1866 * Displays the WordPress events and news feeds. 1867 * 1868 * @since 3.8.0 1869 * @since 4.8.0 Removed popular plugins feed. 1870 * 1871 * @param string $widget_id Widget ID. 1872 * @param array $feeds Array of RSS feeds. 1873 */ 1874 function wp_dashboard_primary_output( $widget_id, $feeds ) { 1875 foreach ( $feeds as $type => $args ) { 1876 $args['type'] = $type; 1877 echo '<div class="rss-widget">'; 1878 wp_widget_rss_output( $args['url'], $args ); 1879 echo '</div>'; 1880 } 1881 } 1882 1883 /** 1884 * Displays file upload quota on dashboard. 1885 * 1886 * Runs on the {@see 'activity_box_end'} hook in wp_dashboard_right_now(). 1887 * 1888 * @since 3.0.0 1889 * 1890 * @return true|null True if not multisite, user can't upload files, or the space check option is disabled. 1891 */ 1892 function wp_dashboard_quota() { 1893 if ( ! is_multisite() || ! current_user_can( 'upload_files' ) 1894 || get_site_option( 'upload_space_check_disabled' ) 1895 ) { 1896 return true; 1897 } 1898 1899 $quota = get_space_allowed(); 1900 $used = get_space_used(); 1901 1902 if ( $used > $quota ) { 1903 $percentused = '100'; 1904 } else { 1905 $percentused = ( $used / $quota ) * 100; 1906 } 1907 1908 $used_class = ( $percentused >= 70 ) ? ' warning' : ''; 1909 $used = round( $used, 2 ); 1910 $percentused = number_format( $percentused ); 1911 1912 ?> 1913 <h3 class="mu-storage"><?php _e( 'Storage Space' ); ?></h3> 1914 <div class="mu-storage"> 1915 <ul> 1916 <li class="storage-count"> 1917 <?php 1918 $text = sprintf( 1919 /* translators: %s: Number of megabytes. */ 1920 __( '%s MB Space Allowed' ), 1921 number_format_i18n( $quota ) 1922 ); 1923 printf( 1924 '<a href="%1$s">%2$s<span class="screen-reader-text"> (%3$s)</span></a>', 1925 esc_url( admin_url( 'upload.php' ) ), 1926 $text, 1927 /* translators: Hidden accessibility text. */ 1928 __( 'Manage Uploads' ) 1929 ); 1930 ?> 1931 </li><li class="storage-count <?php echo $used_class; ?>"> 1932 <?php 1933 $text = sprintf( 1934 /* translators: 1: Number of megabytes, 2: Percentage. */ 1935 __( '%1$s MB (%2$s%%) Space Used' ), 1936 number_format_i18n( $used, 2 ), 1937 $percentused 1938 ); 1939 printf( 1940 '<a href="%1$s" class="musublink">%2$s<span class="screen-reader-text"> (%3$s)</span></a>', 1941 esc_url( admin_url( 'upload.php' ) ), 1942 $text, 1943 /* translators: Hidden accessibility text. */ 1944 __( 'Manage Uploads' ) 1945 ); 1946 ?> 1947 </li> 1948 </ul> 1949 </div> 1950 <?php 1951 return null; 1952 } 1953 1954 /** 1955 * Displays the browser update nag. 1956 * 1957 * @since 3.2.0 1958 * @since 5.8.0 Added a special message for Internet Explorer users. 1959 * 1960 * @global bool $is_IE 1961 */ 1962 function wp_dashboard_browser_nag() { 1963 global $is_IE; 1964 1965 $notice = ''; 1966 $response = wp_check_browser_version(); 1967 1968 if ( $response ) { 1969 if ( $is_IE ) { 1970 $msg = __( 'Internet Explorer does not give you the best WordPress experience. Switch to Microsoft Edge, or another more modern browser to get the most from your site.' ); 1971 } elseif ( $response['insecure'] ) { 1972 $msg = sprintf( 1973 /* translators: %s: Browser name and link. */ 1974 __( "It looks like you're using an insecure version of %s. Using an outdated browser makes your computer unsafe. For the best WordPress experience, please update your browser." ), 1975 sprintf( '<a href="%s">%s</a>', esc_url( $response['update_url'] ), esc_html( $response['name'] ) ) 1976 ); 1977 } else { 1978 $msg = sprintf( 1979 /* translators: %s: Browser name and link. */ 1980 __( "It looks like you're using an old version of %s. For the best WordPress experience, please update your browser." ), 1981 sprintf( '<a href="%s">%s</a>', esc_url( $response['update_url'] ), esc_html( $response['name'] ) ) 1982 ); 1983 } 1984 1985 $browser_nag_class = ''; 1986 if ( ! empty( $response['img_src'] ) ) { 1987 $img_src = ( is_ssl() && ! empty( $response['img_src_ssl'] ) ) ? $response['img_src_ssl'] : $response['img_src']; 1988 1989 $notice .= '<div class="alignright browser-icon"><img src="' . esc_url( $img_src ) . '" alt="" /></div>'; 1990 $browser_nag_class = ' has-browser-icon'; 1991 } 1992 $notice .= "<p class='browser-update-nag{$browser_nag_class}'>{$msg}</p>"; 1993 1994 $browsehappy = 'https://browsehappy.com/'; 1995 $locale = get_user_locale(); 1996 if ( 'en_US' !== $locale ) { 1997 $browsehappy = add_query_arg( 'locale', $locale, $browsehappy ); 1998 } 1999 2000 if ( $is_IE ) { 2001 $msg_browsehappy = sprintf( 2002 /* translators: %s: Browse Happy URL. */ 2003 __( 'Learn how to <a href="%s" class="update-browser-link">browse happy</a>' ), 2004 esc_url( $browsehappy ) 2005 ); 2006 } else { 2007 $msg_browsehappy = sprintf( 2008 /* translators: 1: Browser update URL, 2: Browser name, 3: Browse Happy URL. */ 2009 __( '<a href="%1$s" class="update-browser-link">Update %2$s</a> or learn how to <a href="%3$s" class="browse-happy-link">browse happy</a>' ), 2010 esc_attr( $response['update_url'] ), 2011 esc_html( $response['name'] ), 2012 esc_url( $browsehappy ) 2013 ); 2014 } 2015 2016 $notice .= '<p>' . $msg_browsehappy . '</p>'; 2017 $notice .= '<p class="hide-if-no-js"><a href="" class="dismiss" aria-label="' . esc_attr__( 'Dismiss the browser warning panel' ) . '">' . __( 'Dismiss' ) . '</a></p>'; 2018 $notice .= '<div class="clear"></div>'; 2019 } 2020 2021 /** 2022 * Filters the notice output for the 'Browse Happy' nag meta box. 2023 * 2024 * @since 3.2.0 2025 * 2026 * @param string $notice The notice content. 2027 * @param array|false $response An array containing web browser information, or 2028 * false on failure. See wp_check_browser_version(). 2029 */ 2030 echo apply_filters( 'browse-happy-notice', $notice, $response ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores 2031 } 2032 2033 /** 2034 * Adds an additional class to the browser nag if the current version is insecure. 2035 * 2036 * @since 3.2.0 2037 * 2038 * @param string[] $classes Array of meta box classes. 2039 * @return string[] Modified array of meta box classes. 2040 */ 2041 function dashboard_browser_nag_class( $classes ) { 2042 $response = wp_check_browser_version(); 2043 2044 if ( $response && $response['insecure'] ) { 2045 $classes[] = 'browser-insecure'; 2046 } 2047 2048 return $classes; 2049 } 2050 2051 /** 2052 * Checks if the user needs a browser update. 2053 * 2054 * @since 3.2.0 2055 * 2056 * @return array|false Array of browser data on success, false on failure. 2057 */ 2058 function wp_check_browser_version() { 2059 if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) { 2060 return false; 2061 } 2062 2063 $key = md5( $_SERVER['HTTP_USER_AGENT'] ); 2064 2065 $response = get_site_transient( 'browser_' . $key ); 2066 2067 if ( false === $response ) { 2068 $url = 'http://api.wordpress.org/core/browse-happy/1.1/'; 2069 $options = array( 2070 'body' => array( 'useragent' => $_SERVER['HTTP_USER_AGENT'] ), 2071 'user-agent' => 'WordPress/' . wp_get_wp_version() . '; ' . home_url( '/' ), 2072 ); 2073 2074 if ( wp_http_supports( array( 'ssl' ) ) ) { 2075 $url = set_url_scheme( $url, 'https' ); 2076 } 2077 2078 $response = wp_remote_post( $url, $options ); 2079 2080 if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { 2081 return false; 2082 } 2083 2084 /** 2085 * Response should be an array with: 2086 * 'platform' - string - A user-friendly platform name, if it can be determined 2087 * 'name' - string - A user-friendly browser name 2088 * 'version' - string - The version of the browser the user is using 2089 * 'current_version' - string - The most recent version of the browser 2090 * 'upgrade' - boolean - Whether the browser needs an upgrade 2091 * 'insecure' - boolean - Whether the browser is deemed insecure 2092 * 'update_url' - string - The url to visit to upgrade 2093 * 'img_src' - string - An image representing the browser 2094 * 'img_src_ssl' - string - An image (over SSL) representing the browser 2095 */ 2096 $response = json_decode( wp_remote_retrieve_body( $response ), true ); 2097 2098 if ( ! is_array( $response ) ) { 2099 return false; 2100 } 2101 2102 set_site_transient( 'browser_' . $key, $response, WEEK_IN_SECONDS ); 2103 } 2104 2105 return $response; 2106 } 2107 2108 /** 2109 * Displays the PHP update nag. 2110 * 2111 * @since 5.1.0 2112 */ 2113 function wp_dashboard_php_nag() { 2114 $response = wp_check_php_version(); 2115 2116 if ( ! $response ) { 2117 return; 2118 } 2119 2120 if ( isset( $response['is_secure'] ) && ! $response['is_secure'] ) { 2121 // The `is_secure` array key name doesn't actually imply this is a secure version of PHP. It only means it receives security updates. 2122 2123 if ( $response['is_lower_than_future_minimum'] ) { 2124 $message = sprintf( 2125 /* translators: %s: The server PHP version. */ 2126 __( 'Your site is running on an outdated version of PHP (%s), which does not receive security updates and soon will not be supported by WordPress. Ensure that PHP is updated on your server as soon as possible. Otherwise you will not be able to upgrade WordPress.' ), 2127 PHP_VERSION 2128 ); 2129 } else { 2130 $message = sprintf( 2131 /* translators: %s: The server PHP version. */ 2132 __( 'Your site is running on an outdated version of PHP (%s), which does not receive security updates. It should be updated.' ), 2133 PHP_VERSION 2134 ); 2135 } 2136 } elseif ( $response['is_lower_than_future_minimum'] ) { 2137 $message = sprintf( 2138 /* translators: %s: The server PHP version. */ 2139 __( 'Your site is running on an outdated version of PHP (%s), which soon will not be supported by WordPress. Ensure that PHP is updated on your server as soon as possible. Otherwise you will not be able to upgrade WordPress.' ), 2140 PHP_VERSION 2141 ); 2142 } else { 2143 $message = sprintf( 2144 /* translators: %s: The server PHP version. */ 2145 __( 'Your site is running on an outdated version of PHP (%s), which should be updated.' ), 2146 PHP_VERSION 2147 ); 2148 } 2149 ?> 2150 <p class="bigger-bolder-text"><?php echo $message; ?></p> 2151 2152 <p><?php _e( 'What is PHP and how does it affect my site?' ); ?></p> 2153 <p> 2154 <?php _e( 'PHP is one of the programming languages used to build WordPress. Newer versions of PHP receive regular security updates and may increase your site’s performance.' ); ?> 2155 <?php 2156 if ( ! empty( $response['recommended_version'] ) ) { 2157 printf( 2158 /* translators: %s: The minimum recommended PHP version. */ 2159 __( 'The minimum recommended version of PHP is %s.' ), 2160 $response['recommended_version'] 2161 ); 2162 } 2163 ?> 2164 </p> 2165 2166 <p class="button-container"> 2167 <?php 2168 printf( 2169 '<a class="button button-primary" href="%1$s" target="_blank">%2$s<span class="screen-reader-text"> %3$s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a>', 2170 esc_url( wp_get_update_php_url() ), 2171 __( 'Learn more about updating PHP' ), 2172 /* translators: Hidden accessibility text. */ 2173 __( '(opens in a new tab)' ) 2174 ); 2175 ?> 2176 </p> 2177 <?php 2178 2179 wp_update_php_annotation(); 2180 wp_direct_php_update_button(); 2181 } 2182 2183 /** 2184 * Adds an additional class to the PHP nag if the current version is insecure. 2185 * 2186 * @since 5.1.0 2187 * 2188 * @param string[] $classes Array of meta box classes. 2189 * @return string[] Modified array of meta box classes. 2190 */ 2191 function dashboard_php_nag_class( $classes ) { 2192 $response = wp_check_php_version(); 2193 2194 if ( ! $response ) { 2195 return $classes; 2196 } 2197 2198 if ( isset( $response['is_secure'] ) && ! $response['is_secure'] ) { 2199 $classes[] = 'php-no-security-updates'; 2200 } elseif ( $response['is_lower_than_future_minimum'] ) { 2201 $classes[] = 'php-version-lower-than-future-minimum'; 2202 } 2203 2204 return $classes; 2205 } 2206 2207 /** 2208 * Displays the Site Health Status widget. 2209 * 2210 * @since 5.4.0 2211 */ 2212 function wp_dashboard_site_health() { 2213 $get_issues = get_transient( 'health-check-site-status-result' ); 2214 2215 $issue_counts = array(); 2216 2217 if ( false !== $get_issues ) { 2218 $issue_counts = json_decode( $get_issues, true ); 2219 } 2220 2221 if ( ! is_array( $issue_counts ) || ! $issue_counts ) { 2222 $issue_counts = array( 2223 'good' => 0, 2224 'recommended' => 0, 2225 'critical' => 0, 2226 ); 2227 } 2228 2229 $issues_total = $issue_counts['recommended'] + $issue_counts['critical']; 2230 ?> 2231 <div class="health-check-widget"> 2232 <div class="health-check-widget-title-section site-health-progress-wrapper loading"> 2233 <div class="site-health-progress"> 2234 <svg aria-hidden="true" focusable="false" width="100%" height="100%" viewBox="0 0 200 200" version="1.1" xmlns="http://www.w3.org/2000/svg"> 2235 <circle r="90" cx="100" cy="100" fill="transparent" stroke-dasharray="565.48" stroke-dashoffset="0"></circle> 2236 <circle id="bar" r="90" cx="100" cy="100" fill="transparent" stroke-dasharray="565.48" stroke-dashoffset="0"></circle> 2237 </svg> 2238 </div> 2239 <div class="site-health-progress-label"> 2240 <?php if ( false === $get_issues ) : ?> 2241 <?php _e( 'No information yet…' ); ?> 2242 <?php else : ?> 2243 <?php _e( 'Results are still loading…' ); ?> 2244 <?php endif; ?> 2245 </div> 2246 </div> 2247 2248 <div class="site-health-details"> 2249 <?php if ( false === $get_issues ) : ?> 2250 <p> 2251 <?php 2252 printf( 2253 /* translators: %s: URL to Site Health screen. */ 2254 __( 'Site health checks will automatically run periodically to gather information about your site. You can also <a href="%s">visit the Site Health screen</a> to gather information about your site now.' ), 2255 esc_url( admin_url( 'site-health.php' ) ) 2256 ); 2257 ?> 2258 </p> 2259 <?php else : ?> 2260 <p> 2261 <?php if ( $issues_total <= 0 ) : ?> 2262 <?php _e( 'Great job! Your site currently passes all site health checks.' ); ?> 2263 <?php elseif ( 1 === (int) $issue_counts['critical'] ) : ?> 2264 <?php _e( 'Your site has a critical issue that should be addressed as soon as possible to improve its performance and security.' ); ?> 2265 <?php elseif ( $issue_counts['critical'] > 1 ) : ?> 2266 <?php _e( 'Your site has critical issues that should be addressed as soon as possible to improve its performance and security.' ); ?> 2267 <?php elseif ( 1 === (int) $issue_counts['recommended'] ) : ?> 2268 <?php _e( 'Your site’s health is looking good, but there is still one thing you can do to improve its performance and security.' ); ?> 2269 <?php else : ?> 2270 <?php _e( 'Your site’s health is looking good, but there are still some things you can do to improve its performance and security.' ); ?> 2271 <?php endif; ?> 2272 </p> 2273 <?php endif; ?> 2274 2275 <?php if ( $issues_total > 0 && false !== $get_issues ) : ?> 2276 <p> 2277 <?php 2278 printf( 2279 /* translators: 1: Number of issues. 2: URL to Site Health screen. */ 2280 _n( 2281 'Take a look at the <strong>%1$d item</strong> on the <a href="%2$s">Site Health screen</a>.', 2282 'Take a look at the <strong>%1$d items</strong> on the <a href="%2$s">Site Health screen</a>.', 2283 $issues_total 2284 ), 2285 $issues_total, 2286 esc_url( admin_url( 'site-health.php' ) ) 2287 ); 2288 ?> 2289 </p> 2290 <?php endif; ?> 2291 </div> 2292 </div> 2293 2294 <?php 2295 } 2296 2297 /** 2298 * Outputs empty dashboard widget to be populated by JS later. 2299 * 2300 * Usable by plugins. 2301 * 2302 * @since 2.5.0 2303 */ 2304 function wp_dashboard_empty() {} 2305 2306 /** 2307 * Displays a welcome panel to introduce users to WordPress. 2308 * 2309 * @since 3.3.0 2310 * @since 5.9.0 Send users to the Site Editor if the active theme is block-based. 2311 */ 2312 function wp_welcome_panel() { 2313 list( $display_version ) = explode( '-', wp_get_wp_version() ); 2314 $can_customize = current_user_can( 'customize' ); 2315 $is_block_theme = wp_is_block_theme(); 2316 ?> 2317 <div class="welcome-panel-content"> 2318 <div class="welcome-panel-header-wrap"> 2319 <div class="welcome-panel-header"> 2320 <div class="welcome-panel-header-image"> 2321 <?php echo file_get_contents( dirname( __DIR__ ) . '/images/dashboard-background.svg' ); ?> 2322 </div> 2323 <h2><?php _e( 'Welcome to WordPress!' ); ?></h2> 2324 <p> 2325 <a href="<?php echo esc_url( admin_url( 'about.php' ) ); ?>"> 2326 <?php 2327 /* translators: %s: Current WordPress version. */ 2328 printf( __( 'Learn more about the %s version.' ), esc_html( $display_version ) ); 2329 ?> 2330 </a> 2331 </p> 2332 </div> 2333 </div> 2334 <div class="welcome-panel-column-container"> 2335 <div class="welcome-panel-column"> 2336 <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"> 2337 <rect width="48" height="48" rx="4" fill="#1E1E1E"/> 2338 <path fill-rule="evenodd" clip-rule="evenodd" d="M32.0668 17.0854L28.8221 13.9454L18.2008 24.671L16.8983 29.0827L21.4257 27.8309L32.0668 17.0854ZM16 32.75H24V31.25H16V32.75Z" fill="white"/> 2339 </svg> 2340 <div class="welcome-panel-column-content"> 2341 <h3><?php _e( 'Author rich content with blocks and patterns' ); ?></h3> 2342 <p><?php _e( 'Block patterns are pre-configured block layouts. Use them to get inspired or create new pages in a flash.' ); ?></p> 2343 <a href="<?php echo esc_url( admin_url( 'post-new.php?post_type=page' ) ); ?>"><?php _e( 'Add a new page' ); ?></a> 2344 </div> 2345 </div> 2346 <div class="welcome-panel-column"> 2347 <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"> 2348 <rect width="48" height="48" rx="4" fill="#1E1E1E"/> 2349 <path fill-rule="evenodd" clip-rule="evenodd" d="M18 16h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H18a2 2 0 0 1-2-2V18a2 2 0 0 1 2-2zm12 1.5H18a.5.5 0 0 0-.5.5v3h13v-3a.5.5 0 0 0-.5-.5zm.5 5H22v8h8a.5.5 0 0 0 .5-.5v-7.5zm-10 0h-3V30a.5.5 0 0 0 .5.5h2.5v-8z" fill="#fff"/> 2350 </svg> 2351 <div class="welcome-panel-column-content"> 2352 <?php if ( $is_block_theme ) : ?> 2353 <h3><?php _e( 'Customize your entire site with block themes' ); ?></h3> 2354 <p><?php _e( 'Design everything on your site — from the header down to the footer, all using blocks and patterns.' ); ?></p> 2355 <a href="<?php echo esc_url( admin_url( 'site-editor.php' ) ); ?>"><?php _e( 'Open site editor' ); ?></a> 2356 <?php else : ?> 2357 <h3><?php _e( 'Start Customizing' ); ?></h3> 2358 <p><?php _e( 'Configure your site’s logo, header, menus, and more in the Customizer.' ); ?></p> 2359 <?php if ( $can_customize ) : ?> 2360 <a class="load-customize hide-if-no-customize" href="<?php echo wp_customize_url(); ?>"><?php _e( 'Open the Customizer' ); ?></a> 2361 <?php endif; ?> 2362 <?php endif; ?> 2363 </div> 2364 </div> 2365 <div class="welcome-panel-column"> 2366 <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"> 2367 <rect width="48" height="48" rx="4" fill="#1E1E1E"/> 2368 <path fill-rule="evenodd" clip-rule="evenodd" d="M31 24a7 7 0 0 1-7 7V17a7 7 0 0 1 7 7zm-7-8a8 8 0 1 1 0 16 8 8 0 0 1 0-16z" fill="#fff"/> 2369 </svg> 2370 <div class="welcome-panel-column-content"> 2371 <?php if ( $is_block_theme ) : ?> 2372 <h3><?php _e( 'Switch up your site’s look & feel with Styles' ); ?></h3> 2373 <p><?php _e( 'Tweak your site, or give it a whole new look! Get creative — how about a new color palette or font?' ); ?></p> 2374 <a href="<?php echo esc_url( add_query_arg( 'p', rawurlencode( '/styles' ), admin_url( 'site-editor.php' ) ) ); ?>"><?php _e( 'Edit styles' ); ?></a> 2375 <?php else : ?> 2376 <h3><?php _e( 'Discover a new way to build your site.' ); ?></h3> 2377 <p><?php _e( 'There is a new kind of WordPress theme, called a block theme, that lets you build the site you’ve always wanted — with blocks and styles.' ); ?></p> 2378 <a href="<?php echo esc_url( __( 'https://wordpress.org/documentation/article/block-themes/' ) ); ?>"><?php _e( 'Learn about block themes' ); ?></a> 2379 <?php endif; ?> 2380 </div> 2381 </div> 2382 </div> 2383 </div> 2384 <?php 2385 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sun Oct 25 08:20:35 2026 | Cross-referenced by PHPXref |