├── tests
├── keys
│ ├── parseca.srl
│ ├── client.fp
│ ├── localhost.fp
│ ├── parseca.fp
│ ├── client.der
│ ├── localhost.pubkey.der
│ ├── localhost.pubkey.pem
│ ├── client.crt
│ ├── client.pem
│ ├── localhost.crt
│ ├── localhost.pem
│ ├── parseca.crt
│ ├── parseca.pem
│ ├── client.key
│ ├── localhost.key
│ └── parseca.key
├── Parse
│ ├── ParseObjectMock.php
│ ├── ConfigMock.php
│ ├── HttpClientMock.php
│ ├── ParseSessionStorageAltTest.php
│ ├── ParseCurlHttpClientTest.php
│ ├── ParseSubclassTest.php
│ ├── ParseBytesTest.php
│ ├── ParseStreamHttpClientTest.php
│ ├── ParseConfigTest.php
│ ├── IncrementOperationTest.php
│ ├── ParseMemoryStorageTest.php
│ ├── ParseCurlTest.php
│ ├── ParseQueryFullTextTest.php
│ ├── ParseSessionFixationTest.php
│ ├── RemoveOperationTest.php
│ ├── AddOperationTest.php
│ ├── ParseLogsTest.php
│ ├── Helper.php
│ ├── ParseAnalyticsTest.php
│ ├── ParseSessionStorageTest.php
│ ├── ParseSessionTest.php
│ ├── ParseAudienceTest.php
│ ├── AddUniqueOperationTest.php
│ ├── ParseInstallationTest.php
│ └── ParseRelationOperationTest.php
├── bootstrap.php
├── MockEmailAdapter.js
├── bootstrap-stream.php
├── gencerts.sh
├── cloud-code.js
└── server.js
├── Assets
└── logo large.png
├── .releaserc
├── template.hbs
├── footer.hbs
├── header.hbs
└── commit.hbs
├── .gitignore
├── .github
├── ISSUE_TEMPLATE
│ ├── config.yml
│ ├── ---2-feature-request.md
│ └── ---1-report-an-issue.md
├── pull_request_template.md
└── workflows
│ ├── release-manual-docs.yml
│ ├── ci.yml
│ └── release-automated.yml
├── phpcs.xml.dist
├── src
└── Parse
│ ├── Internal
│ ├── Encodable.php
│ ├── FieldOperation.php
│ ├── DeleteOperation.php
│ ├── SetOperation.php
│ ├── IncrementOperation.php
│ ├── AddOperation.php
│ ├── RemoveOperation.php
│ └── AddUniqueOperation.php
│ ├── ParseException.php
│ ├── ParseAggregateException.php
│ ├── ParseStorageInterface.php
│ ├── ParseBytes.php
│ ├── ParseMemoryStorage.php
│ ├── ParsePolygon.php
│ ├── ParseAnalytics.php
│ ├── ParseAudience.php
│ ├── ParseConfig.php
│ ├── HttpClients
│ ├── ParseHttpable.php
│ ├── ParseStream.php
│ └── ParseCurl.php
│ ├── ParseCloud.php
│ ├── ParseGeoPoint.php
│ ├── ParseLogs.php
│ ├── ParseSessionStorage.php
│ ├── ParseInstallation.php
│ ├── ParseRole.php
│ ├── ParseSession.php
│ ├── ParseRelation.php
│ ├── ParseServerInfo.php
│ ├── ParsePush.php
│ └── ParsePushStatus.php
├── phpunit.xml
├── composer.json
├── autoload.php
├── LICENSE
├── package.json
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
└── release.config.cjs
/tests/keys/parseca.srl:
--------------------------------------------------------------------------------
1 | C4C2841E9E0467CC
2 |
--------------------------------------------------------------------------------
/tests/keys/client.fp:
--------------------------------------------------------------------------------
1 | D7:10:BE:24:E6:85:A2:F8:79:F8:36:EF:42:A0:EC:B3:EC:93:C2:FB
2 |
--------------------------------------------------------------------------------
/tests/keys/localhost.fp:
--------------------------------------------------------------------------------
1 | 29:F3:66:76:EF:A0:CA:18:B5:B5:71:C6:14:45:80:04:4C:B2:89:C2
2 |
--------------------------------------------------------------------------------
/tests/keys/parseca.fp:
--------------------------------------------------------------------------------
1 | AF:05:D0:A9:7C:97:BA:64:A0:E2:88:05:D8:9B:07:9C:4C:55:DD:60
2 |
--------------------------------------------------------------------------------
/Assets/logo large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/parse-community/parse-php-sdk/HEAD/Assets/logo large.png
--------------------------------------------------------------------------------
/tests/keys/client.der:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/parse-community/parse-php-sdk/HEAD/tests/keys/client.der
--------------------------------------------------------------------------------
/tests/keys/localhost.pubkey.der:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/parse-community/parse-php-sdk/HEAD/tests/keys/localhost.pubkey.der
--------------------------------------------------------------------------------
/.releaserc/template.hbs:
--------------------------------------------------------------------------------
1 | {{> header}}
2 |
3 | {{#each commitGroups}}
4 |
5 | {{#if title}}
6 | ### {{title}}
7 |
8 | {{/if}}
9 | {{#each commits}}
10 | {{> commit root=@root}}
11 | {{/each}}
12 | {{/each}}
13 |
14 | {{> footer}}
15 |
--------------------------------------------------------------------------------
/tests/Parse/ParseObjectMock.php:
--------------------------------------------------------------------------------
1 | setConfig([
13 | 'foo' => 'bar',
14 | 'some' => 1,
15 | 'another' => 'value'
16 | ]);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
30 | * $dimensions = array(
31 | * 'gender' => 'm',
32 | * 'source' => 'web',
33 | * 'dayType' => 'weekend'
34 | * );
35 | * ParseAnalytics::track('signup', $dimensions);
36 | *
37 | *
38 | * There is a default limit of 4 dimensions per event tracked.
39 | *
40 | * @param string $name The name of the custom event
41 | * @param array $dimensions The dictionary of segment information
42 | *
43 | * @throws \Exception
44 | *
45 | * @return mixed
46 | */
47 | public static function track($name, $dimensions = [])
48 | {
49 | $name = trim($name);
50 |
51 | if (strlen($name) === 0) {
52 | throw new Exception('A name for the custom event must be provided.');
53 | }
54 |
55 | foreach ($dimensions as $key => $value) {
56 | if (!is_string($key) || !is_string($value)) {
57 | throw new Exception('Dimensions expected string keys and values.');
58 | }
59 | }
60 |
61 | return ParseClient::_request(
62 | 'POST',
63 | 'events/'.$name,
64 | null,
65 | static::_toSaveJSON($dimensions)
66 | );
67 | }
68 |
69 | /**
70 | * Encodes and returns the given data as a json object
71 | *
72 | * @param array $data Data to encode
73 | * @return string
74 | */
75 | public static function _toSaveJSON($data)
76 | {
77 | return json_encode(
78 | [
79 | 'dimensions' => $data,
80 | ],
81 | JSON_FORCE_OBJECT
82 | );
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/Parse/ParseAudience.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Parse
13 | */
14 | class ParseAudience extends ParseObject
15 | {
16 | /**
17 | * Parse Class name
18 | *
19 | * @var string
20 | */
21 | public static $parseClassName = '_Audience';
22 |
23 | /**
24 | * Create a new audience with name & query
25 | *
26 | * @param string $name Name of the audience to create
27 | * @param ParseQuery $query Query to create audience with
28 | * @return ParseAudience
29 | */
30 | public static function createAudience($name, $query)
31 | {
32 | $audience = new ParseAudience();
33 | $audience->setName($name);
34 | $audience->setQuery($query);
35 | return $audience;
36 | }
37 |
38 | /**
39 | * Sets the name of this audience
40 | *
41 | * @param string $name Name to set
42 | */
43 | public function setName($name)
44 | {
45 | $this->set('name', $name);
46 | }
47 |
48 | /**
49 | * Gets the name for this audience
50 | *
51 | * @return string
52 | */
53 | public function getName()
54 | {
55 | return $this->get('name');
56 | }
57 |
58 | /**
59 | * Sets the query for this Audience
60 | *
61 | * @param ParseQuery $query Query for this Audience
62 | */
63 | public function setQuery($query)
64 | {
65 | $this->set('query', json_encode($query->_getOptions()));
66 | }
67 |
68 | /**
69 | * Gets the query for this Audience
70 | *
71 | * @return ParseQuery
72 | */
73 | public function getQuery()
74 | {
75 | $query = new ParseQuery('_Installation');
76 | $query->_setConditions(json_decode($this->get('query'), true));
77 | return $query;
78 | }
79 |
80 | /**
81 | * Gets when this Audience was last used
82 | *
83 | * @return \DateTime|null
84 | */
85 | public function getLastUsed()
86 | {
87 | return $this->get('lastUsed');
88 | }
89 |
90 | /**
91 | * Gets the times this Audience has been used
92 | *
93 | * @return int
94 | */
95 | public function getTimesUsed()
96 | {
97 | return $this->get('timesUsed');
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/tests/Parse/RemoveOperationTest.php:
--------------------------------------------------------------------------------
1 | expectException(
23 | '\Parse\ParseException',
24 | 'RemoveOperation requires an array.'
25 | );
26 | new RemoveOperation('not an array');
27 | }
28 |
29 | /**
30 | * @group remove-op-merge
31 | */
32 | public function testMergePrevious()
33 | {
34 | $removeOp = new RemoveOperation([
35 | 'key1' => 'value1'
36 | ]);
37 |
38 | $this->assertEquals($removeOp, $removeOp->_mergeWithPrevious(null));
39 |
40 | // check delete op
41 | $merged = $removeOp->_mergeWithPrevious(new DeleteOperation());
42 | $this->assertTrue($merged instanceof DeleteOperation);
43 |
44 | // check set op
45 | $merged = $removeOp->_mergeWithPrevious(new SetOperation('newvalue'));
46 | $this->assertTrue($merged instanceof SetOperation);
47 | $this->assertEquals([
48 | 'newvalue'
49 | ], $merged->getValue(), 'Value was not as expected');
50 |
51 | // check self
52 | $merged = $removeOp->_mergeWithPrevious(new RemoveOperation(['key2' => 'value2']));
53 | $this->assertTrue($merged instanceof RemoveOperation);
54 | $this->assertEquals([
55 | 'key2' => 'value2',
56 | 'key1' => 'value1'
57 | ], $merged->getValue(), 'Value was not as expected');
58 | }
59 |
60 | /**
61 | * @group remove-op
62 | */
63 | public function testInvalidMerge()
64 | {
65 | $this->expectException(
66 | '\Parse\ParseException',
67 | 'Operation is invalid after previous operation.'
68 | );
69 | $removeOp = new RemoveOperation([
70 | 'key1' => 'value1'
71 | ]);
72 | $removeOp->_mergeWithPrevious(new AddOperation(['key'=>'value']));
73 | }
74 |
75 | /**
76 | * @group remove-op
77 | */
78 | public function testEmptyApply()
79 | {
80 | $removeOp = new RemoveOperation([
81 | 'key1' => 'value1'
82 | ]);
83 | $this->assertEmpty($removeOp->_apply([], null, null));
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/tests/Parse/AddOperationTest.php:
--------------------------------------------------------------------------------
1 | 'val'
27 | ];
28 | $addOp = new AddOperation($objects);
29 |
30 | $this->assertEquals($objects, $addOp->getValue());
31 | }
32 |
33 | /**
34 | * @group add-op
35 | */
36 | public function testBadObjects()
37 | {
38 | $this->expectException(
39 | '\Parse\ParseException',
40 | 'AddOperation requires an array.'
41 | );
42 | new AddOperation('not an array');
43 | }
44 |
45 | /**
46 | * @group add-op
47 | */
48 | public function testMergePrevious()
49 | {
50 | $addOp = new AddOperation([
51 | 'key1' => 'value1'
52 | ]);
53 |
54 | $this->assertEquals($addOp, $addOp->_mergeWithPrevious(null));
55 |
56 | // check delete op
57 | $merged = $addOp->_mergeWithPrevious(new DeleteOperation());
58 | $this->assertTrue($merged instanceof SetOperation);
59 |
60 | // check set op
61 | $merged = $addOp->_mergeWithPrevious(new SetOperation('newvalue'));
62 | $this->assertTrue($merged instanceof SetOperation);
63 | $this->assertEquals([
64 | 'newvalue',
65 | 'key1' => 'value1'
66 | ], $merged->getValue(), 'Value was not as expected');
67 |
68 | // check self
69 | $merged = $addOp->_mergeWithPrevious(new AddOperation(['key2' => 'value2']));
70 | $this->assertTrue($merged instanceof SetOperation);
71 | $this->assertEquals([
72 | 'key2' => 'value2',
73 | 'key1' => 'value1'
74 | ], $merged->getValue(), 'Value was not as expected');
75 | }
76 |
77 | /**
78 | * @group add-op
79 | */
80 | public function testInvalidMerge()
81 | {
82 | $this->expectException(
83 | '\Parse\ParseException',
84 | 'Operation is invalid after previous operation.'
85 | );
86 | $addOp = new AddOperation([
87 | 'key1' => 'value1'
88 | ]);
89 | $addOp->_mergeWithPrevious(new \DateTime());
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Parse/ParseConfig.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Parse
13 | */
14 | class ParseConfig
15 | {
16 | /**
17 | * Current configuration data
18 | *
19 | * @var array
20 | */
21 | private $currentConfig;
22 |
23 | /**
24 | * ParseConfig constructor.
25 | */
26 | public function __construct()
27 | {
28 | $result = ParseClient::_request('GET', 'config');
29 | $this->setConfig($result['params']);
30 | }
31 |
32 | /**
33 | * Gets a config value
34 | *
35 | * @param string $key Key of value to get
36 | * @return mixed
37 | */
38 | public function get($key)
39 | {
40 | if (isset($this->currentConfig[$key])) {
41 | return $this->currentConfig[$key];
42 | }
43 | return null;
44 | }
45 |
46 | /**
47 | * Sets a config value
48 | *
49 | * @param string $key Key to set value on
50 | * @param mixed $value Value to set
51 | */
52 | public function set($key, $value)
53 | {
54 | $this->currentConfig[$key] = $value;
55 | }
56 |
57 | /**
58 | * Gets a config value with html characters encoded
59 | *
60 | * @param string $key Key of value to get
61 | * @return string|null
62 | */
63 | public function escape($key)
64 | {
65 | if (isset($this->currentConfig[$key])) {
66 | return htmlentities($this->currentConfig[$key]);
67 | }
68 | return null;
69 | }
70 |
71 | /**
72 | * Sets the config
73 | *
74 | * @param array $config Config to set
75 | */
76 | protected function setConfig($config)
77 | {
78 | $this->currentConfig = $config;
79 | }
80 |
81 | /**
82 | * Gets the current config
83 | *
84 | * @return array
85 | */
86 | public function getConfig()
87 | {
88 | return $this->currentConfig;
89 | }
90 |
91 | /**
92 | * Saves the current config
93 | *
94 | * @return bool
95 | */
96 | public function save()
97 | {
98 | $response = ParseClient::_request(
99 | 'PUT',
100 | 'config',
101 | null,
102 | json_encode([
103 | 'params' => $this->currentConfig
104 | ]),
105 | true
106 | );
107 | return $response['result'];
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/Parse/HttpClients/ParseHttpable.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Parse\HttpClients
13 | */
14 | interface ParseHttpable
15 | {
16 | /**
17 | * Adds a header to this request
18 | *
19 | * @param string $key Header name
20 | * @param string $value Header value
21 | */
22 | public function addRequestHeader($key, $value);
23 |
24 | /**
25 | * Gets headers in the response
26 | *
27 | * @return array
28 | */
29 | public function getResponseHeaders();
30 |
31 | /**
32 | * Returns the status code of the response
33 | *
34 | * @return int
35 | */
36 | public function getResponseStatusCode();
37 |
38 | /**
39 | * Returns the content type of the response
40 | *
41 | * @return null|string
42 | */
43 | public function getResponseContentType();
44 |
45 | /**
46 | * Sets the connection timeout
47 | *
48 | * @param int $timeout Timeout to set
49 | */
50 | public function setConnectionTimeout($timeout);
51 |
52 | /**
53 | * Sets the request timeout
54 | *
55 | * @param int $timeout Sets the timeout for the request
56 | */
57 | public function setTimeout($timeout);
58 |
59 | /**
60 | * Sets the CA file to validate requests with
61 | *
62 | * @param string $caFile CA file to set
63 | */
64 | public function setCAFile($caFile);
65 |
66 | /**
67 | * Sets http options to pass to the http client
68 | *
69 | * @param string $httpOptions Options to set
70 | */
71 | public function setHttpOptions($httpOptions);
72 |
73 | /**
74 | * Gets the error code
75 | *
76 | * @return int
77 | */
78 | public function getErrorCode();
79 |
80 | /**
81 | * Gets the error message
82 | *
83 | * @return string
84 | */
85 | public function getErrorMessage();
86 |
87 | /**
88 | * Sets up our client before we make a request
89 | */
90 | public function setup() : void;
91 |
92 | /**
93 | * Sends an HTTP request
94 | *
95 | * @param string $url Url to send this request to
96 | * @param string $method Method to send this request via
97 | * @param array $data Data to send in this request
98 | * @return string
99 | */
100 | public function send($url, $method = 'GET', $data = array());
101 | }
102 |
--------------------------------------------------------------------------------
/src/Parse/ParseCloud.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Parse
13 | */
14 | class ParseCloud
15 | {
16 | /**
17 | * Makes a call to a Cloud function.
18 | *
19 | * @param string $name Cloud function name
20 | * @param array $data Parameters to pass
21 | * @param bool $useMasterKey Whether to use the Master Key
22 | *
23 | * @return mixed
24 | */
25 | public static function run($name, $data = [], $useMasterKey = false)
26 | {
27 | $sessionToken = null;
28 | if (ParseUser::getCurrentUser()) {
29 | $sessionToken = ParseUser::getCurrentUser()->getSessionToken();
30 | }
31 | $response = ParseClient::_request(
32 | 'POST',
33 | 'functions/'.$name,
34 | $sessionToken,
35 | json_encode(ParseClient::_encode($data, false)),
36 | $useMasterKey
37 | );
38 |
39 | return ParseClient::_decode($response['result']);
40 | }
41 |
42 | /**
43 | * Gets data for the current set of cloud jobs
44 | *
45 | * @return array
46 | */
47 | public static function getJobsData()
48 | {
49 | $response = ParseClient::_request(
50 | 'GET',
51 | 'cloud_code/jobs/data',
52 | null,
53 | null,
54 | true
55 | );
56 |
57 | return ParseClient::_decode($response);
58 | }
59 |
60 | /**
61 | * Starts a given cloud job, which will process asynchronously
62 | *
63 | * @param string $jobName Name of job to run
64 | * @param array $data Parameters to pass
65 | * @return string Id for tracking job status
66 | */
67 | public static function startJob($jobName, $data = [])
68 | {
69 | $response = ParseClient::_request(
70 | 'POST',
71 | 'jobs/'.$jobName,
72 | null,
73 | json_encode(ParseClient::_encode($data, false)),
74 | true,
75 | 'application/json',
76 | true
77 | );
78 |
79 | return ParseClient::_decode($response)['_headers']['X-Parse-Job-Status-Id'];
80 | }
81 |
82 | /**
83 | * Gets job status by id
84 | *
85 | * @param string $jobStatusId Id of the job status to return
86 | * @return array|ParseObject
87 | */
88 | public static function getJobStatus($jobStatusId)
89 | {
90 | $query = new ParseQuery('_JobStatus');
91 | return $query->get($jobStatusId, true);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/Parse/Internal/SetOperation.php:
--------------------------------------------------------------------------------
1 |
14 | * @package Parse\Internal
15 | */
16 | class SetOperation implements FieldOperation
17 | {
18 | /**
19 | * Value to set for this operation.
20 | *
21 | * @var mixed
22 | */
23 | private $value;
24 |
25 | /**
26 | * If the value should be forced as object.
27 | *
28 | * @var bool
29 | */
30 | private $isAssociativeArray;
31 |
32 | /**
33 | * Create a SetOperation with a value.
34 | *
35 | * @param mixed $value Value to set for this operation.
36 | * @param bool $isAssociativeArray If the value should be forced as object.
37 | */
38 | public function __construct($value, $isAssociativeArray = false)
39 | {
40 | $this->value = $value;
41 | $this->isAssociativeArray = $isAssociativeArray;
42 | }
43 |
44 | /**
45 | * Get the value for this operation.
46 | *
47 | * @return mixed Value.
48 | */
49 | public function getValue()
50 | {
51 | return $this->value;
52 | }
53 |
54 | /**
55 | * Returns an associative array encoding of the current operation.
56 | *
57 | * @return mixed
58 | */
59 | public function _encode()
60 | {
61 | if ($this->isAssociativeArray) {
62 | $object = new \stdClass();
63 | foreach ($this->value as $key => $value) {
64 | $object->$key = ParseClient::_encode($value, true);
65 | }
66 |
67 | return ParseClient::_encode($object, true);
68 | }
69 |
70 | return ParseClient::_encode($this->value, true);
71 | }
72 |
73 | /**
74 | * Apply the current operation and return the result.
75 | *
76 | * @param mixed $oldValue Value prior to this operation.
77 | * @param mixed $object Value for this operation.
78 | * @param string $key Key to set this value on.
79 | *
80 | * @return mixed
81 | */
82 | public function _apply($oldValue, $object, $key)
83 | {
84 | return $this->value;
85 | }
86 |
87 | /**
88 | * Merge this operation with a previous operation and return the
89 | * resulting operation.
90 | *
91 | * @param FieldOperation $previous Previous operation.
92 | *
93 | * @return FieldOperation
94 | */
95 | public function _mergeWithPrevious($previous)
96 | {
97 | return $this;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/tests/Parse/ParseLogsTest.php:
--------------------------------------------------------------------------------
1 | assertNotEmpty($logs);
35 | $this->assertEquals(1, count($logs));
36 | }
37 |
38 | /**
39 | * @group parse-logs-tests
40 | */
41 | public function testGettingOneLog()
42 | {
43 | $logs = ParseLogs::getInfoLogs(1);
44 | $this->assertEquals(1, count($logs));
45 | $this->assertEquals($logs[0]['method'], 'GET');
46 | $this->assertTrue(isset($logs[0]['url']));
47 | }
48 |
49 | /**
50 | * @group parse-logs-tests
51 | */
52 | public function testFrom()
53 | {
54 | // test getting logs from 4 hours in the future
55 | $date = new \DateTime();
56 | $date->add(new \DateInterval('PT4H'));
57 | $logs = ParseLogs::getInfoLogs(1, $date);
58 | $this->assertEquals(0, count($logs));
59 | }
60 |
61 | /**
62 | * @group parse-logs-tests
63 | */
64 | public function testUntil()
65 | {
66 | // test getting logs from 1950 years in the past (not likely...)
67 | $date = new \DateTime();
68 | $date->sub(new \DateInterval('P1950Y'));
69 | $logs = ParseLogs::getInfoLogs(1, null, $date);
70 | $this->assertEquals(0, count($logs));
71 | }
72 |
73 | /**
74 | * @group parse-logs-tests
75 | */
76 | public function testOrderAscending()
77 | {
78 | $logs = ParseLogs::getInfoLogs(15, null, null, 'asc');
79 | $this->assertEquals(15, count($logs));
80 |
81 | $timestamp1 = $logs[0]['timestamp'];
82 | $timestamp2 = $logs[count($logs)-1]['timestamp'];
83 |
84 | $timestamp1 = preg_replace('/Z$/', '', $timestamp1);
85 | $timestamp2 = preg_replace('/Z$/', '', $timestamp2);
86 |
87 | // get first 2 entries
88 | $entryDate1 = \DateTime::createFromFormat('Y-m-d\TH:i:s.u', $timestamp1);
89 | $entryDate2 = \DateTime::createFromFormat('Y-m-d\TH:i:s.u', $timestamp2);
90 |
91 | $this->assertTrue($entryDate1 < $entryDate2);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/Parse/ParseGeoPoint.php:
--------------------------------------------------------------------------------
1 |
14 | * @package Parse
15 | */
16 | class ParseGeoPoint implements Encodable
17 | {
18 | /**
19 | * The latitude.
20 | *
21 | * @var float
22 | */
23 | private $latitude;
24 |
25 | /**
26 | * The longitude.
27 | *
28 | * @var float
29 | */
30 | private $longitude;
31 |
32 | /**
33 | * Create a Parse GeoPoint object.
34 | *
35 | * @param float $lat Latitude.
36 | * @param float $lon Longitude.
37 | */
38 | public function __construct($lat, $lon)
39 | {
40 | $this->setLatitude($lat);
41 | $this->setLongitude($lon);
42 | }
43 |
44 | /**
45 | * Returns the Latitude value for this GeoPoint.
46 | *
47 | * @return float
48 | */
49 | public function getLatitude()
50 | {
51 | return $this->latitude;
52 | }
53 |
54 | /**
55 | * Set the Latitude value for this GeoPoint.
56 | *
57 | * @param float $lat
58 | *
59 | * @throws ParseException
60 | */
61 | public function setLatitude($lat)
62 | {
63 | if (is_numeric($lat) && !is_float($lat)) {
64 | $lat = (float)$lat;
65 | }
66 | if ($lat > 90.0 || $lat < -90.0) {
67 | throw new ParseException('Latitude must be within range [-90.0, 90.0]');
68 | }
69 | $this->latitude = $lat;
70 | }
71 |
72 | /**
73 | * Returns the Longitude value for this GeoPoint.
74 | *
75 | * @return float
76 | */
77 | public function getLongitude()
78 | {
79 | return $this->longitude;
80 | }
81 |
82 | /**
83 | * Set the Longitude value for this GeoPoint.
84 | *
85 | * @param float $lon
86 | *
87 | * @throws ParseException
88 | */
89 | public function setLongitude($lon)
90 | {
91 | if (is_numeric($lon) && !is_float($lon)) {
92 | $lon = (float)$lon;
93 | }
94 | if ($lon > 180.0 || $lon < -180.0) {
95 | throw new ParseException(
96 | 'Longitude must be within range [-180.0, 180.0]'
97 | );
98 | }
99 | $this->longitude = $lon;
100 | }
101 |
102 | /**
103 | * Encode to associative array representation.
104 | *
105 | * @return array
106 | */
107 | public function _encode()
108 | {
109 | return [
110 | '__type' => 'GeoPoint',
111 | 'latitude' => $this->latitude,
112 | 'longitude' => $this->longitude,
113 | ];
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/Parse/ParseLogs.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Parse
13 | */
14 | class ParseLogs
15 | {
16 |
17 | /**
18 | * Requests script logs from the server
19 | *
20 | * @param string $level Level of logs to return (info/error), default is info
21 | * @param int $size Number of rows to return, default is 100
22 | * @param null $from Earliest logs to return from, defaults to 1 week ago
23 | * @param null $until Latest logs to return from, defaults to current time
24 | * @param null $order Order to sort logs by (asc/desc), defaults to descending
25 | * @return array
26 | */
27 | public static function getScriptLogs(
28 | $level = 'info',
29 | $size = 100,
30 | $from = null,
31 | $until = null,
32 | $order = null
33 | ) {
34 | $data = [
35 | 'level' => $level,
36 | 'size' => $size,
37 | ];
38 |
39 | if (isset($from) && $from instanceof \DateTime) {
40 | $data['from'] = ParseClient::getProperDateFormat($from);
41 | }
42 |
43 | if (isset($until) && $until instanceof \DateTime) {
44 | $data['until'] = ParseClient::getProperDateFormat($until);
45 | }
46 |
47 | if (isset($order)) {
48 | $data['order'] = $order;
49 | }
50 |
51 | $response = ParseClient::_request(
52 | 'GET',
53 | 'scriptlog',
54 | null,
55 | $data,
56 | true
57 | );
58 |
59 | return $response;
60 | }
61 |
62 | /**
63 | * Returns info logs
64 | *
65 | * @param int $size Lines to return, 100 by default
66 | * @param null $from Earliest logs to return from, default is 1 week ago
67 | * @param null $until Latest logs to return from, defaults to current time
68 | * @param null $order Order to sort logs by (asc/desc), defaults to descending
69 | * @return array
70 | */
71 | public static function getInfoLogs($size = 100, $from = null, $until = null, $order = null)
72 | {
73 | return self::getScriptLogs('info', $size, $from, $until, $order);
74 | }
75 |
76 | /**
77 | * Returns error logs
78 | *
79 | * @param int $size Lines to return, 100 by default
80 | * @param null $from Earliest logs to return from, default is 1 week ago
81 | * @param null $until Latest logs to return from, defaults to current time
82 | * @param null $order Order to sort logs by (asc/desc), defaults to descending
83 | * @return array
84 | */
85 | public static function getErrorLogs($size = 100, $from = null, $until = null, $order = null)
86 | {
87 | return self::getScriptLogs('error', $size, $from, $until, $order);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Parse/HttpClients/ParseStream.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Parse\HttpClients
13 | */
14 | class ParseStream
15 | {
16 | /**
17 | * Stream context
18 | *
19 | * @var resource
20 | */
21 | private $stream;
22 |
23 | /**
24 | * Response headers
25 | *
26 | * @var array|null
27 | */
28 | private $responseHeaders;
29 |
30 | /**
31 | * Error message
32 | *
33 | * @var string
34 | */
35 | private $errorMessage;
36 |
37 | /**
38 | * Error code
39 | *
40 | * @var int
41 | */
42 | private $errorCode;
43 |
44 | /**
45 | * Create a stream context
46 | *
47 | * @param array $options Options to pass to our context
48 | */
49 | public function createContext($options)
50 | {
51 | $this->stream = stream_context_create($options);
52 | }
53 |
54 | /**
55 | * Gets the contents from the given url
56 | *
57 | * @param string $url Url to get contents of
58 | * @return string
59 | */
60 | public function get($url)
61 | {
62 | try {
63 | // get our response
64 | $response = $this->getFileContents($url, false, $this->stream);
65 | $this->errorMessage = null;
66 | $this->errorCode = null;
67 | } catch (\Exception $e) {
68 | // set our error message/code and return false
69 | $this->errorMessage = $e->getMessage();
70 | $this->errorCode = $e->getCode();
71 | $this->responseHeaders = null;
72 | return false;
73 | }
74 | return $response;
75 | }
76 |
77 | /**
78 | * Returns the response headers for the last request
79 | *
80 | * @return array
81 | */
82 | public function getResponseHeaders()
83 | {
84 | return $this->responseHeaders;
85 | }
86 |
87 | /**
88 | * Gets the current error message
89 | *
90 | * @return string
91 | */
92 | public function getErrorMessage()
93 | {
94 | return $this->errorMessage;
95 | }
96 |
97 | /**
98 | * Get the current error code
99 | *
100 | * @return int
101 | */
102 | public function getErrorCode()
103 | {
104 | return $this->errorCode;
105 | }
106 |
107 | /**
108 | * Wrapper for file_get_contents, used for testing
109 | */
110 | public function getFileContents($filename, $use_include_path, $context)
111 | {
112 | $result = file_get_contents($filename, $use_include_path, $context);
113 | $this->responseHeaders = $http_response_header;
114 | return $result;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/Parse/Internal/IncrementOperation.php:
--------------------------------------------------------------------------------
1 |
14 | * @package Parse\Internal
15 | */
16 | class IncrementOperation implements FieldOperation
17 | {
18 | /**
19 | * Amount to increment by.
20 | *
21 | * @var int
22 | */
23 | private $value;
24 |
25 | /**
26 | * Creates an IncrementOperation object.
27 | *
28 | * @param int $value Amount to increment by.
29 | */
30 | public function __construct($value = 1)
31 | {
32 | $this->value = $value;
33 | }
34 |
35 | /**
36 | * Get the value for this operation.
37 | *
38 | * @return int
39 | */
40 | public function getValue()
41 | {
42 | return $this->value;
43 | }
44 |
45 | /**
46 | * Get an associative array encoding for this operation.
47 | *
48 | * @return array
49 | */
50 | public function _encode()
51 | {
52 | return ['__op' => 'Increment', 'amount' => $this->value];
53 | }
54 |
55 | /**
56 | * Apply the current operation and return the result.
57 | *
58 | * @param mixed $oldValue Value prior to this operation.
59 | * @param mixed $object Value for this operation.
60 | * @param string $key Key to set Value on.
61 | *
62 | * @throws ParseException
63 | *
64 | * @return int New value after application.
65 | */
66 | public function _apply($oldValue, $object, $key)
67 | {
68 | if ($oldValue && !is_numeric($oldValue)) {
69 | throw new ParseException('Cannot increment a non-number type.');
70 | }
71 |
72 | return $oldValue + $this->value;
73 | }
74 |
75 | /**
76 | * Merge this operation with a previous operation and return the
77 | * resulting operation.
78 | *
79 | * @param FieldOperation $previous Previous Operation.
80 | *
81 | * @throws ParseException
82 | *
83 | * @return FieldOperation
84 | */
85 | public function _mergeWithPrevious($previous)
86 | {
87 | if (!$previous) {
88 | return $this;
89 | }
90 | if ($previous instanceof DeleteOperation) {
91 | return new SetOperation($this->value);
92 | }
93 | if ($previous instanceof SetOperation) {
94 | return new SetOperation($previous->getValue() + $this->value);
95 | }
96 | if ($previous instanceof self) {
97 | return new self(
98 | $previous->getValue() + $this->value
99 | );
100 | }
101 | throw new ParseException(
102 | 'Operation is invalid after previous operation.'
103 | );
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/Parse/ParseSessionStorage.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Parse
13 | */
14 | class ParseSessionStorage implements ParseStorageInterface
15 | {
16 | /**
17 | * Parse will store its values in a specific key.
18 | *
19 | * @var string
20 | */
21 | private $storageKey = 'parseData';
22 |
23 | /**
24 | * ParseSessionStorage constructor.
25 | * @throws ParseException
26 | */
27 | public function __construct()
28 | {
29 | if (session_status() !== PHP_SESSION_ACTIVE) {
30 | throw new ParseException(
31 | 'PHP session_start() must be called first.'
32 | );
33 | }
34 | if (!isset($_SESSION[$this->storageKey])) {
35 | $_SESSION[$this->storageKey] = [];
36 | }
37 | }
38 |
39 | /**
40 | * Sets a key-value pair in storage.
41 | *
42 | * @param string $key The key to set
43 | * @param mixed $value The value to set
44 | *
45 | * @return void
46 | */
47 | public function set($key, $value)
48 | {
49 | $_SESSION[$this->storageKey][$key] = $value;
50 | }
51 |
52 | /**
53 | * Remove a key from storage.
54 | *
55 | * @param string $key The key to remove.
56 | *
57 | * @return void
58 | */
59 | public function remove($key)
60 | {
61 | unset($_SESSION[$this->storageKey][$key]);
62 | }
63 |
64 | /**
65 | * Gets the value for a key from storage.
66 | *
67 | * @param string $key The key to get the value for
68 | *
69 | * @return mixed
70 | */
71 | public function get($key)
72 | {
73 | if (isset($_SESSION[$this->storageKey][$key])) {
74 | return $_SESSION[$this->storageKey][$key];
75 | }
76 |
77 | return null;
78 | }
79 |
80 | /**
81 | * Clear all the values in storage.
82 | *
83 | * @return void
84 | */
85 | public function clear()
86 | {
87 | $_SESSION[$this->storageKey] = [];
88 | }
89 |
90 | /**
91 | * Save the data, if necessary. Not implemented.
92 | */
93 | public function save()
94 | {
95 | // No action required. PHP handles persistence for $_SESSION.
96 | return;
97 | }
98 |
99 | /**
100 | * Get all keys in storage.
101 | *
102 | * @return array
103 | */
104 | public function getKeys()
105 | {
106 | return array_keys($_SESSION[$this->storageKey]);
107 | }
108 |
109 | /**
110 | * Get all key-value pairs from storage.
111 | *
112 | * @return array
113 | */
114 | public function getAll()
115 | {
116 | return $_SESSION[$this->storageKey];
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/tests/Parse/Helper.php:
--------------------------------------------------------------------------------
1 | each(
87 | function (ParseObject $obj) {
88 | $obj->destroy(true);
89 | },
90 | true
91 | );
92 | }
93 |
94 | public static function setUpWithoutCURLExceptions()
95 | {
96 | ParseClient::initialize(
97 | self::$appId,
98 | self::$restKey,
99 | self::$masterKey,
100 | false,
101 | );
102 | }
103 |
104 | public static function print($text)
105 | {
106 | fwrite(STDOUT, $text . "\n");
107 | }
108 |
109 | public static function printArray($array)
110 | {
111 | print_r($array);
112 | ob_end_flush();
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/tests/Parse/ParseAnalyticsTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($expectedJSON, $json);
27 | ParseAnalytics::track($event, $params ?: []);
28 | }
29 |
30 | public function testTrackEvent()
31 | {
32 | $expected = '{"dimensions":{}}';
33 | $this->assertAnalyticsValidation('testTrackEvent', null, $expected);
34 | }
35 |
36 | public function testFailsOnEventName1()
37 | {
38 | $this->expectException(
39 | 'Exception',
40 | 'A name for the custom event must be provided.'
41 | );
42 | ParseAnalytics::track('');
43 | }
44 |
45 | public function testFailsOnEventName2()
46 | {
47 | $this->expectException(
48 | 'Exception',
49 | 'A name for the custom event must be provided.'
50 | );
51 | ParseAnalytics::track(' ');
52 | }
53 |
54 | public function testFailsOnEventName3()
55 | {
56 | $this->expectException(
57 | 'Exception',
58 | 'A name for the custom event must be provided.'
59 | );
60 | ParseAnalytics::track(" \n");
61 | }
62 |
63 | public function testTrackEventDimensions()
64 | {
65 | $expected = '{"dimensions":{"foo":"bar","bar":"baz"}}';
66 | $params = [
67 | 'foo' => 'bar',
68 | 'bar' => 'baz',
69 | ];
70 | $this->assertAnalyticsValidation('testDimensions', $params, $expected);
71 |
72 | $date = date(DATE_RFC3339);
73 | $expected = '{"dimensions":{"foo":"bar","bar":"baz","someDate":"'.
74 | $date.'"}}';
75 | $params = [
76 | 'foo' => 'bar',
77 | 'bar' => 'baz',
78 | 'someDate' => $date,
79 | ];
80 | $this->assertAnalyticsValidation('testDate', $params, $expected);
81 | }
82 |
83 | public function testBadKeyDimension()
84 | {
85 | $this->expectException(
86 | '\Exception',
87 | 'Dimensions expected string keys and values.'
88 | );
89 | ParseAnalytics::track('event', [1=>'good-value']);
90 | }
91 |
92 | public function testBadValueDimension()
93 | {
94 | $this->expectException(
95 | '\Exception',
96 | 'Dimensions expected string keys and values.'
97 | );
98 | ParseAnalytics::track('event', ['good-key'=>1]);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/tests/Parse/ParseSessionStorageTest.php:
--------------------------------------------------------------------------------
1 | clear();
39 | }
40 |
41 | public static function tearDownAfterClass() : void
42 | {
43 | @session_destroy();
44 | }
45 |
46 | public function testIsUsingParseSession()
47 | {
48 | $this->assertTrue(self::$parseStorage instanceof ParseSessionStorage);
49 | }
50 |
51 | public function testSetAndGet()
52 | {
53 | self::$parseStorage->set('foo', 'bar');
54 | $this->assertEquals('bar', self::$parseStorage->get('foo'));
55 | }
56 |
57 | public function testRemove()
58 | {
59 | self::$parseStorage->set('foo', 'bar');
60 | self::$parseStorage->remove('foo');
61 | $this->assertNull(self::$parseStorage->get('foo'));
62 | }
63 |
64 | public function testClear()
65 | {
66 | self::$parseStorage->set('foo', 'bar');
67 | self::$parseStorage->set('foo2', 'bar');
68 | self::$parseStorage->set('foo3', 'bar');
69 | self::$parseStorage->clear();
70 | $this->assertEmpty(self::$parseStorage->getKeys());
71 | }
72 |
73 | public function testGetAll()
74 | {
75 | self::$parseStorage->set('foo', 'bar');
76 | self::$parseStorage->set('foo2', 'bar');
77 | self::$parseStorage->set('foo3', 'bar');
78 | $result = self::$parseStorage->getAll();
79 | $this->assertEquals('bar', $result['foo']);
80 | $this->assertEquals('bar', $result['foo2']);
81 | $this->assertEquals('bar', $result['foo3']);
82 | $this->assertEquals(3, count($result));
83 | }
84 |
85 | public function testSave()
86 | {
87 | // does nothing
88 | self::$parseStorage->save();
89 | $this->assertTrue(true);
90 | }
91 |
92 | /**
93 | * @group session-recreate-storage
94 | */
95 | public function testRecreatingSessionStorage()
96 | {
97 | unset($_SESSION['parseData']);
98 |
99 | $this->assertFalse(isset($_SESSION['parseData']));
100 |
101 | new ParseSessionStorage();
102 |
103 | $this->assertEmpty($_SESSION['parseData']);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at codeofconduct@parseplatform.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/src/Parse/Internal/AddOperation.php:
--------------------------------------------------------------------------------
1 |
15 | * @package Parse\Internal
16 | */
17 | class AddOperation implements FieldOperation
18 | {
19 | /**
20 | * Array with objects to add.
21 | *
22 | * @var array
23 | */
24 | private $objects;
25 |
26 | /**
27 | * Creates an AddOperation with the provided objects.
28 | *
29 | * @param array $objects Objects to add.
30 | *
31 | * @throws ParseException
32 | */
33 | public function __construct($objects)
34 | {
35 | if (!is_array($objects)) {
36 | throw new ParseException('AddOperation requires an array.');
37 | }
38 | $this->objects = $objects;
39 | }
40 |
41 | /**
42 | * Gets the objects for this operation.
43 | *
44 | * @return mixed
45 | */
46 | public function getValue()
47 | {
48 | return $this->objects;
49 | }
50 |
51 | /**
52 | * Returns associative array representing encoded operation.
53 | *
54 | * @return array
55 | */
56 | public function _encode()
57 | {
58 | return ['__op' => 'Add',
59 | 'objects' => ParseClient::_encode($this->objects, true), ];
60 | }
61 |
62 | /**
63 | * Takes a previous operation and returns a merged operation to replace it.
64 | *
65 | * @param FieldOperation $previous Previous operation.
66 | *
67 | * @throws ParseException
68 | *
69 | * @return FieldOperation Merged operation.
70 | */
71 | public function _mergeWithPrevious($previous)
72 | {
73 | if (!$previous) {
74 | return $this;
75 | }
76 | if ($previous instanceof DeleteOperation) {
77 | return new SetOperation($this->objects);
78 | }
79 | if ($previous instanceof SetOperation) {
80 | $oldList = $previous->getValue();
81 |
82 | return new SetOperation(
83 | array_merge((array) $oldList, (array) $this->objects)
84 | );
85 | }
86 | if ($previous instanceof self) {
87 | $oldList = $previous->getValue();
88 |
89 | return new SetOperation(
90 | array_merge((array) $oldList, (array) $this->objects)
91 | );
92 | }
93 | throw new ParseException(
94 | 'Operation is invalid after previous operation.'
95 | );
96 | }
97 |
98 | /**
99 | * Applies current operation, returns resulting value.
100 | *
101 | * @param mixed $oldValue Value prior to this operation.
102 | * @param mixed $obj Value being applied.
103 | * @param string $key Key this operation affects.
104 | *
105 | * @return array
106 | */
107 | public function _apply($oldValue, $obj, $key)
108 | {
109 | if (!$oldValue) {
110 | return $this->objects;
111 | }
112 |
113 | return array_merge((array) $oldValue, (array) $this->objects);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/tests/Parse/ParseSessionTest.php:
--------------------------------------------------------------------------------
1 | setUsername('username');
39 | $user->setPassword('password');
40 | $user->signUp();
41 | $session = ParseSession::getCurrentSession();
42 | $this->assertEquals($user->getSessionToken(), $session->getSessionToken());
43 | $this->assertTrue($session->isCurrentSessionRevocable());
44 |
45 | ParseUser::logOut();
46 |
47 | $this->assertFalse(ParseSession::isCurrentSessionRevocable());
48 |
49 | ParseUser::logIn('username', 'password');
50 | $session = ParseSession::getCurrentSession();
51 | $this->assertEquals(ParseUser::getCurrentUser()->getSessionToken(), $session->getSessionToken());
52 | $this->assertTrue($session->isCurrentSessionRevocable());
53 |
54 | $sessionToken = $session->getSessionToken();
55 |
56 | ParseUser::logOut();
57 |
58 | $this->expectException('Parse\ParseException', 'Invalid session token');
59 | ParseUser::become($sessionToken);
60 | }
61 |
62 | /**
63 | * @group upgrade-to-revocable-session
64 | */
65 | public function testUpgradeToRevocableSession()
66 | {
67 | $user = new ParseUser();
68 | $user->setUsername('revocable_username');
69 | $user->setPassword('revocable_password');
70 | $user->signUp();
71 |
72 | $session = ParseSession::getCurrentSession();
73 | $this->assertEquals($user->getSessionToken(), $session->getSessionToken());
74 |
75 | // upgrade the current session (changes our session as well)
76 | ParseSession::upgradeToRevocableSession();
77 |
78 | // verify that our session has changed, and our updated current user matches it
79 | $session = ParseSession::getCurrentSession();
80 | $user = ParseUser::getCurrentUser();
81 | $this->assertEquals($user->getSessionToken(), $session->getSessionToken());
82 | $this->assertTrue($session->isCurrentSessionRevocable());
83 | }
84 |
85 | /**
86 | * @group upgrade-to-revocable-session
87 | */
88 | public function testBadUpgradeToRevocableSession()
89 | {
90 | // upgrade the current session (changes our session as well)
91 | $this->expectException('Parse\ParseException', 'No session to upgrade.');
92 | ParseSession::upgradeToRevocableSession();
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/tests/keys/client.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | 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
51 | -----END RSA PRIVATE KEY-----
52 |
--------------------------------------------------------------------------------
/tests/keys/localhost.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | 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
51 | -----END RSA PRIVATE KEY-----
52 |
--------------------------------------------------------------------------------
/tests/keys/parseca.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | 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
51 | -----END RSA PRIVATE KEY-----
52 |
--------------------------------------------------------------------------------
/src/Parse/ParseInstallation.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Parse
13 | */
14 | class ParseInstallation extends ParseObject
15 | {
16 | /**
17 | * Parse Class name
18 | *
19 | * @var string
20 | */
21 | public static $parseClassName = '_Installation';
22 |
23 | /**
24 | * Gets the installation id for this installation
25 | *
26 | * @return string
27 | */
28 | public function getInstallationId()
29 | {
30 | return $this->get('installationId');
31 | }
32 |
33 | /**
34 | * Gets the device token for this installation
35 | *
36 | * @return string
37 | */
38 | public function getDeviceToken()
39 | {
40 | return $this->get('deviceToken');
41 | }
42 |
43 | /**
44 | * Get channels for this installation
45 | *
46 | * @return array
47 | */
48 | public function getChannels()
49 | {
50 | return $this->get('channels');
51 | }
52 |
53 | /**
54 | * Gets the device type of this installation
55 | *
56 | * @return string
57 | */
58 | public function getDeviceType()
59 | {
60 | return $this->get('deviceType');
61 | }
62 |
63 | /**
64 | * Gets the push type of this installation
65 | *
66 | * @return string
67 | */
68 | public function getPushType()
69 | {
70 | return $this->get('pushType');
71 | }
72 |
73 | /**
74 | * Gets the GCM sender id of this installation
75 | *
76 | * @return string
77 | */
78 | public function getGCMSenderId()
79 | {
80 | return $this->get('GCMSenderId');
81 | }
82 |
83 | /**
84 | * Gets the time zone of this installation
85 | *
86 | * @return string
87 | */
88 | public function getTimeZone()
89 | {
90 | return $this->get('timeZone');
91 | }
92 |
93 | /**
94 | * Gets the locale id for this installation
95 | *
96 | * @return string
97 | */
98 | public function getLocaleIdentifier()
99 | {
100 | return $this->get('localeIdentifier');
101 | }
102 |
103 | /**
104 | * Gets the badge number of this installation
105 | *
106 | * @return int
107 | */
108 | public function getBadge()
109 | {
110 | return $this->get('badge');
111 | }
112 |
113 | /**
114 | * Gets the app version of this installation
115 | *
116 | * @return string
117 | */
118 | public function getAppVersion()
119 | {
120 | return $this->get('appVersion');
121 | }
122 |
123 | /**
124 | * Get the app name for this installation
125 | *
126 | * @return string
127 | */
128 | public function getAppName()
129 | {
130 | return $this->get('appName');
131 | }
132 |
133 | /**
134 | * Gets the app identifier for this installation
135 | *
136 | * @return string
137 | */
138 | public function getAppIdentifier()
139 | {
140 | return $this->get('appIdentifier');
141 | }
142 |
143 | /**
144 | * Gets the parse version for this installation
145 | *
146 | * @return string
147 | */
148 | public function getParseVersion()
149 | {
150 | return $this->get('parseVersion');
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/Parse/ParseRole.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Parse
13 | */
14 | class ParseRole extends ParseObject
15 | {
16 | /**
17 | * Parse Class name
18 | *
19 | * @var string
20 | */
21 | public static $parseClassName = '_Role';
22 |
23 | /**
24 | * Create a ParseRole object with a given name and ACL.
25 | *
26 | * @param string $name
27 | * @param ParseACL $acl
28 | *
29 | * @return ParseRole
30 | */
31 | public static function createRole($name, ParseACL $acl)
32 | {
33 | $role = new ParseRole();
34 | $role->setName($name);
35 | $role->setACL($acl);
36 |
37 | return $role;
38 | }
39 |
40 | /**
41 | * Returns the role name.
42 | *
43 | * @return string
44 | */
45 | public function getName()
46 | {
47 | return $this->get('name');
48 | }
49 |
50 | /**
51 | * Sets the role name.
52 | *
53 | * @param string $name The role name
54 | *
55 | * @throws ParseException
56 | */
57 | public function setName($name)
58 | {
59 | if ($this->getObjectId()) {
60 | throw new ParseException("A role's name can only be set before it has been saved.");
61 | }
62 | if (!is_string($name)) {
63 | throw new ParseException("A role's name must be a string.", 139);
64 | }
65 |
66 | return $this->set('name', $name);
67 | }
68 |
69 | /**
70 | * Gets the ParseRelation for the ParseUsers which are direct children of
71 | * this role. These users are granted any privileges that this role
72 | * has been granted.
73 | *
74 | * @return ParseRelation
75 | */
76 | public function getUsers()
77 | {
78 | return $this->getRelation('users');
79 | }
80 |
81 | /**
82 | * Gets the ParseRelation for the ParseRoles which are direct children of
83 | * this role. These roles' users are granted any privileges that this role
84 | * has been granted.
85 | *
86 | * @return ParseRelation
87 | */
88 | public function getRoles()
89 | {
90 | return $this->getRelation('roles');
91 | }
92 |
93 | /**
94 | * Handles pre-saving of this role
95 | * Calls ParseObject::save to finish
96 | *
97 | * @param bool $useMasterKey
98 | * @throws ParseException
99 | */
100 | public function save($useMasterKey = false)
101 | {
102 | if (!$this->getACL()) {
103 | throw new ParseException('Roles must have an ACL.', 123);
104 | }
105 | if (!$this->getName() || !is_string($this->getName())) {
106 | throw new ParseException('Roles must have a name.', 139);
107 | }
108 | if ($this->getObjectId() === null) {
109 | // Not yet saved, verify this name is not taken
110 | // ParseServer does not validate duplicate role names as of parse-server v2.3.2
111 | $query = new ParseQuery('_Role');
112 | $query->equalTo('name', $this->getName());
113 | if ($query->count(true) > 0) {
114 | throw new ParseException("Cannot add duplicate role name of '{$this->getName()}'", 137);
115 | }
116 | }
117 |
118 | return parent::save($useMasterKey);
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/Parse/ParseSession.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Parse
13 | */
14 | class ParseSession extends ParseObject
15 | {
16 | /**
17 | * Parse Class name
18 | *
19 | * @var string
20 | */
21 | public static $parseClassName = '_Session';
22 |
23 | /**
24 | * Session token string
25 | *
26 | * @var null|string
27 | */
28 | private $_sessionToken = null;
29 |
30 | /**
31 | * Returns the session token string.
32 | *
33 | * @return string
34 | */
35 | public function getSessionToken()
36 | {
37 | return $this->_sessionToken;
38 | }
39 |
40 | /**
41 | * Retrieves the Session object for the currently logged in user.
42 | *
43 | * @param bool $useMasterKey If the Master Key should be used to override security.
44 | *
45 | * @return ParseSession
46 | */
47 | public static function getCurrentSession($useMasterKey = false)
48 | {
49 | $token = ParseUser::getCurrentUser()->getSessionToken();
50 | $response = ParseClient::_request(
51 | 'GET',
52 | 'sessions/me',
53 | $token,
54 | null,
55 | $useMasterKey
56 | );
57 | $session = new self();
58 | $session->_mergeAfterFetch($response);
59 | $session->handleSaveResult();
60 |
61 | return $session;
62 | }
63 |
64 | /**
65 | * Determines whether the current session token is revocable.
66 | * This method is useful for migrating an existing app to use
67 | * revocable sessions.
68 | *
69 | * @return bool
70 | */
71 | public static function isCurrentSessionRevocable()
72 | {
73 | $user = ParseUser::getCurrentUser();
74 | return $user ? self::_isRevocable($user->getSessionToken()) : false;
75 | }
76 |
77 | /**
78 | * Determines whether a session token is revocable.
79 | *
80 | * @param string $token The session token to check
81 | *
82 | * @return bool
83 | */
84 | public static function _isRevocable($token)
85 | {
86 | return strpos($token, 'r:') === 0;
87 | }
88 |
89 | /**
90 | * Upgrades the current session to a revocable one
91 | *
92 | * @throws ParseException
93 | */
94 | public static function upgradeToRevocableSession()
95 | {
96 | $user = ParseUser::getCurrentUser();
97 | if ($user) {
98 | $token = $user->getSessionToken();
99 | $response = ParseClient::_request(
100 | 'POST',
101 | 'upgradeToRevocableSession',
102 | $token,
103 | null,
104 | false
105 | );
106 | $session = new self();
107 | $session->_mergeAfterFetch($response);
108 | $session->handleSaveResult();
109 | ParseUser::become($session->getSessionToken());
110 | } else {
111 | throw new ParseException('No session to upgrade.');
112 | }
113 | }
114 |
115 | /**
116 | * After a save, perform Session object specific logic.
117 | */
118 | private function handleSaveResult()
119 | {
120 | if (isset($this->serverData['sessionToken'])) {
121 | $this->_sessionToken = $this->serverData['sessionToken'];
122 | unset($this->serverData['sessionToken']);
123 | }
124 | $this->rebuildEstimatedData();
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Parse/HttpClients/ParseCurl.php:
--------------------------------------------------------------------------------
1 |
14 | * @package Parse\HttpClients
15 | */
16 | class ParseCurl
17 | {
18 | /**
19 | * Curl handle
20 | *
21 | * @var resource
22 | */
23 | private $curl;
24 |
25 | /**
26 | * Sets up a new curl instance internally if needed
27 | */
28 | public function init()
29 | {
30 | if ($this->curl === null) {
31 | $this->curl = curl_init();
32 | }
33 | }
34 |
35 | /**
36 | * Executes this curl request
37 | *
38 | * @return mixed
39 | * @throws ParseException
40 | */
41 | public function exec()
42 | {
43 | if (!isset($this->curl)) {
44 | throw new ParseException('You must call ParseCurl::init first');
45 | }
46 |
47 | return curl_exec($this->curl);
48 | }
49 |
50 | /**
51 | * Sets a curl option
52 | *
53 | * @param int $option Option to set
54 | * @param mixed $value Value to set for this option
55 | * @throws ParseException
56 | */
57 | public function setOption($option, $value)
58 | {
59 | if (!isset($this->curl)) {
60 | throw new ParseException('You must call ParseCurl::init first');
61 | }
62 |
63 | curl_setopt($this->curl, $option, $value);
64 | }
65 |
66 | /**
67 | * Sets multiple curl options
68 | *
69 | * @param array $options Array of options to set
70 | * @throws ParseException
71 | */
72 | public function setOptionsArray($options)
73 | {
74 | if (!isset($this->curl)) {
75 | throw new ParseException('You must call ParseCurl::init first');
76 | }
77 |
78 | curl_setopt_array($this->curl, $options);
79 | }
80 |
81 | /**
82 | * Gets info for this curl handle
83 | *
84 | * @param int $info Constant for info to get
85 | * @return mixed
86 | * @throws ParseException
87 | */
88 | public function getInfo($info)
89 | {
90 | if (!isset($this->curl)) {
91 | throw new ParseException('You must call ParseCurl::init first');
92 | }
93 |
94 | return curl_getinfo($this->curl, $info);
95 | }
96 |
97 | /**
98 | * Gets the curl error message
99 | *
100 | * @return string
101 | * @throws ParseException
102 | */
103 | public function getError()
104 | {
105 | if (!isset($this->curl)) {
106 | throw new ParseException('You must call ParseCurl::init first');
107 | }
108 |
109 | return curl_error($this->curl);
110 | }
111 |
112 | /**
113 | * Gets the curl error code
114 | *
115 | * @return int
116 | * @throws ParseException
117 | */
118 | public function getErrorCode()
119 | {
120 | if (!isset($this->curl)) {
121 | throw new ParseException('You must call ParseCurl::init first');
122 | }
123 |
124 | return curl_errno($this->curl);
125 | }
126 |
127 | /**
128 | * Closed our curl handle and disposes of it
129 | */
130 | public function close()
131 | {
132 | if (!isset($this->curl)) {
133 | throw new ParseException('You must call ParseCurl::init first');
134 | }
135 |
136 | // close our handle
137 | curl_close($this->curl);
138 |
139 | // unset our curl handle
140 | $this->curl = null;
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/tests/Parse/ParseAudienceTest.php:
--------------------------------------------------------------------------------
1 | set('installationId', 'id1');
25 | $androidInstallation->set('deviceToken', '12345');
26 | $androidInstallation->set('deviceType', 'android');
27 | $androidInstallation->save(true);
28 |
29 | $iOSInstallation = new ParseInstallation();
30 | $iOSInstallation->set('installationId', 'id2');
31 | $iOSInstallation->set('deviceToken', '54321');
32 | $iOSInstallation->set('deviceType', 'ios');
33 | $iOSInstallation->save();
34 |
35 | ParseObject::saveAll([
36 | $androidInstallation,
37 | $iOSInstallation
38 | ]);
39 | }
40 |
41 | /**
42 | * @group audience-tests
43 | */
44 | public function testPushAudiences()
45 | {
46 | $this->createInstallations();
47 |
48 | $androidQuery = ParseInstallation::query()
49 | ->equalTo('deviceType', 'android');
50 |
51 | $audience = ParseAudience::createAudience('MyAudience', $androidQuery);
52 | $audience->save();
53 |
54 | // count no master should be 0
55 | $query = new ParseQuery('_Audience');
56 | $this->assertEquals(0, $query->count(), 'No master was not 0');
57 |
58 | $query = new ParseQuery('_Audience');
59 | $audience = $query->first(true);
60 | $this->assertNotNull($audience);
61 |
62 | $this->assertEquals('MyAudience', $audience->getName());
63 | $this->assertEquals($androidQuery, $audience->getQuery());
64 | $this->assertNull($audience->getLastUsed());
65 | $this->assertEquals(0, $audience->getTimesUsed());
66 | }
67 |
68 | /**
69 | * @group audience-tests
70 | */
71 | public function testSaveWithoutMaster()
72 | {
73 | $query = ParseAudience::query();
74 | $this->assertEquals(0, $query->count(true), 'Did not start at 0');
75 |
76 | $audience = ParseAudience::createAudience(
77 | 'MyAudience',
78 | ParseInstallation::query()
79 | ->equalTo('deviceType', 'android')
80 | );
81 | $audience->save();
82 |
83 | $query = ParseAudience::query();
84 | $this->assertEquals(1, $query->count(true), 'Did not end at 1');
85 | }
86 |
87 | /**
88 | * @group audience-tests
89 | */
90 | public function testPushWithAudience()
91 | {
92 | $this->createInstallations();
93 |
94 | $audience = ParseAudience::createAudience(
95 | 'MyAudience',
96 | ParseInstallation::query()
97 | ->equalTo('deviceType', 'android')
98 | );
99 | $audience->save(true);
100 |
101 | ParsePush::send([
102 | 'data' => [
103 | 'alert' => 'sample message'
104 | ],
105 | 'where' => $audience->getQuery(),
106 | 'audience_id' => $audience->getObjectId()
107 | ], true);
108 |
109 | $audience->fetch(true);
110 |
111 | $this->assertEquals(1, $audience->getTimesUsed());
112 | $this->assertNotNull($audience->getLastUsed());
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | ------------
3 |
4 | All contributions are under the repository [licence](LICENSE).
5 |
6 | When committing, keep all lines to less than 80 characters, and try to
7 | follow the existing style. Before creating a pull request, squash your commits
8 | into a single commit. Please provide ample explanation in the commit message.
9 |
10 | Installation
11 | ------------
12 |
13 | Testing the Parse PHP SDK requires having a working Parse Server instance to run against.
14 | Additionally you'll need to add some cloud code to it.
15 |
16 | To get started:
17 |
18 | * [Get Composer], the PHP package manager.
19 | * Run `composer install` to download dependencies.
20 |
21 | From here you have to setup an instance of parse server.
22 | For your convenience we have included a testable version of parse server as a dev dependency.
23 |
24 | To setup the Test Parse Server:
25 | * [Get npm], you'll need this to install the server.
26 | * Run `npm install` from the project root to download the server and it's dependencies.
27 | * When you're ready to run tests use `npm start` from the project root to boot up the test server.
28 |
29 | The test server is setup with the appropriate configuration to run the php sdk test suite. Additionally it handles setting up mongodb for the server. If you have a mongodb instance already running, you can use `npm run server-only`.
30 |
31 | If you have specific needs and would like to alter your test server you can fork and modify the aforementioned test project.
32 | Alternately you can configure a compatible test server as follows:
33 |
34 | * [Setup a local Parse Server instance]
35 | * Add cloud-code.js in tests to your Parse Server configuration as a cloud code file
36 | * Ensure your App ID, REST API Key, and Master Key match those contained in tests/Parse/Helper.php
37 | * Add a mock push configuration, for example:
38 | ```json
39 | {
40 | "android":
41 | {
42 | "senderId": "blank-sender-id",
43 | "apiKey": "not-a-real-api-key"
44 | }
45 | }
46 | ```
47 | * Add a mock email adapter configuration, for example:
48 | ```json
49 | {
50 | "module": "parse-server-simple-mailgun-adapter",
51 | "options": {
52 | "apiKey": "not-a-real-api-key",
53 | "domain": "example.com",
54 | "fromAddress": "example@example.com"
55 | }
56 | }
57 | ```
58 | * Ensure the public url is correct. For a locally hosted instance this is probably ```http://localhost:1337/parse```
59 |
60 |
61 | You should now be able to execute the tests, from project root folder:
62 |
63 | ./vendor/bin/phpunit
64 |
65 | You may also run tests directly using phpunit as follows:
66 |
67 | npm test
68 |
69 | For debugging you can use the `print` function found in tests/Parse/Helper.php
70 |
71 | Helper::print()
72 |
73 | Make sure your code is linted with phpcs ([PSR-2 Coding Style]):
74 |
75 | npm run lint
76 |
77 | You can automatically fix lint errors with phpcbf:
78 |
79 | npm run lint:fix
80 |
81 | Finally verify that your new changes are acceptable to phpdoc:
82 |
83 | npm run document-check
84 |
85 | The test suite is setup for code coverage if you have [XDebug] installed and setup.
86 | Coverage is outputted as text and as html in the phpunit-test-results/ directory within the project root.
87 |
88 | If you do not have XDebug tests will still run, just without coverage.
89 |
90 | Please make sure that any new functionality (or issues) you are working on are covered by tests when possible.
91 | If you have XDebug setup and can view code coverage please ensure that you do your best to completely cover any new code you are adding.
92 |
93 | ## Code of Conduct
94 |
95 | This project adheres to the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to honor this code.
96 |
97 | [Get Composer]: https://getcomposer.org/download/
98 | [Get npm]: https://www.npmjs.com/get-npm
99 | [XDebug]: https://xdebug.org/
100 | [Setup a local Parse Server instance]: https://github.com/parse-community/parse-server#user-content-locally
101 | [PSR-2 Coding Style]: http://www.php-fig.org/psr/psr-2/
102 |
103 |
--------------------------------------------------------------------------------
/release.config.cjs:
--------------------------------------------------------------------------------
1 | /**
2 | * Semantic Release Config
3 | */
4 |
5 | const fs = require('fs').promises;
6 | const path = require('path');
7 |
8 | // Get env vars
9 | const ref = process.env.GITHUB_REF;
10 | const serverUrl = process.env.GITHUB_SERVER_URL;
11 | const repository = process.env.GITHUB_REPOSITORY;
12 | const repositoryUrl = serverUrl + '/' + repository;
13 |
14 | // Declare params
15 | const resourcePath = './.releaserc/';
16 | const templates = {
17 | main: { file: 'template.hbs', text: undefined },
18 | header: { file: 'header.hbs', text: undefined },
19 | commit: { file: 'commit.hbs', text: undefined },
20 | footer: { file: 'footer.hbs', text: undefined },
21 | };
22 |
23 | // Declare semantic config
24 | async function config() {
25 |
26 | // Get branch
27 | const branch = ref.split('/').pop();
28 | console.log(`Running on branch: ${branch}`);
29 |
30 | // Set changelog file
31 | //const changelogFile = `./changelogs/CHANGELOG_${branch}.md`;
32 | const changelogFile = `./CHANGELOG.md`;
33 | console.log(`Changelog file output to: ${changelogFile}`);
34 |
35 | // Load template file contents
36 | await loadTemplates();
37 |
38 | const config = {
39 | branches: [
40 | 'master',
41 | // { name: 'alpha', prerelease: true },
42 | // { name: 'beta', prerelease: true },
43 | // 'next-major',
44 | // Long-Term-Support branches
45 | // { name: 'release-1', range: '1.x.x', channel: '1.x' },
46 | // { name: 'release-2', range: '2.x.x', channel: '2.x' },
47 | // { name: 'release-3', range: '3.x.x', channel: '3.x' },
48 | // { name: 'release-4', range: '4.x.x', channel: '4.x' },
49 | ],
50 | dryRun: false,
51 | debug: true,
52 | ci: true,
53 | tagFormat: '${version}',
54 | plugins: [
55 | ['@semantic-release/commit-analyzer', {
56 | preset: 'angular',
57 | releaseRules: [
58 | { type: 'docs', scope: 'README', release: 'patch' },
59 | { scope: 'no-release', release: false },
60 | ],
61 | parserOpts: {
62 | noteKeywords: [ 'BREAKING CHANGE' ],
63 | },
64 | }],
65 | ['@semantic-release/release-notes-generator', {
66 | preset: 'angular',
67 | parserOpts: {
68 | noteKeywords: ['BREAKING CHANGE']
69 | },
70 | writerOpts: {
71 | commitsSort: ['subject', 'scope'],
72 | mainTemplate: templates.main.text,
73 | headerPartial: templates.header.text,
74 | commitPartial: templates.commit.text,
75 | footerPartial: templates.footer.text,
76 | },
77 | }],
78 | ['@semantic-release/changelog', {
79 | 'changelogFile': changelogFile,
80 | }],
81 | ["@semantic-release/exec", {
82 | // Update Parse SDK version
83 | "prepareCmd": `sed -i'' -r "s/const VERSION_STRING = '.*'/const VERSION_STRING = '\${nextRelease.version}'/" ./src/Parse/ParseClient.php`
84 | }],
85 | ['@semantic-release/npm', {
86 | 'npmPublish': false,
87 | }],
88 | ['@semantic-release/git', {
89 | assets: [changelogFile, 'package.json', 'package-lock.json', ['src/Parse/ParseClient.php'] ],
90 | }],
91 | ['@semantic-release/github', {
92 | successComment: getReleaseComment(),
93 | labels: ['type:ci'],
94 | releasedLabels: ['state:released<%= nextRelease.channel ? `-\${nextRelease.channel}` : "" %>']
95 | }],
96 | ],
97 | };
98 |
99 | return config;
100 | }
101 |
102 | async function loadTemplates() {
103 | for (const template of Object.keys(templates)) {
104 | const text = await readFile(path.resolve(__dirname, resourcePath, templates[template].file));
105 | templates[template].text = text;
106 | }
107 | }
108 |
109 | async function readFile(filePath) {
110 | return await fs.readFile(filePath, 'utf-8');
111 | }
112 |
113 | function getReleaseComment() {
114 | const url = repositoryUrl + '/releases/tag/${nextRelease.gitTag}';
115 | let comment = '🎉 This change has been released in version [${nextRelease.version}](' + url + ')';
116 | return comment;
117 | }
118 |
119 | module.exports = config();
120 |
--------------------------------------------------------------------------------
/src/Parse/Internal/RemoveOperation.php:
--------------------------------------------------------------------------------
1 |
17 | * @package Parse\Internal
18 | */
19 | class RemoveOperation implements FieldOperation
20 | {
21 | /**
22 | * Array with objects to remove.
23 | *
24 | * @var array
25 | */
26 | private $objects;
27 |
28 | /**
29 | * Creates an RemoveOperation with the provided objects.
30 | *
31 | * @param array $objects Objects to remove.
32 | *
33 | * @throws ParseException
34 | */
35 | public function __construct($objects)
36 | {
37 | if (!is_array($objects)) {
38 | throw new ParseException('RemoveOperation requires an array.');
39 | }
40 | $this->objects = $objects;
41 | }
42 |
43 | /**
44 | * Gets the objects for this operation.
45 | *
46 | * @return mixed
47 | */
48 | public function getValue()
49 | {
50 | return $this->objects;
51 | }
52 |
53 | /**
54 | * Returns associative array representing encoded operation.
55 | *
56 | * @return array
57 | */
58 | public function _encode()
59 | {
60 | return ['__op' => 'Remove',
61 | 'objects' => ParseClient::_encode($this->objects, true), ];
62 | }
63 |
64 | /**
65 | * Takes a previous operation and returns a merged operation to replace it.
66 | *
67 | * @param FieldOperation $previous Previous operation.
68 | *
69 | * @throws ParseException
70 | *
71 | * @return FieldOperation Merged operation.
72 | */
73 | public function _mergeWithPrevious($previous)
74 | {
75 | if (!$previous) {
76 | return $this;
77 | }
78 | if ($previous instanceof DeleteOperation) {
79 | return $previous;
80 | }
81 | if ($previous instanceof SetOperation) {
82 | return new SetOperation(
83 | $this->_apply($previous->getValue(), $this->objects, null)
84 | );
85 | }
86 | if ($previous instanceof self) {
87 | $oldList = $previous->getValue();
88 |
89 | return new self(
90 | array_merge((array) $oldList, (array) $this->objects)
91 | );
92 | }
93 | throw new ParseException(
94 | 'Operation is invalid after previous operation.'
95 | );
96 | }
97 |
98 | /**
99 | * Applies current operation, returns resulting value.
100 | *
101 | * @param mixed $oldValue Value prior to this operation.
102 | * @param mixed $obj Value being applied.
103 | * @param string $key Key this operation affects.
104 | *
105 | * @return array
106 | */
107 | public function _apply($oldValue, $obj, $key)
108 | {
109 | if (empty($oldValue)) {
110 | return [];
111 | }
112 |
113 | if (!is_array($oldValue)) {
114 | $oldValue = [$oldValue];
115 | }
116 |
117 | $newValue = [];
118 | foreach ($oldValue as $oldObject) {
119 | foreach ($this->objects as $newObject) {
120 | if ($oldObject instanceof ParseObject) {
121 | if ($newObject instanceof ParseObject
122 | && !$oldObject->isDirty()
123 | && $oldObject->getObjectId() == $newObject->getObjectId()
124 | ) {
125 | // found the object, won't add it.
126 | } else {
127 | $newValue[] = $oldObject;
128 | }
129 | } else {
130 | if ($oldObject !== $newObject) {
131 | $newValue[] = $oldObject;
132 | }
133 | }
134 | }
135 | }
136 |
137 | return $newValue;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/Parse/ParseRelation.php:
--------------------------------------------------------------------------------
1 |
16 | * @package Parse
17 | */
18 | class ParseRelation implements Encodable
19 | {
20 | /**
21 | * The parent of this relation.
22 | *
23 | * @var ParseObject
24 | */
25 | private $parent;
26 |
27 | /**
28 | * The key of the relation in the parent object.
29 | *
30 | * @var string
31 | */
32 | private $key;
33 |
34 | /**
35 | * The className of the target objects.
36 | *
37 | * @var string
38 | */
39 | private $targetClassName;
40 |
41 | /**
42 | * Creates a new Relation for the given parent object, key and class name of target objects.
43 | *
44 | * @param ParseObject $parent The parent of this relation.
45 | * @param string $key The key of the relation in the parent object.
46 | * @param string $targetClassName The className of the target objects.
47 | */
48 | public function __construct($parent, $key, $targetClassName = null)
49 | {
50 | $this->parent = $parent;
51 | $this->key = $key;
52 | $this->targetClassName = $targetClassName;
53 | }
54 |
55 | /**
56 | * Adds a ParseObject or an array of ParseObjects to the relation.
57 | *
58 | * @param mixed $objects The item or items to add.
59 | */
60 | public function add($objects)
61 | {
62 | if (!is_array($objects)) {
63 | $objects = [$objects];
64 | }
65 | $operation = new ParseRelationOperation($objects, null);
66 | $this->targetClassName = $operation->_getTargetClass();
67 | $this->parent->_performOperation($this->key, $operation);
68 | }
69 |
70 | /**
71 | * Removes a ParseObject or an array of ParseObjects from this relation.
72 | *
73 | * @param mixed $objects The item or items to remove.
74 | */
75 | public function remove($objects)
76 | {
77 | if (!is_array($objects)) {
78 | $objects = [$objects];
79 | }
80 | $operation = new ParseRelationOperation(null, $objects);
81 | $this->targetClassName = $operation->_getTargetClass();
82 | $this->parent->_performOperation($this->key, $operation);
83 | }
84 |
85 | /**
86 | * Returns the target classname for the relation.
87 | *
88 | * @return string
89 | */
90 | public function getTargetClass()
91 | {
92 | return $this->targetClassName;
93 | }
94 |
95 | /**
96 | * Set the target classname for the relation.
97 | *
98 | * @param string $className
99 | */
100 | public function setTargetClass($className)
101 | {
102 | $this->targetClassName = $className;
103 | }
104 |
105 | /**
106 | * Set the parent object for the relation.
107 | *
108 | * @param ParseObject $parent
109 | */
110 | public function setParent($parent)
111 | {
112 | $this->parent = $parent;
113 | }
114 |
115 | /**
116 | * Gets a query that can be used to query the objects in this relation.
117 | *
118 | * @return ParseQuery That restricts the results to objects in this relations.
119 | */
120 | public function getQuery()
121 | {
122 | $query = new ParseQuery($this->targetClassName);
123 | $query->relatedTo('object', $this->parent->_toPointer());
124 | $query->relatedTo('key', $this->key);
125 |
126 | return $query;
127 | }
128 |
129 | /**
130 | * Return an encoded array of this relation.
131 | *
132 | * @return array
133 | */
134 | public function _encode()
135 | {
136 | return [
137 | '__type' => 'Relation',
138 | 'className' => $this->targetClassName
139 | ];
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/Parse/ParseServerInfo.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Parse
13 | */
14 | class ParseServerInfo
15 | {
16 | /**
17 | * Reported server features and configs
18 | *
19 | * @var array
20 | */
21 | private static $serverFeatures;
22 |
23 | /**
24 | * Reported server version
25 | *
26 | * @var string
27 | */
28 | private static $serverVersion;
29 |
30 | /**
31 | * Requests, sets and returns server features and version
32 | *
33 | * @return array
34 | * @throws ParseException
35 | */
36 | private static function getServerInfo()
37 | {
38 | if (!isset(self::$serverFeatures) || !isset(self::$serverVersion)) {
39 | $info = ParseClient::_request(
40 | 'GET',
41 | 'serverInfo/',
42 | null,
43 | null,
44 | true
45 | );
46 |
47 | // validate we have features & version
48 |
49 | if (!isset($info['features'])) {
50 | throw new ParseException('Missing features in server info.');
51 | }
52 |
53 | if (!isset($info['parseServerVersion'])) {
54 | throw new ParseException('Missing version in server info.');
55 | }
56 |
57 | self::$serverFeatures = $info['features'];
58 | self::_setServerVersion($info['parseServerVersion']);
59 | }
60 |
61 | return [
62 | 'features' => self::$serverFeatures,
63 | 'version' => self::$serverVersion
64 | ];
65 | }
66 |
67 | /**
68 | * Sets the current server version.
69 | * Allows setting the server version to avoid making an additional request
70 | * if the version is obtained elsewhere.
71 | *
72 | * @param string $version Version to set
73 | */
74 | public static function _setServerVersion($version)
75 | {
76 | self::$serverVersion = $version;
77 | }
78 |
79 | /**
80 | * Get a specific feature set from the server
81 | *
82 | * @param string $key Feature set to get
83 | * @return mixed
84 | */
85 | public static function get($key)
86 | {
87 | return self::getServerInfo()['features'][$key];
88 | }
89 |
90 | /**
91 | * Gets features for the current server
92 | *
93 | * @return array
94 | */
95 | public static function getFeatures()
96 | {
97 | return self::getServerInfo()['features'];
98 | }
99 |
100 | /**
101 | * Gets the reported version of the current server
102 | *
103 | * @return string
104 | */
105 | public static function getVersion()
106 | {
107 | if (!isset(self::$serverVersion)) {
108 | return self::getServerInfo()['version'];
109 | } else {
110 | return self::$serverVersion;
111 | }
112 | }
113 |
114 | /**
115 | * Gets features available for globalConfig
116 | *
117 | * @return array
118 | */
119 | public static function getGlobalConfigFeatures()
120 | {
121 | return self::get('globalConfig');
122 | }
123 |
124 | /**
125 | * Gets features available for hooks
126 | *
127 | * @return array
128 | */
129 | public static function getHooksFeatures()
130 | {
131 | return self::get('hooks');
132 | }
133 |
134 | /**
135 | * Gets features available for cloudCode
136 | *
137 | * @return array
138 | */
139 | public static function getCloudCodeFeatures()
140 | {
141 | return self::get('cloudCode');
142 | }
143 |
144 | /**
145 | * Gets features available for logs
146 | *
147 | * @return array
148 | */
149 | public static function getLogsFeatures()
150 | {
151 | return self::get('logs');
152 | }
153 |
154 | /**
155 | * Gets features available for push
156 | *
157 | * @return array
158 | */
159 | public static function getPushFeatures()
160 | {
161 | return self::get('push');
162 | }
163 |
164 | /**
165 | * Gets features available for schemas
166 | *
167 | * @return array
168 | */
169 | public static function getSchemasFeatures()
170 | {
171 | return self::get('schemas');
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/src/Parse/ParsePush.php:
--------------------------------------------------------------------------------
1 |
14 | * @package Parse
15 | */
16 | class ParsePush
17 | {
18 | /**
19 | * Sends a push notification.
20 | *
21 | * @param array $data The data of the push notification. Valid fields
22 | * are:
23 | * channels - An Array of channels to push to.
24 | * push_time - A Date object for when to send the push.
25 | * expiration_time - A Date object for when to expire
26 | * the push.
27 | * expiration_interval - The seconds from now to expire the push.
28 | * where - A ParseQuery over ParseInstallation that is used to match
29 | * a set of installations to push to.
30 | * data - The data to send as part of the push
31 | * @param bool $useMasterKey Whether to use the Master Key for the request
32 | *
33 | * @throws \Exception, ParseException
34 | *
35 | * @return mixed
36 | */
37 | public static function send($data, $useMasterKey = false)
38 | {
39 | if (isset($data['expiration_time'])
40 | && isset($data['expiration_interval'])
41 | ) {
42 | throw new Exception(
43 | 'Both expiration_time and expiration_interval can\'t be set.',
44 | 138
45 | );
46 | }
47 | if (isset($data['where'])) {
48 | if ($data['where'] instanceof ParseQuery) {
49 | $where_options = $data['where']->_getOptions();
50 |
51 | if (!isset($where_options['where'])) {
52 | $data['where'] = '{}';
53 | } else {
54 | $data['where'] = $data['where']->_getOptions()['where'];
55 | }
56 | } else {
57 | throw new Exception(
58 | 'Where parameter for Parse Push must be of type ParseQuery',
59 | 111
60 | );
61 | }
62 | }
63 | if (isset($data['push_time'])) {
64 | //Local push date format is different from iso format generally used in Parse
65 | //Schedule does not work if date format not correct
66 | $data['push_time'] = ParseClient::getPushDateFormat($data['push_time'], isset($data['local_time']));
67 | }
68 | if (isset($data['expiration_time'])) {
69 | $data['expiration_time'] = ParseClient::_encode(
70 | $data['expiration_time'],
71 | false
72 | )['iso'];
73 | }
74 |
75 | return ParseClient::_request(
76 | 'POST',
77 | 'push',
78 | null,
79 | json_encode(ParseClient::_encode($data, true)),
80 | $useMasterKey,
81 | 'application/json',
82 | true
83 | );
84 | }
85 |
86 | /**
87 | * Returns whether or not the given response has a push status
88 | * Checks to see if X-Push-Status-Id is present in $response
89 | *
90 | * @param array $response Response from ParsePush::send
91 | * @return bool
92 | */
93 | public static function hasStatus($response)
94 | {
95 | return(
96 | isset($response['_headers']) &&
97 | isset($response['_headers']['X-Parse-Push-Status-Id'])
98 | );
99 | }
100 |
101 | /**
102 | * Returns the PushStatus for a response from ParsePush::send
103 | *
104 | * @param array $response Response from ParsePush::send
105 | * @return null|ParsePushStatus
106 | */
107 | public static function getStatus($response)
108 | {
109 | if (!isset($response['_headers'])) {
110 | // missing headers
111 | return null;
112 | }
113 |
114 | $headers = $response['_headers'];
115 |
116 | if (!isset($headers['X-Parse-Push-Status-Id'])) {
117 | // missing push status id
118 | return null;
119 | }
120 |
121 | // get our push status id
122 | $pushStatusId = $response['_headers']['X-Parse-Push-Status-Id'];
123 |
124 | // return our push status if it exists
125 | return ParsePushStatus::getFromId($pushStatusId);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Parse/Internal/AddUniqueOperation.php:
--------------------------------------------------------------------------------
1 |
16 | * @package Parse\Internal
17 | */
18 | class AddUniqueOperation implements FieldOperation
19 | {
20 | /**
21 | * Array containing objects to add.
22 | *
23 | * @var array
24 | */
25 | private $objects;
26 |
27 | /**
28 | * Creates an operation for adding unique values to an array key.
29 | *
30 | * @param array $objects Objects to add.
31 | *
32 | * @throws ParseException
33 | */
34 | public function __construct($objects)
35 | {
36 | if (!is_array($objects)) {
37 | throw new ParseException('AddUniqueOperation requires an array.');
38 | }
39 | $this->objects = $objects;
40 | }
41 |
42 | /**
43 | * Returns the values for this operation.
44 | *
45 | * @return mixed
46 | */
47 | public function getValue()
48 | {
49 | return $this->objects;
50 | }
51 |
52 | /**
53 | * Returns an associative array encoding of this operation.
54 | *
55 | * @return array
56 | */
57 | public function _encode()
58 | {
59 | return ['__op' => 'AddUnique',
60 | 'objects' => ParseClient::_encode($this->objects, true), ];
61 | }
62 |
63 | /**
64 | * Merge this operation with the previous operation and return the result.
65 | *
66 | * @param FieldOperation $previous Previous Operation.
67 | *
68 | * @throws ParseException
69 | *
70 | * @return FieldOperation Merged Operation.
71 | */
72 | public function _mergeWithPrevious($previous)
73 | {
74 | if (!$previous) {
75 | return $this;
76 | }
77 | if ($previous instanceof DeleteOperation) {
78 | return new SetOperation($this->objects);
79 | }
80 | if ($previous instanceof SetOperation) {
81 | $oldValue = $previous->getValue();
82 | $result = $this->_apply($oldValue, null, null);
83 |
84 | return new SetOperation($result);
85 | }
86 | if ($previous instanceof self) {
87 | $oldList = $previous->getValue();
88 | $result = $this->_apply($oldList, null, null);
89 |
90 | return new self($result);
91 | }
92 | throw new ParseException(
93 | 'Operation is invalid after previous operation.'
94 | );
95 | }
96 |
97 | /**
98 | * Apply the current operation and return the result.
99 | *
100 | * @param mixed $oldValue Value prior to this operation.
101 | * @param array $obj Value being applied.
102 | * @param string $key Key this operation affects.
103 | *
104 | * @return array
105 | */
106 | public function _apply($oldValue, $obj, $key)
107 | {
108 | if (!$oldValue) {
109 | return $this->objects;
110 | }
111 | if (!is_array($oldValue)) {
112 | $oldValue = (array) $oldValue;
113 | }
114 | foreach ($this->objects as $object) {
115 | if ($object instanceof ParseObject && $object->getObjectId()) {
116 | if (!$this->isParseObjectInArray($object, $oldValue)) {
117 | $oldValue[] = $object;
118 | }
119 | } elseif (is_object($object)) {
120 | if (!in_array($object, $oldValue, true)) {
121 | $oldValue[] = $object;
122 | }
123 | } else {
124 | if (!in_array($object, $oldValue, true)) {
125 | $oldValue[] = $object;
126 | }
127 | }
128 | }
129 |
130 | return $oldValue;
131 | }
132 |
133 | /**
134 | * Checks if a parse object is contained in a given array of values
135 | *
136 | * @param ParseObject $parseObject ParseObject to check for existence of
137 | * @param array $oldValue Array to check if ParseObject is present in
138 | * @return bool
139 | */
140 | private function isParseObjectInArray($parseObject, $oldValue)
141 | {
142 | foreach ($oldValue as $object) {
143 | if ($object instanceof ParseObject && $object->getObjectId() != null) {
144 | if ($object->getObjectId() == $parseObject->getObjectId()) {
145 | return true;
146 | }
147 | }
148 | }
149 |
150 | return false;
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/tests/Parse/AddUniqueOperationTest.php:
--------------------------------------------------------------------------------
1 | 'val1',
35 | 'key2' => 'val2'
36 | ];
37 | $addUnique = new AddUniqueOperation($objects);
38 |
39 | $this->assertEquals($objects, $addUnique->getValue());
40 | }
41 |
42 | /**
43 | * @group add-unique-op
44 | */
45 | public function testBadObjects()
46 | {
47 | $this->expectException(
48 | '\Parse\ParseException',
49 | 'AddUniqueOperation requires an array.'
50 | );
51 | new AddUniqueOperation('not-an-array');
52 | }
53 |
54 | /**
55 | * @group add-unique-op
56 | */
57 | public function testEncode()
58 | {
59 | $objects = [
60 | 'key1' => 'val1',
61 | 'key2' => 'val2'
62 | ];
63 | $addUnique = new AddUniqueOperation($objects);
64 |
65 | $encoded = $addUnique->_encode();
66 |
67 | $this->assertEquals([
68 | '__op' => 'AddUnique',
69 | 'objects' => ParseClient::_encode($objects, true)
70 | ], $encoded);
71 | }
72 |
73 | /**
74 | * @group add-unique-op
75 | */
76 | public function testMergePrevious()
77 | {
78 | $addOp = new AddUniqueOperation([
79 | 'key1' => 'value1'
80 | ]);
81 |
82 | $this->assertEquals($addOp, $addOp->_mergeWithPrevious(null));
83 |
84 | // check delete op
85 | $merged = $addOp->_mergeWithPrevious(new DeleteOperation());
86 | $this->assertTrue($merged instanceof SetOperation);
87 |
88 | // check set op
89 | $merged = $addOp->_mergeWithPrevious(new SetOperation('newvalue'));
90 | $this->assertTrue($merged instanceof SetOperation);
91 | $this->assertEquals([
92 | 'newvalue',
93 | 'value1'
94 | ], $merged->getValue(), 'Value was not as expected');
95 |
96 | // check self
97 | $merged = $addOp->_mergeWithPrevious(new AddUniqueOperation(['key2' => 'value2']));
98 | $this->assertTrue($merged instanceof AddUniqueOperation);
99 | $this->assertEquals([
100 | 'key2' => 'value2',
101 | 'value1'
102 | ], $merged->getValue(), 'Value was not as expected');
103 | }
104 |
105 | /**
106 | * @group add-unique-op
107 | */
108 | public function testInvalidMerge()
109 | {
110 | $this->expectException(
111 | '\Parse\ParseException',
112 | 'Operation is invalid after previous operation.'
113 | );
114 | $addOp = new AddUniqueOperation([
115 | 'key1' => 'value1'
116 | ]);
117 | $addOp->_mergeWithPrevious(new IncrementOperation());
118 | }
119 |
120 | /**
121 | * @group add-unique-op
122 | */
123 | public function testApply()
124 | {
125 | // test a null old value
126 | $objects = [
127 | 'key1' => 'value1'
128 | ];
129 | $addOp = new AddUniqueOperation($objects);
130 | $this->assertEquals($objects, $addOp->_apply(null, null, null));
131 |
132 | $addOp = new AddUniqueOperation([
133 | 'key' => 'string'
134 | ]);
135 | $oldValue = $addOp->_apply('string', null, null);
136 | $this->assertEquals(['string'], $oldValue);
137 |
138 | // test saving an object
139 | $obj = new \DateTime();
140 | $addOp = new AddUniqueOperation([
141 | 'object' => $obj
142 | ]);
143 | $oldValue = $addOp->_apply($obj, null, null);
144 | $this->assertEquals($obj, $oldValue[0]);
145 |
146 | // create a Parse object to save
147 | $obj1 = new ParseObject('TestObject');
148 | $obj1->set('name', 'montymxb');
149 | $obj1->save();
150 |
151 | // test a saved parse object as the old value
152 | $addOp = new AddUniqueOperation([
153 | 'object' => $obj1
154 | ]);
155 | $oldValue = $addOp->_apply($obj1, null, null);
156 | $this->assertEquals($obj1, $oldValue[0]);
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/Parse/ParsePushStatus.php:
--------------------------------------------------------------------------------
1 |
12 | * @package Parse
13 | */
14 | class ParsePushStatus extends ParseObject
15 | {
16 | /**
17 | * Parse Class name
18 | *
19 | * @var string
20 | */
21 | public static $parseClassName = '_PushStatus';
22 |
23 | // possible push status values from parse server
24 | const STATUS_SCHEDULED = 'scheduled';
25 | const STATUS_PENDING = 'pending';
26 | const STATUS_RUNNING = 'running';
27 | const STATUS_SUCCEEDED = 'succeeded';
28 | const STATUS_FAILED = 'failed';
29 |
30 | /**
31 | * 'scheduled', 'pending', etc. Add constants and 'isPending' and such for better status checking
32 | */
33 |
34 | /**
35 | * Returns a push status object or null from an id
36 | *
37 | * @param string $id Id to get this push status by
38 | * @return ParsePushStatus|null
39 | */
40 | public static function getFromId($id)
41 | {
42 | try {
43 | // return the associated PushStatus object
44 | $query = new ParseQuery(self::$parseClassName);
45 | return $query->get($id, true);
46 | } catch (ParseException $pe) {
47 | // no push found
48 | return null;
49 | }
50 | }
51 |
52 | /**
53 | * Gets the time this push was sent at
54 | *
55 | * @return \DateTime
56 | */
57 | public function getPushTime()
58 | {
59 | return new \DateTime($this->get("pushTime"));
60 | }
61 |
62 | /**
63 | * Gets the query used to send this push
64 | *
65 | * @return ParseQuery
66 | */
67 | public function getPushQuery()
68 | {
69 | $query = $this->get("query");
70 |
71 | // get the conditions
72 | $queryConditions = json_decode($query, true);
73 |
74 | // setup a query
75 | $query = new ParseQuery(self::$parseClassName);
76 |
77 | // set the conditions
78 | $query->_setConditions([
79 | 'where' => $queryConditions
80 | ]);
81 |
82 | return $query;
83 | }
84 |
85 | /**
86 | * Gets the payload
87 | *
88 | * @return array
89 | */
90 | public function getPushPayload()
91 | {
92 | return json_decode($this->get("payload"), true);
93 | }
94 |
95 | /**
96 | * Gets the source of this push
97 | *
98 | * @return string
99 | */
100 | public function getPushSource()
101 | {
102 | return $this->get("source");
103 | }
104 |
105 | /**
106 | * Gets the status of this push
107 | *
108 | * @return string
109 | */
110 | public function getPushStatus()
111 | {
112 | return $this->get("status");
113 | }
114 |
115 | /**
116 | * Indicates whether this push is scheduled
117 | *
118 | * @return bool
119 | */
120 | public function isScheduled()
121 | {
122 | return $this->getPushStatus() === self::STATUS_SCHEDULED;
123 | }
124 |
125 | /**
126 | * Indicates whether this push is pending
127 | *
128 | * @return bool
129 | */
130 | public function isPending()
131 | {
132 | return $this->getPushStatus() === self::STATUS_PENDING;
133 | }
134 |
135 | /**
136 | * Indicates whether this push is running
137 | *
138 | * @return bool
139 | */
140 | public function isRunning()
141 | {
142 | return $this->getPushStatus() === self::STATUS_RUNNING;
143 | }
144 |
145 | /**
146 | * Indicates whether this push has succeeded
147 | *
148 | * @return bool
149 | */
150 | public function hasSucceeded()
151 | {
152 | return $this->getPushStatus() === self::STATUS_SUCCEEDED;
153 | }
154 |
155 | /**
156 | * Indicates whether this push has failed
157 | *
158 | * @return bool
159 | */
160 | public function hasFailed()
161 | {
162 | return $this->getPushStatus() === self::STATUS_FAILED;
163 | }
164 |
165 | /**
166 | * Gets the number of pushes sent
167 | *
168 | * @return int
169 | */
170 | public function getPushesSent()
171 | {
172 | return $this->get("numSent");
173 | }
174 |
175 | /**
176 | * Gets the hash for this push
177 | *
178 | * @return string
179 | */
180 | public function getPushHash()
181 | {
182 | return $this->get("pushHash");
183 | }
184 |
185 | /**
186 | * Gets the number of pushes failed
187 | *
188 | * @return int
189 | */
190 | public function getPushesFailed()
191 | {
192 | return $this->get("numFailed");
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/tests/server.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import { ParseServer } from 'parse-server';
3 | import path from 'path';
4 | import fs from 'fs';
5 | import https from 'https';
6 | import emailAdapter from './MockEmailAdapter.js';
7 | const app = express();
8 | const __dirname = path.resolve();
9 |
10 | const server = new ParseServer({
11 | appName: "MyTestApp",
12 | appId: "app-id-here",
13 | masterKey: "master-key-here",
14 | restKey: "rest-api-key-here",
15 | databaseURI: "mongodb://localhost/test",
16 | cloud: __dirname + "/tests/cloud-code.js",
17 | publicServerURL: "http://localhost:1337/parse",
18 | logsFolder: path.resolve(process.cwd(), 'logs'),
19 | verbose: true,
20 | silent: true,
21 | push: {
22 | android: {
23 | senderId: "blank-sender-id",
24 | apiKey: "not-a-real-api-key"
25 | }
26 | },
27 | emailAdapter: emailAdapter({
28 | apiKey: 'not-a-real-api-key',
29 | domain: 'example.com',
30 | fromAddress: 'example@example.com',
31 | }),
32 | auth: {
33 | twitter: {
34 | consumer_key: "not_a_real_consumer_key",
35 | consumer_secret: "not_a_real_consumer_secret"
36 | },
37 | facebook: {
38 | appIds: "not_a_real_facebook_app_id"
39 | }
40 | },
41 | liveQuery: {
42 | classNames: ["TestObject", "_User"],
43 | },
44 | fileUpload: {
45 | enableForPublic: true,
46 | enableForAnonymousUser: true,
47 | enableForAuthenticatedUser: true,
48 | },
49 | });
50 |
51 | await server.start();
52 |
53 | // Serve the Parse API on the /parse URL prefix
54 | app.use('/parse', server.app);
55 |
56 | const port = 1337;
57 | app.listen(port, function() {
58 | console.error('[ Parse Test Http Server running on port ' + port + ' ]');
59 | });
60 |
61 | const options = {
62 | port: process.env.PORT || 1338,
63 | server_key: process.env.SERVER_KEY || __dirname + '/tests/keys/localhost.key',
64 | server_crt: process.env.SERVER_CRT || __dirname + '/tests/keys/localhost.crt',
65 | server_fp: process.env.SERVER_FP || __dirname + '/tests/keys/localhost.fp',
66 | client_key: process.env.CLIENT_KEY || __dirname + '/tests/keys/client.key',
67 | client_crt: process.env.CLIENT_CRT || __dirname + '/tests/keys/client.crt',
68 | client_fp: process.env.CLIENT_FP || __dirname + '/tests/keys/client.fp',
69 | ca: process.env.TLS_CA || __dirname + '/tests/keys/parseca.crt'
70 | }
71 |
72 | // Load fingerprints
73 | const clientFingerprints = [fs.readFileSync(options.server_fp).toString().replace('\n', '')];
74 |
75 | // Configure server
76 | const serverOptions = {
77 | key: fs.readFileSync(options.server_key),
78 | cert: fs.readFileSync(options.server_crt),
79 | ca: fs.readFileSync(options.ca),
80 | requestCert: true,
81 | rejectUnauthorized: true
82 | }
83 |
84 | function onRequest(req) {
85 | console.log(
86 | new Date(),
87 | req.connection.remoteAddress,
88 | req.socket.getPeerCertificate().subject.CN,
89 | req.method,
90 | req.baseUrl,
91 | );
92 | }
93 |
94 | // Create TLS enabled server
95 | const httpsServer = https.createServer(serverOptions, app);
96 | httpsServer.on('request', onRequest);
97 |
98 | // Start Server
99 | httpsServer.listen(options.port, function() {
100 | console.error('[ Parse Test Https Server running on port ' + options.port + ' ]');
101 | });
102 |
103 | // Create TLS request
104 | const requestOptions = {
105 | hostname: 'localhost',
106 | port: options.port,
107 | path: '/parse/health',
108 | method: 'GET',
109 | key: fs.readFileSync(options.client_key),
110 | cert: fs.readFileSync(options.client_crt),
111 | ca: fs.readFileSync(options.ca),
112 | requestCert: true,
113 | rejectUnauthorized: true,
114 | maxCachedSessions: 0,
115 | headers: {
116 | 'Content-Type': 'application/json',
117 | 'X-Parse-Application-Id': 'app-id-here',
118 | 'X-Parse-Master-Key': 'master-key-here',
119 | 'X-Parse-REST-API-Key': 'rest-api-key-here',
120 | }
121 | };
122 |
123 | // Create agent (required for custom trust list)
124 | requestOptions.agent = new https.Agent(requestOptions);
125 |
126 | const req = https.request(requestOptions, (res) => {
127 | console.log('statusCode:', res.statusCode);
128 | });
129 | req.end();
130 |
131 | // Pin server certs
132 | req.on('socket', socket => {
133 | socket.on('secureConnect', () => {
134 | const fingerprint = socket.getPeerCertificate().fingerprint;
135 |
136 | // Check if certificate is valid
137 | if (socket.authorized === false) {
138 | req.emit('error', new Error(socket.authorizationError));
139 | return req.destroy();
140 | }
141 |
142 | // Check if fingerprint matches
143 | if (clientFingerprints.indexOf(fingerprint) === -1) {
144 | req.emit('error', new Error('Fingerprint does not match'));
145 | return req.destroy();
146 | }
147 | });
148 | });
149 |
150 | req.on('error', (e) => {
151 | console.error(e);
152 | process.exit(0);
153 | });
154 |
--------------------------------------------------------------------------------
/tests/Parse/ParseInstallationTest.php:
--------------------------------------------------------------------------------
1 | expectException(
28 | '\Parse\ParseException',
29 | 'at least one ID field (deviceToken, installationId) must be specified in this operation'
30 | );
31 |
32 | (new ParseInstallation())->save();
33 | }
34 |
35 | /**
36 | * @group installation-tests
37 | */
38 | public function testMissingDeviceType()
39 | {
40 | $this->expectException(
41 | '\Parse\ParseException',
42 | 'deviceType must be specified in this operation'
43 | );
44 |
45 | $installation = new ParseInstallation();
46 | $installation->set('deviceToken', '12345');
47 | $installation->save();
48 | }
49 |
50 | /**
51 | * @group installation-tests
52 | */
53 | public function testClientsCannotFindWithoutMasterKey()
54 | {
55 | $this->expectException(
56 | '\Parse\ParseException',
57 | 'Clients aren\'t allowed to perform the find operation on the installation collection.'
58 | );
59 |
60 | $query = ParseInstallation::query();
61 | $query->first();
62 | }
63 |
64 | /**
65 | * @group installation-tests
66 | */
67 | public function testClientsCannotDestroyWithoutMasterKey()
68 | {
69 | $installation = new ParseInstallation();
70 | $installation->set('deviceToken', '12345');
71 | $installation->set('deviceType', 'android');
72 | $installation->save();
73 |
74 | $this->expectException(
75 | '\Parse\ParseException',
76 | "Clients aren't allowed to perform the delete operation on the installation collection."
77 | );
78 |
79 | // try destroying, without using the master key
80 | $installation->destroy();
81 | }
82 |
83 | /**
84 | * @group installation-tests
85 | */
86 | public function testInstallation()
87 | {
88 | $installationId = '12345';
89 | $deviceToken = 'device-token';
90 | $deviceType = 'android';
91 | $channels = [
92 | 'one',
93 | 'zwei',
94 | 'tres'
95 | ];
96 | $pushType = 'a-push-type';
97 | $GCMSenderId = 'gcm-sender-id';
98 | $timeZone = 'Time/Zone';
99 | $localeIdentifier = 'locale';
100 | $badge = 32;
101 | $appVersion = '1.0.0';
102 | $appName = 'Foo Bar App';
103 | $appIdentifier = 'foo-bar-app-id';
104 | $parseVersion = ParseClient::VERSION_STRING;
105 |
106 | $installation = new ParseInstallation();
107 | $installation->set('installationId', $installationId);
108 | $installation->set('deviceToken', $deviceToken);
109 | $installation->setArray('channels', $channels);
110 | $installation->set('deviceType', $deviceType);
111 | $installation->set('pushType', $pushType);
112 | $installation->set('GCMSenderId', $GCMSenderId);
113 | $installation->set('timeZone', $timeZone);
114 | $installation->set('localeIdentifier', $localeIdentifier);
115 | $installation->set('badge', $badge);
116 | $installation->set('appVersion', $appVersion);
117 | $installation->set('appName', $appName);
118 | $installation->set('appIdentifier', $appIdentifier);
119 | $installation->set('parseVersion', $parseVersion);
120 |
121 | $installation->save();
122 |
123 | // query for this installation now
124 | $query = ParseInstallation::query();
125 | $inst = $query->first(true);
126 |
127 | $this->assertNotNull($inst, 'Installation not found');
128 |
129 | $this->assertEquals($inst->getInstallationId(), $installationId);
130 | $this->assertEquals($inst->getDeviceToken(), $deviceToken);
131 | $this->assertEquals($inst->getChannels(), $channels);
132 | $this->assertEquals($inst->getDeviceType(), $deviceType);
133 | $this->assertEquals($inst->getPushType(), $pushType);
134 | $this->assertEquals($inst->getGCMSenderId(), $GCMSenderId);
135 | $this->assertEquals($inst->getTimeZone(), $timeZone);
136 | $this->assertEquals($inst->getLocaleIdentifier(), $localeIdentifier);
137 | $this->assertEquals($inst->getBadge(), $badge);
138 | $this->assertEquals($inst->getAppVersion(), $appVersion);
139 | $this->assertEquals($inst->getAppName(), $appName);
140 | $this->assertEquals($inst->getAppIdentifier(), $appIdentifier);
141 | $this->assertEquals($inst->getParseVersion(), $parseVersion);
142 |
143 | // cleanup
144 | $installation->destroy(true);
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/tests/Parse/ParseRelationOperationTest.php:
--------------------------------------------------------------------------------
1 | expectException(
32 | '\Exception',
33 | 'Cannot create a ParseRelationOperation with no objects.'
34 | );
35 | new ParseRelationOperation(null, null);
36 | }
37 |
38 | /**
39 | * @group parse-relation-op
40 | */
41 | public function testMixedClasses()
42 | {
43 | $this->expectException(
44 | '\Exception',
45 | 'All objects in a relation must be of the same class.'
46 | );
47 |
48 | $objects = [];
49 | $objects[] = new ParseObject('Class1');
50 | $objects[] = new ParseObject('Class2');
51 |
52 | new ParseRelationOperation($objects, null);
53 | }
54 |
55 | /**
56 | * @group parse-relation-op
57 | */
58 | public function testSingleObjects()
59 | {
60 | $addObj = new ParseObject('Class1');
61 | $addObj->save();
62 | $delObj = new ParseObject('Class1');
63 | $delObj->save();
64 |
65 | $op = new ParseRelationOperation($addObj, $delObj);
66 |
67 | $encoded = $op->_encode();
68 |
69 | $this->assertEquals('AddRelation', $encoded['ops'][0]['__op']);
70 | $this->assertEquals('RemoveRelation', $encoded['ops'][1]['__op']);
71 |
72 | ParseObject::destroyAll([$addObj, $delObj]);
73 | }
74 |
75 | /**
76 | * @group parse-relation-op
77 | */
78 | public function testApplyDifferentClassRelation()
79 | {
80 | $this->expectException(
81 | '\Exception',
82 | 'Related object object must be of class '
83 | .'Class1, but DifferentClass'
84 | .' was passed in.'
85 | );
86 |
87 | // create one op
88 | $addObj = new ParseObject('Class1');
89 | $relOp1 = new ParseRelationOperation($addObj, null);
90 |
91 | $relOp1->_apply(new ParseRelation(null, null, 'DifferentClass'), null, null);
92 | }
93 |
94 | /**
95 | * @group parse-relation-op
96 | */
97 | public function testInvalidApply()
98 | {
99 | $this->expectException(
100 | '\Exception',
101 | 'Operation is invalid after previous operation.'
102 | );
103 | $addObj = new ParseObject('Class1');
104 | $op = new ParseRelationOperation($addObj, null);
105 | $op->_apply('bad value', null, null);
106 | }
107 |
108 | /**
109 | * @group parse-relation-op
110 | */
111 | public function testMergeNone()
112 | {
113 | $addObj = new ParseObject('Class1');
114 | $op = new ParseRelationOperation($addObj, null);
115 | $this->assertEquals($op, $op->_mergeWithPrevious(null));
116 | }
117 |
118 | /**
119 | * @group parse-relation-op
120 | */
121 | public function testMergeDifferentClass()
122 | {
123 | $this->expectException(
124 | '\Exception',
125 | 'Related object must be of class '
126 | .'Class1, but AnotherClass'
127 | .' was passed in.'
128 | );
129 |
130 | $addObj = new ParseObject('Class1');
131 | $op = new ParseRelationOperation($addObj, null);
132 |
133 | $diffObj = new ParseObject('AnotherClass');
134 | $mergeOp = new ParseRelationOperation($diffObj, null);
135 |
136 | $this->assertEquals($op, $op->_mergeWithPrevious($mergeOp));
137 | }
138 |
139 | /**
140 | * @group parse-relation-op
141 | */
142 | public function testInvalidMerge()
143 | {
144 | $this->expectException(
145 | '\Exception',
146 | 'Operation is invalid after previous operation.'
147 | );
148 | $obj = new ParseObject('Class1');
149 | $op = new ParseRelationOperation($obj, null);
150 | $op->_mergeWithPrevious('not a relational op');
151 | }
152 |
153 | /**
154 | * @group parse-relation-op
155 | */
156 | public function testRemoveElementsFromArray()
157 | {
158 | // test without passing an array
159 | $array = [
160 | 'removeThis'
161 | ];
162 | ParseRelationOperation::removeElementsFromArray('removeThis', $array);
163 |
164 | $this->assertEmpty($array);
165 | }
166 |
167 | /**
168 | * @group relation-remove-missing-object-id
169 | */
170 | public function testRemoveMissingObjectId()
171 | {
172 | $obj = new ParseObject('Class1');
173 | $op = new ParseRelationOperation(null, $obj);
174 | $op->_mergeWithPrevious(new ParseRelationOperation(null, $obj));
175 | $this->assertTrue(true);
176 | }
177 | }
178 |
--------------------------------------------------------------------------------