diff --git a/README.md b/README.md index 83ff9164..f1c852b4 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ from your PHP app or script. Designed to work with the self-hosted Parse Server - [Push Notifications](#push) - [Push to Channels](#push-to-channels) - [Push with Query](#push-with-query) + - [Push with Audience](#push-with-audience) - [Push Status](#push-status) - [Server Info](#server-info) - [Version](#version) @@ -216,6 +217,7 @@ use Parse\ParseClient; use Parse\ParsePushStatus; use Parse\ParseServerInfo; use Parse\ParseLogs; +use Parse\ParseAudience; ``` ### Parse Objects @@ -428,6 +430,42 @@ ParsePush::send(array( ), true); ``` +#### Push with Audience + +If you want to keep track of your sends when using queries you can use the `ParseAudience` class. +You can create and configure your Audience objects with a name and query. +When you indicate it's being used in a push the `lastUsed` and `timesUsed` values are updated for you. +```php +$iosQuery = ParseInstallation::getQuery(); +$iosQuery->equalTo("deviceType", "ios"); + +// create & save your audience +$audience = ParseAudience::createAudience( + 'MyiOSAudience', + $iosQuery +); +$audience->save(true); + +// send a push using the query in this audience and it's id +// The 'audience_id' is what allows parse to update 'lastUsed' and 'timesUsed' +// You could use any audience_id with any query and it will still update that audience +ParsePush::send([ + 'data' => [ + 'alert' => 'hello ios users!' + ], + 'where' => $audience->getQuery(), + 'audience_id' => $audience->getObjectId() +], true); + +// fetch changes to this audience +$audience->fetch(true); + +// get last & times used for tracking +$timesUsed = $audience->getTimesUsed(); +$lastUsed = $audience->getLastUsed(); +``` +Audiences provide you with a convenient way to group your queries and keep track of how often and when you send to them. + #### Push Status If your server supports it you can extract and check the current status of your pushes. diff --git a/src/Parse/ParseAudience.php b/src/Parse/ParseAudience.php new file mode 100644 index 00000000..27a79da8 --- /dev/null +++ b/src/Parse/ParseAudience.php @@ -0,0 +1,99 @@ + + * @package Parse + */ +class ParseAudience extends ParseObject +{ + /** + * Parse Class name + * + * @var string + */ + public static $parseClassName = '_Audience'; + + /** + * Create a new audience with name & query + * + * @param string $name Name of the audience to create + * @param ParseQuery $query Query to create audience with + * @return ParseAudience + */ + public static function createAudience($name, $query) + { + $audience = new ParseAudience(); + $audience->setName($name); + $audience->setQuery($query); + return $audience; + } + + /** + * Sets the name of this audience + * + * @param string $name Name to set + */ + public function setName($name) + { + $this->set('name', $name); + } + + /** + * Gets the name for this audience + * + * @return string + */ + public function getName() + { + return $this->get('name'); + } + + /** + * Sets the query for this Audience + * + * @param ParseQuery $query Query for this Audience + */ + public function setQuery($query) + { + $this->set('query', json_encode($query->_getOptions())); + } + + /** + * Gets the query for this Audience + * + * @return ParseQuery + */ + public function getQuery() + { + $query = new ParseQuery('_Installation'); + $query->_setConditions(json_decode($this->get('query'), true)); + return $query; + } + + /** + * Gets when this Audience was last used + * + * @return \DateTime|null + */ + public function getLastUsed() + { + return $this->get('lastUsed'); + } + + /** + * Gets the times this Audience has been used + * + * @return int + */ + public function getTimesUsed() + { + return $this->get('timesUsed'); + } +} diff --git a/src/Parse/ParseClient.php b/src/Parse/ParseClient.php index bdabef9c..5a0b7d72 100755 --- a/src/Parse/ParseClient.php +++ b/src/Parse/ParseClient.php @@ -151,6 +151,10 @@ public static function initialize( ParsePushStatus::registerSubclass(); } + if (!ParseObject::hasRegisteredSubclass('_Audience')) { + ParseAudience::registerSubclass(); + } + ParseSession::registerSubclass(); self::$applicationId = $app_id; self::$restKey = $rest_key; diff --git a/src/Parse/ParsePushStatus.php b/src/Parse/ParsePushStatus.php index eb0e42d3..b382ad69 100644 --- a/src/Parse/ParsePushStatus.php +++ b/src/Parse/ParsePushStatus.php @@ -75,7 +75,9 @@ public function getPushQuery() $query = new ParseQuery(self::$parseClassName); // set the conditions - $query->_setConditions($queryConditions); + $query->_setConditions([ + 'where' => $queryConditions + ]); return $query; } diff --git a/src/Parse/ParseQuery.php b/src/Parse/ParseQuery.php index 1035b7bc..3d5a7d8e 100755 --- a/src/Parse/ParseQuery.php +++ b/src/Parse/ParseQuery.php @@ -152,8 +152,40 @@ public function _setConditions($conditions) // iterate over and add each condition foreach ($conditions as $key => $entry) { - foreach ($entry as $condition => $value) { - $this->addCondition($key, $condition, $value); + switch ($key) { + case 'where': + $this->where = $entry; + break; + + case 'include': + $this->includes = explode(',', $entry); + break; + + case 'keys': + $this->selectedKeys = explode(',', $entry); + break; + + case 'limit': + $this->limit = $entry; + break; + + // skip + case 'skip': + $this->skip = $entry; + break; + + // orderBy + case 'order': + $this->orderBy = explode(',', $entry); + break; + + // whether this query is for count or not + case 'count': + $this->count = $entry; + break; + + default: + throw new ParseException("Unknown condition to set '{$key}''"); } } } diff --git a/tests/Parse/ParseAudienceTest.php b/tests/Parse/ParseAudienceTest.php new file mode 100644 index 00000000..b885330a --- /dev/null +++ b/tests/Parse/ParseAudienceTest.php @@ -0,0 +1,112 @@ +set('installationId', 'id1'); + $androidInstallation->set('deviceToken', '12345'); + $androidInstallation->set('deviceType', 'android'); + $androidInstallation->save(true); + + $iOSInstallation = new ParseInstallation(); + $iOSInstallation->set('installationId', 'id2'); + $iOSInstallation->set('deviceToken', '54321'); + $iOSInstallation->set('deviceType', 'ios'); + $iOSInstallation->save(); + + ParseObject::saveAll([ + $androidInstallation, + $iOSInstallation + ]); + } + + /** + * @group audience-tests + */ + public function testPushAudiences() + { + $this->createInstallations(); + + $androidQuery = ParseInstallation::query() + ->equalTo('deviceType', 'android'); + + $audience = ParseAudience::createAudience('MyAudience', $androidQuery); + $audience->save(); + + // count no master should be 0 + $query = new ParseQuery('_Audience'); + $this->assertEquals(0, $query->count(), 'No master was not 0'); + + $query = new ParseQuery('_Audience'); + $audience = $query->first(true); + $this->assertNotNull($audience); + + $this->assertEquals('MyAudience', $audience->getName()); + $this->assertEquals($androidQuery, $audience->getQuery()); + $this->assertNull($audience->getLastUsed()); + $this->assertEquals(0, $audience->getTimesUsed()); + } + + /** + * @group audience-tests + */ + public function testSaveWithoutMaster() + { + $query = ParseAudience::query(); + $this->assertEquals(0, $query->count(true), 'Did not start at 0'); + + $audience = ParseAudience::createAudience( + 'MyAudience', + ParseInstallation::query() + ->equalTo('deviceType', 'android') + ); + $audience->save(); + + $query = ParseAudience::query(); + $this->assertEquals(1, $query->count(true), 'Did not end at 1'); + } + + /** + * @group audience-tests + */ + public function testPushWithAudience() + { + $this->createInstallations(); + + $audience = ParseAudience::createAudience( + 'MyAudience', + ParseInstallation::query() + ->equalTo('deviceType', 'android') + ); + $audience->save(true); + + ParsePush::send([ + 'data' => [ + 'alert' => 'sample message' + ], + 'where' => $audience->getQuery(), + 'audience_id' => $audience->getObjectId() + ], true); + + $audience->fetch(true); + + $this->assertEquals(1, $audience->getTimesUsed()); + $this->assertNotNull($audience->getLastUsed()); + } +} diff --git a/tests/Parse/ParseObjectTest.php b/tests/Parse/ParseObjectTest.php index 1bb791a9..454234b1 100644 --- a/tests/Parse/ParseObjectTest.php +++ b/tests/Parse/ParseObjectTest.php @@ -7,6 +7,7 @@ use Parse\Internal\SetOperation; use Parse\ParseACL; use Parse\ParseAggregateException; +use Parse\ParseAudience; use Parse\ParseClient; use Parse\ParseException; use Parse\ParseFile; @@ -1077,6 +1078,7 @@ public function testNoRegisteredSubclasses() ParseInstallation::_unregisterSubclass(); ParseSession::_unregisterSubclass(); ParsePushStatus::_unregisterSubclass(); + ParseAudience::_unregisterSubclass(); new ParseObject('TestClass'); } diff --git a/tests/Parse/ParseQueryTest.php b/tests/Parse/ParseQueryTest.php index 09f07ac9..c3dd74da 100644 --- a/tests/Parse/ParseQueryTest.php +++ b/tests/Parse/ParseQueryTest.php @@ -2263,14 +2263,62 @@ public function testSetConditions() $this->assertEquals([ 'where' => [ - [ - 'key' => 'value' - ] + 'key' => 'value' ] ], $query->_getOptions()); } - public function testBadConditions() + /** + * @group query-set-conditions + */ + public function testGetAndSetConditions() + { + $query = new ParseQuery('TestObject'); + $query->equalTo('key', 'value'); + $query->notEqualTo('key2', 'value2'); + $query->includeKey(['include1','include2']); + $query->contains('container', 'item'); + $query->addDescending('desc'); + $query->addAscending('asc'); + $query->select(['select1','select2']); + $query->skip(24); + + // sets count = 1 and limit = 0 + $query->count(); + // reset limit up to 42 + $query->limit(42); + + $conditions = $query->_getOptions(); + + $this->assertEquals([ + 'where' => [ + 'key' => 'value', + 'key2' => [ + '$ne' => 'value2', + ], + 'container' => [ + '$regex' => '\Qitem\E' + ] + ], + 'include' => 'include1,include2', + 'keys' => 'select1,select2', + 'limit' => 42, + 'skip' => 24, + 'order' => '-desc,asc', + 'count' => 1 + ], $conditions, 'Conditions were different than expected'); + + $query2 = new ParseQuery('TestObject'); + $query2->_setConditions($conditions); + + $this->assertEquals( + $query, + $query2, + 'Conditions set on query did not give the expected result' + ); + } + + public function testNotArrayConditions() { $this->setExpectedException( '\Parse\ParseException', @@ -2280,4 +2328,20 @@ public function testBadConditions() $query = new ParseQuery('TestObject'); $query->_setConditions('not-an-array'); } + + /** + * @group query-set-conditions + */ + public function testUnknownCondition() + { + $this->setExpectedException( + '\Parse\ParseException', + 'Unknown condition to set \'unrecognized\'' + ); + + $query = new ParseQuery('TestObject'); + $query->_setConditions([ + 'unrecognized' => 1 + ]); + } }