[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-admin/includes/ -> dashboard.php (source)

   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&#8217;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 &#8220;%s&#8221;' ), $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&amp;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 &#8220;%s&#8221;' ), $title );
1059          } else {
1060              /* translators: %s: Post title. */
1061              $aria_label = sprintf( __( 'View &#8220;%s&#8221;' ), $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 &#8220;%s&#8221;' ), $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&hellip;' ) . '</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&#8217;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&hellip;' ); ?>
2242                  <?php else : ?>
2243                      <?php _e( 'Results are still loading&hellip;' ); ?>
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&#8217;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&#8217;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 &#8212; 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&#8217;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&#8217;s look & feel with Styles' ); ?></h3>
2373                  <p><?php _e( 'Tweak your site, or give it a whole new look! Get creative &#8212; 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&#8217;ve always wanted &#8212; 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  }


Generated : Sun Oct 25 08:20:35 2026 Cross-referenced by PHPXref