Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions src/wp-admin/css/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -934,21 +934,29 @@ body #dashboard-widgets .postbox form .submit {
}

#future-posts ul,
#published-posts ul {
#published-posts ul,
#latest-notes ul {
margin: 8px -12px 0 -12px;
}

#future-posts li,
#published-posts li {
#published-posts li,
#latest-notes li {
display: grid;
grid-template-columns: clamp(160px, calc(2vw + 140px), 200px) auto;
column-gap: 10px;
color: #646970;
padding: 4px 12px;
}

/* Recent Notes rows add a third column for the number of open notes. */
#latest-notes li {
grid-template-columns: clamp(160px, calc(2vw + 140px), 200px) auto max-content;
}

#future-posts li:nth-child(odd),
#published-posts li:nth-child(odd) {
#published-posts li:nth-child(odd),
#latest-notes li:nth-child(odd) {
background-color: #f6f7f7;
}

Expand Down Expand Up @@ -1025,6 +1033,21 @@ body #dashboard-widgets .postbox form .submit {
top: 0;
}

/* Dashboard activity widget - notes */

#latest-notes ul {
margin: 8px -12px 0 -12px
}

#latest-notes li{
padding: 4px 12px;
color: #646970;
}

#latest-notes li:nth-child(odd) {
background-color: #f6f7f7;
}

/* Browse happy box */

#dashboard-widgets #dashboard_browser_nag.postbox .inside {
Expand Down
181 changes: 180 additions & 1 deletion src/wp-admin/includes/dashboard.php
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,59 @@ function _wp_dashboard_recent_comments_row( &$comment, $show_date = true ) {
$GLOBALS['comment'] = null;
}

/**
* Outputs a row for the Recent Notes widget. Notes are implemented as a type of comment.
*
* Each row represents a post with open notes, not an individual note.
*
* @access private
* @since 7.2.0
*
* @param WP_Comment $note The most recent open note of the post.
* @param int $open_notes Optional. Number of open notes the post has. Default 1.
*/
function _wp_dashboard_recent_notes_row( $note, $open_notes = 1 ) {
$note_post_id = (int) $note->comment_post_ID;

$today = current_time( 'Y-m-d' );
$tomorrow = current_datetime()->modify( '+1 day' )->format( 'Y-m-d' );
$year = current_time( 'Y' );

$time = get_comment_date( 'U', $note );

if ( ! is_int( $time ) ) {
/* translators: Date and time format for recent notes on the dashboard, from a different calendar year, see https://www.php.net/manual/datetime.format.php */
$date = get_comment_date( __( 'M jS Y' ), $note );
} elseif ( gmdate( 'Y-m-d', $time ) === $today ) {
$date = __( 'Today' );
} elseif ( gmdate( 'Y-m-d', $time ) === $tomorrow ) {
$date = __( 'Tomorrow' );
} elseif ( gmdate( 'Y', $time ) !== $year ) {
/* translators: Date and time format for recent notes on the dashboard, from a different calendar year, see https://www.php.net/manual/datetime.format.php */
$date = date_i18n( __( 'M jS Y' ), $time );
} else {
/* translators: Date and time format for recent notes on the dashboard, see https://www.php.net/manual/datetime.format.php */
$date = date_i18n( __( 'M jS' ), $time );
}

$note_post_title = _draft_or_post_title( $note_post_id );

printf(
'<li><a href="%2$s" aria-label="%3$s">%4$s</a> <span class="open-notes-count">%5$s</span> <span>%1$s</span></li>',
/* translators: 1: Relative date, 2: Time. */
sprintf( _x( '%1$s, %2$s', 'dashboard' ), $date, get_comment_time( '', false, true, $note ) ),
esc_url( get_edit_post_link( $note_post_id ) ),
/* translators: %s: Post title. */
esc_attr( sprintf( __( 'Edit &#8220;%s&#8221;' ), $note_post_title ) ),
$note_post_title,
sprintf(
/* translators: %s: Number of open notes. */
_n( '%s open note', '%s open notes', $open_notes ),
number_format_i18n( $open_notes )
)
);
}

/**
* Outputs the Activity widget.
*
Expand Down Expand Up @@ -964,7 +1017,9 @@ function wp_dashboard_site_activity() {

$recent_comments = wp_dashboard_recent_comments();

if ( ! $future_posts && ! $recent_posts && ! $recent_comments ) {
$recent_notes = wp_dashboard_recent_notes();

if ( ! $future_posts && ! $recent_posts && ! $recent_comments && ! $recent_notes ) {
echo '<div class="no-activity">';
echo '<p>' . __( 'No activity yet!' ) . '</p>';
echo '</div>';
Expand Down Expand Up @@ -1149,6 +1204,130 @@ function wp_dashboard_recent_comments( $total_items = 5 ) {
return true;
}

/**
* Show Notes section.
*
* Lists the posts the current user can edit that have at least one open note,
* ordered by the note or reply most recently added to them.
*
* @since 7.2.0
*
* @param int $total_items Optional. Number of posts to display. Default 5.
* @return bool False if no posts with open notes were found. True otherwise.
*/
function wp_dashboard_recent_notes( $total_items = 5 ) {
/*
* Replies are queried alongside the notes that start a thread, so that a
* thread someone replied to counts as added to when it was replied to.
* Resolving a thread approves the note that starts it, while the replies
* stay on hold, so a reply is only recent activity while its thread is.
*/
$notes_query = array(
'type' => 'note',
'status' => 'hold',
'orderby' => 'comment_date_gmt',
'order' => 'DESC',
'number' => $total_items * 5,
'offset' => 0,
);

// The most recent open note or reply of each post, keyed by post ID.
$latest_notes = array();

do {
$possible = get_comments( $notes_query );

if ( empty( $possible ) || ! is_array( $possible ) ) {
break;
}

/*
* Prime the threads the replies belong to, so that they are not
* queried for one at a time below.
*/
$thread_ids = array();

foreach ( $possible as $note ) {
if ( $note->comment_parent ) {
$thread_ids[] = (int) $note->comment_parent;
}
}

if ( $thread_ids ) {
_prime_comment_caches( array_unique( $thread_ids ), false );
}

foreach ( $possible as $note ) {
$note_post_id = (int) $note->comment_post_ID;

// Only the first note of a post is kept, as notes are ordered by date.
if ( isset( $latest_notes[ $note_post_id ] ) ) {
continue;
}

// A reply is resolved along with the thread it belongs to.
if ( $note->comment_parent ) {
$thread = get_comment( (int) $note->comment_parent );

if ( ! $thread || '0' !== $thread->comment_approved ) {
continue;
}
}

// Notes are only visible to users who can edit the post they belong to.
if ( ! current_user_can( 'edit_post', $note_post_id ) ) {
continue;
}

$latest_notes[ $note_post_id ] = $note;

if ( count( $latest_notes ) === $total_items ) {
break 2;
}
}

$notes_query['offset'] += $notes_query['number'];
$notes_query['number'] = $total_items * 10;
} while ( count( $latest_notes ) < $total_items );

if ( ! $latest_notes ) {
return false;
}

/*
* Count the open notes of each of the posts about to be displayed. A count
* query per post is cheaper than one query for every open note on them: the
* count is a single value, so the notes themselves are never loaded.
*/
$open_notes = array();

foreach ( array_keys( $latest_notes ) as $note_post_id ) {
$open_notes[ $note_post_id ] = (int) get_comments(
array(
'type' => 'note',
'parent' => 0,
'status' => 'hold',
'post_id' => $note_post_id,
'count' => true,
'orderby' => 'none',
)
);
}

echo '<div id="latest-notes" class="activity-block">';
echo '<h3>' . __( 'Recent Notes' ) . '</h3>';

echo '<ul>';
foreach ( $latest_notes as $note_post_id => $note ) {
_wp_dashboard_recent_notes_row( $note, $open_notes[ $note_post_id ] );
}
echo '</ul>';

echo '</div>';

return true;
}

/**
* Display generic dashboard RSS widget feed.
*
Expand Down
Loading
Loading