Repository: cloudconvert/cloudconvert-php
Branch: master
Commit: 47572694dde6
Files: 53
Total size: 93.2 KB
Directory structure:
gitextract_0ugw1w8m/
├── .github/
│ └── workflows/
│ └── run-tests.yml
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml
├── src/
│ ├── CloudConvert.php
│ ├── Exceptions/
│ │ ├── Exception.php
│ │ ├── HttpClientException.php
│ │ ├── HttpServerException.php
│ │ ├── SignatureVerificationException.php
│ │ └── UnexpectedDataException.php
│ ├── Handler/
│ │ ├── SignedUrlBuilder.php
│ │ └── WebhookHandler.php
│ ├── Hydrator/
│ │ ├── HydratorInterface.php
│ │ └── JsonMapperHydrator.php
│ ├── Models/
│ │ ├── Collection.php
│ │ ├── Job.php
│ │ ├── JobCollection.php
│ │ ├── Task.php
│ │ ├── TaskCollection.php
│ │ ├── User.php
│ │ └── WebhookEvent.php
│ ├── Resources/
│ │ ├── AbstractResource.php
│ │ ├── JobsResource.php
│ │ ├── TasksResource.php
│ │ └── UsersResource.php
│ └── Transport/
│ └── HttpTransport.php
└── tests/
├── Integration/
│ ├── JobTest.php
│ ├── TaskTest.php
│ ├── TestCase.php
│ └── UserTest.php
├── Unit/
│ ├── ExceptionsTest.php
│ ├── JobResourceTest.php
│ ├── SignedUrlBuilderTest.php
│ ├── TaskCollectionTest.php
│ ├── TaskResourceTest.php
│ ├── TestCase.php
│ ├── UsersResourceTest.php
│ ├── WebhookHandlerTest.php
│ ├── requests/
│ │ └── webhook_job_finished_payload.json
│ └── responses/
│ ├── error400.json
│ ├── error402.json
│ ├── job.json
│ ├── job_created.json
│ ├── job_export_urls.json
│ ├── jobs.json
│ ├── task.json
│ ├── task_created.json
│ ├── tasks.json
│ ├── upload_task_created.json
│ └── user.json
└── bootstrap.php
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/run-tests.yml
================================================
name: Tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ["8.5", "8.4", "8.3", "8.2", "8.1", "8.0", "7.4"]
steps:
- uses: actions/checkout@v2
- name: Cache dependencies
uses: actions/cache@v4
with:
path: ~/.composer/cache/files
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
restore-keys: |
dependencies-php-${{ matrix.php }}-composer-
dependencies-php-
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: json, dom, curl, libxml, mbstring
coverage: none
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Execute tests
run: vendor/bin/phpunit
================================================
FILE: .gitignore
================================================
.project
.idea
.settings
*.iml
*.sublime-project
*.sublime-workspace
.DS_Store
output.pdf
node_modules
vendor
docs
composer.lock
composer.phar
cloudconvert-php.phar
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 Josias Montag <josias@montag.info>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
cloudconvert-php
=======================
This is the official PHP SDK for the [CloudConvert](https://cloudconvert.com/api/v2) **API v2**.
[](https://github.com/cloudconvert/cloudconvert-php/actions/workflows/run-tests.yml)
[](https://packagist.org/packages/cloudconvert/cloudconvert-php)
[](https://packagist.org/packages/cloudconvert/cloudconvert-php)
Install
-------------------
To install the PHP SDK you will need to be using [Composer]([https://getcomposer.org/)
in your project.
Install the SDK alongside Guzzle:
```bash
composer require cloudconvert/cloudconvert-php guzzlehttp/guzzle
```
This package is not tied to any specific HTTP client by using [PSR-7](https://www.php-fig.org/psr/psr-7/), [PSR-17](https://www.php-fig.org/psr/psr-17/), [PSR-18](https://www.php-fig.org/psr/psr-18/), and [HTTPlug](https://httplug.io/). Therefore, you will also need to install packages that provide [`psr/http-client-implementation`](https://packagist.org/providers/psr/http-client-implementation) and [`psr/http-factory-implementation`](https://packagist.org/providers/psr/http-factory-implementation) (for example Guzzle).
Creating Jobs
-------------------
```php
use \CloudConvert\CloudConvert;
use \CloudConvert\Models\Job;
use \CloudConvert\Models\Task;
$cloudconvert = new CloudConvert([
'api_key' => 'API_KEY',
'sandbox' => false
]);
$job = (new Job())
->setTag('myjob-1')
->addTask(
(new Task('import/url', 'import-my-file'))
->set('url','https://my-url')
)
->addTask(
(new Task('convert', 'convert-my-file'))
->set('input', 'import-my-file')
->set('output_format', 'pdf')
->set('some_other_option', 'value')
)
->addTask(
(new Task('export/url', 'export-my-file'))
->set('input', 'convert-my-file')
);
$cloudconvert->jobs()->create($job)
```
You can use the [CloudConvert Job Builder](https://cloudconvert.com/api/v2/jobs/builder) to see the available options for the various task types.
Uploading Files
-------------------
Uploads to CloudConvert are done via `import/upload` tasks (see the [docs](https://cloudconvert.com/api/v2/import#import-upload-tasks)). This SDK offers a convenient upload method:
```php
use \CloudConvert\Models\Job;
use \CloudConvert\Models\Task;
$job = (new Job())
->addTask(new Task('import/upload','upload-my-file'))
->addTask(
(new Task('convert', 'convert-my-file'))
->set('input', 'upload-my-file')
->set('output_format', 'pdf')
)
->addTask(
(new Task('export/url', 'export-my-file'))
->set('input', 'convert-my-file')
);
$job = $cloudconvert->jobs()->create($job);
$uploadTask = $job->getTasks()->whereName('upload-my-file')[0];
$cloudconvert->tasks()->upload($uploadTask, fopen('./file.pdf', 'r'), 'file.pdf');
```
The `upload()` method accepts a string, PHP resource or PSR-7 `StreamInterface` as second parameter.
You can also directly allow clients to upload files to CloudConvert:
```html
<form action="<?=$uploadTask->getResult()->form->url?>"
method="POST"
enctype="multipart/form-data">
<? foreach ((array)$uploadTask->getResult()->form->parameters as $parameter => $value) { ?>
<input type="hidden" name="<?=$parameter?>" value="<?=$value?>">
<? } ?>
<input type="file" name="file">
<input type="submit">
</form>
```
Downloading Files
-------------------
CloudConvert can generate public URLs for using `export/url` tasks. You can use the PHP SDK to download the output files when the Job is finished.
```php
$cloudconvert->jobs()->wait($job); // Wait for job completion
foreach ($job->getExportUrls() as $file) {
$source = $cloudconvert->getHttpTransport()->download($file->url)->detach();
$dest = fopen('output/' . $file->filename, 'w');
stream_copy_to_stream($source, $dest);
}
```
The `download()` method returns a PSR-7 `StreamInterface`, which can be used as a PHP resource using `detach()`.
Webhooks
-------------------
Webhooks can be created on the [CloudConvert Dashboard](https://cloudconvert.com/dashboard/api/v2/webhooks) and you can also find the required signing secret there.
```php
$cloudconvert = new CloudConvert([
'api_key' => 'API_KEY',
'sandbox' => false
]);
$signingSecret = '...'; // You can find it in your webhook settings
$payload = @file_get_contents('php://input');
$signature = $_SERVER['HTTP_CLOUDCONVERT_SIGNATURE'];
try {
$webhookEvent = $cloudconvert->webhookHandler()->constructEvent($payload, $signature, $signingSecret);
} catch(\CloudConvert\Exceptions\UnexpectedDataException $e) {
// Invalid payload
http_response_code(400);
exit();
} catch(\CloudConvert\Exceptions\SignatureVerificationException $e) {
// Invalid signature
http_response_code(400);
exit();
}
$job = $webhookEvent->getJob();
$job->getTag(); // can be used to store an ID
$exportTask = $job->getTasks()
->whereStatus(Task::STATUS_FINISHED) // get the task with 'finished' status ...
->whereName('export-it')[0]; // ... and with the name 'export-it'
// ...
```
Alternatively, you can construct a `WebhookEvent` using a PSR-7 `RequestInterface`:
```php
$webhookEvent = $cloudconvert->webhookHandler()->constructEventFromRequest($request, $signingSecret);
```
Signed URLs
-------------------
Signed URLs allow converting files on demand only using URL query parameters. The PHP SDK allows to generate such URLs. Therefore, you need to obtain a signed URL base and a signing secret on the [CloudConvert Dashboard](https://cloudconvert.com/dashboard/api/v2/signed-urls).
```php
$cloudconvert = new CloudConvert([
'api_key' => 'API_KEY',
'sandbox' => false
]);
$job = (new Job())
->addTask(
(new Task('import/url', 'import-my-file'))
->set('url', 'https://my.url/file.docx')
)
->addTask(
(new Task('convert', 'convert-my-file'))
->set('input', 'import-my-file')
->set('output_format', 'pdf')
)
->addTask(
(new Task('export/url', 'export-my-file'))
->set('input', 'convert-my-file')
);
$signedUrlBase = 'SIGNED_URL_BASE';
$signingSecret = 'SIGNED_URL_SIGNING_SECRET';
$url = $cloudConvert->signedUrlBuilder()->createFromJob($signedUrlBase, $signingSecret, $job, 'CACHE_KEY');
```
Setting a Region
-----------------
By default, the region in your [account settings](https://cloudconvert.com/dashboard/region) is used. Alternatively, you can set a fixed region:
```php
// Pass the region to the constructor
$cloudconvert = new CloudConvert([
'api_key' => 'API_KEY',
'sandbox' => false,
'region' => 'us-east'
]);
```
Unit Tests
-----------------
vendor/bin/phpunit --testsuite unit
Integration Tests
-----------------
vendor/bin/phpunit --testsuite integration
By default, this runs the integration tests against the Sandbox API with an official CloudConvert account. If you would like to use your own account, you can set your API key using the `CLOUDCONVERT_API_KEY` enviroment variable. In this case you need to whitelist the following MD5 hashes for Sandbox API (using the CloudConvert dashboard).
53d6fe6b688c31c565907c81de625046 input.pdf
99d4c165f77af02015aa647770286cf9 input.png
Resources
---------
* [API Documentation](https://cloudconvert.com/api/v2)
* [CloudConvert Blog](https://cloudconvert.com/blog)
================================================
FILE: composer.json
================================================
{
"name": "cloudconvert/cloudconvert-php",
"description": "PHP SDK for CloudConvert APIs",
"homepage": "https://github.com/cloudconvert/cloudconvert-php",
"authors": [
{
"name": "Josias Montag",
"email": "josias@montag.info"
}
],
"require": {
"php": "^7.4|^8.0",
"ext-json": "*",
"php-http/httplug": "^2.4",
"php-http/client-common": "^2.0",
"php-http/discovery": "^1.17",
"php-http/multipart-stream-builder": "^1.3",
"psr/http-message": "^1.1 || ^2.0",
"psr/http-factory-implementation": "^1.0",
"psr/http-client-implementation": "^1.0",
"symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0",
"netresearch/jsonmapper": "^4.0"
},
"require-dev": {
"php-http/mock-client": "^1.0",
"phpunit/phpunit": "^9.3",
"guzzlehttp/guzzle": "^7.0"
},
"autoload": {
"psr-4": {
"CloudConvert\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"CloudConvert\\Tests\\": "tests/"
}
},
"scripts": {
"tests": [
"vendor/bin/phpunit --verbose"
]
},
"config": {
"allow-plugins": {
"php-http/discovery": false
}
}
}
================================================
FILE: phpunit.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" bootstrap="tests/bootstrap.php">
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Integration">
<directory>tests/Integration</directory>
</testsuite>
</testsuites>
<php>
<env name="CLOUDCONVERT_API_KEY" value="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjI4YmE3OGQyZjc1NWM5ZGE3Yjg1NDRhMWRkMjg2NWM4N2U0YzI5NWI0NzQ0Zjc4ZDNmMzA3OWM2NjU3ZjI0MjVhOTMyYjIxMjU5ZGU2NWQ4In0.eyJhdWQiOiIxIiwianRpIjoiMjhiYTc4ZDJmNzU1YzlkYTdiODU0NGExZGQyODY1Yzg3ZTRjMjk1YjQ3NDRmNzhkM2YzMDc5YzY2NTdmMjQyNWE5MzJiMjEyNTlkZTY1ZDgiLCJpYXQiOjE1NTkwNjc3NzcsIm5iZiI6MTU1OTA2Nzc3NywiZXhwIjo0NzE0NzQxMzc3LCJzdWIiOiIzNzExNjc4NCIsInNjb3BlcyI6WyJ1c2VyLnJlYWQiLCJ1c2VyLndyaXRlIiwidGFzay5yZWFkIiwidGFzay53cml0ZSIsIndlYmhvb2sucmVhZCIsIndlYmhvb2sud3JpdGUiXX0.IkmkfDVGwouCH-ICFAShQMHyFAHK3y90CSoissUVD8h5HFG4GqN5DEw0IFzlPr1auUKp3H1pAvPutdIQtrDMTmUUmGMUb2dRlCAuQdqxa81Q5KAmcKDgOg2YTWOWEGMy3jETTb7W6vyNGsT_3DFMapMdeOw1jdIUTMZqW3QbSCeGXj3PMRnhI7YynaDtmktjzO9IUDHbeT2HRzzMiep97KvVZNjYtZvgM-kbUjE6Mm68_kA8JMuQeor0Yg7896JPV0YM3-MnHf7elKgoCJbfBCDAbvSX_ZYsSI7IGoLLb0mgJVfFcH_HMYAHhJj5cUEJN2Iml-FkODqrRk72bVxyJs9j1GPQBl4ORXuU9yrjUgHrRaZ5YM__LwsUQB3AuB92oyQseCjULn1sWM1PzIXCcyVjKZSpn9LAAGNf9paCF-_G9ok9tZKccRouCiYl9v5XbmuxV8hXYp6fXZxyaAkj_JN2kErVSkxYzVyyZL1e220aFFnbch6nDvLFHgi-WeTQHFQDzuHsM8RKRixV8uD7pk3de4AEYg0EWqZHCr82qY7TGdSQvuAS0QIy3B89OwQW0ROW4k3Yw0XIKgKSYWyKnc7huc7yPQUIDDDAOa5OojXrVY5ZuL_hwQMIOmejcHTKFdAgzAaVnRkC8_FfVh4wHCPBaHjze9hRp5n4O1pnPFI"/>
</php>
</phpunit>
================================================
FILE: src/CloudConvert.php
================================================
<?php
namespace CloudConvert;
use CloudConvert\Handler\SignedUrlBuilder;
use CloudConvert\Handler\WebhookHandler;
use CloudConvert\Hydrator\HydratorInterface;
use CloudConvert\Hydrator\JsonMapperHydrator;
use CloudConvert\Resources\JobsResource;
use CloudConvert\Resources\TasksResource;
use CloudConvert\Resources\UsersResource;
use CloudConvert\Transport\HttpTransport;
use Psr\Http\Client\ClientInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class CloudConvert
{
const VERSION = '3.4.3';
/**
* @var array
*/
protected $options;
/**
* @var HttpTransport
*/
protected $httpTransport;
/**
* @var HydratorInterface
*/
protected $hydrator;
/**
* Api constructor.
*
* @param array $options
*/
public function __construct(array $options = [])
{
$resolver = new OptionsResolver();
$this->configureOptions($resolver);
$this->options = $resolver->resolve($options);
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setRequired('api_key');
$resolver->setAllowedTypes('api_key', 'string');
$resolver->setDefault('sandbox', false);
$resolver->setAllowedTypes('sandbox', 'boolean');
$resolver->setDefined('http_client');
$resolver->setAllowedTypes('http_client', [ClientInterface::class]);
$resolver->setDefined('region');
$resolver->setAllowedTypes('region', 'string');
}
/**
* @return HttpTransport
*/
public function getHttpTransport(): HttpTransport
{
if ($this->httpTransport === null) {
$this->httpTransport = new HttpTransport($this->options);
}
return $this->httpTransport;
}
/**
* @return HydratorInterface
*/
public function getHydrator(): HydratorInterface
{
if ($this->hydrator === null) {
$this->hydrator = new JsonMapperHydrator();
}
return $this->hydrator;
}
/**
* @return UsersResource
*/
public function users(): UsersResource
{
return new UsersResource($this->getHttpTransport(), $this->getHydrator());
}
/**
* @return TasksResource
*/
public function tasks(): TasksResource
{
return new TasksResource($this->getHttpTransport(), $this->getHydrator());
}
/**
* @return JobsResource
*/
public function jobs(): JobsResource
{
return new JobsResource($this->getHttpTransport(), $this->getHydrator());
}
/**
* @return WebhookHandler
*/
public function webhookHandler(): WebhookHandler
{
return new WebhookHandler($this->getHydrator());
}
/**
* @return SignedUrlBuilder
*/
public function signedUrlBuilder(): SignedUrlBuilder
{
return new SignedUrlBuilder();
}
}
================================================
FILE: src/Exceptions/Exception.php
================================================
<?php
namespace CloudConvert\Exceptions;
abstract class Exception extends \RuntimeException
{
}
================================================
FILE: src/Exceptions/HttpClientException.php
================================================
<?php
namespace CloudConvert\Exceptions;
use Psr\Http\Message\ResponseInterface;
class HttpClientException extends Exception
{
/**
* @var ResponseInterface|null
*/
protected $response;
/**
* @var array
*/
protected $responseBody;
/**
* @var int
*/
protected $responseCode;
/**
* @var string|null
*/
protected $errorCode;
/**
* HttpClientException constructor.
*
* @param string $message
* @param int $code
* @param ResponseInterface $response
*/
public function __construct(string $message, int $code, ResponseInterface $response)
{
$this->response = $response;
$this->responseCode = $response->getStatusCode();
$this->responseBody = @json_decode($response->getBody(), true) ?? [];
$this->errorCode = isset($this->responseBody['code']) ? $this->responseBody['code'] : null;
if (isset($this->responseBody['message'])) {
$message = $this->responseBody['message'];
}
if (isset($this->getResponseBody()['errors'])) {
$message = $this->getMessage();
foreach ($this->getResponseBody()['errors'] as $field => $errors) {
$message .= ' ' . $field . ': ' . implode(' ', $errors);
}
}
parent::__construct($message, $code);
}
/**
* @param ResponseInterface $response
*
* @return HttpClientException
*/
public static function badRequest(ResponseInterface $response)
{
return new self('Invalid data.', 400, $response);
}
/**
* @param ResponseInterface $response
*
* @return HttpClientException
*/
public static function unauthorized(ResponseInterface $response)
{
return new self('Unauthorized.', 401, $response);
}
/**
* @param ResponseInterface $response
*
* @return HttpClientException
*/
public static function paymentRequired(ResponseInterface $response)
{
return new self('Credits used up.', 402, $response);
}
/**
* @param ResponseInterface $response
*
* @return HttpClientException
*/
public static function forbidden(ResponseInterface $response)
{
return new self('Forbidden.', 403, $response);
}
/**
* @param ResponseInterface $response
*
* @return HttpClientException
*/
public static function unprocessable(ResponseInterface $response)
{
return new self('Unprocessable.', 422, $response);
}
/**
* @param ResponseInterface $response
*
* @return HttpClientException
*/
public static function notFound(ResponseInterface $response)
{
return new self('The endpoint you have tried to access does not exist. ',
404, $response);
}
/**
* @return ResponseInterface|null
*/
public function getResponse(): ?ResponseInterface
{
return $this->response;
}
/**
* @return array
*/
public function getResponseBody(): array
{
return $this->responseBody;
}
/**
* @return int
*/
public function getResponseCode(): int
{
return $this->responseCode;
}
/**
* @return string|null
*/
public function getErrorCode(): ?string
{
return $this->errorCode;
}
}
================================================
FILE: src/Exceptions/HttpServerException.php
================================================
<?php
namespace CloudConvert\Exceptions;
class HttpServerException extends Exception
{
/**
* @param int $httpStatus
*
* @return HttpServerException
*/
public static function serverError(int $httpStatus = 500)
{
return new self('An unexpected error occurred at CloudConvert\'s servers. Try again later.',
$httpStatus);
}
/**
* @param \Throwable $previous
*
* @return HttpServerException
*/
public static function networkError(\Throwable $previous)
{
return new self('CloudConvert\'s servers are currently unreachable.', 0, $previous);
}
/**
* @param int $code
*
* @return HttpServerException
*/
public static function unknownHttpResponseCode(int $code)
{
return new self(sprintf('Unknown HTTP response code ("%d") received from the API server', $code));
}
}
================================================
FILE: src/Exceptions/SignatureVerificationException.php
================================================
<?php
namespace CloudConvert\Exceptions;
class SignatureVerificationException extends Exception
{
}
================================================
FILE: src/Exceptions/UnexpectedDataException.php
================================================
<?php
namespace CloudConvert\Exceptions;
class UnexpectedDataException extends Exception
{
}
================================================
FILE: src/Handler/SignedUrlBuilder.php
================================================
<?php
namespace CloudConvert\Handler;
use CloudConvert\Models\Job;
class SignedUrlBuilder
{
public function createFromJob(string $base, string $signingSecret, Job $job, ?string $cacheKey = null): string
{
$json = json_encode($job->getPayload());
$base64EncodedJob = rtrim(strtr(base64_encode($json), '+/', '-_'), '=');
$url = $base . '?job=' . $base64EncodedJob;
if($cacheKey) {
$url .= '&cache_key=' . $cacheKey;
}
$signature = hash_hmac('sha256', $url, $signingSecret);
$url .= '&s=' . $signature;
return $url;
}
}
================================================
FILE: src/Handler/WebhookHandler.php
================================================
<?php
namespace CloudConvert\Handler;
use CloudConvert\Exceptions\SignatureVerificationException;
use CloudConvert\Exceptions\UnexpectedDataException;
use CloudConvert\Hydrator\HydratorInterface;
use CloudConvert\Models\WebhookEvent;
use Psr\Http\Message\RequestInterface;
class WebhookHandler
{
/**
* @var HydratorInterface
*/
protected $hydrator;
/**
* WebhookHandler constructor.
*
* @param HydratorInterface $hydrator
*/
public function __construct(HydratorInterface $hydrator)
{
$this->hydrator = $hydrator;
}
/**
* @param string $payload
* @param string $signature
* @param string $signingSecret
*
* @return WebhookEvent
* @throws SignatureVerificationException
* @throws UnexpectedDataException
*/
public function constructEvent(string $payload, string $signature, string $signingSecret): WebhookEvent
{
if(!hash_equals(hash_hmac('sha256', $payload, $signingSecret), $signature)) {
throw new SignatureVerificationException("Invalid webhook signature");
}
return $this->hydrator->hydrateObject(new WebhookEvent(), json_decode($payload));
}
/**
* @param RequestInterface $request
* @param string $signingSecret
*
* @return WebhookEvent
* @throws SignatureVerificationException
* @throws UnexpectedDataException
*/
public function constructEventFromRequest(RequestInterface $request, string $signingSecret): WebhookEvent
{
return $this->constructEvent($request->getBody(), $request->getHeaderLine('CloudConvert-Signature'),
$signingSecret);
}
}
================================================
FILE: src/Hydrator/HydratorInterface.php
================================================
<?php
namespace CloudConvert\Hydrator;
use Psr\Http\Message\ResponseInterface;
interface HydratorInterface
{
public function hydrateObject($object, $data);
public function hydrateArray($array, string $objectClass, $data);
public function createObjectByResponse(string $class, ResponseInterface $response);
public function hydrateObjectByResponse($object, ResponseInterface $response);
public function hydrateArrayByResponse($array, string $objectClass, ResponseInterface $response);
}
================================================
FILE: src/Hydrator/JsonMapperHydrator.php
================================================
<?php
namespace CloudConvert\Hydrator;
use CloudConvert\Exceptions\UnexpectedDataException;
use CloudConvert\Models\ExportUrlTask;
use CloudConvert\Models\ImportUploadTask;
use CloudConvert\Models\Task;
use JsonMapper;
use Psr\Http\Message\ResponseInterface;
class JsonMapperHydrator implements HydratorInterface
{
/**
* @var JsonMapper
*/
protected $jsonMapper;
/**
* JsonMapperHydrator constructor.
*/
public function __construct()
{
$this->jsonMapper = new JsonMapper();
$this->jsonMapper->bIgnoreVisibility = true;
}
/**
* @param $object
* @param $data
*
* @return object
* @throws UnexpectedDataException
*/
public function hydrateObject($object, $data)
{
try {
return $this->jsonMapper->map($data, $object);
} catch (\JsonMapper_Exception $exception) {
throw new UnexpectedDataException($exception);
}
}
/**
* @param $array
* @param $objectClass
* @param $data
*
* @return object
*/
public function hydrateArray($array, string $objectClass, $data)
{
try {
return $this->jsonMapper->map($data, $array, $objectClass);
} catch (\JsonMapper_Exception $exception) {
throw new UnexpectedDataException($exception);
}
}
/**
* @param $object
* @param ResponseInterface $response
*
* @return object
* @throws UnexpectedDataException
*/
public function hydrateObjectByResponse($object, ResponseInterface $response)
{
$body = json_decode($response->getBody());
try {
return $this->jsonMapper->map($body->data, $object);
} catch (\JsonMapper_Exception $exception) {
throw new UnexpectedDataException($exception);
}
}
/**
* @param string $class
* @param ResponseInterface $response
*
* @return object
* @throws UnexpectedDataException
*/
public function createObjectByResponse(string $class, ResponseInterface $response)
{
return $this->hydrateObjectByResponse(new $class, $response);
}
/**
* @param $array
* @param string $objectClass
* @param ResponseInterface $response
*
* @return mixed
* @throws UnexpectedDataException
*/
public function hydrateArrayByResponse($array, string $objectClass, ResponseInterface $response)
{
$body = json_decode($response->getBody());
try {
return $this->jsonMapper->mapArray($body->data, $array, $objectClass);
} catch (\JsonMapper_Exception $exception) {
throw new UnexpectedDataException($exception);
}
}
}
================================================
FILE: src/Models/Collection.php
================================================
<?php
namespace CloudConvert\Models;
abstract class Collection extends \ArrayObject
{
public function filter(callable $callback)
{
$class = get_called_class();
$result = new $class();
foreach ($this as $k => $item) {
if ($callback($item)) {
$result[] = $item;
}
}
return $result;
}
}
================================================
FILE: src/Models/Job.php
================================================
<?php
namespace CloudConvert\Models;
class Job
{
public const STATUS_WATING = 'waiting';
public const STATUS_PROCESSING = 'processing';
public const STATUS_ERROR = 'error';
public const STATUS_FINISHED = 'finished';
/**
* @var string
*/
protected $id;
/**
* @var string|null
*/
protected $tag;
/**
* @var string|null
*/
protected $webhook_url;
/**
* @var \DateTimeImmutable
*/
protected $created_at;
/**
* @var \DateTimeImmutable|null
*/
protected $started_at;
/**
* @var \DateTimeImmutable|null
*/
protected $ended_at;
/**
* @var string|null
*/
protected $status;
/**
* @var TaskCollection[Task]|null
*/
protected $tasks;
/**
* @var object|null
*/
protected $links;
/**
* @return string
*/
public function getId(): string
{
return $this->id;
}
/**
* @return string|null
*/
public function getTag(): ?string
{
return $this->tag;
}
/**
* @param string|null $tag
*
* @return Job
*/
public function setTag(?string $tag): Job
{
$this->tag = $tag;
return $this;
}
/**
* @return string|null
*/
public function getWebhookUrl(): ?string
{
return $this->webhook_url;
}
/**
* @param string|null $webhook_url
*/
public function setWebhookUrl(?string $webhook_url): Job
{
$this->webhook_url = $webhook_url;
return $this;
}
/**
* @return \DateTimeImmutable
*/
public function getCreatedAt(): \DateTimeImmutable
{
return $this->created_at;
}
/**
* @return \DateTimeImmutable|null
*/
public function getStartedAt(): ?\DateTimeImmutable
{
return $this->started_at;
}
/**
* @return \DateTimeImmutable|null
*/
public function getEndedAt(): ?\DateTimeImmutable
{
return $this->ended_at;
}
/**
* @return string|null
*/
public function getStatus(): ?string
{
return $this->status;
}
/**
* @return TaskCollection[Task]|null
*/
public function getTasks(): ?TaskCollection
{
return $this->tasks;
}
/**
* @return object|null
*/
public function getLinks()
{
return $this->links;
}
/**
* @param Task $task
*
* @return Job
*/
public function addTask(Task $task): Job
{
if (!$this->tasks) {
$this->tasks = new TaskCollection();
}
$this->tasks[] = $task;
return $this;
}
/*
* return array
*/
public function getExportUrls()
{
$files = [];
foreach ($this->getTasks()
->status(Task::STATUS_FINISHED)
->operation('export/url') as $exportTask) {
$files = array_merge($files, $exportTask->getResult()->files ?? []);
}
return $files;
}
public function getPayload(): array
{
$tasks = [];
foreach ($this->getTasks() ?? [] as $task) {
$tasks[$task->getName()] = array_merge(
['operation' => $task->getOperation()],
$task->getPayload() ?? []
);
}
return [
'tasks' => $tasks,
'tag' => $this->getTag(),
'webhook_url' => $this->getWebhookUrl()
];
}
}
================================================
FILE: src/Models/JobCollection.php
================================================
<?php
namespace CloudConvert\Models;
class JobCollection extends Collection
{
/**
* Filter job collection by status
*
* @param $status
*
* @return JobCollection
*/
public function whereStatus($status): JobCollection
{
return $this->filter(function (Job $job) use ($status) {
return $job->getStatus() === $status;
});
}
/**
* Filter job collection by status
*
* @deprecated Use whereStatus() instead.
*
* @param $status
*
* @return JobCollection
*/
public function status($status): JobCollection
{
return $this->whereStatus($status);
}
}
================================================
FILE: src/Models/Task.php
================================================
<?php
namespace CloudConvert\Models;
class Task
{
public const STATUS_WATING = 'waiting';
public const STATUS_PROCESSING = 'processing';
public const STATUS_ERROR = 'error';
public const STATUS_FINISHED = 'finished';
/**
* @var string
*/
protected $id;
/**
* @var \DateTimeImmutable|null
*/
protected $created_at;
/**
* @var \DateTimeImmutable|null
*/
protected $started_at;
/**
* @var \DateTimeImmutable|null
*/
protected $ended_at;
/**
* @var string|null
*/
protected $status;
/**
* @var string|null
*/
protected $message;
/**
* @var string|null
*/
protected $code;
/**
* @var object|null
*/
protected $payload;
/**
* @var object|null
*/
protected $result;
/**
* @var TaskCollection[Task]|null
*/
protected $depends_on_tasks;
/**
* @var string|null
*/
protected $job_id;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $operation;
/**
* @var object|null
*/
protected $links;
/**
* Task constructor.
*
* @param string|null $operation
* @param string|null $name
*/
public function __construct(?string $operation = null, ?string $name = null)
{
$this->operation = $operation;
$this->name = $name;
}
/**
* @return string
*/
public function getId(): string
{
return $this->id;
}
/**
* @return \DateTimeImmutable|null
*/
public function getCreatedAt(): ?\DateTimeImmutable
{
return $this->created_at;
}
/**
* @return \DateTimeImmutable|null
*/
public function getStartedAt(): ?\DateTimeImmutable
{
return $this->started_at;
}
/**
* @return \DateTimeImmutable|null
*/
public function getEndedAt(): ?\DateTimeImmutable
{
return $this->ended_at;
}
/**
* @return string
*/
public function getStatus(): ?string
{
return $this->status;
}
/**
* @return string
*/
public function getMessage(): ?string
{
return $this->message;
}
/**
* @return string
*/
public function getCode(): ?string
{
return $this->code;
}
/**
* @return object|null
*/
public function getPayload()
{
return $this->payload;
}
/**
* @return object|null
*/
public function getResult()
{
return $this->result;
}
/**
* @return TaskCollection[Task]|null
*/
public function getDependsOnTasks(): ?TaskCollection
{
return $this->depends_on_tasks;
}
/**
* @return string
*/
public function getJobId(): ?string
{
return $this->job_id;
}
/**
* @return string
*/
public function getOperation(): string
{
return $this->operation;
}
/**
* @return object
*/
public function getLinks()
{
return $this->links;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* Set a task payload parameter
*
* @param string $parameter
* @param $value
*
* @return $this
*/
public function set(string $parameter, $value)
{
if (!$this->payload) {
$this->payload = [];
}
$this->payload[$parameter] = $value;
return $this;
}
}
================================================
FILE: src/Models/TaskCollection.php
================================================
<?php
namespace CloudConvert\Models;
class TaskCollection extends Collection
{
/**
* Filter task collection by status
*
* @param $status
*
* @return TaskCollection
*/
public function whereStatus($status): TaskCollection
{
return $this->filter(function (Task $task) use ($status) {
return $task->getStatus() === $status;
});
}
/**
* Filter task collection by status
*
* @param $status
*
* @return TaskCollection
* @deprecated Use whereStatus() instead
*
*/
public function status($status): TaskCollection
{
return $this->whereStatus($status);
}
/**
* Filter task collection by task name
*
* @param $name
*
* @return TaskCollection
*/
public function whereName($name): TaskCollection
{
return $this->filter(function (Task $task) use ($name) {
return $task->getName() === $name;
});
}
/**
* Filter task collection by task name
*
* @param $name
*
* @return TaskCollection
* @deprecated Use whereName() instead
*
*/
public function name($name): TaskCollection
{
return $this->whereName($name);
}
/**
* Filter task collection by operation
*
* @param $operation
*
* @return TaskCollection
*/
public function whereOperation($operation): TaskCollection
{
return $this->filter(function (Task $task) use ($operation) {
return $task->getOperation() === $operation;
});
}
/**
* Filter task collection by operation
*
* @param $operation
*
* @return TaskCollection
* @deprecated Use whereOperation() instead
*
*/
public function operation($operation): TaskCollection
{
return $this->whereOperation($operation);
}
}
================================================
FILE: src/Models/User.php
================================================
<?php
namespace CloudConvert\Models;
class User
{
/**
* @var int
*/
protected $id;
/**
* @var string
*/
protected $username;
/**
* @var string
*/
protected $email;
/**
* @var int
*/
protected $credits;
/**
* @var \DateTimeImmutable
*/
protected $created_at;
/**
* @var array
*/
protected $links;
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @return string
*/
public function getUsername(): string
{
return $this->username;
}
/**
* @return string
*/
public function getEmail(): string
{
return $this->email;
}
/**
* @return int
*/
public function getCredits(): int
{
return $this->credits;
}
/**
* @return \DateTimeImmutable
*/
public function getCreatedAt(): \DateTimeImmutable
{
return $this->created_at;
}
/**
* @return array
*/
public function getLinks(): array
{
return $this->links;
}
}
================================================
FILE: src/Models/WebhookEvent.php
================================================
<?php
namespace CloudConvert\Models;
class WebhookEvent
{
public const EVENT_JOB_FINISHED = 'job.finished';
public const EVENT_JOB_FAILED = 'job.failed';
public const EVENT_JOB_CREATED = 'job.created';
/**
* @var string
*/
protected $event;
/**
* @var Job|null
*/
protected $job;
/**
* @return string
*/
public function getEvent(): string
{
return $this->event;
}
/**
* @return Job|null
*/
public function getJob(): ?Job
{
return $this->job;
}
}
================================================
FILE: src/Resources/AbstractResource.php
================================================
<?php
namespace CloudConvert\Resources;
use CloudConvert\Hydrator\HydratorInterface;
use CloudConvert\Transport\HttpTransport;
abstract class AbstractResource
{
/**
* @var HttpTransport
*/
protected $httpTransport;
/**
* @var HydratorInterface
*/
protected $hydrator;
/**
* AbstractResource constructor.
*
* @param HttpTransport $httpTransport
* @param HydratorInterface $hydrator
*/
public function __construct(HttpTransport $httpTransport, HydratorInterface $hydrator)
{
$this->httpTransport = $httpTransport;
$this->hydrator = $hydrator;
}
}
================================================
FILE: src/Resources/JobsResource.php
================================================
<?php
namespace CloudConvert\Resources;
use CloudConvert\Models\Job;
use CloudConvert\Models\JobCollection;
class JobsResource extends AbstractResource
{
/**
* @param string $id
*
* @param array|null $query
*
* @return Job
*/
public function get(string $id, $query = null): Job
{
$response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/jobs/' . $id, $query ?? []);
return $this->hydrator->createObjectByResponse(Job::class, $response);
}
/**
* @param array|null $query
*
* @return JobCollection
*/
public function all($query = null): JobCollection
{
$response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/jobs', $query ?? []);
return $this->hydrator->hydrateArrayByResponse(new JobCollection(), Job::class, $response);
}
/**
* @param Job $job
*
* @return Job
*/
public function create(Job $job): Job
{
$response = $this->httpTransport->post($this->httpTransport->getBaseUri() . '/jobs', $job->getPayload());
return $this->hydrator->hydrateObjectByResponse($job, $response);
}
/**
* @param Job $job
*
* @param array|null $query
*
* @return Job
*/
public function refresh(Job $job, $query = null): Job
{
$response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/jobs/' . $job->getId(),
$query ?? []);
return $this->hydrator->hydrateObjectByResponse($job, $response);
}
/**
* @param Job $job
*
* @return Job
*/
public function wait(Job $job): Job
{
$response = $this->httpTransport->get($this->httpTransport->getSyncBaseUri() . '/jobs/' . $job->getId());
return $this->hydrator->hydrateObjectByResponse($job, $response);
}
/**
* @param Job $job
*/
public function delete(Job $job): void
{
$this->httpTransport->delete($this->httpTransport->getBaseUri() . '/jobs/' . $job->getId());
}
}
================================================
FILE: src/Resources/TasksResource.php
================================================
<?php
namespace CloudConvert\Resources;
use CloudConvert\Models\ImportUploadTask;
use CloudConvert\Models\ImportUrlTask;
use CloudConvert\Models\Task;
use CloudConvert\Models\TaskCollection;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
class TasksResource extends AbstractResource
{
/**
* @param string $id
* @param array|null $query
*
* @return Task
*/
public function get(string $id, $query = null): Task
{
$response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/tasks/' . $id, $query ?? []);
return $this->hydrator->createObjectByResponse(Task::class, $response);
}
/**
* @param array|null $query
*
* @return TaskCollection
*/
public function all($query = null): TaskCollection
{
$response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/tasks', $query ?? []);
return $this->hydrator->hydrateArrayByResponse(new TaskCollection(), Task::class, $response);
}
/**
* @param Task $task
*
* @return Task
*/
public function create(Task $task): Task
{
$response = $this->httpTransport->post($this->httpTransport->getBaseUri() . '/' . $task->getOperation(),
array_merge(
['name' => $task->getName()],
$task->getPayload() ?? []
)
);
return $this->hydrator->hydrateObjectByResponse($task, $response);
}
/**
* @param Task $task
* @param array|null $query
*
* @return Task
*/
public function refresh(Task $task, $query = null): Task
{
$response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/tasks/' . $task->getId(),
$query ?? []);
return $this->hydrator->hydrateObjectByResponse($task, $response);
}
/**
* @param Task $task
*
* @return Task
*/
public function wait(Task $task): Task
{
$response = $this->httpTransport->get($this->httpTransport->getSyncBaseUri() . '/tasks/' . $task->getId());
return $this->hydrator->hydrateObjectByResponse($task, $response);
}
/**
* @param Task $task
*/
public function delete(Task $task): void
{
$this->httpTransport->delete($this->httpTransport->getBaseUri() . '/tasks/' . $task->getId());
}
/**
* @param Task $task
* @param string|resource|StreamInterface $file
* @param string|null $fileName
*
* @return ResponseInterface
*/
public function upload(Task $task, $file, ?string $fileName = null): ResponseInterface
{
if ($task->getOperation() !== 'import/upload') {
throw new \BadMethodCallException('The task operation is not import/upload');
}
if ($task->getStatus() !== Task::STATUS_WATING
|| !$task->getResult()
|| !isset($task->getResult()->form)) {
throw new \BadMethodCallException('The task is not ready for uploading');
}
$form = $task->getResult()->form;
return $this->httpTransport->upload($form->url, $file, $fileName, (array)$form->parameters ?? []);
}
}
================================================
FILE: src/Resources/UsersResource.php
================================================
<?php
namespace CloudConvert\Resources;
use CloudConvert\Models\User;
class UsersResource extends AbstractResource
{
/**
* @return User
* @throws \Http\Client\Exception
*/
public function me(): User
{
$response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/users/me');
return $this->hydrator->createObjectByResponse(User::class, $response);
}
}
================================================
FILE: src/Transport/HttpTransport.php
================================================
<?php
namespace CloudConvert\Transport;
use CloudConvert\CloudConvert;
use CloudConvert\Exceptions\HttpClientException;
use CloudConvert\Exceptions\HttpServerException;
use Http\Client\Common\Plugin\HeaderDefaultsPlugin;
use Http\Client\Common\Plugin\RedirectPlugin;
use Http\Client\Common\PluginClient;
use Http\Discovery\Psr17FactoryDiscovery;
use Http\Discovery\Psr18ClientDiscovery;
use Http\Message\MultipartStream\MultipartStreamBuilder;
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UriFactoryInterface;
class HttpTransport
{
protected $options;
protected $httpClient;
/**
* HttpTransport constructor.
*
* @param $options
*/
public function __construct($options)
{
$this->options = $options;
$this->httpClient = $this->createHttpClientInstance();
}
/**
* Creates a new instance of the HTTP client.
*
* @return PluginClient
*/
protected function createHttpClientInstance(): PluginClient
{
$httpClient = $this->options['http_client'] ?? Psr18ClientDiscovery::find();
$httpClientPlugins = [
new HeaderDefaultsPlugin([
'User-Agent' => 'cloudconvert-php/v' . CloudConvert::VERSION . ' (https://github.com/cloudconvert/cloudconvert-php)',
]),
new RedirectPlugin()
];
return new PluginClient($httpClient, $httpClientPlugins);
}
/**
* @return string
*/
public function getBaseUri(): string
{
if ($this->options['sandbox']) {
return 'https://api.sandbox.cloudconvert.com/v2';
}
return isset($this->options['region']) ? 'https://' . $this->options['region'] . '.api.cloudconvert.com/v2' : 'https://api.cloudconvert.com/v2';
}
/**
* @return string
*/
public function getSyncBaseUri(): string
{
if ($this->options['sandbox']) {
return 'https://sync.api.sandbox.cloudconvert.com/v2';
}
return isset($this->options['region']) ? 'https://' . $this->options['region'] . '.sync.api.cloudconvert.com/v2' : 'https://sync.api.cloudconvert.com/v2';
}
/**
* @return PluginClient
*/
public function getHttpClient(): PluginClient
{
return $this->httpClient;
}
/**
* @return RequestFactoryInterface
*/
public function getRequestFactory(): RequestFactoryInterface
{
return $this->options['request_factory'] ?? Psr17FactoryDiscovery::findRequestFactory();
}
/**
* @return UriFactoryInterface
*/
public function getUriFactory(): UriFactoryInterface
{
return $this->options['uri_factory'] ?? Psr17FactoryDiscovery::findUriFactory();
}
/**
* @return StreamFactoryInterface
*/
public function getStreamFactory(): StreamFactoryInterface
{
return $this->options['stream_factory'] ?? Psr17FactoryDiscovery::findStreamFactory();
}
/**
* @param $path
* @param array $query
*
* @return ResponseInterface
* @throws \CloudConvert\Exceptions\Exception
*/
public function get(string $path, array $query = []): ResponseInterface
{
if (count($query) > 0) {
$path .= '?' . http_build_query($query);
}
return $this->sendRequest($this->getRequestFactory()->createRequest('GET', $path, [
'accept-encoding' => 'application/json'
]));
}
/**
* @param string $url
*
* @return StreamInterface
*/
public function download(string $url)
{
return $this->sendRequest($this->getRequestFactory()->createRequest('GET', $url), false)->getBody();
}
/**
* @param array<string, mixed>|string $body
*/
protected function buildBody($body): StreamInterface
{
$stringBody = is_array($body) ? json_encode($body, JSON_THROW_ON_ERROR) : $body;
return $this->getStreamFactory()->createStream($stringBody);
}
/**
* @param $path
* @param $body
*
* @return ResponseInterface
*/
public function post(string $path, array $body): ResponseInterface
{
return $this->sendRequest(
$this->getRequestFactory()->createRequest('POST', $path)
->withHeader('content-type', 'application/json')
->withHeader('accept-encoding', 'application/json')
->withBody($this->buildBody($body))
);
}
/**
* @param $path
* @param $body
*
* @return ResponseInterface
*/
public function put(string $path, array $body): ResponseInterface
{
return $this->sendRequest(
$this->getRequestFactory()->createRequest('POST', $path)
->withHeader('content-type', 'application/json')
->withHeader('accept-encoding', 'application/json')
->withBody($this->buildBody($body))
);
}
/**
* @param $path
*
* @return ResponseInterface
*/
public function delete(string $path): ResponseInterface
{
return $this->sendRequest($this->getRequestFactory()->createRequest('DELETE', $path, [
'accept-encoding' => 'application/json'
]));
}
/**
* @param $path
* @param string|resource|StreamInterface $file
* @param string|null $fileName
* @param array $additionalParameters
*
* @return ResponseInterface
*/
public function upload($path, $file, ?string $fileName = null, array $additionalParameters = []): ResponseInterface
{
$builder = new MultipartStreamBuilder($this->getStreamFactory());
foreach ($additionalParameters as $parameter => $value) {
$builder->addResource($parameter, strval($value));
}
$resourceOptions = [];
if ($fileName !== null) {
$resourceOptions['filename'] = $fileName;
}
$builder->addResource('file', $file, $resourceOptions);
$multipartStream = $builder->build();
$boundary = $builder->getBoundary();
$request = $this->getRequestFactory()->createRequest(
'POST',
$path
)
->withHeader('Content-Type', 'multipart/form-data; boundary="' . $boundary . '"')
->withBody($multipartStream);
return $this->sendRequest($request, false);
}
/**
* @param RequestInterface $request
*
* @param bool $authenticate
*
* @return ResponseInterface
* @throws \Exception
*/
protected function sendRequest(RequestInterface $request, $authenticate = true)
{
try {
if ($authenticate) {
$request = $request->withHeader('Authorization', 'Bearer ' . $this->options['api_key']);
}
$response = $this->getHttpClient()->sendRequest($request);
} catch (\Http\Client\Exception $exception) {
throw HttpServerException::networkError($exception);
}
if (!in_array($response->getStatusCode(), [200, 201, 204])) {
$this->handleErrors($response);
}
return $response;
}
/**
* Throw the correct exception for this error.
*
* @throws \CloudConvert\Exceptions\Exception
*/
protected function handleErrors(ResponseInterface $response)
{
$statusCode = $response->getStatusCode();
switch ($statusCode) {
case 400:
throw HttpClientException::badRequest($response);
case 401:
throw HttpClientException::unauthorized($response);
case 402:
throw HttpClientException::paymentRequired($response);
case 403:
throw HttpClientException::forbidden($response);
case 404:
throw HttpClientException::notFound($response);
case 422:
throw HttpClientException::unprocessable($response);
case 500 <= $statusCode:
throw HttpServerException::serverError($statusCode);
default:
throw HttpServerException::unknownHttpResponseCode($statusCode);
}
}
}
================================================
FILE: tests/Integration/JobTest.php
================================================
<?php
namespace CloudConvert\Tests\Integration;
use CloudConvert\Models\ConvertTask;
use CloudConvert\Models\ExportUrlTask;
use CloudConvert\Models\ImportUploadTask;
use CloudConvert\Models\ImportUrlTask;
use CloudConvert\Models\Job;
use CloudConvert\Models\Task;
class JobTest extends TestCase
{
public function testCreateJob()
{
$job = (new Job())
->setTag('integration-test-create-job')
->addTask(
(new Task('import/url', 'import-it'))
->set('url', 'http://invalid.url')
->set('filename', 'test.file')
)
->addTask(
(new Task('convert', 'convert-it'))
->set('input', ['import-it'])
->set('output_format', 'pdf')
);
$this->cloudConvert->jobs()->create($job);
$this->assertNotNull($job->getId());
$this->assertEquals('integration-test-create-job', $job->getTag());
$this->assertNotNull($job->getCreatedAt());
$this->assertCount(2, $job->getTasks());
$task1 = $job->getTasks()->whereOperation('convert')[0];
$task2 = $job->getTasks()->whereOperation('import/url')[0];
$this->assertEquals('convert-it', $task1->getName());
$this->assertEquals('import-it', $task2->getName());
$this->cloudConvert->jobs()->delete($job);
}
public function testUploadAndDownloadFiles()
{
$job = (new Job())
->setTag('integration-test-upload-download')
->addTask(
new Task('import/upload', 'import-it')
)
->addTask(
(new Task('export/url', 'export-it'))
->set('input', ['import-it'])
);
$this->cloudConvert->jobs()->create($job);
$uploadTask = $job->getTasks()->whereName('import-it')[0];
$this->cloudConvert->tasks()->upload($uploadTask, fopen(__DIR__ . '/files/input.pdf', 'r'));
$this->cloudConvert->jobs()->wait($job);
$this->assertEquals(Job::STATUS_FINISHED, $job->getStatus());
$exportTask = $job->getTasks()->whereStatus(Task::STATUS_FINISHED)->whereName('export-it')[0];
$this->assertNotNull($exportTask->getResult());
$file = $job->getExportUrls()[0];
$this->assertNotEmpty($file->url);
$source = $this->cloudConvert->getHttpTransport()->download($file->url)->detach();
$dest = tmpfile();
$destPath = stream_get_meta_data($dest)['uri'];
stream_copy_to_stream($source, $dest);
$this->assertEquals(filesize($destPath), 172570);
$this->cloudConvert->jobs()->delete($job);
}
}
================================================
FILE: tests/Integration/TaskTest.php
================================================
<?php
namespace CloudConvert\Tests\Integration;
use CloudConvert\Models\ImportUploadTask;
use CloudConvert\Models\ImportUrlTask;
use CloudConvert\Models\Task;
class TaskTest extends TestCase
{
public function testCreateImportUrlTask()
{
$task = (new Task('import/url', 'url-test'))
->set('url', 'http://invalid.url')
->set('filename', 'test.file');
$this->cloudConvert->tasks()->create($task);
$this->assertNotNull($task->getId());
$this->assertNotNull($task->getCreatedAt());
$this->assertEquals('import/url', $task->getOperation());
$this->assertEquals([
'url' => 'http://invalid.url',
'filename' => 'test.file'
], (array)$task->getPayload());
$this->assertEquals(Task::STATUS_WATING, $task->getStatus());
$this->cloudConvert->tasks()->delete($task);
}
public function testUploadFile()
{
$task = (new Task('import/upload', 'upload-test'));
$this->cloudConvert->tasks()->create($task);
$response = $this->cloudConvert->tasks()->upload($task, fopen(__DIR__ . '/files/input.pdf', 'r'));
$this->assertEquals(201, $response->getStatusCode());
$this->cloudConvert->tasks()->wait($task);
$this->assertEquals(Task::STATUS_FINISHED, $task->getStatus());
$this->assertEquals('input.pdf', $task->getResult()->files[0]->filename);
$this->cloudConvert->tasks()->delete($task);
}
}
================================================
FILE: tests/Integration/TestCase.php
================================================
<?php
namespace CloudConvert\Tests\Integration;
use CloudConvert\CloudConvert;
use Http\Mock\Client;
abstract class TestCase extends \PHPUnit\Framework\TestCase
{
/**
* @var CloudConvert
*/
protected $cloudConvert;
public function setUp(): void
{
$this->cloudConvert = new CloudConvert([
'sandbox' => true,
'api_key' => getenv('CLOUDCONVERT_API_KEY')
]);
parent::setUp();
}
}
================================================
FILE: tests/Integration/UserTest.php
================================================
<?php
namespace CloudConvert\Tests\Integration;
use CloudConvert\Models\User;
class UserTest extends TestCase
{
public function testMe()
{
$user = $this->cloudConvert->users()->me();
$this->assertInstanceOf(User::class, $user);
$this->assertNotNull($user->getId());
$this->assertNotNull($user->getUsername());
$this->assertNotNull($user->getEmail());
$this->assertNotNull($user->getCredits());
}
}
================================================
FILE: tests/Unit/ExceptionsTest.php
================================================
<?php
namespace CloudConvert\Tests\Unit;
use CloudConvert\Exceptions\HttpClientException;
use CloudConvert\Exceptions\HttpServerException;
use CloudConvert\Models\Job;
use GuzzleHttp\Psr7\Response;
use Http\Message\RequestMatcher\RequestMatcher;
class ExceptionsTest extends TestCase
{
public function testUnauthorized()
{
$response = new Response(401, [
'Content-Type' => 'application/json'
], '{
"message": "Message!"
}');
$this->getMockClient()->on(new RequestMatcher('/v2/users/me', null, 'GET'), $response);
$this->expectException(HttpClientException::class);
$this->expectExceptionMessageRegExp("/Message!/");
$this->cloudConvert->users()->me();
}
public function testValidationErrors()
{
$response = new Response(400, [
'Content-Type' => 'application/json'
], file_get_contents(__DIR__ . '/responses/error400.json'));
$this->getMockClient()->on(new RequestMatcher('/v2/jobs', null, 'POST'), $response);
$this->expectException(HttpClientException::class);
$this->expectExceptionMessageRegExp("/The given data was invalid/");
$this->expectExceptionMessageRegExp("/Cannot change status: task already completed/");
$this->cloudConvert->jobs()->create(new Job());
}
public function test503()
{
$response = new Response(503, [
'Content-Type' => 'text/html'
], 'error!');
$this->getMockClient()->on(new RequestMatcher('/v2/users/me', null, 'GET'), $response);
$this->expectException(HttpServerException::class);
$this->cloudConvert->users()->me();
}
public function testsCreditsExceeded()
{
$response = new Response(402, [
'Content-Type' => 'application/json'
], file_get_contents(__DIR__ . '/responses/error402.json'));
$this->getMockClient()->on(new RequestMatcher('/v2/jobs', null, 'POST'), $response);
try {
$this->cloudConvert->jobs()->create(new Job());
} catch (HttpClientException $exception) {
$this->assertEquals('CREDITS_EXCEEDED', $exception->getErrorCode());
}
}
}
================================================
FILE: tests/Unit/JobResourceTest.php
================================================
<?php
namespace CloudConvert\Tests\Unit;
use CloudConvert\Models\Job;
use CloudConvert\Models\Task;
use GuzzleHttp\Psr7\Response;
use Http\Message\RequestMatcher\CallbackRequestMatcher;
use Http\Message\RequestMatcher\RequestMatcher;
use Psr\Http\Message\RequestInterface;
class JobResourceTest extends TestCase
{
public function testGet()
{
$response = new Response(200, [
'Content-Type' => 'application/json'
], file_get_contents(__DIR__ . '/responses/job.json'));
$this->getMockClient()->on(new RequestMatcher('/v2/jobs/cd82535b-0614-4b23-bbba-b24ab0e892f7', null, 'GET'),
$response);
$job = $this->cloudConvert->jobs()->get('cd82535b-0614-4b23-bbba-b24ab0e892f7');
$this->assertInstanceOf(Job::class, $job);
$this->assertEquals('cd82535b-0614-4b23-bbba-b24ab0e892f7', $job->getId());
$this->assertEquals('test-1234', $job->getTag());
$this->assertEquals(Job::STATUS_ERROR, $job->getStatus());
$this->assertInstanceOf(\DateTimeImmutable::class, $job->getCreatedAt());
$this->assertInstanceOf(\DateTimeImmutable::class, $job->getEndedAt());
$this->assertInstanceOf(\DateTimeImmutable::class, $job->getStartedAt());
$this->assertCount(3, $job->getTasks());
$this->assertInstanceOf(Task::class, $job->getTasks()[0]);
$this->assertEquals('4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b', $job->getTasks()[0]->getId());
}
public function testAll()
{
$response = new Response(200, [
'Content-Type' => 'application/json'
], file_get_contents(__DIR__ . '/responses/jobs.json'));
$this->getMockClient()->on(new RequestMatcher('/v2/jobs', null, 'GET'),
$response);
$jobs = $this->cloudConvert->jobs()->all();
$this->assertCount(1, $jobs);
$this->assertInstanceOf(Job::class, $jobs[0]);
$this->assertEquals('bd7d06b4-60fb-472b-b3a3-9034b273df07', $jobs[0]->getId());
}
public function testCreateJob()
{
$job = (new Job())
->addTask(
(new Task('import/url', 'import-it'))
->set('url', 'http://invalid.url')
->set('filename', 'test.file')
)
->addTask(
(new Task('convert', 'convert-it'))
->set('input', 'import-it')
->set('output_format', 'pdf')
);
$response = new Response(201, [
'Content-Type' => 'application/json'
], file_get_contents(__DIR__ . '/responses/job_created.json'));
$this->getMockClient()->on(
new CallbackRequestMatcher(function (RequestInterface $request) {
if (strpos($request->getUri(), '/jobs') === false) {
return false;
}
$body = json_decode($request->getBody(), true);
if (!isset($body['tasks']['import-it'])
|| !isset($body['tasks']['convert-it'])
|| $body['tasks']['import-it']['operation'] !== 'import/url'
|| $body['tasks']['import-it']['url'] !== 'http://invalid.url'
) {
return false;
}
return true;
}), $response);
$this->cloudConvert->jobs()->create($job);
$this->assertNotNull($job->getId());
$this->assertNotNull($job->getCreatedAt());
$this->assertCount(2, $job->getTasks());
$task1 = $job->getTasks()[0];
$task2 = $job->getTasks()[1];
$this->assertEquals('import/url', $task1->getOperation());
$this->assertEquals('import-it', $task1->getName());
$this->assertEquals([
'operation' => 'import/url',
'url' => 'http://invalid.url',
'filename' => 'test.file'
], (array)$task1->getPayload());
$this->assertEquals('convert', $task2->getOperation());
$this->assertEquals('convert-it', $task2->getName());
$this->assertEquals([
'operation' => 'convert',
'input' => ['import-it'],
'output_format' => 'pdf',
], (array)$task2->getPayload());
}
public function testGetExportUrls()
{
$response = new Response(200, [
'Content-Type' => 'application/json'
], file_get_contents(__DIR__ . '/responses/job_export_urls.json'));
$this->getMockClient()->on(new RequestMatcher('/v2/jobs/cd82535b-0614-4b23-bbba-b24ab0e892f7', null, 'GET'),
$response);
$job = $this->cloudConvert->jobs()->get('cd82535b-0614-4b23-bbba-b24ab0e892f7');
$this->assertInstanceOf(Job::class, $job);
$urls = $job->getExportUrls();
$this->assertCount(2, $urls);
$this->assertEquals('file.mp4', $urls[0]->filename);
$this->assertEquals('https://storage.cloudconvert.com/file.mp4', $urls[0]->url);
$this->assertEquals('file2.mp4', $urls[1]->filename);
}
}
================================================
FILE: tests/Unit/SignedUrlBuilderTest.php
================================================
<?php
namespace CloudConvert\Tests\Unit;
use CloudConvert\Models\Job;
use CloudConvert\Models\Task;
class SignedUrlBuilderTest extends TestCase
{
public function testCreateSignedUrl() {
$job = (new Job())
->addTask(
(new Task('import/url', 'import-it'))
->set('url', 'https://some.url')
->set('filename', 'test.png')
)
->addTask(
(new Task('export/url', 'export-it'))
->set('input', 'import-it')
->set('inline', true)
);
$url = $this->cloudConvert->signedUrlBuilder()->createFromJob('https://s.cloudconvert.com/b3d85428-584e-4639-bc11-76b7dee9c109', 'NT8dpJkttEyfSk3qlRgUJtvTkx64vhyX', $job, 'mykey');
$this->assertStringStartsWith('https://s.cloudconvert.com/', $url);
$this->assertStringContainsString('?job=', $url);
$this->assertStringContainsString('&cache_key=mykey', $url);
$this->assertStringContainsString('&s=fb2760b572f652316d2ca218cec980024057ff108a472425c9fcc6136709cbe8', $url);
}
}
================================================
FILE: tests/Unit/TaskCollectionTest.php
================================================
<?php
namespace CloudConvert\Tests\Unit;
use CloudConvert\Models\Task;
use CloudConvert\Models\TaskCollection;
class TaskCollectionTest extends TestCase
{
public function testFilterByStatus() {
$task1 = new Task('import/url', 'test');
$reflection = new \ReflectionClass($task1);
$property = $reflection->getProperty('status');
$property->setAccessible(true);
$property->setValue($task1, Task::STATUS_FINISHED);
$task2 = new Task('import/url', 'test');
$reflection = new \ReflectionClass($task1);
$property = $reflection->getProperty('status');
$property->setAccessible(true);
$property->setValue($task2, Task::STATUS_ERROR);
$task3 = new Task('import/url', 'test');
$reflection = new \ReflectionClass($task1);
$property = $reflection->getProperty('status');
$property->setAccessible(true);
$property->setValue($task3, Task::STATUS_WATING);
$collection = new TaskCollection([$task1, $task2, $task3]);
$filtered = $collection->status(Task::STATUS_ERROR);
$this->assertCount(1, $filtered);
$this->assertCount(3, $collection); // original collection not modified
$this->assertEquals($filtered[0], $task2);
}
}
================================================
FILE: tests/Unit/TaskResourceTest.php
================================================
<?php
namespace CloudConvert\Tests\Unit;
use CloudConvert\Models\ExportUrlTask;
use CloudConvert\Models\ImportUploadTask;
use CloudConvert\Models\ImportUrlTask;
use CloudConvert\Models\Task;
use GuzzleHttp\Psr7\Response;
use Http\Message\RequestMatcher\CallbackRequestMatcher;
use Http\Message\RequestMatcher\RequestMatcher;
use Psr\Http\Message\RequestInterface;
class TaskResourceTest extends TestCase
{
public function testGet()
{
$response = new Response(200, [
'Content-Type' => 'application/json'
], file_get_contents(__DIR__ . '/responses/task.json'));
$this->getMockClient()->on(new RequestMatcher('/v2/tasks/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b', null, 'GET'),
$response);
$task = $this->cloudConvert->tasks()->get('4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b');
$this->assertInstanceOf(Task::class, $task);
$this->assertEquals('4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b', $task->getId());
$this->assertEquals('export-1', $task->getName());
$this->assertEquals(Task::STATUS_ERROR, $task->getStatus());
$this->assertEquals('INPUT_TASK_FAILED', $task->getCode());
$this->assertEquals('Input task has failed', $task->getMessage());
$this->assertInstanceOf(\DateTimeImmutable::class, $task->getCreatedAt());
$this->assertInstanceOf(\DateTimeImmutable::class, $task->getEndedAt());
$this->assertNull($task->getStartedAt());
$this->assertCount(1, $task->getDependsOnTasks());
$this->assertInstanceOf(Task::class, $task->getDependsOnTasks()[0]);
$this->assertEquals('6df0920a-7042-4e87-be52-f38a0a29a67e', $task->getDependsOnTasks()[0]->getId());
}
public function testAll()
{
$response = new Response(200, [
'Content-Type' => 'application/json'
], file_get_contents(__DIR__ . '/responses/tasks.json'));
$this->getMockClient()->on(new RequestMatcher('/v2/tasks', null, 'GET'),
$response);
$tasks = $this->cloudConvert->tasks()->all();
$this->assertCount(1, $tasks);
$this->assertInstanceOf(Task::class, $tasks[0]);
$this->assertEquals('4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b', $tasks[0]->getId());
}
public function testCreateImportUrlTask()
{
$task = (new Task('import/url', 'test'))
->set('url', 'http://invalid.url')
->set('filename', 'test.file');
$response = new Response(201, [
'Content-Type' => 'application/json'
], file_get_contents(__DIR__ . '/responses/task_created.json'));
$this->getMockClient()->on(
new CallbackRequestMatcher(function (RequestInterface $request) {
if (strpos($request->getUri(), '/import/url') === false) {
return false;
}
$body = json_decode($request->getBody(), true);
if ($body['name'] !== 'test'
|| $body['url'] !== 'http://invalid.url'
|| $body['filename'] !== 'test.file') {
return false;
}
return true;
}), $response);
$this->cloudConvert->tasks()->create($task);
$this->assertNotNull($task->getId());
$this->assertNotNull($task->getCreatedAt());
$this->assertEquals([
'name' => 'test',
'url' => 'http://invalid.url',
'filename' => 'test.file'
], (array)$task->getPayload());
$this->assertEquals(Task::STATUS_WATING, $task->getStatus());
}
public function testRefresh()
{
$task = new Task('import/url', 'test');
$reflection = new \ReflectionClass($task);
$property = $reflection->getProperty('id');
$property->setAccessible(true);
$property->setValue($task, '4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b');
$this->assertNull($task->getCreatedAt());
$response = new Response(200, [
'Content-Type' => 'application/json'
], file_get_contents(__DIR__ . '/responses/task.json'));
$this->getMockClient()->on(new RequestMatcher('/v2/tasks/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b', null, 'GET'),
$response);
$this->cloudConvert->tasks()->refresh($task);
$this->assertNotNull($task->getCreatedAt());
$this->assertEquals(Task::STATUS_ERROR, $task->getStatus());
$this->assertEquals('Input task has failed', $task->getMessage());
}
public function testUploadFile()
{
$task = new Task('import/upload', 'upload-test');
$response = new Response(200, [
'Content-Type' => 'application/json'
], file_get_contents(__DIR__ . '/responses/upload_task_created.json'));
$this->getMockClient()->on(new RequestMatcher('/v2/import/upload', null, 'POST'),
$response);
$this->cloudConvert->tasks()->create($task);
$this->getMockClient()->on(
new CallbackRequestMatcher(function (RequestInterface $request) use ($task) {
if ((string)$request->getUri() !== $task->getResult()->form->url) {
return false;
}
$body = (string)$request->getBody();
foreach ((array)$task->getResult()->form->parameters as $parameter => $value) {
$this->assertStringContainsString('name="' . $parameter . '"', $body);
$this->assertStringContainsString((string)$value, $body);
}
return true;
}), new Response(201, [], ""));
$response = $this->cloudConvert->tasks()->upload($task, "filecontent");
$this->assertEquals(201, $response->getStatusCode());
}
}
================================================
FILE: tests/Unit/TestCase.php
================================================
<?php
namespace CloudConvert\Tests\Unit;
use CloudConvert\CloudConvert;
use GuzzleHttp\Psr7\Response;
use Http\Mock\Client;
abstract class TestCase extends \PHPUnit\Framework\TestCase
{
/**
* @var CloudConvert
*/
protected $cloudConvert;
/**
* @var Client
*/
protected $mockClient;
public function setUp(): void
{
$this->cloudConvert = new CloudConvert([
'api_key' => 'test_api_key',
'http_client' => $this->getMockClient()
]);
parent::setUp();
}
protected function getMockClient(): Client
{
if ($this->mockClient === null) {
$this->mockClient = new Client();
$this->mockClient->setDefaultResponse(new Response(404, [], ''));
}
return $this->mockClient;
}
/**
* The method is here to provice BC compatibility with PHPUnit >= 9 where this method was removed.
*
* @param string $regex
*/
public function expectExceptionMessageRegExp(string $regex): void
{
if (!method_exists($this, 'expectExceptionMessageMatches')) {
parent::expectExceptionMessageRegExp($regex);
return;
}
$this->expectExceptionMessageMatches($regex);
}
}
================================================
FILE: tests/Unit/UsersResourceTest.php
================================================
<?php
namespace CloudConvert\Tests\Unit;
use CloudConvert\Models\User;
use GuzzleHttp\Psr7\Response;
use Http\Message\RequestMatcher\RequestMatcher;
class UsersResourceTest extends TestCase
{
public function testMe()
{
$response = new Response(200, [
'Content-Type' => 'application/json'
], file_get_contents(__DIR__ . '/responses/user.json'));
$this->getMockClient()->on(new RequestMatcher('/v2/users/me', null, 'GET'), $response);
$user = $this->cloudConvert->users()->me();
$this->assertInstanceOf(User::class, $user);
$this->assertEquals(1, $user->getId());
$this->assertEquals('Username', $user->getUsername());
$this->assertEquals('me@example.com', $user->getEmail());
$this->assertEquals(4434, $user->getCredits());
}
}
================================================
FILE: tests/Unit/WebhookHandlerTest.php
================================================
<?php
namespace CloudConvert\Tests\Unit;
use CloudConvert\Exceptions\SignatureVerificationException;
use CloudConvert\Models\ExportUrlTask;
use CloudConvert\Models\Job;
use CloudConvert\Models\Task;
use CloudConvert\Models\WebhookEvent;
use GuzzleHttp\Psr7\Request;
class WebhookHandlerTest extends TestCase
{
public function testInvalidSignature()
{
$request = new Request('POST', '/webhook', [
'CloudConvert-Signature' => 'invalid'
], file_get_contents(__DIR__ . '/requests/webhook_job_finished_payload.json'));
$this->expectException(SignatureVerificationException::class);
$this->cloudConvert->webhookHandler()->constructEventFromRequest($request, 'secret');
}
public function testConstructsWebhookEvent()
{
$request = new Request('POST', '/webhook', [
'CloudConvert-Signature' => '576b653f726c85265a389532988f483b5c7d7d5f40cede5f5ddf9c3f02934f35'
], file_get_contents(__DIR__ . '/requests/webhook_job_finished_payload.json'));
$webhookEvent = $this->cloudConvert->webhookHandler()->constructEventFromRequest($request, 'secret');
$this->assertInstanceOf(WebhookEvent::class, $webhookEvent);
$this->assertEquals(WebhookEvent::EVENT_JOB_FINISHED, $webhookEvent->getEvent());
$this->assertInstanceOf(Job::class, $webhookEvent->getJob());
$this->assertCount(3, $webhookEvent->getJob()->getTasks());
$this->assertInstanceOf(Task::class, $webhookEvent
->getJob()
->getTasks()
->status(Task::STATUS_FINISHED)
->name('export-it')[0]);
}
}
================================================
FILE: tests/Unit/requests/webhook_job_finished_payload.json
================================================
{
"event": "job.finished",
"job": {
"id": "c677ccf7-8876-4f48-bb96-0ab8e0d88cd7",
"status": "finished",
"created_at": "2019-06-01T00:35:33+00:00",
"started_at": "2019-06-01T00:35:33+00:00",
"ended_at": "2019-06-01T00:35:33+00:00",
"tasks": [
{
"id": "22b3d686-126b-4fe2-8238-a9781cd023d9",
"name": "import-it",
"job_id": "c677ccf7-8876-4f48-bb96-0ab8e0d88cd7",
"status": "finished",
"code": null,
"message": null,
"percent": 100,
"operation": "import\/url",
"payload": {
"operation": "import\/url",
"url": "http:\/\/invalid.url",
"filename": "test.file"
},
"result": null,
"created_at": "2019-06-01T00:35:33+00:00",
"started_at": "2019-06-01T00:35:33+00:00",
"ended_at": "2019-06-01T00:35:33+00:00",
"retry_of_task_id": null,
"copy_of_task_id": null,
"host_name": "jena",
"storage": null,
"depends_on_task_ids": [],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/22b3d686-126b-4fe2-8238-a9781cd023d9"
}
},
{
"id": "15281118-acc3-441f-970d-39a49457d9e5",
"name": "convert-it",
"job_id": "c677ccf7-8876-4f48-bb96-0ab8e0d88cd7",
"status": "finished",
"code": null,
"message": null,
"percent": 100,
"operation": "convert",
"engine": null,
"engine_version": null,
"payload": {
"operation": "convert",
"input": [
"import-it"
],
"output_format": "pdf"
},
"result": null,
"created_at": "2019-06-01T00:35:33+00:00",
"started_at": "2019-06-01T00:35:33+00:00",
"ended_at": "2019-06-01T00:35:33+00:00",
"retry_of_task_id": null,
"copy_of_task_id": null,
"host_name": null,
"storage": null,
"depends_on_task_ids": [
"22b3d686-126b-4fe2-8238-a9781cd023d9"
],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/15281118-acc3-441f-970d-39a49457d9e5"
}
},
{
"id": "72dc4f5f-7ef0-4f46-9811-29589a541400",
"name": "export-it",
"job_id": "c677ccf7-8876-4f48-bb96-0ab8e0d88cd7",
"status": "finished",
"code": null,
"message": null,
"percent": 100,
"operation": "export/url",
"engine": null,
"engine_version": null,
"payload": {
"operation": "export/url",
"input": [
"convert-it"
]
},
"result": {
"files": [
{
"filename": "file.pdf",
"url": "https://storage.cloudconvert.com/eed87242-577e-4e3e-8178-9edbe51975dd/file.pdf?temp_url_sig=79c2db4d884926bbcc5476d01b4922a19137aee9&temp_url_expires=1545962104"
}
]
},
"created_at": "2019-06-01T00:35:33+00:00",
"started_at": "2019-06-01T00:35:33+00:00",
"ended_at": "2019-06-01T00:35:33+00:00",
"retry_of_task_id": null,
"copy_of_task_id": null,
"host_name": null,
"storage": null,
"depends_on_task_ids": [
"15281118-acc3-441f-970d-39a49457d9e5"
],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/72dc4f5f-7ef0-4f46-9811-29589a541400"
}
}
],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/jobs\/c677ccf7-8876-4f48-bb96-0ab8e0d88cd7"
}
}
}
================================================
FILE: tests/Unit/responses/error400.json
================================================
{
"message": "The given data was invalid.",
"code": "INVALID_DATA",
"errors": {
"status": [
"Cannot change status: task already completed."
]
}
}
================================================
FILE: tests/Unit/responses/error402.json
================================================
{
"message": "Your account has run out of conversion minutes",
"code": "CREDITS_EXCEEDED"
}
================================================
FILE: tests/Unit/responses/job.json
================================================
{
"data": {
"id": "cd82535b-0614-4b23-bbba-b24ab0e892f7",
"tag": "test-1234",
"status": "error",
"created_at": "2019-05-30T10:53:01+00:00",
"started_at": "2019-05-30T10:53:05+00:00",
"ended_at": "2019-05-30T10:53:23+00:00",
"tasks": [
{
"id": "4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b",
"name": "export-1",
"job_id": "cd82535b-0614-4b23-bbba-b24ab0e892f7",
"status": "error",
"code": "INPUT_TASK_FAILED",
"message": "Input task has failed",
"percent": 100,
"operation": "export\/url",
"payload": {
"operation": "export\/url",
"input": [
"task-1"
]
},
"result": null,
"created_at": "2019-05-30T10:53:01+00:00",
"started_at": null,
"ended_at": "2019-05-30T10:53:23+00:00",
"retry_of_task_id": null,
"copy_of_task_id": null,
"host_name": null,
"storage": null,
"depends_on_task_ids": [
"6df0920a-7042-4e87-be52-f38a0a29a67e"
],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b"
}
},
{
"id": "6df0920a-7042-4e87-be52-f38a0a29a67e",
"name": "task-1",
"job_id": "cd82535b-0614-4b23-bbba-b24ab0e892f7",
"status": "error",
"code": "INPUT_TASK_FAILED",
"message": "Input task has failed",
"percent": 100,
"operation": "convert",
"engine": null,
"engine_version": null,
"payload": {
"operation": "convert",
"input_format": "mp4",
"output_format": "mp4",
"engine": "ffmpeg",
"input": [
"import-1"
],
"video_codec": "x264",
"crf": 0,
"preset": "veryslow",
"profile": "baseline",
"width": 1920,
"height": 1080,
"audio_codec": "copy",
"audio_bitrate": 320,
"engine_version": "4.1.1"
},
"result": null,
"created_at": "2019-05-30T10:53:01+00:00",
"started_at": null,
"ended_at": "2019-05-30T10:53:23+00:00",
"retry_of_task_id": null,
"copy_of_task_id": null,
"host_name": null,
"storage": null,
"depends_on_task_ids": [
"22be63c2-0e3f-4909-9c2a-2261dc540aba"
],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/6df0920a-7042-4e87-be52-f38a0a29a67e"
}
},
{
"id": "22be63c2-0e3f-4909-9c2a-2261dc540aba",
"name": "import-1",
"job_id": "cd82535b-0614-4b23-bbba-b24ab0e892f7",
"status": "error",
"code": "SANDBOX_FILE_NOT_ALLOWED",
"message": "The file file.mp4 is not whitelisted for sandbox use",
"percent": 100,
"operation": "import\/url",
"payload": {
"operation": "import\/url",
"url": "https:\/\/some.url\/file.mp4",
"filename": "file.mp4"
},
"result": {
"files": [
{
"filename": "file.mp4",
"md5": "c03538f8edd84537190c264109fa2284"
}
]
},
"created_at": "2019-05-30T10:53:01+00:00",
"started_at": "2019-05-30T10:53:05+00:00",
"ended_at": "2019-05-30T10:53:23+00:00",
"retry_of_task_id": null,
"copy_of_task_id": null,
"host_name": "leta",
"storage": null,
"depends_on_task_ids": [],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/22be63c2-0e3f-4909-9c2a-2261dc540aba"
}
}
],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/jobs\/cd82535b-0614-4b23-bbba-b24ab0e892f7"
}
}
}
================================================
FILE: tests/Unit/responses/job_created.json
================================================
{
"data": {
"id": "c677ccf7-8876-4f48-bb96-0ab8e0d88cd7",
"status": "waiting",
"created_at": "2019-06-01T00:35:33+00:00",
"started_at": "2019-06-01T00:35:33+00:00",
"ended_at": null,
"tasks": [
{
"id": "22b3d686-126b-4fe2-8238-a9781cd023d9",
"name": "import-it",
"job_id": "c677ccf7-8876-4f48-bb96-0ab8e0d88cd7",
"status": "waiting",
"code": null,
"message": null,
"percent": 100,
"operation": "import\/url",
"payload": {
"operation": "import\/url",
"url": "http:\/\/invalid.url",
"filename": "test.file"
},
"result": null,
"created_at": "2019-06-01T00:35:33+00:00",
"started_at": "2019-06-01T00:35:33+00:00",
"ended_at": null,
"retry_of_task_id": null,
"copy_of_task_id": null,
"host_name": "jena",
"storage": null,
"depends_on_task_ids": [],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/22b3d686-126b-4fe2-8238-a9781cd023d9"
}
},
{
"id": "15281118-acc3-441f-970d-39a49457d9e5",
"name": "convert-it",
"job_id": "c677ccf7-8876-4f48-bb96-0ab8e0d88cd7",
"status": "waiting",
"code": null,
"message": null,
"percent": 100,
"operation": "convert",
"engine": null,
"engine_version": null,
"payload": {
"operation": "convert",
"input": [
"import-it"
],
"output_format": "pdf"
},
"result": null,
"created_at": "2019-06-01T00:35:33+00:00",
"started_at": null,
"ended_at": null,
"retry_of_task_id": null,
"copy_of_task_id": null,
"host_name": null,
"storage": null,
"depends_on_task_ids": [
"22b3d686-126b-4fe2-8238-a9781cd023d9"
],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/15281118-acc3-441f-970d-39a49457d9e5"
}
}
],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/jobs\/c677ccf7-8876-4f48-bb96-0ab8e0d88cd7"
}
}
}
================================================
FILE: tests/Unit/responses/job_export_urls.json
================================================
{
"data": {
"id": "cd82535b-0614-4b23-bbba-b24ab0e892f7",
"tag": "test-1234",
"status": "error",
"created_at": "2019-05-30T10:53:01+00:00",
"started_at": "2019-05-30T10:53:05+00:00",
"ended_at": "2019-05-30T10:53:23+00:00",
"tasks": [
{
"id": "4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b",
"name": "export-1",
"job_id": "cd82535b-0614-4b23-bbba-b24ab0e892f7",
"status": "finished",
"percent": 100,
"operation": "export\/url",
"created_at": "2019-05-30T10:53:01+00:00",
"started_at": null,
"ended_at": "2019-05-30T10:53:23+00:00",
"retry_of_task_id": null,
"copy_of_task_id": null,
"host_name": null,
"storage": null,
"result": {
"files": [
{
"filename": "file.mp4",
"url": "https://storage.cloudconvert.com/file.mp4"
}
]
},
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b"
}
},
{
"id": "4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b",
"name": "export-2",
"job_id": "cd82535b-0614-4b23-bbba-b24ab0e892f7",
"status": "finished",
"percent": 100,
"operation": "export\/url",
"created_at": "2019-05-30T10:53:01+00:00",
"started_at": null,
"ended_at": "2019-05-30T10:53:23+00:00",
"retry_of_task_id": null,
"copy_of_task_id": null,
"host_name": null,
"storage": null,
"result": {
"files": [
{
"filename": "file2.mp4",
"url": "https://storage.cloudconvert.com/file2.mp4"
}
]
},
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b"
}
}
],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/jobs\/cd82535b-0614-4b23-bbba-b24ab0e892f7"
}
}
}
================================================
FILE: tests/Unit/responses/jobs.json
================================================
{
"data": [
{
"id": "bd7d06b4-60fb-472b-b3a3-9034b273df07",
"status": "waiting",
"created_at": "2019-05-13T19:52:21+00:00",
"started_at": null,
"ended_at": null,
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/jobs\/bd7d06b4-60fb-472b-b3a3-9034b273df07"
}
}
],
"links": {
"first": "https:\/\/api.cloudconvert.com\/v2\/jobs?page=1",
"last": null,
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"path": "https:\/\/api.cloudconvert.com\/v2\/jobs",
"per_page": 100,
"to": 1
}
}
================================================
FILE: tests/Unit/responses/task.json
================================================
{
"data": {
"id": "4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b",
"name": "export-1",
"job_id": "cd82535b-0614-4b23-bbba-b24ab0e892f7",
"status": "error",
"code": "INPUT_TASK_FAILED",
"message": "Input task has failed",
"percent": 100,
"operation": "export\/url",
"payload": {
"operation": "export\/url",
"input": [
"task-1"
]
},
"result": null,
"created_at": "2019-05-30T10:53:01+00:00",
"started_at": null,
"ended_at": "2019-05-30T10:53:23+00:00",
"retry_of_task_id": null,
"copy_of_task_id": null,
"retries": [],
"host_name": null,
"storage": null,
"depends_on_tasks": [
{
"id": "6df0920a-7042-4e87-be52-f38a0a29a67e",
"name": "task-1",
"job_id": "cd82535b-0614-4b23-bbba-b24ab0e892f7",
"status": "error",
"code": "INPUT_TASK_FAILED",
"message": "Input task has failed",
"percent": 100,
"operation": "convert",
"engine": null,
"engine_version": null,
"payload": {
"operation": "convert",
"input_format": "mp4",
"output_format": "mp4",
"engine": "ffmpeg",
"input": [
"import-1"
],
"video_codec": "x264",
"crf": 0,
"preset": "veryslow",
"profile": "baseline",
"width": 1920,
"height": 1080,
"audio_codec": "copy",
"audio_bitrate": 320,
"engine_version": "4.1.1"
},
"result": null,
"created_at": "2019-05-30T10:53:01+00:00",
"started_at": null,
"ended_at": "2019-05-30T10:53:23+00:00",
"retry_of_task_id": null,
"copy_of_task_id": null,
"host_name": null,
"storage": null,
"depends_on_task_ids": [
"22be63c2-0e3f-4909-9c2a-2261dc540aba"
],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/6df0920a-7042-4e87-be52-f38a0a29a67e"
}
}
],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b"
}
}
}
================================================
FILE: tests/Unit/responses/task_created.json
================================================
{
"data": {
"id": "2f901289-c9fe-4c89-9c4b-98be526bdfbf",
"job_id": null,
"status": "waiting",
"code": null,
"message": null,
"percent": 100,
"operation": "import\/url",
"payload": {
"name": "test",
"url": "http:\/\/invalid.url",
"filename": "test.file"
},
"result": null,
"created_at": "2019-05-31T23:52:39+00:00",
"started_at": "2019-05-31T23:52:39+00:00",
"ended_at": "2019-05-31T23:53:26+00:00",
"retry_of_task_id": null,
"copy_of_task_id": null,
"retries": [],
"depends_on_tasks": [],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/2f901289-c9fe-4c89-9c4b-98be526bdfbf"
}
}
}
================================================
FILE: tests/Unit/responses/tasks.json
================================================
{
"data": [
{
"id": "4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b",
"name": "export-1",
"job_id": "cd82535b-0614-4b23-bbba-b24ab0e892f7",
"status": "error",
"code": "INPUT_TASK_FAILED",
"message": "Input task has failed",
"percent": 100,
"operation": "export\/url",
"payload": {
"operation": "export\/url",
"input": [
"task-1"
]
},
"result": null,
"created_at": "2019-05-30T10:53:01+00:00",
"started_at": null,
"ended_at": "2019-05-30T10:53:23+00:00",
"retry_of_task_id": null,
"copy_of_task_id": null,
"retries": [],
"host_name": null,
"storage": null,
"depends_on_task_ids": [],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b"
}
}
],
"links": {
"first": "https:\/\/api.cloudconvert.com\/v2\/tasks?page=1",
"last": null,
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"path": "https:\/\/api.cloudconvert.com\/v2\/tasks",
"per_page": 100,
"to": 1
}
}
================================================
FILE: tests/Unit/responses/upload_task_created.json
================================================
{
"data": {
"id": "2f901289-c9fe-4c89-9c4b-98be526bdfbf",
"job_id": null,
"status": "waiting",
"code": null,
"message": null,
"percent": 100,
"operation": "import\/upload",
"payload": null,
"result": {
"form": {
"url": "https://upload.sandbox.cloudconvert.com/storage.de1.cloud.ovh.net/v1/AUTH_b2cffe8f45324c2bba39e8db1aedb58f/cloudconvert-files-sandbox/8aefdb39-34c8-4c7a-9f2e-1751686d615e/?s=jNf7hn3zox1iZfZY6NirNA&e=1559588529",
"parameters": {
"expires": 1559588529,
"max_file_count": 1,
"max_file_size": 10000000000,
"signature": "79fda6c5ffbfaa857ae9a1430641cc68c5a72297"
}
}
},
"created_at": "2019-05-31T23:52:39+00:00",
"started_at": "2019-05-31T23:52:39+00:00",
"ended_at": "2019-05-31T23:53:26+00:00",
"retry_of_task_id": null,
"copy_of_task_id": null,
"retries": [],
"depends_on_tasks": [],
"links": {
"self": "https:\/\/api.cloudconvert.com\/v2\/tasks\/2f901289-c9fe-4c89-9c4b-98be526bdfbf"
}
}
}
================================================
FILE: tests/Unit/responses/user.json
================================================
{
"data": {
"id": 1,
"username": "Username",
"email": "me@example.com",
"created_at": "2018-12-01T22:26:29+00:00",
"credits": 4434,
"links": {
"self": "https://api.cloudconvert.com/v2/users/1"
}
}
}
================================================
FILE: tests/bootstrap.php
================================================
<?php
require __DIR__ . '/../vendor/autoload.php';
gitextract_0ugw1w8m/
├── .github/
│ └── workflows/
│ └── run-tests.yml
├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml
├── src/
│ ├── CloudConvert.php
│ ├── Exceptions/
│ │ ├── Exception.php
│ │ ├── HttpClientException.php
│ │ ├── HttpServerException.php
│ │ ├── SignatureVerificationException.php
│ │ └── UnexpectedDataException.php
│ ├── Handler/
│ │ ├── SignedUrlBuilder.php
│ │ └── WebhookHandler.php
│ ├── Hydrator/
│ │ ├── HydratorInterface.php
│ │ └── JsonMapperHydrator.php
│ ├── Models/
│ │ ├── Collection.php
│ │ ├── Job.php
│ │ ├── JobCollection.php
│ │ ├── Task.php
│ │ ├── TaskCollection.php
│ │ ├── User.php
│ │ └── WebhookEvent.php
│ ├── Resources/
│ │ ├── AbstractResource.php
│ │ ├── JobsResource.php
│ │ ├── TasksResource.php
│ │ └── UsersResource.php
│ └── Transport/
│ └── HttpTransport.php
└── tests/
├── Integration/
│ ├── JobTest.php
│ ├── TaskTest.php
│ ├── TestCase.php
│ └── UserTest.php
├── Unit/
│ ├── ExceptionsTest.php
│ ├── JobResourceTest.php
│ ├── SignedUrlBuilderTest.php
│ ├── TaskCollectionTest.php
│ ├── TaskResourceTest.php
│ ├── TestCase.php
│ ├── UsersResourceTest.php
│ ├── WebhookHandlerTest.php
│ ├── requests/
│ │ └── webhook_job_finished_payload.json
│ └── responses/
│ ├── error400.json
│ ├── error402.json
│ ├── job.json
│ ├── job_created.json
│ ├── job_export_urls.json
│ ├── jobs.json
│ ├── task.json
│ ├── task_created.json
│ ├── tasks.json
│ ├── upload_task_created.json
│ └── user.json
└── bootstrap.php
SYMBOL INDEX (178 symbols across 34 files)
FILE: src/CloudConvert.php
class CloudConvert (line 16) | class CloudConvert
method __construct (line 40) | public function __construct(array $options = [])
method configureOptions (line 51) | public function configureOptions(OptionsResolver $resolver): void
method getHttpTransport (line 71) | public function getHttpTransport(): HttpTransport
method getHydrator (line 84) | public function getHydrator(): HydratorInterface
method users (line 95) | public function users(): UsersResource
method tasks (line 103) | public function tasks(): TasksResource
method jobs (line 111) | public function jobs(): JobsResource
method webhookHandler (line 119) | public function webhookHandler(): WebhookHandler
method signedUrlBuilder (line 128) | public function signedUrlBuilder(): SignedUrlBuilder
FILE: src/Exceptions/Exception.php
class Exception (line 7) | abstract class Exception extends \RuntimeException
FILE: src/Exceptions/HttpClientException.php
class HttpClientException (line 9) | class HttpClientException extends Exception
method __construct (line 36) | public function __construct(string $message, int $code, ResponseInterf...
method badRequest (line 62) | public static function badRequest(ResponseInterface $response)
method unauthorized (line 72) | public static function unauthorized(ResponseInterface $response)
method paymentRequired (line 82) | public static function paymentRequired(ResponseInterface $response)
method forbidden (line 93) | public static function forbidden(ResponseInterface $response)
method unprocessable (line 103) | public static function unprocessable(ResponseInterface $response)
method notFound (line 113) | public static function notFound(ResponseInterface $response)
method getResponse (line 122) | public function getResponse(): ?ResponseInterface
method getResponseBody (line 130) | public function getResponseBody(): array
method getResponseCode (line 138) | public function getResponseCode(): int
method getErrorCode (line 146) | public function getErrorCode(): ?string
FILE: src/Exceptions/HttpServerException.php
class HttpServerException (line 7) | class HttpServerException extends Exception
method serverError (line 16) | public static function serverError(int $httpStatus = 500)
method networkError (line 27) | public static function networkError(\Throwable $previous)
method unknownHttpResponseCode (line 37) | public static function unknownHttpResponseCode(int $code)
FILE: src/Exceptions/SignatureVerificationException.php
class SignatureVerificationException (line 7) | class SignatureVerificationException extends Exception
FILE: src/Exceptions/UnexpectedDataException.php
class UnexpectedDataException (line 7) | class UnexpectedDataException extends Exception
FILE: src/Handler/SignedUrlBuilder.php
class SignedUrlBuilder (line 7) | class SignedUrlBuilder
method createFromJob (line 11) | public function createFromJob(string $base, string $signingSecret, Job...
FILE: src/Handler/WebhookHandler.php
class WebhookHandler (line 13) | class WebhookHandler
method __construct (line 26) | public function __construct(HydratorInterface $hydrator)
method constructEvent (line 41) | public function constructEvent(string $payload, string $signature, str...
method constructEventFromRequest (line 61) | public function constructEventFromRequest(RequestInterface $request, s...
FILE: src/Hydrator/HydratorInterface.php
type HydratorInterface (line 9) | interface HydratorInterface
method hydrateObject (line 12) | public function hydrateObject($object, $data);
method hydrateArray (line 14) | public function hydrateArray($array, string $objectClass, $data);
method createObjectByResponse (line 16) | public function createObjectByResponse(string $class, ResponseInterfac...
method hydrateObjectByResponse (line 18) | public function hydrateObjectByResponse($object, ResponseInterface $re...
method hydrateArrayByResponse (line 20) | public function hydrateArrayByResponse($array, string $objectClass, Re...
FILE: src/Hydrator/JsonMapperHydrator.php
class JsonMapperHydrator (line 14) | class JsonMapperHydrator implements HydratorInterface
method __construct (line 25) | public function __construct()
method hydrateObject (line 39) | public function hydrateObject($object, $data)
method hydrateArray (line 56) | public function hydrateArray($array, string $objectClass, $data)
method hydrateObjectByResponse (line 73) | public function hydrateObjectByResponse($object, ResponseInterface $re...
method createObjectByResponse (line 91) | public function createObjectByResponse(string $class, ResponseInterfac...
method hydrateArrayByResponse (line 105) | public function hydrateArrayByResponse($array, string $objectClass, Re...
FILE: src/Models/Collection.php
class Collection (line 7) | abstract class Collection extends \ArrayObject
method filter (line 10) | public function filter(callable $callback)
FILE: src/Models/Job.php
class Job (line 7) | class Job
method getId (line 63) | public function getId(): string
method getTag (line 71) | public function getTag(): ?string
method setTag (line 81) | public function setTag(?string $tag): Job
method getWebhookUrl (line 90) | public function getWebhookUrl(): ?string
method setWebhookUrl (line 98) | public function setWebhookUrl(?string $webhook_url): Job
method getCreatedAt (line 107) | public function getCreatedAt(): \DateTimeImmutable
method getStartedAt (line 116) | public function getStartedAt(): ?\DateTimeImmutable
method getEndedAt (line 125) | public function getEndedAt(): ?\DateTimeImmutable
method getStatus (line 134) | public function getStatus(): ?string
method getTasks (line 142) | public function getTasks(): ?TaskCollection
method getLinks (line 151) | public function getLinks()
method addTask (line 162) | public function addTask(Task $task): Job
method getExportUrls (line 175) | public function getExportUrls()
method getPayload (line 187) | public function getPayload(): array
FILE: src/Models/JobCollection.php
class JobCollection (line 7) | class JobCollection extends Collection
method whereStatus (line 18) | public function whereStatus($status): JobCollection
method status (line 36) | public function status($status): JobCollection
FILE: src/Models/Task.php
class Task (line 7) | class Task
method __construct (line 91) | public function __construct(?string $operation = null, ?string $name =...
method getId (line 101) | public function getId(): string
method getCreatedAt (line 109) | public function getCreatedAt(): ?\DateTimeImmutable
method getStartedAt (line 118) | public function getStartedAt(): ?\DateTimeImmutable
method getEndedAt (line 126) | public function getEndedAt(): ?\DateTimeImmutable
method getStatus (line 134) | public function getStatus(): ?string
method getMessage (line 142) | public function getMessage(): ?string
method getCode (line 150) | public function getCode(): ?string
method getPayload (line 158) | public function getPayload()
method getResult (line 166) | public function getResult()
method getDependsOnTasks (line 174) | public function getDependsOnTasks(): ?TaskCollection
method getJobId (line 182) | public function getJobId(): ?string
method getOperation (line 190) | public function getOperation(): string
method getLinks (line 198) | public function getLinks()
method getName (line 206) | public function getName(): string
method set (line 220) | public function set(string $parameter, $value)
FILE: src/Models/TaskCollection.php
class TaskCollection (line 7) | class TaskCollection extends Collection
method whereStatus (line 17) | public function whereStatus($status): TaskCollection
method status (line 35) | public function status($status): TaskCollection
method whereName (line 47) | public function whereName($name): TaskCollection
method name (line 66) | public function name($name): TaskCollection
method whereOperation (line 79) | public function whereOperation($operation): TaskCollection
method operation (line 97) | public function operation($operation): TaskCollection
FILE: src/Models/User.php
class User (line 7) | class User
method getId (line 39) | public function getId(): int
method getUsername (line 48) | public function getUsername(): string
method getEmail (line 57) | public function getEmail(): string
method getCredits (line 66) | public function getCredits(): int
method getCreatedAt (line 75) | public function getCreatedAt(): \DateTimeImmutable
method getLinks (line 84) | public function getLinks(): array
FILE: src/Models/WebhookEvent.php
class WebhookEvent (line 7) | class WebhookEvent
method getEvent (line 28) | public function getEvent(): string
method getJob (line 36) | public function getJob(): ?Job
FILE: src/Resources/AbstractResource.php
class AbstractResource (line 10) | abstract class AbstractResource
method __construct (line 28) | public function __construct(HttpTransport $httpTransport, HydratorInte...
FILE: src/Resources/JobsResource.php
class JobsResource (line 10) | class JobsResource extends AbstractResource
method get (line 20) | public function get(string $id, $query = null): Job
method all (line 33) | public function all($query = null): JobCollection
method create (line 45) | public function create(Job $job): Job
method refresh (line 59) | public function refresh(Job $job, $query = null): Job
method wait (line 71) | public function wait(Job $job): Job
method delete (line 80) | public function delete(Job $job): void
FILE: src/Resources/TasksResource.php
class TasksResource (line 14) | class TasksResource extends AbstractResource
method get (line 23) | public function get(string $id, $query = null): Task
method all (line 37) | public function all($query = null): TaskCollection
method create (line 49) | public function create(Task $task): Task
method refresh (line 67) | public function refresh(Task $task, $query = null): Task
method wait (line 79) | public function wait(Task $task): Task
method delete (line 88) | public function delete(Task $task): void
method upload (line 101) | public function upload(Task $task, $file, ?string $fileName = null): R...
FILE: src/Resources/UsersResource.php
class UsersResource (line 9) | class UsersResource extends AbstractResource
method me (line 16) | public function me(): User
FILE: src/Transport/HttpTransport.php
class HttpTransport (line 24) | class HttpTransport
method __construct (line 36) | public function __construct($options)
method createHttpClientInstance (line 48) | protected function createHttpClientInstance(): PluginClient
method getBaseUri (line 66) | public function getBaseUri(): string
method getSyncBaseUri (line 77) | public function getSyncBaseUri(): string
method getHttpClient (line 88) | public function getHttpClient(): PluginClient
method getRequestFactory (line 96) | public function getRequestFactory(): RequestFactoryInterface
method getUriFactory (line 104) | public function getUriFactory(): UriFactoryInterface
method getStreamFactory (line 112) | public function getStreamFactory(): StreamFactoryInterface
method get (line 124) | public function get(string $path, array $query = []): ResponseInterface
method download (line 142) | public function download(string $url)
method buildBody (line 151) | protected function buildBody($body): StreamInterface
method post (line 164) | public function post(string $path, array $body): ResponseInterface
method put (line 180) | public function put(string $path, array $body): ResponseInterface
method delete (line 195) | public function delete(string $path): ResponseInterface
method upload (line 210) | public function upload($path, $file, ?string $fileName = null, array $...
method sendRequest (line 244) | protected function sendRequest(RequestInterface $request, $authenticat...
method handleErrors (line 270) | protected function handleErrors(ResponseInterface $response)
FILE: tests/Integration/JobTest.php
class JobTest (line 14) | class JobTest extends TestCase
method testCreateJob (line 17) | public function testCreateJob()
method testUploadAndDownloadFiles (line 52) | public function testUploadAndDownloadFiles()
FILE: tests/Integration/TaskTest.php
class TaskTest (line 11) | class TaskTest extends TestCase
method testCreateImportUrlTask (line 14) | public function testCreateImportUrlTask()
method testUploadFile (line 38) | public function testUploadFile()
FILE: tests/Integration/TestCase.php
class TestCase (line 9) | abstract class TestCase extends \PHPUnit\Framework\TestCase
method setUp (line 18) | public function setUp(): void
FILE: tests/Integration/UserTest.php
class UserTest (line 10) | class UserTest extends TestCase
method testMe (line 13) | public function testMe()
FILE: tests/Unit/ExceptionsTest.php
class ExceptionsTest (line 13) | class ExceptionsTest extends TestCase
method testUnauthorized (line 16) | public function testUnauthorized()
method testValidationErrors (line 37) | public function testValidationErrors()
method test503 (line 57) | public function test503()
method testsCreditsExceeded (line 75) | public function testsCreditsExceeded()
FILE: tests/Unit/JobResourceTest.php
class JobResourceTest (line 17) | class JobResourceTest extends TestCase
method testGet (line 20) | public function testGet()
method testAll (line 49) | public function testAll()
method testCreateJob (line 72) | public function testCreateJob()
method testGetExportUrls (line 140) | public function testGetExportUrls()
FILE: tests/Unit/SignedUrlBuilderTest.php
class SignedUrlBuilderTest (line 8) | class SignedUrlBuilderTest extends TestCase
method testCreateSignedUrl (line 11) | public function testCreateSignedUrl() {
FILE: tests/Unit/TaskCollectionTest.php
class TaskCollectionTest (line 10) | class TaskCollectionTest extends TestCase
method testFilterByStatus (line 14) | public function testFilterByStatus() {
FILE: tests/Unit/TaskResourceTest.php
class TaskResourceTest (line 18) | class TaskResourceTest extends TestCase
method testGet (line 21) | public function testGet()
method testAll (line 53) | public function testAll()
method testCreateImportUrlTask (line 75) | public function testCreateImportUrlTask()
method testRefresh (line 119) | public function testRefresh()
method testUploadFile (line 151) | public function testUploadFile()
FILE: tests/Unit/TestCase.php
class TestCase (line 10) | abstract class TestCase extends \PHPUnit\Framework\TestCase
method setUp (line 24) | public function setUp(): void
method getMockClient (line 35) | protected function getMockClient(): Client
method expectExceptionMessageRegExp (line 52) | public function expectExceptionMessageRegExp(string $regex): void
FILE: tests/Unit/UsersResourceTest.php
class UsersResourceTest (line 13) | class UsersResourceTest extends TestCase
method testMe (line 16) | public function testMe()
FILE: tests/Unit/WebhookHandlerTest.php
class WebhookHandlerTest (line 14) | class WebhookHandlerTest extends TestCase
method testInvalidSignature (line 17) | public function testInvalidSignature()
method testConstructsWebhookEvent (line 31) | public function testConstructsWebhookEvent()
Condensed preview — 53 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (104K chars).
[
{
"path": ".github/workflows/run-tests.yml",
"chars": 993,
"preview": "name: Tests\n\non:\n push:\n branches: [ master ]\n pull_request:\n branches: [ master ]\n\njobs:\n build:\n\n runs-on:"
},
{
"path": ".gitignore",
"chars": 164,
"preview": ".project\n.idea\n.settings\n*.iml\n*.sublime-project\n*.sublime-workspace\n.DS_Store\noutput.pdf\nnode_modules\nvendor\ndocs\ncompo"
},
{
"path": "LICENSE",
"chars": 1091,
"preview": "MIT License\n\nCopyright (c) 2017 Josias Montag <josias@montag.info>\n\nPermission is hereby granted, free of charge, to any"
},
{
"path": "README.md",
"chars": 7745,
"preview": "cloudconvert-php\n=======================\n\nThis is the official PHP SDK for the [CloudConvert](https://cloudconvert.com/a"
},
{
"path": "composer.json",
"chars": 1156,
"preview": "{\n \"name\": \"cloudconvert/cloudconvert-php\",\n \"description\": \"PHP SDK for CloudConvert APIs\",\n \"homepage\": \"https://gi"
},
{
"path": "phpunit.xml",
"chars": 1610,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit colors=\"true\" bootstrap=\"tests/bootstrap.php\">\n <testsuites>\n "
},
{
"path": "src/CloudConvert.php",
"chars": 2984,
"preview": "<?php\n\nnamespace CloudConvert;\n\nuse CloudConvert\\Handler\\SignedUrlBuilder;\nuse CloudConvert\\Handler\\WebhookHandler;\nuse "
},
{
"path": "src/Exceptions/Exception.php",
"chars": 101,
"preview": "<?php\n\n\nnamespace CloudConvert\\Exceptions;\n\n\nabstract class Exception extends \\RuntimeException\n{\n\n}\n"
},
{
"path": "src/Exceptions/HttpClientException.php",
"chars": 3441,
"preview": "<?php\n\n\nnamespace CloudConvert\\Exceptions;\n\n\nuse Psr\\Http\\Message\\ResponseInterface;\n\nclass HttpClientException extends "
},
{
"path": "src/Exceptions/HttpServerException.php",
"chars": 911,
"preview": "<?php\n\n\nnamespace CloudConvert\\Exceptions;\n\n\nclass HttpServerException extends Exception\n{\n\n\n /**\n * @param int $"
},
{
"path": "src/Exceptions/SignatureVerificationException.php",
"chars": 105,
"preview": "<?php\n\n\nnamespace CloudConvert\\Exceptions;\n\n\nclass SignatureVerificationException extends Exception\n{\n\n}\n"
},
{
"path": "src/Exceptions/UnexpectedDataException.php",
"chars": 98,
"preview": "<?php\n\n\nnamespace CloudConvert\\Exceptions;\n\n\nclass UnexpectedDataException extends Exception\n{\n\n}\n"
},
{
"path": "src/Handler/SignedUrlBuilder.php",
"chars": 617,
"preview": "<?php\n\nnamespace CloudConvert\\Handler;\n\nuse CloudConvert\\Models\\Job;\n\nclass SignedUrlBuilder\n{\n\n\n public function cre"
},
{
"path": "src/Handler/WebhookHandler.php",
"chars": 1699,
"preview": "<?php\n\n\nnamespace CloudConvert\\Handler;\n\n\nuse CloudConvert\\Exceptions\\SignatureVerificationException;\nuse CloudConvert\\E"
},
{
"path": "src/Hydrator/HydratorInterface.php",
"chars": 516,
"preview": "<?php\n\n\nnamespace CloudConvert\\Hydrator;\n\n\nuse Psr\\Http\\Message\\ResponseInterface;\n\ninterface HydratorInterface\n{\n\n p"
},
{
"path": "src/Hydrator/JsonMapperHydrator.php",
"chars": 2824,
"preview": "<?php\n\n\nnamespace CloudConvert\\Hydrator;\n\n\nuse CloudConvert\\Exceptions\\UnexpectedDataException;\nuse CloudConvert\\Models\\"
},
{
"path": "src/Models/Collection.php",
"chars": 382,
"preview": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nabstract class Collection extends \\ArrayObject\n{\n\n public function filter(ca"
},
{
"path": "src/Models/Job.php",
"chars": 3573,
"preview": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nclass Job\n{\n\n public const STATUS_WATING = 'waiting';\n public const STATU"
},
{
"path": "src/Models/JobCollection.php",
"chars": 685,
"preview": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nclass JobCollection extends Collection\n{\n\n\n /**\n * Filter job collection"
},
{
"path": "src/Models/Task.php",
"chars": 3651,
"preview": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nclass Task\n{\n\n public const STATUS_WATING = 'waiting';\n public const STAT"
},
{
"path": "src/Models/TaskCollection.php",
"chars": 1933,
"preview": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nclass TaskCollection extends Collection\n{\n\n /**\n * Filter task collectio"
},
{
"path": "src/Models/User.php",
"chars": 1156,
"preview": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nclass User\n{\n\n /**\n * @var int\n */\n protected $id;\n /**\n *"
},
{
"path": "src/Models/WebhookEvent.php",
"chars": 574,
"preview": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nclass WebhookEvent\n{\n\n public const EVENT_JOB_FINISHED = 'job.finished';\n "
},
{
"path": "src/Resources/AbstractResource.php",
"chars": 651,
"preview": "<?php\n\n\nnamespace CloudConvert\\Resources;\n\n\nuse CloudConvert\\Hydrator\\HydratorInterface;\nuse CloudConvert\\Transport\\Http"
},
{
"path": "src/Resources/JobsResource.php",
"chars": 2099,
"preview": "<?php\n\n\nnamespace CloudConvert\\Resources;\n\n\nuse CloudConvert\\Models\\Job;\nuse CloudConvert\\Models\\JobCollection;\n\nclass J"
},
{
"path": "src/Resources/TasksResource.php",
"chars": 3287,
"preview": "<?php\n\n\nnamespace CloudConvert\\Resources;\n\n\nuse CloudConvert\\Models\\ImportUploadTask;\nuse CloudConvert\\Models\\ImportUrlT"
},
{
"path": "src/Resources/UsersResource.php",
"chars": 423,
"preview": "<?php\n\n\nnamespace CloudConvert\\Resources;\n\n\nuse CloudConvert\\Models\\User;\n\nclass UsersResource extends AbstractResource\n"
},
{
"path": "src/Transport/HttpTransport.php",
"chars": 8504,
"preview": "<?php\n\n\nnamespace CloudConvert\\Transport;\n\n\nuse CloudConvert\\CloudConvert;\nuse CloudConvert\\Exceptions\\HttpClientExcepti"
},
{
"path": "tests/Integration/JobTest.php",
"chars": 2719,
"preview": "<?php\n\n\nnamespace CloudConvert\\Tests\\Integration;\n\n\nuse CloudConvert\\Models\\ConvertTask;\nuse CloudConvert\\Models\\ExportU"
},
{
"path": "tests/Integration/TaskTest.php",
"chars": 1508,
"preview": "<?php\n\n\nnamespace CloudConvert\\Tests\\Integration;\n\n\nuse CloudConvert\\Models\\ImportUploadTask;\nuse CloudConvert\\Models\\Im"
},
{
"path": "tests/Integration/TestCase.php",
"chars": 465,
"preview": "<?php\n\n\nnamespace CloudConvert\\Tests\\Integration;\n\nuse CloudConvert\\CloudConvert;\nuse Http\\Mock\\Client;\n\nabstract class "
},
{
"path": "tests/Integration/UserTest.php",
"chars": 470,
"preview": "<?php\n\n\nnamespace CloudConvert\\Tests\\Integration;\n\n\nuse CloudConvert\\Models\\User;\n\n\nclass UserTest extends TestCase\n{\n\n "
},
{
"path": "tests/Unit/ExceptionsTest.php",
"chars": 2275,
"preview": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\n\nuse CloudConvert\\Exceptions\\HttpClientException;\nuse CloudConvert\\Exception"
},
{
"path": "tests/Unit/JobResourceTest.php",
"chars": 5094,
"preview": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\n\n\nuse CloudConvert\\Models\\Job;\nuse CloudConvert\\Models\\Task;\n\n\nuse GuzzleHtt"
},
{
"path": "tests/Unit/SignedUrlBuilderTest.php",
"chars": 1125,
"preview": "<?php\n\nnamespace CloudConvert\\Tests\\Unit;\n\nuse CloudConvert\\Models\\Job;\nuse CloudConvert\\Models\\Task;\n\nclass SignedUrlBu"
},
{
"path": "tests/Unit/TaskCollectionTest.php",
"chars": 1297,
"preview": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\n\nuse CloudConvert\\Models\\Task;\nuse CloudConvert\\Models\\TaskCollection;\n\nclas"
},
{
"path": "tests/Unit/TaskResourceTest.php",
"chars": 5839,
"preview": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\n\nuse CloudConvert\\Models\\ExportUrlTask;\nuse CloudConvert\\Models\\ImportUpload"
},
{
"path": "tests/Unit/TestCase.php",
"chars": 1284,
"preview": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\nuse CloudConvert\\CloudConvert;\nuse GuzzleHttp\\Psr7\\Response;\nuse Http\\Mock\\C"
},
{
"path": "tests/Unit/UsersResourceTest.php",
"chars": 842,
"preview": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\n\nuse CloudConvert\\Models\\User;\n\n\nuse GuzzleHttp\\Psr7\\Response;\nuse Http\\Mess"
},
{
"path": "tests/Unit/WebhookHandlerTest.php",
"chars": 1647,
"preview": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\n\nuse CloudConvert\\Exceptions\\SignatureVerificationException;\nuse CloudConver"
},
{
"path": "tests/Unit/requests/webhook_job_finished_payload.json",
"chars": 3602,
"preview": "{\n \"event\": \"job.finished\",\n \"job\": {\n \"id\": \"c677ccf7-8876-4f48-bb96-0ab8e0d88cd7\",\n \"status\": \"finished\",\n "
},
{
"path": "tests/Unit/responses/error400.json",
"chars": 168,
"preview": "{\n \"message\": \"The given data was invalid.\",\n \"code\": \"INVALID_DATA\",\n \"errors\": {\n \"status\": [\n \"Cannot chan"
},
{
"path": "tests/Unit/responses/error402.json",
"chars": 96,
"preview": "{\n \"message\": \"Your account has run out of conversion minutes\",\n \"code\": \"CREDITS_EXCEEDED\"\n}\n"
},
{
"path": "tests/Unit/responses/job.json",
"chars": 3830,
"preview": "{\n \"data\": {\n \"id\": \"cd82535b-0614-4b23-bbba-b24ab0e892f7\",\n \"tag\": \"test-1234\",\n \"status\": \"error\",\n \"crea"
},
{
"path": "tests/Unit/responses/job_created.json",
"chars": 2198,
"preview": "{\n \"data\": {\n \"id\": \"c677ccf7-8876-4f48-bb96-0ab8e0d88cd7\",\n \"status\": \"waiting\",\n \"created_at\": \"2019-06-01T0"
},
{
"path": "tests/Unit/responses/job_export_urls.json",
"chars": 2031,
"preview": "{\n \"data\": {\n \"id\": \"cd82535b-0614-4b23-bbba-b24ab0e892f7\",\n \"tag\": \"test-1234\",\n \"status\": \"error\",\n \"crea"
},
{
"path": "tests/Unit/responses/jobs.json",
"chars": 611,
"preview": "{\n \"data\": [\n {\n \"id\": \"bd7d06b4-60fb-472b-b3a3-9034b273df07\",\n \"status\": \"waiting\",\n \"created_at\": \""
},
{
"path": "tests/Unit/responses/task.json",
"chars": 2161,
"preview": "{\n \"data\": {\n \"id\": \"4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b\",\n \"name\": \"export-1\",\n \"job_id\": \"cd82535b-0614-4b2"
},
{
"path": "tests/Unit/responses/task_created.json",
"chars": 701,
"preview": "{\n \"data\": {\n \"id\": \"2f901289-c9fe-4c89-9c4b-98be526bdfbf\",\n \"job_id\": null,\n \"status\": \"waiting\",\n \"code\":"
},
{
"path": "tests/Unit/responses/tasks.json",
"chars": 1149,
"preview": "{\n \"data\": [\n {\n \"id\": \"4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b\",\n \"name\": \"export-1\",\n \"job_id\": \"cd825"
},
{
"path": "tests/Unit/responses/upload_task_created.json",
"chars": 1071,
"preview": "{\n \"data\": {\n \"id\": \"2f901289-c9fe-4c89-9c4b-98be526bdfbf\",\n \"job_id\": null,\n \"status\": \"waiting\",\n \"code\":"
},
{
"path": "tests/Unit/responses/user.json",
"chars": 237,
"preview": "{\n \"data\": {\n \"id\": 1,\n \"username\": \"Username\",\n \"email\": \"me@example.com\",\n \"created_at\": \"2018-12-01T22:2"
},
{
"path": "tests/bootstrap.php",
"chars": 50,
"preview": "<?php\nrequire __DIR__ . '/../vendor/autoload.php';"
}
]
About this extraction
This page contains the full source code of the cloudconvert/cloudconvert-php GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 53 files (93.2 KB), approximately 27.0k tokens, and a symbol index with 178 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.