├── .coveralls.yml ├── .empty ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .vscode └── php-sdk.code-workspace ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── composer.json ├── composer.lock ├── examples ├── 1-assembly-request.php ├── 2-assembly-form.php ├── 3-assembly-form-with-jquery-plugin.php ├── 4-fetch-assembly-status.php ├── 5-assembly-with-template.php ├── 6-assembly-with-timeout.php ├── 7-disable-ssl-verification.php ├── common │ └── loader.php └── fixture │ └── straw-apple.jpg ├── lib └── transloadit │ ├── CurlRequest.php │ ├── CurlResponse.php │ ├── Transloadit.php │ ├── TransloaditRequest.php │ └── TransloaditResponse.php ├── phpcs.xml ├── phpunit.xml ├── release.sh ├── test ├── bootstrap.php ├── config.php ├── fixture │ └── image-resize-robot.jpg ├── simple │ ├── CurlRequestTest.php │ ├── CurlResponseTest.php │ ├── TransloaditRequestTest.php │ ├── TransloaditResponseTest.php │ └── TransloaditTest.php └── system │ ├── CurlRequest │ └── CurlRequestRootTest.php │ ├── Transloadit │ ├── TransloaditAssemblyCreateTest.php │ └── TransloaditCreateAssemblyWaitForCompletionTest.php │ └── TransloaditRequest │ ├── Base.php │ ├── TransloaditRequestAssemblyCreateTest.php │ ├── TransloaditRequestErrorTest.php │ ├── TransloaditRequestGetBillTest.php │ ├── TransloaditRequestHttpsRootTest.php │ ├── TransloaditRequestNoJsonErrorTest.php │ └── TransloaditRequestRootTest.php └── tool ├── generate-example-docs.php └── node-smartcdn-sig.ts /.coveralls.yml: -------------------------------------------------------------------------------- 1 | # for php-coveralls 2 | # https://coveralls.io/r/transloadit/php-sdk 3 | # https://github.com/satooshi/php-coveralls 4 | src_dir: lib/transloadit 5 | coverage_clover: build/logs/clover.xml 6 | -------------------------------------------------------------------------------- /.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transloadit/php-sdk/4eabc08bfb2cd8744c35d13238587190379a0e4e/.empty -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | types: 8 | - opened 9 | - synchronize 10 | jobs: 11 | ci: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: true 15 | max-parallel: 1 16 | matrix: 17 | php: 18 | - 8.1 19 | - 8.2 20 | dependencies: 21 | - locked 22 | - lowest 23 | - highest 24 | name: PHP ${{ matrix.php }} - ${{ matrix.dependencies }} 25 | steps: 26 | - uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 1 29 | - uses: actions/setup-node@v4 30 | with: 31 | node-version: '20' 32 | - name: Install tsx 33 | run: npm install -g tsx 34 | - uses: shivammathur/setup-php@v2 35 | with: 36 | php-version: ${{ matrix.php }} 37 | tools: php-cs-fixer, phpunit 38 | coverage: ${{ matrix.php == '8.1' && matrix.dependencies == 'locked' && 'xdebug' || 'none' }} 39 | - uses: ramsey/composer-install@v3 40 | with: 41 | dependency-versions: ${{ matrix.dependencies }} 42 | composer-options: '--ignore-platform-reqs' 43 | - name: Test with Coverage 44 | if: matrix.php == '8.1' && matrix.dependencies == 'locked' 45 | run: | 46 | make test-all-coverage 47 | env: 48 | TRANSLOADIT_KEY: ${{secrets.TEST_ACCOUNT_KEY}} 49 | TRANSLOADIT_SECRET: ${{secrets.TEST_ACCOUNT_SECRET}} 50 | TEST_NODE_PARITY: 1 51 | - name: Test without Coverage 52 | if: matrix.php != '8.1' || matrix.dependencies != 'locked' 53 | run: | 54 | make test-all 55 | env: 56 | TRANSLOADIT_KEY: ${{secrets.TEST_ACCOUNT_KEY}} 57 | TRANSLOADIT_SECRET: ${{secrets.TEST_ACCOUNT_SECRET}} 58 | TEST_NODE_PARITY: 1 59 | - name: Publish Coverage Report 60 | if: github.event_name == 'pull_request' && matrix.php == '8.1' && matrix.dependencies == 'locked' 61 | uses: lucassabreu/comment-coverage-clover@v0.13.0 62 | with: 63 | file: ./build/logs/clover.xml 64 | with-table: true 65 | with-chart: false 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Readme.html 2 | 3 | composer.phar 4 | build/ 5 | vendor/ 6 | bin/composer 7 | env.sh 8 | .phpunit.cache 9 | .aider* 10 | .env 11 | -------------------------------------------------------------------------------- /.vscode/php-sdk.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": ".." 5 | } 6 | ], 7 | "settings": { 8 | "workbench.colorCustomizations": { 9 | "titleBar.activeForeground": "#232531", 10 | "titleBar.activeBackground": "#8993be" 11 | }, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Versions 2 | 3 | ### [main](https://github.com/transloadit/php-sdk/tree/main) 4 | 5 | diff: https://github.com/transloadit/php-sdk/compare/3.2.0...main 6 | 7 | ### [3.2.0](https://github.com/transloadit/php-sdk/tree/3.2.0) 8 | 9 | - Implement `signedSmartCDNUrl` 10 | 11 | diff: https://github.com/transloadit/php-sdk/compare/3.1.0...3.2.0 12 | 13 | ### [3.1.0](https://github.com/transloadit/php-sdk/tree/3.1.0) 14 | 15 | - Pass down `curlOptions` when `TransloaditRequest` reinstantiates itself for `waitForCompletion` 16 | 17 | diff: https://github.com/transloadit/php-sdk/compare/3.0.4-dev...3.1.0 18 | 19 | ### [3.0.4-dev](https://github.com/transloadit/php-sdk/tree/3.0.4-dev) 20 | 21 | - Pass down `curlOptions` when `TransloaditRequest` reinstantiates itself for `waitForCompletion` 22 | 23 | diff: https://github.com/transloadit/php-sdk/compare/3.0.4...3.0.4-dev 24 | 25 | ### [3.0.4](https://github.com/transloadit/php-sdk/tree/3.0.4) 26 | 27 | - Ditch `v` prefix in versions as that's more idiomatic 28 | - Bring back the getAssembly() function 29 | - Implement Transloadit client header. Closes #25. (#28) 30 | - Fix waitForCompletion 31 | - Travis php & ubuntu version changes 32 | - fix: remove deprecation warning 33 | - Rename tl->deleteAssembly to cancelAssembly and add it to the Readme 34 | 35 | diff: https://github.com/transloadit/php-sdk/compare/v2.0.0...3.0.4 36 | 37 | ### [v2.1.0](https://github.com/transloadit/php-sdk/tree/v2.1.0) 38 | 39 | - Fix for CURL deprecated functions (thanks @ABerkhout) 40 | - CI improvements (phpunit, travis, composer) 41 | - Add example for fetching the assembly status 42 | - Add ability to set additional curl_setopt (thanks @michaelkasper) 43 | 44 | diff: https://github.com/transloadit/php-sdk/compare/v2.0.0...v2.1.0 45 | 46 | ### [v2.0.0](https://github.com/transloadit/php-sdk/tree/v2.0.0) 47 | 48 | - Retire host + protocol in favor of one endpoint property, 49 | allow passing that on to the Request object. 50 | - Improve readme (getting started) 51 | - Don't rely on globally installed phpunit when we can ship it via Composer 52 | 53 | diff: https://github.com/transloadit/php-sdk/compare/v1.0.1...v2.0.0 54 | 55 | ### [v1.0.1](https://github.com/transloadit/php-sdk/tree/v1.0.1) 56 | 57 | - Fix broken examples 58 | - Improve documentation (version changelogs) 59 | 60 | diff: https://github.com/transloadit/php-sdk/compare/v1.0.0...v1.0.1 61 | 62 | ### [v1.0.0](https://github.com/transloadit/php-sdk/tree/v1.0.0) 63 | 64 | A big thanks to [@nervetattoo](https://github.com/nervetattoo) for making this version happen! 65 | 66 | - Add support for Composer 67 | - Make phpunit run through Composer 68 | - Change to namespaced PHP 69 | 70 | diff: https://github.com/transloadit/php-sdk/compare/v0.10.0...v1.0.0 71 | 72 | ### [v0.10.0](https://github.com/transloadit/php-sdk/tree/v0.10.0) 73 | 74 | - Add support for Strict mode 75 | - Add support for more auth params 76 | - Improve documentation 77 | - Bug fixes 78 | - Refactoring 79 | 80 | diff: https://github.com/transloadit/php-sdk/compare/v0.9.1...v0.10.0 81 | 82 | ### [v0.9.1](https://github.com/transloadit/php-sdk/tree/v0.9.1) 83 | 84 | - Improve documentation 85 | - Better handling of errors & non-json responses 86 | - Change directory layout 87 | 88 | diff: https://github.com/transloadit/php-sdk/compare/v0.9...v0.9.1 89 | 90 | ### [v0.9](https://github.com/transloadit/php-sdk/tree/v0.9) 91 | 92 | - Use markdown for docs 93 | - Add support for signed GET requests 94 | - Add support for HTTPS 95 | - Simplified API 96 | - Improve handling of magic quotes 97 | 98 | diff: https://github.com/transloadit/php-sdk/compare/v0.2...v0.9 99 | 100 | ### [v0.2](https://github.com/transloadit/php-sdk/tree/v0.2) 101 | 102 | - Add error handling 103 | 104 | diff: https://github.com/transloadit/php-sdk/compare/v0.1...v0.2 105 | 106 | ### [v0.1](https://github.com/transloadit/php-sdk/tree/v0.1) 107 | 108 | The very first version 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2010 Transloadit 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. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /usr/bin/env bash 2 | 3 | export PATH := $(PATH):bin 4 | 5 | phpUnit = vendor/bin/phpunit --colors --verbose --stderr --configuration phpunit.xml $(2) $(1) 6 | 7 | .PHONY: install 8 | install: 9 | which composer || curl -sS https://getcomposer.org/installer | php -- --install-dir=bin --filename=composer 10 | composer install --no-interaction --prefer-source 11 | 12 | .PHONY: test 13 | test: test-simple 14 | 15 | .PHONY: test-all-coverage 16 | test-all-coverage: 17 | $(call phpUnit,test,--coverage-clover build/logs/clover.xml) 18 | 19 | .PHONY: test-all 20 | test-all: lint test-simple test-system 21 | 22 | .PHONY: test-simple 23 | test-simple: 24 | $(call phpUnit,test/simple) 25 | 26 | .PHONY: test-system 27 | test-system: 28 | $(call phpUnit,test/system) 29 | 30 | .PHONY: docs 31 | docs: 32 | php tool/generate-example-docs.php 33 | 34 | .PHONY: lint 35 | lint: 36 | @vendor/bin/phpcs --warning-severity=0 --standard=./phpcs.xml lib/ examples/ test/ tool/ 37 | 38 | .PHONY: fix 39 | fix: 40 | @vendor/bin/phpcbf --standard=./phpcs.xml lib/ examples/ test/ tool/ 41 | 42 | .PHONY: docs-html 43 | docs-html: docs 44 | Markdown.pl --html4tags Readme.md > Readme.html 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Transloadit PHP SDK 2 | 3 | [![Test Actions Status][test_badge]][test_link] 4 | [![Code coverage][codecov_badge]][codecov_link] 5 | ![Packagist PHP Version Support][php_verison_badge] 6 | [![License][licence_badge]][licence_link] 7 | 8 | ## Introduction 9 | 10 | The Transloadit PHP SDK provides a simple and efficient way to interact with Transloadit's file processing service in your PHP applications. With this SDK, you can easily: 11 | 12 | - Create and manage file upload assemblies 13 | - Use pre-defined templates for common file processing tasks 14 | - Handle notifications and retrieve assembly statuses 15 | - Integrate Transloadit's powerful file processing capabilities into your PHP projects 16 | 17 | This SDK simplifies the process of working with Transloadit's REST API, allowing you to focus on building great applications without worrying about the complexities of file processing. 18 | 19 | ## Install 20 | 21 | ``` 22 | composer require transloadit/php-sdk 23 | ``` 24 | 25 | Keep your Transloadit account's Auth Key & Secret nearby. You can check 26 | the [API credentials](https://transloadit.com/accounts/credentials) page for 27 | these values. 28 | 29 | ## Usage 30 | 31 | 32 | 33 | ### 1. Upload and resize an image from your server 34 | 35 | This example demonstrates how you can use the SDK to create an Assembly 36 | on your server. 37 | 38 | It takes a sample image file, uploads it to Transloadit, and starts a 39 | resizing job on it. 40 | 41 | ```php 42 | 'MY_TRANSLOADIT_KEY', 49 | 'secret' => 'MY_TRANSLOADIT_SECRET', 50 | ]); 51 | 52 | $response = $transloadit->createAssembly([ 53 | 'files' => ['/PATH/TO/FILE.jpg'], 54 | 'params' => [ 55 | 'steps' => [ 56 | 'resize' => [ 57 | 'robot' => '/image/resize', 58 | 'width' => 200, 59 | 'height' => 100, 60 | ], 61 | ], 62 | ], 63 | ]); 64 | 65 | // Show the results of the assembly we spawned 66 | echo '
';
 67 | print_r($response);
 68 | echo '
'; 69 | 70 | ``` 71 | 72 | ### 2. Create a simple end-user upload form 73 | 74 | This example shows you how to create a simple Transloadit upload form 75 | that redirects back to your site after the upload is done. 76 | 77 | Once the script receives the redirect request, the current status for 78 | this Assembly is shown using `Transloadit::response()`. 79 | 80 |
81 | Note: There is no guarantee that the Assembly has already finished 82 | executing by the time the $response is fetched. You should use 83 | the notify_url parameter for this. 84 |
85 | 86 | ```php 87 | 'MY_TRANSLOADIT_KEY', 94 | 'secret' => 'MY_TRANSLOADIT_SECRET', 95 | ]); 96 | 97 | // Check if this request is a Transloadit redirect_url notification. 98 | // If so fetch the response and output the current assembly status: 99 | $response = Transloadit::response(); 100 | if ($response) { 101 | echo '

Assembly Status:

'; 102 | echo '
';
103 |   print_r($response);
104 |   echo '
'; 105 | exit; 106 | } 107 | 108 | // This should work on most environments, but you might have to modify 109 | // this for your particular setup. 110 | $redirectUrl = sprintf('http://%s%s', $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI']); 111 | 112 | // Setup a simple file upload form that resizes an image to 200x100px 113 | echo $transloadit->createAssemblyForm([ 114 | 'params' => [ 115 | 'steps' => [ 116 | 'resize' => [ 117 | 'robot' => '/image/resize', 118 | 'width' => 200, 119 | 'height' => 100, 120 | ], 121 | ], 122 | 'redirect_url' => $redirectUrl, 123 | ], 124 | ]); 125 | ?> 126 |

Pick an image to resize

127 | 128 | 129 | 130 | 131 | ``` 132 | 133 | ### 3. Use Uppy for file uploads 134 | 135 | We recommend using [Uppy](https://transloadit.com/docs/sdks/uppy/), our next-gen file uploader for the web, instead of the jQuery SDK. Uppy provides a more modern, flexible, and feature-rich solution for handling file uploads with Transloadit. 136 | 137 | To integrate Uppy with your PHP backend: 138 | 139 | 1. Include Uppy in your HTML: 140 | 141 | ```html 142 | 146 | 147 | ``` 148 | 149 | 2. Initialize Uppy with Transloadit plugin: 150 | 151 | ```html 152 |
153 | 154 | 172 | ``` 173 | 174 | 3. Handle the assembly status on your PHP backend: 175 | 176 | Create a new file named `transloadit_notify.php` in your project: 177 | 178 | ```php 179 | 'MY_TRANSLOADIT_KEY', 186 | 'secret' => 'MY_TRANSLOADIT_SECRET', 187 | ]); 188 | 189 | $response = Transloadit::response(); 190 | if ($response) { 191 | // Process the assembly result 192 | $assemblyId = $response->data['assembly_id']; 193 | $assemblyStatus = $response->data['ok']; 194 | 195 | // You can store the assembly information in your database 196 | // or perform any other necessary actions here 197 | 198 | // Log the response for debugging 199 | error_log('Transloadit Assembly Completed: ' . $assemblyId); 200 | error_log('Assembly Status: ' . ($assemblyStatus ? 'Success' : 'Failed')); 201 | 202 | // Optionally, you can write the response to a file 203 | file_put_contents('transloadit_response_' . $assemblyId . '.json', json_encode($response->data)); 204 | 205 | // Send a 200 OK response to Transloadit 206 | http_response_code(200); 207 | echo 'OK'; 208 | } else { 209 | // If it's not a Transloadit notification, return a 400 Bad Request 210 | http_response_code(400); 211 | echo 'Bad Request'; 212 | } 213 | ?> 214 | ``` 215 | 216 | Make sure to replace `'https://your-site.com/transloadit_notify.php'` with the actual URL where you'll host this PHP script. 217 | 218 | For more detailed information on using Uppy with Transloadit, please refer to our [Uppy documentation](https://transloadit.com/docs/sdks/uppy/). 219 | 220 | ### 4. Fetch the Assembly Status JSON 221 | 222 | You can use the `getAssembly` method to get the Assembly Status. 223 | 224 | ```php 225 | 'MY_TRANSLOADIT_KEY', 231 | 'secret' => 'MY_TRANSLOADIT_SECRET', 232 | ]); 233 | 234 | $response = $transloadit->getAssembly($assemblyId); 235 | 236 | echo '
';
237 | print_r($response);
238 | echo '
'; 239 | 240 | ``` 241 | 242 | ### 5. Create an Assembly with a Template. 243 | 244 | This example demonstrates how you can use the SDK to create an Assembly 245 | with Templates. 246 | 247 | You are expected to create a Template on your Transloadit account dashboard 248 | and add the Template ID here. 249 | 250 | ```php 251 | 'MY_TRANSLOADIT_KEY', 258 | 'secret' => 'MY_TRANSLOADIT_SECRET', 259 | ]); 260 | 261 | $response = $transloadit->createAssembly([ 262 | 'files' => ['/PATH/TO/FILE.jpg'], 263 | 'params' => [ 264 | 'template_id' => 'MY_TEMPLATE_ID', 265 | ], 266 | ]); 267 | 268 | // Show the results of the assembly we spawned 269 | echo '
';
270 | print_r($response);
271 | echo '
'; 272 | 273 | ``` 274 | 275 | ### Signature Auth (Assemblies) 276 | 277 | Signature Authentication is done by the PHP SDK by default internally so you do not need to worry about this :) 278 | 279 | ### Signature Auth (Smart CDN) 280 | 281 | You can use the `signedSmartCDNUrl` method to generate signed URLs for Transloadit's [Smart CDN](https://transloadit.com/services/content-delivery/): 282 | 283 | ```php 284 | 'MY_TRANSLOADIT_KEY', 291 | 'secret' => 'MY_TRANSLOADIT_SECRET', 292 | ]); 293 | 294 | // Basic usage 295 | $url = $transloadit->signedSmartCDNUrl( 296 | 'your-workspace-slug', 297 | 'your-template-slug', 298 | 'avatars/jane.jpg' 299 | ); 300 | 301 | // Advanced usage with custom parameters and expiry 302 | $url = $transloadit->signedSmartCDNUrl( 303 | 'your-workspace-slug', 304 | 'your-template-slug', 305 | 'avatars/jane.jpg', 306 | ['width' => 100, 'height' => 100], // Additional parameters 307 | 1732550672867, // Expiry date in milliseconds since epoch 308 | ); 309 | 310 | echo $url; 311 | ``` 312 | 313 | The generated URL will be in the format: 314 | 315 | ``` 316 | https://{workspace-slug}.tlcdn.com/{template-slug}/{input-field}?{query-params}&sig=sha256:{signature} 317 | ``` 318 | 319 | Note that: 320 | 321 | - The URL will expire after the specified time (default: 1 hour) 322 | - All parameters are properly encoded 323 | - The signature is generated using HMAC SHA-256 324 | - Query parameters are sorted alphabetically before signing 325 | 326 | ## Example 327 | 328 | For fully working examples take a look at [`examples/`](https://github.com/transloadit/php-sdk/tree/HEAD/examples). 329 | 330 | ## API 331 | 332 | ### $Transloadit = new Transloadit($properties = []); 333 | 334 | Creates a new Transloadit instance and applies the given $properties. 335 | 336 | #### $Transloadit->key = null; 337 | 338 | The auth key of your Transloadit account. 339 | 340 | #### $Transloadit->secret = null; 341 | 342 | The auth secret of your Transloadit account. 343 | 344 | #### $Transloadit->request($options = [], $execute = true); 345 | 346 | Creates a new `TransloaditRequest` using the `$Transloadit->key` and 347 | `$Transloadit->secret` properties. 348 | 349 | If `$execute` is set to `true`, `$TransloaditRequest->execute()` will be 350 | called and used as the return value. 351 | 352 | Otherwise the new `TransloaditRequest` instance is being returned. 353 | 354 | #### $Transloadit->createAssemblyForm($options = []); 355 | 356 | Creates a new Transloadit assembly form including the hidden 'params' and 357 | 'signature' fields. A closing form tag is not included. 358 | 359 | `$options` is an array of `TransloaditRequest` properties to be used. 360 | For example: `"params"`, `"expires"`, `"endpoint"`, etc.. 361 | 362 | In addition to that, you can also pass an `"attributes"` key, which allows 363 | you to set custom form attributes. For example: 364 | 365 | ```php 366 | $Transloadit->createAssemblyForm(array( 367 | 'attributes' => array( 368 | 'id' => 'my_great_upload_form', 369 | 'class' => 'transloadit_form', 370 | ), 371 | )); 372 | ``` 373 | 374 | #### $Transloadit->createAssembly($options); 375 | 376 | Sends a new assembly request to Transloadit. This is the preferred way of 377 | uploading files from your server. 378 | 379 | `$options` is an array of `TransloaditRequest` properties to be used with the exception that you can 380 | also use the `waitForCompletion` option here: 381 | 382 | `waitForCompletion` is a boolean (default is false) to indicate whether you want to wait for the 383 | Assembly to finish with all encoding results present before the callback is called. If 384 | waitForCompletion is true, this SDK will poll for status updates and return when all encoding work 385 | is done. 386 | 387 | Check example #1 above for more information. 388 | 389 | #### $Transloadit->getAssembly($assemblyId); 390 | 391 | Retrieves the Assembly status json for a given Assembly ID. 392 | 393 | #### $Transloadit->cancelAssembly($assemblyId); 394 | 395 | Cancels an assembly that is currently executing and prevents any further encodings costing money. 396 | 397 | This will result in `ASSEMBLY_NOT_FOUND` errors if invoked on assemblies that are not currently 398 | executing (anymore). 399 | 400 | #### Transloadit::response() 401 | 402 | This static method is used to parse the notifications Transloadit sends to 403 | your server. 404 | 405 | There are two kinds of notifications this method handles: 406 | 407 | - When using the `redirect_url` parameter, and Transloadit redirects 408 | back to your site, a `$_GET['assembly_url']` query parameter gets added. 409 | This method detects the presence of this parameter and fetches the current 410 | assembly status from that url and returns it as a `TransloaditResponse`. 411 | - When using the `notify_url` parameter, Transloadit sends a 412 | `$_POST['transloadit']` parameter. This method detects this, and parses 413 | the notification JSON into a `TransloaditResponse` object for you. 414 | 415 | If the current request does not seem to be invoked by Transloadit, this 416 | method returns `false`. 417 | 418 | ### $TransloaditRequest = new TransloaditRequest($properties = []); 419 | 420 | Creates a new TransloaditRequest instance and applies the given $properties. 421 | 422 | #### $TransloaditRequest->key = null; 423 | 424 | The auth key of your Transloadit account. 425 | 426 | #### $TransloaditRequest->secret = null; 427 | 428 | The auth secret of your Transloadit account. 429 | 430 | #### $TransloaditRequest->method = 'GET'; 431 | 432 | Inherited from `CurlRequest`. Can be used to set the type of request to be 433 | made. 434 | 435 | #### $TransloaditRequest->curlOptions = []; 436 | 437 | Inherited from `CurlRequest`. Can be used to tweak cURL behavior using [any cURL option that your PHP/cURL version supports](https://www.php.net/manual/en/function.curl-setopt.php). 438 | 439 | Here is an [example](examples/6-assembly-with-timeout.php) that illustrates 440 | using this option to change the timeout of a request (drastically, to `1ms`, just to prove you can make the SDK abort after a time of your choosing). 441 | 442 | The default timeouts and options depend on the cURL version on your system and can be verified by checking `phpinfo()` and the [curl_setopt](https://www.php.net/manual/en/function.curl-setopt.php) documentation. 443 | 444 | #### $TransloaditRequest->endpoint = 'https://api2.transloadit.com'; 445 | 446 | The endpoint to send this request to. 447 | 448 | #### $TransloaditRequest->path = null; 449 | 450 | The url path to request. 451 | 452 | #### $TransloaditRequest->url = null; 453 | 454 | Inherited from `CurlRequest`. Lets you overwrite the above endpoint / path 455 | properties with a fully custom url alltogether. 456 | 457 | #### $TransloaditRequest->fields = []; 458 | 459 | A list of additional fields to send along with your request. Transloadit 460 | will include those in all assembly related notifications. 461 | 462 | #### $TransloaditRequest->files = []; 463 | 464 | An array of paths to local files you would like to upload. For example: 465 | 466 | ```php 467 | $TransloaditRequest->files = array('/my/file.jpg'); 468 | ``` 469 | 470 | or 471 | 472 | ```php 473 | $TransloaditRequest->files = array('my_upload' => '/my/file.jpg'); 474 | ``` 475 | 476 | The first example would automatically give your file a field name of 477 | `'file_1'` when executing the request. 478 | 479 | #### $TransloaditRequest->params = []; 480 | 481 | An array representing the JSON params to be send to Transloadit. You 482 | do not have to include an `'auth'` key here, as this class handles that 483 | for you as part of `$TransloaditRequest->prepare()`. 484 | 485 | #### $TransloaditRequest->expires = '+2 hours'; 486 | 487 | If you have configured a '`$TransloaditRequest->secret`', this class will 488 | automatically sign your request. The expires property lets you configure 489 | the duration for which the signature is valid. 490 | 491 | #### $TransloaditRequest->headers = []; 492 | 493 | Lets you send additional headers along with your request. You should not 494 | have to change this property. 495 | 496 | #### $TransloaditRequest->execute() 497 | 498 | Sends this request to Transloadit and returns a `TransloaditResponse` 499 | instance. 500 | 501 | ### $TransloaditResponse = new TransloaditResponse($properties = []); 502 | 503 | Creates a new TransloaditResponse instance and applies the given $properties. 504 | 505 | #### $TransloaditResponse->data = null; 506 | 507 | Inherited from `CurlResponse`. Contains an array of the parsed JSON 508 | response from Transloadit. 509 | 510 | You should generally only access this property after having checked for 511 | errors using `$TransloaditResponse->error()`. 512 | 513 | #### $TransloaditResponse->error(); 514 | 515 | Returns `false` or a string containing an explanation of what went wrong. 516 | 517 | All of the following will cause an error string to be returned: 518 | 519 | - Network issues of any kind 520 | - The Transloadit response JSON contains an `{"error": "..."}` key 521 | - A malformed response was received 522 | 523 | **_Note_**: You will need to set waitForCompletion = True in the $Transloadit->createAssembly($options) function call. 524 | 525 | ## Contributing 526 | 527 | Feel free to fork this project. We will happily merge bug fixes or other small 528 | improvements. For bigger changes you should probably get in touch with us 529 | before you start to avoid not seeing them merged. 530 | 531 | ### Testing 532 | 533 | #### Basic Tests 534 | 535 | ```bash 536 | make test 537 | ``` 538 | 539 | #### System Tests 540 | 541 | System tests require: 542 | 543 | 1. Valid Transloadit credentials in environment: 544 | 545 | ```bash 546 | export TRANSLOADIT_KEY='your-auth-key' 547 | export TRANSLOADIT_SECRET='your-auth-secret' 548 | ``` 549 | 550 | Then run: 551 | 552 | ```bash 553 | make test-all 554 | ``` 555 | 556 | #### Node.js Reference Implementation Parity Assertions 557 | 558 | The SDK includes assertions that compare URL signing with our reference Node.js implementation. To run these tests: 559 | 560 | 1. Requirements: 561 | 562 | - Node.js installed 563 | - tsx installed globally (`npm install -g tsx`) 564 | 565 | 2. Install dependencies: 566 | 567 | ```bash 568 | npm install -g tsx 569 | ``` 570 | 571 | 3. Run the test: 572 | 573 | ```bash 574 | export TRANSLOADIT_KEY='your-auth-key' 575 | export TRANSLOADIT_SECRET='your-auth-secret' 576 | TEST_NODE_PARITY=1 make test-all 577 | ``` 578 | 579 | CI opts-into `TEST_NODE_PARITY=1`, and you can optionally do this locally as well. 580 | 581 | ### Releasing a new version 582 | 583 | To release, say `3.2.0` [Packagist](https://packagist.org/packages/transloadit/php-sdk), follow these steps: 584 | 585 | 1. Make sure `PACKAGIST_TOKEN` is set in your `.env` file 586 | 1. Make sure you are in main: `git checkout main` 587 | 1. Update `CHANGELOG.md` and `composer.json` 588 | 1. Commit: `git add CHANGELOG.md composer.json && git commit -m "Release 3.2.0"` 589 | 1. Tag, push, and release: `source env.sh && VERSION=3.2.0 ./release.sh` 590 | 591 | This project implements the [Semantic Versioning](http://semver.org/) guidelines. 592 | 593 | ## License 594 | 595 | [MIT Licensed](LICENSE) 596 | 597 | [test_badge]: https://github.com/transloadit/php-sdk/actions/workflows/ci.yml/badge.svg 598 | [test_link]: https://github.com/transloadit/php-sdk/actions/workflows/ci.yml 599 | [codecov_badge]: https://codecov.io/gh/transloadit/php-sdk/branch/main/graph/badge.svg 600 | [codecov_link]: https://codecov.io/gh/transloadit/php-sdk 601 | [php_verison_badge]: https://img.shields.io/packagist/php-v/transloadit/php-sdk 602 | [licence_badge]: https://img.shields.io/badge/License-MIT-green.svg 603 | [licence_link]: https://github.com/transloadit/php-sdk/blob/main/LICENSE 604 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "transloadit/php-sdk", 3 | "type": "library", 4 | "description": "Transloadit SDK", 5 | "keywords": [ 6 | "transloadit", 7 | "video", 8 | "encoding", 9 | "thumbnails", 10 | "image", 11 | "resizing", 12 | "audio", 13 | "waveform", 14 | "document", 15 | "processing", 16 | "file", 17 | "uploading" 18 | ], 19 | "homepage": "https://github.com/transloadit/php-sdk", 20 | "license": "MIT", 21 | "version": "3.1.0", 22 | "require": { 23 | "php": ">=7.4.0" 24 | }, 25 | "require-dev": { 26 | "php-coveralls/php-coveralls": "^2.5", 27 | "phpunit/phpunit": "^9.5", 28 | "squizlabs/php_codesniffer": "^3.7" 29 | }, 30 | "autoload": { 31 | "psr-0": { 32 | "transloadit": "lib" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/1-assembly-request.php: -------------------------------------------------------------------------------- 1 | Assembly 8 | on your server. 9 | 10 | It takes a sample image file, uploads it to Transloadit, and starts a 11 | resizing job on it. 12 | */ 13 | 14 | use transloadit\Transloadit; 15 | 16 | $transloadit = new Transloadit([ 17 | 'key' => 'MY_TRANSLOADIT_KEY', 18 | 'secret' => 'MY_TRANSLOADIT_SECRET', 19 | ]); 20 | 21 | $response = $transloadit->createAssembly([ 22 | // Use dirname(__FILE__) to get the current directory, then append the relative path to the image 23 | // You can replace this with an absolute path to any file on your server that PHP can access 24 | 'files' => [dirname(__FILE__) . '/fixture/straw-apple.jpg'], 25 | 'params' => [ 26 | 'steps' => [ 27 | 'resize' => [ 28 | 'robot' => '/image/resize', 29 | 'width' => 200, 30 | 'height' => 100, 31 | ], 32 | ], 33 | ], 34 | ]); 35 | 36 | // Show the results of the assembly we spawned 37 | echo '
';
38 | print_r($response);
39 | echo '
'; 40 | -------------------------------------------------------------------------------- /examples/2-assembly-form.php: -------------------------------------------------------------------------------- 1 | Assembly is shown using `Transloadit::response()`. 12 | 13 |
14 | There is no guarantee that the Assembly has already finished 15 | executing by the time the `$response` is fetched. You should use 16 | the `notify_url` parameter for this. 17 |
18 | */ 19 | 20 | use transloadit\Transloadit; 21 | 22 | $transloadit = new Transloadit([ 23 | 'key' => 'MY_TRANSLOADIT_KEY', 24 | 'secret' => 'MY_TRANSLOADIT_SECRET', 25 | ]); 26 | 27 | // Check if this request is a Transloadit redirect_url notification. 28 | // If so fetch the response and output the current assembly status: 29 | $response = Transloadit::response(); 30 | if ($response) { 31 | echo '

Assembly Status:

'; 32 | echo '
';
33 |   print_r($response);
34 |   echo '
'; 35 | exit; 36 | } 37 | 38 | // This should work on most environments, but you might have to modify 39 | // this for your particular setup. 40 | $redirectUrl = sprintf('http://%s%s', $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI']); 41 | 42 | // Setup a simple file upload form that resizes an image to 200x100px 43 | echo $transloadit->createAssemblyForm([ 44 | 'params' => [ 45 | 'steps' => [ 46 | 'resize' => [ 47 | 'robot' => '/image/resize', 48 | 'width' => 200, 49 | 'height' => 100, 50 | ], 51 | ], 52 | 'redirect_url' => $redirectUrl, 53 | ], 54 | ]); 55 | ?> 56 |

Pick an image to resize

57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/3-assembly-form-with-jquery-plugin.php: -------------------------------------------------------------------------------- 1 | 'MY_TRANSLOADIT_KEY', 17 | 'secret' => 'MY_TRANSLOADIT_SECRET', 18 | ]); 19 | 20 | $response = Transloadit::response(); 21 | if ($response) { 22 | echo '

Assembly Status:

'; 23 | echo '
';
24 |   print_r($response);
25 |   echo '
'; 26 | exit; 27 | } 28 | 29 | $redirectUrl = sprintf('http://%s%s', $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI']); 30 | 31 | echo $transloadit->createAssemblyForm([ 32 | 'params' => [ 33 | 'steps' => [ 34 | 'resize' => [ 35 | 'robot' => '/image/resize', 36 | 'width' => 200, 37 | 'height' => 100, 38 | ], 39 | ], 40 | 'redirect_url' => $redirectUrl, 41 | ], 42 | ]); 43 | ?> 44 | 48 | 49 | 53 | 59 | 60 |

Pick an image to resize

61 |
62 | 63 | 64 |
65 | -------------------------------------------------------------------------------- /examples/4-fetch-assembly-status.php: -------------------------------------------------------------------------------- 1 | Assembly Status. 9 | */ 10 | $assemblyId = 'MY_ASSEMBLY_ID'; 11 | 12 | $transloadit = new Transloadit([ 13 | 'key' => 'MY_TRANSLOADIT_KEY', 14 | 'secret' => 'MY_TRANSLOADIT_SECRET', 15 | ]); 16 | 17 | $response = $transloadit->getAssembly($assemblyId); 18 | 19 | echo '
';
20 | print_r($response);
21 | echo '
'; 22 | -------------------------------------------------------------------------------- /examples/5-assembly-with-template.php: -------------------------------------------------------------------------------- 1 | Assembly 8 | with Templates. 9 | 10 | You are expected to create a Template on your Transloadit account dashboard 11 | and add the Template ID here. 12 | */ 13 | 14 | use transloadit\Transloadit; 15 | 16 | $transloadit = new Transloadit([ 17 | 'key' => 'MY_TRANSLOADIT_KEY', 18 | 'secret' => 'MY_TRANSLOADIT_SECRET', 19 | ]); 20 | 21 | $response = $transloadit->createAssembly([ 22 | 'files' => [dirname(__FILE__) . '/fixture/straw-apple.jpg'], 23 | 'params' => [ 24 | 'template_id' => 'MY_TEMPLATE_ID', 25 | ], 26 | ]); 27 | 28 | // Show the results of the assembly we spawned 29 | echo '
';
30 | print_r($response);
31 | echo '
'; 32 | -------------------------------------------------------------------------------- /examples/6-assembly-with-timeout.php: -------------------------------------------------------------------------------- 1 | getenv('MY_TRANSLOADIT_KEY'), 9 | 'secret' => getenv('MY_TRANSLOADIT_SECRET'), 10 | ]); 11 | 12 | $response = $transloadit->createAssembly([ 13 | 'files' => [dirname(__FILE__) . '/fixture/straw-apple.jpg'], 14 | 'curlOptions' => [ 15 | CURLOPT_TIMEOUT_MS => 1, 16 | // We can't finish in the specified: '1ms' so we expect this example 17 | // to fail with: $response->curlErrorNumber === 28 18 | // 19 | // You can pass any curl option here that your PHP/curl version supports: 20 | // https://www.php.net/manual/en/function.curl-setopt.php 21 | // Note that if you are interested in timeouts, perhaps also consider 22 | // that you can set waitForCompletion to false and use the 23 | // notify_url feature to get a webhook pingback when the Assembly is done. 24 | ], 25 | 'params' => [ 26 | 'steps' => [ 27 | 'resize' => [ 28 | 'robot' => '/image/resize', 29 | 'width' => 200, 30 | 'height' => 100, 31 | ], 32 | ], 33 | ], 34 | ]); 35 | 36 | // Show the results of the assembly we spawned 37 | echo ''; 38 | print_r([ 39 | 'errcode' => $response->curlErrorNumber, 40 | 'errmsg' => $response->curlErrorMessage, 41 | ]); 42 | echo ''; 43 | -------------------------------------------------------------------------------- /examples/7-disable-ssl-verification.php: -------------------------------------------------------------------------------- 1 | getenv('MY_TRANSLOADIT_KEY'), 13 | 'secret' => getenv('MY_TRANSLOADIT_SECRET'), 14 | ]); 15 | 16 | $response = $transloadit->createAssembly([ 17 | 'files' => [dirname(__FILE__) . '/fixture/straw-apple.jpg'], 18 | 'curlOptions' => [ 19 | CURLOPT_SSL_VERIFYHOST => 0, 20 | CURLOPT_SSL_VERIFYPEER => 0, 21 | ], 22 | 'waitForCompletion' => true, 23 | 'params' => [ 24 | 'steps' => [ 25 | 'resize' => [ 26 | 'robot' => '/image/resize', 27 | 'width' => 200, 28 | 'height' => 100, 29 | ], 30 | ], 31 | ], 32 | ]); 33 | 34 | // Show the results of the assembly we spawned 35 | echo ''; 36 | print_r($response); 37 | echo ''; 38 | -------------------------------------------------------------------------------- /examples/common/loader.php: -------------------------------------------------------------------------------- 1 | $val) { 17 | $this->{$key} = $val; 18 | } 19 | } 20 | 21 | public function getCurlOptions() { 22 | $url = $this->url; 23 | 24 | $hasBody = ($this->method === 'PUT' || $this->method === 'POST'); 25 | if (!$hasBody) { 26 | $url .= '?' . http_build_query($this->fields); 27 | } 28 | 29 | if (!is_array($this->curlOptions)) { 30 | $this->curlOptions = [$this->curlOptions]; 31 | } 32 | 33 | // Obtain SDK version 34 | if (empty($this->version)) { 35 | $pathToComposerJson = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..'; 36 | $pathToComposerJson .= DIRECTORY_SEPARATOR . 'composer.json'; 37 | $composerData = json_decode(file_get_contents($pathToComposerJson)); 38 | $this->version = $composerData->version; 39 | } 40 | 41 | if (!empty($this->headers)) { 42 | foreach ($this->headers as $key => $value) { 43 | if (strpos($value, 'Transloadit-Client') === 0) { 44 | $this->headers[$key] = sprintf($value, $this->version); 45 | } 46 | } 47 | } 48 | 49 | $options = $this->curlOptions + [ 50 | CURLOPT_RETURNTRANSFER => true, 51 | CURLOPT_CUSTOMREQUEST => $this->method, 52 | CURLOPT_URL => $url, 53 | CURLOPT_HTTPHEADER => $this->headers, 54 | ]; 55 | 56 | if ($hasBody) { 57 | $fields = $this->fields; 58 | foreach ($this->files as $field => $file) { 59 | if (!file_exists($file)) { 60 | trigger_error('File ' . $file . ' does not exist', E_USER_ERROR); 61 | return false; 62 | } 63 | if (is_int($field)) { 64 | $field = 'file_' . ($field + 1); 65 | } 66 | 67 | // -- Start edit -- 68 | // Edit by Aart Berkhout involving issue #8: CURL depricated functions (PHP 5.5) 69 | // https://github.com/transloadit/php-sdk/issues/8 70 | if (function_exists('curl_file_create')) { 71 | // For >= PHP 5.5 use curl_file_create 72 | $fields[$field] = curl_file_create($file); 73 | } else { 74 | // For < PHP 5.5 use @filename API 75 | $fields[$field] = '@' . $file; 76 | } 77 | // -- End edit -- 78 | } 79 | $options[CURLOPT_POSTFIELDS] = $fields; 80 | } 81 | 82 | return $options; 83 | } 84 | 85 | public function execute($response = null) { 86 | $curl = curl_init(); 87 | 88 | // -- Start edit -- 89 | // For PHP 5.6 Safe Upload is required to upload files using curl in PHP 5.5, add the CURLOPT_SAFE_UPLOAD = true option 90 | if (defined('CURLOPT_SAFE_UPLOAD')) { 91 | curl_setopt($curl, CURLOPT_SAFE_UPLOAD, function_exists('curl_file_create') ? true : false); 92 | } 93 | // -- End edit -- 94 | 95 | curl_setopt_array($curl, $this->getCurlOptions()); 96 | 97 | if (!$response) { 98 | $response = new CurlResponse(); 99 | } 100 | $response->data = curl_exec($curl); 101 | $response->curlInfo = curl_getinfo($curl); 102 | $response->curlErrorNumber = curl_errno($curl); 103 | $response->curlErrorMessage = curl_error($curl); 104 | 105 | curl_close($curl); 106 | 107 | return $response; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /lib/transloadit/CurlResponse.php: -------------------------------------------------------------------------------- 1 | $val) { 15 | $this->{$key} = $val; 16 | } 17 | } 18 | 19 | public function parseJson() { 20 | $decoded = json_decode($this->data, true); 21 | if (!is_array($decoded)) { 22 | return false; 23 | } 24 | 25 | $this->data = $decoded; 26 | return true; 27 | } 28 | 29 | public function error() { 30 | if (!$this->curlErrorNumber) { 31 | return false; 32 | } 33 | 34 | return sprintf( 35 | 'curl: %d: %s', 36 | $this->curlErrorNumber, 37 | $this->curlErrorMessage 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/transloadit/Transloadit.php: -------------------------------------------------------------------------------- 1 | $val) { 12 | $this->{$key} = $val; 13 | } 14 | } 15 | 16 | public function request($options = [], $execute = true) { 17 | $options = $options + [ 18 | 'key' => $this->key, 19 | 'secret' => $this->secret, 20 | 'endpoint' => $this->endpoint, 21 | 'waitForCompletion' => false, 22 | ]; 23 | $request = new TransloaditRequest($options); 24 | return ($execute) 25 | ? $request->execute() 26 | : $request; 27 | } 28 | 29 | public static function response() { 30 | if (!empty($_POST['transloadit'])) { 31 | $json = $_POST['transloadit']; 32 | if (ini_get('magic_quotes_gpc') === '1') { 33 | $json = stripslashes($json); 34 | } 35 | 36 | $response = new TransloaditResponse(); 37 | $response->data = json_decode($json, true); 38 | return $response; 39 | } 40 | 41 | if (!empty($_GET['assembly_url'])) { 42 | $request = new TransloaditRequest([ 43 | 'url' => $_GET['assembly_url'], 44 | ]); 45 | return $request->execute(); 46 | } 47 | return false; 48 | } 49 | 50 | public function createAssemblyForm($options = []) { 51 | $out = []; 52 | 53 | $customFormAttributes = []; 54 | if (array_key_exists('attributes', $options)) { 55 | $customFormAttributes = $options['attributes']; 56 | unset($options['attributes']); 57 | } 58 | 59 | $assembly = $this->request($options + [ 60 | 'method' => 'POST', 61 | 'path' => '/assemblies', 62 | ], false); 63 | $assembly->prepare(); 64 | 65 | $formAttributes = [ 66 | 'action' => $assembly->url, 67 | 'method' => $assembly->method, 68 | 'enctype' => 'multipart/form-data', 69 | ] + $customFormAttributes; 70 | 71 | $formAttributeList = []; 72 | foreach ($formAttributes as $key => $val) { 73 | $formAttributeList[] = sprintf('%s="%s"', $key, htmlentities($val)); 74 | } 75 | 76 | $out[] = '
'; 77 | 78 | foreach ($assembly->fields as $field => $val) { 79 | $out[] = sprintf( 80 | '', 81 | 'hidden', 82 | $field, 83 | htmlentities($val) 84 | ); 85 | } 86 | 87 | return join("\n", $out); 88 | } 89 | 90 | public function createAssembly($options) { 91 | return $this->request($options + [ 92 | 'method' => 'POST', 93 | 'path' => '/assemblies', 94 | ]); 95 | } 96 | 97 | // Leave this in for BC. 98 | public function getAssembly($assembly_id) { 99 | $response = $this->request([ 100 | 'method' => 'GET', 101 | 'path' => '/assemblies/' . $assembly_id, 102 | ], true); 103 | 104 | return $response; 105 | } 106 | 107 | public function deleteAssembly($assembly_id) { 108 | return $this->cancelAssembly($assembly_id); 109 | } 110 | 111 | public function cancelAssembly($assembly_id) { 112 | // Look up the host for this assembly 113 | $response = $this->request([ 114 | 'method' => 'GET', 115 | 'path' => '/assemblies/' . $assembly_id, 116 | ], true); 117 | 118 | $error = $response->error(); 119 | if ($error) { 120 | return $error; 121 | } 122 | 123 | $url = parse_url($response->data['assembly_url']); 124 | 125 | $response = $this->request([ 126 | 'method' => 'DELETE', 127 | 'path' => $url['path'], 128 | 'host' => $url['host'], 129 | ]); 130 | 131 | $error = $response->error(); 132 | if ($error) { 133 | return $error; 134 | } else { 135 | return $response; 136 | } 137 | } 138 | 139 | /** 140 | * Generates a signed URL for Transloadit's Smart CDN 141 | * https://transloadit.com/services/content-delivery/ 142 | * 143 | * @param string $workspaceSlug The workspace slug 144 | * @param string $templateSlug The template slug 145 | * @param string $inputField The input field (optional) 146 | * @param array $params Additional parameters (optional) 147 | * @param int $expireAtMs Number of milliseconds since epoch at which the URL expires 148 | * @return string The signed URL 149 | */ 150 | public function signedSmartCDNUrl( 151 | string $workspaceSlug, 152 | string $templateSlug, 153 | string $inputField = '', 154 | array $params = [], 155 | int $expireAtMs = null 156 | ): string { 157 | // Validate required fields 158 | if (!$workspaceSlug) { 159 | throw new \InvalidArgumentException('workspace is required'); 160 | } 161 | if (!$templateSlug) { 162 | throw new \InvalidArgumentException('template is required'); 163 | } 164 | if ($inputField === null) { 165 | throw new \InvalidArgumentException('input must be a string'); 166 | } 167 | 168 | // Add auth parameters 169 | $queryParams = []; 170 | 171 | // Process params to match Node.js behavior 172 | foreach ($params as $key => $value) { 173 | if (is_array($value)) { 174 | foreach ($value as $val) { 175 | if ($val !== null) { 176 | $queryParams[$key][] = $val; 177 | } 178 | } 179 | } elseif ($value !== null) { 180 | $queryParams[$key] = $value; 181 | } 182 | } 183 | 184 | $queryParams['auth_key'] = $this->key; 185 | $queryParams['exp'] = (string)($expireAtMs ?? (time() * 1000 + 3600000)); // Default 1 hour 186 | 187 | // Sort parameters alphabetically 188 | ksort($queryParams); 189 | 190 | // Build query string manually to match Node.js behavior 191 | $queryParts = []; 192 | foreach ($queryParams as $key => $value) { 193 | if (is_array($value)) { 194 | foreach ($value as $val) { 195 | $queryParts[] = rawurlencode($key) . '=' . rawurlencode($val); 196 | } 197 | } else { 198 | $queryParts[] = rawurlencode($key) . '=' . rawurlencode($value); 199 | } 200 | } 201 | $queryString = implode('&', $queryParts); 202 | 203 | // Build the string to sign 204 | $stringToSign = sprintf( 205 | '%s/%s/%s?%s', 206 | rawurlencode($workspaceSlug), 207 | rawurlencode($templateSlug), 208 | rawurlencode($inputField), 209 | $queryString 210 | ); 211 | 212 | // Generate signature 213 | $signature = hash_hmac('sha256', $stringToSign, $this->secret); 214 | 215 | // Add signature to query string 216 | $finalQueryString = $queryString . '&sig=' . rawurlencode('sha256:' . $signature); 217 | 218 | // Build final URL 219 | return sprintf( 220 | 'https://%s.tlcdn.com/%s/%s?%s', 221 | rawurlencode($workspaceSlug), 222 | rawurlencode($templateSlug), 223 | rawurlencode($inputField), 224 | $finalQueryString 225 | ); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /lib/transloadit/TransloaditRequest.php: -------------------------------------------------------------------------------- 1 | method = $method; 25 | $this->path = $path; 26 | } 27 | 28 | public function getParamsString() { 29 | $params = $this->params; 30 | if (!isset($params['auth'])) { 31 | $params['auth'] = []; 32 | } 33 | 34 | if (!ini_get('date.timezone')) { 35 | date_default_timezone_set('Etc/UTC'); 36 | } 37 | 38 | $params['auth'] = $params['auth'] + [ 39 | 'key' => $this->key, 40 | 'expires' => gmdate('Y/m/d H:i:s+00:00', strtotime($this->expires)), 41 | ]; 42 | return json_encode($params); 43 | } 44 | 45 | public function signString($string) { 46 | if (empty($this->secret)) { 47 | return null; 48 | } 49 | 50 | return hash_hmac('sha1', $string, $this->secret); 51 | } 52 | 53 | public function prepare() { 54 | $params = $this->getParamsString(); 55 | $this->fields['params'] = $params; 56 | 57 | $signature = $this->signString($params); 58 | if ($signature) { 59 | $this->fields['signature'] = $signature; 60 | } 61 | 62 | $this->configureUrl(); 63 | } 64 | 65 | public function configureUrl() { 66 | if (!empty($this->url)) { 67 | return; 68 | } 69 | 70 | $this->url = sprintf( 71 | '%s%s', 72 | $this->endpoint, 73 | $this->path 74 | ); 75 | } 76 | 77 | public function execute($response = null) { 78 | // note: $response is not used here, only needed to keep PHP strict mode 79 | // happy. 80 | 81 | $this->prepare(); 82 | $response = parent::execute(new TransloaditResponse()); 83 | $response->parseJson(); 84 | 85 | if ($this->path === '/assemblies' && $this->waitForCompletion) { 86 | return $this->_waitForCompletion($response); 87 | } 88 | return $response; 89 | } 90 | 91 | private function _waitForCompletion($response) { 92 | // Try assembly_ssl_url first, fall back to assembly_url 93 | $assemblyUrl = $response->data['assembly_ssl_url'] ?? $response->data['assembly_url'] ?? null; 94 | if (!$assemblyUrl) { 95 | throw new \RuntimeException('No assembly URL found in response. Response data: ' . json_encode($response->data)); 96 | } 97 | 98 | $parts = parse_url($assemblyUrl); 99 | 100 | while (true) { 101 | $req = new TransloaditRequest(); 102 | $req->endpoint = 'https://' . $parts['host']; 103 | $req->path = $parts['path']; 104 | $req->curlOptions = $this->curlOptions; 105 | $response = $req->execute(); 106 | 107 | if (isset($response->data['ok'])) { 108 | if ($response->data['ok'] === 'ASSEMBLY_UPLOADING' || $response->data['ok'] === 'ASSEMBLY_EXECUTING') { 109 | sleep(1); 110 | continue; 111 | } 112 | } 113 | 114 | // If this is an unknown, erroneous or completed Assembly completion state, return right away. 115 | return $response; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lib/transloadit/TransloaditResponse.php: -------------------------------------------------------------------------------- 1 | data)) { 17 | return sprintf('transloadit: bad response, no json: %s', $this->data); 18 | } 19 | 20 | if (array_key_exists('error', $this->data)) { 21 | $error = sprintf('transloadit: %s', $this->data['error']); 22 | 23 | if (array_key_exists('message', $this->data)) { 24 | $error .= sprintf(': %s', $this->data['message']); 25 | } 26 | 27 | if (array_key_exists('reason', $this->data)) { 28 | $error .= sprintf(': %s', $this->data['reason']); 29 | } 30 | 31 | return $error; 32 | } 33 | 34 | if (!array_key_exists('ok', $this->data)) { 35 | return 'transloadit: bad response data, no ok / error key included.'; 36 | } 37 | 38 | return false; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | plugins/ 5 | vendor/ 6 | vendors/ 7 | webroot/ 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 0 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | */config/* 79 | */tests/* 80 | */src/Command/* 81 | 82 | 83 | 84 | 0 85 | 86 | 87 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | test 20 | 21 | 22 | 23 | 25 | 26 | lib 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o errexit 3 | set -o errtrace 4 | set -o nounset 5 | set -o pipefail 6 | # set -o xtrace 7 | 8 | if [[ -z "${PACKAGIST_TOKEN:-}" ]]; then 9 | echo "PACKAGIST_TOKEN is not set" 10 | exit 1 11 | fi 12 | 13 | if ! grep "${VERSION}" composer.json > /dev/null 2>&1; then 14 | echo "First add '${VERSION}' to composer.json please" 15 | exit 1 16 | fi 17 | if ! grep "${VERSION}" CHANGELOG.md > /dev/null 2>&1; then 18 | echo "First add '${VERSION}' to CHANGELOG.md please" 19 | exit 1 20 | fi 21 | if [ -n "$(git status --porcelain)" ]; then 22 | echo "Git working tree not clean. First commit all your work please." 23 | exit 1 24 | fi 25 | 26 | git tag -f "${VERSION}" 27 | git push --tags -f 28 | curl \ 29 | -X POST \ 30 | -H 'Content-Type: application/json' \ 31 | -d '{"repository":{"url":"https://github.com/transloadit/php-sdk"}}' \ 32 | "https://packagist.org/api/update-package?username=kvz&apiToken=${PACKAGIST_TOKEN}" 33 | -------------------------------------------------------------------------------- /test/bootstrap.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 19 | 'Have a look at test/config.php to get this test to run.' 20 | ); 21 | return; 22 | } 23 | 24 | $this->request = new \transloadit\TransloaditRequest([ 25 | 'key' => TRANSLOADIT_KEY, 26 | 'secret' => TRANSLOADIT_SECRET, 27 | ]); 28 | } 29 | } 30 | 31 | class TransloaditRequestTestCase extends \PHPUnit\Framework\TestCase { 32 | protected \transloadit\Transloadit $transloadit; 33 | 34 | public function setUp(): void { 35 | if (!defined('TRANSLOADIT_KEY') || !defined('TRANSLOADIT_SECRET')) { 36 | $this->markTestSkipped( 37 | 'Have a look at test/config.php to get this test to run.' 38 | ); 39 | return; 40 | } 41 | 42 | $this->transloadit = new \transloadit\Transloadit([ 43 | 'key' => TRANSLOADIT_KEY, 44 | 'secret' => TRANSLOADIT_SECRET, 45 | ]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/config.php: -------------------------------------------------------------------------------- 1 | request = new CurlRequest(); 12 | } 13 | 14 | public function testAttributes() { 15 | $this->assertEquals('GET', $this->request->method); 16 | $this->assertEquals(null, $this->request->url); 17 | $this->assertEquals([], $this->request->headers); 18 | $this->assertEquals([], $this->request->fields); 19 | $this->assertEquals([], $this->request->files); 20 | } 21 | 22 | public function testConstructor() { 23 | $request = new CurlRequest(['url' => 'foobar']); 24 | $this->assertEquals('foobar', $request->url); 25 | } 26 | 27 | public function testGetCurlOptions() { 28 | // test return transfer 29 | $options = $this->request->getCurlOptions(); 30 | $this->assertEquals(true, $options[CURLOPT_RETURNTRANSFER]); 31 | 32 | // test method 33 | $this->request->method = 'PUT'; 34 | $options = $this->request->getCurlOptions(); 35 | $this->assertEquals($this->request->method, $options[CURLOPT_CUSTOMREQUEST]); 36 | 37 | // test url 38 | $this->request->url = 'http://foo.com/bar'; 39 | $options = $this->request->getCurlOptions(); 40 | $this->assertEquals($this->request->url, $options[CURLOPT_URL]); 41 | 42 | // test headers 43 | $this->request->headers = ['Foo: bar']; 44 | $options = $this->request->getCurlOptions(); 45 | $this->assertEquals($this->request->headers, $options[CURLOPT_HTTPHEADER]); 46 | 47 | // test put fields 48 | $this->request->fields = ['hello' => 'world']; 49 | $options = $this->request->getCurlOptions(); 50 | $this->assertEquals($this->request->fields, $options[CURLOPT_POSTFIELDS]); 51 | 52 | // test post fields 53 | $this->request->method = 'POST'; 54 | $options = $this->request->getCurlOptions(); 55 | $this->assertEquals($this->request->fields, $options[CURLOPT_POSTFIELDS]); 56 | $this->assertEquals($this->request->url, $options[CURLOPT_URL]); 57 | 58 | // test get query 59 | $this->request->method = 'GET'; 60 | $options = $this->request->getCurlOptions(); 61 | $this->assertEquals( 62 | $this->request->url . '?' . http_build_query($this->request->fields), 63 | $options[CURLOPT_URL] 64 | ); 65 | $this->assertArrayNotHasKey(CURLOPT_POSTFIELDS, $options); 66 | 67 | $fixture = dirname(dirname(__FILE__)) . '/fixture/image-resize-robot.jpg'; 68 | 69 | // test post files 70 | $this->request->method = 'POST'; 71 | $this->request->fields = ['super' => 'cool']; 72 | $this->request->files = ['foo' => $fixture]; 73 | $options = $this->request->getCurlOptions(); 74 | 75 | // -- Start edit -- 76 | // Edit by Aart Berkhout involving issue #8: CURL depricated functions (PHP 5.5) 77 | // https://github.com/transloadit/php-sdk/issues/8 78 | $filesOptions = function_exists('curl_file_create') ? 79 | ['foo' => curl_file_create($this->request->files['foo'])] : 80 | ['foo' => '@' . $this->request->files['foo']]; 81 | 82 | $this->assertEquals( 83 | array_merge( 84 | $this->request->fields, 85 | $filesOptions 86 | ), 87 | $options[CURLOPT_POSTFIELDS] 88 | ); 89 | 90 | // test file numbering 91 | $this->request->files = [$fixture]; 92 | $options = $this->request->getCurlOptions(); 93 | 94 | $filesOptions = function_exists('curl_file_create') ? 95 | ['file_1' => curl_file_create($this->request->files[0])] : 96 | ['file_1' => '@' . $this->request->files[0]]; 97 | 98 | $this->assertEquals( 99 | array_merge( 100 | $this->request->fields, 101 | $filesOptions 102 | ), 103 | $options[CURLOPT_POSTFIELDS] 104 | ); 105 | // -- End edit -- 106 | } 107 | 108 | public function testExecute() { 109 | // Can't test this method because PHP doesn't allow stubbing the calls 110 | // to curl easily. However, the method hardly contains any logic as all 111 | // of that is located in other methods. 112 | $this->assertTrue(true); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /test/simple/CurlResponseTest.php: -------------------------------------------------------------------------------- 1 | response = new CurlResponse(); 12 | } 13 | 14 | public function testAttributes() { 15 | $this->assertEquals(null, $this->response->data); 16 | $this->assertEquals(null, $this->response->curlErrorNumber); 17 | $this->assertEquals(null, $this->response->curlErrorMessage); 18 | $this->assertEquals(null, $this->response->curlInfo); 19 | } 20 | 21 | public function testConstructor() { 22 | $transloadit = new CurlResponse(['data' => 'foobar']); 23 | $this->assertEquals('foobar', $transloadit->data); 24 | } 25 | 26 | public function testParseJson() { 27 | $data = ['foo' => 'bar']; 28 | 29 | $this->response->data = json_encode($data); 30 | $r = $this->response->parseJson(); 31 | 32 | $this->assertEquals(true, $r); 33 | $this->assertEquals($data, $this->response->data); 34 | 35 | $data = $this->response->data = 'no json'; 36 | $r = $this->response->parseJson(); 37 | 38 | $this->assertEquals(false, $r); 39 | $this->assertEquals($data, $this->response->data); 40 | } 41 | 42 | public function testError() { 43 | $error = $this->response->error(); 44 | $this->assertEquals(false, $error); 45 | 46 | $number = $this->response->curlErrorNumber = 27; 47 | $message = $this->response->curlErrorMessage = 'Something went wrong'; 48 | $error = $this->response->error(); 49 | $this->assertEquals(sprintf('curl: %d: %s', $number, $message), $error); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/simple/TransloaditRequestTest.php: -------------------------------------------------------------------------------- 1 | request = new TransloaditRequest(); 13 | } 14 | 15 | public function testConstructor() { 16 | $this->assertInstanceOf('transloadit\\CurlRequest', $this->request); 17 | } 18 | 19 | public function testAttributes() { 20 | $this->assertEquals($this->request->endpoint, 'https://api2.transloadit.com'); 21 | $this->assertEquals($this->request->path, null); 22 | $this->assertEquals($this->request->key, null); 23 | $this->assertEquals($this->request->secret, null); 24 | $this->assertEquals($this->request->params, []); 25 | $this->assertEquals($this->request->expires, '+2 hours'); 26 | $this->assertEquals('Expect:', $this->request->headers[0]); 27 | $this->assertContains('Transloadit-Client: php-sdk:%s', $this->request->headers); 28 | } 29 | 30 | public function testInit() { 31 | $METHOD = 'CONNECT'; 32 | $PATH = '/foo'; 33 | 34 | $this->request->setMethodAndPath($METHOD, $PATH); 35 | $this->assertEquals($METHOD, $this->request->method); 36 | $this->assertEquals($PATH, $this->request->path); 37 | } 38 | 39 | public function testPrepare() { 40 | $this->request = $this->getMockBuilder(TransloaditRequest::class) 41 | ->setMethods(['getParamsString', 'signString', 'configureUrl']) 42 | ->getMock(); 43 | 44 | $PARAMS_STRING = '{super}'; 45 | $SIGNATURE_STRING = 'dsasjhdsajda'; 46 | 47 | $this->request 48 | ->method('getParamsString') 49 | ->willReturn($PARAMS_STRING); 50 | 51 | $this->request 52 | ->method('signString') 53 | ->with($this->equalTo($PARAMS_STRING)) 54 | ->willReturn($SIGNATURE_STRING); 55 | 56 | $this->request 57 | ->method('configureUrl'); 58 | 59 | $this->request->prepare(); 60 | $this->assertEquals($PARAMS_STRING, $this->request->fields['params']); 61 | $this->assertEquals($SIGNATURE_STRING, $this->request->fields['signature']); 62 | 63 | // Without signature 64 | $this->request = $this->getMockBuilder(TransloaditRequest::class) 65 | ->setMethods(['getParamsString', 'signString', 'configureUrl']) 66 | ->getMock(); 67 | $SIGNATURE_STRING = null; 68 | 69 | $this->request 70 | ->method('getParamsString') 71 | ->will($this->returnValue($PARAMS_STRING)); 72 | 73 | $this->request 74 | ->method('signString') 75 | ->with($this->equalTo($PARAMS_STRING)) 76 | ->will($this->returnValue($SIGNATURE_STRING)); 77 | 78 | $this->request 79 | ->method('configureUrl'); 80 | 81 | $this->request->prepare(); 82 | $this->assertEquals($PARAMS_STRING, $this->request->fields['params']); 83 | $this->assertArrayNotHasKey('signature', $this->request->fields); 84 | } 85 | 86 | public function testConfigureUrl() { 87 | $PATH = $this->request->path = '/foo'; 88 | $ENDPOINT = $this->request->endpoint = 'ftp://bar.com'; 89 | $this->request->configureUrl(); 90 | 91 | $this->assertEquals('ftp://bar.com/foo', $this->request->url); 92 | 93 | $URL = $this->request->url = 'http://custom.org/manual'; 94 | $this->request->configureUrl(); 95 | $this->assertEquals($URL, $this->request->url); 96 | } 97 | 98 | public function testSignString() { 99 | // No secret, no signature 100 | $this->assertEquals(null, $this->request->signString('foo')); 101 | 102 | // Verify the test vector given in the documentation, see: http://transloadit.com/docs/authentication 103 | $this->request->secret = 'd805593620e689465d7da6b8caf2ac7384fdb7e9'; 104 | $expectedSignature = 'fec703ccbe36b942c90d17f64b71268ed4f5f512'; 105 | 106 | $params = '{"auth":{"expires":"2010\/10\/19 09:01:20+00:00","key":"2b0c45611f6440dfb64611e872ec3211"},"steps":{"encode":{"robot":"\/video\/encode"}}}'; 107 | $signature = $this->request->signString($params); 108 | $this->assertEquals($expectedSignature, $signature); 109 | } 110 | 111 | public function testGetParamsString() { 112 | $this->request->key = 'dskjadjk2j42jkh4'; 113 | $PARAMS = $this->request->params = ['foo' => 'bar']; 114 | $paramsString = $this->request->getParamsString(); 115 | $params = json_decode($paramsString, true); 116 | 117 | $this->assertEquals($this->request->key, $params['auth']['key']); 118 | $this->assertEquals(gmdate('Y/m/d H:i:s+00:00', strtotime($this->request->expires)), $params['auth']['expires']); 119 | $this->assertEquals($PARAMS['foo'], $params['foo']); 120 | } 121 | 122 | public function testExecute() { 123 | // Can't test this method because PHP doesn't allow stubbing the calls 124 | // to curl easily. However, the method hardly contains any logic as all 125 | // of that is located in other methods. 126 | $this->assertTrue(true); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /test/simple/TransloaditResponseTest.php: -------------------------------------------------------------------------------- 1 | response = new TransloaditResponse(); 13 | } 14 | 15 | public function testConstructor() { 16 | $this->assertInstanceOf('transloadit\\CurlResponse', $this->response); 17 | } 18 | 19 | public function testError() { 20 | $this->response->data = 'no json'; 21 | $error = $this->response->error(); 22 | $this->assertEquals( 23 | sprintf('transloadit: bad response, no json: ' . $this->response->data), 24 | $error 25 | ); 26 | 27 | $this->response->data = ['ok' => 'ASSEMBLY_DOING_SOMETHING']; 28 | $error = $this->response->error(); 29 | $this->assertEquals(false, $error); 30 | 31 | unset($this->response->data['ok']); 32 | $error = $this->response->error(); 33 | $this->assertEquals( 34 | sprintf('transloadit: bad response data, no ok / error key included.'), 35 | $error 36 | ); 37 | 38 | $ERROR = 'ASSEMBLY_WENT_TOTALLY_BAD'; 39 | $this->response->data['error'] = $ERROR; 40 | $error = $this->response->error(); 41 | $this->assertEquals( 42 | sprintf('transloadit: %s', $ERROR), 43 | $error 44 | ); 45 | 46 | $MESSAGE = 'Something went awefully wrong!'; 47 | $this->response->data['message'] = $MESSAGE; 48 | $error = $this->response->error(); 49 | $this->assertEquals( 50 | sprintf('transloadit: %s: %s', $ERROR, $MESSAGE), 51 | $error 52 | ); 53 | 54 | $REASON = 'Something went awefully wrong!'; 55 | $this->response->data['reason'] = $REASON; 56 | $error = $this->response->error(); 57 | $this->assertEquals( 58 | sprintf('transloadit: %s: %s: %s', $ERROR, $MESSAGE, $REASON), 59 | $error 60 | ); 61 | 62 | $this->response->curlErrorNumber = 27; 63 | $error = $this->response->error(); 64 | $this->assertStringContainsString('curl', $error); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/simple/TransloaditTest.php: -------------------------------------------------------------------------------- 1 | transloadit = new Transloadit(); 14 | } 15 | 16 | public function testConstructor() { 17 | $transloadit = new Transloadit(['endpoint' => 'foobar']); 18 | $this->assertEquals('foobar', $transloadit->endpoint); 19 | } 20 | 21 | public function testAttributes() { 22 | $this->assertEquals($this->transloadit->key, null); 23 | $this->assertEquals($this->transloadit->secret, null); 24 | } 25 | 26 | public function testCreateAssembly() { 27 | $transloadit = $this->getMockBuilder(Transloadit::class) 28 | ->setMethods(['request']) 29 | ->getMock(); 30 | $assembly = $this->getMockBuilder(TransloaditResponse::class) 31 | ->getMock(); 32 | 33 | $options = ['foo' => 'bar']; 34 | 35 | $transloadit 36 | ->method('request') 37 | ->with($this->equalTo($options + [ 38 | 'method' => 'POST', 39 | 'path' => '/assemblies', 40 | ])) 41 | ->willReturn($assembly); 42 | 43 | $this->assertEquals($assembly, $transloadit->createAssembly($options)); 44 | } 45 | 46 | public function testCancelAssembly() { 47 | $transloadit = $this->getMockBuilder(Transloadit::class) 48 | ->setMethods(['request']) 49 | ->getMock(); 50 | $assembly = $this->getMockBuilder(TransloaditResponse::class) 51 | ->getMock(); 52 | $response = $this->getMockBuilder(TransloaditResponse::class) 53 | ->getMock(); 54 | 55 | $assemblyId = 'b7716f21ba1a400f8b1a60a6e1c6acf1'; 56 | $assembly->data = ['assembly_url' => sprintf('https://api2-phpsdktest.transloadit.com/assemblies/%s', $assemblyId)]; 57 | 58 | $transloadit 59 | ->method('request') 60 | ->withConsecutive( 61 | [$this->equalTo([ 62 | 'method' => 'GET', 63 | 'path' => sprintf('/assemblies/%s', $assemblyId), 64 | ]) 65 | ], 66 | [$this->equalTo([ 67 | 'method' => 'DELETE', 68 | 'path' => sprintf('/assemblies/%s', $assemblyId), 69 | 'host' => 'api2-phpsdktest.transloadit.com', 70 | ]) 71 | ], 72 | ) 73 | ->willReturnOnConsecutiveCalls($assembly, $response); 74 | 75 | $this->assertEquals($response, $transloadit->cancelAssembly($assemblyId)); 76 | } 77 | 78 | public function testRequest() { 79 | $this->transloadit->key = 'my-key'; 80 | $this->transloadit->secret = 'my-secret'; 81 | $request = $this->transloadit->request(['url' => 'foobar'], false); 82 | 83 | $this->assertEquals($this->transloadit->key, $request->key); 84 | $this->assertEquals($this->transloadit->secret, $request->secret); 85 | $this->assertEquals('foobar', $request->url); 86 | 87 | // Unfortunately we can't test the $execute parameter because PHP 88 | // is a little annoying. But that's ok for now. 89 | } 90 | 91 | public function testResponse() { 92 | $response = Transloadit::response(); 93 | $this->assertEquals(false, $response); 94 | 95 | $data = ['foo' => 'bar']; 96 | $_POST['transloadit'] = json_encode($data); 97 | $response = Transloadit::response(); 98 | $this->assertInstanceOf(TransloaditResponse::class, $response); 99 | $this->assertEquals($data, $response->data); 100 | 101 | 102 | // Can't really test the $_GET['assembly_url'] case because of PHP for now. 103 | } 104 | 105 | public function testCreateAssemblyForm() { 106 | $transloadit = $this->getMockBuilder(Transloadit::class) 107 | ->setMethods(['request']) 108 | ->getMock(); 109 | $assembly = $this->getMockBuilder(TransloaditResponse::class) 110 | ->setMethods(['prepare']) 111 | ->getMock(); 112 | 113 | $assembly->method = 'ROCK'; 114 | $assembly->url = 'http://api999.transloadit.com/assemblies'; 115 | $assembly->fields = [ 116 | 'foo' => 'bar"bar', 117 | 'hey' => 'you', 118 | ]; 119 | $options = ['foo' => 'bar']; 120 | 121 | $transloadit 122 | ->method('request') 123 | ->with($this->equalTo($options + [ 124 | 'method' => 'POST', 125 | 'path' => '/assemblies', 126 | ]), $this->equalTo(false)) 127 | ->willReturn($assembly); 128 | 129 | $assembly 130 | ->method('prepare'); 131 | 132 | $options['attributes'] = ['class' => 'nice']; 133 | $tags = explode("\n", $transloadit->createAssemblyForm($options)); 134 | 135 | $formTag = array_shift($tags); 136 | $this->assertTrue(preg_match('/action="http:\/\/api999\.transloadit\.com\/assemblies"/', $formTag) !== false); 137 | $this->assertTrue(preg_match('/method="ROCK"/', $formTag) !== false); 138 | $this->assertTrue(preg_match('/enctype="multipart\/form-data"/', $formTag) !== false); 139 | $this->assertTrue(preg_match('/class="nice"/', $formTag) !== false); 140 | 141 | foreach ($assembly->fields as $field => $val) { 142 | $inputTag = array_shift($tags); 143 | $this->assertTrue(preg_match('/type="hidden"/', $inputTag) !== false); 144 | $this->assertTrue(preg_match('/name="' . $field . '"/', $inputTag) !== false); 145 | $this->assertTrue(preg_match('/value="' . $val . '"/', $inputTag) !== false); 146 | } 147 | } 148 | 149 | private function getExpectedUrl(array $params): ?string { 150 | if (getenv('TEST_NODE_PARITY') !== '1') { 151 | return null; 152 | } 153 | 154 | // Check for tsx before trying to use it 155 | exec('which tsx 2>/dev/null', $output, $returnVar); 156 | if ($returnVar !== 0) { 157 | throw new \RuntimeException('tsx command not found. Please install it with: npm install -g tsx'); 158 | } 159 | 160 | $scriptPath = __DIR__ . '/../../tool/node-smartcdn-sig.ts'; 161 | $jsonInput = json_encode($params); 162 | 163 | $descriptorspec = [ 164 | 0 => ["pipe", "r"], // stdin 165 | 1 => ["pipe", "w"], // stdout 166 | 2 => ["pipe", "w"] // stderr 167 | ]; 168 | 169 | $process = proc_open("tsx $scriptPath", $descriptorspec, $pipes); 170 | 171 | if (!is_resource($process)) { 172 | throw new \RuntimeException('Failed to start Node script'); 173 | } 174 | 175 | fwrite($pipes[0], $jsonInput); 176 | fclose($pipes[0]); 177 | 178 | $output = stream_get_contents($pipes[1]); 179 | $error = stream_get_contents($pipes[2]); 180 | 181 | fclose($pipes[1]); 182 | fclose($pipes[2]); 183 | 184 | $exitCode = proc_close($process); 185 | 186 | if ($exitCode !== 0) { 187 | throw new \RuntimeException("Node script failed: $error"); 188 | } 189 | 190 | return trim($output); 191 | } 192 | 193 | private function assertParityWithNode(string $url, array $params, string $message = ''): void { 194 | $expectedUrl = $this->getExpectedUrl($params); 195 | if ($expectedUrl !== null) { 196 | $this->assertEquals($expectedUrl, $url, $message ?: 'URL should match Node.js reference implementation'); 197 | } 198 | } 199 | 200 | private function debugUrls(string $phpUrl, string $nodeUrl, string $message = ''): void { 201 | echo "\n\nDebug $message:\n"; 202 | echo "PHP URL: $phpUrl\n"; 203 | echo "Node URL: $nodeUrl\n\n"; 204 | } 205 | 206 | public function testSignedSmartCDNUrl() { 207 | $transloadit = new Transloadit([ 208 | 'key' => 'test-key', 209 | 'secret' => 'test-secret' 210 | ]); 211 | 212 | // Use fixed timestamp for all tests 213 | $expireAtMs = 1732550672867; 214 | 215 | // Test basic URL generation 216 | $params = [ 217 | 'workspace' => 'workspace', 218 | 'template' => 'template', 219 | 'input' => 'file.jpg', 220 | 'auth_key' => 'test-key', 221 | 'auth_secret' => 'test-secret', 222 | 'expire_at_ms' => $expireAtMs 223 | ]; 224 | $url = $transloadit->signedSmartCDNUrl( 225 | $params['workspace'], 226 | $params['template'], 227 | $params['input'], 228 | [], 229 | $expireAtMs 230 | ); 231 | $nodeUrl = $this->getExpectedUrl($params); 232 | $expectedUrl = 'https://workspace.tlcdn.com/template/file.jpg?auth_key=test-key&exp=1732550672867&sig=sha256%3Ad994b8a737db1c43d6e04a07018dc33e8e28b23b27854bd6383d828a212cfffb'; 233 | $this->assertEquals($expectedUrl, $url, 'PHP URL should match expected'); 234 | $this->assertEquals($expectedUrl, $nodeUrl, 'Node.js URL should match expected'); 235 | 236 | // Test with input field 237 | $params['input'] = 'input.jpg'; 238 | $url = $transloadit->signedSmartCDNUrl( 239 | $params['workspace'], 240 | $params['template'], 241 | $params['input'], 242 | [], 243 | $expireAtMs 244 | ); 245 | $nodeUrl = $this->getExpectedUrl($params); 246 | $expectedUrl = 'https://workspace.tlcdn.com/template/input.jpg?auth_key=test-key&exp=1732550672867&sig=sha256%3A75991f02828d194792c9c99f8fea65761bcc4c62dbb287a84f642033128297c0'; 247 | $this->assertEquals($expectedUrl, $url, 'PHP URL should match expected'); 248 | $this->assertEquals($expectedUrl, $nodeUrl, 'Node.js URL should match expected'); 249 | 250 | // Test with additional params 251 | $params['input'] = 'file.jpg'; 252 | $params['url_params'] = ['width' => 100]; 253 | $url = $transloadit->signedSmartCDNUrl( 254 | $params['workspace'], 255 | $params['template'], 256 | $params['input'], 257 | $params['url_params'], 258 | $expireAtMs 259 | ); 260 | $nodeUrl = $this->getExpectedUrl($params); 261 | $expectedUrl = 'https://workspace.tlcdn.com/template/file.jpg?auth_key=test-key&exp=1732550672867&width=100&sig=sha256%3Ae5271d8fb6482d9351ebe4285b6fc75539c4d311ff125c4d76d690ad71c258ef'; 262 | $this->assertEquals($expectedUrl, $url, 'PHP URL should match expected'); 263 | $this->assertEquals($expectedUrl, $nodeUrl, 'Node.js URL should match expected'); 264 | 265 | // Test with empty param string 266 | $params['url_params'] = ['width' => '', 'height' => '200']; 267 | $url = $transloadit->signedSmartCDNUrl( 268 | $params['workspace'], 269 | $params['template'], 270 | $params['input'], 271 | $params['url_params'], 272 | $expireAtMs 273 | ); 274 | $nodeUrl = $this->getExpectedUrl($params); 275 | $expectedUrl = 'https://workspace.tlcdn.com/template/file.jpg?auth_key=test-key&exp=1732550672867&height=200&width=&sig=sha256%3A1a26733c859f070bc3d83eb3174650d7a0155642e44a5ac448a43bc728bc0f85'; 276 | $this->assertEquals($expectedUrl, $url, 'PHP URL should match expected'); 277 | $this->assertEquals($expectedUrl, $nodeUrl, 'Node.js URL should match expected'); 278 | 279 | // Test with null width parameter (should be excluded) 280 | $params['url_params'] = ['width' => null, 'height' => '200']; 281 | $url = $transloadit->signedSmartCDNUrl( 282 | $params['workspace'], 283 | $params['template'], 284 | $params['input'], 285 | $params['url_params'], 286 | $expireAtMs 287 | ); 288 | $nodeUrl = $this->getExpectedUrl($params); 289 | $expectedUrl = 'https://workspace.tlcdn.com/template/file.jpg?auth_key=test-key&exp=1732550672867&height=200&sig=sha256%3Adb740ebdfad6e766ebf6516ed5ff6543174709f8916a254f8d069c1701cef517'; 290 | $this->assertEquals($expectedUrl, $url, 'PHP URL should match expected'); 291 | $this->assertEquals($expectedUrl, $nodeUrl, 'Node.js URL should match expected'); 292 | 293 | // Test with only empty width parameter 294 | $params['url_params'] = ['width' => '']; 295 | $url = $transloadit->signedSmartCDNUrl( 296 | $params['workspace'], 297 | $params['template'], 298 | $params['input'], 299 | $params['url_params'], 300 | $expireAtMs 301 | ); 302 | $nodeUrl = $this->getExpectedUrl($params); 303 | $expectedUrl = 'https://workspace.tlcdn.com/template/file.jpg?auth_key=test-key&exp=1732550672867&width=&sig=sha256%3A840426f9ac72dde02fd080f09b2304d659fdd41e630b1036927ec1336c312e9d'; 304 | $this->assertEquals($expectedUrl, $url, 'PHP URL should match expected'); 305 | $this->assertEquals($expectedUrl, $nodeUrl, 'Node.js URL should match expected'); 306 | } 307 | 308 | public function testTsxRequiredForParityTesting(): void { 309 | if (getenv('TEST_NODE_PARITY') !== '1') { 310 | $this->markTestSkipped('Parity testing not enabled'); 311 | } 312 | 313 | // Temporarily override PATH to simulate missing tsx 314 | $originalPath = getenv('PATH'); 315 | putenv('PATH=/usr/bin:/bin'); 316 | 317 | try { 318 | $params = [ 319 | 'workspace' => 'test', 320 | 'template' => 'test', 321 | 'input' => 'test.jpg', 322 | 'auth_key' => 'test', 323 | 'auth_secret' => 'test' 324 | ]; 325 | $this->getExpectedUrl($params); 326 | $this->fail('Expected RuntimeException when tsx is not available'); 327 | } catch (\RuntimeException $e) { 328 | $this->assertStringContainsString('tsx command not found', $e->getMessage()); 329 | $this->assertStringContainsString('npm install -g tsx', $e->getMessage()); 330 | } finally { 331 | // Restore original PATH 332 | putenv("PATH=$originalPath"); 333 | } 334 | } 335 | 336 | public function testExpireAtMs(): void { 337 | $transloadit = new Transloadit([ 338 | 'key' => 'test-key', 339 | 'secret' => 'test-secret' 340 | ]); 341 | 342 | // Test with explicit expireAtMs 343 | $expireAtMs = 1732550672867; 344 | $params = [ 345 | 'workspace' => 'workspace', 346 | 'template' => 'template', 347 | 'input' => 'file.jpg', 348 | 'auth_key' => 'test-key', 349 | 'auth_secret' => 'test-secret', 350 | 'expire_at_ms' => $expireAtMs 351 | ]; 352 | 353 | $url = $transloadit->signedSmartCDNUrl( 354 | $params['workspace'], 355 | $params['template'], 356 | $params['input'], 357 | [], 358 | $params['expire_at_ms'] 359 | ); 360 | 361 | $this->assertStringContainsString("exp=$expireAtMs", $url); 362 | $this->assertParityWithNode($url, $params); 363 | 364 | // Test default expiry (should be about 1 hour from now) 365 | unset($params['expire_at_ms']); 366 | $url = $transloadit->signedSmartCDNUrl( 367 | $params['workspace'], 368 | $params['template'], 369 | $params['input'] 370 | ); 371 | 372 | $matches = []; 373 | preg_match('/exp=(\d+)/', $url, $matches); 374 | $this->assertNotEmpty($matches[1], 'URL should contain expiry timestamp'); 375 | 376 | $expiry = (int)$matches[1]; 377 | $now = time() * 1000; 378 | $oneHour = 60 * 60 * 1000; 379 | 380 | $this->assertGreaterThan($now, $expiry, 'Expiry should be in the future'); 381 | $this->assertLessThan($now + $oneHour + 5000, $expiry, 'Expiry should be about 1 hour from now'); 382 | $this->assertGreaterThan($now + $oneHour - 5000, $expiry, 'Expiry should be about 1 hour from now'); 383 | 384 | // For parity test, set the exact expiry time to match Node.js 385 | $params['expire_at_ms'] = $expiry; 386 | $this->assertParityWithNode($url, $params); 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /test/system/CurlRequest/CurlRequestRootTest.php: -------------------------------------------------------------------------------- 1 | url = 'http://api2.transloadit.com/'; 11 | $request->method = 'GET'; 12 | $response = $request->execute(); 13 | $this->assertStringContainsString('"ok"', $response->data); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/system/Transloadit/TransloaditAssemblyCreateTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 13 | 'TRANSLOADIT_KEY and TRANSLOADIT_SECRET environment variables are required.' 14 | ); 15 | return; 16 | } 17 | 18 | $this->transloadit = new Transloadit([ 19 | 'key' => getenv('TRANSLOADIT_KEY'), 20 | 'secret' => getenv('TRANSLOADIT_SECRET'), 21 | ]); 22 | } 23 | 24 | public function testRoot() { 25 | $response = $this->transloadit->createAssembly([ 26 | 'files' => [TEST_FIXTURE_DIR . '/image-resize-robot.jpg'], 27 | 'params' => [ 28 | 'steps' => [ 29 | 'resize' => [ 30 | 'robot' => '/image/resize', 31 | 'width' => 100, 32 | 'height' => 100, 33 | 'result' => true, 34 | ], 35 | ], 36 | ] 37 | ]); 38 | $this->assertEquals('ASSEMBLY_EXECUTING', $response->data['ok']); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/system/Transloadit/TransloaditCreateAssemblyWaitForCompletionTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped( 13 | 'Have a look at test/config.php to get this test to run.' 14 | ); 15 | return; 16 | } 17 | 18 | // @todo Load config from git excluded config file 19 | $this->transloadit = new Transloadit([ 20 | 'key' => TRANSLOADIT_KEY, 21 | 'secret' => TRANSLOADIT_SECRET, 22 | ]); 23 | } 24 | public function testRoot() { 25 | $response = $this->transloadit->createAssembly([ 26 | 'files' => [TEST_FIXTURE_DIR . '/image-resize-robot.jpg'], 27 | 'params' => [ 28 | 'steps' => [ 29 | 'resize' => [ 30 | 'robot' => '/image/resize', 31 | 'width' => 100, 32 | 'height' => 100, 33 | 'result' => true, 34 | ], 35 | ], 36 | ], 37 | 'waitForCompletion' => true 38 | ]); 39 | $this->assertEquals('ASSEMBLY_COMPLETED', $response->data['ok']); 40 | 41 | $getResp = $this->transloadit->getAssembly($response->data['assembly_id']); 42 | $this->assertEquals('ASSEMBLY_COMPLETED', $getResp->data['ok']); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/system/TransloaditRequest/Base.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transloadit/php-sdk/4eabc08bfb2cd8744c35d13238587190379a0e4e/test/system/TransloaditRequest/Base.php -------------------------------------------------------------------------------- /test/system/TransloaditRequest/TransloaditRequestAssemblyCreateTest.php: -------------------------------------------------------------------------------- 1 | request->setMethodAndPath('POST', '/assemblies'); 8 | $this->request->files[] = TEST_FIXTURE_DIR . '/image-resize-robot.jpg'; 9 | $this->request->params = [ 10 | 'steps' => [ 11 | 'resize' => [ 12 | 'robot' => '/image/resize', 13 | 'width' => 100, 14 | 'height' => 100, 15 | 'result' => true, 16 | ], 17 | ], 18 | ]; 19 | $response = $this->request->execute(); 20 | 21 | $this->assertEquals('ASSEMBLY_EXECUTING', $response->data['ok']); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/system/TransloaditRequest/TransloaditRequestErrorTest.php: -------------------------------------------------------------------------------- 1 | request->setMethodAndPath('POST', '/assemblies'); 8 | $response = $this->request->execute(); 9 | 10 | $error = $response->error(); 11 | $this->assertStringContainsString('transloadit', $error); 12 | $this->assertStringContainsString('STEPS', $error); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/system/TransloaditRequest/TransloaditRequestGetBillTest.php: -------------------------------------------------------------------------------- 1 | request->setMethodAndPath('GET', '/bill/' . date('Y-m')); 8 | $response = $this->request->execute(); 9 | 10 | $this->assertStringContainsString('BILL', $response->data['ok']); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/system/TransloaditRequest/TransloaditRequestHttpsRootTest.php: -------------------------------------------------------------------------------- 1 | request->protocol = 'https'; 8 | $this->request->setMethodAndPath('GET', '/'); 9 | $response = $this->request->execute(); 10 | 11 | $this->assertEquals(true, array_key_exists('ok', $response->data)); 12 | $this->assertInstanceOf('transloadit\TransloaditResponse', $response); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/system/TransloaditRequest/TransloaditRequestNoJsonErrorTest.php: -------------------------------------------------------------------------------- 1 | request->url = 'http://google.com/'; 8 | $response = $this->request->execute(); 9 | 10 | $error = $response->error(); 11 | $this->assertStringContainsString('no json', $error); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/system/TransloaditRequest/TransloaditRequestRootTest.php: -------------------------------------------------------------------------------- 1 | request->setMethodAndPath('GET', '/'); 8 | $response = $this->request->execute(); 9 | 10 | $this->assertStringStartsWith('Transloadit-Client: php-sdk:', $this->request->headers[1]); 11 | $this->assertEquals(true, isset($response->data['ok'])); 12 | $this->assertInstanceOf('transloadit\TransloaditResponse', $response); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tool/generate-example-docs.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | import { createHash, createHmac } from 'crypto' 9 | 10 | interface SmartCDNParams { 11 | workspace: string 12 | template: string 13 | input: string 14 | expire_at_ms?: number 15 | auth_key?: string 16 | auth_secret?: string 17 | url_params?: Record 18 | } 19 | 20 | function signSmartCDNUrl(params: SmartCDNParams): string { 21 | const { 22 | workspace, 23 | template, 24 | input, 25 | expire_at_ms, 26 | auth_key, 27 | auth_secret, 28 | url_params = {}, 29 | } = params 30 | 31 | if (!workspace) throw new Error('workspace is required') 32 | if (!template) throw new Error('template is required') 33 | if (input === null || input === undefined) 34 | throw new Error('input must be a string') 35 | if (!auth_key) throw new Error('auth_key is required') 36 | if (!auth_secret) throw new Error('auth_secret is required') 37 | 38 | const workspaceSlug = encodeURIComponent(workspace) 39 | const templateSlug = encodeURIComponent(template) 40 | const inputField = encodeURIComponent(input) 41 | 42 | const expireAt = expire_at_ms ?? Date.now() + 60 * 60 * 1000 // 1 hour default 43 | 44 | const queryParams: Record = {} 45 | 46 | // Handle url_params 47 | Object.entries(url_params).forEach(([key, value]) => { 48 | if (value === null || value === undefined) return 49 | if (Array.isArray(value)) { 50 | value.forEach((val) => { 51 | if (val === null || val === undefined) return 52 | ;(queryParams[key] ||= []).push(String(val)) 53 | }) 54 | } else { 55 | queryParams[key] = [String(value)] 56 | } 57 | }) 58 | 59 | queryParams.auth_key = [auth_key] 60 | queryParams.exp = [String(expireAt)] 61 | 62 | // Sort parameters to ensure consistent ordering 63 | const sortedParams = Object.entries(queryParams) 64 | .sort() 65 | .map(([key, values]) => 66 | values.map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`) 67 | ) 68 | .flat() 69 | .join('&') 70 | 71 | const stringToSign = `${workspaceSlug}/${templateSlug}/${inputField}?${sortedParams}` 72 | const signature = createHmac('sha256', auth_secret) 73 | .update(stringToSign) 74 | .digest('hex') 75 | 76 | const finalParams = `${sortedParams}&sig=${encodeURIComponent( 77 | `sha256:${signature}` 78 | )}` 79 | return `https://${workspaceSlug}.tlcdn.com/${templateSlug}/${inputField}?${finalParams}` 80 | } 81 | 82 | // Read JSON from stdin 83 | let jsonInput = '' 84 | process.stdin.on('data', (chunk) => { 85 | jsonInput += chunk 86 | }) 87 | 88 | process.stdin.on('end', () => { 89 | const params = JSON.parse(jsonInput) 90 | console.log(signSmartCDNUrl(params)) 91 | }) 92 | --------------------------------------------------------------------------------