├── .github
├── ISSUE_TEMPLATE
│ ├── ---1-report-an-issue.md
│ ├── ---2-feature-request.md
│ └── config.yml
├── pull_request_template.md
└── workflows
│ ├── ci.yml
│ ├── release-automated.yml
│ └── release-manual-docs.yml
├── .gitignore
├── .releaserc
├── commit.hbs
├── footer.hbs
├── header.hbs
└── template.hbs
├── Assets
└── logo large.png
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── autoload.php
├── composer.json
├── composer.lock
├── package-lock.json
├── package.json
├── phpcs.xml.dist
├── phpunit.xml
├── release.config.cjs
├── src
└── Parse
│ ├── HttpClients
│ ├── ParseCurl.php
│ ├── ParseCurlHttpClient.php
│ ├── ParseHttpable.php
│ ├── ParseStream.php
│ └── ParseStreamHttpClient.php
│ ├── Internal
│ ├── AddOperation.php
│ ├── AddUniqueOperation.php
│ ├── DeleteOperation.php
│ ├── Encodable.php
│ ├── FieldOperation.php
│ ├── IncrementOperation.php
│ ├── ParseRelationOperation.php
│ ├── RemoveOperation.php
│ └── SetOperation.php
│ ├── ParseACL.php
│ ├── ParseAggregateException.php
│ ├── ParseAnalytics.php
│ ├── ParseAudience.php
│ ├── ParseBytes.php
│ ├── ParseClient.php
│ ├── ParseCloud.php
│ ├── ParseConfig.php
│ ├── ParseException.php
│ ├── ParseFile.php
│ ├── ParseGeoPoint.php
│ ├── ParseHooks.php
│ ├── ParseInstallation.php
│ ├── ParseLogs.php
│ ├── ParseMemoryStorage.php
│ ├── ParseObject.php
│ ├── ParsePolygon.php
│ ├── ParsePush.php
│ ├── ParsePushStatus.php
│ ├── ParseQuery.php
│ ├── ParseRelation.php
│ ├── ParseRole.php
│ ├── ParseSchema.php
│ ├── ParseServerInfo.php
│ ├── ParseSession.php
│ ├── ParseSessionStorage.php
│ ├── ParseStorageInterface.php
│ └── ParseUser.php
└── tests
├── MockEmailAdapter.js
├── Parse
├── AddOperationTest.php
├── AddUniqueOperationTest.php
├── ConfigMock.php
├── Helper.php
├── HttpClientMock.php
├── IncrementOperationTest.php
├── IncrementTest.php
├── ParseACLTest.php
├── ParseAnalyticsTest.php
├── ParseAudienceTest.php
├── ParseBytesTest.php
├── ParseClientTest.php
├── ParseCloudTest.php
├── ParseConfigTest.php
├── ParseCurlHttpClientTest.php
├── ParseCurlTest.php
├── ParseFileTest.php
├── ParseGeoBoxTest.php
├── ParseGeoPointTest.php
├── ParseHooksTest.php
├── ParseInstallationTest.php
├── ParseLogsTest.php
├── ParseMemoryStorageTest.php
├── ParseObjectMock.php
├── ParseObjectTest.php
├── ParsePolygonTest.php
├── ParsePushTest.php
├── ParseQueryAggregateTest.php
├── ParseQueryFullTextTest.php
├── ParseQueryRelativeTimeTest.php
├── ParseQueryTest.php
├── ParseRelationOperationTest.php
├── ParseRelationTest.php
├── ParseRoleTest.php
├── ParseSchemaTest.php
├── ParseServerInfoTest.php
├── ParseSessionFixationTest.php
├── ParseSessionStorageAltTest.php
├── ParseSessionStorageTest.php
├── ParseSessionTest.php
├── ParseStreamHttpClientTest.php
├── ParseSubclassTest.php
├── ParseUserTest.php
└── RemoveOperationTest.php
├── bootstrap-stream.php
├── bootstrap.php
├── cloud-code.js
├── gencerts.sh
├── keys
├── client.crt
├── client.der
├── client.fp
├── client.key
├── client.pem
├── localhost.crt
├── localhost.fp
├── localhost.key
├── localhost.pem
├── localhost.pubkey.der
├── localhost.pubkey.pem
├── parseca.crt
├── parseca.fp
├── parseca.key
├── parseca.pem
└── parseca.srl
└── server.js
/.github/ISSUE_TEMPLATE/---1-report-an-issue.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41B Report an issue"
3 | about: A feature is not working as expected.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### New Issue Checklist
11 |
16 |
17 | - [ ] I am not disclosing a [vulnerability](https://github.com/parse-community/parse-php-sdk/security/policy).
18 | - [ ] I am not just asking a [question](https://github.com/parse-community/.github/blob/main/SUPPORT.md).
19 | - [ ] I have searched through [existing issues](https://github.com/parse-community/parse-php-sdk/issues?q=is%3Aissue).
20 | - [ ] I can reproduce the issue with the latest version of [Parse Server](https://github.com/parse-community/parse-server/releases) and the [Parse PHP SDK](https://github.com/parse-community/parse-php-sdk/releases).
21 |
22 | ### Issue Description
23 |
24 |
25 | ### Steps to reproduce
26 |
27 |
28 | ### Actual Outcome
29 |
30 |
31 | ### Expected Outcome
32 |
33 |
34 | ### Environment
35 |
36 |
37 | Parse PHP SDK
38 | - SDK version: `FILL_THIS_OUT`
39 | - PHP version: `FILL_THIS_OUT`
40 |
41 | Server
42 | - Parse Server version: `FILL_THIS_OUT`
43 | - Operating system: `FILL_THIS_OUT`
44 | - Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): `FILL_THIS_OUT`
45 |
46 | Database
47 | - System (MongoDB or Postgres): `FILL_THIS_OUT`
48 | - Database version: `FILL_THIS_OUT`
49 | - Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc): `FILL_THIS_OUT`
50 |
51 | ### Logs
52 |
53 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---2-feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F4A1 Request a feature"
3 | about: Suggest new functionality or an enhancement of existing functionality.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### New Feature / Enhancement Checklist
11 |
16 |
17 | - [ ] I am not disclosing a [vulnerability](https://github.com/parse-community/parse-php-sdk/security/policy).
18 | - [ ] I am not just asking a [question](https://github.com/parse-community/.github/blob/main/SUPPORT.md).
19 | - [ ] I have searched through [existing issues](https://github.com/parse-community/parse-php-sdk/issues?q=is%3Aissue).
20 |
21 | ### Current Limitation
22 |
23 |
24 | ### Feature / Enhancement Description
25 |
26 |
27 | ### Example Use Case
28 |
29 |
30 | ### Alternatives / Workarounds
31 |
32 |
33 | ### 3rd Party References
34 |
35 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: 🙋🏽♀️ Getting help with code
4 | url: https://stackoverflow.com/questions/tagged/parse-platform
5 | about: Get help with code-level questions on Stack Overflow.
6 | - name: 🙋 Getting general help
7 | url: https://community.parseplatform.org
8 | about: Get help with other questions on our Community Forum.
9 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ### New Pull Request Checklist
2 |
7 |
8 | - [ ] I am not disclosing a [vulnerability](https://github.com/parse-community/parse-php-sdk/blob/master/SECURITY.md).
9 | - [ ] I am creating this PR in reference to an [issue](https://github.com/parse-community/parse-php-sdk/issues?q=is%3Aissue).
10 |
11 | ### Issue Description
12 |
13 |
14 | Closes: #`FILL_THIS_OUT`
15 |
16 | ### Approach
17 |
18 |
19 | ### TODOs before merging
20 |
24 |
25 | - [ ] Add tests
26 | - [ ] Add changes to documentation (guides, repository pages, in-code descriptions)
27 | - [ ] Add changelog entry
28 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: ci
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - "**"
10 | jobs:
11 | check-lock-file-version:
12 | name: NPM Lock File Version
13 | timeout-minutes: 5
14 | runs-on: ubuntu-20.04
15 | steps:
16 | - uses: actions/checkout@v3
17 | - name: Check NPM lock file version
18 | uses: mansona/npm-lockfile-version@v1
19 | with:
20 | version: 2
21 | build:
22 | runs-on: ubuntu-20.04
23 | timeout-minutes: 30
24 | strategy:
25 | matrix:
26 | include:
27 | - name: PHP 8.3
28 | PHP_VERSION: 8.3
29 | - name: PHP 8.2
30 | PHP_VERSION: 8.2
31 | - name: PHP 8.1
32 | PHP_VERSION: 8.1
33 | fail-fast: false
34 | name: Test ${{ matrix.name }}
35 | steps:
36 | - uses: actions/checkout@v3
37 | - name: Use Node.js
38 | uses: actions/setup-node@v3
39 | with:
40 | node-version: 16.17.1
41 | cache: npm
42 | - name: Setup PHP with PECL extension
43 | uses: shivammathur/setup-php@v2
44 | with:
45 | php-version: ${{ matrix.PHP_VERSION }}
46 | - run: composer install
47 | - run: npm ci
48 | - run: npm start
49 | - run: npm run lint
50 | - run: npm run test-stream
51 | - run: npm run test:coverage
52 | - run: npm run document-check
53 | - run: npm run document
54 | env:
55 | CI: true
56 | - run: bash <(curl -s https://codecov.io/bash)
57 | concurrency:
58 | group: ${{ github.workflow }}-${{ github.ref }}
59 | cancel-in-progress: true
60 |
--------------------------------------------------------------------------------
/.github/workflows/release-automated.yml:
--------------------------------------------------------------------------------
1 | name: release-automated
2 | on:
3 | push:
4 | branches: [ master, release, alpha, beta ]
5 | env:
6 | NODE_VERSION: 18
7 | PHP_VERSION: 8.1
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | outputs:
12 | current_tag: ${{ steps.tag.outputs.current_tag }}
13 | steps:
14 | - uses: actions/checkout@v3
15 | with:
16 | persist-credentials: false
17 | - uses: actions/setup-node@v3
18 | with:
19 | node-version: ${{ env.NODE_VERSION }}
20 | - run: npm ci
21 | - run: npx semantic-release
22 | env:
23 | GH_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }}
24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25 | - name: Determine tag on current commit
26 | id: tag
27 | run: echo "::set-output name=current_tag::$(git describe --tags --abbrev=0 --exact-match || echo '')"
28 | docs-publish:
29 | needs: release
30 | if: needs.release.outputs.current_tag != ''
31 | runs-on: ubuntu-latest
32 | timeout-minutes: 15
33 | steps:
34 | - uses: actions/checkout@v3
35 | with:
36 | ref: ${{ needs.release.outputs.current_tag }}
37 | - name: Use Node.js
38 | uses: actions/setup-node@v3
39 | with:
40 | node-version: ${{ env.NODE_VERSION }}
41 | - name: Setup PHP
42 | uses: shivammathur/setup-php@v2
43 | with:
44 | php-version: ${{ env.PHP_VERSION }}
45 | - name: Generate Docs
46 | run: |
47 | composer install
48 | npm run document-check
49 | npm run document
50 | env:
51 | SOURCE_TAG: ${{ needs.release.outputs.current_tag }}
52 | - name: Deploy
53 | uses: peaceiris/actions-gh-pages@v3.7.3
54 | with:
55 | github_token: ${{ secrets.GITHUB_TOKEN }}
56 | publish_dir: ./.phpdoc/build
57 |
--------------------------------------------------------------------------------
/.github/workflows/release-manual-docs.yml:
--------------------------------------------------------------------------------
1 | name: release-manual-docs
2 | on:
3 | workflow_dispatch:
4 | inputs:
5 | tag:
6 | default: ''
7 | description: 'Version tag:'
8 | env:
9 | NODE_VERSION: 18
10 | PHP_VERSION: 8.1
11 | jobs:
12 | docs-publish:
13 | if: github.event.inputs.tag != ''
14 | runs-on: ubuntu-latest
15 | timeout-minutes: 15
16 | steps:
17 | - uses: actions/checkout@v3
18 | with:
19 | ref: ${{ github.event.inputs.tag }}
20 | - name: Use Node.js
21 | uses: actions/setup-node@v3
22 | with:
23 | node-version: ${{ env.NODE_VERSION }}
24 | - name: Setup PHP
25 | uses: shivammathur/setup-php@v2
26 | with:
27 | php-version: ${{ env.PHP_VERSION }}
28 | - name: Generate Docs
29 | run: |
30 | composer install
31 | npm run document-check
32 | npm run document
33 | env:
34 | SOURCE_TAG: ${{ github.event.inputs.tag }}
35 | - name: Deploy
36 | uses: peaceiris/actions-gh-pages@v3.7.3
37 | with:
38 | github_token: ${{ secrets.GITHUB_TOKEN }}
39 | publish_dir: ./.phpdoc/build
40 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .DS_Store
3 | vendor
4 |
5 | # npm based parse-server-test
6 | node_modules
7 | logs
8 | test_logs
9 |
10 | # ignore test results
11 | phpunit-test-results
12 | .phpunit.result.cache
13 | .phpunit.cache
14 | *.log
15 |
16 | coverage.xml
17 |
18 | # ignore phpdoc
19 | output/
20 | .phpdoc/
21 |
22 | composer.phar
23 |
--------------------------------------------------------------------------------
/.releaserc/commit.hbs:
--------------------------------------------------------------------------------
1 | *{{#if scope}} **{{scope}}:**
2 | {{~/if}} {{#if subject}}
3 | {{~subject}}
4 | {{~else}}
5 | {{~header}}
6 | {{~/if}}
7 |
8 | {{~!-- commit link --}} {{#if @root.linkReferences~}}
9 | ([{{shortHash}}](
10 | {{~#if @root.repository}}
11 | {{~#if @root.host}}
12 | {{~@root.host}}/
13 | {{~/if}}
14 | {{~#if @root.owner}}
15 | {{~@root.owner}}/
16 | {{~/if}}
17 | {{~@root.repository}}
18 | {{~else}}
19 | {{~@root.repoUrl}}
20 | {{~/if}}/
21 | {{~@root.commit}}/{{hash}}))
22 | {{~else}}
23 | {{~shortHash}}
24 | {{~/if}}
25 |
26 | {{~!-- commit references --}}
27 | {{~#if references~}}
28 | , closes
29 | {{~#each references}} {{#if @root.linkReferences~}}
30 | [
31 | {{~#if this.owner}}
32 | {{~this.owner}}/
33 | {{~/if}}
34 | {{~this.repository}}#{{this.issue}}](
35 | {{~#if @root.repository}}
36 | {{~#if @root.host}}
37 | {{~@root.host}}/
38 | {{~/if}}
39 | {{~#if this.repository}}
40 | {{~#if this.owner}}
41 | {{~this.owner}}/
42 | {{~/if}}
43 | {{~this.repository}}
44 | {{~else}}
45 | {{~#if @root.owner}}
46 | {{~@root.owner}}/
47 | {{~/if}}
48 | {{~@root.repository}}
49 | {{~/if}}
50 | {{~else}}
51 | {{~@root.repoUrl}}
52 | {{~/if}}/
53 | {{~@root.issue}}/{{this.issue}})
54 | {{~else}}
55 | {{~#if this.owner}}
56 | {{~this.owner}}/
57 | {{~/if}}
58 | {{~this.repository}}#{{this.issue}}
59 | {{~/if}}{{/each}}
60 | {{~/if}}
61 |
62 |
--------------------------------------------------------------------------------
/.releaserc/footer.hbs:
--------------------------------------------------------------------------------
1 | {{#if noteGroups}}
2 | {{#each noteGroups}}
3 |
4 | ### {{title}}
5 |
6 | {{#each notes}}
7 | * {{#if commit.scope}}**{{commit.scope}}:** {{/if}}{{text}} ([{{commit.shortHash}}]({{commit.shortHash}}))
8 | {{/each}}
9 | {{/each}}
10 |
11 | {{/if}}
12 |
--------------------------------------------------------------------------------
/.releaserc/header.hbs:
--------------------------------------------------------------------------------
1 | {{#if isPatch~}}
2 | ##
3 | {{~else~}}
4 | #
5 | {{~/if}} {{#if @root.linkCompare~}}
6 | [{{version}}](
7 | {{~#if @root.repository~}}
8 | {{~#if @root.host}}
9 | {{~@root.host}}/
10 | {{~/if}}
11 | {{~#if @root.owner}}
12 | {{~@root.owner}}/
13 | {{~/if}}
14 | {{~@root.repository}}
15 | {{~else}}
16 | {{~@root.repoUrl}}
17 | {{~/if~}}
18 | /compare/{{previousTag}}...{{currentTag}})
19 | {{~else}}
20 | {{~version}}
21 | {{~/if}}
22 | {{~#if title}} "{{title}}"
23 | {{~/if}}
24 | {{~#if date}} ({{date}})
25 | {{/if}}
26 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/Assets/logo large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/parse-community/parse-php-sdk/081690dd822629933b27b8fa6b8adf143828a48d/Assets/logo large.png
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD License
2 |
3 | For Parse Server software
4 |
5 | Copyright (c) 2015-present, Parse, LLC. All rights reserved.
6 |
7 | Redistribution and use in source and binary forms, with or without modification,
8 | are permitted provided that the following conditions are met:
9 |
10 | * Redistributions of source code must retain the above copyright notice, this
11 | list of conditions and the following disclaimer.
12 |
13 | * Redistributions in binary form must reproduce the above copyright notice,
14 | this list of conditions and the following disclaimer in the documentation
15 | and/or other materials provided with the distribution.
16 |
17 | * Neither the name Parse nor the names of its contributors may be used to
18 | endorse or promote products derived from this software without specific
19 | prior written permission.
20 |
21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 |
32 | -----
33 |
34 | As of April 5, 2017, Parse, LLC has transferred this code to the parse-community organization, and will no longer be contributing to or distributing this code.
35 |
--------------------------------------------------------------------------------
/autoload.php:
--------------------------------------------------------------------------------
1 | =8.1 <8.4",
16 | "ext-curl": "*",
17 | "ext-json": "*"
18 | },
19 | "require-dev": {
20 | "phpunit/phpunit": "10.1.1",
21 | "squizlabs/php_codesniffer": "3.7.2",
22 | "phpdocumentor/phpdocumentor": "3.0.0",
23 | "jms/serializer": "1.7.1",
24 | "nikic/php-parser": "4.15"
25 | },
26 | "minimum-stability": "dev",
27 | "autoload": {
28 | "psr-4": {
29 | "Parse\\": "src/Parse/"
30 | }
31 | },
32 | "autoload-dev": {
33 | "psr-4": {
34 | "Parse\\Test\\": "tests/Parse/"
35 | }
36 | },
37 | "config": {
38 | "allow-plugins": {
39 | "symfony/flex": true
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parse-php-sdk",
3 | "scripts": {
4 | "test": "./vendor/bin/phpunit",
5 | "test-stream": "./vendor/bin/phpunit --bootstrap=./tests/bootstrap-stream.php",
6 | "test:coverage": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-clover=coverage.xml",
7 | "test-stream:coverage": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --bootstrap=./tests/bootstrap-stream.php --coverage-clover=coverage.xml",
8 | "lint": "./vendor/bin/phpcs --standard=./phpcs.xml.dist ./src/Parse ./tests/Parse",
9 | "lint:fix": "./vendor/bin/phpcbf --standard=./phpcs.xml.dist ./src/Parse ./tests/Parse",
10 | "prestart": "MONGODB_VERSION=4.0.4 MONGODB_TOPOLOGY=replicaset MONGODB_STORAGE_ENGINE=wiredTiger mongodb-runner start",
11 | "start": "TESTING=1 node ./tests/server.js &",
12 | "server-only": "TESTING=1 node ./tests/server.js",
13 | "document-check": "./vendor/bin/phpdoc -d ./src/ --template='default'",
14 | "document": "./vendor/bin/phpdoc -d ./src/ --title 'Parse PHP SDK API Reference' --template='default'"
15 | },
16 | "type": "module",
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/parse-community/parse-php-sdk"
20 | },
21 | "license": "BSD-3-Clause",
22 | "homepage": "https://parseplatform.org",
23 | "devDependencies": {
24 | "@semantic-release/changelog": "6.0.3",
25 | "@semantic-release/commit-analyzer": "9.0.2",
26 | "@semantic-release/exec": "6.0.3",
27 | "@semantic-release/git": "10.0.1",
28 | "@semantic-release/github": "8.0.7",
29 | "@semantic-release/release-notes-generator": "10.0.3",
30 | "mongodb-runner": "4.8.1",
31 | "parse-server": "github:parse-community/parse-server#alpha",
32 | "semantic-release": "21.0.1",
33 | "winston": "3.2.1"
34 | },
35 | "version": "2.4.0"
36 | }
37 |
--------------------------------------------------------------------------------
/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 | -------------------------------------------------------------------------------- /src/Parse/ParseBytes.php: -------------------------------------------------------------------------------- 1 | 14 | * @package Parse 15 | */ 16 | class ParseBytes implements Encodable 17 | { 18 | /** 19 | * Byte array. 20 | * 21 | * @var array 22 | */ 23 | private $byteArray; 24 | 25 | /** 26 | * Create a ParseBytes object with a given byte array. 27 | * 28 | * @param array $byteArray 29 | * 30 | * @return ParseBytes 31 | */ 32 | public static function createFromByteArray(array $byteArray) 33 | { 34 | $bytes = new self(); 35 | $bytes->setByteArray($byteArray); 36 | 37 | return $bytes; 38 | } 39 | 40 | /** 41 | * Create a ParseBytes object with a given base 64 encoded data string. 42 | * 43 | * @param string $base64Data 44 | * 45 | * @return ParseBytes 46 | */ 47 | public static function createFromBase64Data($base64Data) 48 | { 49 | $bytes = new self(); 50 | $bytes->setBase64Data($base64Data); 51 | 52 | return $bytes; 53 | } 54 | 55 | /** 56 | * Decodes and unpacks a given base64 encoded array of data 57 | * 58 | * @param string $base64Data 59 | */ 60 | private function setBase64Data($base64Data) 61 | { 62 | $byteArray = unpack('C*', base64_decode($base64Data)); 63 | $this->setByteArray($byteArray); 64 | } 65 | 66 | /** 67 | * Sets a new byte array 68 | * 69 | * @param array $byteArray Byte array to set 70 | */ 71 | private function setByteArray(array $byteArray) 72 | { 73 | $this->byteArray = $byteArray; 74 | } 75 | 76 | /** 77 | * Encode to associative array representation. 78 | * 79 | * @return array 80 | */ 81 | public function _encode() 82 | { 83 | $data = ''; 84 | foreach ($this->byteArray as $byte) { 85 | $data .= chr($byte); 86 | } 87 | 88 | return [ 89 | '__type' => 'Bytes', 90 | 'base64' => base64_encode($data), 91 | ]; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /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/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/ParseException.php: -------------------------------------------------------------------------------- 1 | 14 | * @package Parse 15 | */ 16 | class ParseException extends Exception 17 | { 18 | /** 19 | * Constructs a Parse\Exception. 20 | * 21 | * @param string $message Message for the Exception. 22 | * @param int $code Error code. 23 | * @param \Exception $previous Previous Exception. 24 | */ 25 | public function __construct($message, $code = 0, Exception $previous = null) 26 | { 27 | parent::__construct($message, $code, $previous); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /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/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/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/ParseMemoryStorage.php: -------------------------------------------------------------------------------- 1 | 13 | * @package Parse 14 | */ 15 | class ParseMemoryStorage implements ParseStorageInterface 16 | { 17 | /** 18 | * Memory storage 19 | * 20 | * @var array 21 | */ 22 | private $storage = []; 23 | 24 | /** 25 | * Sets a key-value pair in storage. 26 | * 27 | * @param string $key The key to set 28 | * @param mixed $value The value to set 29 | * 30 | * @return void 31 | */ 32 | public function set($key, $value) 33 | { 34 | $this->storage[$key] = $value; 35 | } 36 | 37 | /** 38 | * Remove a key from storage. 39 | * 40 | * @param string $key The key to remove. 41 | * 42 | * @return void 43 | */ 44 | public function remove($key) 45 | { 46 | unset($this->storage[$key]); 47 | } 48 | 49 | /** 50 | * Gets the value for a key from storage. 51 | * 52 | * @param string $key The key to get the value for 53 | * 54 | * @return mixed 55 | */ 56 | public function get($key) 57 | { 58 | if (isset($this->storage[$key])) { 59 | return $this->storage[$key]; 60 | } 61 | return null; 62 | } 63 | 64 | /** 65 | * Clear all the values in storage. 66 | */ 67 | public function clear() 68 | { 69 | $this->storage = []; 70 | } 71 | 72 | /** 73 | * Save the data, if necessary. Not implemented. 74 | */ 75 | public function save() 76 | { 77 | // No action required. 78 | return; 79 | } 80 | 81 | /** 82 | * Get all keys in storage. 83 | * 84 | * @return array 85 | */ 86 | public function getKeys() 87 | { 88 | return array_keys($this->storage); 89 | } 90 | 91 | /** 92 | * Get all key-value pairs from storage. 93 | * 94 | * @return array 95 | */ 96 | public function getAll() 97 | { 98 | return $this->storage; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Parse/ParsePolygon.php: -------------------------------------------------------------------------------- 1 | 14 | * @package Parse 15 | */ 16 | class ParsePolygon implements Encodable 17 | { 18 | /** 19 | * The coordinates. 20 | * 21 | * @var array 22 | */ 23 | private $coordinates; 24 | 25 | /** 26 | * Create a Parse Polygon object. 27 | * 28 | * @param array $coords GeoPoints or Coordinates. 29 | */ 30 | public function __construct($coords) 31 | { 32 | $this->setCoordinates($coords); 33 | } 34 | 35 | /** 36 | * Set the Coordinates value for this Polygon. 37 | * 38 | * @param array $coords GeoPoints or Coordinates. 39 | * 40 | * @throws ParseException 41 | */ 42 | public function setCoordinates($coords) 43 | { 44 | if (!is_array($coords)) { 45 | throw new ParseException('Coordinates must be an Array'); 46 | } 47 | if (count($coords) < 3) { 48 | throw new ParseException('Polygon must have at least 3 GeoPoints or Points'); 49 | } 50 | $points = []; 51 | foreach ($coords as $coord) { 52 | $geoPoint = null; 53 | if ($coord instanceof ParseGeoPoint) { 54 | $geoPoint = $coord; 55 | } elseif (is_array($coord) && count($coord) === 2) { 56 | $geoPoint = new ParseGeoPoint($coord[0], $coord[1]); 57 | } else { 58 | throw new ParseException('Coordinates must be an Array of GeoPoints or Points'); 59 | } 60 | $points[] = [$geoPoint->getLatitude(), $geoPoint->getLongitude()]; 61 | } 62 | $this->coordinates = $points; 63 | } 64 | 65 | /** 66 | * Returns the Coordinates value for this Polygon. 67 | * 68 | * @return array 69 | */ 70 | public function getCoordinates() 71 | { 72 | return $this->coordinates; 73 | } 74 | 75 | /** 76 | * Encode to associative array representation. 77 | * 78 | * @return array 79 | */ 80 | public function _encode() 81 | { 82 | return [ 83 | '__type' => 'Polygon', 84 | 'coordinates' => $this->coordinates, 85 | ]; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /src/Parse/ParseStorageInterface.php: -------------------------------------------------------------------------------- 1 | 12 | * @package Parse 13 | */ 14 | interface ParseStorageInterface 15 | { 16 | /** 17 | * Sets a key-value pair in storage. 18 | * 19 | * @param string $key The key to set 20 | * @param mixed $value The value to set 21 | * 22 | * @return null 23 | */ 24 | public function set($key, $value); 25 | 26 | /** 27 | * Remove a key from storage. 28 | * 29 | * @param string $key The key to remove. 30 | * 31 | * @return null 32 | */ 33 | public function remove($key); 34 | 35 | /** 36 | * Gets the value for a key from storage. 37 | * 38 | * @param string $key The key to get the value for 39 | * 40 | * @return mixed 41 | */ 42 | public function get($key); 43 | 44 | /** 45 | * Clear all the values in storage. 46 | * 47 | * @return null 48 | */ 49 | public function clear(); 50 | 51 | /** 52 | * Save the data, if necessary. This would be a no-op when using the 53 | * $_SESSION implementation, but could be used for saving to file or 54 | * database as an action instead of on every set. 55 | * 56 | * @return null 57 | */ 58 | public function save(); 59 | 60 | /** 61 | * Get all keys in storage. 62 | * 63 | * @return array 64 | */ 65 | public function getKeys(); 66 | 67 | /** 68 | * Get all key-value pairs from storage. 69 | * 70 | * @return array 71 | */ 72 | public function getAll(); 73 | } 74 | -------------------------------------------------------------------------------- /tests/MockEmailAdapter.js: -------------------------------------------------------------------------------- 1 | const MockEmailAdapter = options => { 2 | if (!options) { 3 | throw 'Options were not provided'; 4 | } 5 | const adapter = { 6 | sendVerificationEmail: () => Promise.resolve(), 7 | sendPasswordResetEmail: () => Promise.resolve(), 8 | sendMail: () => Promise.resolve(), 9 | }; 10 | if (options.sendMail) { 11 | adapter.sendMail = options.sendMail; 12 | } 13 | if (options.sendPasswordResetEmail) { 14 | adapter.sendPasswordResetEmail = options.sendPasswordResetEmail; 15 | } 16 | if (options.sendVerificationEmail) { 17 | adapter.sendVerificationEmail = options.sendVerificationEmail; 18 | } 19 | 20 | return adapter; 21 | }; 22 | 23 | export default MockEmailAdapter; 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/Parse/ConfigMock.php: -------------------------------------------------------------------------------- 1 | setConfig([ 13 | 'foo' => 'bar', 14 | 'some' => 1, 15 | 'another' => 'value' 16 | ]); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /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/HttpClientMock.php: -------------------------------------------------------------------------------- 1 | response; 17 | } 18 | 19 | public function setResponse($resp) 20 | { 21 | $this->response = $resp; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Parse/IncrementOperationTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(32, $addOp->getValue()); 29 | } 30 | 31 | /** 32 | * @group increment-op 33 | */ 34 | public function testMergePrevious() 35 | { 36 | $addOp = new IncrementOperation(); 37 | 38 | $this->assertEquals($addOp, $addOp->_mergeWithPrevious(null)); 39 | 40 | // check delete op 41 | $merged = $addOp->_mergeWithPrevious(new DeleteOperation()); 42 | $this->assertTrue($merged instanceof SetOperation); 43 | 44 | // check set op 45 | $merged = $addOp->_mergeWithPrevious(new SetOperation(12)); 46 | $this->assertTrue($merged instanceof SetOperation); 47 | $this->assertEquals(13, $merged->getValue(), 'Value was not as expected'); 48 | 49 | // check self 50 | $merged = $addOp->_mergeWithPrevious(new IncrementOperation(32)); 51 | $this->assertTrue($merged instanceof IncrementOperation); 52 | $this->assertEquals(33, $merged->getValue(), 'Value was not as expected'); 53 | } 54 | 55 | /** 56 | * @group increment-op 57 | */ 58 | public function testInvalidMerge() 59 | { 60 | $this->expectException( 61 | '\Parse\ParseException', 62 | 'Operation is invalid after previous operation.' 63 | ); 64 | $addOp = new IncrementOperation(); 65 | $addOp->_mergeWithPrevious(new AddOperation(['key' => 'value'])); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /tests/Parse/ParseBytesTest.php: -------------------------------------------------------------------------------- 1 | set('byteColumn', $bytes); 34 | $obj->save(); 35 | 36 | $query = new ParseQuery('BytesObject'); 37 | $objAgain = $query->get($obj->getObjectId()); 38 | $this->assertEquals('Fosco', $objAgain->get('byteColumn')); 39 | } 40 | 41 | public function testParseBytesFromBase64Data() 42 | { 43 | $obj = ParseObject::create('BytesObject'); 44 | $bytes = ParseBytes::createFromBase64Data('R3JhbnRsYW5k'); 45 | $obj->set('byteColumn', $bytes); 46 | $obj->save(); 47 | 48 | $query = new ParseQuery('BytesObject'); 49 | $objAgain = $query->get($obj->getObjectId()); 50 | $this->assertEquals('Grantland', $objAgain->get('byteColumn')); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Parse/ParseConfigTest.php: -------------------------------------------------------------------------------- 1 | assertEquals([], $config->getConfig()); 29 | } 30 | 31 | /** 32 | * @group parse-config 33 | */ 34 | public function testGetConfig() 35 | { 36 | $config = new ConfigMock(); 37 | $this->assertEquals('bar', $config->get('foo')); 38 | $this->assertEquals(1, $config->get('some')); 39 | 40 | // check null value 41 | $this->assertNull($config->get('notakey')); 42 | 43 | // check html value 44 | $this->assertEquals('value', $config->get('another')); 45 | } 46 | 47 | /** 48 | * @group parse-config 49 | */ 50 | public function testEscapeConfig() 51 | { 52 | $config = new ConfigMock(); 53 | 54 | // check html encoded value 55 | $this->assertEquals('<html>value</html>', $config->escape('another')); 56 | 57 | // check null value 58 | $this->assertNull($config->escape('notakey')); 59 | 60 | // check normal value 61 | $this->assertEquals('bar', $config->escape('foo')); 62 | } 63 | 64 | /** 65 | * @group parse-config 66 | */ 67 | public function testSaveConfig() 68 | { 69 | $config = new ParseConfig(); 70 | $this->assertNull($config->get('key')); 71 | $config->set('key', 'value'); 72 | $config->save(); 73 | 74 | $config = new ParseConfig(); 75 | $this->assertEquals($config->get('key'), 'value'); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/Parse/ParseCurlHttpClientTest.php: -------------------------------------------------------------------------------- 1 | setup(); 19 | $client->send("https://example.org"); 20 | 21 | $this->assertEquals(200, $client->getResponseStatusCode()); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Parse/ParseCurlTest.php: -------------------------------------------------------------------------------- 1 | expectException( 17 | '\Parse\ParseException', 18 | 'You must call ParseCurl::init first' 19 | ); 20 | 21 | $parseCurl = new ParseCurl(); 22 | $parseCurl->exec(); 23 | } 24 | 25 | public function testBadSetOption() 26 | { 27 | $this->expectException( 28 | '\Parse\ParseException', 29 | 'You must call ParseCurl::init first' 30 | ); 31 | 32 | $parseCurl = new ParseCurl(); 33 | $parseCurl->setOption(1, 1); 34 | } 35 | 36 | public function testBadSetOptionsArray() 37 | { 38 | $this->expectException( 39 | '\Parse\ParseException', 40 | 'You must call ParseCurl::init first' 41 | ); 42 | 43 | $parseCurl = new ParseCurl(); 44 | $parseCurl->setOptionsArray([]); 45 | } 46 | 47 | public function testBadGetInfo() 48 | { 49 | $this->expectException( 50 | '\Parse\ParseException', 51 | 'You must call ParseCurl::init first' 52 | ); 53 | 54 | $parseCurl = new ParseCurl(); 55 | $parseCurl->getInfo(1); 56 | } 57 | 58 | public function testBadGetError() 59 | { 60 | $this->expectException( 61 | '\Parse\ParseException', 62 | 'You must call ParseCurl::init first' 63 | ); 64 | 65 | $parseCurl = new ParseCurl(); 66 | $parseCurl->getError(); 67 | } 68 | 69 | public function testBadErrorCode() 70 | { 71 | $this->expectException( 72 | '\Parse\ParseException', 73 | 'You must call ParseCurl::init first' 74 | ); 75 | 76 | $parseCurl = new ParseCurl(); 77 | $parseCurl->getErrorCode(); 78 | } 79 | 80 | public function testBadClose() 81 | { 82 | $this->expectException( 83 | '\Parse\ParseException', 84 | 'You must call ParseCurl::init first' 85 | ); 86 | 87 | $parseCurl = new ParseCurl(); 88 | $parseCurl->close(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /tests/Parse/ParseMemoryStorageTest.php: -------------------------------------------------------------------------------- 1 | clear(); 27 | } 28 | 29 | public function testIsUsingDefaultStorage() 30 | { 31 | $this->assertTrue( 32 | self::$parseStorage instanceof ParseMemoryStorage 33 | ); 34 | } 35 | 36 | public function testSetAndGet() 37 | { 38 | self::$parseStorage->set('foo', 'bar'); 39 | $this->assertEquals('bar', self::$parseStorage->get('foo')); 40 | } 41 | 42 | public function testRemove() 43 | { 44 | self::$parseStorage->set('foo', 'bar'); 45 | self::$parseStorage->remove('foo'); 46 | $this->assertNull(self::$parseStorage->get('foo')); 47 | } 48 | 49 | public function testClear() 50 | { 51 | self::$parseStorage->set('foo', 'bar'); 52 | self::$parseStorage->set('foo2', 'bar'); 53 | self::$parseStorage->set('foo3', 'bar'); 54 | self::$parseStorage->clear(); 55 | $this->assertEmpty(self::$parseStorage->getKeys()); 56 | } 57 | 58 | public function testGetAll() 59 | { 60 | self::$parseStorage->set('foo', 'bar'); 61 | self::$parseStorage->set('foo2', 'bar'); 62 | self::$parseStorage->set('foo3', 'bar'); 63 | $result = self::$parseStorage->getAll(); 64 | $this->assertEquals('bar', $result['foo']); 65 | $this->assertEquals('bar', $result['foo2']); 66 | $this->assertEquals('bar', $result['foo3']); 67 | $this->assertEquals(3, count($result)); 68 | } 69 | 70 | public function testSave() 71 | { 72 | // does nothing 73 | self::$parseStorage->save(); 74 | $this->assertTrue(true); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Parse/ParseObjectMock.php: -------------------------------------------------------------------------------- 1 | set('subject', $subjects[$i]); 47 | $allObjects[] = $obj; 48 | } 49 | ParseObject::saveAll($allObjects); 50 | } 51 | 52 | public function testFullTextQuery() 53 | { 54 | $this->provideTestObjects(); 55 | $query = new ParseQuery('TestObject'); 56 | $query->fullText('subject', 'coffee'); 57 | $results = $query->find(); 58 | $this->assertEquals( 59 | 3, 60 | count($results), 61 | 'Did not return correct objects.' 62 | ); 63 | } 64 | 65 | public function testFullTextSort() 66 | { 67 | $this->provideTestObjects(); 68 | $query = new ParseQuery('TestObject'); 69 | $query->fullText('subject', 'coffee'); 70 | $query->ascending('$score'); 71 | $query->select('$score'); 72 | $results = $query->find(); 73 | $this->assertEquals( 74 | 3, 75 | count($results), 76 | 'Did not return correct number of objects.' 77 | ); 78 | $this->assertEquals(1, $results[0]->get('score')); 79 | $this->assertEquals(0.75, $results[1]->get('score')); 80 | $this->assertEquals(0.75, $results[2]->get('score')); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/Parse/ParseSessionFixationTest.php: -------------------------------------------------------------------------------- 1 | set('test', 'hi'); 48 | $noUserSessionId = session_id(); 49 | $user = ParseUser::loginWithAnonymous(); 50 | $anonymousSessionId = session_id(); 51 | $this->assertNotEquals($noUserSessionId, $anonymousSessionId); 52 | $this->assertEquals(ParseClient::getStorage()->get('test'), 'hi'); 53 | } 54 | 55 | public function testCookieIdChangedForAnonymousToRegistered() 56 | { 57 | $user = ParseUser::loginWithAnonymous(); 58 | $anonymousSessionId = session_id(); 59 | ParseClient::getStorage()->set('test', 'hi'); 60 | $user->setUsername('testy'); 61 | $user->setPassword('testy'); 62 | $user->save(); 63 | $user->login('testy', 'testy'); 64 | $registeredSessionId = session_id(); 65 | $this->assertNotEquals($anonymousSessionId, $registeredSessionId); 66 | $this->assertEquals(ParseClient::getStorage()->get('test'), 'hi'); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/Parse/ParseSessionStorageAltTest.php: -------------------------------------------------------------------------------- 1 | expectException( 20 | '\Parse\ParseException', 21 | 'PHP session_start() must be called first.' 22 | ); 23 | new ParseSessionStorage(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/Parse/ParseStreamHttpClientTest.php: -------------------------------------------------------------------------------- 1 | send('https://example.org'); 23 | 24 | // get response code 25 | $this->assertEquals(200, $client->getResponseStatusCode()); 26 | 27 | // get response headers 28 | $headers = $client->getResponseHeaders(); 29 | 30 | $this->assertTrue(preg_match('|HTTP/1\.\d\s200\sOK|', $headers['http_code']) === 1); 31 | } 32 | 33 | public function testInvalidUrl() 34 | { 35 | $url = 'http://example.com/lots of spaces here'; 36 | 37 | $this->expectException( 38 | '\Parse\ParseException', 39 | 'Url may not contain spaces for stream client: ' 40 | .$url 41 | ); 42 | 43 | $client = new ParseStreamHttpClient(); 44 | $client->send($url); 45 | } 46 | 47 | /** 48 | * @group test-stream-context-error 49 | */ 50 | public function testStreamContextError() 51 | { 52 | $client = $this->getMockBuilder(ParseStream::class) 53 | ->onlyMethods(['getFileContents']) 54 | ->getMock(); 55 | 56 | $client->expects($this->once()) 57 | ->method('getFileContents') 58 | ->willThrowException(new ParseException('Cannot retrieve data.', 1)); 59 | 60 | $client->get('https://example.org'); 61 | 62 | $this->assertEquals('Cannot retrieve data.', $client->getErrorMessage()); 63 | $this->assertEquals('1', $client->getErrorCode()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/Parse/ParseSubclassTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($install instanceof ParseInstallation); 26 | $this->assertTrue(is_subclass_of($install, 'Parse\ParseObject')); 27 | } 28 | 29 | public function testCreateFromParseObject() 30 | { 31 | $install = ParseObject::create('_Installation'); 32 | $this->assertTrue($install instanceof ParseInstallation); 33 | $this->assertTrue(is_subclass_of($install, 'Parse\ParseObject')); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /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/bootstrap-stream.php: -------------------------------------------------------------------------------- 1 | { 3 | if (request.params.key2 === 'value1') { 4 | return 'Foo'; 5 | } else { 6 | throw 'bad stuff happened'; 7 | } 8 | }); 9 | 10 | Parse.Cloud.define('createTestUser', async (request) => { 11 | const user = new Parse.User(); 12 | user.set('username', 'harry'); 13 | user.set('password', 'potter'); 14 | await user.signUp(); 15 | const loggedIn = await Parse.User.logIn('harry', 'potter'); 16 | return loggedIn.getSessionToken(); 17 | }); 18 | 19 | Parse.Cloud.define('foo', (request) => { 20 | var key1 = request.params.key1; 21 | var key2 = request.params.key2; 22 | if (key1 === "value1" && key2 23 | && key2.length === 3 && key2[0] === 1 24 | && key2[1] === 2 && key2[2] === 3) { 25 | result = { 26 | object: { 27 | __type: 'Object', 28 | className: 'Foo', 29 | objectId: '1', 30 | x: 2, 31 | relation: { 32 | __type: 'Object', 33 | className: 'Bar', 34 | objectId: '2', 35 | x: 3 36 | } 37 | }, 38 | array:[ 39 | { 40 | __type: 'Object', 41 | className: 'Bar', 42 | objectId: '10', 43 | x: 2 44 | } 45 | ] 46 | }; 47 | return result; 48 | } else if (key1 === 'value1') { 49 | return { a: 2 }; 50 | } else { 51 | throw 'invalid!'; 52 | } 53 | }); 54 | 55 | Parse.Cloud.job('CloudJob1', () => { 56 | return { 57 | status: 'cloud job completed' 58 | }; 59 | }); 60 | 61 | Parse.Cloud.job('CloudJob2', () => { 62 | return new Promise((resolve) => { 63 | setTimeout(function() { 64 | resolve({ 65 | status: 'cloud job completed' 66 | }) 67 | }, 3000); 68 | }); 69 | }); 70 | 71 | Parse.Cloud.job('CloudJobFailing', () => { 72 | throw 'cloud job failed'; 73 | }); -------------------------------------------------------------------------------- /tests/gencerts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # https://gist.github.com/ryankurte/bc0d8cff6e73a6bb1950 3 | # https://curl.se/libcurl/c/CURLOPT_PINNEDPUBLICKEY.html 4 | # ./gencerts.sh parseca localhost parsephp keys/ 5 | # ./gencerts.sh parseca client parsephp keys/ 6 | 7 | set -e 8 | 9 | if [ "$#" -ne 3 ] && [ "$#" -ne 4 ]; then 10 | echo "Usage: $0 CA NAME ORG" 11 | echo "CA - name of fake CA" 12 | echo "NAME - name of fake client" 13 | echo "ORG - organisation for both" 14 | echo "[DIR] - directory for cert output" 15 | exit 16 | fi 17 | 18 | CA=$1 19 | NAME=$2 20 | ORG=$3 21 | 22 | if [ -z "$4" ]; then 23 | DIR=./ 24 | else 25 | DIR=$4 26 | fi 27 | 28 | if [ ! -d "$DIR" ]; then 29 | mkdir -p $DIR 30 | fi 31 | 32 | LENGTH=4096 33 | DAYS=1000 34 | 35 | SUBJECT=/C=NZ/ST=AKL/L=Auckland/O=$ORG 36 | 37 | if [ ! -f "$DIR/$CA.key" ]; then 38 | 39 | echo Generating CA 40 | openssl genrsa -out $DIR/$CA.key $LENGTH 41 | 42 | echo Signing CA 43 | openssl req -x509 -new -nodes -key $DIR/$CA.key -sha256 -days 1024 -out $DIR/$CA.crt -subj $SUBJECT/CN=$CA 44 | 45 | openssl x509 -in $DIR/$CA.crt -out $DIR/$CA.pem -text 46 | openssl x509 -sha1 -noout -in $DIR/$CA.pem -fingerprint | sed 's/SHA1 Fingerprint=//g' >> $DIR/$CA.fp 47 | 48 | else 49 | echo Located existing CA 50 | fi 51 | 52 | if [ ! -f "$DIR/$NAME.key" ]; then 53 | 54 | echo Generating keys 55 | openssl genrsa -out $DIR/$NAME.key $LENGTH 56 | 57 | echo Generating CSR 58 | openssl req -new -out $DIR/$NAME.csr -key $DIR/$NAME.key -subj $SUBJECT/CN=$NAME 59 | 60 | echo Signing cert 61 | openssl x509 -req -days $DAYS -in $DIR/$NAME.csr -out $DIR/$NAME.crt -CA $DIR/$CA.crt -CAkey $DIR/$CA.key -CAcreateserial 62 | 63 | echo Generating PEM 64 | openssl x509 -in $DIR/$NAME.crt -out $DIR/$NAME.pem -text 65 | 66 | openssl x509 -sha1 -noout -in $DIR/$NAME.pem -fingerprint | sed 's/SHA1 Fingerprint=//g' > $DIR/$NAME.fp 67 | 68 | echo Cleaning Up 69 | rm $DIR/*.csr 70 | 71 | else 72 | echo Located existing client certificate 73 | fi 74 | 75 | echo Done 76 | -------------------------------------------------------------------------------- /tests/keys/client.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFITCCAwkCCQDEwoQengRnzDANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJO 3 | WjEMMAoGA1UECAwDQUtMMREwDwYDVQQHDAhBdWNrbGFuZDERMA8GA1UECgwIcGFy 4 | c2VwaHAxEDAOBgNVBAMMB3BhcnNlY2EwHhcNMjMwNTEyMjA0MzAzWhcNMjYwMjA1 5 | MjA0MzAzWjBSMQswCQYDVQQGEwJOWjEMMAoGA1UECAwDQUtMMREwDwYDVQQHDAhB 6 | dWNrbGFuZDERMA8GA1UECgwIcGFyc2VwaHAxDzANBgNVBAMMBmNsaWVudDCCAiIw 7 | DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMlKDLogZSNxm5S4h97XM8A1+MsP 8 | RaK15g8ebVEP7OGrwX1bvLVis0U/ixwHs0mqjQ9tbuefMZyiRgdds+8tpRCCuqGo 9 | dwSk8YMmOlrF5xIpBT2cXJLhGvDyY0F0RLFZYBoioTYFth4i91DkzhmBaL6vjyB7 10 | dXduR1JQbzTpQHkhofPziQsNtinf8qBqLbH1dFaqwUUEgtsKJyaPlxJR21TF3Fv+ 11 | 2K/fmoyzP6Er7eUSCvJRRH1hCwzHxl7GqTKyQeaS1RLdrHYqZmSeiJpxwl8uSdBs 12 | x4y8wG4lhRdantCCANlTwLd7zPiuIu+RBP276o2+02K8my9N2STZUgHXefSoNx4M 13 | alYujKBUV+2qmhR9H2HlUB/C/h5Sb8PSlfWyD/bo3+agyw/1+9rfMUYCmiEtdik7 14 | amoBaahoqAHL8S3K19L0ytWkgFejSMzn+i4VDifnwupXHifDL7CPDX0vevFNDvC7 15 | HMLkBWmkNaTduDL5P3ximtIXE7akhK+ufiNoO3KLItenCENxCzUdNdDHguei2U5E 16 | vhTyaTmIIrkUGxQ+aVDXRF2njAeQNMdTjsCAiiSN1corYX8RXvNo8QZQUEaHG42u 17 | O6Yolsw8EZotbpExo1jbiDlI2pVIuwJdtaDCucPN/X6uZ8odGQ0LUeyTBYda/1OG 18 | VIQzPZnxSHzqPzuZAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAHOLgs6FLIv+Vvpx 19 | fNtwabgOI2JxkFwaAujwWJS10tmczJp9qZilOlVBBhDFRBwBKqAaanHKCYkEfP6u 20 | dgC8RMOOYOb0gk6Tj3+zhvM4Qz+n8Cn2fA2+EtFXTKyMfJHuG/zddTLep2Phh9c9 21 | t5s/8aHAuqM9RGiA66V7mJiR9G5E8cNpyHniCh8Z11kABPMzAy92LyEGUlRwCrWx 22 | fCwItnzY2/7J8IW20rPIpb0EWmYHhxkUUzu7APQgvJpAUTdhmVKb9GLCUyY+oICE 23 | 1WrnV9OQiqYVGFQkry9FXyKbsLVM6b6ar8DIXpYTYnd11sqFdiUUo4oItYYrO/1O 24 | 0Bt0PX6hWYjR4r7ZT23KWAHZdlU4EFfrLJeZ6HDeYttJF68x9s8RZGgVU9Xlb/7X 25 | KGRVyCWI7aWzvI1lBVAnc7b7B9LrIkdHnYDt/ettmRvI/zZRBh73T7EPOQB7bEzP 26 | M8BXfAr/+qa2ToBWNd9AJrw7rg+OWGD801iXqsREyLr15nRIR12mGdKuyMkfghk1 27 | 9J1Sd0fkfB2ci7Rn3afRdKksGuADQ2fvYihw0lALOPzSq/FYRqZBzwv9Qmw43CKd 28 | euEPcCfT7VYY47lmfFfKBcVv8d7NiJZRGkIUYUxS/UAsrLiBCgRkaUACcbLok7sJ 29 | jrdaTDx4EZu93dmJbEozNO6dRiLb 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /tests/keys/client.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parse-community/parse-php-sdk/081690dd822629933b27b8fa6b8adf143828a48d/tests/keys/client.der -------------------------------------------------------------------------------- /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/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /tests/keys/client.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFITCCAwkCCQDEwoQengRnzDANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJO 3 | WjEMMAoGA1UECAwDQUtMMREwDwYDVQQHDAhBdWNrbGFuZDERMA8GA1UECgwIcGFy 4 | c2VwaHAxEDAOBgNVBAMMB3BhcnNlY2EwHhcNMjMwNTEyMjA0MzAzWhcNMjYwMjA1 5 | MjA0MzAzWjBSMQswCQYDVQQGEwJOWjEMMAoGA1UECAwDQUtMMREwDwYDVQQHDAhB 6 | dWNrbGFuZDERMA8GA1UECgwIcGFyc2VwaHAxDzANBgNVBAMMBmNsaWVudDCCAiIw 7 | DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMlKDLogZSNxm5S4h97XM8A1+MsP 8 | RaK15g8ebVEP7OGrwX1bvLVis0U/ixwHs0mqjQ9tbuefMZyiRgdds+8tpRCCuqGo 9 | dwSk8YMmOlrF5xIpBT2cXJLhGvDyY0F0RLFZYBoioTYFth4i91DkzhmBaL6vjyB7 10 | dXduR1JQbzTpQHkhofPziQsNtinf8qBqLbH1dFaqwUUEgtsKJyaPlxJR21TF3Fv+ 11 | 2K/fmoyzP6Er7eUSCvJRRH1hCwzHxl7GqTKyQeaS1RLdrHYqZmSeiJpxwl8uSdBs 12 | x4y8wG4lhRdantCCANlTwLd7zPiuIu+RBP276o2+02K8my9N2STZUgHXefSoNx4M 13 | alYujKBUV+2qmhR9H2HlUB/C/h5Sb8PSlfWyD/bo3+agyw/1+9rfMUYCmiEtdik7 14 | amoBaahoqAHL8S3K19L0ytWkgFejSMzn+i4VDifnwupXHifDL7CPDX0vevFNDvC7 15 | HMLkBWmkNaTduDL5P3ximtIXE7akhK+ufiNoO3KLItenCENxCzUdNdDHguei2U5E 16 | vhTyaTmIIrkUGxQ+aVDXRF2njAeQNMdTjsCAiiSN1corYX8RXvNo8QZQUEaHG42u 17 | O6Yolsw8EZotbpExo1jbiDlI2pVIuwJdtaDCucPN/X6uZ8odGQ0LUeyTBYda/1OG 18 | VIQzPZnxSHzqPzuZAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAHOLgs6FLIv+Vvpx 19 | fNtwabgOI2JxkFwaAujwWJS10tmczJp9qZilOlVBBhDFRBwBKqAaanHKCYkEfP6u 20 | dgC8RMOOYOb0gk6Tj3+zhvM4Qz+n8Cn2fA2+EtFXTKyMfJHuG/zddTLep2Phh9c9 21 | t5s/8aHAuqM9RGiA66V7mJiR9G5E8cNpyHniCh8Z11kABPMzAy92LyEGUlRwCrWx 22 | fCwItnzY2/7J8IW20rPIpb0EWmYHhxkUUzu7APQgvJpAUTdhmVKb9GLCUyY+oICE 23 | 1WrnV9OQiqYVGFQkry9FXyKbsLVM6b6ar8DIXpYTYnd11sqFdiUUo4oItYYrO/1O 24 | 0Bt0PX6hWYjR4r7ZT23KWAHZdlU4EFfrLJeZ6HDeYttJF68x9s8RZGgVU9Xlb/7X 25 | KGRVyCWI7aWzvI1lBVAnc7b7B9LrIkdHnYDt/ettmRvI/zZRBh73T7EPOQB7bEzP 26 | M8BXfAr/+qa2ToBWNd9AJrw7rg+OWGD801iXqsREyLr15nRIR12mGdKuyMkfghk1 27 | 9J1Sd0fkfB2ci7Rn3afRdKksGuADQ2fvYihw0lALOPzSq/FYRqZBzwv9Qmw43CKd 28 | euEPcCfT7VYY47lmfFfKBcVv8d7NiJZRGkIUYUxS/UAsrLiBCgRkaUACcbLok7sJ 29 | jrdaTDx4EZu93dmJbEozNO6dRiLb 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /tests/keys/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFJDCCAwwCCQDEwoQengRnyzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJO 3 | WjEMMAoGA1UECAwDQUtMMREwDwYDVQQHDAhBdWNrbGFuZDERMA8GA1UECgwIcGFy 4 | c2VwaHAxEDAOBgNVBAMMB3BhcnNlY2EwHhcNMjMwNTEyMjA0MjQ0WhcNMjYwMjA1 5 | MjA0MjQ0WjBVMQswCQYDVQQGEwJOWjEMMAoGA1UECAwDQUtMMREwDwYDVQQHDAhB 6 | dWNrbGFuZDERMA8GA1UECgwIcGFyc2VwaHAxEjAQBgNVBAMMCWxvY2FsaG9zdDCC 7 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANhB+NiGfXAgK4yJKdJEU9gC 8 | kBUVwTmEVYIbHjK/HuWxbDIeVAHMSM1CWFZm72zfcGiLQ2vzqSNQ2mCGpefRlKGG 9 | E5ch/HPDqeEKb/Qm+UnQTyMjKOMWckNEzqhilOiMHGR0hb6h5givz8aQ7kjp3ELm 10 | RWaBWJ1rBizooB3fEDAdGtEEIDiq/i2/sSRcFmOeVYJ8ynIJ2/SmqF3KttQAZnuv 11 | tFRGD+7C+loyxz+vCbAIkl1Smwj3ZsTKfGYGoSQA7Z7QVLzee7QgGmHCWiRmvKxX 12 | ZfaW27SE7xokw68Dx84FeFYeUTXZugCLFtcHQ/M7enZsfUNjfGqFE88piKXTDgXa 13 | qqCGKTqUhWYjnvH8AmiSbzUWYCJ+YtAY7wO7IdjrVzPh8pnCRXKefM2xJEJhDbSg 14 | 5lWkla7rJQfvKXuRlyiKonyI5hIYFwzgwPPoz+TKPLRltyzmPdmNshBN8USXOd3Q 15 | 5vG9JWSM2pP/l5UUusRRA2SrDEsud004mnQeCh1IHbF+srudSHQKPyxYOdc/WzUV 16 | yy+RN53N1VG9JkH60t3Dg0rYiC+wilqS2G56MbgrSN4HTldmcGebzJMIdFpNSKLJ 17 | uI886kgefd90OdsPqSI8rjPrEVAyL06w7P19/baWyyRlDQL3Do5uvMRk2YGHK2Ej 18 | malB6uq4MatqOWXoroYrAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAJnoVzVRxyQF 19 | qUchsTRZZFfPXZI3n6Xn1wI/U5YIUyOODbTLn35c99G/sCbECFmciBZKXLvmiBn0 20 | tGYphpFFLT+qHtrYn4a29LFCsYDMlmfSJPQ0+V6FNpfqo2xPMDZ8sW+SVwx/PyNx 21 | D/NyQ1+dZs5jKvVqyddU3e/jNG6hKIJF8rTfM5X2wp8RC55VGUeocWuPLCD9Ozmf 22 | Xhv6J8Jv9LhkOl9BjudgpQ43so8mfa3InC/1SU/2kjSjc6hpWfyjErVz32Yf1QF0 23 | ceVgZJ4EoMVecZMFbo6b1cdlg3AoRvBEr0LpMwFWsEWicCPQjWg/wyJLRqhDIvoC 24 | CWPGZrYsFilhHy9Cx7fOK7ZqLJK+dNbY5It7//TIZ364cTzUpYqfiv7lQ5Xp0c0h 25 | 9FiMCrFX4C3Su0UZfJjZ3KQTJfL0LnkwcvoT98RcNVPFT4Jgbe7thZPFHHZAAmag 26 | 5+2P9Xzz8OsNYUbGhDjd9Pq7gSiM4ZpJ2tc425qCZ2E84YqDYRtwZo3UIca7ktm0 27 | xckuoVQzxiIE/BhyL5+wL3NLTkgVjWh11XeiKn/h6BaOMj9QKTKc3RzLtqC57oVD 28 | iMowJq80nQUUsU8I1GibfuLM+0ahcK8an5Lzdl0dS+43eKQLJWhpfBZNgFMfXDEe 29 | o7eD8auY/MfkFWiUBpWGx7Grb/2a/z1q 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /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/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /tests/keys/localhost.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFJDCCAwwCCQDEwoQengRnyzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJO 3 | WjEMMAoGA1UECAwDQUtMMREwDwYDVQQHDAhBdWNrbGFuZDERMA8GA1UECgwIcGFy 4 | c2VwaHAxEDAOBgNVBAMMB3BhcnNlY2EwHhcNMjMwNTEyMjA0MjQ0WhcNMjYwMjA1 5 | MjA0MjQ0WjBVMQswCQYDVQQGEwJOWjEMMAoGA1UECAwDQUtMMREwDwYDVQQHDAhB 6 | dWNrbGFuZDERMA8GA1UECgwIcGFyc2VwaHAxEjAQBgNVBAMMCWxvY2FsaG9zdDCC 7 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANhB+NiGfXAgK4yJKdJEU9gC 8 | kBUVwTmEVYIbHjK/HuWxbDIeVAHMSM1CWFZm72zfcGiLQ2vzqSNQ2mCGpefRlKGG 9 | E5ch/HPDqeEKb/Qm+UnQTyMjKOMWckNEzqhilOiMHGR0hb6h5givz8aQ7kjp3ELm 10 | RWaBWJ1rBizooB3fEDAdGtEEIDiq/i2/sSRcFmOeVYJ8ynIJ2/SmqF3KttQAZnuv 11 | tFRGD+7C+loyxz+vCbAIkl1Smwj3ZsTKfGYGoSQA7Z7QVLzee7QgGmHCWiRmvKxX 12 | ZfaW27SE7xokw68Dx84FeFYeUTXZugCLFtcHQ/M7enZsfUNjfGqFE88piKXTDgXa 13 | qqCGKTqUhWYjnvH8AmiSbzUWYCJ+YtAY7wO7IdjrVzPh8pnCRXKefM2xJEJhDbSg 14 | 5lWkla7rJQfvKXuRlyiKonyI5hIYFwzgwPPoz+TKPLRltyzmPdmNshBN8USXOd3Q 15 | 5vG9JWSM2pP/l5UUusRRA2SrDEsud004mnQeCh1IHbF+srudSHQKPyxYOdc/WzUV 16 | yy+RN53N1VG9JkH60t3Dg0rYiC+wilqS2G56MbgrSN4HTldmcGebzJMIdFpNSKLJ 17 | uI886kgefd90OdsPqSI8rjPrEVAyL06w7P19/baWyyRlDQL3Do5uvMRk2YGHK2Ej 18 | malB6uq4MatqOWXoroYrAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAJnoVzVRxyQF 19 | qUchsTRZZFfPXZI3n6Xn1wI/U5YIUyOODbTLn35c99G/sCbECFmciBZKXLvmiBn0 20 | tGYphpFFLT+qHtrYn4a29LFCsYDMlmfSJPQ0+V6FNpfqo2xPMDZ8sW+SVwx/PyNx 21 | D/NyQ1+dZs5jKvVqyddU3e/jNG6hKIJF8rTfM5X2wp8RC55VGUeocWuPLCD9Ozmf 22 | Xhv6J8Jv9LhkOl9BjudgpQ43so8mfa3InC/1SU/2kjSjc6hpWfyjErVz32Yf1QF0 23 | ceVgZJ4EoMVecZMFbo6b1cdlg3AoRvBEr0LpMwFWsEWicCPQjWg/wyJLRqhDIvoC 24 | CWPGZrYsFilhHy9Cx7fOK7ZqLJK+dNbY5It7//TIZ364cTzUpYqfiv7lQ5Xp0c0h 25 | 9FiMCrFX4C3Su0UZfJjZ3KQTJfL0LnkwcvoT98RcNVPFT4Jgbe7thZPFHHZAAmag 26 | 5+2P9Xzz8OsNYUbGhDjd9Pq7gSiM4ZpJ2tc425qCZ2E84YqDYRtwZo3UIca7ktm0 27 | xckuoVQzxiIE/BhyL5+wL3NLTkgVjWh11XeiKn/h6BaOMj9QKTKc3RzLtqC57oVD 28 | iMowJq80nQUUsU8I1GibfuLM+0ahcK8an5Lzdl0dS+43eKQLJWhpfBZNgFMfXDEe 29 | o7eD8auY/MfkFWiUBpWGx7Grb/2a/z1q 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /tests/keys/localhost.pubkey.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parse-community/parse-php-sdk/081690dd822629933b27b8fa6b8adf143828a48d/tests/keys/localhost.pubkey.der -------------------------------------------------------------------------------- /tests/keys/localhost.pubkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2EH42IZ9cCArjIkp0kRT 3 | 2AKQFRXBOYRVghseMr8e5bFsMh5UAcxIzUJYVmbvbN9waItDa/OpI1DaYIal59GU 4 | oYYTlyH8c8Op4Qpv9Cb5SdBPIyMo4xZyQ0TOqGKU6IwcZHSFvqHmCK/PxpDuSOnc 5 | QuZFZoFYnWsGLOigHd8QMB0a0QQgOKr+Lb+xJFwWY55VgnzKcgnb9KaoXcq21ABm 6 | e6+0VEYP7sL6WjLHP68JsAiSXVKbCPdmxMp8ZgahJADtntBUvN57tCAaYcJaJGa8 7 | rFdl9pbbtITvGiTDrwPHzgV4Vh5RNdm6AIsW1wdD8zt6dmx9Q2N8aoUTzymIpdMO 8 | BdqqoIYpOpSFZiOe8fwCaJJvNRZgIn5i0BjvA7sh2OtXM+HymcJFcp58zbEkQmEN 9 | tKDmVaSVruslB+8pe5GXKIqifIjmEhgXDODA8+jP5Mo8tGW3LOY92Y2yEE3xRJc5 10 | 3dDm8b0lZIzak/+XlRS6xFEDZKsMSy53TTiadB4KHUgdsX6yu51IdAo/LFg51z9b 11 | NRXLL5E3nc3VUb0mQfrS3cODStiIL7CKWpLYbnoxuCtI3gdOV2ZwZ5vMkwh0Wk1I 12 | osm4jzzqSB5933Q52w+pIjyuM+sRUDIvTrDs/X39tpbLJGUNAvcOjm68xGTZgYcr 13 | YSOZqUHq6rgxq2o5ZeiuhisCAwEAAQ== 14 | -----END PUBLIC KEY----- 15 | -------------------------------------------------------------------------------- /tests/keys/parseca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFIjCCAwoCCQDSW4/JivGcBDANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJO 3 | WjEMMAoGA1UECAwDQUtMMREwDwYDVQQHDAhBdWNrbGFuZDERMA8GA1UECgwIcGFy 4 | c2VwaHAxEDAOBgNVBAMMB3BhcnNlY2EwHhcNMjMwNTEyMjA0MjQzWhcNMjYwMzAx 5 | MjA0MjQzWjBTMQswCQYDVQQGEwJOWjEMMAoGA1UECAwDQUtMMREwDwYDVQQHDAhB 6 | dWNrbGFuZDERMA8GA1UECgwIcGFyc2VwaHAxEDAOBgNVBAMMB3BhcnNlY2EwggIi 7 | MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDWBtWQ37CZfah2JMbiW2m8CuW8 8 | BSVJhVZzHY70kmBzGdIOK2uv89/Edf0YLkY3I3OL/19KBl8vNcg7g9wEwAx6u02b 9 | 7foX/OukoPYrT6iNIKwpa9OVzYe8+RwVlSkoX0bQ6IGqFCRbDFmrXRItElyvaX9/ 10 | fql1RRVjlA4AeCy8MFPW+OUtRsgCUaDTWUr2kTvn82YPcKtgCKaVlkxZ7aYYsYwj 11 | xV6AHBb5/HPTadi1hEsSMe3DAm51cNCpcSMWakzFRddOc9na13D3RskBuGm8GAec 12 | zSx38t7OLnTCKtI6PPxp0GWYrQSNtamvZM3i6fHMWHIyV6/siWWiJJipFmswjxWe 13 | Hp9mmc1UFrmcJG/mVmvOsJPqjHkRwm6Vxi1b86RizbQ6mw6e+cQGn35WmcwzEf/V 14 | uM3P382UXAaEjOB21ib9VsBPoJnwTollqDVOr6wLMn92sPTsNpLRPC9+mSDxemfj 15 | z7JC5U0w2OgS3dOs82UkNNuDmnE1VuCcG0bdFpk1vOEk1weo47UJ4QnopxILAbER 16 | c1eeoXZUhv1pAs3LbOR229DkCzijhWQgd+BoDUF3p/2M/528/a3W/vIcZMc9dx5B 17 | qzG5VLhRSPkxs9K5YGYN7G+7OcdhyKNY9LklbOfUitGhdufXAO62b6XRtrE0dfNh 18 | O1Z6yZHKGib5P+0TNQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBBeMaiptQT8teD 19 | Q63CW7eYc6skvq8qOfB51sqr2NPcxMdLr/6yjtc5Ln2y23YWTB6JpWST0CeRpew3 20 | TRAjf264xd6EHDiiYqYhsrvQEnyHOtY43UEXeyE6tEP+8djgqvydAx9G+ok9L+IE 21 | WlzeS5hegUaHcSQCRs5RdSKL9OvN/u7LNqDlcxTrz/cicNyjA5pQCAlbD/3cs/8+ 22 | tmKMYN+iriAm/xKxzVfJsH6qvZZny/mi/8CG+DWqosQAX/GhWHp32l/C30HvAcDf 23 | FF6r20h58PEfCHK2mQwPRzh2iNlKnCVYYcl5clYx7qPtpwKJyo7X/qeRkNOtnhtL 24 | KahXHhBfeixYNiNMBK2lUgZ1QD3/0ARJZIYsjo/bLu9uECXrqxXC/HC/YzqgJj+H 25 | AtPMCFTVxMDlPyMh6tFxgB6CV9SJ+/PKeXjvIaHKeHoSQtpKrgWk5ooh/Xxl2k1Y 26 | 4xGmvZQo1Rp0iIQSZ43sT8QQPw/BJSktxamhhn8ctk5sfOR9ctPk17Tru2tEMV2T 27 | xAtmduoluSlxDodnAoUi6zCB4nxYhHhCuD3lDuUnmIbYxGwQCbPiiLWGDl3/6XKP 28 | Fn76bLcFV3yqzwDtSwmIv7yxgF8WdA3F47BOXPqm7SxMtLYNEje65RgnwVHjq6ax 29 | jTTWuHUVY9pjF76qCHv3PcjIyAdgvQ== 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/keys/parseca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /tests/keys/parseca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFIjCCAwoCCQDSW4/JivGcBDANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJO 3 | WjEMMAoGA1UECAwDQUtMMREwDwYDVQQHDAhBdWNrbGFuZDERMA8GA1UECgwIcGFy 4 | c2VwaHAxEDAOBgNVBAMMB3BhcnNlY2EwHhcNMjMwNTEyMjA0MjQzWhcNMjYwMzAx 5 | MjA0MjQzWjBTMQswCQYDVQQGEwJOWjEMMAoGA1UECAwDQUtMMREwDwYDVQQHDAhB 6 | dWNrbGFuZDERMA8GA1UECgwIcGFyc2VwaHAxEDAOBgNVBAMMB3BhcnNlY2EwggIi 7 | MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDWBtWQ37CZfah2JMbiW2m8CuW8 8 | BSVJhVZzHY70kmBzGdIOK2uv89/Edf0YLkY3I3OL/19KBl8vNcg7g9wEwAx6u02b 9 | 7foX/OukoPYrT6iNIKwpa9OVzYe8+RwVlSkoX0bQ6IGqFCRbDFmrXRItElyvaX9/ 10 | fql1RRVjlA4AeCy8MFPW+OUtRsgCUaDTWUr2kTvn82YPcKtgCKaVlkxZ7aYYsYwj 11 | xV6AHBb5/HPTadi1hEsSMe3DAm51cNCpcSMWakzFRddOc9na13D3RskBuGm8GAec 12 | zSx38t7OLnTCKtI6PPxp0GWYrQSNtamvZM3i6fHMWHIyV6/siWWiJJipFmswjxWe 13 | Hp9mmc1UFrmcJG/mVmvOsJPqjHkRwm6Vxi1b86RizbQ6mw6e+cQGn35WmcwzEf/V 14 | uM3P382UXAaEjOB21ib9VsBPoJnwTollqDVOr6wLMn92sPTsNpLRPC9+mSDxemfj 15 | z7JC5U0w2OgS3dOs82UkNNuDmnE1VuCcG0bdFpk1vOEk1weo47UJ4QnopxILAbER 16 | c1eeoXZUhv1pAs3LbOR229DkCzijhWQgd+BoDUF3p/2M/528/a3W/vIcZMc9dx5B 17 | qzG5VLhRSPkxs9K5YGYN7G+7OcdhyKNY9LklbOfUitGhdufXAO62b6XRtrE0dfNh 18 | O1Z6yZHKGib5P+0TNQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBBeMaiptQT8teD 19 | Q63CW7eYc6skvq8qOfB51sqr2NPcxMdLr/6yjtc5Ln2y23YWTB6JpWST0CeRpew3 20 | TRAjf264xd6EHDiiYqYhsrvQEnyHOtY43UEXeyE6tEP+8djgqvydAx9G+ok9L+IE 21 | WlzeS5hegUaHcSQCRs5RdSKL9OvN/u7LNqDlcxTrz/cicNyjA5pQCAlbD/3cs/8+ 22 | tmKMYN+iriAm/xKxzVfJsH6qvZZny/mi/8CG+DWqosQAX/GhWHp32l/C30HvAcDf 23 | FF6r20h58PEfCHK2mQwPRzh2iNlKnCVYYcl5clYx7qPtpwKJyo7X/qeRkNOtnhtL 24 | KahXHhBfeixYNiNMBK2lUgZ1QD3/0ARJZIYsjo/bLu9uECXrqxXC/HC/YzqgJj+H 25 | AtPMCFTVxMDlPyMh6tFxgB6CV9SJ+/PKeXjvIaHKeHoSQtpKrgWk5ooh/Xxl2k1Y 26 | 4xGmvZQo1Rp0iIQSZ43sT8QQPw/BJSktxamhhn8ctk5sfOR9ctPk17Tru2tEMV2T 27 | xAtmduoluSlxDodnAoUi6zCB4nxYhHhCuD3lDuUnmIbYxGwQCbPiiLWGDl3/6XKP 28 | Fn76bLcFV3yqzwDtSwmIv7yxgF8WdA3F47BOXPqm7SxMtLYNEje65RgnwVHjq6ax 29 | jTTWuHUVY9pjF76qCHv3PcjIyAdgvQ== 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /tests/keys/parseca.srl: -------------------------------------------------------------------------------- 1 | C4C2841E9E0467CC 2 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------