├── .github ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── src ├── Exceptions │ ├── ConnectionException.php │ ├── ConfigurationException.php │ ├── TypeMismatchException.php │ ├── MissingArgumentException.php │ ├── UnexpectedArgumentException.php │ ├── UploadException.php │ └── ChatkitException.php └── Chatkit.php ├── examples ├── delete_user.php ├── get_user_rooms.php ├── get_room_messages.php ├── get_rooms.php ├── get_read_cursors_for_user.php ├── get_users_by_id.php ├── join_room.php ├── add_users_to_room.php ├── create_room.php ├── remove_users_from_room.php ├── set_read_cursor.php ├── send_message.php ├── get_users.php ├── authenticate.php ├── create_user.php └── update_user.php ├── composer.json ├── LICENSE.md └── CHANGELOG.md /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### What? 2 | 3 | 4 | 5 | ### Suggested improvements 6 | 7 | 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### What? 2 | 3 | 4 | 5 | ### Why? 6 | 7 | 8 | 9 | ---- 10 | 11 | - [ ] CHANGELOG updated if relevant? 12 | -------------------------------------------------------------------------------- /src/Exceptions/ConnectionException.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->deleteUser([ 'id' => 'phptest' ])); 11 | -------------------------------------------------------------------------------- /examples/get_user_rooms.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->getUserRooms([ 'id' => 'ham' ])); 11 | -------------------------------------------------------------------------------- /examples/get_room_messages.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->getRoomMessages([ 'room_id' => '123456' ])); 11 | -------------------------------------------------------------------------------- /examples/get_rooms.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->getRooms([ 11 | 'include_private' => true 12 | ])); 13 | -------------------------------------------------------------------------------- /examples/get_read_cursors_for_user.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->getReadCursorsForUser([ 'user_id' => 'ham' ])); 11 | -------------------------------------------------------------------------------- /examples/get_users_by_id.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->getUsersById([ 11 | 'user_ids' => ['ham', 'phptest'] 12 | ])); 13 | -------------------------------------------------------------------------------- /examples/join_room.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->joinRoom([ 11 | 'user_id' => 'ham', 12 | 'room_id' => '123' 13 | ])); 14 | -------------------------------------------------------------------------------- /examples/add_users_to_room.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->addUsersToRoom([ 11 | 'room_id' => '123', 12 | 'user_ids' => ['ham', 'another'] 13 | ])); 14 | -------------------------------------------------------------------------------- /examples/create_room.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->createRoom([ 11 | 'creator_id' => 'ham', 12 | 'name' => 'A BIG ROOM', 13 | 'private' => false 14 | ])); 15 | -------------------------------------------------------------------------------- /examples/remove_users_from_room.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->removeUsersFromRoom([ 11 | 'room_id' => '123', 12 | 'user_ids' => ['ham', 'another'] 13 | ])); 14 | -------------------------------------------------------------------------------- /examples/set_read_cursor.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->setReadCursor([ 11 | 'user_id' => 'ham', 12 | 'room_id' => '456789', 13 | 'position' => 123, 14 | ])); 15 | -------------------------------------------------------------------------------- /examples/send_message.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->sendMessage([ 11 | 'sender_id' => 'ham', 12 | 'room_id' => '123456', 13 | 'text' => 'Vivan is the best' 14 | ])); 15 | -------------------------------------------------------------------------------- /examples/get_users.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->getUsers()); 11 | 12 | // example with a from_timestamp parameter 13 | // print_r($chatkit->getUsers([ 'from_timestamp' => '2018-04-17T12:46:51Z' ])); 14 | -------------------------------------------------------------------------------- /examples/authenticate.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | $auth_data = $chatkit->authenticate([ 'user_id' => 'ham' ]); 11 | 12 | print_r($auth_data); 13 | 14 | print($auth_data['status']); 15 | print(json_encode($auth_data['body'])); 16 | -------------------------------------------------------------------------------- /examples/create_user.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->createUser([ 11 | 'id' => 'phptest', 12 | 'name' => 'PHP IS KING', 13 | 'avatar_url' => 'https://placekitten.com/400/500', 14 | 'custom_data' => [ 15 | 'a' => 'test' 16 | ] 17 | ])); 18 | -------------------------------------------------------------------------------- /examples/update_user.php: -------------------------------------------------------------------------------- 1 | 'your:instance:locator', 7 | 'key' => 'your:key' 8 | ]); 9 | 10 | print_r($chatkit->updateUser([ 11 | 'id' => 'phptest', 12 | 'name' => 'PHP IS THE BEST', 13 | 'avatar_url' => 'https://placekitten.com/200/300', 14 | 'custom_data' => [ 15 | 'a' => 'test' 16 | ] 17 | ])); 18 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pusher/pusher-chatkit-server", 3 | "description" : "Pusher Chatkit PHP SDK", 4 | "keywords": ["pusher-chatkit-server", "pusher", "chatkit", "chatkit-server", "chatkit-php"], 5 | "license": "MIT", 6 | "require": { 7 | "php": "^5.6 || ^7.0", 8 | "ext-curl": "*", 9 | "firebase/php-jwt": "^5.0" 10 | }, 11 | "require-dev": { 12 | "phpunit/phpunit": "^4.8 || ^5.7" 13 | }, 14 | "autoload": { 15 | "psr-4": { 16 | "Chatkit\\": "src/" 17 | }, 18 | "exclude-from-classmap": ["/tests/"] 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { "": "tests/" } 22 | }, 23 | "config": { 24 | "preferred-install": "dist" 25 | }, 26 | "minimum-stability": "dev", 27 | "prefer-stable": true 28 | } 29 | -------------------------------------------------------------------------------- /src/Exceptions/UploadException.php: -------------------------------------------------------------------------------- 1 | body; 28 | } 29 | 30 | /** 31 | * @param array $body 32 | * @return $this 33 | */ 34 | public function setBody($body) 35 | { 36 | $this->body = $body; 37 | return $this; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Exceptions/ChatkitException.php: -------------------------------------------------------------------------------- 1 | body; 28 | } 29 | 30 | /** 31 | * @param array $body 32 | * @return $this 33 | */ 34 | public function setBody($body) 35 | { 36 | $this->body = $body; 37 | return $this; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 13 | 14 | **Describe the bug** 15 | A clear and concise description of what the bug is. 16 | 17 | **To Reproduce** 18 | Steps to reproduce the behavior: 19 | 1. Go to '...' 20 | 2. Click on '....' 21 | 3. Scroll down to '....' 22 | 4. See error 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen. 26 | 27 | **Screenshots** 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | **Additional context** 31 | SDK version: 32 | Platform/ OS/ Browser: 33 | 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: feature request 6 | assignees: '' 7 | 8 | --- 9 | 10 | 13 | 14 | **Is your feature request related to a problem? Please describe.** 15 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 16 | 17 | **Describe the solution you'd like** 18 | A clear and concise description of what you want to happen. 19 | 20 | **Describe alternatives you've considered** 21 | A clear and concise description of any alternative solutions or features you've considered. 22 | 23 | **Additional context** 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2017 Pusher. https://pusher.com 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased](https://github.com/pusher/chatkit-server-php/compare/1.7.0...HEAD) 8 | 9 | ## [1.9.0](https://github.com/pusher/chatkit-server-php/compare/1.7.0...1.9.0) 10 | 11 | ### Added 12 | 13 | - Adds message editing via `edit{Simple,Multipart,}Message`. 14 | 15 | ## 1.8.0 Yanked 16 | 17 | ## [1.7.0](https://github.com/pusher/chatkit-server-php/compare/1.6.2...1.7.0) 18 | 19 | ### Added 20 | 21 | - Support for fetching a message by its message ID, via `fetchMultipartMessage` 22 | 23 | ## [1.6.2](https://github.com/pusher/chatkit-server-ruby/compare/1.6.1...1.6.2) - 2019-11-19 24 | 25 | ### Fixed 26 | 27 | - `generateToken` now correctly sets the JWT token expiry on all cases 28 | 29 | ## [1.6.1](https://github.com/pusher/chatkit-server-ruby/compare/1.5.0...1.6.1) - 2019-09-06 30 | 31 | ### Fixed 32 | 33 | - `getOptionalFields` now works as expected and returns optionally set values. 34 | 35 | ## [1.6.0](https://github.com/pusher/chatkit-server-ruby/compare/1.5.0...1.6.0) - 2019-07-30 36 | 37 | ### Added 38 | 39 | - Support for `push_notification_title_override` to `createRoom` and `updateRoom` 40 | 41 | ## [1.5.0](https://github.com/pusher/chatkit-server-ruby/compare/1.4.0...1.5.0) - 2019-07-04 42 | 43 | ### Added 44 | 45 | - `createRoom` now accepts an optional `id` parameter that is then uniquely used to identify the 46 | room. If one isn't provided, the server will generate an ID instead. 47 | 48 | ### Changed 49 | 50 | - The `deleteMessage` method now *requires* a room ID parameter, `room_id` and the `id` 51 | parameter has been renamed to `message_id` to avoid any ambiguity. 52 | 53 | ## [1.4.0](https://github.com/pusher/chatkit-server-ruby/compare/1.3.0...1.4.0) - 2019-06-24 54 | 55 | ### Changed 56 | 57 | - Unread counts. No new methods are added, but `getUserRooms` now include `unread_count` and `last_message_at` in the response 58 | 59 | 60 | ## [1.3.0](https://github.com/pusher/chatkit-server-php/compare/1.2.0...1.3.0) - 2019-06-18 61 | 62 | ### Added 63 | 64 | - Async deletion methods. `asyncDeleteUser`, `asyncDeleteRoom` and `getDeleteStatus`. 65 | The `deleteRoom` and `deleteUser` methods should be considered deprecated, and will be removed in a future version. 66 | 67 | ## [1.2.0](https://github.com/pusher/chatkit-server-php/compare/1.1.0...1.2.0) - 2019-03-08 68 | 69 | ### Added 70 | 71 | - `sendMultipartMessage` and `sendSimpleMessage` using the new V3 endpoint for message sending 72 | - `fetchMultipartMessage` using the new V3 endpoint for message fetching 73 | 74 | ### Changed 75 | 76 | - all methods except `sendMessage` and `getRoomMessages` uses new V3 endpoints 77 | 78 | ## [1.1.0](https://github.com/pusher/chatkit-server-php/compare/1.0.0...1.1.0) - 2018-11-08 79 | 80 | ### Added 81 | 82 | - `custom_data` option that can be passed to `createRoom` and `updateRoom` 83 | 84 | ## [1.0.0](https://github.com/pusher/chatkit-server-php/compare/0.5.9...1.0.0) - 2018-10-30 85 | 86 | ### Changed 87 | 88 | ### Breaking Changes 89 | 90 | - `getUsersByIds` is now `getUsersById` 91 | - `getUsers` now takes an array with an optional `from_timestamp` key instead of a `from_ts` key 92 | - `getUserReadCursors` is now `getReadCursorsForUser` 93 | 94 | ### Added 95 | 96 | - The following new methods: 97 | - `generateSuToken` 98 | - `createUsers` 99 | - `getUser` 100 | - `updateRoom` 101 | - `deleteRoom` 102 | - `getRoom` 103 | - `getRooms` 104 | - `getUserJoinableRooms` 105 | - `addUsersToRoom` 106 | - `removeUsersFromRoom` 107 | - `createGlobalRole` 108 | - `createRoomRole` 109 | - `deleteGlobalRole` 110 | - `deleteRoomRole` 111 | - `assignGlobalRoleToUser` 112 | - `assignRoomRoleToUser` 113 | - `getRoles` 114 | - `getUserRoles` 115 | - `removeGlobalRoleForUser` 116 | - `removeRoomRoleForUser` 117 | - `getPermissionsForGlobalRole` 118 | - `getPermissionsForRoomRole` 119 | - `updatePermissionsForGlobalRole` 120 | - `updatePermissionsForRoomRole` 121 | - `getReadCursor` 122 | - `getReadCursorsForRoom` 123 | - `apiRequest` 124 | - `authorizerRequest` 125 | - `cursorsRequest` 126 | 127 | ## [0.5.9](https://github.com/pusher/chatkit-server-php/compare/0.5.8...0.5.9) - 2018-08-29 128 | 129 | ### Added 130 | 131 | - `getRooms` functionality added. [#31](https://github.com/pusher/chatkit-server-php/pull/31) by [@philipnjuguna66](https://github.com/philipnjuguna66) 132 | 133 | ## [0.5.8](https://github.com/pusher/chatkit-server-php/compare/0.5.7...0.5.8) - 2018-08-21 134 | 135 | ### Added 136 | 137 | - `addUsersToRoom` and `removeUsersFromRoom` functionality added. [#29](https://github.com/pusher/chatkit-server-php/pull/29) by [@mludi](https://github.com/mludi) 138 | 139 | ## [0.5.7](https://github.com/pusher/chatkit-server-php/compare/0.5.6...0.5.7) - 2018-08-14 140 | 141 | ### Added 142 | 143 | - `joinRoom` functionality added. [#27](https://github.com/pusher/chatkit-server-php/pull/27) by [@mludi](https://github.com/mludi) 144 | 145 | ## [0.5.6](https://github.com/pusher/chatkit-server-php/compare/0.5.5...0.5.6) - 2018-07-30 146 | 147 | ### Fixed 148 | 149 | - `getUsersByIds` and `getUsers` now properly set query parameters 150 | 151 | ## [0.5.5](https://github.com/pusher/chatkit-server-php/compare/0.5.4...0.5.5) - 2018-07-30 152 | 153 | ### Added 154 | 155 | - `setReadCursor` functionality added. [#22](https://github.com/pusher/chatkit-server-php/pull/22) by [@morrislaptop](https://github.com/morrislaptop) 156 | - `getUserReadCursors` functionality added. [#22](https://github.com/pusher/chatkit-server-php/pull/22) by [@morrislaptop](https://github.com/morrislaptop) 157 | - `getRoomMessages` now supports providing an `initial_id`, a `limit`, and a `direction`. [#22](https://github.com/pusher/chatkit-server-php/pull/22) by [@morrislaptop](https://github.com/morrislaptop) 158 | 159 | ## [0.5.4](https://github.com/pusher/chatkit-server-php/compare/0.5.3...0.5.4) - 2018-06-25 160 | 161 | ### Added 162 | 163 | - `getRoomMessages` functionality added [#21](https://github.com/pusher/chatkit-server-php/pull/21) by [@morrislaptop](https://github.com/morrislaptop) 164 | 165 | ## [0.5.3](https://github.com/pusher/chatkit-server-php/compare/0.5.2...0.5.3) - 2018-06-11 166 | 167 | ### Added 168 | 169 | - `deleteRoom` functionality added [#19](https://github.com/pusher/chatkit-server-php/pull/19) by [@morrislaptop](https://github.com/morrislaptop) 170 | - `getUsers` functionality added [#20](https://github.com/pusher/chatkit-server-php/pull/20) by [@morrislaptop](https://github.com/morrislaptop) 171 | 172 | ## [0.5.2](https://github.com/pusher/chatkit-server-php/compare/0.5.1...0.5.2) - 2018-06-07 173 | 174 | ### Fixed 175 | 176 | - `updateUser` validates information properly [#18](https://github.com/pusher/chatkit-server-php/pull/18) by [@morrislaptop](https://github.com/morrislaptop) 177 | 178 | ### Added 179 | 180 | - `sendMessage` supports send messages with attachments [#18](https://github.com/pusher/chatkit-server-php/pull/18) by [@morrislaptop](https://github.com/morrislaptop) 181 | 182 | ## [0.5.1](https://github.com/pusher/chatkit-server-php/compare/0.5.0...0.5.1) - 2018-05-25 183 | 184 | ### Changed 185 | 186 | - User ID is validated to be a string as part of `createUser`, `authenticate`, and `generateAccessToken` calls 187 | 188 | ### Fixed 189 | 190 | - `getUserRooms` no longer crashes if `joinable` option not set 191 | 192 | ## [0.5.0](https://github.com/pusher/chatkit-server-php/compare/0.4.0...0.5.0) - 2018-05-11 193 | 194 | ### Changed 195 | 196 | - API calls to Chatkit servers now return an associative array that has the keys `'status'` and `'body'`. 197 | 198 | ## [0.4.0](https://github.com/pusher/chatkit-server-php/compare/0.3.0...0.4.0) - 2018-04-20 199 | 200 | ### Added 201 | 202 | - `authenticate` has been added. This should be the function you use to authenticate your users for Chatkit. 203 | 204 | You need to provide an associative array that has a `user_id` key with a string value. For example: 205 | 206 | ```php 207 | $chatkit->authenticate([ 'user_id' => 'ham' ]); 208 | ``` 209 | 210 | It returns an associative array that is structured like this: 211 | 212 | ```php 213 | [ 214 | 'status' => 200, 215 | 'headers' => [ 216 | 'Some-Header' => 'some-value' 217 | ], 218 | 'body' => [ 219 | 'access_token' => 'an.access.token', 220 | 'token_type' => 'bearer', 221 | 'expires_in' => 86400 222 | ] 223 | ] 224 | ``` 225 | 226 | where: 227 | 228 | * `status` is the suggested HTTP response status code, 229 | * `headers` are the suggested response headers, 230 | * `body` holds the token payload. 231 | 232 | ### Removed 233 | 234 | - `getTokenPair` has been removed 235 | 236 | ### Changed 237 | 238 | - Authentication no longer returns refresh tokens. 239 | 240 | If your client devices are running the: 241 | 242 | * Swift SDK - (**breaking change**) you must be using version `>= 0.8.0` of [chatkit-swift](https://github.com/pusher/chatkit-swift). 243 | * Android SDK - you won't be affected regardless of which version you are running. 244 | * JS SDK - you won't be affected regardless of which version you are running. 245 | 246 | - (Nearly) all functions now take an object (an associative array) as their sole parameter. As examples: 247 | 248 | * Constructing a Chatkit object is done like this: 249 | 250 | ```php 251 | $chatkit = new Chatkit\Chatkit([ 252 | 'instance_locator' => 'your:instance:locator', 253 | 'key' => 'your:key' 254 | ]); 255 | ``` 256 | 257 | * `createUser` is now called like this: 258 | 259 | ```php 260 | $chatkit->createUser([ 261 | 'id' => 'dr_php', 262 | 'name' => 'Dr PHP', 263 | 'avatar_url' => 'https://placekitten.com/400/500', 264 | 'custom_data' => [ 265 | 'a' => 'piece of data' 266 | ] 267 | ]); 268 | ``` 269 | 270 | * `authenticate` (previously `getTokenPair`) is now called like this: 271 | 272 | ```php 273 | $chatkit->authenticate([ 274 | 'user_id' => 'dr_php' 275 | ]); 276 | ``` 277 | 278 | * `createRoom` is now called like this: 279 | 280 | ```php 281 | $chatkit->createRoom([ 282 | 'creator_id' => 'dr_php', 283 | 'name' => 'A room with a name', 284 | 'private' => false 285 | ]); 286 | ``` 287 | -------------------------------------------------------------------------------- /src/Chatkit.php: -------------------------------------------------------------------------------- 1 | 'https', 17 | 'port' => 80, 18 | 'timeout' => 30, 19 | 'debug' => false, 20 | 'curl_options' => array(), 21 | ); 22 | protected $logger = null; 23 | protected $ch = null; // Curl handler 24 | 25 | protected $api_settings = array(); 26 | protected $authorizer_settings = array(); 27 | protected $cursor_settings = array(); 28 | 29 | const GLOBAL_SCOPE = 'global'; 30 | const ROOM_SCOPE = 'room'; 31 | 32 | /** 33 | * 34 | * Initializes a new Chatkit instance. 35 | * 36 | * 37 | * @param array $options Options to configure the Chatkit instance. 38 | * instance_locator - your Chatkit instance locator 39 | * key - your Chatkit instance's key 40 | * scheme - e.g. http or https 41 | * host - the host; no trailing forward slash. 42 | * port - the http port 43 | * timeout - the http timeout 44 | */ 45 | public function __construct($options) 46 | { 47 | $this->checkCompatibility(); 48 | 49 | if (!isset($options['instance_locator'])) { 50 | throw new MissingArgumentException('You must provide an instance_locator'); 51 | } 52 | if (!isset($options['key'])) { 53 | throw new MissingArgumentException('You must provide a key'); 54 | } 55 | 56 | $this->settings['instance_locator'] = $options['instance_locator']; 57 | $this->settings['key'] = $options['key']; 58 | $this->api_settings['service_name'] = 'chatkit'; 59 | $this->api_settings['service_version'] = 'v6'; 60 | $this->api_settings_v2['service_name'] = 'chatkit'; 61 | $this->api_settings_v2['service_version'] = 'v2'; 62 | $this->authorizer_settings['service_name'] = 'chatkit_authorizer'; 63 | $this->authorizer_settings['service_version'] = 'v2'; 64 | $this->cursor_settings['service_name'] = 'chatkit_cursors'; 65 | $this->cursor_settings['service_version'] = 'v2'; 66 | $this->scheduler_settings['service_name'] = 'chatkit_scheduler'; 67 | $this->scheduler_settings['service_version'] = 'v1'; 68 | 69 | foreach ($options as $key => $value) { 70 | // only set if valid setting/option 71 | if (isset($this->settings[$key])) { 72 | $this->settings[$key] = $value; 73 | } 74 | } 75 | } 76 | 77 | public function authenticate($auth_options) 78 | { 79 | if (!isset($auth_options['user_id'])) { 80 | throw new MissingArgumentException('You must provide a user ID'); 81 | } 82 | 83 | $access_token = $this->generateAccessToken($auth_options)['token']; 84 | 85 | return [ 86 | 'status' => 200, 87 | 'headers' => array(), 88 | 'body' => [ 89 | 'access_token' => $access_token, 90 | 'token_type' => 'bearer', 91 | 'expires_in' => 24 * 60 * 60 92 | ] 93 | ]; 94 | } 95 | 96 | public function generateAccessToken($auth_options) 97 | { 98 | if (empty($auth_options)) { 99 | throw new MissingArgumentException('You must provide a either a user_id or `su: true`'); 100 | } 101 | return $this->generateToken($auth_options); 102 | } 103 | 104 | public function generateSuToken($auth_options = []) 105 | { 106 | $auth_options = array_merge($auth_options, [ 'su' => true ]); 107 | return $this->generateToken($auth_options); 108 | } 109 | 110 | public function generateToken($auth_options = []) 111 | { 112 | $split_instance_locator = explode(':', $this->settings['instance_locator']); 113 | $split_key = explode(':', $this->settings['key']); 114 | 115 | $now = time(); 116 | $claims = array( 117 | 'instance' => $split_instance_locator[2], 118 | 'iss' => 'api_keys/'.$split_key[0], 119 | 'iat' => $now 120 | ); 121 | 122 | if (isset($auth_options['user_id'])) { 123 | if (gettype($auth_options['user_id']) != 'string') { 124 | throw new TypeMismatchException('User ID must be a string'); 125 | } 126 | $claims['sub'] = $auth_options['user_id']; 127 | } 128 | 129 | if (isset($auth_options['su']) && $auth_options['su'] === true) { 130 | $claims['su'] = true; 131 | } 132 | 133 | // not using functions such as `strtotime` as timezones/daylight savings are problematic 134 | $claims['exp'] = $now + 24 * 60 * 60; 135 | 136 | $jwt = JWT::encode($claims, $split_key[1]); 137 | return [ 138 | 'token' => $jwt, 139 | 'expires_in' => 24 * 60 * 60 140 | ]; 141 | } 142 | 143 | /** 144 | * Set a logger to be informed of internal log messages. 145 | * 146 | * @return void 147 | */ 148 | public function setLogger($logger) 149 | { 150 | $this->logger = $logger; 151 | } 152 | 153 | /** 154 | * Log a string. 155 | * 156 | * @param string $msg The message to log 157 | * 158 | * @return void 159 | */ 160 | protected function log($msg) 161 | { 162 | if (is_null($this->logger) === false) { 163 | $this->logger->log('Chatkit: '.$msg); 164 | } 165 | } 166 | 167 | /** 168 | * Check if the current PHP setup is sufficient to run this class. 169 | * 170 | * @throws ChatkitException if any required dependencies are missing 171 | * 172 | * @return void 173 | */ 174 | protected function checkCompatibility() 175 | { 176 | if (!extension_loaded('curl')) { 177 | throw new ConfigurationException('The Chatkit SDK requires the PHP cURL module. Please ensure it is installed'); 178 | } 179 | 180 | if (!extension_loaded('json')) { 181 | throw new ConfigurationException('The Chatkit SDK requires the PHP JSON module. Please ensure it is installed'); 182 | } 183 | } 184 | 185 | // User API 186 | 187 | public function createUser($options) 188 | { 189 | if (!isset($options['id'])) { 190 | throw new MissingArgumentException('You must provide an ID'); 191 | } 192 | if (gettype($options['id']) != 'string') { 193 | throw new TypeMismatchException('User ID must be a string'); 194 | } 195 | if (!isset($options['name'])) { 196 | throw new MissingArgumentException('You must provide a name'); 197 | } 198 | 199 | $body = array( 200 | 'id' => $options['id'], 201 | 'name' => $options['name'] 202 | ); 203 | 204 | if (isset($options['avatar_url']) && !is_null($options['avatar_url'])) { 205 | $body['avatar_url'] = $options['avatar_url']; 206 | } 207 | 208 | if (isset($options['custom_data']) && !is_null($options['custom_data'])) { 209 | $body['custom_data'] = $options['custom_data']; 210 | } 211 | 212 | $options = [ 213 | 'method' => 'POST', 214 | 'path' => '/users', 215 | 'jwt' => $this->getServerToken()['token'], 216 | 'body' => $body 217 | ]; 218 | 219 | return $this->apiRequest($options); 220 | } 221 | 222 | public function createUsers($options) 223 | { 224 | if (!isset($options['users'])) { 225 | throw new MissingArgumentException('You must provide a list of users you want to create'); 226 | } 227 | 228 | $options = [ 229 | 'method' => 'POST', 230 | 'path' => '/batch_users', 231 | 'jwt' => $this->getServerToken()['token'], 232 | 'body' => $options 233 | ]; 234 | 235 | return $this->apiRequest($options); 236 | } 237 | 238 | public function updateUser($options) 239 | { 240 | if (!isset($options['id'])) { 241 | throw new MissingArgumentException('You must provide the ID of the user you want to update'); 242 | } 243 | 244 | $body = array(); 245 | 246 | if (isset($options['name']) && !is_null($options['name'])) { 247 | $body['name'] = $options['name']; 248 | } 249 | if (isset($options['avatar_url']) && !is_null($options['avatar_url'])) { 250 | $body['avatar_url'] = $options['avatar_url']; 251 | } 252 | if (isset($options['custom_data']) && !is_null($options['custom_data'])) { 253 | $body['custom_data'] = $options['custom_data']; 254 | } 255 | 256 | if (empty($body)) { 257 | throw new MissingArgumentException('At least one of the following are required: name, avatar_url, or custom_data.'); 258 | } 259 | 260 | $user_id = rawurlencode($options['id']); 261 | $token = $this->getServerToken([ 'user_id' => $user_id ])['token']; 262 | 263 | return $this->apiRequest([ 264 | 'method' => 'PUT', 265 | 'path' => "/users/$user_id", 266 | 'jwt' => $token, 267 | 'body' => $body 268 | ]); 269 | } 270 | 271 | public function deleteUser($options) 272 | { 273 | if (!isset($options['id'])) { 274 | throw new MissingArgumentException('You must provide the ID of the user you want to delete'); 275 | } 276 | 277 | $user_id = rawurlencode($options['id']); 278 | 279 | return $this->apiRequest([ 280 | 'method' => 'DELETE', 281 | 'path' => "/users/$user_id", 282 | 'jwt' => $this->getServerToken()['token'] 283 | ]); 284 | } 285 | 286 | public function asyncDeleteUser($options) 287 | { 288 | if (!isset($options['id'])) { 289 | throw new MissingArgumentException('You must provide the ID of the user you want to delete'); 290 | } 291 | 292 | $user_id = rawurlencode($options['id']); 293 | 294 | return $this->schedulerRequest([ 295 | 'method' => 'PUT', 296 | 'path' => "/users/$user_id", 297 | 'jwt' => $this->getServerToken()['token'] 298 | ]); 299 | } 300 | 301 | public function getDeleteStatus($options) 302 | { 303 | if (!isset($options['id'])) { 304 | throw new MissingArgumentException('You must provide the ID of the job to query status of'); 305 | } 306 | 307 | $job_id = rawurlencode($options['id']); 308 | 309 | return $this->schedulerRequest([ 310 | 'method' => 'GET', 311 | 'path' => "/status/$job_id", 312 | 'jwt' => $this->getServerToken()['token'] 313 | ]); 314 | } 315 | 316 | public function getUser($options) 317 | { 318 | if (!isset($options['id'])) { 319 | throw new MissingArgumentException('You must provide the ID of the user you want to fetch'); 320 | } 321 | 322 | $user_id = rawurlencode($options['id']); 323 | 324 | return $this->apiRequest([ 325 | 'method' => 'GET', 326 | 'path' => "/users/$user_id", 327 | 'jwt' => $this->getServerToken()['token'] 328 | ]); 329 | } 330 | 331 | /** 332 | * $options['from_timestamp'] should be in the B8601DZw.d format 333 | * 334 | * e.g. 2018-04-17T14:02:00Z 335 | */ 336 | public function getUsers($options = []) 337 | { 338 | $query_params = []; 339 | 340 | if (!empty($options['from_timestamp'])) { 341 | $query_params['from_ts'] = $options['from_timestamp']; 342 | } 343 | 344 | if (!empty($options['limit'])) { 345 | $query_params['limit'] = $options['limit']; 346 | } 347 | 348 | return $this->apiRequest([ 349 | 'method' => 'GET', 350 | 'path' => '/users', 351 | 'jwt' => $this->getServerToken()['token'], 352 | 'query' => $query_params 353 | ]); 354 | } 355 | 356 | public function getUsersByID($options) 357 | { 358 | if (!isset($options['user_ids'])) { 359 | throw new MissingArgumentException('You must provide the IDs of the users you want to fetch'); 360 | } 361 | 362 | return $this->apiRequest([ 363 | 'method' => 'GET', 364 | 'path' => '/users_by_ids', 365 | 'jwt' => $this->getServerToken()['token'], 366 | 'query' => [ 'id' => $options['user_ids'] ] 367 | ]); 368 | } 369 | 370 | // Room API 371 | 372 | /** 373 | * Creates a new room. 374 | * 375 | * @param array $options The room options 376 | * [Available Options] 377 | * • creator_id (string|required): Represents the ID of the user that you want to create the room. 378 | * • name (string|optional): Represents the name with which the room is identified. 379 | * A room name must not be longer than 40 characters and can only contain lowercase letters, 380 | * numbers, underscores and hyphens. 381 | * • private (boolean|optional): Indicates if a room should be private or public. Private by default. 382 | * • user_ids (array|optional): If you wish to add users to the room at the point of creation, 383 | * you may provide their user IDs. 384 | * • custom_data (assoc array|optional): If you wish to attach some custom data to a room, 385 | * you may provide a list of key value pairs. 386 | * @return array 387 | */ 388 | public function createRoom($options) 389 | { 390 | if (!isset($options['creator_id'])) { 391 | throw new MissingArgumentException('You must provide the ID of the user creating the room'); 392 | } 393 | if (!isset($options['name'])) { 394 | throw new MissingArgumentException('You must provide a name for the room'); 395 | } 396 | 397 | $body = [ 398 | 'name' => $options['name'], 399 | 'private' => false 400 | ]; 401 | if (isset($options['id'])) { 402 | $body['id'] = $options['id']; 403 | } 404 | if (isset($options['private'])) { 405 | $body['private'] = $options['private']; 406 | } 407 | if (isset($options['user_ids'])) { 408 | $body['user_ids'] = $options['user_ids']; 409 | } 410 | if (isset($options['push_notification_title_override'])) { 411 | $body['push_notification_title_override'] = $options['push_notification_title_override']; 412 | } 413 | if (isset($options['custom_data'])) { 414 | $body['custom_data'] = $options['custom_data']; 415 | } 416 | 417 | $token = $this->getServerToken([ 'user_id' => $options['creator_id'] ])['token']; 418 | 419 | return $this->apiRequest([ 420 | 'method' => 'POST', 421 | 'path' => '/rooms', 422 | 'jwt' => $token, 423 | 'body' => $body 424 | ]); 425 | } 426 | 427 | public function updateRoom($options) 428 | { 429 | if (!isset($options['id'])) { 430 | throw new MissingArgumentException('You must provide the ID of the room to update'); 431 | } 432 | 433 | $body = []; 434 | if (isset($options['private'])) { 435 | $body['private'] = $options['private']; 436 | } 437 | if (isset($options['name'])) { 438 | $body['name'] = $options['name']; 439 | } 440 | if (array_key_exists('push_notification_title_override', $options)) { // We want to accept null 441 | $body['push_notification_title_override'] = $options['push_notification_title_override']; 442 | } 443 | if (isset($options['custom_data'])) { 444 | $body['custom_data'] = $options['custom_data']; 445 | } 446 | 447 | $room_id = rawurlencode($options['id']); 448 | 449 | return $this->apiRequest([ 450 | 'method' => 'PUT', 451 | 'path' => "/rooms/$room_id", 452 | 'jwt' => $this->getServerToken()['token'], 453 | 'body' => $body 454 | ]); 455 | } 456 | 457 | public function deleteRoom($options) 458 | { 459 | if (!isset($options['id'])) { 460 | throw new MissingArgumentException('You must provide the ID of the room to delete'); 461 | } 462 | 463 | $room_id = rawurlencode($options['id']); 464 | 465 | return $this->apiRequest([ 466 | 'method' => 'DELETE', 467 | 'path' => "/rooms/$room_id", 468 | 'jwt' => $this->getServerToken()['token'] 469 | ]); 470 | } 471 | 472 | public function asyncDeleteRoom($options) 473 | { 474 | if (!isset($options['id'])) { 475 | throw new MissingArgumentException('You must provide the ID of the room to delete'); 476 | } 477 | 478 | $room_id = rawurlencode($options['id']); 479 | 480 | return $this->schedulerRequest([ 481 | 'method' => 'PUT', 482 | 'path' => "/rooms/$room_id", 483 | 'jwt' => $this->getServerToken()['token'] 484 | ]); 485 | } 486 | 487 | public function getRoom($options) 488 | { 489 | if (!isset($options['id'])) { 490 | throw new MissingArgumentException('You must provide the ID of the room to fetch'); 491 | } 492 | 493 | $room_id = rawurlencode($options['id']); 494 | 495 | return $this->apiRequest([ 496 | 'method' => 'GET', 497 | 'path' => "/rooms/$room_id", 498 | 'jwt' => $this->getServerToken()['token'] 499 | ]); 500 | } 501 | 502 | public function getRooms($options = []) 503 | { 504 | $query_params = []; 505 | 506 | if (!empty($options['from_id'])) { 507 | $query_params['from_id'] = $options['from_id']; 508 | } 509 | 510 | if (!empty($options['include_private'])) { 511 | $query_params['include_private'] = $options['include_private']; 512 | } 513 | 514 | return $this->apiRequest([ 515 | 'method' => 'GET', 516 | 'path' => "/rooms", 517 | 'jwt' => $this->getServerToken()['token'], 518 | 'query' => $query_params 519 | ]); 520 | } 521 | 522 | /** 523 | * Get all rooms a user belongs to 524 | */ 525 | public function getUserRooms($options) 526 | { 527 | return $this->getRoomsForUser($options); 528 | } 529 | 530 | /** 531 | * Get all rooms that are joinable for a given user 532 | */ 533 | public function getUserJoinableRooms($options) 534 | { 535 | $options = array_merge($options, [ 'joinable' => true ]); 536 | return $this->getRoomsForUser($options); 537 | } 538 | 539 | public function addUsersToRoom($options) 540 | { 541 | if (!isset($options['room_id'])) { 542 | throw new MissingArgumentException('You must provide the ID of the room you want to add users to'); 543 | } 544 | if (!isset($options['user_ids'])) { 545 | throw new MissingArgumentException('You must provide a list of IDs of the users you want to add to the room'); 546 | } 547 | 548 | $room_id = rawurlencode($options['room_id']); 549 | 550 | return $this->apiRequest([ 551 | 'method' => 'PUT', 552 | 'path' => "/rooms/$room_id/users/add", 553 | 'jwt' => $this->getServerToken()['token'], 554 | 'body' => [ 'user_ids' => $options['user_ids'] ] 555 | ]); 556 | } 557 | 558 | public function removeUsersFromRoom($options) 559 | { 560 | if (!isset($options['room_id'])) { 561 | throw new MissingArgumentException('You must provide the ID of the room you want to remove users from'); 562 | } 563 | if (!isset($options['user_ids'])) { 564 | throw new MissingArgumentException('You must provide a list of IDs of the users you want to remove from the room'); 565 | } 566 | 567 | $room_id = rawurlencode($options['room_id']); 568 | 569 | return $this->apiRequest([ 570 | 'method' => 'PUT', 571 | 'path' => "/rooms/$room_id/users/remove", 572 | 'jwt' => $this->getServerToken()['token'], 573 | 'body' => [ 'user_ids' => $options['user_ids'] ] 574 | ]); 575 | } 576 | 577 | # Messages API 578 | 579 | /** 580 | * Get messages in a room 581 | * 582 | * @param array $options 583 | * [Available Options] 584 | * • room_id (string|required): Represents the ID of the room that you want to get the messages for. 585 | * • initial_id (integer|optional): Starting ID of the range of messages. 586 | * • limit (integer|optional): Number of messages to return 587 | * • direction (string|optional): Order of messages - one of newer or older 588 | * @return array 589 | * @throws ChatkitException or MissingArgumentException 590 | */ 591 | public function getRoomMessages($options) 592 | { 593 | if (!isset($options['room_id'])) { 594 | throw new MissingArgumentException('You must provide the ID of the room to fetch messages from'); 595 | } 596 | 597 | $query_params = []; 598 | if (!empty($options['initial_id'])) { 599 | $query_params['initial_id'] = $options['initial_id']; 600 | } 601 | if (!empty($options['limit'])) { 602 | $query_params['limit'] = $options['limit']; 603 | } 604 | if (!empty($options['direction'])) { 605 | $query_params['direction'] = $options['direction']; 606 | } 607 | 608 | $room_id = rawurlencode($options['room_id']); 609 | 610 | return $this->apiRequestV2([ 611 | 'method' => 'GET', 612 | 'path' => "/rooms/$room_id/messages", 613 | 'jwt' => $this->getServerToken()['token'], 614 | 'query' => $query_params 615 | ]); 616 | } 617 | 618 | public function sendMessage($options) 619 | { 620 | verify([SENDER_ID, 621 | ROOM_ID, 622 | [ 'text' => [ 623 | 'type' => 'string', 624 | 'missing_message' => 625 | 'You must provide some text for the message' ] 626 | ] 627 | ], $options); 628 | 629 | $body = array( 630 | 'text' => $options['text'] 631 | ); 632 | 633 | if (isset($options['attachment'])) { 634 | if (!isset($options['attachment']['resource_link'])) { 635 | throw new MissingArgumentException('You must provide a resource_link for the message attachment'); 636 | } 637 | 638 | $valid_file_types = ['image', 'video', 'audio', 'file']; 639 | if (!isset($options['attachment']['type']) || !in_array($options['attachment']['type'], $valid_file_types)) { 640 | $valid_file_types_str = implode(',', $valid_file_types); 641 | throw new MissingArgumentException("You must provide the type for the attachment. This can be one of $valid_file_types_str"); 642 | } 643 | 644 | $body['attachment'] = array( 645 | 'resource_link' => $options['attachment']['resource_link'], 646 | 'type' => $options['attachment']['type'] 647 | ); 648 | } 649 | 650 | $token = $this->getServerToken([ 'user_id' => $options['sender_id'] ])['token']; 651 | $room_id = rawurlencode($options['room_id']); 652 | 653 | return $this->apiRequestV2([ 654 | 'method' => 'POST', 655 | 'path' => "/rooms/$room_id/messages", 656 | 'jwt' => $token, 657 | 'body' => $body 658 | ]); 659 | } 660 | 661 | public function sendSimpleMessage($options) 662 | { 663 | verify([ [ 'text' => [ 664 | 'type' => 'string', 665 | 'missing_message' => 666 | 'You must provide some text for the message' ] ] 667 | ], $options); 668 | 669 | $options['parts'] = [ [ 'type' => 'text/plain', 670 | 'content' => $options['text'] ] 671 | ]; 672 | unset($options['text']); 673 | return $this->sendMultipartMessage($options); 674 | } 675 | 676 | public function sendMultipartMessage($options) 677 | { 678 | verify([SENDER_ID, 679 | ROOM_ID, 680 | [ 'parts' => [ 681 | 'type' => 'non_empty_array', 682 | 'missing_message' => 683 | 'You must provide a non-empty parts array' ] 684 | ] 685 | ], $options); 686 | 687 | // this assumes the token lives long enough to finish all S3 uploads 688 | $token = $this->getServerToken([ 'user_id' => $options['sender_id'] ])['token']; 689 | $room_id = rawurlencode($options['room_id']); 690 | 691 | foreach($options['parts'] as &$part) { 692 | verify([ [ 'type' => [ 'type' => 'string', 693 | 'missing_message' => 'Each part must have a type' ] ], 694 | [ 'file' => OPTIONAL_STRING ], 695 | [ 'content' => OPTIONAL_STRING ], 696 | [ 'url' => OPTIONAL_STRING ], 697 | [ 'name' => OPTIONAL_STRING ], 698 | [ 'customData' => [ 'type' => 'json', 'optional' => true ] ] 699 | ], $part); 700 | 701 | if (!isset($part['content']) && !isset($part['url']) && !isset($part['file'])) { 702 | throw new MissingArgumentException('Each part must define either file, content or url'); 703 | } 704 | 705 | if (isset($part['file'])) { 706 | $attachment_id = $this->uploadAttachment($token, $room_id, $part); 707 | $part['attachment'] = [ 'id' => $attachment_id ]; 708 | unset($part['file']); 709 | } 710 | 711 | } 712 | 713 | return $this->apiRequest([ 714 | 'method' => 'POST', 715 | 'path' => "/rooms/$room_id/messages", 716 | 'jwt' => $token, 717 | 'body' => [ 'parts' => $options['parts'] ] 718 | ]); 719 | } 720 | 721 | public function fetchMultipartMessage($options) 722 | { 723 | verify([ROOM_ID, MESSAGE_ID], $options); 724 | 725 | $message_id = $options['message_id']; 726 | $room_id = rawurlencode($options['room_id']); 727 | 728 | return $this->apiRequest([ 729 | 'method' => 'GET', 730 | 'path' => "/rooms/$room_id/messages/$message_id", 731 | 'jwt' => $this->getServerToken()['token'] 732 | ]); 733 | } 734 | 735 | public function fetchMultipartMessages($options) 736 | { 737 | verify([ROOM_ID, 738 | [ 'limit' => OPTIONAL_INT, 739 | 'direction' => OPTIONAL_STRING, 740 | 'initial_id' => OPTIONAL_STRING, 741 | ] 742 | ], $options); 743 | 744 | $optional_fields = ['limit', 'direction', 'initial_id']; 745 | $query_params = $this->getOptionalFields($optional_fields, $options); 746 | $room_id = rawurlencode($options['room_id']); 747 | 748 | return $this->apiRequest([ 749 | 'method' => 'GET', 750 | 'path' => "/rooms/$room_id/messages", 751 | 'jwt' => $this->getServerToken()['token'], 752 | 'query' => $query_params 753 | ]); 754 | } 755 | 756 | public function editMessage($room_id, $message_id, $options) 757 | { 758 | verify([ROOM_ID,MESSAGE_ID], ['room_id' => $room_id, 'message_id' => $message_id]); 759 | verify([SENDER_ID, 760 | [ 'text' => [ 761 | 'type' => 'string', 762 | 'missing_message' => 763 | 'You must provide some text for the message' ] 764 | ] 765 | ], $options); 766 | if (isset($options['room_id'])) { 767 | throw new UnexpectedArgumentException('a messages room ids cannot be edited'); 768 | } 769 | if (isset($options['message_id'])) { 770 | throw new UnexpectedArgumentException('a messages id cannot be edited'); 771 | } 772 | 773 | $body = array( 774 | 'text' => $options['text'] 775 | ); 776 | 777 | if (isset($options['attachment'])) { 778 | if (!isset($options['attachment']['resource_link'])) { 779 | throw new MissingArgumentException('You must provide a resource_link for the message attachment'); 780 | } 781 | 782 | $valid_file_types = ['image', 'video', 'audio', 'file']; 783 | if (!isset($options['attachment']['type']) || !in_array($options['attachment']['type'], $valid_file_types)) { 784 | $valid_file_types_str = implode(',', $valid_file_types); 785 | throw new MissingArgumentException("You must provide the type for the attachment. This can be one of $valid_file_types_str"); 786 | } 787 | 788 | $body['attachment'] = array( 789 | 'resource_link' => $options['attachment']['resource_link'], 790 | 'type' => $options['attachment']['type'] 791 | ); 792 | } 793 | 794 | $token = $this->getServerToken([ 'user_id' => $options['sender_id'] ])['token']; 795 | $room_id = rawurlencode($room_id); 796 | 797 | return $this->apiRequestV2([ 798 | 'method' => 'PUT', 799 | 'path' => "/rooms/$room_id/messages/$message_id", 800 | 'jwt' => $token, 801 | 'body' => $body 802 | ]); 803 | } 804 | 805 | public function editSimpleMessage($room_id, $message_id, $options) 806 | { 807 | verify([ [ 'text' => [ 808 | 'type' => 'string', 809 | 'missing_message' => 810 | 'You must provide some text for the message' ] ] 811 | ], $options); 812 | 813 | $options['parts'] = [ [ 'type' => 'text/plain', 814 | 'content' => $options['text'] ] 815 | ]; 816 | unset($options['text']); 817 | return $this->editMultipartMessage($room_id, $message_id, $options); 818 | } 819 | 820 | public function editMultipartMessage($room_id, $message_id, $options) 821 | { 822 | verify([ROOM_ID,MESSAGE_ID], ['room_id' => $room_id, 'message_id' => $message_id]); 823 | verify([SENDER_ID, 824 | [ 'parts' => [ 825 | 'type' => 'non_empty_array', 826 | 'missing_message' => 827 | 'You must provide a non-empty parts array' ] 828 | ] 829 | ], $options); 830 | if (isset($options['room_id'])) { 831 | throw new UnexpectedArgumentException('a messages room id cannot be edited'); 832 | } 833 | if (isset($options['message_id'])) { 834 | throw new UnexpectedArgumentException('a message id cannot be edited'); 835 | } 836 | 837 | // this assumes the token lives long enough to finish all S3 uploads 838 | $token = $this->getServerToken([ 'user_id' => $options['sender_id'] ])['token']; 839 | $room_id = rawurlencode($room_id); 840 | 841 | foreach($options['parts'] as &$part) { 842 | verify([ [ 'type' => [ 'type' => 'string', 843 | 'missing_message' => 'Each part must have a type' ] ], 844 | [ 'file' => OPTIONAL_STRING ], 845 | [ 'content' => OPTIONAL_STRING ], 846 | [ 'url' => OPTIONAL_STRING ], 847 | [ 'name' => OPTIONAL_STRING ], 848 | [ 'customData' => [ 'type' => 'json', 'optional' => true ] ] 849 | ], $part); 850 | 851 | if (!isset($part['content']) && !isset($part['url']) && !isset($part['file'])) { 852 | throw new MissingArgumentException('Each part must define either file, content or url'); 853 | } 854 | 855 | if (isset($part['file'])) { 856 | $attachment_id = $this->uploadAttachment($token, $room_id, $part); 857 | $part['attachment'] = [ 'id' => $attachment_id ]; 858 | unset($part['file']); 859 | } 860 | 861 | } 862 | return $this->apiRequest([ 863 | 'method' => 'PUT', 864 | 'path' => "/rooms/$room_id/messages/$message_id", 865 | 'jwt' => $token, 866 | 'body' => [ 'parts' => $options['parts'] ] 867 | ]); 868 | } 869 | 870 | public function deleteMessage($options) 871 | { 872 | if (!isset($options['message_id'])) { 873 | throw new MissingArgumentException('You must provide the ID of the message to delete'); 874 | } 875 | 876 | if (!isset($options['room_id'])) { 877 | throw new MissingArgumentException('You must provide the ID of the room to which the message belongs'); 878 | } 879 | 880 | $message_id = $options['message_id']; 881 | $room_id = $options['room_id']; 882 | 883 | return $this->apiRequest([ 884 | 'method' => 'DELETE', 885 | 'path' => "/rooms/$room_id/messages/$message_id", 886 | 'jwt' => $this->getServerToken()['token'] 887 | ]); 888 | } 889 | 890 | // Roles and permissions API 891 | 892 | public function createGlobalRole($options) 893 | { 894 | $options['scope'] = self::GLOBAL_SCOPE; 895 | return $this->createRole($options); 896 | } 897 | 898 | public function createRoomRole($options) 899 | { 900 | $options['scope'] = self::ROOM_SCOPE; 901 | return $this->createRole($options); 902 | } 903 | 904 | public function deleteGlobalRole($options) 905 | { 906 | $options['scope'] = self::GLOBAL_SCOPE; 907 | return $this->deleteRole($options); 908 | } 909 | 910 | public function deleteRoomRole($options) 911 | { 912 | $options['scope'] = self::ROOM_SCOPE; 913 | return $this->deleteRole($options); 914 | } 915 | 916 | public function assignGlobalRoleToUser($options) 917 | { 918 | return $this->assignRoleToUser($options); 919 | } 920 | 921 | public function assignRoomRoleToUser($options) 922 | { 923 | if (!isset($options['room_id'])) { 924 | throw new MissingArgumentException('You must provide a room ID to assign a room role to a user'); 925 | } 926 | return $this->assignRoleToUser($options); 927 | } 928 | 929 | public function getRoles() 930 | { 931 | return $this->authorizerRequest([ 932 | 'method' => 'GET', 933 | 'path' => '/roles', 934 | 'jwt' => $this->getServerToken()['token'] 935 | ]); 936 | } 937 | 938 | public function getUserRoles($options) 939 | { 940 | if (!isset($options['user_id'])) { 941 | throw new MissingArgumentException('You must provide the ID of the user whose roles you want to fetch'); 942 | } 943 | 944 | $user_id = rawurlencode($options['user_id']); 945 | 946 | return $this->authorizerRequest([ 947 | 'method' => 'GET', 948 | 'path' => "/users/$user_id/roles", 949 | 'jwt' => $this->getServerToken()['token'] 950 | ]); 951 | } 952 | 953 | public function removeGlobalRoleForUser($options) 954 | { 955 | return $this->removeRoleForUser($options); 956 | } 957 | 958 | public function removeRoomRoleForUser($options) 959 | { 960 | if (!isset($options['room_id'])) { 961 | throw new MissingArgumentException('You must provide a room ID to remove a room role for a user'); 962 | } 963 | return $this->removeRoleForUser($options); 964 | } 965 | 966 | public function getPermissionsForGlobalRole($options) 967 | { 968 | $options['scope'] = self::GLOBAL_SCOPE; 969 | return $this->getPermissionsForRole($options); 970 | } 971 | 972 | public function getPermissionsForRoomRole($options) 973 | { 974 | $options['scope'] = self::ROOM_SCOPE; 975 | return $this->getPermissionsForRole($options); 976 | } 977 | 978 | public function updatePermissionsForGlobalRole($options) 979 | { 980 | $options['scope'] = self::GLOBAL_SCOPE; 981 | return $this->updatePermissionsForRole($options); 982 | } 983 | 984 | public function updatePermissionsForRoomRole($options) 985 | { 986 | $options['scope'] = self::ROOM_SCOPE; 987 | return $this->updatePermissionsForRole($options); 988 | } 989 | 990 | // Cursors API 991 | 992 | public function getReadCursor($options) 993 | { 994 | if (!isset($options['user_id'])) { 995 | throw new MissingArgumentException('You must provide the ID of the user whose read cursor you want to fetch'); 996 | } 997 | if (!isset($options['room_id'])) { 998 | throw new MissingArgumentException('You must provide the ID of the room that you want the read cursor for'); 999 | } 1000 | 1001 | $user_id = rawurlencode($options['user_id']); 1002 | $room_id = rawurlencode($options['room_id']); 1003 | 1004 | return $this->cursorsRequest([ 1005 | 'method' => 'GET', 1006 | 'path' => "/cursors/0/rooms/$room_id/users/$user_id", 1007 | 'jwt' => $this->getServerToken()['token'] 1008 | ]); 1009 | } 1010 | 1011 | /** 1012 | * Sets the read cursor for a user in a room. 1013 | * 1014 | * @param array $options 1015 | * [Available Options] 1016 | * • user_id (string|required): Represents the ID of the user that you want to set the cursor for. 1017 | * • room_id (string|required): Represents the ID of the room that you want to set the cursor for. 1018 | * • position (integer|required): Represents the ID of the message the user has read. 1019 | * @return array 1020 | * @throws ChatkitException or MissingArgumentException 1021 | */ 1022 | public function setReadCursor($options) 1023 | { 1024 | if (!isset($options['user_id'])) { 1025 | throw new MissingArgumentException('You must provide the ID of the user that you want to get the cursor for'); 1026 | } 1027 | if (!isset($options['room_id'])) { 1028 | throw new MissingArgumentException('You must provide the room ID of the room that you want to set the cursor for'); 1029 | } 1030 | if (!isset($options['position'])) { 1031 | throw new MissingArgumentException('You must provide the position of the cursor'); 1032 | } 1033 | 1034 | $user_id = rawurlencode($options['user_id']); 1035 | $room_id = rawurlencode($options['room_id']); 1036 | 1037 | return $this->cursorsRequest([ 1038 | 'method' => 'PUT', 1039 | 'path' => "/cursors/0/rooms/$room_id/users/$user_id", 1040 | 'jwt' => $this->getServerToken()['token'], 1041 | 'body' => ['position' => $options['position']] 1042 | ]); 1043 | } 1044 | 1045 | /** 1046 | * Get all read cursors for a user 1047 | * 1048 | * @param array $options 1049 | * [Available Options] 1050 | * • user_id (string|required): Represents the ID of the user that you want to get the read cursors for. 1051 | * @return array 1052 | * @throws ChatkitException or MissingArgumentException 1053 | */ 1054 | public function getReadCursorsForUser($options) 1055 | { 1056 | if (!isset($options['user_id'])) { 1057 | throw new MissingArgumentException('You must provide the ID of the user that you want the read cursors for'); 1058 | } 1059 | 1060 | $user_id = rawurlencode($options['user_id']); 1061 | 1062 | return $this->cursorsRequest([ 1063 | 'method' => 'GET', 1064 | 'path' => "/cursors/0/users/$user_id", 1065 | 'jwt' => $this->getServerToken()['token'] 1066 | ]); 1067 | } 1068 | 1069 | /** 1070 | * Get all read cursors for a room 1071 | * 1072 | * @param array $options 1073 | * [Available Options] 1074 | * • room_id (string|required): Represents the ID of the room that you want to get the read cursors for. 1075 | * @return array 1076 | * @throws ChatkitException or MissingArgumentException 1077 | */ 1078 | public function getReadCursorsForRoom($options) 1079 | { 1080 | if (!isset($options['room_id'])) { 1081 | throw new MissingArgumentException('You must provide the ID of the room that you want the read cursors for'); 1082 | } 1083 | 1084 | $room_id = rawurlencode($options['room_id']); 1085 | 1086 | return $this->cursorsRequest([ 1087 | 'method' => 'GET', 1088 | 'path' => "/cursors/0/rooms/$room_id", 1089 | 'jwt' => $this->getServerToken()['token'] 1090 | ]); 1091 | } 1092 | 1093 | // Service-specific helpers 1094 | 1095 | public function apiRequest($options) 1096 | { 1097 | return $this->makeRequest($this->api_settings, $options); 1098 | } 1099 | 1100 | // keep v2 for backwards compatibility 1101 | public function apiRequestV2($options) 1102 | { 1103 | return $this->makeRequest($this->api_settings_v2, $options); 1104 | } 1105 | 1106 | public function authorizerRequest($options) 1107 | { 1108 | return $this->makeRequest($this->authorizer_settings, $options); 1109 | } 1110 | 1111 | public function cursorsRequest($options) 1112 | { 1113 | return $this->makeRequest($this->cursor_settings, $options); 1114 | } 1115 | 1116 | public function schedulerRequest($options) 1117 | { 1118 | return $this->makeRequest($this->scheduler_settings, $options); 1119 | } 1120 | 1121 | protected function makeRequest($instance_settings, $options) 1122 | { 1123 | $options = array_merge($options, [ 'Content-Type' => 'application/json' ]); 1124 | 1125 | $ch = $this->createCurl( 1126 | $instance_settings, 1127 | $options['path'], 1128 | $options['jwt'], 1129 | $options['method'], 1130 | isset($options['body']) ? $options['body'] : null, 1131 | isset($options['query']) ? $options['query'] : [] 1132 | ); 1133 | 1134 | return $this->execCurl($ch); 1135 | } 1136 | 1137 | protected function getRoomsForUser($options) 1138 | { 1139 | if (!isset($options['id'])) { 1140 | throw new MissingArgumentException('You must provide the ID of the user that you want to get the rooms for'); 1141 | } 1142 | 1143 | $query_params = []; 1144 | if (!empty($options['joinable'])) { 1145 | $query_params['joinable'] = $options['joinable']; 1146 | } 1147 | 1148 | $user_id = rawurlencode($options['id']); 1149 | 1150 | return $this->apiRequest([ 1151 | 'method' => 'GET', 1152 | 'path' => "/users/$user_id/rooms", 1153 | 'jwt' => $this->getServerToken()['token'], 1154 | 'query' => $query_params 1155 | ]); 1156 | } 1157 | 1158 | protected function createRole($options) 1159 | { 1160 | if (!isset($options['name'])) { 1161 | throw new MissingArgumentException('You must provide a name for the role'); 1162 | } 1163 | 1164 | if (!isset($options['permissions'])) { 1165 | throw new MissingArgumentException("You must provide permissions for the role, even if it's an empty list"); 1166 | } 1167 | 1168 | return $this->authorizerRequest([ 1169 | 'method' => 'POST', 1170 | 'path' => '/roles', 1171 | 'jwt' => $this->getServerToken()['token'], 1172 | 'body' => [ 1173 | 'scope' => $options['scope'], 1174 | 'name' => $options['name'], 1175 | 'permissions' => $options['permissions'] 1176 | ] 1177 | ]); 1178 | } 1179 | 1180 | protected function deleteRole($options) 1181 | { 1182 | if (!isset($options['name'])) { 1183 | throw new MissingArgumentException("You must provide the role's name"); 1184 | } 1185 | 1186 | $role_name = rawurlencode($options['name']); 1187 | $scope = $options['scope']; 1188 | 1189 | return $this->authorizerRequest([ 1190 | 'method' => 'DELETE', 1191 | 'path' => "/roles/$role_name/scope/$scope", 1192 | 'jwt' => $this->getServerToken()['token'] 1193 | ]); 1194 | } 1195 | 1196 | protected function assignRoleToUser($options) 1197 | { 1198 | if (!isset($options['name'])) { 1199 | throw new MissingArgumentException("You must provide the role's name"); 1200 | } 1201 | 1202 | if (!isset($options['user_id'])) { 1203 | throw new MissingArgumentException('You must provide the ID of the user you want to assign the role to'); 1204 | } 1205 | 1206 | $body = [ 1207 | 'name' => $options['name'] 1208 | ]; 1209 | 1210 | if (isset($options['room_id'])) { 1211 | $body['room_id'] = $options['room_id']; 1212 | } 1213 | 1214 | $user_id = rawurlencode($options['user_id']); 1215 | 1216 | return $this->authorizerRequest([ 1217 | 'method' => 'PUT', 1218 | 'path' => "/users/$user_id/roles", 1219 | 'jwt' => $this->getServerToken()['token'], 1220 | 'body' => $body 1221 | ]); 1222 | } 1223 | 1224 | protected function removeRoleForUser($options) 1225 | { 1226 | if (!isset($options['user_id'])) { 1227 | throw new MissingArgumentException('You must provide the ID of the user you want to remove the role for'); 1228 | } 1229 | 1230 | $user_id = rawurlencode($options['user_id']); 1231 | 1232 | $req_opts = [ 1233 | 'method' => 'DELETE', 1234 | 'path' => "/users/$user_id/roles", 1235 | 'jwt' => $this->getServerToken()['token'], 1236 | ]; 1237 | 1238 | if (isset($options['room_id'])) { 1239 | $req_opts['query'] = [ 'room_id' => $options['room_id'] ]; 1240 | } 1241 | 1242 | return $this->authorizerRequest($req_opts); 1243 | } 1244 | 1245 | protected function getPermissionsForRole($options) 1246 | { 1247 | if (!isset($options['name'])) { 1248 | throw new MissingArgumentException('You must provide the name of the role you want to fetch the permissions of'); 1249 | } 1250 | 1251 | $role_name = rawurlencode($options['name']); 1252 | $scope = $options['scope']; 1253 | 1254 | return $this->authorizerRequest([ 1255 | 'method' => 'GET', 1256 | 'path' => "/roles/$role_name/scope/$scope/permissions", 1257 | 'jwt' => $this->getServerToken()['token'] 1258 | ]); 1259 | } 1260 | 1261 | protected function updatePermissionsForRole($options) 1262 | { 1263 | if (!isset($options['name'])) { 1264 | throw new MissingArgumentException('You must provide the name of the role you want to update the permissions of'); 1265 | } 1266 | 1267 | if ((!isset($options['permissions_to_add']) || empty($options['permissions_to_add'])) && (!isset($options['permissions_to_remove']) || empty($options['permissions_to_remove']))) { 1268 | throw new MissingArgumentException('permissions_to_add and permissions_to_remove cannot both be empty'); 1269 | } 1270 | 1271 | $role_name = rawurlencode($options['name']); 1272 | $scope = $options['scope']; 1273 | 1274 | $body = []; 1275 | if (isset($options['permissions_to_add']) && !empty($options['permissions_to_add'])) { 1276 | $body['add_permissions'] = $options['permissions_to_add']; 1277 | } 1278 | if (isset($options['permissions_to_remove']) && !empty($options['permissions_to_remove'])) { 1279 | $body['remove_permissions'] = $options['permissions_to_remove']; 1280 | } 1281 | 1282 | return $this->authorizerRequest([ 1283 | 'method' => 'PUT', 1284 | 'path' => "/roles/$role_name/scope/$scope/permissions", 1285 | 'jwt' => $this->getServerToken()['token'], 1286 | 'body' => $body 1287 | ]); 1288 | } 1289 | 1290 | protected function uploadAttachment($token, $room_id, $file_part) { 1291 | $body = $file_part['file']; 1292 | $content_length = strlen($body); 1293 | $content_type = $file_part['type']; 1294 | 1295 | if ($content_length <= 0 || is_null($body)) { 1296 | throw new MissingArgumentException('File contents size must be greater than 0'); 1297 | } 1298 | 1299 | $attachment_req = [ 'content_type' => $content_type, 1300 | 'content_length' => $content_length ]; 1301 | 1302 | foreach (['origin', 'name', 'customData'] as $field_name) { 1303 | if (isset($file_part[$field_name])) { 1304 | $attachment_req[$field_name] = $file_part[$field_name]; 1305 | } 1306 | } 1307 | 1308 | $attachment_response = $this->apiRequest([ 1309 | 'method' => 'POST', 1310 | 'path' => "/rooms/$room_id/attachments", 1311 | 'jwt' => $token, 1312 | 'body' => $attachment_req 1313 | ]); 1314 | 1315 | $url = $attachment_response['body']['upload_url']; 1316 | $ch = $this->createRawCurl('PUT', $url, $body, $content_type); 1317 | $upload_response = curl_exec($ch); 1318 | $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); 1319 | 1320 | if($status !== 200) { 1321 | throw (new UploadException('Failed to upload attachment', $status))->setBody($upload_response['body']); 1322 | } 1323 | 1324 | $attachment_id = $attachment_response['body']['attachment_id']; 1325 | return $attachment_id; 1326 | } 1327 | 1328 | protected function getOptionalFields($field_names, $options) { 1329 | $fields = []; 1330 | foreach ($field_names as $field_name) { 1331 | if(isset($options[$field_name])) { 1332 | $fields[$field_name] = $options[$field_name]; 1333 | } 1334 | } 1335 | 1336 | return $fields; 1337 | } 1338 | 1339 | /** 1340 | * Utility function used to create the curl object setup to interact with the Pusher API 1341 | */ 1342 | protected function createCurl($service_settings, $path, $jwt, $request_method, $body = null, $query_params = array()) 1343 | { 1344 | $split_instance_locator = explode(':', $this->settings['instance_locator']); 1345 | 1346 | $scheme = 'https'; 1347 | $host = $split_instance_locator[1].'.pusherplatform.io'; 1348 | $service_path_fragment = $service_settings['service_name'].'/'.$service_settings['service_version']; 1349 | $instance_id = $split_instance_locator[2]; 1350 | 1351 | $full_url = $scheme.'://'.$host.'/services/'.$service_path_fragment.'/'.$instance_id.$path; 1352 | $query = http_build_query($query_params); 1353 | // Passing foo = [1, 2, 3] to query params will encode it as foo[0]=1&foo[1]=2 1354 | // however, we want foo=1&foo=2 (to treat them as an array) 1355 | $query_string = preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', $query); 1356 | $final_url = $full_url.'?'.$query_string; 1357 | 1358 | $this->log('INFO: createCurl( '.$final_url.' )'); 1359 | 1360 | return $this->createRawCurl($request_method, $final_url, $body, null, $jwt, true); 1361 | } 1362 | 1363 | /** 1364 | * Utility function used to create the curl object with common settings. 1365 | */ 1366 | protected function createRawCurl($request_method, $url, $body = null, $content_type = null, $jwt = null, $encode_json = false) 1367 | { 1368 | // Create or reuse existing curl handle 1369 | if (null === $this->ch) { 1370 | $this->ch = curl_init(); 1371 | } 1372 | 1373 | if ($this->ch === false) { 1374 | throw new ConfigurationException('Could not initialise cURL!'); 1375 | } 1376 | 1377 | $ch = $this->ch; 1378 | 1379 | // curl handle is not reusable unless reset 1380 | if (function_exists('curl_reset')) { 1381 | curl_reset($ch); 1382 | } 1383 | 1384 | $headers = array(); 1385 | 1386 | if(!is_null($jwt)) { 1387 | array_push($headers, 'Authorization: Bearer '.$jwt); 1388 | } 1389 | if(!is_null($content_type)) { 1390 | array_push($headers, 'Content-Type: '.$content_type); 1391 | } 1392 | // Set cURL opts and execute request 1393 | curl_setopt($ch, CURLOPT_URL, $url); 1394 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 1395 | curl_setopt($ch, CURLOPT_TIMEOUT, $this->settings['timeout']); 1396 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $request_method); 1397 | 1398 | if (!is_null($body)) { 1399 | if ($encode_json) { 1400 | $body = json_encode($body, JSON_ERROR_UTF8); 1401 | array_push($headers, 'Content-Type: application/json'); 1402 | } 1403 | curl_setopt($ch, CURLOPT_POSTFIELDS, $body); 1404 | array_push($headers, 'Content-Length: '.strlen($body)); 1405 | } 1406 | curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 1407 | 1408 | // Set custom curl options 1409 | if (!empty($this->settings['curl_options'])) { 1410 | foreach ($this->settings['curl_options'] as $option => $value) { 1411 | curl_setopt($ch, $option, $value); 1412 | } 1413 | } 1414 | 1415 | return $ch; 1416 | } 1417 | 1418 | protected function getServerToken($options = []) 1419 | { 1420 | $token_options = [ 'su' => true ]; 1421 | if (isset($options['user_id']) && !is_null($options['user_id'])) { 1422 | $token_options['user_id'] = $options['user_id']; 1423 | } 1424 | return $this->generateAccessToken($token_options); 1425 | } 1426 | 1427 | /** 1428 | * Utility function to execute curl and create capture response information. 1429 | */ 1430 | protected function execCurl($ch) 1431 | { 1432 | $headers = []; 1433 | $response = []; 1434 | 1435 | curl_setopt($ch, CURLOPT_HEADERFUNCTION, 1436 | function($curl, $header) use (&$headers) 1437 | { 1438 | $len = strlen($header); 1439 | $header = explode(':', $header, 2); 1440 | if (count($header) < 2) { 1441 | return $len; 1442 | } 1443 | 1444 | $name = strtolower(trim($header[0])); 1445 | if (!array_key_exists($name, $headers)) { 1446 | $headers[$name] = [trim($header[1])]; 1447 | } else { 1448 | $headers[$name][] = trim($header[1]); 1449 | } 1450 | 1451 | return $len; 1452 | } 1453 | ); 1454 | 1455 | $data = curl_exec($ch); 1456 | $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); 1457 | $body = empty($data) ? null : json_decode($data, true); 1458 | 1459 | $response = [ 1460 | 'status' => $status, 1461 | 'headers' => $headers, 1462 | 'body' => $body 1463 | ]; 1464 | 1465 | // inform the user of a connection failure 1466 | if ($status == 0 || $response['body'] === false) { 1467 | throw new ConnectionException(curl_error($ch)); 1468 | } 1469 | 1470 | // or an error response from Chatkit 1471 | if ($status >= 400) { 1472 | $this->log('ERROR: execCurl error: '.print_r($response, true)); 1473 | throw (new ChatkitException($response['body']['error_description'], $status))->setBody($response['body']); 1474 | } 1475 | 1476 | $this->log('INFO: execCurl response: '.print_r($response, true)); 1477 | return $response; 1478 | } 1479 | 1480 | }; 1481 | 1482 | const OPTIONAL_STRING = [ 'type' => 'string', "optional" => true ]; 1483 | const OPTIONAL_INT = [ 'type' => 'int', "optional" => true ]; 1484 | 1485 | const ROOM_ID = [ 'room_id' => 1486 | [ 'type' => 'string', 1487 | 'missing_message' => 1488 | 'You must provide the ID of the room' 1489 | ] 1490 | ]; 1491 | const SENDER_ID = [ 'sender_id' => 1492 | [ 'type' => 'string', 1493 | 'missing_message' => 1494 | 'You must provide the ID of the user sending the message' 1495 | ] 1496 | ]; 1497 | 1498 | const MESSAGE_ID = [ 'message_id' => 1499 | [ 'type' => 'int', 1500 | 'missing_message' => 1501 | 'You must provide the ID of the message' 1502 | ] 1503 | ]; 1504 | 1505 | function verify($fields, $options) { 1506 | foreach ($fields as $field) { 1507 | $name = key($field); 1508 | $rules = $field[$name]; 1509 | 1510 | if (!isset($options[$name]) && !isset($rules['optional'])) { 1511 | throw new MissingArgumentException($rules['missing_message']); 1512 | } elseif (isset($options[$name])) { 1513 | switch ($rules['type']) { 1514 | case 'string': 1515 | if (!is_string($options[$name])) { 1516 | throw new TypeMismatchException($options[$name]." must be of type string"); 1517 | } 1518 | break; 1519 | case 'int': 1520 | if (!is_int($options[$name]) || $options[$name] < 0) { 1521 | throw new TypeMismatchException($options[$name]." must be a positive int"); 1522 | } 1523 | break; 1524 | case 'non_empty_array': 1525 | if (!is_array($options[$name]) || empty($options[$name])) { 1526 | throw new TypeMismatchException($options[$name]." must be a non-empty array"); 1527 | } 1528 | break; 1529 | default: 1530 | break; 1531 | 1532 | } 1533 | } 1534 | } 1535 | } 1536 | --------------------------------------------------------------------------------