diff --git a/includes/interface-notification.php b/includes/interface-notification.php deleted file mode 100644 index 51798b70..00000000 --- a/includes/interface-notification.php +++ /dev/null @@ -1,43 +0,0 @@ -sub( $interval ); return $this->find_by_date_range( + $user_id, $start, $end, Order::DESCENDING, diff --git a/includes/persistence/class-wpdb-notification-repository.php b/includes/persistence/class-wpdb-notification-repository.php index fa63c709..6a3f1db2 100644 --- a/includes/persistence/class-wpdb-notification-repository.php +++ b/includes/persistence/class-wpdb-notification-repository.php @@ -3,63 +3,90 @@ namespace WP\Notifications\Persistence; use DateTimeInterface; +use WP\Notifications\Model; +use WP_Error; -use WP\Notifications; - +/** + * Database repository placeholder; persistence queries are not implemented yet. + */ class Wpdb_Notification_Repository extends Abstract_Notification_Repository { /** - * Find a notification by ID. - * - * @param int $id ID to look for. - * - * @return Notifications\Notification|false Notification that matches the ID, or - * false if not found. + * {@inheritDoc} */ - public function find_by_id( $id ) { - if ( ! is_int( $id ) ) { - return false; - } + public function find_by_id( int $message_id, int $user_id ): ?array { + // TODO: Implement query. + return null; + } + /** + * {@inheritDoc} + */ + public function find_by_user( int $user_id, array $args = array() ): array { // TODO: Implement query. - return false; + return array(); + } + + /** + * {@inheritDoc} + */ + public function count_by_user( int $user_id, array $args = array() ): int { + // TODO: Implement query. + return 0; } /** - * Find all notifications for a given date period. - * - * @param DateTimeInterface $start Start date of the date range.search. - * @param DateTimeInterface $end End date of the date range.search. - * @param string $order Optional. Sorting order, defaults to - * descending. - * @param int $pagination Optional. Number of elements per - * page. Defaults to 10. - * @param int $offset Optional. Offset into the result - * set. Defaults to 0. - * - * @return Notifications\Notification[] Array of notifications, empty array if - * none found. + * {@inheritDoc} */ public function find_by_date_range( + int $user_id, DateTimeInterface $start, DateTimeInterface $end, $order = Order::DESCENDING, $pagination = 10, $offset = 0 - ) { + ): array { // TODO: Implement query. return array(); } /** - * Add a new notification to the repository. - * - * @param Notifications\Notification $notification Notification to add. - * - * @return int ID that the notification was stored under. + * {@inheritDoc} + */ + public function add( Model\Message $message, string $channel_name, array $user_ids, string $context = 'adminbar' ) { + // TODO: Implement query. + return new WP_Error( 'not_implemented', __( 'Notification persistence is not implemented yet.', 'wp-feature-notifications' ) ); + } + + /** + * {@inheritDoc} + */ + public function dismiss( int $message_id, int $user_id ): bool { + // TODO: Implement query. + return false; + } + + /** + * {@inheritDoc} + */ + public function mark_displayed( int $message_id, int $user_id ): bool { + // TODO: Implement query. + return false; + } + + /** + * {@inheritDoc} + */ + public function remove( int $message_id, int $user_id ): bool { + // TODO: Implement query. + return false; + } + + /** + * {@inheritDoc} */ - public function add( Notifications\Notification $notification ) { + public function purge_orphans(): int { // TODO: Implement query. - return -1; + return 0; } } diff --git a/includes/persistence/interface-notification-repository.php b/includes/persistence/interface-notification-repository.php index 9ab7e314..40f02b4e 100644 --- a/includes/persistence/interface-notification-repository.php +++ b/includes/persistence/interface-notification-repository.php @@ -4,70 +4,149 @@ use DateInterval; use DateTimeInterface; +use WP\Notifications\Model; -use WP\Notifications; - - +/** + * Stores messages and their per-user notification queue entries. + * + * All message IDs are scoped to the supplied user, including IDs exposed by REST. + */ interface Notification_Repository { /** - * Find a notification by ID. + * Find a message and its notification belonging to a user. * - * @param int $id ID to look for. + * @param int $message_id Message ID, not a surrogate queue ID. + * @param int $user_id User who owns the notification. + * @return array{notification: Model\Notification, message: Model\Message}|null Null if not found for this user. + */ + public function find_by_id( int $message_id, int $user_id ): ?array; + + /** + * Find notifications belonging to a user. + * + * @param int $user_id User who owns the notifications. + * @param array $args { + * Optional query arguments. * - * @return Notifications\Notification|false Notification that matches the ID, or - * false if not found. + * @type string $status One of the WP\Notifications\Status_Interface values: + * 'new', 'undisplayed', 'displayed', or 'dismissed'. + * 'new' and 'undisplayed' both require displayed_at and + * dismissed_at to be null. 'displayed' requires displayed_at + * to be set and dismissed_at to be null. 'dismissed' requires + * dismissed_at to be set, regardless of displayed_at. + * Omit to include all statuses. + * @type string $channel_name Namespaced channel name to match. + * @type string $context Display context to match. + * @type string $after Exclusive lower bound on created_at, used by polling. + * ISO 8601 in UTC, e.g. '2026-10-17T09:00:00Z'. + * @type string $before Exclusive upper bound on created_at. + * ISO 8601 in UTC, e.g. '2026-10-18T09:00:00Z'. + * @type bool $include_expired Include expired notifications. Default false. + * @type string $order Order::ASCENDING ('asc') or Order::DESCENDING ('desc'). + * Default Order::DESCENDING. + * @type string $orderby Field to sort by. Only 'created_at' is supported. + * Default 'created_at'. + * @type int $per_page Number of results per page. Default 10. + * @type int $offset Offset into the result set. Default 0. + * } + * @return array Array of array{notification: Model\Notification, message: Model\Message} pairs. */ - public function find_by_id( $id ); + public function find_by_user( int $user_id, array $args = array() ): array; /** - * Find all notifications for a given date period. + * Count a user's matching notifications for totals such as X-WP-Total. * - * @param DateTimeInterface $start Start date of the date range.search. - * @param DateTimeInterface $end End date of the date range.search. - * @param string $order Optional. Sorting order, defaults - * to - * descending. - * @param int $pagination Optional. Number of elements per - * page. Defaults to 10. - * @param int $offset Optional. Offset into the result - * set. Defaults to 0. + * @param int $user_id User who owns the notifications. + * @param array $args Filters as in find_by_user(); pagination is ignored. + * @return int Number of matching notifications. + */ + public function count_by_user( int $user_id, array $args = array() ): int; + + /** + * Find a user's notifications within a date range. * - * @return Notifications\Notification[] Array of notifications, empty array if - * none found. + * @param int $user_id User who owns the notifications. + * @param DateTimeInterface $start Start of the date range. + * @param DateTimeInterface $end End of the date range. + * @param string $order Sort direction. Default descending. + * @param int $pagination Number of results per page. Default 10. + * @param int $offset Offset into the result set. Default 0. + * @return array Array of array{notification: Model\Notification, message: Model\Message} pairs. */ public function find_by_date_range( + int $user_id, DateTimeInterface $start, DateTimeInterface $end, $order = Order::DESCENDING, $pagination = 10, $offset = 0 - ); + ): array; /** - * Find the latest notifications for a given date range. + * Find a user's latest notifications within an interval ending now. * + * @param int $user_id User who owns the notifications. * @param DateInterval $interval Time interval to search. - * @param int $pagination Optional. Number of elements per page. - * Defaults to 10. - * @param int $offset Optional. Offset into the result set. - * Defaults to 0. - * - * @return Notifications\Notification[] Array of notifications, empty array - * if none found. + * @param int $pagination Number of results per page. Default 10. + * @param int $offset Offset into the result set. Default 0. + * @return array Array of array{notification: Model\Notification, message: Model\Message} pairs. */ public function find_latest( + int $user_id, DateInterval $interval, $pagination = 10, $offset = 0 - ); + ): array; + + /** + * Store a message and fan it out to the supplied users. + * + * Duplicate user IDs are collapsed into a single recipient. An empty recipient + * list or any nonexistent user ID must return WP_Error without storing anything. + * The operation is all-or-nothing: if storing the message or any recipient's + * notification fails, return WP_Error and leave no message or notifications + * from this operation persisted. + * + * @param Model\Message $message Message to store. + * @param string $channel_name Namespaced channel name. + * @param int[] $user_ids Recipient user IDs. + * @param string $context Display context. Default 'adminbar'. + * @return int|\WP_Error Stored message ID, or an error on failure. + */ + public function add( Model\Message $message, string $channel_name, array $user_ids, string $context = 'adminbar' ); /** - * Add a new notification to the repository. + * Dismiss a user's notification. * - * @param Notifications\Notification $notification Notification to add. + * @param int $message_id Message ID. + * @param int $user_id User who owns the notification. + * @return bool Whether the operation succeeded. + */ + public function dismiss( int $message_id, int $user_id ): bool; + + /** + * Mark a user's notification as displayed. + * + * @param int $message_id Message ID. + * @param int $user_id User who owns the notification. + * @return bool Whether the operation succeeded. + */ + public function mark_displayed( int $message_id, int $user_id ): bool; + + /** + * Remove a user's notification. + * + * @param int $message_id Message ID. + * @param int $user_id User who owns the notification. + * @return bool Whether the operation succeeded. + */ + public function remove( int $message_id, int $user_id ): bool; + + /** + * Purge messages with no remaining notification queue entries. * - * @return int ID that the notification was stored under. + * @return int Number of messages removed. */ - public function add( Notifications\Notification $notification ); + public function purge_orphans(): int; } diff --git a/tests/phpunit/tests/persistence/test-notification-repository.php b/tests/phpunit/tests/persistence/test-notification-repository.php new file mode 100644 index 00000000..4df5eb29 --- /dev/null +++ b/tests/phpunit/tests/persistence/test-notification-repository.php @@ -0,0 +1,52 @@ +query = func_get_args(); + return array( + array( + 'notification' => new Model\Notification(), + 'message' => new Model\Message(), + ), + ); + } + }; + + $before = new DateTime( 'now' ); + $result = $repository->find_latest( 42, new DateInterval( 'P2D' ), 5, 10 ); + $after = new DateTime( 'now' ); + + $this->assertSame( 42, $repository->query[0] ); + $this->assertEquals( $repository->query[2], ( clone $repository->query[1] )->add( new DateInterval( 'P2D' ) ) ); + $this->assertTrue( $repository->query[2] >= $before && $repository->query[2] <= $after ); + $this->assertSame( Order::DESCENDING, $repository->query[3] ); + $this->assertSame( 5, $repository->query[4] ); + $this->assertSame( 10, $repository->query[5] ); + $this->assertInstanceOf( Model\Notification::class, $result[0]['notification'] ); + $this->assertInstanceOf( Model\Message::class, $result[0]['message'] ); + } +} diff --git a/wp-feature-notifications.php b/wp-feature-notifications.php index 0b60bb53..189b6bf1 100644 --- a/wp-feature-notifications.php +++ b/wp-feature-notifications.php @@ -51,6 +51,7 @@ require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/channels.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/image/interface-image.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/image/class-base-image.php'; +require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/persistence/interface-order.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/persistence/interface-notification-repository.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/persistence/class-abstract-notification-repository.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/persistence/class-wpdb-notification-repository.php';