Skip to content

Commit 97a59da

Browse files
enejbsimison
andauthored
Forms: add read/unread state to the UI (#45350)
* Update the feedback class to have is_unread property * Add new endpoint that lets us marks things as read and unread * Update the UI that lets us shows us things that are unread and mark them as read once they are viewed * Add DataView actions * changelog * Update the style * Make things bold * Swap icons and add back indicator dot * Update the sidebar counter to match Replaces legacy unread feedback count logic with new static methods in Contact_Form_Plugin and Feedback classes, storing the count in a new option. Updates backend to recalculate and return the unread count after marking feedback as read/unread. Frontend now updates the admin menu counter dynamically using the new count from the server, improving accuracy and consistency. * Clarify JS comments * Move menu updated to utils * Always output counter badge * Make the dot red. * Add mark as read buttons * Update the option name * Update the tests to remove untestable code * Add read unread buttons to each inbox header * Fix typescript error * fix phan * More tests. * More tests... --------- Co-authored-by: Mikael Korpela <[email protected]>
1 parent d5795ab commit 97a59da

File tree

16 files changed

+957
-67
lines changed

16 files changed

+957
-67
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: patch
2+
Type: added
3+
4+
Forms: add read and unread state

projects/packages/forms/src/contact-form/class-contact-form-endpoint.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,29 @@ public function register_routes() {
254254
'callback' => array( $this, 'get_forms_config' ),
255255
)
256256
);
257+
258+
// Mark feedback as read/unread endpoint.
259+
register_rest_route(
260+
$this->namespace,
261+
$this->rest_base . '/(?P<id>\d+)/read',
262+
array(
263+
'methods' => \WP_REST_Server::CREATABLE,
264+
'callback' => array( $this, 'update_read_status' ),
265+
'permission_callback' => array( $this, 'update_item_permissions_check' ),
266+
'args' => array(
267+
'id' => array(
268+
'type' => 'integer',
269+
'required' => true,
270+
'sanitize_callback' => 'absint',
271+
),
272+
'is_unread' => array(
273+
'type' => 'boolean',
274+
'required' => true,
275+
'sanitize_callback' => 'rest_sanitize_boolean',
276+
),
277+
),
278+
)
279+
);
257280
}
258281

259282
/**
@@ -488,6 +511,16 @@ public function get_item_schema() {
488511
'readonly' => true,
489512
);
490513

514+
$schema['properties']['is_unread'] = array(
515+
'description' => __( 'Whether the form response is unread.', 'jetpack-forms' ),
516+
'type' => 'boolean',
517+
'context' => array( 'view', 'edit', 'embed' ),
518+
'arg_options' => array(
519+
'sanitize_callback' => 'rest_sanitize_boolean',
520+
),
521+
'readonly' => true,
522+
);
523+
491524
$this->schema = $schema;
492525

493526
return $this->add_additional_fields_schema( $this->schema );
@@ -631,6 +664,10 @@ public function prepare_item_for_response( $item, $request ) {
631664
$data['has_file'] = $feedback_response->has_file();
632665
}
633666

667+
if ( rest_is_field_included( 'is_unread', $fields ) ) {
668+
$data['is_unread'] = $feedback_response->is_unread();
669+
}
670+
634671
$response->set_data( $data );
635672

636673
return rest_ensure_response( $response );
@@ -1033,6 +1070,45 @@ public function disable_integration( $request ) {
10331070
return rest_ensure_response( array( 'deleted' => $is_deleted ) );
10341071
}
10351072

1073+
/**
1074+
* Updates the read/unread status of a feedback item.
1075+
*
1076+
* @param WP_REST_Request $request Request object.
1077+
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
1078+
*/
1079+
public function update_read_status( $request ) {
1080+
$post_id = $request->get_param( 'id' );
1081+
$is_unread = $request->get_param( 'is_unread' );
1082+
1083+
$feedback_response = Feedback::get( $post_id );
1084+
if ( ! $feedback_response ) {
1085+
return new WP_Error(
1086+
'rest_post_invalid_id',
1087+
__( 'Invalid feedback ID.', 'jetpack-forms' ),
1088+
array( 'status' => 404 )
1089+
);
1090+
}
1091+
1092+
$success = $is_unread ? $feedback_response->mark_as_unread() : $feedback_response->mark_as_read();
1093+
1094+
Contact_Form_Plugin::recalculate_unread_count();
1095+
if ( ! $success ) {
1096+
return new WP_Error(
1097+
'rest_cannot_update',
1098+
__( 'Failed to update feedback read status.', 'jetpack-forms' ),
1099+
array( 'status' => 500 )
1100+
);
1101+
}
1102+
1103+
return rest_ensure_response(
1104+
array(
1105+
'id' => $post_id,
1106+
'is_unread' => $feedback_response->is_unread(),
1107+
'count' => Contact_Form_Plugin::get_unread_count(),
1108+
)
1109+
);
1110+
}
1111+
10361112
/**
10371113
* Return consolidated Forms config payload.
10381114
*

projects/packages/forms/src/contact-form/class-contact-form-plugin.php

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,70 +1406,93 @@ public function allow_feedback_rest_api_type( $post_types ) {
14061406
* Display the count of new feedback entries received. It's reset when user visits the Feedback screen.
14071407
*
14081408
* @since 4.1.0
1409-
*
1410-
* @param object $screen Information about the current screen.
14111409
*/
1412-
public function unread_count( $screen ) {
1413-
if ( isset( $screen->post_type ) && 'feedback' === $screen->post_type || $screen->id === 'jetpack_page_jetpack-forms-admin' ) {
1414-
update_option( 'feedback_unread_count', 0 );
1415-
} else {
1416-
global $submenu, $menu;
1417-
if ( apply_filters( 'jetpack_forms_use_new_menu_parent', true ) && current_user_can( 'edit_pages' ) ) {
1418-
// show the count on Jetpack and Jetpack → Forms
1419-
$unread = get_option( 'feedback_unread_count', 0 );
1420-
1421-
if ( $unread > 0 && isset( $submenu['jetpack'] ) && is_array( $submenu['jetpack'] ) && ! empty( $submenu['jetpack'] ) ) {
1422-
$forms_unread_count_tag = " <span class='count-{$unread} awaiting-mod'><span>" . number_format_i18n( $unread ) . '</span></span>';
1423-
$jetpack_badge_count = $unread;
1424-
1425-
// Main menu entries
1426-
foreach ( $menu as $index => $main_menu_item ) {
1427-
if ( isset( $main_menu_item[1] ) && 'jetpack_admin_page' === $main_menu_item[1] ) {
1428-
// Parse the menu item
1429-
$jetpack_menu_item = $this->parse_menu_item( $menu[ $index ][0] );
1430-
1431-
if ( isset( $jetpack_menu_item['badge'] ) && is_numeric( $jetpack_menu_item['badge'] ) && intval( $jetpack_menu_item['badge'] ) ) {
1432-
$jetpack_badge_count += intval( $jetpack_menu_item['badge'] );
1433-
}
1410+
public function unread_count() {
14341411

1435-
if ( isset( $jetpack_menu_item['count'] ) && is_numeric( $jetpack_menu_item['count'] ) && intval( $jetpack_menu_item['count'] ) ) {
1436-
$jetpack_badge_count += intval( $jetpack_menu_item['count'] );
1437-
}
1412+
global $submenu, $menu;
1413+
if ( apply_filters( 'jetpack_forms_use_new_menu_parent', true ) && current_user_can( 'edit_pages' ) ) {
1414+
// show the count on Jetpack and Jetpack → Forms
1415+
$unread = self::get_unread_count();
14381416

1439-
$jetpack_unread_tag = " <span class='count-{$jetpack_badge_count} awaiting-mod'><span>" . number_format_i18n( $jetpack_badge_count ) . '</span></span>';
1417+
if ( isset( $submenu['jetpack'] ) && is_array( $submenu['jetpack'] ) && ! empty( $submenu['jetpack'] ) ) {
1418+
$inline_style = ( $unread > 0 ) ? '' : 'style="display: none;"';
14401419

1441-
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1442-
$menu[ $index ][0] = $jetpack_menu_item['title'] . ' ' . $jetpack_unread_tag;
1420+
$forms_unread_count_tag = " <span class='jp-feedback-unread-counter count-{$unread} awaiting-mod' {$inline_style}><span class='feedback-unread-counter'>" . number_format_i18n( $unread ) . '</span></span>';
1421+
$jetpack_badge_count = $unread;
1422+
1423+
// Main menu entries
1424+
foreach ( $menu as $index => $main_menu_item ) {
1425+
if ( isset( $main_menu_item[1] ) && 'jetpack_admin_page' === $main_menu_item[1] ) {
1426+
// Parse the menu item
1427+
$jetpack_menu_item = $this->parse_menu_item( $menu[ $index ][0] );
1428+
1429+
if ( isset( $jetpack_menu_item['badge'] ) && is_numeric( $jetpack_menu_item['badge'] ) && intval( $jetpack_menu_item['badge'] ) ) {
1430+
$jetpack_badge_count += intval( $jetpack_menu_item['badge'] );
14431431
}
1444-
}
14451432

1446-
// Jetpack submenu entries
1447-
foreach ( $submenu['jetpack'] as $index => $menu_item ) {
1448-
if ( 'jetpack-forms-admin' === $menu_item[2] ) {
1449-
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1450-
$submenu['jetpack'][ $index ][0] .= $forms_unread_count_tag;
1433+
if ( isset( $jetpack_menu_item['count'] ) && is_numeric( $jetpack_menu_item['count'] ) && intval( $jetpack_menu_item['count'] ) ) {
1434+
$jetpack_badge_count += intval( $jetpack_menu_item['count'] );
14511435
}
1436+
1437+
$jetpack_unread_tag = " <span data-unread-diff='" . ( $jetpack_badge_count - $unread ) . "' class='jp-feedback-unread-counter count-{$jetpack_badge_count} awaiting-mod'><span class='feedback-unread-counter'>" . number_format_i18n( $jetpack_badge_count ) . '</span></span>';
1438+
1439+
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1440+
$menu[ $index ][0] = $jetpack_menu_item['title'] . ' ' . $jetpack_unread_tag;
1441+
}
1442+
}
1443+
1444+
// Jetpack submenu entries
1445+
foreach ( $submenu['jetpack'] as $index => $menu_item ) {
1446+
if ( 'jetpack-forms-admin' === $menu_item[2] ) {
1447+
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1448+
$submenu['jetpack'][ $index ][0] .= $forms_unread_count_tag;
14521449
}
14531450
}
1454-
return;
14551451
}
1456-
if ( isset( $submenu['feedback'] ) && is_array( $submenu['feedback'] ) && ! empty( $submenu['feedback'] ) ) {
1457-
foreach ( $submenu['feedback'] as $index => $menu_item ) {
1458-
if ( 'edit.php?post_type=feedback' === $menu_item[2] ) {
1459-
$unread = get_option( 'feedback_unread_count', 0 );
1460-
if ( $unread > 0 ) {
1461-
$unread_count = current_user_can( 'publish_pages' ) ? " <span class='feedback-unread count-{$unread} awaiting-mod'><span class='feedback-unread-count'>" . number_format_i18n( $unread ) . '</span></span>' : '';
1462-
1463-
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1464-
$submenu['feedback'][ $index ][0] .= $unread_count;
1465-
}
1466-
break;
1452+
return;
1453+
}
1454+
1455+
if ( isset( $submenu['feedback'] ) && is_array( $submenu['feedback'] ) && ! empty( $submenu['feedback'] ) ) {
1456+
foreach ( $submenu['feedback'] as $index => $menu_item ) {
1457+
if ( 'edit.php?post_type=feedback' === $menu_item[2] ) {
1458+
$unread = self::get_unread_count();
1459+
1460+
if ( $unread > 0 ) {
1461+
$unread_count = current_user_can( 'publish_pages' ) ? " <span class='feedback-unread jp-feedback-unread-counter count-{$unread} awaiting-mod'><span class='feedback-unread-count'>" . number_format_i18n( $unread ) . '</span></span>' : '';
1462+
1463+
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1464+
$submenu['feedback'][ $index ][0] .= $unread_count;
14671465
}
1466+
break;
14681467
}
14691468
}
14701469
}
14711470
}
14721471

1472+
/**
1473+
* Get the count of unread feedback entries.
1474+
*
1475+
* @since $$next-version$$
1476+
*
1477+
* @return int The count of unread feedback entries.
1478+
*/
1479+
public static function get_unread_count() {
1480+
return (int) get_option( 'jetpack_feedback_unread_count', 0 ); // previously defaulted named "feedback_unread_count".
1481+
}
1482+
1483+
/**
1484+
* Recalculate the count of unread feedback entries.
1485+
*
1486+
* @since $$next-version$$
1487+
*
1488+
* @return int The count of unread feedback entries.
1489+
*/
1490+
public static function recalculate_unread_count() {
1491+
$count = Feedback::get_unread_count();
1492+
update_option( 'jetpack_feedback_unread_count', $count );
1493+
return $count;
1494+
}
1495+
14731496
/**
14741497
* Handles all contact-form POST submissions
14751498
*

projects/packages/forms/src/contact-form/class-contact-form.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,9 +1934,7 @@ public function process_submission() {
19341934
update_post_meta( $post_id, '_feedback_extra_fields', $this->addslashes_deep( $extra_values ) );
19351935

19361936
if ( 'publish' === $feedback_status ) {
1937-
// Increase count of unread feedback.
1938-
$unread = (int) get_option( 'feedback_unread_count', 0 ) + 1;
1939-
update_option( 'feedback_unread_count', $unread );
1937+
Contact_Form_Plugin::recalculate_unread_count();
19401938
}
19411939

19421940
if ( defined( 'AKISMET_VERSION' ) ) {

0 commit comments

Comments
 (0)