From 73c9acb0d92d8b18c48ed68a6e296377228f60b0 Mon Sep 17 00:00:00 2001 From: Serhiy Lunak Date: Sun, 21 Jun 2020 21:04:05 +0100 Subject: [PATCH] Add attachment support --- Example/CompleteNotificationExample.php | 3 + src/Api/Message/Attachment.php | 154 ++++++++++++++++++++++++ src/Api/Message/Notification.php | 21 ++++ src/Api/Message/Request.php | 17 +++ tests/Api/Message/AttachmentTest.php | 112 +++++++++++++++++ tests/Api/Message/NotificationTest.php | 24 ++++ tests/Api/Message/RequestTest.php | 17 +++ 7 files changed, 348 insertions(+) create mode 100644 src/Api/Message/Attachment.php create mode 100644 tests/Api/Message/AttachmentTest.php diff --git a/Example/CompleteNotificationExample.php b/Example/CompleteNotificationExample.php index 1387753..baee499 100644 --- a/Example/CompleteNotificationExample.php +++ b/Example/CompleteNotificationExample.php @@ -12,6 +12,7 @@ namespace Serhiy\Pushover\Example; use Serhiy\Pushover\Api\Message\Application; +use Serhiy\Pushover\Api\Message\Attachment; use Serhiy\Pushover\Api\Message\Client; use Serhiy\Pushover\Api\Message\Message; use Serhiy\Pushover\Api\Message\Notification; @@ -51,6 +52,8 @@ public function completeNotification() $notification = new Notification($application, $recipient, $message); // set notification sound $notification->setSound(new Sound(Sound::PUSHOVER)); + // add attachment + $notification->setAttachment(new Attachment("/path/to/file.jpg", Attachment::MIME_TYPE_JPEG)); // push notification /** @var Response $response */ diff --git a/src/Api/Message/Attachment.php b/src/Api/Message/Attachment.php new file mode 100644 index 0000000..678e321 --- /dev/null +++ b/src/Api/Message/Attachment.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Serhiy\Pushover\Api\Message; + +use Serhiy\Pushover\Exception\InvalidArgumentException; + +/** + * Pushover messages can include an image attachment. + * When received by a device, it will attempt to download the image and display it with the notification. + * If this fails or takes too long, the notification will be displayed without it and the image download can be retried inside the Pushover app. + * Note that, like messages, once attachments are downloaded by the device, they are deleted from our servers and only stored on the device going forward. + * Attachments uploaded for devices that are not running at least version 3.0 of our apps will be discarded as they cannot be displayed by those devices. + * + * @author Serhiy Lunak + */ +class Attachment +{ + /** + * Windows OS/2 Bitmap Graphics. + */ + const MIME_TYPE_JPEG = 'image/jpeg'; + + /** + * Portable Network Graphics. + */ + const MIME_TYPE_PNG = 'image/png'; + + /** + * Graphics Interchange Format (GIF). + */ + const MIME_TYPE_GIF = 'image/gif'; + + /** + * Windows OS/2 Bitmap Graphics + */ + const MIME_TYPE_BMP = 'image/bmp'; + + /** + * Icon format + */ + const MIME_TYPE_ICO = 'image/vnd.microsoft.icon'; + + /** + * Scalable Vector Graphics (SVG) + */ + const MIME_TYPE_SVG = 'image/svg+xml'; + + /** + * Tagged Image File Format (TIFF) + */ + const MIME_TYPE_TIFF = 'image/tiff'; + + /** + * WEBP image + */ + const MIME_TYPE_WEBP = 'image/webp'; + + /** + * MIME type. + * A media type (also known as a Multipurpose Internet Mail Extensions or MIME type) is a standard + * that indicates the nature and format of a document, file, or assortment of bytes. + * In case of Pushover attachment only image MIME type is accepted. + * + * @var string + */ + private $mimeType; + + /** + * Path to the file. + * Path and filename of the image file to be sent with notification. + * + * @var string + */ + private $filename; + + public function __construct(string $filename, string $mimeType) + { + $this->setMimeType($mimeType); + $this->setFilename($filename); + } + + /** + * Generates array with all supported attachment types. Attachment types are taken from the constants of this class. + * + * @return array + */ + public function getSupportedAttachmentTypes(): array + { + $oClass = new \ReflectionClass(__CLASS__); + return $oClass->getConstants(); + } + + /** + * Supported extensions. + * Returns array of supported extensions. + * + * @return array + */ + public function getSupportedAttachmentExtensions(): array + { + return array( + 'bmp', 'gif', 'ico', 'jpeg', 'jpg', 'png', 'svg', 'tif', 'tiff', 'webp' + ); + } + + /** + * @return string + */ + public function getMimeType(): string + { + return $this->mimeType; + } + + /** + * @param string $mimeType + */ + public function setMimeType(string $mimeType): void + { + if (!in_array($mimeType, $this->getSupportedAttachmentTypes())) { + throw new InvalidArgumentException(sprintf('Attachment type "%s" is not supported.', $mimeType)); + } + + $this->mimeType = $mimeType; + } + + /** + * @return string + */ + public function getFilename(): string + { + return $this->filename; + } + + /** + * @param string $filename + */ + public function setFilename(string $filename): void + { + if (!in_array(pathinfo($filename, PATHINFO_EXTENSION), $this->getSupportedAttachmentExtensions())) { + throw new InvalidArgumentException(sprintf('Attachment extension "%s" is not supported.', pathinfo($filename, PATHINFO_EXTENSION))); + } + + $this->filename = $filename; + } +} diff --git a/src/Api/Message/Notification.php b/src/Api/Message/Notification.php index 0848ce8..17b72e4 100644 --- a/src/Api/Message/Notification.php +++ b/src/Api/Message/Notification.php @@ -40,6 +40,11 @@ class Notification */ private $sound; + /** + * @var Attachment + */ + private $attachment; + public function __construct(Application $application, Recipient $recipient, Message $message) { $this->application = $application; @@ -110,4 +115,20 @@ public function setSound(?Sound $sound): void { $this->sound = $sound; } + + /** + * @return Attachment|null + */ + public function getAttachment(): ?Attachment + { + return $this->attachment; + } + + /** + * @param Attachment|null $attachment + */ + public function setAttachment(?Attachment $attachment): void + { + $this->attachment = $attachment; + } } diff --git a/src/Api/Message/Request.php b/src/Api/Message/Request.php index dc6cb6a..eee8bd9 100644 --- a/src/Api/Message/Request.php +++ b/src/Api/Message/Request.php @@ -11,6 +11,8 @@ namespace Serhiy\Pushover\Api\Message; +use Serhiy\Pushover\Exception\LogicException; + /** * Request object. * @@ -146,6 +148,21 @@ private function buildCurlPostFields(Notification $notification) $curlPostFields['sound'] = $notification->getSound()->getSound(); } + if (null !== $notification->getAttachment()) { + if (! is_readable($notification->getAttachment()->getFilename())) { + throw new LogicException(sprintf('File "%s" does not exist or is not readable.', $notification->getAttachment()->getFilename())); + } + + if (2621440 < filesize($notification->getAttachment()->getFilename())) { + throw new LogicException(sprintf('Attachments are currently limited to 2621440 bytes (2.5 megabytes). %s bytes attachment provided.', filesize($notification->getAttachment()->getFilename()))); + } + + $curlPostFields['attachment'] = curl_file_create( + $notification->getAttachment()->getFilename(), + $notification->getAttachment()->getMimeType() + ); + } + return $curlPostFields; } } diff --git a/tests/Api/Message/AttachmentTest.php b/tests/Api/Message/AttachmentTest.php new file mode 100644 index 0000000..cafcf52 --- /dev/null +++ b/tests/Api/Message/AttachmentTest.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Api\Message; + +use Serhiy\Pushover\Api\Message\Attachment; +use PHPUnit\Framework\TestCase; +use Serhiy\Pushover\Exception\InvalidArgumentException; + +/** + * @author Serhiy Lunak + */ +class AttachmentTest extends TestCase +{ + public function testCanBeCreated() + { + $attachment = new Attachment('/images/test.jpeg', Attachment::MIME_TYPE_JPEG); + $this->assertInstanceOf(Attachment::class, $attachment); + + return $attachment; + } + + public function testCannotBeCreatedWithInvalidMimeType() + { + $this->expectException(InvalidArgumentException::class); + + new Attachment('/images/test.jpeg', 'image/invalid'); + } + + public function testCannotBeCreatedWithInvalidExtension() + { + $this->expectException(InvalidArgumentException::class); + + new Attachment('/images/test.invalid', Attachment::MIME_TYPE_JPEG); + } + + /** + * @depends testCanBeCreated + * @param Attachment $attachment + */ + public function testGetMimeType(Attachment $attachment) + { + $this->assertEquals(Attachment::MIME_TYPE_JPEG, $attachment->getMimeType()); + } + + /** + * @depends testCanBeCreated + * @param Attachment $attachment + */ + public function testGetFilename(Attachment $attachment) + { + $this->assertEquals('/images/test.jpeg', $attachment->getFilename()); + } + + /** + * @depends testCanBeCreated + * @param Attachment $attachment + */ + public function testSetMimeType(Attachment $attachment) + { + $attachment->setMimeType(Attachment::MIME_TYPE_JPEG); + $this->assertEquals(Attachment::MIME_TYPE_JPEG, $attachment->getMimeType()); + + $this->expectException(InvalidArgumentException::class); + $attachment->setMimeType('image/invalid'); + } + + /** + * @depends testCanBeCreated + * @param Attachment $attachment + */ + public function testSetFilename(Attachment $attachment) + { + $attachment->setMimeType(Attachment::MIME_TYPE_JPEG); + $this->assertEquals(Attachment::MIME_TYPE_JPEG, $attachment->getMimeType()); + + $this->expectException(InvalidArgumentException::class); + $attachment->setMimeType('image/invalid'); + } + + /** + * @depends testCanBeCreated + * @param Attachment $attachment + */ + public function testSupportedAttachmentTypes(Attachment $attachment) + { + $supportedAttachmentsTypes = new \ReflectionClass(Attachment::class); + + $this->assertEquals($supportedAttachmentsTypes->getConstants(), $attachment->getSupportedAttachmentTypes()); + } + + /** + * @depends testCanBeCreated + * @param Attachment $attachment + */ + public function testSupportedAttachmentExtensions(Attachment $attachment) + { + $supportedAttachmentExtensions = array( + 'bmp', 'gif', 'ico', 'jpeg', 'jpg', 'png', 'svg', 'tif', 'tiff', 'webp' + ); + + $this->assertEquals($supportedAttachmentExtensions, $attachment->getSupportedAttachmentExtensions()); + } +} diff --git a/tests/Api/Message/NotificationTest.php b/tests/Api/Message/NotificationTest.php index eff25e0..af74464 100644 --- a/tests/Api/Message/NotificationTest.php +++ b/tests/Api/Message/NotificationTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Serhiy\Pushover\Api\Message\Application; +use Serhiy\Pushover\Api\Message\Attachment; use Serhiy\Pushover\Api\Message\Message; use Serhiy\Pushover\Api\Message\Notification; use Serhiy\Pushover\Api\Message\Recipient; @@ -58,4 +59,27 @@ public function testNoSound(Notification $notification) $this->assertNull($notification->getSound()); } + + /** + * @depends testCanBeCreated + * @param Notification $notification + */ + public function testNotificationAttachment(Notification $notification) + { + $notification->setAttachment(new Attachment("/path/to/file.jpg", Attachment::MIME_TYPE_JPEG)); + + $this->assertEquals('/path/to/file.jpg', $notification->getAttachment()->getFilename()); + $this->assertEquals('image/jpeg', $notification->getAttachment()->getMimeType()); + } + + /** + * @depends testCanBeCreated + * @param Notification $notification + */ + public function testNoAttachment(Notification $notification) + { + $notification->setAttachment(null); + + $this->assertNull($notification->getAttachment()); + } } diff --git a/tests/Api/Message/RequestTest.php b/tests/Api/Message/RequestTest.php index db513c3..2450957 100644 --- a/tests/Api/Message/RequestTest.php +++ b/tests/Api/Message/RequestTest.php @@ -13,11 +13,13 @@ use PHPUnit\Framework\TestCase; use Serhiy\Pushover\Api\Message\Application; +use Serhiy\Pushover\Api\Message\Attachment; use Serhiy\Pushover\Api\Message\Client; use Serhiy\Pushover\Api\Message\Message; use Serhiy\Pushover\Api\Message\Notification; use Serhiy\Pushover\Api\Message\Recipient; use Serhiy\Pushover\Api\Message\Request; +use Serhiy\Pushover\Exception\LogicException; /** * @author Serhiy Lunak @@ -83,4 +85,19 @@ public function testBuildCurlPostFields(Request $request) $this->assertEquals("This is a test message", $curlPostFields["message"]); $this->assertEquals("This is a title of the message", $curlPostFields["title"]); } + + /** + * Necessary to test logic exception when file does not exist or is not readable. + * + * @depends testNotification + * @param Request $request + */ + public function testAttachment(Request $request) + { + $notification = $request->getNotification(); + $notification->setAttachment(new Attachment("/path/to/file.jpg", Attachment::MIME_TYPE_JPEG)); + + $this->expectException(LogicException::class); + $request->setCurlPostFields($notification); + } }