├── .php-cs-fixer.dist.php ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── phpstan-baseline.php ├── phpstan.neon.dist └── src ├── AbstractApi.php ├── Apps.php ├── Config.php ├── Devices.php ├── Dto ├── AbstractDto.php ├── Filter │ ├── AbstractFilter.php │ ├── AmountSpentFilter.php │ ├── AppVersionFilter.php │ ├── BoughtSkuFilter.php │ ├── ConditionalFilter.php │ ├── CountryFilter.php │ ├── FirstSessionFilter.php │ ├── LanguageFilter.php │ ├── LastSessionFilter.php │ ├── LocationFilter.php │ ├── SessionCountFilter.php │ ├── SessionTimeFilter.php │ └── TagFilter.php └── Segment │ ├── CreateSegment.php │ └── ListSegments.php ├── Exception ├── BadMethodCallException.php ├── InvalidArgumentException.php ├── JsonException.php ├── OneSignalExceptionInterface.php └── UnsuccessfulResponse.php ├── Notifications.php ├── OneSignal.php ├── Resolver ├── AppOutcomesResolver.php ├── AppResolver.php ├── DeviceFocusResolver.php ├── DevicePurchaseResolver.php ├── DeviceResolver.php ├── DeviceSessionResolver.php ├── DeviceTagsResolver.php ├── NotificationHistoryResolver.php ├── NotificationResolver.php ├── ResolverFactory.php ├── ResolverInterface.php └── SegmentResolver.php ├── Response ├── AbstractResponse.php └── Segment │ ├── CreateSegmentResponse.php │ ├── DeleteSegmentResponse.php │ ├── ListSegmentsResponse.php │ └── Segment.php └── Segments.php /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | in([ 5 | __DIR__.'/src', 6 | __DIR__.'/tests', 7 | ]); 8 | 9 | return (new PhpCsFixer\Config()) 10 | ->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect()) 11 | ->setRiskyAllowed(true) 12 | ->setRules([ 13 | '@Symfony' => true, 14 | '@PHP70Migration' => true, 15 | '@PHP70Migration:risky' => true, 16 | '@PHP71Migration:risky' => true, 17 | '@PHP73Migration' => true, 18 | 'linebreak_after_opening_tag' => true, 19 | 'global_namespace_import' => [ 20 | 'import_classes' => true, 21 | 'import_constants' => true, 22 | 'import_functions' => true, 23 | ], 24 | 'ordered_imports' => [ 25 | 'imports_order' => ['class', 'const', 'function'], 26 | ], 27 | 'yoda_style' => false, 28 | 'static_lambda' => true, 29 | 'align_multiline_comment' => true, 30 | 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'], 31 | ]) 32 | ->setFinder($finder); 33 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Version 2.8.0 4 | 5 | - add external user id auth hash in device to enable identity verification 6 | - Allow Symfony 6 7 | 8 | ## Version 2.7.0 9 | 10 | - Additional SMS related fields (on the Notifications payload) 11 | 12 | ## Version 2.6.0 13 | 14 | - Add ability to update device IP address 15 | - add "name" to Notifications payload 16 | 17 | ## Version 2.5.0 18 | 19 | - Implement 'Edit tags with external user id' API endpoint 20 | - Implement View Outcomes endpoint 21 | 22 | ## Version 2.4.0 23 | 24 | - Add new device types 25 | 26 | ## Version 2.3.0 27 | 28 | - Add app_url option in NotificationResolver 29 | 30 | ## Version 2.2.0 31 | 32 | - Add channel_for_external_user_ids option in NotificationResolver 33 | 34 | ## Version 2.1.1 35 | 36 | - Allow to install on php 8.0 37 | 38 | ## Version 2.1.0 39 | 40 | - Add "kind" argument to notifications getAll method 41 | 42 | ## Version 2.0.3 43 | 44 | - Add field "include_email_tokens" 45 | 46 | ## Version 2.0.2 47 | 48 | - Add field "apns_push_type_override" 49 | 50 | ## Version 2.0.1 51 | 52 | - Add missed chrome_web_badge option to NotificationResolver 53 | - Add create/update segments and notification history examples to readme 54 | 55 | ## Version 2.0.0 56 | 57 | - At least PHP 7.3 version is now required. 58 | - `OneSignal\OneSignal` client now requires always to provide `OneSignal\Config`. 59 | - `OneSignal\OneSignal` client now expects `Psr\Http\Client\ClientInterface` as a second arguments instead of `Http\Client\Common\HttpMethodsClient` and is mandatory. 60 | - `OneSignal\OneSignal` client now requires always to provide `Psr\Http\Message\RequestFactoryInterface` as a third argument and `Psr\Http\Message\StreamFactoryInterface` as a fourth argument. 61 | - Replaced magic __get method with __call in `OneSignal\OneSignal`, so from now calls like 62 | `$oneSignal->apps` must be used as `$oneSignal->apps()`. It is better to use Dependency injection, because these calls will construct new instances. 63 | - Removed `OneSignal\Exception\OneSignalException` and added `OneSignal\Exception\OneSignalExceptionInterface`. 64 | - Removed `setConfig` and `setClient` methods in `OneSignal\OneSignal`. You can build new instances with different configs or http clients. 65 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-present Tomas Norkūnas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OneSignal API for PHP 2 | 3 | [![Latest Stable Version](https://img.shields.io/packagist/v/norkunas/onesignal-php-api.svg?color=%23039be5)](https://packagist.org/packages/norkunas/onesignal-php-api) 4 | [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/norkunas/onesignal-php-api.svg?color=%23039be5)](https://scrutinizer-ci.com/g/norkunas/onesignal-php-api) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/norkunas/onesignal-php-api.svg?color=%23039be5)](https://packagist.org/packages/norkunas/onesignal-php-api/stats) 6 | ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/norkunas/onesignal-php-api/ci.yml?branch=master&color=%23039be5) 7 | [![Software License](https://img.shields.io/github/license/norkunas/onesignal-php-api?color=%23039be5)](LICENSE) 8 | 9 | ## Install 10 | 11 | Note: All examples are for v2, if you are using PHP <7.3 please read [v1 documentation](https://github.com/norkunas/onesignal-php-api/blob/1.0/README.md). 12 | 13 | This packages requires a PSR-18 HTTP client and PSR-17 HTTP factories to work. You can choose any from 14 | [psr/http-client-implementation](https://packagist.org/providers/psr/http-client-implementation) 15 | and [psr/http-factory-implementation](https://packagist.org/providers/psr/http-factory-implementation) 16 | 17 | Example with Symfony HttpClient and nyholm/psr7 http factories, install it with [Composer](https://getcomposer.org/): 18 | 19 | ``` 20 | composer require symfony/http-client nyholm/psr7 norkunas/onesignal-php-api 21 | ``` 22 | 23 | And now configure the OneSignal api client: 24 | 25 | ```php 26 | apps()->getAll(); 52 | ``` 53 | 54 | View the details of a single OneSignal application ([official documentation](https://documentation.onesignal.com/reference#view-an-app)): 55 | 56 | ```php 57 | $myApp = $oneSignal->apps()->getOne('application_id'); 58 | ``` 59 | 60 | Create a new OneSignal app ([official documentation](https://documentation.onesignal.com/reference#create-an-app)): 61 | 62 | ```php 63 | $newApp = $oneSignal->apps()->add([ 64 | 'name' => 'app name', 65 | 'gcm_key' => 'key' 66 | ]); 67 | ``` 68 | 69 | Update the name or configuration settings of OneSignal application ([official documentation](https://documentation.onesignal.com/reference#update-an-app)): 70 | 71 | ```php 72 | $oneSignal->apps()->update('application_id', [ 73 | 'name' => 'new app name' 74 | ]); 75 | ``` 76 | 77 | Create Segments ([official documentation](https://documentation.onesignal.com/reference#create-segments)): 78 | 79 | ```php 80 | $oneSignal->apps()->createSegment('application_id', [ 81 | 'name' => 'Segment Name', 82 | 'filters' => [ 83 | ['field' => 'session_count', 'relation' => '>', 'value' => 1], 84 | ['operator' => 'AND'], 85 | ['field' => 'tag', 'relation' => '!=', 'key' => 'tag_key', 'value' => '1'], 86 | ['operator' => 'OR'], 87 | ['field' => 'last_session', 'relation' => '<', 'value' => '30,'], 88 | ], 89 | ]); 90 | ``` 91 | 92 | Delete Segments ([official documentation](https://documentation.onesignal.com/reference#delete-segments)): 93 | 94 | ```php 95 | $oneSignal->apps()->deleteSegment('application_id', 'segment_id'); 96 | ``` 97 | 98 | View the details of all the outcomes associated with your app ([official documentation](https://documentation.onesignal.com/reference/view-outcomes)): 99 | 100 | ```php 101 | use OneSignal\Apps; 102 | use OneSignal\Devices; 103 | 104 | $outcomes = $oneSignal->apps()->outcomes('application_id', [ 105 | 'outcome_names' => [ 106 | 'os__session_duration.count', 107 | 'os__click.count', 108 | 'Sales, Purchase.sum', 109 | ], 110 | 'outcome_time_range' => Apps::OUTCOME_TIME_RANGE_MONTH, 111 | 'outcome_platforms' => [Devices::IOS, Devices::ANDROID], 112 | 'outcome_attribution' => Apps::OUTCOME_ATTRIBUTION_DIRECT, 113 | ]); 114 | ``` 115 | 116 | ### Devices API 117 | 118 | View the details of multiple devices in one of your OneSignal apps ([official documentation](https://documentation.onesignal.com/reference#view-devices)): 119 | 120 | ```php 121 | $devices = $oneSignal->devices()->getAll(); 122 | ``` 123 | 124 | View the details of an existing device in your configured OneSignal application ([official documentation](https://documentation.onesignal.com/reference#view-device)): 125 | 126 | ```php 127 | $device = $oneSignal->devices()->getOne('device_id'); 128 | ``` 129 | 130 | Register a new device to your configured OneSignal application ([official documentation](https://documentation.onesignal.com/reference#add-a-device)): 131 | 132 | ```php 133 | use OneSignal\Devices; 134 | 135 | $newDevice = $oneSignal->devices()->add([ 136 | 'device_type' => Devices::ANDROID, 137 | 'identifier' => 'abcdefghijklmn', 138 | ]); 139 | ``` 140 | 141 | Update an existing device in your configured OneSignal application ([official documentation](https://documentation.onesignal.com/reference#edit-device)): 142 | 143 | ```php 144 | $oneSignal->devices()->update('device_id', [ 145 | 'session_count' => 2, 146 | 'ip' => '127.0.0.1', // Optional. New IP Address of your device 147 | ]); 148 | ``` 149 | 150 | Update an existing device's tags in one of your OneSignal apps using the External User ID ([official documentation](https://documentation.onesignal.com/reference/edit-tags-with-external-user-id)): 151 | 152 | ```php 153 | $externalUserId = '12345'; 154 | $response = $oneSignal->devices()->editTags($externalUserId, [ 155 | 'tags' => [ 156 | 'a' => '1', 157 | 'foo' => '', 158 | ], 159 | ]); 160 | ``` 161 | 162 | ### Notifications API 163 | 164 | View the details of multiple notifications ([official documentation](https://documentation.onesignal.com/reference#view-notifications)): 165 | 166 | ```php 167 | $notifications = $oneSignal->notifications()->getAll(); 168 | ``` 169 | 170 | Get the details of a single notification ([official documentation](https://documentation.onesignal.com/reference#view-notification)): 171 | 172 | ```php 173 | $notification = $oneSignal->notifications()->getOne('notification_id'); 174 | ``` 175 | 176 | Create and send notifications or emails to a segment or individual users. 177 | You may target users in one of three ways using this method: by Segment, by 178 | Filter, or by Device (at least one targeting parameter must be specified) ([official documentation](https://documentation.onesignal.com/reference#create-notification)): 179 | 180 | ```php 181 | $oneSignal->notifications()->add([ 182 | 'contents' => [ 183 | 'en' => 'Notification message' 184 | ], 185 | 'included_segments' => ['All'], 186 | 'data' => ['foo' => 'bar'], 187 | 'isChrome' => true, 188 | 'send_after' => new \DateTime('1 hour'), 189 | 'filters' => [ 190 | [ 191 | 'field' => 'tag', 192 | 'key' => 'is_vip', 193 | 'relation' => '!=', 194 | 'value' => 'true', 195 | ], 196 | [ 197 | 'operator' => 'OR', 198 | ], 199 | [ 200 | 'field' => 'tag', 201 | 'key' => 'is_admin', 202 | 'relation' => '=', 203 | 'value' => 'true', 204 | ], 205 | ], 206 | // ..other options 207 | ]); 208 | ``` 209 | 210 | Mark notification as opened ([official documentation](https://documentation.onesignal.com/reference#track-open)): 211 | 212 | ```php 213 | $oneSignal->notifications()->open('notification_id'); 214 | ``` 215 | 216 | Stop a scheduled or currently outgoing notification ([official documentation](https://documentation.onesignal.com/reference#cancel-notification)): 217 | 218 | ```php 219 | $oneSignal->notifications()->cancel('notification_id'); 220 | ``` 221 | 222 | Notification History ([official documentation](https://documentation.onesignal.com/reference#notification-history)): 223 | 224 | ```php 225 | $oneSignal->notifications()->history('notification_id', [ 226 | 'events' => 'clicked', // or 'sent' 227 | 'email' => 'your_email@email.com', 228 | ]); 229 | ``` 230 | 231 | ## Questions? 232 | 233 | If you have any questions please [open a discussion](https://github.com/norkunas/onesignal-php-api/discussions/new). 234 | 235 | ## License 236 | 237 | This library is released under the MIT License. See the bundled [LICENSE](https://github.com/norkunas/onesignal-php-api/blob/master/LICENSE) file for details. 238 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "norkunas/onesignal-php-api", 3 | "description": "OneSignal API for PHP", 4 | "keywords": ["onesignal", "api", "notifications", "push", "apns", "gcm"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Tomas Norkūnas", 9 | "email": "norkunas.tom@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=7.4", 14 | "ext-json": "*", 15 | "psr/http-client": "^1.0", 16 | "psr/http-client-implementation": "^1.0", 17 | "psr/http-factory": "^1.0", 18 | "psr/http-factory-implementation": "^1.0", 19 | "symfony/deprecation-contracts": "^2.1|^3.0", 20 | "symfony/options-resolver": "^4.4|^5.0|^6.0|^7.0" 21 | }, 22 | "require-dev": { 23 | "nyholm/psr7": "^1.8.1", 24 | "php-cs-fixer/shim": "^v3.64.0", 25 | "phpstan/phpstan": "^1.12.0", 26 | "phpstan/phpstan-phpunit": "^1.4.0", 27 | "symfony/http-client": "^5.0|^6.2|^7.0", 28 | "symfony/phpunit-bridge": "^5.3|^6.2|^7.0" 29 | }, 30 | "autoload": { 31 | "psr-4": { "OneSignal\\": "src/" } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "OneSignal\\Tests\\": "tests/" 36 | } 37 | }, 38 | "extra": { 39 | "branch-alias": { 40 | "dev-master": "2.0-dev" 41 | } 42 | }, 43 | "config": { 44 | "sort-packages": true 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /phpstan-baseline.php: -------------------------------------------------------------------------------- 1 | '#^Method OneSignal\\\\Resolver\\\\NotificationResolver\\:\\:filterAndroidBackgroundLayout\\(\\) has parameter \\$layouts with no value type specified in iterable type array\\.$#', 7 | 'count' => 1, 8 | 'path' => __DIR__ . '/src/Resolver/NotificationResolver.php', 9 | ]; 10 | $ignoreErrors[] = [ 11 | // identifier: missingType.iterableValue 12 | 'message' => '#^Method OneSignal\\\\Resolver\\\\NotificationResolver\\:\\:filterIosAttachments\\(\\) has parameter \\$attachments with no value type specified in iterable type array\\.$#', 13 | 'count' => 1, 14 | 'path' => __DIR__ . '/src/Resolver/NotificationResolver.php', 15 | ]; 16 | $ignoreErrors[] = [ 17 | // identifier: missingType.iterableValue 18 | 'message' => '#^Method OneSignal\\\\Resolver\\\\NotificationResolver\\:\\:filterWebButtons\\(\\) has parameter \\$buttons with no value type specified in iterable type array\\.$#', 19 | 'count' => 1, 20 | 'path' => __DIR__ . '/src/Resolver/NotificationResolver.php', 21 | ]; 22 | $ignoreErrors[] = [ 23 | // identifier: missingType.iterableValue 24 | 'message' => '#^Method OneSignal\\\\Resolver\\\\NotificationResolver\\:\\:normalizeButtons\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', 25 | 'count' => 1, 26 | 'path' => __DIR__ . '/src/Resolver/NotificationResolver.php', 27 | ]; 28 | $ignoreErrors[] = [ 29 | // identifier: missingType.iterableValue 30 | 'message' => '#^Method OneSignal\\\\Resolver\\\\NotificationResolver\\:\\:normalizeButtons\\(\\) return type has no value type specified in iterable type array\\.$#', 31 | 'count' => 1, 32 | 'path' => __DIR__ . '/src/Resolver/NotificationResolver.php', 33 | ]; 34 | $ignoreErrors[] = [ 35 | // identifier: missingType.iterableValue 36 | 'message' => '#^Method OneSignal\\\\Resolver\\\\NotificationResolver\\:\\:normalizeFilters\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', 37 | 'count' => 1, 38 | 'path' => __DIR__ . '/src/Resolver/NotificationResolver.php', 39 | ]; 40 | $ignoreErrors[] = [ 41 | // identifier: missingType.iterableValue 42 | 'message' => '#^Method OneSignal\\\\Resolver\\\\NotificationResolver\\:\\:normalizeFilters\\(\\) return type has no value type specified in iterable type array\\.$#', 43 | 'count' => 1, 44 | 'path' => __DIR__ . '/src/Resolver/NotificationResolver.php', 45 | ]; 46 | $ignoreErrors[] = [ 47 | // identifier: missingType.iterableValue 48 | 'message' => '#^Method OneSignal\\\\Resolver\\\\SegmentResolver\\:\\:normalizeFilters\\(\\) has parameter \\$values with no value type specified in iterable type array\\.$#', 49 | 'count' => 1, 50 | 'path' => __DIR__ . '/src/Resolver/SegmentResolver.php', 51 | ]; 52 | $ignoreErrors[] = [ 53 | // identifier: missingType.iterableValue 54 | 'message' => '#^Method OneSignal\\\\Resolver\\\\SegmentResolver\\:\\:normalizeFilters\\(\\) return type has no value type specified in iterable type array\\.$#', 55 | 'count' => 1, 56 | 'path' => __DIR__ . '/src/Resolver/SegmentResolver.php', 57 | ]; 58 | $ignoreErrors[] = [ 59 | // identifier: argument.type 60 | 'message' => '#^Parameter \\#1 \\$success of class OneSignal\\\\Response\\\\Segment\\\\CreateSegmentResponse constructor expects bool, mixed given\\.$#', 61 | 'count' => 1, 62 | 'path' => __DIR__ . '/src/Response/Segment/CreateSegmentResponse.php', 63 | ]; 64 | $ignoreErrors[] = [ 65 | // identifier: argument.type 66 | 'message' => '#^Parameter \\#2 \\$id of class OneSignal\\\\Response\\\\Segment\\\\CreateSegmentResponse constructor expects non\\-empty\\-string, mixed given\\.$#', 67 | 'count' => 1, 68 | 'path' => __DIR__ . '/src/Response/Segment/CreateSegmentResponse.php', 69 | ]; 70 | $ignoreErrors[] = [ 71 | // identifier: argument.type 72 | 'message' => '#^Parameter \\#1 \\$success of class OneSignal\\\\Response\\\\Segment\\\\DeleteSegmentResponse constructor expects bool, mixed given\\.$#', 73 | 'count' => 1, 74 | 'path' => __DIR__ . '/src/Response/Segment/DeleteSegmentResponse.php', 75 | ]; 76 | $ignoreErrors[] = [ 77 | // identifier: argument.type 78 | 'message' => '#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(mixed\\)\\: mixed\\)\\|null, Closure\\(array\\)\\: OneSignal\\\\Response\\\\Segment\\\\Segment given\\.$#', 79 | 'count' => 1, 80 | 'path' => __DIR__ . '/src/Response/Segment/ListSegmentsResponse.php', 81 | ]; 82 | $ignoreErrors[] = [ 83 | // identifier: argument.type 84 | 'message' => '#^Parameter \\#1 \\$totalCount of class OneSignal\\\\Response\\\\Segment\\\\ListSegmentsResponse constructor expects int\\<0, max\\>, mixed given\\.$#', 85 | 'count' => 1, 86 | 'path' => __DIR__ . '/src/Response/Segment/ListSegmentsResponse.php', 87 | ]; 88 | $ignoreErrors[] = [ 89 | // identifier: argument.type 90 | 'message' => '#^Parameter \\#2 \\$array of function array_map expects array, mixed given\\.$#', 91 | 'count' => 1, 92 | 'path' => __DIR__ . '/src/Response/Segment/ListSegmentsResponse.php', 93 | ]; 94 | $ignoreErrors[] = [ 95 | // identifier: argument.type 96 | 'message' => '#^Parameter \\#2 \\$offset of class OneSignal\\\\Response\\\\Segment\\\\ListSegmentsResponse constructor expects int\\<0, 2147483648\\>, mixed given\\.$#', 97 | 'count' => 1, 98 | 'path' => __DIR__ . '/src/Response/Segment/ListSegmentsResponse.php', 99 | ]; 100 | $ignoreErrors[] = [ 101 | // identifier: argument.type 102 | 'message' => '#^Parameter \\#3 \\$limit of class OneSignal\\\\Response\\\\Segment\\\\ListSegmentsResponse constructor expects int\\<0, 2147483648\\>, mixed given\\.$#', 103 | 'count' => 1, 104 | 'path' => __DIR__ . '/src/Response/Segment/ListSegmentsResponse.php', 105 | ]; 106 | $ignoreErrors[] = [ 107 | // identifier: argument.type 108 | 'message' => '#^Parameter \\#4 \\$segments of class OneSignal\\\\Response\\\\Segment\\\\ListSegmentsResponse constructor expects list\\, array\\ given\\.$#', 109 | 'count' => 1, 110 | 'path' => __DIR__ . '/src/Response/Segment/ListSegmentsResponse.php', 111 | ]; 112 | 113 | return ['parameters' => ['ignoreErrors' => $ignoreErrors]]; 114 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - vendor/phpstan/phpstan-phpunit/extension.neon 3 | - vendor/phpstan/phpstan/conf/bleedingEdge.neon 4 | - phpstan-baseline.php 5 | 6 | parameters: 7 | bootstrapFiles: 8 | - vendor/bin/.phpunit/phpunit/vendor/autoload.php 9 | paths: 10 | - src/ 11 | - tests/ 12 | tmpDir: %currentWorkingDirectory%/.phpstan 13 | level: 9 14 | inferPrivatePropertyTypeFromConstructor: true 15 | checkGenericClassInNonGenericObjectType: true 16 | checkUninitializedProperties: true 17 | ignoreErrors: 18 | - 19 | path: %currentWorkingDirectory%/src/Notifications.php 20 | message: '#PHPDoc tag @param references unknown parameter: \$kind#' 21 | -------------------------------------------------------------------------------- /src/AbstractApi.php: -------------------------------------------------------------------------------- 1 | client = $client; 24 | } 25 | 26 | protected function createRequest(string $method, string $uri): RequestInterface 27 | { 28 | $request = $this->client->getRequestFactory()->createRequest($method, OneSignal::API_URL.$uri); 29 | $request = $request->withHeader('Accept', 'application/json'); 30 | 31 | return $request; 32 | } 33 | 34 | /** 35 | * @param mixed $value String content with which to populate the stream 36 | * 37 | * @phpstan-param int<1, max> $maxDepth 38 | */ 39 | protected function createStream($value, ?int $flags = null, int $maxDepth = 512): StreamInterface 40 | { 41 | $flags = $flags ?? (JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_PRESERVE_ZERO_FRACTION); 42 | 43 | try { 44 | $value = json_encode($value, $flags | JSON_THROW_ON_ERROR, $maxDepth); 45 | } catch (JsonException $e) { 46 | throw new InvalidArgumentException("Invalid value for json encoding: {$e->getMessage()}."); 47 | } 48 | 49 | return $this->client->getStreamFactory()->createStream($value); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Apps.php: -------------------------------------------------------------------------------- 1 | resolverFactory = $resolverFactory; 27 | } 28 | 29 | /** 30 | * Get information about application with provided ID. 31 | * 32 | * User authentication key must be set. 33 | * 34 | * @param string $id ID of your application 35 | * 36 | * @return array 37 | */ 38 | public function getOne(string $id): array 39 | { 40 | $request = $this->createRequest('GET', "/apps/$id"); 41 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getUserAuthKey()}"); 42 | 43 | return $this->client->sendRequest($request); 44 | } 45 | 46 | /** 47 | * Get information about all your created applications. 48 | * 49 | * User authentication key must be set. 50 | * 51 | * @return array 52 | */ 53 | public function getAll(): array 54 | { 55 | $request = $this->createRequest('GET', '/apps'); 56 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getUserAuthKey()}"); 57 | 58 | return $this->client->sendRequest($request); 59 | } 60 | 61 | /** 62 | * Create a new application with provided data. 63 | * 64 | * User authentication key must be set. 65 | * 66 | * @param array $data Application data 67 | * 68 | * @return array 69 | */ 70 | public function add(array $data): array 71 | { 72 | $resolvedData = $this->resolverFactory->createAppResolver()->resolve($data); 73 | 74 | $request = $this->createRequest('POST', '/apps'); 75 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getUserAuthKey()}"); 76 | $request = $request->withHeader('Content-Type', 'application/json'); 77 | $request = $request->withBody($this->createStream($resolvedData)); 78 | 79 | return $this->client->sendRequest($request); 80 | } 81 | 82 | /** 83 | * Update application with provided data. 84 | * 85 | * User authentication key must be set. 86 | * 87 | * @param string $id ID of your application 88 | * @param array $data New application data 89 | * 90 | * @return array 91 | */ 92 | public function update(string $id, array $data): array 93 | { 94 | $resolvedData = $this->resolverFactory->createAppResolver()->resolve($data); 95 | 96 | $request = $this->createRequest('PUT', "/apps/$id"); 97 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getUserAuthKey()}"); 98 | $request = $request->withHeader('Content-Type', 'application/json'); 99 | $request = $request->withBody($this->createStream($resolvedData)); 100 | 101 | return $this->client->sendRequest($request); 102 | } 103 | 104 | /** 105 | * Create a new segment for application with provided data. 106 | * 107 | * @param string $appId ID of your application 108 | * @param array $data Segment Data 109 | * 110 | * @return array 111 | */ 112 | public function createSegment(string $appId, array $data): array 113 | { 114 | $resolvedData = $this->resolverFactory->createSegmentResolver()->resolve($data); 115 | 116 | $request = $this->createRequest('POST', "/apps/$appId/segments"); 117 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 118 | $request = $request->withHeader('Content-Type', 'application/json'); 119 | $request = $request->withBody($this->createStream($resolvedData)); 120 | 121 | return $this->client->sendRequest($request); 122 | } 123 | 124 | /** 125 | * Delete existing segment from your application. 126 | * 127 | * Application auth key must be set. 128 | * 129 | * @param string $appId Application ID 130 | * @param string $segmentId Segment ID 131 | * 132 | * @return array 133 | */ 134 | public function deleteSegment(string $appId, string $segmentId): array 135 | { 136 | $request = $this->createRequest('DELETE', "/apps/$appId/segments/$segmentId"); 137 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 138 | 139 | return $this->client->sendRequest($request); 140 | } 141 | 142 | /** 143 | * View the details of all the outcomes associated with your app. 144 | * 145 | * @param string $appId Application ID 146 | * @param array $data Outcome data filters 147 | * 148 | * @return array 149 | */ 150 | public function outcomes(string $appId, array $data): array 151 | { 152 | $resolvedData = $this->resolverFactory->createOutcomesResolver()->resolve($data); 153 | 154 | $queryString = preg_replace('/%5B\d+%5D/', '%5B%5D', http_build_query($resolvedData)); 155 | 156 | $request = $this->createRequest('GET', "/apps/$appId/outcomes?$queryString"); 157 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 158 | 159 | return $this->client->sendRequest($request); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | applicationId = $applicationId; 32 | $this->applicationAuthKey = $applicationAuthKey; 33 | $this->userAuthKey = $userAuthKey; 34 | } 35 | 36 | /** 37 | * Get OneSignal application id. 38 | * 39 | * @return non-empty-string 40 | */ 41 | public function getApplicationId(): string 42 | { 43 | return $this->applicationId; 44 | } 45 | 46 | /** 47 | * Get OneSignal application authentication key. 48 | * 49 | * @return non-empty-string 50 | */ 51 | public function getApplicationAuthKey(): string 52 | { 53 | return $this->applicationAuthKey; 54 | } 55 | 56 | /** 57 | * Get user authentication key. 58 | * 59 | * @return non-empty-string|null 60 | */ 61 | public function getUserAuthKey(): ?string 62 | { 63 | return $this->userAuthKey; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Devices.php: -------------------------------------------------------------------------------- 1 | resolverFactory = $resolverFactory; 34 | } 35 | 36 | /** 37 | * Get information about device with provided ID. 38 | * 39 | * @param non-empty-string $id Device ID 40 | * 41 | * @return array 42 | */ 43 | public function getOne(string $id): array 44 | { 45 | $request = $this->createRequest('GET', "/players/$id?app_id={$this->client->getConfig()->getApplicationId()}"); 46 | 47 | return $this->client->sendRequest($request); 48 | } 49 | 50 | /** 51 | * Get information about all registered devices for your application. 52 | * 53 | * Application auth key must be set. 54 | * 55 | * @param int<0, 300>|null $limit How many devices to return. Max is 300. Default is 300 56 | * @param int|null $offset Result offset. Default is 0. Results are sorted by id 57 | * 58 | * @return array 59 | */ 60 | public function getAll(?int $limit = null, ?int $offset = null): array 61 | { 62 | $query = ['app_id' => $this->client->getConfig()->getApplicationId()]; 63 | 64 | if ($limit !== null) { 65 | $query['limit'] = $limit; 66 | } 67 | 68 | if ($offset !== null) { 69 | $query['offset'] = $offset; 70 | } 71 | 72 | $request = $this->createRequest('GET', '/players?'.http_build_query($query)); 73 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 74 | 75 | return $this->client->sendRequest($request); 76 | } 77 | 78 | /** 79 | * Register a device for your application. 80 | * 81 | * @param array $data Device data 82 | * 83 | * @return array 84 | */ 85 | public function add(array $data): array 86 | { 87 | $resolvedData = $this->resolverFactory->createNewDeviceResolver()->resolve($data); 88 | 89 | $request = $this->createRequest('POST', '/players'); 90 | $request = $request->withHeader('Content-Type', 'application/json'); 91 | $request = $request->withBody($this->createStream($resolvedData)); 92 | 93 | return $this->client->sendRequest($request); 94 | } 95 | 96 | /** 97 | * Update existing registered device for your application with provided data. 98 | * 99 | * @param non-empty-string $id Device ID 100 | * @param array $data New device data 101 | * 102 | * @return array 103 | */ 104 | public function update(string $id, array $data): array 105 | { 106 | $resolvedData = $this->resolverFactory->createExistingDeviceResolver()->resolve($data); 107 | 108 | $request = $this->createRequest('PUT', "/players/$id"); 109 | $request = $request->withHeader('Content-Type', 'application/json'); 110 | $request = $request->withBody($this->createStream($resolvedData)); 111 | 112 | return $this->client->sendRequest($request); 113 | } 114 | 115 | /** 116 | * Delete existing registered device from your application. 117 | * 118 | * OneSignal supports DELETE on the players API endpoint which is not documented in their official documentation 119 | * Reference: https://documentation.onesignal.com/docs/handling-personal-data#section-deleting-users-or-other-data-from-onesignal 120 | * 121 | * Application auth key must be set. 122 | * 123 | * @param non-empty-string $id Device ID 124 | * 125 | * @return array 126 | */ 127 | public function delete(string $id): array 128 | { 129 | $request = $this->createRequest('DELETE', "/players/$id?app_id={$this->client->getConfig()->getApplicationId()}"); 130 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 131 | 132 | return $this->client->sendRequest($request); 133 | } 134 | 135 | /** 136 | * Call on new device session in your app. 137 | * 138 | * @param non-empty-string $id Device ID 139 | * @param array $data Device data 140 | * 141 | * @return array 142 | */ 143 | public function onSession(string $id, array $data): array 144 | { 145 | $resolvedData = $this->resolverFactory->createDeviceSessionResolver()->resolve($data); 146 | 147 | $request = $this->createRequest('POST', "/players/$id/on_session"); 148 | $request = $request->withHeader('Content-Type', 'application/json'); 149 | $request = $request->withBody($this->createStream($resolvedData)); 150 | 151 | return $this->client->sendRequest($request); 152 | } 153 | 154 | /** 155 | * Track a new purchase. 156 | * 157 | * @param non-empty-string $id Device ID 158 | * @param array $data Device data 159 | * 160 | * @return array 161 | */ 162 | public function onPurchase(string $id, array $data): array 163 | { 164 | $resolvedData = $this->resolverFactory->createDevicePurchaseResolver()->resolve($data); 165 | 166 | $request = $this->createRequest('POST', "/players/$id/on_purchase"); 167 | $request = $request->withHeader('Content-Type', 'application/json'); 168 | $request = $request->withBody($this->createStream($resolvedData)); 169 | 170 | return $this->client->sendRequest($request); 171 | } 172 | 173 | /** 174 | * Increment the device's total session length. 175 | * 176 | * @param non-empty-string $id Device ID 177 | * @param array $data Device data 178 | * 179 | * @return array 180 | */ 181 | public function onFocus(string $id, array $data): array 182 | { 183 | $resolvedData = $this->resolverFactory->createDeviceFocusResolver()->resolve($data); 184 | 185 | $request = $this->createRequest('POST', "/players/$id/on_focus"); 186 | $request = $request->withHeader('Content-Type', 'application/json'); 187 | $request = $request->withBody($this->createStream($resolvedData)); 188 | 189 | return $this->client->sendRequest($request); 190 | } 191 | 192 | /** 193 | * Export all information about devices in a CSV format for your application. 194 | * 195 | * Application auth key must be set. 196 | * 197 | * @param array $extraFields Additional fields that you wish to include. 198 | * Currently supports: "location", "country", "rooted" 199 | * @param non-empty-string|null $segmentName A segment name to filter the scv export by. 200 | * Only devices from that segment will make it into the export 201 | * @param int|null $lastActiveSince An epoch to filter results to users active after this time 202 | * 203 | * @return array 204 | */ 205 | public function csvExport(array $extraFields = [], ?string $segmentName = null, ?int $lastActiveSince = null): array 206 | { 207 | $request = $this->createRequest('POST', "/players/csv_export?app_id={$this->client->getConfig()->getApplicationId()}"); 208 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 209 | $request = $request->withHeader('Content-Type', 'application/json'); 210 | 211 | $body = ['extra_fields' => $extraFields]; 212 | 213 | if ($segmentName !== null) { 214 | $body['segment_name'] = $segmentName; 215 | } 216 | 217 | if ($lastActiveSince !== null) { 218 | $body['last_active_since'] = (string) $lastActiveSince; 219 | } 220 | 221 | $request = $request->withBody($this->createStream($body)); 222 | 223 | return $this->client->sendRequest($request); 224 | } 225 | 226 | /** 227 | * Update an existing device's tags using the External User ID. 228 | * 229 | * @param non-empty-string $externalUserId External User ID 230 | * @param array $data Tags data 231 | * 232 | * @return array 233 | */ 234 | public function editTags(string $externalUserId, array $data): array 235 | { 236 | $resolvedData = $this->resolverFactory->createDeviceTagsResolver()->resolve($data); 237 | 238 | $request = $this->createRequest('PUT', "/apps/{$this->client->getConfig()->getApplicationId()}/users/$externalUserId"); 239 | $request = $request->withHeader('Content-Type', 'application/json'); 240 | $request = $request->withBody($this->createStream($resolvedData)); 241 | 242 | return $this->client->sendRequest($request); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/Dto/AbstractDto.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | public function toArray(): array; 13 | } 14 | -------------------------------------------------------------------------------- /src/Dto/Filter/AbstractFilter.php: -------------------------------------------------------------------------------- 1 | '; 12 | 13 | public const LT = '<'; 14 | 15 | public const EQ = '='; 16 | 17 | public const NEQ = '!='; 18 | } 19 | -------------------------------------------------------------------------------- /src/Dto/Filter/AmountSpentFilter.php: -------------------------------------------------------------------------------- 1 | '; 10 | 11 | public const LT = '<'; 12 | 13 | public const EQ = '='; 14 | 15 | /** 16 | * @var self::GT|self::LT|self::EQ 17 | */ 18 | protected string $relation; 19 | 20 | /** 21 | * @var int|float 22 | */ 23 | protected $value; 24 | 25 | /** 26 | * @param self::GT|self::LT|self::EQ $relation 27 | * @param int|float $value 28 | */ 29 | public function __construct(string $relation, $value) 30 | { 31 | $this->relation = $relation; 32 | $this->value = $value; 33 | } 34 | 35 | /** 36 | * @return array{ 37 | * field: 'amount_spent', 38 | * relation: self::GT|self::LT|self::EQ, 39 | * value: int|float 40 | * } 41 | */ 42 | public function toArray(): array 43 | { 44 | return [ 45 | 'field' => 'amount_spent', 46 | 'relation' => $this->relation, 47 | 'value' => $this->value, 48 | ]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Dto/Filter/AppVersionFilter.php: -------------------------------------------------------------------------------- 1 | relation = $relation; 22 | $this->value = $value; 23 | } 24 | 25 | /** 26 | * @return array{ 27 | * field: 'app_version', 28 | * relation: self::GT|self::LT|self::EQ|self::NEQ, 29 | * value: string 30 | * } 31 | */ 32 | public function toArray(): array 33 | { 34 | return [ 35 | 'field' => 'app_version', 36 | 'relation' => $this->relation, 37 | 'value' => $this->value, 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Dto/Filter/BoughtSkuFilter.php: -------------------------------------------------------------------------------- 1 | relation = $relation; 28 | $this->key = $key; 29 | $this->value = $value; 30 | } 31 | 32 | /** 33 | * @return array{ 34 | * field: 'bought_sku', 35 | * key: string, 36 | * value: int|float 37 | * } 38 | */ 39 | public function toArray(): array 40 | { 41 | return [ 42 | 'field' => 'bought_sku', 43 | 'relation' => $this->relation, 44 | 'key' => $this->key, 45 | 'value' => $this->value, 46 | ]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Dto/Filter/ConditionalFilter.php: -------------------------------------------------------------------------------- 1 | operator = $operator; 24 | } 25 | 26 | /** 27 | * @param self::AND|self::OR $operator 28 | */ 29 | public function setOperator(string $operator): self 30 | { 31 | $this->operator = $operator; 32 | 33 | return $this; 34 | } 35 | 36 | /** 37 | * @return array{ 38 | * operator: self::AND|self::OR 39 | * } 40 | */ 41 | public function toArray(): array 42 | { 43 | return [ 44 | 'operator' => $this->operator, 45 | ]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Dto/Filter/CountryFilter.php: -------------------------------------------------------------------------------- 1 | value = $value; 14 | } 15 | 16 | /** 17 | * @return array{ 18 | * field: 'country', 19 | * relation: self::EQ, 20 | * value: string 21 | * } 22 | */ 23 | public function toArray(): array 24 | { 25 | return [ 26 | 'field' => 'country', 27 | 'relation' => self::EQ, 28 | 'value' => $this->value, 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Dto/Filter/FirstSessionFilter.php: -------------------------------------------------------------------------------- 1 | relation = $relation; 26 | $this->hoursAgo = $hoursAgo; 27 | } 28 | 29 | /** 30 | * @return array{ 31 | * field: 'first_session', 32 | * relation: self::GT|self::LT, 33 | * hours_ago: int|float 34 | * } 35 | */ 36 | public function toArray(): array 37 | { 38 | return [ 39 | 'field' => 'first_session', 40 | 'relation' => $this->relation, 41 | 'hours_ago' => $this->hoursAgo, 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Dto/Filter/LanguageFilter.php: -------------------------------------------------------------------------------- 1 | relation = $relation; 22 | $this->value = $value; 23 | } 24 | 25 | /** 26 | * @return array{ 27 | * field: 'language', 28 | * relation: self::EQ|self::NEQ, 29 | * value: string 30 | * } 31 | */ 32 | public function toArray(): array 33 | { 34 | return [ 35 | 'field' => 'language', 36 | 'relation' => $this->relation, 37 | 'value' => $this->value, 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Dto/Filter/LastSessionFilter.php: -------------------------------------------------------------------------------- 1 | relation = $relation; 26 | $this->hoursAgo = $hoursAgo; 27 | } 28 | 29 | /** 30 | * @return array{ 31 | * field: 'last_session', 32 | * relation: self::GT|self::LT, 33 | * hours_ago: int|float 34 | * } 35 | */ 36 | public function toArray(): array 37 | { 38 | return [ 39 | 'field' => 'last_session', 40 | 'relation' => $this->relation, 41 | 'hours_ago' => $this->hoursAgo, 42 | ]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Dto/Filter/LocationFilter.php: -------------------------------------------------------------------------------- 1 | radius = $radius; 18 | $this->lat = $lat; 19 | $this->long = $long; 20 | } 21 | 22 | /** 23 | * @return array{ 24 | * field: 'location', 25 | * radius: int, 26 | * lat: float, 27 | * long: float 28 | * } 29 | */ 30 | public function toArray(): array 31 | { 32 | return [ 33 | 'field' => 'location', 34 | 'radius' => $this->radius, 35 | 'lat' => $this->lat, 36 | 'long' => $this->long, 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Dto/Filter/SessionCountFilter.php: -------------------------------------------------------------------------------- 1 | relation = $relation; 22 | $this->value = $value; 23 | } 24 | 25 | /** 26 | * @return array{ 27 | * field: 'session_count', 28 | * relation: self::GT|self::LT|self::EQ|self::NEQ, 29 | * value: int 30 | * } 31 | */ 32 | public function toArray(): array 33 | { 34 | return [ 35 | 'field' => 'session_count', 36 | 'relation' => $this->relation, 37 | 'value' => $this->value, 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Dto/Filter/SessionTimeFilter.php: -------------------------------------------------------------------------------- 1 | relation = $relation; 22 | $this->value = $value; 23 | } 24 | 25 | /** 26 | * @return array{ 27 | * field: 'session_time', 28 | * relation: self::GT|self::LT, 29 | * value: int 30 | * } 31 | */ 32 | public function toArray(): array 33 | { 34 | return [ 35 | 'field' => 'session_time', 36 | 'relation' => $this->relation, 37 | 'value' => $this->value, 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Dto/Filter/TagFilter.php: -------------------------------------------------------------------------------- 1 | relation = $relation; 36 | $this->key = $key; 37 | $this->value = $value; 38 | } 39 | 40 | /** 41 | * @return array{ 42 | * field: 'tag', 43 | * relation: self::GT|self::LT|self::EQ|self::NEQ|self::EXISTS|self::NOT_EXISTS|self::TIME_ELAPSED_GT|self::TIME_ELAPSED_LT, 44 | * key: string, 45 | * value: string|int|null 46 | * } 47 | */ 48 | public function toArray(): array 49 | { 50 | return [ 51 | 'field' => 'tag', 52 | 'relation' => $this->relation, 53 | 'key' => $this->key, 54 | 'value' => $this->value, 55 | ]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Dto/Segment/CreateSegment.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | protected ?array $filters = null; 21 | 22 | /** 23 | * @param non-empty-string|null $name 24 | * @param array|null $filters 25 | */ 26 | public function __construct(?string $name = null, ?array $filters = null) 27 | { 28 | $this->name = $name; 29 | $this->filters = $filters; 30 | } 31 | 32 | /** 33 | * @param non-empty-string $name 34 | */ 35 | public function name(string $name): self 36 | { 37 | $this->name = $name; 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * @param list $filters 44 | */ 45 | public function filters(array $filters): self 46 | { 47 | $this->filters = $filters; 48 | 49 | return $this; 50 | } 51 | 52 | /** 53 | * @return array{ 54 | * name?: non-empty-string, 55 | * filters?: array 56 | * } 57 | */ 58 | public function toArray(): array 59 | { 60 | return array_filter([ 61 | 'name' => $this->name, 62 | 'filters' => $this->filters !== null 63 | ? array_map( 64 | static function (AbstractFilter $filter): array { 65 | return $filter->toArray(); 66 | }, 67 | $this->filters 68 | ) 69 | : null, 70 | ]); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Dto/Segment/ListSegments.php: -------------------------------------------------------------------------------- 1 | |null 13 | */ 14 | protected ?int $limit = null; 15 | 16 | /** 17 | * @var int<0, 2147483648>|null 18 | */ 19 | protected ?int $offset = null; 20 | 21 | /** 22 | * @param int<0, 2147483648>|null $limit 23 | * @param int<0, 2147483648>|null $offset 24 | */ 25 | public function __construct(?int $limit = null, ?int $offset = null) 26 | { 27 | $this->limit = $limit; 28 | $this->offset = $offset; 29 | } 30 | 31 | /** 32 | * @param int<0, 2147483648> $limit 33 | */ 34 | public function limit(int $limit): self 35 | { 36 | $this->limit = $limit; 37 | 38 | return $this; 39 | } 40 | 41 | /** 42 | * @param int<0, 2147483648> $offset 43 | */ 44 | public function offset(int $offset): self 45 | { 46 | $this->offset = $offset; 47 | 48 | return $this; 49 | } 50 | 51 | public function toArray(): array 52 | { 53 | return array_filter([ 54 | 'limit' => $this->limit, 55 | 'offset' => $this->offset, 56 | ]); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Exception/BadMethodCallException.php: -------------------------------------------------------------------------------- 1 | request = $request; 20 | $this->response = $response; 21 | 22 | parent::__construct(); 23 | } 24 | 25 | public function getRequest(): RequestInterface 26 | { 27 | return $this->request; 28 | } 29 | 30 | public function getResponse(): ResponseInterface 31 | { 32 | return $this->response; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Notifications.php: -------------------------------------------------------------------------------- 1 | resolverFactory = $resolverFactory; 21 | } 22 | 23 | /** 24 | * Get information about notification with provided ID. 25 | * 26 | * Application authentication key and ID must be set. 27 | * 28 | * @param non-empty-string $id Notification ID 29 | * 30 | * @return array 31 | */ 32 | public function getOne(string $id): array 33 | { 34 | $request = $this->createRequest('GET', "/notifications/$id?app_id={$this->client->getConfig()->getApplicationId()}"); 35 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 36 | 37 | return $this->client->sendRequest($request); 38 | } 39 | 40 | /** 41 | * Get information about all notifications. 42 | * 43 | * Application authentication key and ID must be set. 44 | * 45 | * @param int<0, 50>|null $limit How many notifications to return (max 50) 46 | * @param int|null $offset Results offset (results are sorted by ID) 47 | * 48 | * @phpstan-param int $kind Kind of notifications returned. Default (not set) is all notification types 49 | * 50 | * @return array 51 | */ 52 | public function getAll(?int $limit = null, ?int $offset = null/* , ?int $kind = null */): array 53 | { 54 | if (func_num_args() > 2 && !is_int(func_get_arg(2))) { 55 | trigger_deprecation('norkunas/onesignal-php-api', '2.1.0', 'Method %s() will have a third `int $kind` argument. Not defining it or passing a non integer value is deprecated.', __METHOD__); 56 | } elseif (__CLASS__ !== static::class) { 57 | $r = new ReflectionMethod($this, __FUNCTION__); 58 | 59 | if (count($r->getParameters()) > 2) { 60 | trigger_deprecation('norkunas/onesignal-php-api', '2.1.0', 'Method %s() will have a third `int $kind` argument. Not defining it or passing a non integer value is deprecated.', __METHOD__); 61 | } 62 | } 63 | 64 | $query = ['app_id' => $this->client->getConfig()->getApplicationId()]; 65 | 66 | if ($limit !== null) { 67 | $query['limit'] = $limit; 68 | } 69 | 70 | if ($offset !== null) { 71 | $query['offset'] = $offset; 72 | } 73 | 74 | if (func_num_args() > 2 && is_int(func_get_arg(2))) { 75 | $query['kind'] = func_get_arg(2); 76 | } 77 | 78 | $request = $this->createRequest('GET', '/notifications?'.http_build_query($query)); 79 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 80 | 81 | return $this->client->sendRequest($request); 82 | } 83 | 84 | /** 85 | * Send new notification with provided data. 86 | * 87 | * Application authentication key and ID must be set. 88 | * 89 | * @param array $data 90 | * 91 | * @return array 92 | */ 93 | public function add(array $data): array 94 | { 95 | $resolvedData = $this->resolverFactory->createNotificationResolver()->resolve($data); 96 | 97 | $request = $this->createRequest('POST', '/notifications'); 98 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 99 | $request = $request->withHeader('Content-Type', 'application/json'); 100 | $request = $request->withBody($this->createStream($resolvedData)); 101 | 102 | return $this->client->sendRequest($request); 103 | } 104 | 105 | /** 106 | * Open notification. 107 | * 108 | * Application authentication key and ID must be set. 109 | * 110 | * @param non-empty-string $id Notification ID 111 | * 112 | * @return array 113 | */ 114 | public function open(string $id): array 115 | { 116 | $request = $this->createRequest('PUT', "/notifications/$id"); 117 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 118 | $request = $request->withHeader('Content-Type', 'application/json'); 119 | $request = $request->withBody($this->createStream([ 120 | 'app_id' => $this->client->getConfig()->getApplicationId(), 121 | 'opened' => true, 122 | ])); 123 | 124 | return $this->client->sendRequest($request); 125 | } 126 | 127 | /** 128 | * Cancel notification. 129 | * 130 | * Application authentication key and ID must be set. 131 | * 132 | * @param non-empty-string $id Notification ID 133 | * 134 | * @return array 135 | */ 136 | public function cancel(string $id): array 137 | { 138 | $request = $this->createRequest('DELETE', "/notifications/$id?app_id={$this->client->getConfig()->getApplicationId()}"); 139 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 140 | 141 | return $this->client->sendRequest($request); 142 | } 143 | 144 | /** 145 | * View the devices sent a notification. 146 | * 147 | * Application authentication key and ID must be set. 148 | * 149 | * @param non-empty-string $id Notification ID 150 | * @param array $data 151 | * 152 | * @return array 153 | */ 154 | public function history(string $id, array $data): array 155 | { 156 | $resolvedData = $this->resolverFactory->createNotificationHistoryResolver()->resolve($data); 157 | 158 | $request = $this->createRequest('POST', "/notifications/$id/history"); 159 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 160 | $request = $request->withHeader('Cache-Control', 'no-cache'); 161 | $request = $request->withHeader('Content-Type', 'application/json'); 162 | $request = $request->withBody($this->createStream($resolvedData)); 163 | 164 | return $this->client->sendRequest($request); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/OneSignal.php: -------------------------------------------------------------------------------- 1 | config = $config; 41 | $this->httpClient = $httpClient; 42 | $this->requestFactory = $requestFactory; 43 | $this->streamFactory = $streamFactory; 44 | $this->resolverFactory = new ResolverFactory($this->config); 45 | } 46 | 47 | public function getConfig(): Config 48 | { 49 | return $this->config; 50 | } 51 | 52 | public function getRequestFactory(): RequestFactoryInterface 53 | { 54 | return $this->requestFactory; 55 | } 56 | 57 | public function getStreamFactory(): StreamFactoryInterface 58 | { 59 | return $this->streamFactory; 60 | } 61 | 62 | /** 63 | * @return array 64 | * 65 | * @throws JsonException 66 | * @throws ClientExceptionInterface 67 | */ 68 | public function sendRequest(RequestInterface $request): array 69 | { 70 | $response = $this->httpClient->sendRequest($request); 71 | 72 | $contentType = $response->getHeader('Content-Type')[0] ?? 'application/json'; 73 | 74 | if (!preg_match('/\bjson\b/i', $contentType)) { 75 | throw new JsonException("Response content-type is '$contentType' while a JSON-compatible one was expected."); 76 | } 77 | 78 | $content = $response->getBody()->__toString(); 79 | 80 | try { 81 | $content = json_decode($content, true, 512, JSON_BIGINT_AS_STRING | JSON_THROW_ON_ERROR); 82 | } catch (\JsonException $e) { 83 | throw new JsonException($e->getMessage(), $e->getCode(), $e); 84 | } 85 | 86 | if (!is_array($content)) { 87 | throw new JsonException(sprintf('JSON content was expected to decode to an array, %s returned.', gettype($content))); 88 | } 89 | 90 | if (!isset($content['_status_code'])) { 91 | $content['_status_code'] = $response->getStatusCode(); 92 | } 93 | 94 | return $content; 95 | } 96 | 97 | /** 98 | * @return array 99 | * 100 | * @throws ClientExceptionInterface 101 | * @throws JsonException 102 | * @throws UnsuccessfulResponse 103 | */ 104 | public function makeRequest(RequestInterface $request): array 105 | { 106 | $response = $this->httpClient->sendRequest($request); 107 | 108 | if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 400) { 109 | throw new UnsuccessfulResponse($request, $response); 110 | } 111 | 112 | $content = $response->getBody()->__toString(); 113 | 114 | try { 115 | $content = json_decode($content, true, 512, JSON_BIGINT_AS_STRING | JSON_THROW_ON_ERROR); 116 | } catch (\JsonException $e) { 117 | throw new JsonException($e->getMessage(), $e->getCode(), $e); 118 | } 119 | 120 | if (!is_array($content)) { 121 | throw new JsonException(sprintf('JSON content was expected to decode to an array, %s returned.', gettype($content))); 122 | } 123 | 124 | return $content; 125 | } 126 | 127 | /** 128 | * @return object 129 | * 130 | * @throws InvalidArgumentException 131 | */ 132 | public function api(string $name) 133 | { 134 | switch ($name) { 135 | case 'apps': 136 | $api = new Apps($this, $this->resolverFactory); 137 | 138 | break; 139 | case 'devices': 140 | $api = new Devices($this, $this->resolverFactory); 141 | 142 | break; 143 | case 'notifications': 144 | $api = new Notifications($this, $this->resolverFactory); 145 | 146 | break; 147 | case 'segments': 148 | $api = new Segments($this); 149 | 150 | break; 151 | default: 152 | throw new InvalidArgumentException("Undefined api instance called: '$name'."); 153 | } 154 | 155 | return $api; 156 | } 157 | 158 | /** 159 | * @param array $args 160 | */ 161 | public function __call(string $name, array $args): object 162 | { 163 | try { 164 | return $this->api($name); 165 | } catch (InvalidArgumentException $e) { 166 | throw new BadMethodCallException("Undefined method called: '$name'."); 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/Resolver/AppOutcomesResolver.php: -------------------------------------------------------------------------------- 1 | setDefined('outcome_names') 18 | ->setAllowedTypes('outcome_names', 'string[]') 19 | ->setDefined('outcome_time_range') 20 | ->setAllowedTypes('outcome_time_range', 'string') 21 | ->setAllowedValues('outcome_time_range', [Apps::OUTCOME_TIME_RANGE_HOUR, Apps::OUTCOME_TIME_RANGE_DAY, Apps::OUTCOME_TIME_RANGE_MONTH]) 22 | ->setDefault('outcome_time_range', Apps::OUTCOME_TIME_RANGE_HOUR) 23 | ->setDefined('outcome_platforms') 24 | ->setAllowedTypes('outcome_platforms', 'int[]') 25 | ->setAllowedValues('outcome_platforms', static function (array $platforms): bool { 26 | $intersect = array_intersect($platforms, [ 27 | Devices::IOS, 28 | Devices::ANDROID, 29 | Devices::AMAZON, 30 | Devices::WINDOWS_PHONE, 31 | Devices::WINDOWS_PHONE_MPNS, 32 | Devices::CHROME_APP, 33 | Devices::CHROME_WEB, 34 | Devices::WINDOWS_PHONE_WNS, 35 | Devices::SAFARI, 36 | Devices::FIREFOX, 37 | Devices::MACOS, 38 | Devices::ALEXA, 39 | Devices::EMAIL, 40 | Devices::HUAWEI, 41 | Devices::SMS, 42 | ]); 43 | 44 | return count($intersect) === count($platforms); 45 | }) 46 | ->setNormalizer('outcome_platforms', static function (Options $options, array $value): string { 47 | return implode(',', $value); 48 | }) 49 | ->setDefined('outcome_attribution') 50 | ->setAllowedTypes('outcome_attribution', 'string') 51 | ->setAllowedValues('outcome_attribution', [ 52 | Apps::OUTCOME_ATTRIBUTION_TOTAL, 53 | Apps::OUTCOME_ATTRIBUTION_DIRECT, 54 | Apps::OUTCOME_ATTRIBUTION_INFLUENCED, 55 | Apps::OUTCOME_ATTRIBUTION_UNATTRIBUTED, 56 | ]) 57 | ->setDefault('outcome_attribution', Apps::OUTCOME_ATTRIBUTION_TOTAL) 58 | ->setRequired(['outcome_names']) 59 | ->resolve($data); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Resolver/AppResolver.php: -------------------------------------------------------------------------------- 1 | setRequired('name') 15 | ->setAllowedTypes('name', 'string') 16 | ->setDefined('apns_env') 17 | ->setAllowedTypes('apns_env', 'string') 18 | ->setAllowedValues('apns_env', ['sandbox', 'production']) 19 | ->setDefined('apns_p12') 20 | ->setAllowedTypes('apns_p12', 'string') 21 | ->setDefined('apns_p12_password') 22 | ->setAllowedTypes('apns_p12_password', 'string') 23 | ->setDefined('gcm_key') 24 | ->setAllowedTypes('gcm_key', 'string') 25 | ->setDefined('android_gcm_sender_id') 26 | ->setAllowedTypes('android_gcm_sender_id', 'string') 27 | ->setDefined('chrome_key') 28 | ->setAllowedTypes('chrome_key', 'string') 29 | ->setDefined('safari_apns_p12') 30 | ->setAllowedTypes('safari_apns_p12', 'string') 31 | ->setDefined('chrome_web_key') 32 | ->setAllowedTypes('chrome_web_key', 'string') 33 | ->setDefined('safari_apns_p12_password') 34 | ->setAllowedTypes('safari_apns_p12_password', 'string') 35 | ->setDefined('site_name') 36 | ->setAllowedTypes('site_name', 'string') 37 | ->setDefined('safari_site_origin') 38 | ->setAllowedTypes('safari_site_origin', 'string') 39 | ->setDefined('safari_icon_16_16') 40 | ->setAllowedTypes('safari_icon_16_16', 'string') 41 | ->setDefined('safari_icon_32_32') 42 | ->setAllowedTypes('safari_icon_32_32', 'string') 43 | ->setDefined('safari_icon_64_64') 44 | ->setAllowedTypes('safari_icon_64_64', 'string') 45 | ->setDefined('safari_icon_128_128') 46 | ->setAllowedTypes('safari_icon_128_128', 'string') 47 | ->setDefined('safari_icon_256_256') 48 | ->setAllowedTypes('safari_icon_256_256', 'string') 49 | ->setDefined('chrome_web_origin') 50 | ->setAllowedTypes('chrome_web_origin', 'string') 51 | ->setDefined('chrome_web_gcm_sender_id') 52 | ->setAllowedTypes('chrome_web_gcm_sender_id', 'string') 53 | ->setDefined('chrome_web_default_notification_icon') 54 | ->setAllowedTypes('chrome_web_default_notification_icon', 'string') 55 | ->setDefined('chrome_web_sub_domain') 56 | ->setAllowedTypes('chrome_web_sub_domain', 'string') 57 | ->setDefined('organization_id') 58 | ->setAllowedTypes('organization_id', 'string') 59 | ->resolve($data); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Resolver/DeviceFocusResolver.php: -------------------------------------------------------------------------------- 1 | setDefault('state', 'ping') 15 | ->setAllowedTypes('state', 'string') 16 | ->setRequired('active_time') 17 | ->setAllowedTypes('active_time', 'int') 18 | ->resolve($data); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Resolver/DevicePurchaseResolver.php: -------------------------------------------------------------------------------- 1 | setDefined('existing') 15 | ->setAllowedTypes('existing', 'bool') 16 | ->setRequired('purchases') 17 | ->setAllowedTypes('purchases', 'array') 18 | ->resolve($data); 19 | 20 | foreach ($data['purchases'] as $key => $purchase) { 21 | $data['purchases'][$key] = (new OptionsResolver()) 22 | ->setRequired('sku') 23 | ->setAllowedTypes('sku', 'string') 24 | ->setRequired('amount') 25 | ->setAllowedTypes('amount', 'float') 26 | ->setRequired('iso') 27 | ->setAllowedTypes('iso', 'string') 28 | ->resolve($purchase); 29 | } 30 | 31 | return $data; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Resolver/DeviceResolver.php: -------------------------------------------------------------------------------- 1 | config = $config; 19 | $this->isNewDevice = $isNewDevice; 20 | } 21 | 22 | public function resolve(array $data): array 23 | { 24 | $resolver = (new OptionsResolver()) 25 | ->setDefined('identifier') 26 | ->setAllowedTypes('identifier', 'string') 27 | ->setDefined('identifier_auth_hash') 28 | ->setAllowedTypes('identifier_auth_hash', 'string') 29 | ->setDefined('language') 30 | ->setAllowedTypes('language', 'string') 31 | ->setDefined('timezone') 32 | ->setAllowedTypes('timezone', 'int') 33 | ->setDefined('game_version') 34 | ->setAllowedTypes('game_version', 'string') 35 | ->setDefined('device_model') 36 | ->setAllowedTypes('device_model', 'string') 37 | ->setDefined('device_os') 38 | ->setAllowedTypes('device_os', 'string') 39 | ->setDefined('ad_id') 40 | ->setAllowedTypes('ad_id', 'string') 41 | ->setDefined('sdk') 42 | ->setAllowedTypes('sdk', 'string') 43 | ->setDefined('session_count') 44 | ->setAllowedTypes('session_count', 'int') 45 | ->setDefined('tags') 46 | ->setAllowedTypes('tags', 'array') 47 | ->setDefined('amount_spent') 48 | ->setAllowedTypes('amount_spent', 'float') 49 | ->setDefined('created_at') 50 | ->setAllowedTypes('created_at', 'int') 51 | ->setDefined('playtime') 52 | ->setAllowedTypes('playtime', 'int') 53 | ->setDefined('badge_count') 54 | ->setAllowedTypes('badge_count', 'int') 55 | ->setDefined('last_active') 56 | ->setAllowedTypes('last_active', 'int') 57 | ->setDefined('notification_types') 58 | ->setAllowedTypes('notification_types', 'int') 59 | ->setAllowedValues('notification_types', [1, -2]) 60 | ->setDefined('test_type') 61 | ->setAllowedTypes('test_type', 'int') 62 | ->setAllowedValues('test_type', [1, 2]) 63 | ->setDefined('long') 64 | ->setAllowedTypes('long', 'double') 65 | ->setDefined('lat') 66 | ->setAllowedTypes('lat', 'double') 67 | ->setDefined('country') 68 | ->setAllowedTypes('country', 'string') 69 | ->setDefined('external_user_id') 70 | ->setAllowedTypes('external_user_id', 'string') 71 | ->setDefined('external_user_id_auth_hash') 72 | ->setAllowedTypes('external_user_id_auth_hash', 'string') 73 | ->setDefault('app_id', $this->config->getApplicationId()) 74 | ->setAllowedTypes('app_id', 'string'); 75 | 76 | if ($this->isNewDevice) { 77 | $resolver 78 | ->setRequired('device_type') 79 | ->setAllowedTypes('device_type', 'int') 80 | ->setAllowedValues('device_type', [ 81 | Devices::IOS, 82 | Devices::ANDROID, 83 | Devices::AMAZON, 84 | Devices::WINDOWS_PHONE, 85 | Devices::WINDOWS_PHONE_MPNS, 86 | Devices::CHROME_APP, 87 | Devices::CHROME_WEB, 88 | Devices::WINDOWS_PHONE_WNS, 89 | Devices::SAFARI, 90 | Devices::FIREFOX, 91 | Devices::MACOS, 92 | Devices::ALEXA, 93 | Devices::EMAIL, 94 | Devices::HUAWEI, 95 | Devices::SMS, 96 | ]); 97 | } else { 98 | $resolver 99 | ->setDefined('ip') 100 | ->setAllowedTypes('ip', 'string') 101 | ->setAllowedValues('ip', static function (string $ip): bool { 102 | return (bool) filter_var($ip, FILTER_VALIDATE_IP); 103 | }); 104 | } 105 | 106 | return $resolver->resolve($data); 107 | } 108 | 109 | public function setIsNewDevice(bool $isNewDevice): void 110 | { 111 | $this->isNewDevice = $isNewDevice; 112 | } 113 | 114 | public function getIsNewDevice(): bool 115 | { 116 | return $this->isNewDevice; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Resolver/DeviceSessionResolver.php: -------------------------------------------------------------------------------- 1 | setDefined('identifier') 16 | ->setAllowedTypes('identifier', 'string') 17 | ->setDefined('language') 18 | ->setAllowedTypes('language', 'string') 19 | ->setDefined('timezone') 20 | ->setAllowedTypes('timezone', 'int') 21 | ->setDefined('game_version') 22 | ->setAllowedTypes('game_version', 'string') 23 | ->setDefined('device_os') 24 | ->setAllowedTypes('device_os', 'string') 25 | // @todo: remove "device_model" later (this option is probably deprecated as it is removed from documentation) 26 | ->setDefined('device_model') 27 | ->setAllowedTypes('device_model', 'string') 28 | ->setDefined('ad_id') 29 | ->setAllowedTypes('ad_id', 'string') 30 | ->setDefined('sdk') 31 | ->setAllowedTypes('sdk', 'string') 32 | ->setDefined('tags') 33 | ->setAllowedTypes('tags', 'array') 34 | ->setDefined('device_type') 35 | ->setAllowedTypes('device_type', 'int') 36 | ->setAllowedValues('device_type', [ 37 | Devices::IOS, 38 | Devices::ANDROID, 39 | Devices::AMAZON, 40 | Devices::WINDOWS_PHONE, 41 | Devices::WINDOWS_PHONE_MPNS, 42 | Devices::CHROME_APP, 43 | Devices::CHROME_WEB, 44 | Devices::WINDOWS_PHONE_WNS, 45 | Devices::SAFARI, 46 | Devices::FIREFOX, 47 | Devices::MACOS, 48 | Devices::ALEXA, 49 | Devices::EMAIL, 50 | Devices::HUAWEI, 51 | Devices::SMS, 52 | ]) 53 | ->resolve($data); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Resolver/DeviceTagsResolver.php: -------------------------------------------------------------------------------- 1 | setDefined('tags') 15 | ->setAllowedTypes('tags', 'array') 16 | ->setRequired(['tags']) 17 | ->resolve($data); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Resolver/NotificationHistoryResolver.php: -------------------------------------------------------------------------------- 1 | config = $config; 17 | } 18 | 19 | public function resolve(array $data): array 20 | { 21 | return (new OptionsResolver()) 22 | ->setRequired('events') 23 | ->setAllowedTypes('events', 'string') 24 | ->setAllowedValues('events', ['sent', 'clicked']) 25 | ->setRequired('email') 26 | ->setAllowedTypes('email', 'string') 27 | ->setDefault('app_id', $this->config->getApplicationId()) 28 | ->setAllowedTypes('app_id', 'string') 29 | ->resolve($data); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Resolver/NotificationResolver.php: -------------------------------------------------------------------------------- 1 | config = $config; 22 | } 23 | 24 | public function resolve(array $data): array 25 | { 26 | return (new OptionsResolver()) 27 | ->setDefined('name') 28 | ->setAllowedTypes('name', 'string') 29 | ->setDefined('contents') 30 | ->setAllowedTypes('contents', 'array') 31 | ->setDefined('headings') 32 | ->setAllowedTypes('headings', 'array') 33 | ->setDefined('subtitle') 34 | ->setAllowedTypes('subtitle', 'array') 35 | ->setDefined('isIos') 36 | ->setAllowedTypes('isIos', 'bool') 37 | ->setDefined('isAndroid') 38 | ->setAllowedTypes('isAndroid', 'bool') 39 | ->setDefined('isHuawei') 40 | ->setAllowedTypes('isHuawei', 'bool') 41 | ->setDefined('isWP') 42 | ->setAllowedTypes('isWP', 'bool') 43 | ->setDefined('isWP_WNS') 44 | ->setAllowedTypes('isWP_WNS', 'bool') 45 | ->setDefined('isAdm') 46 | ->setAllowedTypes('isAdm', 'bool') 47 | ->setDefined('isChrome') 48 | ->setAllowedTypes('isChrome', 'bool') 49 | ->setDefined('isChromeWeb') 50 | ->setAllowedTypes('isChromeWeb', 'bool') 51 | ->setDefined('isFirefox') 52 | ->setAllowedTypes('isFirefox', 'bool') 53 | ->setDefined('isSafari') 54 | ->setAllowedTypes('isSafari', 'bool') 55 | ->setDefined('isAnyWeb') 56 | ->setAllowedTypes('isAnyWeb', 'bool') 57 | ->setDefined('included_segments') 58 | ->setAllowedTypes('included_segments', 'array') 59 | ->setDefined('excluded_segments') 60 | ->setAllowedTypes('excluded_segments', 'array') 61 | ->setDefined('include_subscription_ids') 62 | ->setAllowedTypes('include_subscription_ids', 'array') 63 | ->setDefined('include_player_ids') 64 | ->setAllowedTypes('include_player_ids', 'array') 65 | ->setDefined('include_ios_tokens') 66 | ->setAllowedTypes('include_ios_tokens', 'array') 67 | ->setDefined('include_android_reg_ids') 68 | ->setAllowedTypes('include_android_reg_ids', 'array') 69 | ->setDefined('include_external_user_ids') 70 | ->setAllowedTypes('include_external_user_ids', 'array') 71 | ->setDefined('channel_for_external_user_ids') 72 | ->setAllowedTypes('channel_for_external_user_ids', 'string') 73 | ->setAllowedValues('channel_for_external_user_ids', ['push', 'email']) 74 | ->setDefined('include_email_tokens') 75 | ->setAllowedTypes('include_email_tokens', 'array') 76 | ->setDefined('include_phone_numbers') 77 | ->setAllowedTypes('include_phone_numbers', 'array') 78 | ->setDefined('include_wp_uris') 79 | ->setAllowedTypes('include_wp_uris', 'array') 80 | ->setDefined('include_wp_wns_uris') 81 | ->setAllowedTypes('include_wp_wns_uris', 'array') 82 | ->setDefined('include_amazon_reg_ids') 83 | ->setAllowedTypes('include_amazon_reg_ids', 'array') 84 | ->setDefined('include_chrome_reg_ids') 85 | ->setAllowedTypes('include_chrome_reg_ids', 'array') 86 | ->setDefined('include_chrome_web_reg_ids') 87 | ->setAllowedTypes('include_chrome_web_reg_ids', 'array') 88 | ->setDefined('include_aliases') 89 | ->setAllowedTypes('include_aliases', 'array') 90 | ->setDefined('target_channel') 91 | ->setAllowedTypes('target_channel', 'string') 92 | ->setAllowedValues('target_channel', ['push', 'email', 'sms']) 93 | ->setDefined('app_ids') 94 | ->setAllowedTypes('app_ids', 'array') 95 | ->setDefined('filters') 96 | ->setAllowedTypes('filters', 'array') 97 | ->setNormalizer('filters', function (Options $options, array $values) { 98 | return $this->normalizeFilters($options, $values); 99 | }) 100 | ->setDefined('ios_badgeType') 101 | ->setAllowedTypes('ios_badgeType', 'string') 102 | ->setAllowedValues('ios_badgeType', ['None', 'SetTo', 'Increase']) 103 | ->setDefined('ios_badgeCount') 104 | ->setAllowedTypes('ios_badgeCount', 'int') 105 | ->setDefined('ios_sound') 106 | ->setAllowedTypes('ios_sound', 'string') 107 | ->setDefined('android_sound') 108 | ->setAllowedTypes('android_sound', 'string') 109 | ->setDefined('adm_sound') 110 | ->setAllowedTypes('adm_sound', 'string') 111 | ->setDefined('wp_sound') 112 | ->setAllowedTypes('wp_sound', 'string') 113 | ->setDefined('wp_wns_sound') 114 | ->setAllowedTypes('wp_wns_sound', 'string') 115 | ->setDefined('data') 116 | ->setAllowedTypes('data', 'array') 117 | ->setDefined('buttons') 118 | ->setAllowedTypes('buttons', 'array') 119 | ->setNormalizer('buttons', function (Options $options, array $values) { 120 | return $this->normalizeButtons($values); 121 | }) 122 | ->setDefined('android_channel_id') 123 | ->setAllowedTypes('android_channel_id', 'string') 124 | ->setDefined('existing_android_channel_id') 125 | ->setAllowedTypes('existing_android_channel_id', 'string') 126 | ->setDefined('android_background_layout') 127 | ->setAllowedTypes('android_background_layout', 'array') 128 | ->setAllowedValues('android_background_layout', function (array $layouts) { 129 | return $this->filterAndroidBackgroundLayout($layouts); 130 | }) 131 | ->setDefined('small_icon') 132 | ->setAllowedTypes('small_icon', 'string') 133 | ->setDefined('large_icon') 134 | ->setAllowedTypes('large_icon', 'string') 135 | ->setDefined('ios_attachments') 136 | ->setAllowedTypes('ios_attachments', 'array') 137 | ->setAllowedValues('ios_attachments', function (array $attachments) { 138 | return $this->filterIosAttachments($attachments); 139 | }) 140 | ->setDefined('big_picture') 141 | ->setAllowedTypes('big_picture', 'string') 142 | ->setDefined('adm_small_icon') 143 | ->setAllowedTypes('adm_small_icon', 'string') 144 | ->setDefined('adm_large_icon') 145 | ->setAllowedTypes('adm_large_icon', 'string') 146 | ->setDefined('adm_big_picture') 147 | ->setAllowedTypes('adm_big_picture', 'string') 148 | ->setDefined('web_buttons') 149 | ->setAllowedTypes('web_buttons', 'array') 150 | ->setAllowedValues('web_buttons', function (array $buttons) { 151 | return $this->filterWebButtons($buttons); 152 | }) 153 | ->setDefined('ios_category') 154 | ->setAllowedTypes('ios_category', 'string') 155 | ->setDefined('chrome_icon') 156 | ->setAllowedTypes('chrome_icon', 'string') 157 | ->setDefined('chrome_big_picture') 158 | ->setAllowedTypes('chrome_big_picture', 'string') 159 | ->setDefined('chrome_web_icon') 160 | ->setAllowedTypes('chrome_web_icon', 'string') 161 | ->setDefined('chrome_web_image') 162 | ->setAllowedTypes('chrome_web_image', 'string') 163 | ->setDefined('chrome_web_badge') 164 | ->setAllowedTypes('chrome_web_badge', 'string') 165 | ->setDefined('firefox_icon') 166 | ->setAllowedTypes('firefox_icon', 'string') 167 | ->setDefined('url') 168 | ->setAllowedTypes('url', 'string') 169 | ->setAllowedValues('url', function (string $value) { 170 | return $this->filterUrl($value); 171 | }) 172 | ->setDefined('web_url') 173 | ->setAllowedTypes('web_url', 'string') 174 | ->setAllowedValues('web_url', function (string $value) { 175 | return $this->filterUrl($value); 176 | }) 177 | ->setDefined('app_url') 178 | ->setAllowedTypes('app_url', 'string') 179 | ->setDefined('send_after') 180 | ->setAllowedTypes('send_after', DateTimeInterface::class) 181 | ->setNormalizer('send_after', function (Options $options, DateTimeInterface $value) { 182 | return $this->normalizeDateTime($options, $value, self::SEND_AFTER_FORMAT); 183 | }) 184 | ->setDefined('delayed_option') 185 | ->setAllowedTypes('delayed_option', 'string') 186 | ->setAllowedValues('delayed_option', ['timezone', 'last-active']) 187 | ->setDefined('delivery_time_of_day') 188 | ->setAllowedTypes('delivery_time_of_day', DateTimeInterface::class) 189 | ->setNormalizer('delivery_time_of_day', function (Options $options, DateTimeInterface $value) { 190 | return $this->normalizeDateTime($options, $value, self::DELIVERY_TIME_OF_DAY_FORMAT); 191 | }) 192 | ->setDefined('android_led_color') 193 | ->setAllowedTypes('android_led_color', 'string') 194 | ->setDefined('android_accent_color') 195 | ->setAllowedTypes('android_accent_color', 'string') 196 | ->setDefined('android_visibility') 197 | ->setAllowedTypes('android_visibility', 'int') 198 | ->setAllowedValues('android_visibility', [-1, 0, 1]) 199 | ->setDefined('collapse_id') 200 | ->setAllowedTypes('collapse_id', 'string') 201 | ->setDefined('content_available') 202 | ->setAllowedTypes('content_available', 'bool') 203 | ->setDefined('mutable_content') 204 | ->setAllowedTypes('mutable_content', 'bool') 205 | ->setDefined('android_background_data') 206 | ->setAllowedTypes('android_background_data', 'bool') 207 | ->setDefined('amazon_background_data') 208 | ->setAllowedTypes('amazon_background_data', 'bool') 209 | ->setDefined('template_id') 210 | ->setAllowedTypes('template_id', 'string') 211 | ->setDefined('android_group') 212 | ->setAllowedTypes('android_group', 'string') 213 | ->setDefined('android_group_message') 214 | ->setAllowedTypes('android_group_message', 'array') 215 | ->setDefined('adm_group') 216 | ->setAllowedTypes('adm_group', 'string') 217 | ->setDefined('adm_group_message') 218 | ->setAllowedTypes('adm_group_message', 'array') 219 | ->setDefined('thread_id') 220 | ->setAllowedTypes('thread_id', 'string') 221 | ->setDefined('summary_arg') 222 | ->setAllowedTypes('summary_arg', 'string') 223 | ->setDefined('summary_arg_count') 224 | ->setAllowedTypes('summary_arg_count', 'int') 225 | ->setDefined('ios_interruption_level') 226 | ->setAllowedTypes('ios_interruption_level', 'string') 227 | ->setDefined('ttl') 228 | ->setAllowedTypes('ttl', 'int') 229 | ->setDefined('priority') 230 | ->setAllowedTypes('priority', 'int') 231 | ->setDefault('app_id', $this->config->getApplicationId()) 232 | ->setAllowedTypes('app_id', 'string') 233 | ->setDefined('email_subject') 234 | ->setAllowedTypes('email_subject', 'string') 235 | ->setDefined('email_body') 236 | ->setAllowedTypes('email_body', 'string') 237 | ->setDefined('email_from_name') 238 | ->setAllowedTypes('email_from_name', 'string') 239 | ->setDefined('email_from_address') 240 | ->setAllowedTypes('email_from_address', 'string') 241 | ->setDefined('external_id') 242 | ->setAllowedTypes('external_id', 'string') 243 | ->setDefined('web_push_topic') 244 | ->setAllowedTypes('web_push_topic', 'string') 245 | ->setDefined('apns_push_type_override') 246 | ->setAllowedTypes('apns_push_type_override', 'string') 247 | ->setAllowedValues('apns_push_type_override', ['voip']) 248 | ->setDefined('sms_from') 249 | ->setAllowedTypes('sms_from', 'string') 250 | ->setDefined('sms_media_urls') 251 | ->setAllowedTypes('sms_media_urls', 'array') 252 | ->resolve($data); 253 | } 254 | 255 | private function normalizeFilters(Options $options, array $values): array 256 | { 257 | $filters = []; 258 | 259 | foreach ($values as $filter) { 260 | if (isset($filter['field'])) { 261 | $filters[] = $filter; 262 | } elseif (isset($filter['operator'])) { 263 | $filters[] = ['operator' => 'OR']; 264 | } 265 | } 266 | 267 | return $filters; 268 | } 269 | 270 | /** 271 | * @param mixed $value Url value to filter 272 | */ 273 | private function filterUrl($value): bool 274 | { 275 | return (bool) filter_var($value, FILTER_VALIDATE_URL); 276 | } 277 | 278 | private function normalizeButtons(array $values): array 279 | { 280 | $buttons = []; 281 | 282 | foreach ($values as $button) { 283 | if (!isset($button['text'])) { 284 | continue; 285 | } 286 | 287 | $buttons[] = [ 288 | 'id' => $button['id'] ?? random_int(0, PHP_INT_MAX), 289 | 'text' => $button['text'], 290 | 'icon' => $button['icon'] ?? null, 291 | ]; 292 | } 293 | 294 | return $buttons; 295 | } 296 | 297 | private function filterAndroidBackgroundLayout(array $layouts): bool 298 | { 299 | if (count($layouts) === 0) { 300 | return false; 301 | } 302 | 303 | $requiredKeys = ['image', 'headings_color', 'contents_color']; 304 | 305 | foreach ($layouts as $k => $v) { 306 | if (!is_string($v) || !in_array($k, $requiredKeys, true)) { 307 | return false; 308 | } 309 | } 310 | 311 | return true; 312 | } 313 | 314 | private function filterIosAttachments(array $attachments): bool 315 | { 316 | foreach ($attachments as $key => $value) { 317 | if (!is_string($key) || !is_string($value)) { 318 | return false; 319 | } 320 | } 321 | 322 | return true; 323 | } 324 | 325 | private function filterWebButtons(array $buttons): bool 326 | { 327 | $requiredKeys = ['id', 'text', 'icon', 'url']; 328 | 329 | foreach ($buttons as $button) { 330 | if (!is_array($button)) { 331 | return false; 332 | } 333 | 334 | if (count(array_intersect_key(array_flip($requiredKeys), $button)) !== count($requiredKeys)) { 335 | return false; 336 | } 337 | } 338 | 339 | return true; 340 | } 341 | 342 | private function normalizeDateTime(Options $options, DateTimeInterface $value, string $format): string 343 | { 344 | return $value->format($format); 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /src/Resolver/ResolverFactory.php: -------------------------------------------------------------------------------- 1 | config = $config; 16 | } 17 | 18 | public function createAppResolver(): AppResolver 19 | { 20 | return new AppResolver(); 21 | } 22 | 23 | public function createSegmentResolver(): SegmentResolver 24 | { 25 | return new SegmentResolver(); 26 | } 27 | 28 | public function createOutcomesResolver(): AppOutcomesResolver 29 | { 30 | return new AppOutcomesResolver(); 31 | } 32 | 33 | public function createDeviceSessionResolver(): DeviceSessionResolver 34 | { 35 | return new DeviceSessionResolver(); 36 | } 37 | 38 | public function createDevicePurchaseResolver(): DevicePurchaseResolver 39 | { 40 | return new DevicePurchaseResolver(); 41 | } 42 | 43 | public function createDeviceFocusResolver(): DeviceFocusResolver 44 | { 45 | return new DeviceFocusResolver(); 46 | } 47 | 48 | public function createNewDeviceResolver(): DeviceResolver 49 | { 50 | return new DeviceResolver($this->config, true); 51 | } 52 | 53 | public function createExistingDeviceResolver(): DeviceResolver 54 | { 55 | return new DeviceResolver($this->config, false); 56 | } 57 | 58 | public function createDeviceTagsResolver(): DeviceTagsResolver 59 | { 60 | return new DeviceTagsResolver(); 61 | } 62 | 63 | public function createNotificationResolver(): NotificationResolver 64 | { 65 | return new NotificationResolver($this->config); 66 | } 67 | 68 | public function createNotificationHistoryResolver(): NotificationHistoryResolver 69 | { 70 | return new NotificationHistoryResolver($this->config); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Resolver/ResolverInterface.php: -------------------------------------------------------------------------------- 1 | $data 13 | * 14 | * @return array 15 | */ 16 | public function resolve(array $data): array; 17 | } 18 | -------------------------------------------------------------------------------- /src/Resolver/SegmentResolver.php: -------------------------------------------------------------------------------- 1 | setDefined('id') 16 | ->setAllowedTypes('id', 'string') 17 | ->setRequired('name') 18 | ->setAllowedTypes('name', 'string') 19 | ->setDefined('filters') 20 | ->setAllowedTypes('filters', 'array') 21 | ->setNormalizer('filters', function (Options $options, array $values) { 22 | return $this->normalizeFilters($options, $values); 23 | }) 24 | ->resolve($data); 25 | } 26 | 27 | private function normalizeFilters(Options $options, array $values): array 28 | { 29 | $filters = []; 30 | 31 | foreach ($values as $filter) { 32 | if (isset($filter['field']) || isset($filter['operator'])) { 33 | $filters[] = $filter; 34 | } 35 | } 36 | 37 | return $filters; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Response/AbstractResponse.php: -------------------------------------------------------------------------------- 1 | $request 11 | */ 12 | public static function makeFromResponse(array $request): self; 13 | } 14 | -------------------------------------------------------------------------------- /src/Response/Segment/CreateSegmentResponse.php: -------------------------------------------------------------------------------- 1 | success = $success; 24 | $this->id = $id; 25 | } 26 | 27 | public static function makeFromResponse(array $response): self 28 | { 29 | return new static( 30 | $response['success'], 31 | $response['id'] 32 | ); 33 | } 34 | 35 | public function getSuccess(): bool 36 | { 37 | return $this->success; 38 | } 39 | 40 | public function getId(): string 41 | { 42 | return $this->id; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Response/Segment/DeleteSegmentResponse.php: -------------------------------------------------------------------------------- 1 | success = $success; 16 | } 17 | 18 | public static function makeFromResponse(array $response): self 19 | { 20 | return new static( 21 | $response['success'] 22 | ); 23 | } 24 | 25 | public function getSuccess(): bool 26 | { 27 | return $this->success; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Response/Segment/ListSegmentsResponse.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | protected int $offset; 21 | 22 | /** 23 | * @var int<0, 2147483648> 24 | */ 25 | protected int $limit; 26 | 27 | /** 28 | * @var list 29 | */ 30 | protected array $segments; 31 | 32 | /** 33 | * @param non-negative-int $totalCount 34 | * @param int<0, 2147483648> $limit 35 | * @param int<0, 2147483648> $offset 36 | * @param list $segments 37 | */ 38 | public function __construct(int $totalCount, int $offset, int $limit, array $segments) 39 | { 40 | $this->totalCount = $totalCount; 41 | $this->offset = $offset; 42 | $this->limit = $limit; 43 | $this->segments = $segments; 44 | } 45 | 46 | public static function makeFromResponse(array $response): self 47 | { 48 | $segments = array_map( 49 | static function (array $segment): Segment { 50 | return new Segment( 51 | $segment['id'], 52 | $segment['name'], 53 | new DateTimeImmutable($segment['created_at']), 54 | new DateTimeImmutable($segment['updated_at']), 55 | $segment['app_id'], 56 | $segment['read_only'], 57 | $segment['is_active'], 58 | ); 59 | }, 60 | $response['segments'] 61 | ); 62 | 63 | return new static( 64 | $response['total_count'], 65 | $response['offset'], 66 | $response['limit'], 67 | $segments 68 | ); 69 | } 70 | 71 | /** 72 | * @return non-negative-int 73 | */ 74 | public function getTotalCount(): int 75 | { 76 | return $this->totalCount; 77 | } 78 | 79 | /** 80 | * @return int<0, 2147483648> 81 | */ 82 | public function getOffset(): int 83 | { 84 | return $this->offset; 85 | } 86 | 87 | /** 88 | * @return int<0, 2147483648> 89 | */ 90 | public function getLimit(): int 91 | { 92 | return $this->limit; 93 | } 94 | 95 | /** 96 | * @return list 97 | */ 98 | public function getSegments(): array 99 | { 100 | return $this->segments; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Response/Segment/Segment.php: -------------------------------------------------------------------------------- 1 | id = $id; 49 | $this->name = $name; 50 | $this->createdAt = $createdAt; 51 | $this->updatedAt = $updatedAt; 52 | $this->appId = $appId; 53 | $this->readOnly = $readOnly; 54 | $this->isActive = $isActive; 55 | } 56 | 57 | public function getId(): string 58 | { 59 | return $this->id; 60 | } 61 | 62 | public function getName(): string 63 | { 64 | return $this->name; 65 | } 66 | 67 | public function getCreatedAt(): DateTimeImmutable 68 | { 69 | return $this->createdAt; 70 | } 71 | 72 | public function getUpdatedAt(): DateTimeImmutable 73 | { 74 | return $this->updatedAt; 75 | } 76 | 77 | public function getAppId(): string 78 | { 79 | return $this->appId; 80 | } 81 | 82 | public function getReadOnly(): bool 83 | { 84 | return $this->readOnly; 85 | } 86 | 87 | public function getIsActive(): bool 88 | { 89 | return $this->isActive; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Segments.php: -------------------------------------------------------------------------------- 1 | client->getConfig()->getApplicationId(); 28 | 29 | $request = $this->createRequest('GET', '/apps/'.$appId.'/segments?'.http_build_query($listSegmentsDto->toArray())); 30 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 31 | 32 | return ListSegmentsResponse::makeFromResponse($this->client->makeRequest($request)); 33 | } 34 | 35 | /** 36 | * Create new segment with provided data. 37 | * 38 | * Application authentication key and ID must be set. 39 | */ 40 | public function create(CreateSegment $createSegmentDto): CreateSegmentResponse 41 | { 42 | $appId = $this->client->getConfig()->getApplicationId(); 43 | 44 | $request = $this->createRequest('POST', '/apps/'.$appId.'/segments'); 45 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 46 | $request = $request->withHeader('Content-Type', 'application/json'); 47 | $request = $request->withBody($this->createStream($createSegmentDto->toArray())); 48 | 49 | return CreateSegmentResponse::makeFromResponse($this->client->makeRequest($request)); 50 | } 51 | 52 | /** 53 | * Delete segment. 54 | * 55 | * Application authentication key and ID must be set. 56 | * 57 | * @param non-empty-string $id Segment ID 58 | */ 59 | public function delete(string $id): DeleteSegmentResponse 60 | { 61 | $appId = $this->client->getConfig()->getApplicationId(); 62 | 63 | $request = $this->createRequest('DELETE', '/apps/'.$appId.'/segments/'.$id); 64 | $request = $request->withHeader('Authorization', "Basic {$this->client->getConfig()->getApplicationAuthKey()}"); 65 | 66 | return DeleteSegmentResponse::makeFromResponse($this->client->makeRequest($request)); 67 | } 68 | } 69 | --------------------------------------------------------------------------------