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
26 changes: 26 additions & 0 deletions includes/persistence/class-wpdb-notification-repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,30 @@ public function add( Notifications\Notification $notification ) {
// TODO: Implement query.
return -1;
}

/**
* Remove a user's notification.
*
* Only the user's queue row is removed, the message itself can still
* belong to other users.
*
* @param int $message_id Message ID.
* @param int $user_id User who owns the notification.
*
* @return bool Whether a notification was removed.
*/
public function remove( int $message_id, int $user_id ): bool {
global $wpdb;

$deleted = $wpdb->delete(
$wpdb->prefix . 'notifications_queue',
array(
'message_id' => $message_id,
'user_id' => $user_id,
),
array( '%d', '%d' )
);

return ! empty( $deleted );
}
}
10 changes: 10 additions & 0 deletions includes/persistence/interface-notification-repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,14 @@ public function find_latest(
* @return int ID that the notification was stored under.
*/
public function add( Notifications\Notification $notification );

/**
* Remove a user's notification.
*
* @param int $message_id Message ID.
* @param int $user_id User who owns the notification.
*
* @return bool Whether a notification was removed.
*/
public function remove( int $message_id, int $user_id ): bool;
}
86 changes: 86 additions & 0 deletions includes/restapi/class-notification-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use WP\Notifications\Persistence\Notification_Repository;
use WP\Notifications\Persistence\Wpdb_Notification_Repository;

/**
* REST API Notification Controller class
Expand All @@ -32,6 +34,23 @@ class Notification_Controller extends WP_REST_Controller {
*/
public const NOTIFICATION_BASE = 'notifications';

/**
* Notification repository.
*
* @var Notification_Repository
*/
protected $repository;

/**
* Constructor.
*
* @param Notification_Repository|null $repository Optional. Notification repository,
* defaults to the wpdb repository.
*/
public function __construct( ?Notification_Repository $repository = null ) {
$this->repository = $repository ?? new Wpdb_Notification_Repository();
}

/**
* Register REST routes
*
Expand All @@ -52,6 +71,26 @@ public function register_routes(): void {
),
false
);

register_rest_route(
self::NAMESPACE,
self::NOTIFICATION_BASE . '/(?P<id>[\d]+)',
array(
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the notification.' ),
'type' => 'integer',
),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
),
false
);
}

/**
Expand Down Expand Up @@ -83,6 +122,53 @@ public function get_items( $request ) {
return rest_ensure_response( array() );
}

/**
* Checks if a given request has access to delete a notification.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return true|WP_Error True if the request has access to delete the item, error object otherwise.
*/
public function delete_item_permissions_check( $request ) {
if ( ! is_user_logged_in() ) {
return new WP_Error(
'rest_notifications_login_required',
__( 'Sorry, you must be logged in to delete notifications.' ),
array( 'status' => 401 )
);
}
return true;
}

/**
* Delete a notification of the current user.
*
* The ID is a message ID. Only the current user's copy is removed, so a
* notification of another user returns the same error as a missing one.
*
* @param WP_REST_Request $request Received REST request
*
* @return WP_REST_RESPONSE|WP_Error REST response or WP Error
*/
public function delete_item( $request ) {
$id = (int) $request['id'];

if ( ! $this->repository->remove( $id, get_current_user_id() ) ) {
return new WP_Error(
'rest_notification_invalid_id',
__( 'Invalid notification ID.' ),
array( 'status' => 404 )
);
}

return rest_ensure_response(
array(
'deleted' => true,
'id' => $id,
)
);
}

/**
* Retrieves the notification's schema, conforming to JSON Schema.
*
Expand Down
91 changes: 90 additions & 1 deletion tests/phpunit/tests/rest-api/test-notification-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,48 @@
* Unit tests covering WP/Notifications/Notification_Controller functionality.
*/
class WP_Test_REST_Notification_Controller extends WP_Test_REST_Controller_Testcase {
protected static $user_id;
protected static $other_user_id;

public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) {
require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/class-activator.php';

// Created outside the test transaction so the table is not temporary.
WP\Notifications\Activator::create_tables_v1();

self::$user_id = $factory->user->create();
self::$other_user_id = $factory->user->create();
}

public static function wpTearDownAfterClass() {
self::delete_user( self::$user_id );
self::delete_user( self::$other_user_id );
}

protected function add_queue_row( int $message_id, int $user_id ) {
global $wpdb;

$wpdb->insert(
$wpdb->prefix . 'notifications_queue',
array(
'message_id' => $message_id,
'user_id' => $user_id,
),
array( '%d', '%d' )
);
}

protected function count_queue_rows( int $message_id ): int {
global $wpdb;

return (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM {$wpdb->prefix}notifications_queue WHERE message_id = %d",
$message_id
)
);
}

public function test_register_routes() {
$this->markTestSkipped( 'TODO Implement' );
}
Expand All @@ -28,7 +70,54 @@ public function test_update_item() {
}

public function test_delete_item() {
$this->markTestSkipped( 'TODO Implement' );
$this->add_queue_row( 42, self::$user_id );
$this->add_queue_row( 42, self::$other_user_id );
wp_set_current_user( self::$user_id );

$request = new WP_REST_Request( 'DELETE', '/wp-notifications/v1/notifications/42' );
$response = rest_get_server()->dispatch( $request );

$this->assertSame( 200, $response->get_status() );
$this->assertSame(
array(
'deleted' => true,
'id' => 42,
),
$response->get_data()
);
// The other user's copy of the message must stay.
$this->assertSame( 1, $this->count_queue_rows( 42 ) );
}

public function test_delete_item_logged_out() {
$this->add_queue_row( 43, self::$user_id );
wp_set_current_user( 0 );

$request = new WP_REST_Request( 'DELETE', '/wp-notifications/v1/notifications/43' );
$response = rest_get_server()->dispatch( $request );

$this->assertErrorResponse( 'rest_notifications_login_required', $response, 401 );
$this->assertSame( 1, $this->count_queue_rows( 43 ) );
}

public function test_delete_item_of_other_user() {
$this->add_queue_row( 44, self::$other_user_id );
wp_set_current_user( self::$user_id );

$request = new WP_REST_Request( 'DELETE', '/wp-notifications/v1/notifications/44' );
$response = rest_get_server()->dispatch( $request );

$this->assertErrorResponse( 'rest_notification_invalid_id', $response, 404 );
$this->assertSame( 1, $this->count_queue_rows( 44 ) );
}

public function test_delete_item_invalid_id() {
wp_set_current_user( self::$user_id );

$request = new WP_REST_Request( 'DELETE', '/wp-notifications/v1/notifications/999999' );
$response = rest_get_server()->dispatch( $request );

$this->assertErrorResponse( 'rest_notification_invalid_id', $response, 404 );
}

public function test_prepare_item() {
Expand Down
Loading