Skip to content
4 changes: 4 additions & 0 deletions projects/packages/forms/changelog/add-forms-unread-state
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: added

Forms: add read and unread state
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,29 @@ public function register_routes() {
'callback' => array( $this, 'get_forms_config' ),
)
);

// Mark feedback as read/unread endpoint.
register_rest_route(
$this->namespace,
$this->rest_base . '/(?P<id>\d+)/read',
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'update_read_status' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => array(
'id' => array(
'type' => 'integer',
'required' => true,
'sanitize_callback' => 'absint',
),
'is_unread' => array(
'type' => 'boolean',
'required' => true,
'sanitize_callback' => 'rest_sanitize_boolean',
),
),
)
);
}

/**
Expand Down Expand Up @@ -488,6 +511,16 @@ public function get_item_schema() {
'readonly' => true,
);

$schema['properties']['is_unread'] = array(
'description' => __( 'Whether the form response is unread.', 'jetpack-forms' ),
'type' => 'boolean',
'context' => array( 'view', 'edit', 'embed' ),
'arg_options' => array(
'sanitize_callback' => 'rest_sanitize_boolean',
),
'readonly' => true,
);

$this->schema = $schema;

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

if ( rest_is_field_included( 'is_unread', $fields ) ) {
$data['is_unread'] = $feedback_response->is_unread();
}

$response->set_data( $data );

return rest_ensure_response( $response );
Expand Down Expand Up @@ -1033,6 +1070,45 @@ public function disable_integration( $request ) {
return rest_ensure_response( array( 'deleted' => $is_deleted ) );
}

/**
* Updates the read/unread status of a feedback item.
*
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function update_read_status( $request ) {
$post_id = $request->get_param( 'id' );
$is_unread = $request->get_param( 'is_unread' );

$feedback_response = Feedback::get( $post_id );
if ( ! $feedback_response ) {
return new WP_Error(
'rest_post_invalid_id',
__( 'Invalid feedback ID.', 'jetpack-forms' ),
array( 'status' => 404 )
);
}

$success = $is_unread ? $feedback_response->mark_as_unread() : $feedback_response->mark_as_read();

Contact_Form_Plugin::recalculate_unread_count();
if ( ! $success ) {
return new WP_Error(
'rest_cannot_update',
__( 'Failed to update feedback read status.', 'jetpack-forms' ),
array( 'status' => 500 )
);
}

return rest_ensure_response(
array(
'id' => $post_id,
'is_unread' => $feedback_response->is_unread(),
'count' => Contact_Form_Plugin::get_unread_count(),
)
);
}

/**
* Return consolidated Forms config payload.
*
Expand Down
119 changes: 71 additions & 48 deletions projects/packages/forms/src/contact-form/class-contact-form-plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -1406,70 +1406,93 @@ public function allow_feedback_rest_api_type( $post_types ) {
* Display the count of new feedback entries received. It's reset when user visits the Feedback screen.
*
* @since 4.1.0
*
* @param object $screen Information about the current screen.
*/
public function unread_count( $screen ) {
if ( isset( $screen->post_type ) && 'feedback' === $screen->post_type || $screen->id === 'jetpack_page_jetpack-forms-admin' ) {
update_option( 'feedback_unread_count', 0 );
} else {
global $submenu, $menu;
if ( apply_filters( 'jetpack_forms_use_new_menu_parent', true ) && current_user_can( 'edit_pages' ) ) {
// show the count on Jetpack and Jetpack → Forms
$unread = get_option( 'feedback_unread_count', 0 );

if ( $unread > 0 && isset( $submenu['jetpack'] ) && is_array( $submenu['jetpack'] ) && ! empty( $submenu['jetpack'] ) ) {
$forms_unread_count_tag = " <span class='count-{$unread} awaiting-mod'><span>" . number_format_i18n( $unread ) . '</span></span>';
$jetpack_badge_count = $unread;

// Main menu entries
foreach ( $menu as $index => $main_menu_item ) {
if ( isset( $main_menu_item[1] ) && 'jetpack_admin_page' === $main_menu_item[1] ) {
// Parse the menu item
$jetpack_menu_item = $this->parse_menu_item( $menu[ $index ][0] );

if ( isset( $jetpack_menu_item['badge'] ) && is_numeric( $jetpack_menu_item['badge'] ) && intval( $jetpack_menu_item['badge'] ) ) {
$jetpack_badge_count += intval( $jetpack_menu_item['badge'] );
}
public function unread_count() {

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

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

// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$menu[ $index ][0] = $jetpack_menu_item['title'] . ' ' . $jetpack_unread_tag;
$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>';
$jetpack_badge_count = $unread;

// Main menu entries
foreach ( $menu as $index => $main_menu_item ) {
if ( isset( $main_menu_item[1] ) && 'jetpack_admin_page' === $main_menu_item[1] ) {
// Parse the menu item
$jetpack_menu_item = $this->parse_menu_item( $menu[ $index ][0] );

if ( isset( $jetpack_menu_item['badge'] ) && is_numeric( $jetpack_menu_item['badge'] ) && intval( $jetpack_menu_item['badge'] ) ) {
$jetpack_badge_count += intval( $jetpack_menu_item['badge'] );
}
}

// Jetpack submenu entries
foreach ( $submenu['jetpack'] as $index => $menu_item ) {
if ( 'jetpack-forms-admin' === $menu_item[2] ) {
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$submenu['jetpack'][ $index ][0] .= $forms_unread_count_tag;
if ( isset( $jetpack_menu_item['count'] ) && is_numeric( $jetpack_menu_item['count'] ) && intval( $jetpack_menu_item['count'] ) ) {
$jetpack_badge_count += intval( $jetpack_menu_item['count'] );
}

$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>';

// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$menu[ $index ][0] = $jetpack_menu_item['title'] . ' ' . $jetpack_unread_tag;
}
}

// Jetpack submenu entries
foreach ( $submenu['jetpack'] as $index => $menu_item ) {
if ( 'jetpack-forms-admin' === $menu_item[2] ) {
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$submenu['jetpack'][ $index ][0] .= $forms_unread_count_tag;
}
}
return;
}
if ( isset( $submenu['feedback'] ) && is_array( $submenu['feedback'] ) && ! empty( $submenu['feedback'] ) ) {
foreach ( $submenu['feedback'] as $index => $menu_item ) {
if ( 'edit.php?post_type=feedback' === $menu_item[2] ) {
$unread = get_option( 'feedback_unread_count', 0 );
if ( $unread > 0 ) {
$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>' : '';

// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$submenu['feedback'][ $index ][0] .= $unread_count;
}
break;
return;
}

if ( isset( $submenu['feedback'] ) && is_array( $submenu['feedback'] ) && ! empty( $submenu['feedback'] ) ) {
foreach ( $submenu['feedback'] as $index => $menu_item ) {
if ( 'edit.php?post_type=feedback' === $menu_item[2] ) {
$unread = self::get_unread_count();

if ( $unread > 0 ) {
$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>' : '';

// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$submenu['feedback'][ $index ][0] .= $unread_count;
}
break;
}
}
}
}

/**
* Get the count of unread feedback entries.
*
* @since $$next-version$$
*
* @return int The count of unread feedback entries.
*/
public static function get_unread_count() {
return (int) get_option( 'feedback_unread_count_v2', 0 );
}

/**
* Recalculate the count of unread feedback entries.
*
* @since $$next-version$$
*
* @return int The count of unread feedback entries.
*/
public static function recalculate_unread_count() {
$count = Feedback::get_unread_count();
update_option( 'feedback_unread_count_v2', $count );
return $count;
}

/**
* Handles all contact-form POST submissions
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1929,9 +1929,7 @@ public function process_submission() {
update_post_meta( $post_id, '_feedback_extra_fields', $this->addslashes_deep( $extra_values ) );

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

if ( defined( 'AKISMET_VERSION' ) ) {
Expand Down
Loading
Loading