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


Generated : Wed Aug 26 08:20:24 2026 Cross-referenced by PHPXref