From 33f6353a8d85c20744d1450f41d6187a4b092a76 Mon Sep 17 00:00:00 2001 From: John Hooks Date: Wed, 19 Apr 2023 15:50:34 -0700 Subject: [PATCH 1/4] fix: clean php includes of dead code --- includes/class-aggregate-factory.php | 89 -------- includes/class-base-notification.php | 199 ------------------ includes/class-factory.php | 98 --------- .../class-failed-to-add-recipient.php | 23 -- .../exceptions/class-invalid-recipient.php | 23 -- includes/exceptions/class-invalid-type.php | 32 --- includes/image/interface-image.php | 2 +- includes/interface-json-unserializable.php | 15 -- includes/interface-notification.php | 2 +- includes/interface-status.php | 9 +- includes/messages/class-aggregate-factory.php | 20 -- includes/messages/class-base-factory.php | 62 ------ includes/messages/class-base-message.php | 63 ------ includes/messages/interface-factory.php | 26 --- includes/messages/interface-message.php | 26 --- .../class-wpdb-notification-repository.php | 23 -- .../interface-notification-repository.php | 20 -- .../recipients/class-aggregate-factory.php | 20 -- includes/recipients/class-base-factory.php | 99 --------- includes/recipients/class-collection.php | 134 ------------ includes/recipients/class-role.php | 16 -- includes/recipients/class-user.php | 38 ---- includes/recipients/interface-factory.php | 25 --- includes/recipients/interface-recipient.php | 7 - includes/senders/class-base-sender.php | 92 -------- includes/senders/interface-factory.php | 15 -- includes/senders/interface-sender.php | 9 - tests/phpunit/includes/bootstrap.php | 1 - .../phpunit/includes/class-dummy-message.php | 25 --- tests/phpunit/tests/test-base-message.php | 28 --- .../phpunit/tests/test-base-notification.php | 43 ---- tests/phpunit/tests/test-factory.php | 41 ---- .../tests/test-notification-repository.php | 27 --- .../tests/test-recipient-collection.php | 97 --------- tests/phpunit/tests/test-role-recipient.php | 18 -- tests/phpunit/tests/test-user-recipient.php | 42 ---- wp-feature-notifications.php | 22 +- 37 files changed, 9 insertions(+), 1522 deletions(-) delete mode 100644 includes/class-aggregate-factory.php delete mode 100644 includes/class-base-notification.php delete mode 100644 includes/class-factory.php delete mode 100644 includes/exceptions/class-failed-to-add-recipient.php delete mode 100644 includes/exceptions/class-invalid-recipient.php delete mode 100644 includes/exceptions/class-invalid-type.php delete mode 100644 includes/interface-json-unserializable.php delete mode 100644 includes/messages/class-aggregate-factory.php delete mode 100644 includes/messages/class-base-factory.php delete mode 100644 includes/messages/class-base-message.php delete mode 100644 includes/messages/interface-factory.php delete mode 100644 includes/messages/interface-message.php delete mode 100644 includes/recipients/class-aggregate-factory.php delete mode 100644 includes/recipients/class-base-factory.php delete mode 100644 includes/recipients/class-collection.php delete mode 100644 includes/recipients/class-role.php delete mode 100644 includes/recipients/class-user.php delete mode 100644 includes/recipients/interface-factory.php delete mode 100644 includes/recipients/interface-recipient.php delete mode 100644 includes/senders/class-base-sender.php delete mode 100644 includes/senders/interface-factory.php delete mode 100644 includes/senders/interface-sender.php delete mode 100644 tests/phpunit/includes/class-dummy-message.php delete mode 100644 tests/phpunit/tests/test-base-message.php delete mode 100644 tests/phpunit/tests/test-base-notification.php delete mode 100644 tests/phpunit/tests/test-factory.php delete mode 100644 tests/phpunit/tests/test-notification-repository.php delete mode 100644 tests/phpunit/tests/test-recipient-collection.php delete mode 100644 tests/phpunit/tests/test-role-recipient.php delete mode 100644 tests/phpunit/tests/test-user-recipient.php diff --git a/includes/class-aggregate-factory.php b/includes/class-aggregate-factory.php deleted file mode 100644 index 8a23d541..00000000 --- a/includes/class-aggregate-factory.php +++ /dev/null @@ -1,89 +0,0 @@ -register( $factory ); - } - } - - /** - * Register a new factory implementation with the aggregate factory. - * - * @param object $factory Factory to register. - */ - public function register( $factory ) { - if ( ! is_a( $factory, $this->get_interface() ) ) { - // TODO: Throw exception. - } - - $factory_class = get_class( $factory ); - - if ( ! array_key_exists( $factory_class, $this->factories ) ) { - $this->factories[ $factory_class ] = $factory; - } - } - - /** - * Create a new instance of the interface the factory represents. - * - * @param mixed $value Value to use for creation. - * @param string $type Optional. Type to use for creation. - * - * @return mixed - */ - public function create( $value, $type = null ) { - foreach ( $this->factories as $factory ) { - if ( $factory->accepts( $type ) ) { - return $factory->create( $value, $type ); - } - } - - // TODO: Throw exception. - } - - /** - * Whether the factory accepts a given type for instantiation. - * - * @param string $type Type that should be instantiated. - * - * @return bool Whether the factory accepts the given type. - */ - public function accepts( $type ) { - foreach ( $this->factories as $factory ) { - if ( $factory->accepts( $type ) ) { - return true; - } - } - - return false; - } - - /** - * Get the interface that this aggregate factory can instantiate - * implementations of. - * - * @return string Class name of the interface. - */ - abstract protected function get_interface(); -} diff --git a/includes/class-base-notification.php b/includes/class-base-notification.php deleted file mode 100644 index a797345f..00000000 --- a/includes/class-base-notification.php +++ /dev/null @@ -1,199 +0,0 @@ -sender = $sender; - $this->recipients = $recipients; - $this->message = $message; - $this->timestamp = $this->validate_timestamp( $timestamp ); - $this->id = $id; - } - - /** - * Get the ID of the notification. - * - * @return int ID of the notification, -1 if none was attributed yet. - */ - public function get_id() { - return $this->id; - } - - /** - * Get the timestamp of the notification. - * - * @return int Timestamp of the notification. - */ - public function get_timestamp() { - return $this->timestamp; - } - - /** - * Validate a timestamp. - * - * @param mixed $timestamp Timestamp to validate. - * - * @return int Validated timestamp. - */ - protected function validate_timestamp( $timestamp ) { - if ( null === $timestamp ) { - $timestamp = time(); - } - - return $timestamp; - } - - /** - * Get the sender of the notification. - * - * @return Senders\Sender Sender of the notification. - */ - public function get_sender() { - return $this->sender; - } - - /** - * Gets the recipients for the notification. - * - * @return Recipients\Collection Notification recipients. - */ - public function get_recipients() { - return $this->recipients; - } - - /** - * Gets the message for the notification. - * - * @return Messages\Message Notification message. - */ - public function get_message() { - return $this->message; - } - - /** - * Get the current status of the notification. - * - * @return string Status of the notification. - */ - public function get_status() { - return $this->status; - } - - /** - * Set the status of the current notification. - * - * @param string $status Status to set the notification to. - * - * @return $this - */ - public function set_status( $status ) { - $this->status = $status; - - return $this; - } - - /** - * Specifies data which should be serialized to JSON. - * - * @return mixed Data which can be serialized by json_encode, which is a - * value of any type other than a resource. - */ - public function jsonSerialize() { - return array( - get_class( $this->recipients ) => $this->recipients, - get_class( $this->message ) => $this->message, - get_class( $this->sender ) => $this->sender, - ); - } - - /** - * Creates a new instance from JSON-encoded data. - * - * @param string $json JSON-encoded data to create the instance from. - * - * @return self - */ - public static function json_unserialize( $json ) { - $data = json_decode( $json ); - - reset( $data ); - $recipients_class = key( $data ); - $recipients = new $recipients_class( current( $data ) ); - - next( $data ); - $message_class = key( $data ); - $message = new $message_class( current( $data ) ); - - next( $data ); - $sender_class = key( $data ); - $sender_reflection = new ReflectionClass( $sender_class ); - $sender = $sender_reflection->newInstanceArgs( array_values( (array) current( $data ) ) ); - - return new self( $sender, $recipients, $message ); - } -} diff --git a/includes/class-factory.php b/includes/class-factory.php deleted file mode 100644 index 8774731c..00000000 --- a/includes/class-factory.php +++ /dev/null @@ -1,98 +0,0 @@ -message_factory = $message_factory; - $this->recipient_factory = $recipient_factory; - $this->sender_factory = $sender_factory; - } - - /** - * Create a notification instance - * - * @param $args - * - * @return Base_Notification - */ - public function create( $args ) { - - list( $message_args, $recipients_args, $sender_args ) = $this->validate( $args ); - - $sender = $this->sender_factory->create( $sender_args ); - $recipients = new Recipients\Collection(); - $message = $this->message_factory->create( $message_args ); - - foreach ( $recipients_args as $type => $value ) { - $recipients->add( - $this->recipient_factory->create( $value, $type ) - ); - } - - return new Base_Notification( $sender, $recipients, $message ); - } - - private function validate( $args ) { - // TODO: Validate args. - if ( is_string( $args ) ) { - $args = json_decode( $args ); - } - - list( $message_args, $recipients_args, $sender_args ) = $args; - - return array( - $this->get_message_args( $message_args ), - $this->get_recipients_args( $recipients_args ), - $this->get_sender_args( $sender_args ), - ); - } - - private function get_message_args( $args ) { - // TODO: Parse message args. - return $args; - } - - private function get_recipients_args( $args ) { - // TODO: Parse recipients args. - return $args; - } - - public function get_sender_args( $args ) { - // TODO: Parse sender args. - return $args; - } -} diff --git a/includes/exceptions/class-failed-to-add-recipient.php b/includes/exceptions/class-failed-to-add-recipient.php deleted file mode 100644 index 11cc466b..00000000 --- a/includes/exceptions/class-failed-to-add-recipient.php +++ /dev/null @@ -1,23 +0,0 @@ -'; - - $message = sprintf( - /* translators: "%1$s" is a type of recipient and "%2$s" is recipient's name or identifier. */ - __( - 'Notification user recipient of type "%1$s" and value "%2$s" did not validate as a valid user ID.', - 'wp-notification-center' - ), - $type, - $value - ); - - return new self( $message ); - } -} diff --git a/includes/exceptions/class-invalid-type.php b/includes/exceptions/class-invalid-type.php deleted file mode 100644 index aa442229..00000000 --- a/includes/exceptions/class-invalid-type.php +++ /dev/null @@ -1,32 +0,0 @@ -accepts( $type ) ) { - throw Exceptions\Invalid_Type::from_message_type( $type ); - } - - list( $type, $value ) = $this->validate( $type, $value ); - - switch ( $type ) { - case self::TYPE_STANDARD: - default: - return new Base_Message( $value ); - } - } - - /** - * Whether the factory accepts a given type for instantiation. - * - * @param string $type Type that should be instantiated. - * - * @return bool Whether the factory accepts the given type. - */ - public function accepts( $type ) { - $accepted_types = array( self::TYPE_STANDARD ); - - return in_array( $type, $accepted_types, true ); - } - - /** - * Validate provided arguments. - * - * @param string $type Type of the message to create. - * @param mixed $value Value of the message to create. - * - * @return array - */ - private function validate( $type, $value ) { - // TODO: Validate arguments. - - return array( $type, $value ); - } -} diff --git a/includes/messages/class-base-message.php b/includes/messages/class-base-message.php deleted file mode 100644 index 2300b3e3..00000000 --- a/includes/messages/class-base-message.php +++ /dev/null @@ -1,63 +0,0 @@ -message = $message; - } - - /** - * Get the message content. - * - * @return string Message content. - */ - public function get_content() { - return (string) $this->message; - } - - /** - * Convert the message object into a string representing its content. - * - * @return string Message content as a string. - */ - public function __toString() { - return $this->get_content(); - } - - /** - * Specifies data which should be serialized to JSON. - * - * @return mixed Data which can be serialized by json_encode, which is a - * value of any type other than a resource. - */ - public function jsonSerialize() { - return $this->message; - } - - /** - * Creates a new instance from JSON-encoded data. - * - * @param string $json JSON-encoded data to create the instance from. - * - * @return self - */ - public static function json_unserialize( $json ) { - $message = json_decode( $json ); - - return new self( $message ); - } -} diff --git a/includes/messages/interface-factory.php b/includes/messages/interface-factory.php deleted file mode 100644 index 1e6aaa10..00000000 --- a/includes/messages/interface-factory.php +++ /dev/null @@ -1,26 +0,0 @@ -accepts( $type ) ) { - throw Exceptions\Invalid_Type::from_recipient_type( $type ); - } - - list( $type, $value ) = $this->validate( $type, $value ); - - $class = $this->get_implementation_for_type( $type ); - - return new $class( $value ); - } - - /** - * Whether the factory accepts a given type for instantiation. - * - * @param string $type Type that should be instantiated. - * - * @return bool Whether the factory accepts the given type. - */ - public function accepts( $type ) { - return in_array( $type, $this->get_accepted_types(), true ); - } - - /** - * Get the corresponding implementation class for a given type. - * - * @param string $type Type to get the implementation class for. - * - * @return string Implementation class. - * @throws Exceptions\Invalid_Type If the recipient type was not valid. - */ - public function get_implementation_for_type( $type ) { - if ( ! $this->accepts( $type ) ) { - throw Exceptions\Invalid_Type::from_recipient_type( $type ); - } - - $mappings = $this->get_type_mappings(); - - return $mappings[ $type ]; - } - - /** - * Validate provided arguments. - * - * @param string $type Type of the recipient to create. - * @param mixed $value Value of the recipient to create. - * - * @return array Validated arguments. - */ - private function validate( $type, $value ) { - // TODO: Validate arguments. - - return array( $type, $value ); - } - - /** - * Get an array of type to class mappings. - * - * @return array - */ - private function get_type_mappings() { - return apply_filters( - 'wp_feature_notifications_recipient_type_mappings', - array( - self::TYPE_USER => 'User_Recipient', - self::TYPE_ROLE => 'Role_Recipient', - ) - ); - } - - /** - * Get an array of accepted type identifiers. - * - * @return array - */ - private function get_accepted_types() { - return array_keys( $this->get_type_mappings() ); - } -} diff --git a/includes/recipients/class-collection.php b/includes/recipients/class-collection.php deleted file mode 100644 index d5ef6c4b..00000000 --- a/includes/recipients/class-collection.php +++ /dev/null @@ -1,134 +0,0 @@ -recipients = $this->validate_recipients( $recipients ); - } - - /** - * Validate the recipients. - * - * @param Recipient|array $recipients Recipient or array of - * recipients to validate. - * - * @return array Validated array of recipients. - * @throws Failed_To_Add_Recipient If a recipient could not be added. - */ - protected function validate_recipients( $recipients ) { - if ( ! is_array( $recipients ) ) { - $recipients = array( $recipients ); - } - - foreach ( $recipients as $recipient ) { - if ( ! $recipient instanceof Recipient ) { - throw Exceptions\Failed_To_Add_Recipient::from_invalid_recipient( $recipient ); - } - } - - return $recipients; - } - - public function add( Recipient $recipient ) { - $this->recipients[] = $recipient; - } - - public function count(): int { - return count( $this->recipients ); - } - - /** - * Return the current recipient. - * - * @return Recipient Recipient - */ - public function current() { - return current( $this->recipients ); - } - - /** - * Move forward to next recipient - * - * @return void Any returned value is ignored. - */ - public function next() { - next( $this->recipients ); - } - - /** - * Return the key of the current recipient - * - * @return mixed scalar on success, or null on failure. - */ - public function key() { - return key( $this->recipients ); - } - - /** - * Checks if current position is valid - * - * @return boolean The return value will be casted to boolean and then - * evaluated. Returns true on success or false on failure. - */ - public function valid() { - // TODO: Implement valid() method. - } - - /** - * Rewind the Iterator to the first recipient. - * - * @return void Any returned value is ignored. - */ - public function rewind() { - reset( $this->recipients ); - } - - /** - * Specifies data which should be serialized to JSON. - * - * @return mixed Data which can be serialized by json_encode, which is a - * value of any type other than a resource. - */ - public function jsonSerialize() { - return $this->recipients; - } - - /** - * Creates a new instance from JSON-encoded data. - * - * @param string $json JSON-encoded data to create the instance from. - * - * @return self - */ - public static function json_unserialize( $json ) { - $recipients = json_decode( $json ); - - return new self( $recipients ); - } -} diff --git a/includes/recipients/class-role.php b/includes/recipients/class-role.php deleted file mode 100644 index 78273cd3..00000000 --- a/includes/recipients/class-role.php +++ /dev/null @@ -1,16 +0,0 @@ -role = $role; - } - - public function get_role() { - return $this->role; - } -} diff --git a/includes/recipients/class-user.php b/includes/recipients/class-user.php deleted file mode 100644 index 41c31c78..00000000 --- a/includes/recipients/class-user.php +++ /dev/null @@ -1,38 +0,0 @@ -user_id = $this->validate( $user_id ); - } - - public function get_user_id() { - return $this->user_id; - } - - public function get_user_object() { - if ( null === $this->user_object ) { - $this->user_object = new WP_User( $this->user_id ); - } - - return $this->user_object; - } - - private function validate( $user_id ) { - if ( ! is_numeric( $user_id ) - || ! ( ( (int) $user_id ) > 0 ) ) { - throw Invalid_Recipient::from_invalid_user_id( $user_id ); - } - - return $user_id; - } -} diff --git a/includes/recipients/interface-factory.php b/includes/recipients/interface-factory.php deleted file mode 100644 index 378e1f8d..00000000 --- a/includes/recipients/interface-factory.php +++ /dev/null @@ -1,25 +0,0 @@ -name = $name; - $this->image = $image; - } - - /** - * @return array - */ - public function jsonSerialize() { - - $data = array( - 'name' => $this->name, - ); - - if ( $this->image ) { - $data[ get_class( $this->image ) ] = $this->image; - } - - return $data; - } - - /** - * Creates a new instance from JSON-encoded data. - * - * @param string $json JSON-encoded data to create the instance from. - * - * @return self - */ - public static function json_unserialize( $json ) { - - $data = json_decode( $json, true ); - - reset( $data ); - $name = current( $data ); - - next( $data ); - $class_name = key( $data ); - $image_data = current( $data ); - - $image = null; - - if ( ! empty( $image_data ) && is_subclass_of( $class_name, 'Image' ) ) { - $image_reflection = new ReflectionClass( $class_name ); - $image = $image_reflection->newInstanceArgs( array_values( $image_data ) ); - } - - return new self( $name, $image ); - } - - /** - * Gets the name of the sender - * - * @return string - */ - public function get_name() { - - return $this->name; - } - - /** - * @return Base_Image|null - */ - public function get_image() { - return $this->image; - } -} diff --git a/includes/senders/interface-factory.php b/includes/senders/interface-factory.php deleted file mode 100644 index 8391844b..00000000 --- a/includes/senders/interface-factory.php +++ /dev/null @@ -1,15 +0,0 @@ -assertInstanceOf( '\WP\Notifications\Messages\Base_Message', $testee ); - } - - public function test_it_implements_the_interface() { - $testee = new Messages\Base_Message( 'Message' ); - $this->assertInstanceOf( '\WP\Notifications\Messages\Message', $testee ); - } - - public function test_it_can_return_its_content() { - $testee = new Messages\Base_Message( 'Message' ); - $this->assertEquals( 'Message', $testee->get_content() ); - } - - public function test_it_can_be_cast_to_string() { - $testee = new Messages\Base_Message( 'Message' ); - $this->assertEquals( 'Message', (string) $testee ); - } -} diff --git a/tests/phpunit/tests/test-base-notification.php b/tests/phpunit/tests/test-base-notification.php deleted file mode 100644 index cb509f1c..00000000 --- a/tests/phpunit/tests/test-base-notification.php +++ /dev/null @@ -1,43 +0,0 @@ -createMock( '\WP\Notifications\Senders\Base_Sender' ); - $recipients_mock = $this->createMock( '\WP\Notifications\Recipients\Collection' ); - $testee = new Notifications\Base_Notification( - $sender_mock, - $recipients_mock, - new Dummy_Message() - ); - $this->assertInstanceOf( '\WP\Notifications\Base_Notification', $testee ); - } - - public function test_it_implements_the_interface() { - $sender_mock = $this->createMock( '\WP\Notifications\Senders\Base_Sender' ); - $recipients_mock = $this->createMock( '\WP\Notifications\Recipients\Collection' ); - $testee = new Notifications\Base_Notification( - $sender_mock, - $recipients_mock, - new Dummy_Message() - ); - $this->assertInstanceOf( '\WP\Notifications\Notification', $testee ); - } - - public function test_it_can_return_its_content() { - $sender_mock = $this->createMock( '\WP\Notifications\Senders\Base_Sender' ); - $recipients_mock = $this->createMock( '\WP\Notifications\Recipients\Collection' ); - $dummy_message = new Dummy_Message(); - $testee = new Notifications\Base_Notification( - $sender_mock, - $recipients_mock, - $dummy_message - ); - $this->assertEquals( $recipients_mock, $testee->get_recipients() ); - $this->assertEquals( $dummy_message, $testee->get_message() ); - } -} diff --git a/tests/phpunit/tests/test-factory.php b/tests/phpunit/tests/test-factory.php deleted file mode 100644 index 61caaf75..00000000 --- a/tests/phpunit/tests/test-factory.php +++ /dev/null @@ -1,41 +0,0 @@ -createMock( '\WP\Notifications\Senders\Sender' ); - - $message_factory = $this->getMockBuilder( '\WP\Notifications\Messages\Factory' ) - ->setMethods( array( 'create', 'accepts' ) ) - ->getMock(); - - $message_factory->method( 'create' )->willReturn( $this->createMock( '\WP\Notifications\Messages\Message' ) ); - $message_factory->method( 'accepts' )->willReturn( true ); - - $sender_factory = $this->getMockBuilder( '\WP\Notifications\Senders\Factory' ) - ->setMethods( array( 'create' ) ) - ->getMock(); - - $sender_factory->method( 'create' )->willReturn( $this->createMock( '\WP\Notifications\Senders\Sender' ) ); - - $factory = new Notifications\Factory( - $message_factory, - $this->createMock( '\WP\Notifications\Recipients\Factory' ), - $sender_factory - ); - - $args = array( - array(), - array(), - array(), - ); - - $notification = $factory->create( $args ); - $this->assertEquals( $vendor_sender, $notification->get_sender() ); - } -} diff --git a/tests/phpunit/tests/test-notification-repository.php b/tests/phpunit/tests/test-notification-repository.php deleted file mode 100644 index 19dc9e89..00000000 --- a/tests/phpunit/tests/test-notification-repository.php +++ /dev/null @@ -1,27 +0,0 @@ -find_by_id( $id ); - $this->assertFalse( $result ); - } - - public function data_provider_it_returns_false_on_invalid_ids() { - return array( - array( - 1 ), - array( 0 ), - array( 'nonsense' ), - array( array() ), - array( new stdClass() ), - ); - } -} diff --git a/tests/phpunit/tests/test-recipient-collection.php b/tests/phpunit/tests/test-recipient-collection.php deleted file mode 100644 index a28423b4..00000000 --- a/tests/phpunit/tests/test-recipient-collection.php +++ /dev/null @@ -1,97 +0,0 @@ -assertInstanceOf( '\WP\Notifications\Recipients\Collection', $testee ); - } - - public function test_it_is_countable() { - $testee = new Recipients\Collection(); - $this->assertInstanceOf( 'Countable', $testee ); - } - - public function test_it_is_traversable() { - $testee = new Recipients\Collection(); - $this->assertInstanceOf( 'Traversable', $testee ); - } - - public function test_it_is_empty_when_instantiated_without_arguments() { - $testee = new Recipients\Collection(); - $this->assertCount( 0, $testee ); - } - - public function test_it_can_accept_a_singular_recipient() { - $mock_recipient = $this->createMock( '\WP\Notifications\Recipients\Recipient' ); - $testee = new Recipients\Collection( $mock_recipient ); - $this->assertCount( 1, $testee ); - } - - public function test_it_can_accept_an_array_of_recipients() { - $mock_recipient_1 = $this->createMock( '\WP\Notifications\Recipients\Recipient' ); - $mock_recipient_2 = $this->createMock( '\WP\Notifications\Recipients\Recipient' ); - $mock_recipient_3 = $this->createMock( '\WP\Notifications\Recipients\Recipient' ); - $testee = new Recipients\Collection( - array( - $mock_recipient_1, - $mock_recipient_2, - $mock_recipient_3, - ) - ); - $this->assertCount( 3, $testee ); - } - - public function test_recipients_can_be_added() { - $mock_recipient_1 = $this->createMock( '\WP\Notifications\Recipients\Recipient' ); - $mock_recipient_2 = $this->createMock( '\WP\Notifications\Recipients\Recipient' ); - $mock_recipient_3 = $this->createMock( '\WP\Notifications\Recipients\Recipient' ); - $testee = new Recipients\Collection( $mock_recipient_1 ); - $this->assertCount( 1, $testee ); - $testee->add( $mock_recipient_2 ); - $this->assertCount( 2, $testee ); - $testee->add( $mock_recipient_3 ); - $this->assertCount( 3, $testee ); - } - - /** @dataProvider data_provider_it_throws_on_invalid_type */ - public function test_it_throws_on_invalid_type( $invalid_recipient ) { - $this->expectException( '\WP\Notifications\Exceptions\Runtime_Exception' ); - new Recipients\Collection( $invalid_recipient ); - } - - public function data_provider_it_throws_on_invalid_type() { - $mock_recipient_1 = $this->createMock( '\WP\Notifications\Recipients\Recipient' ); - $mock_recipient_2 = $this->createMock( '\WP\Notifications\Recipients\Recipient' ); - - return array( - array( null ), - array( true ), - array( new stdClass ), - array( 'invalid' ), - array( array( 1, 2, 3 ) ), - array( array( $mock_recipient_1, 'invalid', $mock_recipient_2 ) ), - ); - } - - public function test_it_can_be_json_encoded() { - $testee = new Recipients\Collection( array() ); - $this->assertEquals( - '[]', - json_encode( $testee ) - ); - } - - public function test_it_can_be_instantiated_from_json() { - $json = '[]'; - $testee = Recipients\Collection::json_unserialize( $json ); - $this->assertInstanceOf( '\WP\Notifications\Recipients\Collection', $testee ); - $this->assertEquals( 0, $testee->count() ); - } -} diff --git a/tests/phpunit/tests/test-role-recipient.php b/tests/phpunit/tests/test-role-recipient.php deleted file mode 100644 index d92a6092..00000000 --- a/tests/phpunit/tests/test-role-recipient.php +++ /dev/null @@ -1,18 +0,0 @@ -assertInstanceOf( '\WP\Notifications\Recipients\Role', $testee ); - } - - public function test_it_implements_the_interface() { - $testee = new Recipients\Role( 1 ); - $this->assertInstanceOf( '\WP\Notifications\Recipients\Recipient', $testee ); - } -} diff --git a/tests/phpunit/tests/test-user-recipient.php b/tests/phpunit/tests/test-user-recipient.php deleted file mode 100644 index 60ed2d72..00000000 --- a/tests/phpunit/tests/test-user-recipient.php +++ /dev/null @@ -1,42 +0,0 @@ -assertInstanceOf( '\WP\Notifications\Recipients\User', $testee ); - } - - public function test_it_implements_the_recipient_interface() { - $testee = new Recipients\User( 1 ); - $this->assertInstanceOf( '\WP\Notifications\Recipients\Recipient', $testee ); - } - - public function test_it_accepts_a_user_id_as_a_string() { - $testee = new Recipients\User( '1' ); - $this->assertInstanceOf( '\WP\Notifications\Recipients\Recipient', $testee ); - } - - /** @dataProvider data_provider_it_throws_on_invalid_type */ - public function test_it_throws_on_invalid_type( $invalid_user_id ) { - $this->expectException( '\WP\Notifications\Exceptions\Invalid_Recipient' ); - new Recipients\User( $invalid_user_id ); - } - - public function data_provider_it_throws_on_invalid_type() { - return array( - array( null ), - array( - 1 ), - array( 0 ), - array( 'invalid' ), - array( new stdClass ), - array( array( 1, 2 ) ), - ); - } -} diff --git a/wp-feature-notifications.php b/wp-feature-notifications.php index 0132681f..62d3f5fb 100644 --- a/wp-feature-notifications.php +++ b/wp-feature-notifications.php @@ -32,32 +32,12 @@ } // Require interface/class declarations.. + require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/exceptions/interface-exception.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/exceptions/class-runtime-exception.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/exceptions/class-invalid-recipient.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/exceptions/class-failed-to-add-recipient.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/interface-json-unserializable.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/interface-status.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/interface-notification.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/class-factory.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/class-aggregate-factory.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/class-base-notification.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/senders/interface-sender.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/senders/class-base-sender.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/recipients/interface-recipient.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/recipients/class-collection.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/recipients/class-user.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/recipients/class-role.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/recipients/interface-factory.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/recipients/class-base-factory.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/recipients/class-aggregate-factory.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/messages/interface-message.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/messages/class-base-message.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/messages/interface-factory.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/messages/class-base-factory.php'; -require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/messages/class-aggregate-factory.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'; From 87d2c1f8a899df2898b54cc886207fc0607c6a0b Mon Sep 17 00:00:00 2001 From: John Hooks Date: Thu, 20 Apr 2023 06:36:11 -0700 Subject: [PATCH 2/4] feature: add initial model classes --- includes/helper/class-serde.php | 60 +++++ includes/model/class-channel.php | 135 +++++++++++ includes/model/class-message.php | 309 ++++++++++++++++++++++++++ includes/model/class-notification.php | 195 ++++++++++++++++ includes/model/class-subscription.php | 117 ++++++++++ wp-feature-notifications.php | 6 +- 6 files changed, 821 insertions(+), 1 deletion(-) create mode 100644 includes/helper/class-serde.php create mode 100644 includes/model/class-channel.php create mode 100644 includes/model/class-message.php create mode 100644 includes/model/class-notification.php create mode 100644 includes/model/class-subscription.php diff --git a/includes/helper/class-serde.php b/includes/helper/class-serde.php new file mode 100644 index 00000000..131d08a5 --- /dev/null +++ b/includes/helper/class-serde.php @@ -0,0 +1,60 @@ +format( DateTime::ATOM ); + } + + /** + * Maybe deserialize a datetime string in MySQL format. + * + * @param string|DateTime|null $date The possible MySQL datetime to deserialize. + * + * @return DateTime|null Maybe a DateTime object. + */ + public static function maybe_deserialize_mysql_date( $date ) { + if ( null === $date ) { + return null; + } + + if ( $date instanceof DateTime ) { + return $date; + } + + if ( is_string( $date ) ) { + $date = DateTime::createFromFormat( 'Y-m-d H:i:s', $date ); + + if ( false === $date ) { + $date = null; + } + } + + return $date; + } +} diff --git a/includes/model/class-channel.php b/includes/model/class-channel.php new file mode 100644 index 00000000..f8a64f06 --- /dev/null +++ b/includes/model/class-channel.php @@ -0,0 +1,135 @@ +name = $name; + $this->title = $title; + + // Optional properties + + $this->context = $context; + $this->icon = $icon; + $this->description = $description; + } + + /** + * Specifies data which should be serialized to JSON. + * + * @return mixed Data which can be serialized by json_encode, which is a + * value of any type other than a resource. + */ + public function jsonSerialize(): mixed { + return array( + 'name' => $this->name, + 'title' => $this->title, + 'context' => $this->context, + 'icon' => $this->icon, + 'description' => $this->description, + ); + } + + /** + * Get the namespaced name. + * + * @return string The namespaced name of the channel. + */ + public function get_name(): string { + return $this->name; + } + + /** + * Get the human-readable label. + * + * @return string The title of the channel. + */ + public function get_title(): string { + return $this->title; + } + + /** + * Get the default display context. + * + * @return ?string The context of the channel. + */ + public function get_context(): ?string { + return $this->context; + } + + /** + * Get the detailed description. + * + * @return ?string The description of the channel. + */ + public function get_description(): ?string { + return $this->description; + } + + /** + * Get the icon. + * + * @return ?string The icon of the channel. + */ + public function get_icon(): ?string { + return $this->icon; + } +} diff --git a/includes/model/class-message.php b/includes/model/class-message.php new file mode 100644 index 00000000..2dd49e81 --- /dev/null +++ b/includes/model/class-message.php @@ -0,0 +1,309 @@ +message = $message; + $this->accept_label = $accept_label; + $this->accept_link = $accept_link; + $this->channel_title = $channel_title; + $this->created_at = $created_at; + $this->dismiss_label = $dismiss_label; + $this->expires_at = $expires_at; + $this->icon = $icon; + $this->id = $id; + $this->is_dismissible = $is_dismissible; + $this->severity = $severity; + $this->title = $title; + } + + /** + * Specifies data which should be serialized to JSON. + * + * @return mixed Data which can be serialized by json_encode, which is a + * value of any type other than a resource. + */ + public function jsonSerialize(): mixed { + return array_merge( + $this->collect_meta(), + array( + 'created_at' => Helper\Serde::maybe_serialize_json_date( $this->created_at ), + 'expires_at' => Helper\Serde::maybe_serialize_json_date( $this->expires_at ), + 'id' => $this->id, + 'message' => $this->message, + 'title' => $this->title, + ) + ); + } + + /** + * Returns the JSON representation of the message metadata, or `false` if encoding fails. + * + * @return string|false + */ + public function encode_meta() { + return json_encode( $this->collect_meta() ); + } + + /** + * Get the title. + * + * @return string The title of the message. + */ + public function get_title(): string { + return $this->title; + } + + /** + * Get the content. + * + * @return string The content of the message. + */ + public function get_message(): string { + return $this->message; + } + + // Optional property getters + + /** + * Get the accept action label. + * + * @return ?string The accept action label of message. + */ + public function get_accept_label(): ?string { + return $this->accept_label; + } + + /** + * Get the accept action link. + * + * @return ?string The accept action link of the message. + */ + public function get_accept_link(): ?string { + return $this->accept_link; + } + + /** + * Get the created at datetime. + * + * @return DateTime The datetime at which the message was created. + */ + public function get_created_at(): ?DateTime { + return $this->created_at; + } + + /** + * Get whether the message is dismissible. + * + * @return ?bool The is dismissible property of the message. + */ + public function get_is_dismissible(): ?bool { + return $this->is_dismissible; + } + + /** + * Get the dismiss label. + * + * @return ?string The dismiss label of the message. + */ + public function get_dismiss_label(): ?string { + return $this->dismiss_label; + } + + /** + * Get the expires at datetime. + * + * @return ?DateTime The expires at datetime of the message. + */ + public function get_expires_at(): ?DateTime { + return $this->expires_at; + } + + /** + * Get the icon. + * + * @return ?string The icon of the message. + */ + public function get_icon(): ?string { + return $this->icon; + } + + /** + * Get the database ID. + * + * @return ?int The database ID of the message. + */ + public function get_id(): ?int { + return $this->id; + } + + /** + * Get the severity. + * + * @return ?string The severity of the message. + */ + public function get_severity(): ?string { + return $this->severity; + } + + /** + * Check whether the content of the message is valid. + */ + protected function validate_message() { + if ( ! is_string( $this->message ) ) { + return false; + } + + $length = mb_strlen( $this->message, '8bit' ); + + if ( $length > 255 ) { + return false; + } + + return true; + } + + /** + * Collect the message metadata values which are non null. + * + * @return mixed The metadata of the message. + */ + protected function collect_meta(): mixed { + $metadata = array(); + + foreach ( self::$meta_keys as $key ) { + if ( null !== $this->{ $key } ) { + $metadata[ $key ] = $this->{ $key }; + } + } + + return $metadata; + } +} diff --git a/includes/model/class-notification.php b/includes/model/class-notification.php new file mode 100644 index 00000000..cb8954fa --- /dev/null +++ b/includes/model/class-notification.php @@ -0,0 +1,195 @@ +channel_name = $channel_name; + $this->message_id = $message_id; + $this->user_id = $user_id; + + // Optional properties + + $this->context = $context; + $this->created_at = $created_at; + $this->dismissed_at = $dismissed_at; + $this->displayed_at = $displayed_at; + $this->expires_at = $expires_at; + } + + /** + * Specifies data which should be serialized to JSON. + * + * @return mixed Data which can be serialized by json_encode, which is a + * value of any type other than a resource. + */ + public function jsonSerialize(): mixed { + return array( + 'channel_name' => $this->channel_name, + 'context' => $this->context, + 'created_at' => Helper\Serde::maybe_serialize_json_date( $this->created_at ), + 'dismissed_at' => Helper\Serde::maybe_serialize_json_date( $this->dismissed_at ), + 'displayed_at' => Helper\Serde::maybe_serialize_json_date( $this->displayed_at ), + 'expires_at' => Helper\Serde::maybe_serialize_json_date( $this->expires_at ), + 'message_id' => $this->message_id, + 'user_id' => $this->user_id, + ); + } + + /** + * Get the namespaced channel name. + * + * @return string The namespaced channel name of the notification. + */ + public function get_channel_name(): string { + return $this->channel_name; + } + + /** + * Get the display context. + * + * @return ?string The display context of the notification. + */ + public function get_context(): ?string { + return $this->context; + } + + /** + * Get the created at datetime. + * + * @return ?DateTime The datetime at which the notification was created. + */ + public function get_created_at(): ?DateTime { + return $this->created_at; + } + + /** + * Get the dismissed at datetime. + * + * @return ?DateTime The datetime at which the notification was dismissed. + */ + public function get_dismissed_at(): ?DateTime { + return $this->dismissed_at; + } + + /** + * Get the displayed at datetime. + * + * @return ?DateTime The datetime at which the notification was first displayed. + */ + public function get_displayed_at(): ?DateTime { + return $this->displayed_at; + } + + /** + * Get the expires at datetime. + * + * @return ?DateTime The datetime at which the notification expires. + */ + public function get_expires_at(): ?DateTime { + return $this->expires_at; + } + + /** + * Get the message ID. + * + * @return int The database ID of the message related to the notification. + */ + public function get_message_id(): int { + return $this->message_id; + } + + /** + * Get the user ID. + * + * @return int The database ID of the user the notification belongs to. + */ + public function get_user_id(): int { + return $this->user_id; + } +} diff --git a/includes/model/class-subscription.php b/includes/model/class-subscription.php new file mode 100644 index 00000000..55bae951 --- /dev/null +++ b/includes/model/class-subscription.php @@ -0,0 +1,117 @@ +channel_name = $channel_name; + $this->user_id = $user_id; + + // Optional properties + + $this->created_at = $created_at; + $this->snoozed_until = $snoozed_until; + } + + /** + * Specifies data which should be serialized to JSON. + * + * @return mixed Data which can be serialized by json_encode, which is a + * value of any type other than a resource. + */ + public function jsonSerialize(): mixed { + return array( + 'channel_name' => $this->channel_name, + 'created_at' => Helper\Serde::maybe_serialize_json_date( $this->created_at ), + 'user_id' => $this->user_id, + 'snoozed_until' => Helper\Serde::maybe_serialize_json_date( $this->snoozed_until ), + ); + } + + /** + * Get the namespaced channel name. + * + * @return string The namespaced channel name of the subscription. + */ + public function get_channel_name(): string { + return $this->channel_name; + } + + /** + * Get the created at datetime. + * + * @return ?DateTime The datetime at which the subscription was created. + */ + public function get_created_at(): ?DateTime { + return $this->created_at; + } + + /** + * Get the user ID. + * + * @return int The user ID of the subscription. + */ + public function get_user_id(): int { + return $this->user_id; + } + + /** + * Get the snoozed until option. + * + * @return ?DateTime The snoozed until option of the subscription. + */ + public function get_snoozed_until(): ?DateTime { + return $this->snoozed_until; + } +} diff --git a/wp-feature-notifications.php b/wp-feature-notifications.php index 62d3f5fb..ea76c9ca 100644 --- a/wp-feature-notifications.php +++ b/wp-feature-notifications.php @@ -32,7 +32,11 @@ } // Require interface/class declarations.. - +require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/helper/class-serde.php'; +require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/model/class-channel.php'; +require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/model/class-message.php'; +require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/model/class-notification.php'; +require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/model/class-subscription.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/exceptions/interface-exception.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/exceptions/class-runtime-exception.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/interface-status.php'; From 460b3c7973f6e348fc3c13153632ee6b90c157f3 Mon Sep 17 00:00:00 2001 From: John Hooks Date: Thu, 20 Apr 2023 07:33:15 -0700 Subject: [PATCH 3/4] feature: add initial factory classes --- includes/factory/class-messages.php | 85 +++++++++++++++++++++++ includes/factory/class-notifications.php | 87 ++++++++++++++++++++++++ includes/factory/class-subscriptions.php | 68 ++++++++++++++++++ includes/framework/class-factory.php | 48 +++++++++++++ wp-feature-notifications.php | 4 ++ 5 files changed, 292 insertions(+) create mode 100644 includes/factory/class-messages.php create mode 100644 includes/factory/class-notifications.php create mode 100644 includes/factory/class-subscriptions.php create mode 100644 includes/framework/class-factory.php diff --git a/includes/factory/class-messages.php b/includes/factory/class-messages.php new file mode 100644 index 00000000..cd427aed --- /dev/null +++ b/includes/factory/class-messages.php @@ -0,0 +1,85 @@ + + */ +class Messages extends Framework\Factory { + + /** + * Instantiates a Message object. + * + * @param array|string $args { + * Array or string of arguments for creating a message. Supported arguments + * are described below. + * + * @type string $message Text content of the message. + * @type ?string $accept_label Optional label of the accept action. + * @type ?string $accept_link Optional url of the accept action. + * @type ?string $accept_message Optional label of the accept action. + * @type ?string $channel_title Optional human-readable title of the channel + * the message was emitted from. + * @type ?DateTime $created_at Optional datetime at which a message was created. + * Default `'null'` + * @type ?string $dismiss_label Optional label of the dismiss action. + * @type ?DateTime $expires_at Optional datetime at which a message expires. + * Default `'null'` + * @type ?string $icon Optional icon of the message. Default `null` + * @type ?int $id Optional database ID of the message. Default `null` + * @type bool $is_dismissible Optional boolean of whether the notice can + * be dismissed. Default `true` + * @type ?string $severity Optional severity of the message. Default `null` + * @type string $title Optional human-readable message label. Default `''` + * } + * + * @return Model\Message|false A newly created instance of Message or false. + */ + public function make( $args ) { + $parsed = wp_parse_args( $args ); + + // Required properties + + $message = $parsed['message']; + + // Optional properties + + $accept_label = array_key_exists( 'accept_label', $parsed ) ? $parsed['accept_label'] : null; + $accept_link = array_key_exists( 'accept_link', $parsed ) ? $parsed['accept_link'] : null; + $channel_title = array_key_exists( 'channel_title', $parsed ) ? $parsed['channel_title'] : null; + $created_at = array_key_exists( 'created_at', $parsed ) ? $parsed['created_at'] : null; + $dismiss_label = array_key_exists( 'dismiss_label', $parsed ) ? $parsed['dismiss_label'] : null; + $expires_at = array_key_exists( 'expires_at', $parsed ) ? $parsed['expires_at'] : null; + $icon = array_key_exists( 'icon', $parsed ) ? $parsed['icon'] : null; + $id = array_key_exists( 'id', $parsed ) ? $parsed['id'] : null; + $is_dismissible = array_key_exists( 'is_dismissible', $parsed ) ? $parsed['is_dismissible'] : true; + $severity = array_key_exists( 'severity', $parsed ) ? $parsed['severity'] : null; + $title = array_key_exists( 'title', $parsed ) ? $parsed['title'] : ''; + + return new Model\Message( + $message, + $accept_label, + $accept_link, + $channel_title, + $created_at, + $dismiss_label, + $expires_at, + $icon, + $id, + $is_dismissible, + $severity, + $title, + ); + } +} diff --git a/includes/factory/class-notifications.php b/includes/factory/class-notifications.php new file mode 100644 index 00000000..2cde718b --- /dev/null +++ b/includes/factory/class-notifications.php @@ -0,0 +1,87 @@ + + */ +class Notifications extends Framework\Factory { + + /** + * Instantiates a Notification object. + + * @param array|string $args { + * Array or string of arguments for creating a notification. Supported arguments are described below. + * + * @type string $channel_name Channel name, including namespace, + * the notification was emitted from. + * @type int $message_id ID of the message related to the + * notification. + * @type int $user_id ID of the user the notification + * belongs to. + * @type ?string $context Optional display context of the + * notification. Default `'adminbar'` + * @type string|DateTime|null $created_at Optional datetime at which the + * notification was created. Default `null` + * @type string|DateTime|null $dismissed_at Optional datetime t which the + * notification was dismissed. Default `null` + * @type string|DateTime|null $displayed_at Optional datetime at which the + * notification was first displayed. + * Default `null` + * @type string|DateTime|null $expires_at Optional datetime at which the + * notification expires. Default `null` + * } + * @param bool $validate Optionally validate the arguments. + * + * @return Model\Notification|false A newly created instance of Channel or false. + */ + public function make( $args ) { + $parsed = wp_parse_args( $args ); + + // Required properties + + $channel_name = $parsed['channel_name']; + $message_id = $parsed['message_id']; + $user_id = $parsed['user_id']; + + // Optional properties + + $context = array_key_exists( 'context', $parsed ) ? $parsed['context'] : 'adminbar'; + $created_at = array_key_exists( 'created_at', $parsed ) ? $parsed['created_at'] : null; + $dismissed_at = array_key_exists( 'dismissed_at', $parsed ) ? $parsed['dismissed_at'] : null; + $displayed_at = array_key_exists( 'displayed_at', $parsed ) ? $parsed['displayed_at'] : null; + $expires_at = array_key_exists( 'expires_at', $parsed ) ? $parsed['expires_at'] : null; + + // Deserialize MySQL datetime strings. + + $created_at = Helper\Serde::maybe_deserialize_mysql_date( $created_at ); + $dismissed_at = Helper\Serde::maybe_deserialize_mysql_date( $dismissed_at ); + $displayed_at = Helper\Serde::maybe_deserialize_mysql_date( $displayed_at ); + $expires_at = Helper\Serde::maybe_deserialize_mysql_date( $expires_at ); + + $notification = new Model\Notification( + $channel_name, + $message_id, + $user_id, + $context, + $created_at, + $dismissed_at, + $displayed_at, + $expires_at + ); + + return $notification; + } +} diff --git a/includes/factory/class-subscriptions.php b/includes/factory/class-subscriptions.php new file mode 100644 index 00000000..fcc1b871 --- /dev/null +++ b/includes/factory/class-subscriptions.php @@ -0,0 +1,68 @@ +. + */ +class Subscriptions extends Framework\Factory { + + /** + * Instantiates a Subscription object. + * + * @param array|string $args { + * Array or string of arguments for creating a subscription. Supported + * arguments are described below. + * + * @type string $channel_name Namespaced channel name of the + * subscription. + * @type int $user_id ID of the user the subscription + * belongs to. + * @type string|DateTime|null $created_at Optional datetime at which the + * subscription was created. + * @type string|DateTime|null $snoozed_until Optional snoozed until datetime + * of the subscription. + * } + * + * @return Model\Subscription|false A newly created instance of Subscription or false. + */ + public function make( $args ) { + $parsed = wp_parse_args( $args ); + + // Required properties + + $channel_name = $parsed['channel_name']; + $user_id = $parsed['user_id']; + + // Optional properties + + $created_at = array_key_exists( 'created_at', $parsed ) ? $parsed['created_at'] : null; + $snoozed_until = array_key_exists( 'snoozed_until', $parsed ) ? $parsed['snoozed_until'] : null; + + // Deserialize MySQL datetime strings. + + $created_at = Helper\Serde::maybe_deserialize_mysql_date( $created_at ); + $snoozed_until = Helper\Serde::maybe_deserialize_mysql_date( $snoozed_until ); + + $subscription = new Model\Subscription( + $channel_name, + $user_id, + $created_at, + $snoozed_until, + ); + + return $subscription; + } +} diff --git a/includes/framework/class-factory.php b/includes/framework/class-factory.php new file mode 100644 index 00000000..40262e25 --- /dev/null +++ b/includes/framework/class-factory.php @@ -0,0 +1,48 @@ + Date: Thu, 20 Apr 2023 08:12:39 -0700 Subject: [PATCH 4/4] feature: add channel registry --- includes/channels.php | 135 +++++++++++++++++++ includes/class-channel-registry.php | 200 ++++++++++++++++++++++++++++ wp-feature-notifications.php | 2 + 3 files changed, 337 insertions(+) create mode 100644 includes/channels.php create mode 100644 includes/class-channel-registry.php diff --git a/includes/channels.php b/includes/channels.php new file mode 100644 index 00000000..98eb976d --- /dev/null +++ b/includes/channels.php @@ -0,0 +1,135 @@ +register( $name, $args ); +} + +/** + * Unregister a channel. + * + * @param string|Model\Channel $name Channel name including namespace, or + * alternatively a complete Channel instance. + * @return Model\Channel|false The unregistered channel on success, or false on failure. + */ +function unregister_channel( $name ) { + return Channel_Registry::get_instance()->unregister( $name ); +} + +// Register core notification channels. + +add_action( + 'init', + function () { + register_channel( + 'core/updates', + array( + 'title' => __( 'WordPress Updates', 'wp-feature-notifications' ), + 'icon' => 'wordpress', + 'description' => __( 'WordPress core update events.', 'wp-feature-notifications' ), + ) + ); + + register_channel( + 'core/plugin-install', + array( + 'title' => __( 'Plugin Install', 'wp-feature-notifications' ), + 'icon' => 'wordpress', + 'description' => __( 'Plugin install events.', 'wp-feature-notifications' ), + ) + ); + + register_channel( + 'core/plugin-uninstall', + array( + 'title' => __( 'Plugin Uninstall', 'wp-feature-notifications' ), + 'icon' => 'wordpress', + 'description' => __( 'Plugin uninstall events.', 'wp-feature-notifications' ), + ) + ); + + register_channel( + 'core/plugin-activate', + array( + 'title' => __( 'Plugin Activate', 'wp-feature-notifications' ), + 'icon' => 'wordpress', + 'description' => __( 'Plugin activation events.', 'wp-feature-notifications' ), + ) + ); + + register_channel( + 'core/plugin-deactivate', + array( + 'title' => __( 'Plugin Deactivate', 'wp-feature-notifications' ), + 'icon' => 'wordpress', + 'description' => __( 'Plugin deactivation events.', 'wp-feature-notifications' ), + ) + ); + + register_channel( + 'core/plugin-updates', + array( + 'title' => __( 'Plugin Update', 'wp-feature-notifications' ), + 'icon' => 'wordpress', + 'description' => __( 'Plugin update events.', 'wp-feature-notifications' ), + ) + ); + + register_channel( + 'core/post-new', + array( + 'title' => __( 'New Post', 'wp-feature-notifications' ), + 'icon' => 'wordpress', + 'description' => __( 'Post creation events.', 'wp-feature-notifications' ), + ) + ); + + register_channel( + 'core/post-edit', + array( + 'title' => __( 'Edit Post', 'wp-feature-notifications' ), + 'icon' => 'wordpress', + 'description' => __( 'Post edit events.', 'wp-feature-notifications' ), + ) + ); + + register_channel( + 'core/post-delete', + array( + 'title' => __( 'Delete Post', 'wp-feature-notifications' ), + 'icon' => 'wordpress', + 'description' => __( 'Post delete events.', 'wp-feature-notifications' ), + ) + ); + + register_channel( + 'core/comment-new', + array( + 'title' => __( 'New Comment', 'wp-feature-notifications' ), + 'icon' => 'wordpress', + 'description' => __( 'Comment creation events.', 'wp-feature-notifications' ), + ) + ); + } +); diff --git a/includes/class-channel-registry.php b/includes/class-channel-registry.php new file mode 100644 index 00000000..48ac26d2 --- /dev/null +++ b/includes/class-channel-registry.php @@ -0,0 +1,200 @@ + $instance` pairs. + * + * @var Model\Channel[] + */ + private $registered_channels = array(); + + /** + * Container for the main instance of the class. + * + * @var Channel_Registry|null + */ + private static $instance = null; + + /** + * Registers a channel. + * + * @see \WP\Notifications\Channel::__construct() + * + * @param string|Model\Channel $name Channel name including namespace, or + * alternatively a complete Channel instance. + * In case a Channel is provided, the $args + * parameter will be ignored. + * @param array|string $args { + * Array or string of arguments for registering a channel. Supported arguments + * are described below. + * + * @type string $title Human-readable label of the channel. + * @type string|null $context Optional display context of the channel + * @type string|null $description Optional detailed description of the channel. + * @type string|null $icon Optional icon of the channel. + * } + * @return Model\Channel|false The registered channel on success, or false on failure. + */ + public function register( $name, $args = array() ) { + $channel = null; + + if ( $name instanceof Channel ) { + $channel = $name; + $name = $channel->get_name(); + } + + if ( ! is_string( $name ) ) { + _doing_it_wrong( + __METHOD__, + __( 'Channel names must be strings.' ), + '1.0.0' + ); + return false; + } + + if ( preg_match( '/[A-Z]+/', $name ) ) { + _doing_it_wrong( + __METHOD__, + __( 'Channel names must not contain uppercase characters.' ), + '1.0.0' + ); + return false; + } + + $name_matcher = '/^[a-z0-9-]+\/[a-z0-9-]+$/'; + if ( ! preg_match( $name_matcher, $name ) ) { + _doing_it_wrong( + __METHOD__, + __( 'Channel names must contain a namespace prefix. Example: my-plugin/my-channel' ), + '1.0.0' + ); + return false; + } + + if ( $this->is_registered( $name ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Channel name. */ + sprintf( __( 'Channel "%s" is already registered.' ), $name ), + '1.0.0' + ); + return false; + } + + if ( ! $channel ) { + $parsed = wp_parse_args( $args ); + + $title = $parsed['title']; + $context = array_key_exists( 'context', $parsed ) ? $parsed['context'] : null; + $description = array_key_exists( 'description', $parsed ) ? $parsed['description'] : null; + $icon = array_key_exists( 'icon', $parsed ) ? $parsed['icon'] : null; + + $channel = new Model\Channel( $name, $title, $context, $description, $icon ); + } + + $this->registered_channels[ $channel->get_name() ] = $channel; + + return $channel; + } + + /** + * Unregister a channel. + * + * @param string|Model/Channel $name Channel type name including namespace, or + * alternatively a complete Channel instance. + * @return Model/Channel|false The unregistered channel on success, or false on failure. + */ + public function unregister( $name ) { + if ( $name instanceof Channel ) { + $name = $name->get_name(); + } + + if ( ! $this->is_registered( $name ) ) { + _doing_it_wrong( + __METHOD__, + /* translators: %s: Channel name. */ + sprintf( __( 'Channel "%s" is not registered.' ), $name ), + '1.0.0' + ); + return false; + } + + $unregistered_channel = $this->registered_channels[ $name ]; + unset( $this->registered_channels[ $name ] ); + + return $unregistered_channel; + } + + /** + * Retrieves a registered channel. + * + * @param string $name Channel name including namespace. + * + * @return Model\Channel|null The registered channel, or null if it is not registered. + */ + public function get_registered( $name ) { + if ( ! $this->is_registered( $name ) ) { + return null; + } + + return $this->registered_channels[ $name ]; + } + + /** + * Retrieves all registered channels. + * + * @return Model\Channel[] Associative array of `$channel_name => $channel` pairs. + */ + public function get_all_registered() { + return $this->registered_channels; + } + + /** + * Checks if a channel is registered. + * + * @param string $name Chanel name including namespace. + * + * @return bool True if the channel is registered, false otherwise. + */ + public function is_registered( $name ) { + return isset( $this->registered_channels[ $name ] ); + } + + /** + * Utility method to retrieve the main instance of the class. + * + * The instance will be created if it does not exist yet. + * + * @return Channel_Registry The main instance. + */ + public static function get_instance() { + if ( null === self::$instance ) { + self::$instance = new self(); + } + + return self::$instance; + } +} diff --git a/wp-feature-notifications.php b/wp-feature-notifications.php index 90360b59..2b9e0200 100644 --- a/wp-feature-notifications.php +++ b/wp-feature-notifications.php @@ -41,6 +41,8 @@ require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/factory/class-messages.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/factory/class-notifications.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/factory/class-subscriptions.php'; +require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/class-channel-registry.php'; +require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/channels.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/exceptions/interface-exception.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/exceptions/class-runtime-exception.php'; require_once WP_FEATURE_NOTIFICATION_PLUGIN_DIR . '/includes/interface-status.php';