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
88 changes: 83 additions & 5 deletions includes/persistence/class-wpdb-notification-repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,90 @@ public function find_by_date_range(
/**
* Add a new notification to the repository.
*
* @param Notifications\Notification $notification Notification to add.
* @param Notifications\Model\Notification $notification Notification to add.
*
* @return int ID that the notification was stored under.
* @return int|false Notification message ID, zero if the message does not exist, or false on error.
*/
public function add( Notifications\Notification $notification ) {
// TODO: Implement query.
return -1;
public function add( Notifications\Model\Notification $notification ) {
global $wpdb;

$message_id = $notification->get_message_id();
$user_id = $notification->get_user_id();

if ( ! is_int( $message_id ) || $message_id < 1 || ! is_int( $user_id ) || $user_id < 1 ) {
return false;
}

$queue_table = $wpdb->prefix . 'notifications_queue';
$messages_table = $wpdb->prefix . 'notifications_messages';
$result = $wpdb->query(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- The table names are generated internally.
"INSERT INTO {$queue_table} (message_id, user_id) SELECT id, %d FROM {$messages_table} WHERE id = %d",
$user_id,
$message_id
)
);

if ( false === $result ) {
return false;
}

return 0 === $result ? 0 : $message_id;
}

/**
* Update state timestamps for a user's notification.
*
* @param int $id Notification message ID.
* @param int $user_id ID of the notification owner.
* @param array $state State timestamp fields to update.
*
* @return int|false One when the notification exists, zero when it does not, or false on error.
*/
public function update_state( $id, $user_id, array $state ) {
global $wpdb;

$allowed_fields = array( 'displayed_at', 'dismissed_at' );
$data = array();
$formats = array();

foreach ( $allowed_fields as $field ) {
if ( array_key_exists( $field, $state ) ) {
$data[ $field ] = $state[ $field ] ? $state[ $field ]->format( 'Y-m-d H:i:s' ) : null;
$formats[] = '%s';
}
}

if ( empty( $data ) ) {
return false;
}

$table = $wpdb->prefix . 'notifications_queue';
$exists = $wpdb->get_var(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- The table name is generated internally.
"SELECT 1 FROM {$table} WHERE message_id = %d AND user_id = %d LIMIT 1",
$id,
$user_id
)
);

if ( null === $exists ) {
return 0;
}

$result = $wpdb->update(
$table,
$data,
array(
'message_id' => $id,
'user_id' => $user_id,
),
$formats,
array( '%d', '%d' )
);

return false === $result ? false : 1;
}
}
17 changes: 14 additions & 3 deletions includes/persistence/interface-notification-repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,20 @@ public function find_latest(
/**
* Add a new notification to the repository.
*
* @param Notifications\Notification $notification Notification to add.
* @param Notifications\Model\Notification $notification Notification to add.
*
* @return int ID that the notification was stored under.
* @return int|false Notification message ID, zero if the message does not exist, or false on error.
*/
public function add( Notifications\Notification $notification );
public function add( Notifications\Model\Notification $notification );

/**
* Update state timestamps for a user's notification.
*
* @param int $id Notification message ID.
* @param int $user_id ID of the notification owner.
* @param array $state State timestamp fields to update.
*
* @return int|false One when the notification exists, zero when it does not, or false on error.
*/
public function update_state( $id, $user_id, array $state );
}
211 changes: 210 additions & 1 deletion includes/restapi/class-notification-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,29 @@

namespace WP\Notifications\REST;

use DateTimeImmutable;
use DateTimeZone;
use WP_Error;
use WP_REST_Controller;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;
use WP\Notifications\Factory\Notification as Notification_Factory;
use WP\Notifications\Persistence\Notification_Repository;
use WP\Notifications\Persistence\Wpdb_Notification_Repository;

/**
* REST API Notification Controller class
*/
class Notification_Controller extends WP_REST_Controller {

/**
* Notification persistence.
*
* @var Notification_Repository
*/
private $repository;

/**
* Namespace for the REST endpoint.
*
Expand All @@ -32,6 +44,15 @@ class Notification_Controller extends WP_REST_Controller {
*/
public const NOTIFICATION_BASE = 'notifications';

/**
* Create a notification controller.
*
* @param Notification_Repository|null $repository Notification persistence.
*/
public function __construct( ?Notification_Repository $repository = null ) {
$this->repository = $repository ? $repository : new Wpdb_Notification_Repository();
}

/**
* Register REST routes
*
Expand All @@ -48,12 +69,201 @@ public function register_routes(): void {
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the notification message.' ),
'type' => 'integer',
'minimum' => 1,
'required' => true,
),
),
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
),
false
);

register_rest_route(
self::NAMESPACE,
self::NOTIFICATION_BASE . '/(?P<id>[\d]+)',
array(
array(
'methods' => 'PUT',
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the notification.' ),
'type' => 'integer',
'minimum' => 1,
),
'dismissed_at' => array(
'description' => __( 'The datetime the notification was dismissed, in UTC time.' ),
'type' => array( 'string', 'null' ),
'format' => 'date-time',
),
'displayed_at' => array(
'description' => __( 'The datetime the notification was displayed, in UTC time.' ),
'type' => array( 'string', 'null' ),
'format' => 'date-time',
),
),
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
),
false
);
}

/**
* Checks whether the request can create a notification.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return true|WP_Error True when allowed, otherwise an error.
*/
public function create_item_permissions_check( $request ) {
if ( ! is_user_logged_in() ) {
return new WP_Error(
'rest_notifications_login_required',
__( 'Sorry, you must be logged in to create notifications.' ),
array( 'status' => 401 )
);
}

return true;
}

/**
* Add a notification for the current user.
*
* @param WP_REST_Request $request Received REST request.
*
* @return WP_REST_Response|WP_Error REST response or WP Error.
*/
public function create_item( $request ) {
$factory = new Notification_Factory();
$notification = $factory->make(
array(
'message_id' => $request['id'],
'user_id' => get_current_user_id(),
)
);
$result = $this->repository->add( $notification );

if ( false === $result ) {
return new WP_Error(
'rest_notification_create_failed',
__( 'The notification could not be created.' ),
array( 'status' => 500 )
);
}

if ( 0 === $result ) {
return new WP_Error(
'rest_notification_message_not_found',
__( 'The notification message could not be found.' ),
array( 'status' => 404 )
);
}

$response = new WP_REST_Response(
array(
'id' => $result,
'user_id' => get_current_user_id(),
'dismissed_at' => null,
'displayed_at' => null,
),
201
);
$response->header( 'Location', rest_url( self::NAMESPACE . '/' . self::NOTIFICATION_BASE . '/' . $result ) );

return $response;
}

/**
* Checks whether the request can update a notification.
*
* @param WP_REST_Request $request Full details about the request.
*
* @return true|WP_Error True when allowed, otherwise an error.
*/
public function update_item_permissions_check( $request ) {
if ( ! is_user_logged_in() ) {
return new WP_Error(
'rest_notifications_login_required',
__( 'Sorry, you must be logged in to update notifications.' ),
array( 'status' => 401 )
);
}

return true;
}

/**
* Update a notification for the current user.
*
* @param WP_REST_Request $request Received REST request.
*
* @return WP_REST_Response|WP_Error REST response or WP Error.
*/
public function update_item( $request ) {
$state = array();

foreach ( array( 'displayed_at', 'dismissed_at' ) as $field ) {
if ( $request->has_param( $field ) ) {
$state[ $field ] = null;

if ( null !== $request[ $field ] ) {
$date = new DateTimeImmutable( $request[ $field ] );
$state[ $field ] = $date->setTimezone( new DateTimeZone( 'UTC' ) );
}
}
}

if ( empty( $state ) ) {
return new WP_Error(
'rest_notification_state_required',
__( 'At least one notification state field is required.' ),
array( 'status' => 400 )
);
}

$result = $this->repository->update_state(
$request['id'],
get_current_user_id(),
$state
);

if ( false === $result ) {
return new WP_Error(
'rest_notification_update_failed',
__( 'The notification could not be updated.' ),
array( 'status' => 500 )
);
}

if ( 0 === $result ) {
return new WP_Error(
'rest_notification_not_found',
__( 'The notification could not be found.' ),
array( 'status' => 404 )
);
}

$response = array( 'id' => (int) $request['id'] );

foreach ( $state as $field => $date ) {
$response[ $field ] = $date ? $date->format( DATE_RFC3339 ) : null;
}

return rest_ensure_response( $response );
}

/**
* Checks if a given request has access to read notifications.
*
Expand Down Expand Up @@ -157,7 +367,6 @@ public function get_item_schema(): array {
'type' => array( 'string', 'null' ),
'format' => 'date-time',
'context' => array( 'view', 'embed' ),
'readonly' => true,
),
'expires_at' => array(
'description' => __( 'The datetime the notification expires, in UTC time.' ),
Expand Down
Loading
Loading