diff --git a/Example/GlancesExample.php b/Example/GlancesExample.php new file mode 100644 index 0000000..86556a7 --- /dev/null +++ b/Example/GlancesExample.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Serhiy\Pushover\Example; + +use Serhiy\Pushover\Api\Glances\Glance; +use Serhiy\Pushover\Api\Glances\GlanceDataFields; +use Serhiy\Pushover\Application; +use Serhiy\Pushover\Client\Response\GlancesResponse; +use Serhiy\Pushover\Recipient; + +/** + * @author Serhiy Lunak + */ +class GlancesExample +{ + public function glancesExample() + { + // instantiate pushover application and recipient to verify (can be injected into service using Dependency Injection) + $application = new Application("replace_with_pushover_application_api_token"); + $recipient = new Recipient("replace_with_pushover_user_key"); + + // create glance data fields + $glanceDataFields = new GlanceDataFields(); + $glanceDataFields + ->setTitle("Title") + ->setText("Text") + ->setSubtext("Subtext") + ->setCount(1) + ->setPercent(99) + ; + + // instantiate glance + $glance = new Glance($application, $glanceDataFields); + + // set recipient + $glance->setRecipient($recipient); + + // push it + /** @var GlancesResponse $response */ + $response = $glance->push(); + + // or loop over recipients + $recipients = array(); // array of Recipient objects + foreach ($recipients as $recipient) { + $glance->setRecipient($recipient); + $response = $glance->push(); + } + + // work with response + if ($response->isSuccessful()) { + // ... + } + } +} diff --git a/README.md b/README.md index f601331..bc9cc67 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,12 @@ Light, simple and fast, yet comprehensive wrapper for the [Pushover](https://pus - Add / Remove users - Enable / Disable users - Rename the group +- Glances API ([Example](Example/GlancesExample.php)) + - Title + - Text + - Subtext + - Count + - Percent *Note: Project is in constant development; update to newer versions with caution.* diff --git a/src/Api/Glances/Glance.php b/src/Api/Glances/Glance.php new file mode 100644 index 0000000..3b23b02 --- /dev/null +++ b/src/Api/Glances/Glance.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Serhiy\Pushover\Api\Glances; + +use Serhiy\Pushover\Application; +use Serhiy\Pushover\Client\Curl\Curl; +use Serhiy\Pushover\Client\GlancesClient; +use Serhiy\Pushover\Client\Request\Request; +use Serhiy\Pushover\Client\Response\GlancesResponse; +use Serhiy\Pushover\Recipient; + +/** + * Pushover Glances API (Beta). + * With Pushover's Glances API, you can push small bits of data directly to a constantly-updated screen, + * referred to as a widget, such as a complication on your smart watch or a widget on your phone's lock screen. + * Glances API is used for sending short pieces of text or numerical data, such as "Garage door open" in response to an alarm system, + * or just "30" representing the number of items sold in your store today. + * These pieces of data should be low-priority since they often cannot get updated in real-time or very frequently, + * and they must be concise because they are often viewed on small screens such as a watch face. + * + * @author Serhiy Lunak + */ +class Glance +{ + /** + * @var Application Pushover application. + */ + private $application; + + /** + * @var Recipient Pushover user. + */ + private $recipient; + + /** + * @var GlanceDataFields Glance Data Fields. + */ + private $glanceDataFields; + + public function __construct(Application $application, GlanceDataFields $glanceDataFields) + { + $this->application = $application; + $this->glanceDataFields = $glanceDataFields; + } + + /** + * @return Application + */ + public function getApplication(): Application + { + return $this->application; + } + + /** + * @param Application $application + */ + public function setApplication(Application $application): void + { + $this->application = $application; + } + + /** + * @return Recipient + */ + public function getRecipient(): Recipient + { + return $this->recipient; + } + + /** + * @param Recipient $recipient + */ + public function setRecipient(Recipient $recipient): void + { + $this->recipient = $recipient; + } + + /** + * @return GlanceDataFields + */ + public function getGlanceDataFields(): GlanceDataFields + { + return $this->glanceDataFields; + } + + /** + * @param GlanceDataFields $glanceDataFields + */ + public function setGlanceDataFields(GlanceDataFields $glanceDataFields): void + { + $this->glanceDataFields = $glanceDataFields; + } + + /** + * @return bool + */ + public function hasAtLeastOneField(): bool + { + if (null === $this->getGlanceDataFields()->getTitle() && + null === $this->getGlanceDataFields()->getSubtext() && + null === $this->getGlanceDataFields()->getCount() && + null === $this->getGlanceDataFields()->getPercent() + ) { + return false; + } + + return true; + } + + /** + * @return bool + */ + public function hasRecipient(): bool + { + if (null === $this->recipient) { + return false; + } + + return true; + } + + /** + * Push glance. + * + * @return GlancesResponse + */ + public function push() + { + $client = new GlancesClient(); + $request = new Request($client->buildApiUrl(), Request::POST, $client->buildCurlPostFields($this)); + + $curlResponse = Curl::do($request); + + $response = new GlancesResponse($curlResponse); + $response->setRequest($request); + + return $response; + } +} diff --git a/src/Api/Glances/GlanceDataFields.php b/src/Api/Glances/GlanceDataFields.php new file mode 100644 index 0000000..d3fa3d4 --- /dev/null +++ b/src/Api/Glances/GlanceDataFields.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Serhiy\Pushover\Api\Glances; + +use Serhiy\Pushover\Exception\InvalidArgumentException; + +/** + * Glance Data Fields. + * Currently the following fields are available for updating. Each field is shown differently on different screens, + * so you may need to experiment with them to find out which field works for you given your screen and type of data. + * For example, each watch face on the Apple Watch uses a different sized complication, + * with different size specifications and types of data. Some are text strings, some are just numbers. + * + * @author Serhiy Lunak + */ +class GlanceDataFields +{ + /** + * @var string|null (100 characters) - a description of the data being shown, such as "Widgets Sold". + */ + private $title; + + /** + * @var string|null (100 characters) - the main line of data, used on most screens. + */ + private $text; + + /** + * @var string|null (100 characters) - a second line of data. + */ + private $subtext; + + /** + * @var int|null (integer, may be negative) - shown on smaller screens; useful for simple counts. + */ + private $count; + + /** + * @var int|null (integer 0 through 100, inclusive) - shown on some screens as a progress bar/circle. + */ + private $percent; + + public function __construct() + { + } + + /** + * @return string|null + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @param string|null $title + * @return GlanceDataFields + */ + public function setTitle(?string $title): GlanceDataFields + { + if (strlen($title) > 100) { + throw new InvalidArgumentException(sprintf("Title can be no more than 100 characters long. %s characters long title provided.", strlen($title))); + } + + $this->title = $title; + + return $this; + } + + /** + * @return string|null + */ + public function getText(): ?string + { + return $this->text; + } + + /** + * @param string|null $text + * @return GlanceDataFields + */ + public function setText(?string $text): GlanceDataFields + { + if (strlen($text) > 100) { + throw new InvalidArgumentException(sprintf("Text can be no more than 100 characters long. %s characters long text provided.", strlen($text))); + } + + $this->text = $text; + + return $this; + } + + /** + * @return string|null + */ + public function getSubtext(): ?string + { + return $this->subtext; + } + + /** + * @param string|null $subtext + * @return GlanceDataFields + */ + public function setSubtext(?string $subtext): GlanceDataFields + { + if (strlen($subtext) > 100) { + throw new InvalidArgumentException(sprintf("Subtext can be no more than 100 characters long. %s characters long subtext provided.", strlen($subtext))); + } + + $this->subtext = $subtext; + + return $this; + } + + /** + * @return int|null + */ + public function getCount(): ?int + { + return $this->count; + } + + /** + * @param int|null $count + * @return GlanceDataFields + */ + public function setCount(?int $count): GlanceDataFields + { + $this->count = $count; + + return $this; + } + + /** + * @return int|null + */ + public function getPercent(): ?int + { + return $this->percent; + } + + /** + * @param int|null $percent + * @return GlanceDataFields + */ + public function setPercent(?int $percent): GlanceDataFields + { + if (! ($percent >= 0 && $percent <= 100)) { + throw new InvalidArgumentException(sprintf("Percent should be an integer 0 through 100, inclusive. %s provided.", $percent)); + } + + $this->percent = $percent; + + return $this; + } +} diff --git a/src/Client/GlancesClient.php b/src/Client/GlancesClient.php new file mode 100644 index 0000000..ea2c63d --- /dev/null +++ b/src/Client/GlancesClient.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Serhiy\Pushover\Client; + +use Serhiy\Pushover\Api\Glances\Glance; +use Serhiy\Pushover\Client\Curl\Curl; +use Serhiy\Pushover\Exception\LogicException; + +/** + * @author Serhiy Lunak + */ +class GlancesClient extends Client implements ClientInterface +{ + const API_PATH = "glances.json"; + + /** + * @inheritDoc + */ + public function buildApiUrl() + { + return Curl::API_BASE_URL."/".Curl::API_VERSION."/".self::API_PATH; + } + + /** + * Builds array for CURLOPT_POSTFIELDS curl argument. + * + * @param Glance $glance + * @return array[] + */ + public function buildCurlPostFields(Glance $glance) + { + if (!$glance->hasRecipient()) { + throw new LogicException(sprintf('Glance recipient is not set.')); + } + + if (!$glance->hasAtLeastOneField()) { + throw new LogicException(sprintf('At least one of the data fields must be supplied. To clear a previously-updated field, send the field with a blank (empty string) value.')); + } + + $curlPostFields = array( + "token" => $glance->getApplication()->getToken(), + "user" => $glance->getRecipient()->getUserKey(), + ); + + if (! empty($glance->getRecipient()->getDevice())) { + if (count($glance->getRecipient()->getDevice()) > 1) { + throw new LogicException(sprintf('Glance can be pushed to only one device. "%s" devices provided.', count($glance->getRecipient()->getDevice()))); + } + + $curlPostFields['device'] = $glance->getRecipient()->getDeviceListCommaSeparated(); + } + + if (null !== $glance->getGlanceDataFields()->getTitle()) { + $curlPostFields['title'] = $glance->getGlanceDataFields()->getTitle(); + } + if (null !== $glance->getGlanceDataFields()->getText()) { + $curlPostFields['text'] = $glance->getGlanceDataFields()->getText(); + } + if (null !== $glance->getGlanceDataFields()->getSubtext()) { + $curlPostFields['subtext'] = $glance->getGlanceDataFields()->getSubtext(); + } + if (null !== $glance->getGlanceDataFields()->getCount()) { + $curlPostFields['count'] = $glance->getGlanceDataFields()->getCount(); + } + if (null !== $glance->getGlanceDataFields()->getPercent()) { + $curlPostFields['percent'] = $glance->getGlanceDataFields()->getPercent(); + } + + return $curlPostFields; + } +} diff --git a/src/Client/Response/GlancesResponse.php b/src/Client/Response/GlancesResponse.php new file mode 100644 index 0000000..b42dca3 --- /dev/null +++ b/src/Client/Response/GlancesResponse.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Serhiy\Pushover\Client\Response; + +use Serhiy\Pushover\Client\Response\Base\Response; + +/** + * @author Serhiy Lunak + */ +class GlancesResponse extends Response +{ + /** + * @param mixed $curlResponse + */ + public function __construct($curlResponse) + { + $this->processCurlResponse($curlResponse); + } + + /** + * @param mixed $curlResponse + */ + private function processCurlResponse($curlResponse): void + { + $decodedCurlResponse = $this->processInitialCurlResponse($curlResponse); + } +} diff --git a/tests/Api/Glances/GlanceDataFieldsTest.php b/tests/Api/Glances/GlanceDataFieldsTest.php new file mode 100644 index 0000000..e07628d --- /dev/null +++ b/tests/Api/Glances/GlanceDataFieldsTest.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Api\Glances; + +use Serhiy\Pushover\Api\Glances\GlanceDataFields; +use PHPUnit\Framework\TestCase; +use Serhiy\Pushover\Exception\InvalidArgumentException; + +/** + * @author Serhiy Lunak + */ +class GlanceDataFieldsTest extends TestCase +{ + public function testCanBeCreated() + { + $glanceDataFields = new GlanceDataFields(); + + $this->assertInstanceOf(GlanceDataFields::class, $glanceDataFields); + } + + public function testSetTitle() + { + $glanceDataFields = new GlanceDataFields(); + + $glanceDataFields->setTitle("This is test title"); + $this->assertEquals("This is test title", $glanceDataFields->getTitle()); + + $glanceDataFields->setTitle(null); + $this->assertNull($glanceDataFields->getTitle()); + + $glanceDataFields->setTitle(""); + $this->assertEquals("", $glanceDataFields->getTitle()); + + $this->expectException(InvalidArgumentException::class); + $glanceDataFields->setTitle("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); + } + + public function testSetText() + { + $glanceDataFields = new GlanceDataFields(); + + $glanceDataFields->setText("This is test text"); + $this->assertEquals("This is test text", $glanceDataFields->getText()); + + $glanceDataFields->setText(null); + $this->assertNull($glanceDataFields->getText()); + + $glanceDataFields->setText(""); + $this->assertEquals("", $glanceDataFields->getText()); + + $this->expectException(InvalidArgumentException::class); + $glanceDataFields->setText("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); + } + + public function testSetSubtext() + { + $glanceDataFields = new GlanceDataFields(); + + $glanceDataFields->setSubtext("This is test subtext"); + $this->assertEquals("This is test subtext", $glanceDataFields->getSubtext()); + + $glanceDataFields->setSubtext(null); + $this->assertNull($glanceDataFields->getSubtext()); + + $glanceDataFields->setSubtext(""); + $this->assertEquals("", $glanceDataFields->getSubtext()); + + $this->expectException(InvalidArgumentException::class); + $glanceDataFields->setSubtext("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); + } + + public function testSetCount() + { + $glanceDataFields = new GlanceDataFields(); + + $glanceDataFields->setCount(0); + $this->assertEquals(0, $glanceDataFields->getCount()); + + $glanceDataFields->setCount(-1000); + $this->assertEquals(-1000, $glanceDataFields->getCount()); + + $glanceDataFields->setCount(1000); + $this->assertEquals(1000, $glanceDataFields->getCount()); + } + + public function testSetPercent() + { + $glanceDataFields = new GlanceDataFields(); + + $glanceDataFields->setPercent(0); + $this->assertEquals(0, $glanceDataFields->getPercent()); + + $glanceDataFields->setPercent(100); + $this->assertEquals(100, $glanceDataFields->getPercent()); + + $this->expectException(InvalidArgumentException::class); + $glanceDataFields->setPercent(101); + } + + public function testGetTitle() + { + $glanceDataFields = new GlanceDataFields(); + + $this->assertNull($glanceDataFields->getTitle()); + } + + public function testGetText() + { + $glanceDataFields = new GlanceDataFields(); + + $this->assertNull($glanceDataFields->getText()); + } + + public function testGetSubtext() + { + $glanceDataFields = new GlanceDataFields(); + + $this->assertNull($glanceDataFields->getSubtext()); + } + + public function testGetCount() + { + $glanceDataFields = new GlanceDataFields(); + + $this->assertNull($glanceDataFields->getCount()); + } + + public function testGetPercent() + { + $glanceDataFields = new GlanceDataFields(); + + $this->assertNull($glanceDataFields->getPercent()); + } +} diff --git a/tests/Api/Glances/GlanceTest.php b/tests/Api/Glances/GlanceTest.php new file mode 100644 index 0000000..7571127 --- /dev/null +++ b/tests/Api/Glances/GlanceTest.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Api\Glances; + +use Serhiy\Pushover\Api\Glances\Glance; +use PHPUnit\Framework\TestCase; +use Serhiy\Pushover\Api\Glances\GlanceDataFields; +use Serhiy\Pushover\Application; +use Serhiy\Pushover\Recipient; + +/** + * @author Serhiy Lunak + */ +class GlanceTest extends TestCase +{ + /** + * @return Glance + */ + public function testCanBeCreated(): Glance + { + $application = new Application("zaGDORePK8gMaC0QOYAMyEEuzJnyUi"); // using dummy token + $recipient = new Recipient("uQiRzpo4DXghDmr9QzzfQu27cmVRsG"); // using dummy user key + $glanceDataFields = new GlanceDataFields(); + + $glance = new Glance($application, $glanceDataFields); + $glance->setRecipient($recipient); + + $this->assertInstanceOf(Glance::class, $glance); + + return $glance; + } + + /** + * @depends testCanBeCreated + * @param Glance $glance + */ + public function testGetGlanceDataFields(Glance $glance) + { + $this->assertInstanceOf(GlanceDataFields::class, $glance->getGlanceDataFields()); + } + + /** + * @depends testCanBeCreated + * @param Glance $glance + */ + public function testGetApplication(Glance $glance) + { + $this->assertInstanceOf(Application::class, $glance->getApplication()); + } + + /** + * @depends testCanBeCreated + * @param Glance $glance + */ + public function testGetRecipient(Glance $glance) + { + $this->assertInstanceOf(Recipient::class, $glance->getRecipient()); + } + + /** + * @depends testCanBeCreated + * @param Glance $glance + */ + public function testSetApplication(Glance $glance) + { + $application = new Application("zaGDORePK8gMaC0QOYAMyEEuzJnyUi"); // using dummy token + $glance->setApplication($application); + + $this->assertInstanceOf(Application::class, $glance->getApplication()); + } + + /** + * @depends testCanBeCreated + * @param Glance $glance + */ + public function testSetGlanceDataFields(Glance $glance) + { + $glanceDataFields = new GlanceDataFields(); + $glance->setGlanceDataFields($glanceDataFields); + + $this->assertInstanceOf(GlanceDataFields::class, $glance->getGlanceDataFields()); + } + + /** + * @depends testCanBeCreated + * @param Glance $glance + */ + public function testSetRecipient(Glance $glance) + { + $recipient = new Recipient("uQiRzpo4DXghDmr9QzzfQu27cmVRsG"); // using dummy user key + $glance->setRecipient($recipient); + + $this->assertInstanceOf(Recipient::class, $recipient); + } + + /** + * @depends testCanBeCreated + * @param Glance $glance + */ + public function testHasAtLeastOneField(Glance $glance) + { + $this->assertFalse($glance->hasAtLeastOneField()); + + $glance->getGlanceDataFields()->setTitle("This is test title"); + + $this->assertTrue($glance->hasAtLeastOneField()); + } + + /** + * @depends testCanBeCreated + * @param Glance $glance + */ + public function testHasRecipient(Glance $glance) + { + $this->assertTrue($glance->hasRecipient()); + } +} diff --git a/tests/Client/GlancesClientTest.php b/tests/Client/GlancesClientTest.php new file mode 100644 index 0000000..4288408 --- /dev/null +++ b/tests/Client/GlancesClientTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Client; + +use Serhiy\Pushover\Api\Glances\Glance; +use Serhiy\Pushover\Api\Glances\GlanceDataFields; +use Serhiy\Pushover\Application; +use Serhiy\Pushover\Client\GlancesClient; +use PHPUnit\Framework\TestCase; +use Serhiy\Pushover\Recipient; + +class GlancesClientTest extends TestCase +{ + public function testCanBeCreated() + { + $client = new GlancesClient(); + + $this->assertInstanceOf(GlancesClient::class, $client); + } + + public function testBuildApiUrl() + { + $client = new GlancesClient(); + + $this->assertEquals("https://api.pushover.net/1/glances.json", $client->buildApiUrl()); + } + + public function testBuildCurlPostFields() + { + $client = new GlancesClient(); + + $application = new Application("zaGDORePK8gMaC0QOYAMyEEuzJnyUi"); // using dummy token + $recipient = new Recipient("uQiRzpo4DXghDmr9QzzfQu27cmVRsG"); // using dummy user key + $glanceDataFields = new GlanceDataFields(); + + $glance = new Glance($application, $glanceDataFields); + $glance->setRecipient($recipient); + $glance->getGlanceDataFields()->setTitle("This is test title"); + + $curlPostFields = array( + "token" => "zaGDORePK8gMaC0QOYAMyEEuzJnyUi", + "user" => "uQiRzpo4DXghDmr9QzzfQu27cmVRsG", + "title" => "This is test title", + ); + + $this->assertEquals($curlPostFields, $client->buildCurlPostFields($glance)); + } +} diff --git a/tests/Client/Response/GlancesResponseTest.php b/tests/Client/Response/GlancesResponseTest.php new file mode 100644 index 0000000..b697570 --- /dev/null +++ b/tests/Client/Response/GlancesResponseTest.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Client\Response; + +use Serhiy\Pushover\Client\Response\GlancesResponse; +use PHPUnit\Framework\TestCase; + +class GlancesResponseTest extends TestCase +{ + public function testCanBeCreated() + { + $successfulCurlResponse = '{"status":1,"request":"6g890a90-7943-4at2-b739-4aubi545b508"}'; + $response = new GlancesResponse($successfulCurlResponse); + + $this->assertInstanceOf(GlancesResponse::class, $response); + $this->assertTrue($response->isSuccessful()); + $this->assertEquals("6g890a90-7943-4at2-b739-4aubi545b508", $response->getRequestToken()); + + $unSuccessfulCurlResponse = '{"token":"invalid","errors":["application token is invalid"],"status":0,"request":"6g890a90-7943-4at2-b739-4aubi545b508"}'; + $response = new GlancesResponse($unSuccessfulCurlResponse); + + $this->assertInstanceOf(GlancesResponse::class, $response); + $this->assertFalse($response->isSuccessful()); + $this->assertEquals("6g890a90-7943-4at2-b739-4aubi545b508", $response->getRequestToken()); + $this->assertEquals(array(0 => "application token is invalid"), $response->getErrors()); + } +}