[
  {
    "path": ".github/workflows/run-tests.yml",
    "content": "name: Tests\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    \n    strategy:\n      fail-fast: false\n      matrix:\n        php: [\"8.5\", \"8.4\", \"8.3\", \"8.2\", \"8.1\", \"8.0\", \"7.4\"]\n\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Cache dependencies\n        uses: actions/cache@v4\n        with:\n          path: ~/.composer/cache/files\n          key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}\n          restore-keys: |\n            dependencies-php-${{ matrix.php }}-composer-\n            dependencies-php-\n            \n      - name: Setup PHP\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: ${{ matrix.php }}\n          extensions: json, dom, curl, libxml, mbstring\n          coverage: none\n\n      - name: Install dependencies\n        run: composer install --prefer-dist --no-progress\n\n      - name: Execute tests\n        run: vendor/bin/phpunit\n"
  },
  {
    "path": ".gitignore",
    "content": ".project\n.idea\n.settings\n*.iml\n*.sublime-project\n*.sublime-workspace\n.DS_Store\noutput.pdf\nnode_modules\nvendor\ndocs\ncomposer.lock\ncomposer.phar\ncloudconvert-php.phar"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Josias Montag <josias@montag.info>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "cloudconvert-php\n=======================\n\nThis is the official PHP SDK for the [CloudConvert](https://cloudconvert.com/api/v2) **API v2**.\n\n[![Tests](https://github.com/cloudconvert/cloudconvert-php/actions/workflows/run-tests.yml/badge.svg)](https://github.com/cloudconvert/cloudconvert-php/actions/workflows/run-tests.yml)\n[![Latest Stable Version](https://poser.pugx.org/cloudconvert/cloudconvert-php/v/stable)](https://packagist.org/packages/cloudconvert/cloudconvert-php)\n[![Total Downloads](https://poser.pugx.org/cloudconvert/cloudconvert-php/downloads)](https://packagist.org/packages/cloudconvert/cloudconvert-php)\n\n\nInstall\n-------------------\n\nTo install the PHP SDK you will need to be using [Composer]([https://getcomposer.org/)\nin your project. \n \nInstall the SDK alongside Guzzle:\n\n```bash\ncomposer require cloudconvert/cloudconvert-php guzzlehttp/guzzle\n```\n\nThis 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).\n\n\n\nCreating Jobs\n-------------------\n```php\nuse \\CloudConvert\\CloudConvert;\nuse \\CloudConvert\\Models\\Job;\nuse \\CloudConvert\\Models\\Task;\n\n\n$cloudconvert = new CloudConvert([\n    'api_key' => 'API_KEY',\n    'sandbox' => false\n]);\n\n\n$job = (new Job())\n    ->setTag('myjob-1')\n    ->addTask(\n        (new Task('import/url', 'import-my-file'))\n            ->set('url','https://my-url')\n    )\n    ->addTask(\n        (new Task('convert', 'convert-my-file'))\n            ->set('input', 'import-my-file')\n            ->set('output_format', 'pdf')\n            ->set('some_other_option', 'value')\n    )\n    ->addTask(\n        (new Task('export/url', 'export-my-file'))\n            ->set('input', 'convert-my-file')\n    );\n\n$cloudconvert->jobs()->create($job)\n\n```\n\nYou can use the [CloudConvert Job Builder](https://cloudconvert.com/api/v2/jobs/builder) to see the available options for the various task types.\n\n\nUploading Files\n-------------------\nUploads 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:\n\n```php\nuse \\CloudConvert\\Models\\Job;\nuse \\CloudConvert\\Models\\Task;\n\n\n$job = (new Job())\n    ->addTask(new Task('import/upload','upload-my-file'))\n    ->addTask(\n        (new Task('convert', 'convert-my-file'))\n            ->set('input', 'upload-my-file')\n            ->set('output_format', 'pdf')\n    )\n    ->addTask(\n        (new Task('export/url', 'export-my-file'))\n            ->set('input', 'convert-my-file')\n    );\n\n$job = $cloudconvert->jobs()->create($job);\n\n$uploadTask = $job->getTasks()->whereName('upload-my-file')[0];\n\n$cloudconvert->tasks()->upload($uploadTask, fopen('./file.pdf', 'r'), 'file.pdf');\n```\nThe `upload()` method accepts a string, PHP resource or PSR-7 `StreamInterface` as second parameter.\n\nYou can also directly allow clients to upload files to CloudConvert:\n\n```html\n<form action=\"<?=$uploadTask->getResult()->form->url?>\"\n      method=\"POST\"\n      enctype=\"multipart/form-data\">\n    <? foreach ((array)$uploadTask->getResult()->form->parameters as $parameter => $value) { ?>\n        <input type=\"hidden\" name=\"<?=$parameter?>\" value=\"<?=$value?>\">\n    <? } ?>\n    <input type=\"file\" name=\"file\">\n    <input type=\"submit\">\n</form>\n```\n\n\nDownloading Files\n-------------------\n\nCloudConvert 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.\n\n```php\n$cloudconvert->jobs()->wait($job); // Wait for job completion\n\nforeach ($job->getExportUrls() as $file) {\n\n    $source = $cloudconvert->getHttpTransport()->download($file->url)->detach();\n    $dest = fopen('output/' . $file->filename, 'w');\n    \n    stream_copy_to_stream($source, $dest);\n\n}\n```\n\nThe `download()` method returns a PSR-7 `StreamInterface`, which can be used as a PHP resource using `detach()`.\n\n\n\nWebhooks\n-------------------\n\nWebhooks can be created on the [CloudConvert Dashboard](https://cloudconvert.com/dashboard/api/v2/webhooks) and you can also find the required signing secret there.\n\n```php\n$cloudconvert = new CloudConvert([\n    'api_key' => 'API_KEY',\n    'sandbox' => false\n]);\n\n$signingSecret = '...'; // You can find it in your webhook settings\n\n$payload = @file_get_contents('php://input');\n$signature = $_SERVER['HTTP_CLOUDCONVERT_SIGNATURE'];\n\ntry {\n    $webhookEvent = $cloudconvert->webhookHandler()->constructEvent($payload, $signature, $signingSecret);\n} catch(\\CloudConvert\\Exceptions\\UnexpectedDataException $e) {\n    // Invalid payload\n    http_response_code(400);\n    exit();\n} catch(\\CloudConvert\\Exceptions\\SignatureVerificationException $e) {\n    // Invalid signature\n    http_response_code(400);\n    exit();\n}\n\n$job = $webhookEvent->getJob();\n\n$job->getTag(); // can be used to store an ID\n\n$exportTask = $job->getTasks()\n            ->whereStatus(Task::STATUS_FINISHED) // get the task with 'finished' status ...\n            ->whereName('export-it')[0];        // ... and with the name 'export-it'\n// ...\n\n```\n\nAlternatively, you can construct a `WebhookEvent` using a PSR-7 `RequestInterface`:\n```php\n$webhookEvent = $cloudconvert->webhookHandler()->constructEventFromRequest($request, $signingSecret);\n```\n\nSigned URLs\n-------------------\n\nSigned 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).\n\n```php\n$cloudconvert = new CloudConvert([\n    'api_key' => 'API_KEY',\n    'sandbox' => false\n]);\n\n$job = (new Job())\n    ->addTask(\n        (new Task('import/url', 'import-my-file'))\n            ->set('url', 'https://my.url/file.docx')\n    )\n    ->addTask(\n        (new Task('convert', 'convert-my-file'))\n            ->set('input', 'import-my-file')\n            ->set('output_format', 'pdf')\n    )\n    ->addTask(\n        (new Task('export/url', 'export-my-file'))\n            ->set('input', 'convert-my-file')\n    );\n\n$signedUrlBase = 'SIGNED_URL_BASE';\n$signingSecret = 'SIGNED_URL_SIGNING_SECRET';\n$url = $cloudConvert->signedUrlBuilder()->createFromJob($signedUrlBase, $signingSecret, $job, 'CACHE_KEY');\n```\n\n\n\nSetting a Region\n-----------------\n\nBy default, the region in your [account settings](https://cloudconvert.com/dashboard/region) is used. Alternatively, you can set a fixed region:\n\n```php\n// Pass the region to the constructor\n$cloudconvert = new CloudConvert([\n    'api_key' => 'API_KEY',\n    'sandbox' => false,\n    'region'  => 'us-east'\n]);\n```\n\n\nUnit Tests\n-----------------\n\n    vendor/bin/phpunit --testsuite unit\n\n\nIntegration Tests\n-----------------\n\n    vendor/bin/phpunit --testsuite integration\n\nBy 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).\n\n    53d6fe6b688c31c565907c81de625046  input.pdf\n    99d4c165f77af02015aa647770286cf9  input.png\n\nResources\n---------\n\n* [API Documentation](https://cloudconvert.com/api/v2)\n* [CloudConvert Blog](https://cloudconvert.com/blog)\n"
  },
  {
    "path": "composer.json",
    "content": "{\n  \"name\": \"cloudconvert/cloudconvert-php\",\n  \"description\": \"PHP SDK for CloudConvert APIs\",\n  \"homepage\": \"https://github.com/cloudconvert/cloudconvert-php\",\n  \"authors\": [\n    {\n      \"name\": \"Josias Montag\",\n      \"email\": \"josias@montag.info\"\n    }\n  ],\n  \"require\": {\n    \"php\": \"^7.4|^8.0\",\n    \"ext-json\": \"*\",\n    \"php-http/httplug\": \"^2.4\",\n    \"php-http/client-common\": \"^2.0\",\n    \"php-http/discovery\": \"^1.17\",\n    \"php-http/multipart-stream-builder\": \"^1.3\",\n    \"psr/http-message\": \"^1.1 || ^2.0\",\n    \"psr/http-factory-implementation\": \"^1.0\",\n    \"psr/http-client-implementation\": \"^1.0\",\n    \"symfony/options-resolver\": \"^4.2 || ^5.0 || ^6.0 || ^7.0\",\n    \"netresearch/jsonmapper\": \"^4.0\"\n  },\n  \"require-dev\": {\n    \"php-http/mock-client\": \"^1.0\",\n    \"phpunit/phpunit\": \"^9.3\",\n    \"guzzlehttp/guzzle\": \"^7.0\"\n  },\n  \"autoload\": {\n    \"psr-4\": {\n      \"CloudConvert\\\\\": \"src/\"\n    }\n  },\n  \"autoload-dev\": {\n    \"psr-4\": {\n      \"CloudConvert\\\\Tests\\\\\": \"tests/\"\n    }\n  },\n  \"scripts\": {\n    \"tests\": [\n      \"vendor/bin/phpunit --verbose\"\n    ]\n  },\n  \"config\": {\n    \"allow-plugins\": {\n      \"php-http/discovery\": false\n    }\n  }\n}\n"
  },
  {
    "path": "phpunit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit colors=\"true\" bootstrap=\"tests/bootstrap.php\">\n    <testsuites>\n        <testsuite name=\"Unit\">\n            <directory>tests/Unit</directory>\n        </testsuite>\n        <testsuite name=\"Integration\">\n            <directory>tests/Integration</directory>\n        </testsuite>\n    </testsuites>\n    <php>\n        <env name=\"CLOUDCONVERT_API_KEY\" value=\"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjI4YmE3OGQyZjc1NWM5ZGE3Yjg1NDRhMWRkMjg2NWM4N2U0YzI5NWI0NzQ0Zjc4ZDNmMzA3OWM2NjU3ZjI0MjVhOTMyYjIxMjU5ZGU2NWQ4In0.eyJhdWQiOiIxIiwianRpIjoiMjhiYTc4ZDJmNzU1YzlkYTdiODU0NGExZGQyODY1Yzg3ZTRjMjk1YjQ3NDRmNzhkM2YzMDc5YzY2NTdmMjQyNWE5MzJiMjEyNTlkZTY1ZDgiLCJpYXQiOjE1NTkwNjc3NzcsIm5iZiI6MTU1OTA2Nzc3NywiZXhwIjo0NzE0NzQxMzc3LCJzdWIiOiIzNzExNjc4NCIsInNjb3BlcyI6WyJ1c2VyLnJlYWQiLCJ1c2VyLndyaXRlIiwidGFzay5yZWFkIiwidGFzay53cml0ZSIsIndlYmhvb2sucmVhZCIsIndlYmhvb2sud3JpdGUiXX0.IkmkfDVGwouCH-ICFAShQMHyFAHK3y90CSoissUVD8h5HFG4GqN5DEw0IFzlPr1auUKp3H1pAvPutdIQtrDMTmUUmGMUb2dRlCAuQdqxa81Q5KAmcKDgOg2YTWOWEGMy3jETTb7W6vyNGsT_3DFMapMdeOw1jdIUTMZqW3QbSCeGXj3PMRnhI7YynaDtmktjzO9IUDHbeT2HRzzMiep97KvVZNjYtZvgM-kbUjE6Mm68_kA8JMuQeor0Yg7896JPV0YM3-MnHf7elKgoCJbfBCDAbvSX_ZYsSI7IGoLLb0mgJVfFcH_HMYAHhJj5cUEJN2Iml-FkODqrRk72bVxyJs9j1GPQBl4ORXuU9yrjUgHrRaZ5YM__LwsUQB3AuB92oyQseCjULn1sWM1PzIXCcyVjKZSpn9LAAGNf9paCF-_G9ok9tZKccRouCiYl9v5XbmuxV8hXYp6fXZxyaAkj_JN2kErVSkxYzVyyZL1e220aFFnbch6nDvLFHgi-WeTQHFQDzuHsM8RKRixV8uD7pk3de4AEYg0EWqZHCr82qY7TGdSQvuAS0QIy3B89OwQW0ROW4k3Yw0XIKgKSYWyKnc7huc7yPQUIDDDAOa5OojXrVY5ZuL_hwQMIOmejcHTKFdAgzAaVnRkC8_FfVh4wHCPBaHjze9hRp5n4O1pnPFI\"/>\n\t</php>\n</phpunit>\n"
  },
  {
    "path": "src/CloudConvert.php",
    "content": "<?php\n\nnamespace CloudConvert;\n\nuse CloudConvert\\Handler\\SignedUrlBuilder;\nuse CloudConvert\\Handler\\WebhookHandler;\nuse CloudConvert\\Hydrator\\HydratorInterface;\nuse CloudConvert\\Hydrator\\JsonMapperHydrator;\nuse CloudConvert\\Resources\\JobsResource;\nuse CloudConvert\\Resources\\TasksResource;\nuse CloudConvert\\Resources\\UsersResource;\nuse CloudConvert\\Transport\\HttpTransport;\nuse Psr\\Http\\Client\\ClientInterface;\nuse Symfony\\Component\\OptionsResolver\\OptionsResolver;\n\nclass CloudConvert\n{\n\n    const VERSION = '3.4.3';\n\n    /**\n     * @var array\n     */\n    protected $options;\n    /**\n     * @var HttpTransport\n     */\n    protected $httpTransport;\n    /**\n     * @var HydratorInterface\n     */\n    protected $hydrator;\n\n\n    /**\n     * Api constructor.\n     *\n     * @param array $options\n     */\n    public function __construct(array $options = [])\n    {\n        $resolver = new OptionsResolver();\n        $this->configureOptions($resolver);\n\n        $this->options = $resolver->resolve($options);\n    }\n\n    /**\n     * @param OptionsResolver $resolver\n     */\n    public function configureOptions(OptionsResolver $resolver): void\n    {\n        $resolver->setRequired('api_key');\n        $resolver->setAllowedTypes('api_key', 'string');\n\n        $resolver->setDefault('sandbox', false);\n        $resolver->setAllowedTypes('sandbox', 'boolean');\n\n        $resolver->setDefined('http_client');\n        $resolver->setAllowedTypes('http_client', [ClientInterface::class]);\n\n        $resolver->setDefined('region');\n        $resolver->setAllowedTypes('region', 'string');\n\n    }\n\n\n    /**\n     * @return HttpTransport\n     */\n    public function getHttpTransport(): HttpTransport\n    {\n        if ($this->httpTransport === null) {\n            $this->httpTransport = new HttpTransport($this->options);\n        }\n\n        return $this->httpTransport;\n    }\n\n\n    /**\n     * @return HydratorInterface\n     */\n    public function getHydrator(): HydratorInterface\n    {\n        if ($this->hydrator === null) {\n            $this->hydrator = new JsonMapperHydrator();\n        }\n        return $this->hydrator;\n    }\n\n    /**\n     * @return UsersResource\n     */\n    public function users(): UsersResource\n    {\n        return new UsersResource($this->getHttpTransport(), $this->getHydrator());\n    }\n\n    /**\n     * @return TasksResource\n     */\n    public function tasks(): TasksResource\n    {\n        return new TasksResource($this->getHttpTransport(), $this->getHydrator());\n    }\n\n    /**\n     * @return JobsResource\n     */\n    public function jobs(): JobsResource\n    {\n        return new JobsResource($this->getHttpTransport(), $this->getHydrator());\n    }\n\n    /**\n     * @return WebhookHandler\n     */\n    public function webhookHandler(): WebhookHandler\n    {\n        return new WebhookHandler($this->getHydrator());\n    }\n\n\n    /**\n     * @return SignedUrlBuilder\n     */\n    public function signedUrlBuilder(): SignedUrlBuilder\n    {\n        return new SignedUrlBuilder();\n    }\n\n}\n"
  },
  {
    "path": "src/Exceptions/Exception.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Exceptions;\n\n\nabstract class Exception extends \\RuntimeException\n{\n\n}\n"
  },
  {
    "path": "src/Exceptions/HttpClientException.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Exceptions;\n\n\nuse Psr\\Http\\Message\\ResponseInterface;\n\nclass HttpClientException extends Exception\n{\n    /**\n     * @var ResponseInterface|null\n     */\n    protected $response;\n    /**\n     * @var array\n     */\n    protected $responseBody;\n    /**\n     * @var int\n     */\n    protected $responseCode;\n\n    /**\n     * @var string|null\n     */\n    protected $errorCode;\n\n    /**\n     * HttpClientException constructor.\n     *\n     * @param string            $message\n     * @param int               $code\n     * @param ResponseInterface $response\n     */\n    public function __construct(string $message, int $code, ResponseInterface $response)\n    {\n        $this->response = $response;\n        $this->responseCode = $response->getStatusCode();\n        $this->responseBody = @json_decode($response->getBody(), true) ?? [];\n        $this->errorCode = isset($this->responseBody['code']) ? $this->responseBody['code'] : null;\n\n        if (isset($this->responseBody['message'])) {\n            $message = $this->responseBody['message'];\n        }\n\n        if (isset($this->getResponseBody()['errors'])) {\n            $message = $this->getMessage();\n            foreach ($this->getResponseBody()['errors'] as $field => $errors) {\n                $message .= ' ' . $field . ': ' . implode(' ', $errors);\n            }\n        }\n\n        parent::__construct($message, $code);\n    }\n\n    /**\n     * @param ResponseInterface $response\n     *\n     * @return HttpClientException\n     */\n    public static function badRequest(ResponseInterface $response)\n    {\n        return new self('Invalid data.', 400, $response);\n    }\n\n    /**\n     * @param ResponseInterface $response\n     *\n     * @return HttpClientException\n     */\n    public static function unauthorized(ResponseInterface $response)\n    {\n        return new self('Unauthorized.', 401, $response);\n    }\n\n    /**\n     * @param ResponseInterface $response\n     *\n     * @return HttpClientException\n     */\n    public static function paymentRequired(ResponseInterface $response)\n    {\n        return new self('Credits used up.', 402, $response);\n    }\n\n\n    /**\n     * @param ResponseInterface $response\n     *\n     * @return HttpClientException\n     */\n    public static function forbidden(ResponseInterface $response)\n    {\n        return new self('Forbidden.', 403, $response);\n    }\n\n    /**\n     * @param ResponseInterface $response\n     *\n     * @return HttpClientException\n     */\n    public static function unprocessable(ResponseInterface $response)\n    {\n        return new self('Unprocessable.', 422, $response);\n    }\n\n    /**\n     * @param ResponseInterface $response\n     *\n     * @return HttpClientException\n     */\n    public static function notFound(ResponseInterface $response)\n    {\n        return new self('The endpoint you have tried to access does not exist. ',\n            404, $response);\n    }\n\n    /**\n     * @return ResponseInterface|null\n     */\n    public function getResponse(): ?ResponseInterface\n    {\n        return $this->response;\n    }\n\n    /**\n     * @return array\n     */\n    public function getResponseBody(): array\n    {\n        return $this->responseBody;\n    }\n\n    /**\n     * @return int\n     */\n    public function getResponseCode(): int\n    {\n        return $this->responseCode;\n    }\n\n    /**\n     * @return string|null\n     */\n    public function getErrorCode(): ?string\n    {\n        return $this->errorCode;\n    }\n\n\n}\n"
  },
  {
    "path": "src/Exceptions/HttpServerException.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Exceptions;\n\n\nclass HttpServerException extends Exception\n{\n\n\n    /**\n     * @param int $httpStatus\n     *\n     * @return HttpServerException\n     */\n    public static function serverError(int $httpStatus = 500)\n    {\n        return new self('An unexpected error occurred at CloudConvert\\'s servers. Try again later.',\n            $httpStatus);\n    }\n\n    /**\n     * @param \\Throwable $previous\n     *\n     * @return HttpServerException\n     */\n    public static function networkError(\\Throwable $previous)\n    {\n        return new self('CloudConvert\\'s servers are currently unreachable.', 0, $previous);\n    }\n\n    /**\n     * @param int $code\n     *\n     * @return HttpServerException\n     */\n    public static function unknownHttpResponseCode(int $code)\n    {\n        return new self(sprintf('Unknown HTTP response code (\"%d\") received from the API server', $code));\n    }\n\n\n}\n"
  },
  {
    "path": "src/Exceptions/SignatureVerificationException.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Exceptions;\n\n\nclass SignatureVerificationException extends Exception\n{\n\n}\n"
  },
  {
    "path": "src/Exceptions/UnexpectedDataException.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Exceptions;\n\n\nclass UnexpectedDataException extends Exception\n{\n\n}\n"
  },
  {
    "path": "src/Handler/SignedUrlBuilder.php",
    "content": "<?php\n\nnamespace CloudConvert\\Handler;\n\nuse CloudConvert\\Models\\Job;\n\nclass SignedUrlBuilder\n{\n\n\n    public function createFromJob(string $base, string $signingSecret, Job $job, ?string $cacheKey = null): string\n    {\n\n        $json = json_encode($job->getPayload());\n        $base64EncodedJob = rtrim(strtr(base64_encode($json), '+/', '-_'), '=');\n\n        $url = $base . '?job=' . $base64EncodedJob;\n\n        if($cacheKey) {\n            $url .= '&cache_key=' . $cacheKey;\n        }\n\n        $signature = hash_hmac('sha256', $url, $signingSecret);\n\n        $url .= '&s=' . $signature;\n\n        return $url;\n\n    }\n\n}"
  },
  {
    "path": "src/Handler/WebhookHandler.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Handler;\n\n\nuse CloudConvert\\Exceptions\\SignatureVerificationException;\nuse CloudConvert\\Exceptions\\UnexpectedDataException;\nuse CloudConvert\\Hydrator\\HydratorInterface;\nuse CloudConvert\\Models\\WebhookEvent;\nuse Psr\\Http\\Message\\RequestInterface;\n\nclass WebhookHandler\n{\n\n    /**\n     * @var HydratorInterface\n     */\n    protected $hydrator;\n\n    /**\n     * WebhookHandler constructor.\n     *\n     * @param HydratorInterface $hydrator\n     */\n    public function __construct(HydratorInterface $hydrator)\n    {\n        $this->hydrator = $hydrator;\n    }\n\n\n    /**\n     * @param string $payload\n     * @param string $signature\n     * @param string $signingSecret\n     *\n     * @return WebhookEvent\n     * @throws SignatureVerificationException\n     * @throws UnexpectedDataException\n     */\n    public function constructEvent(string $payload, string $signature, string $signingSecret): WebhookEvent\n    {\n\n        if(!hash_equals(hash_hmac('sha256', $payload, $signingSecret), $signature)) {\n            throw new SignatureVerificationException(\"Invalid webhook signature\");\n        }\n\n        return $this->hydrator->hydrateObject(new WebhookEvent(), json_decode($payload));\n\n    }\n\n\n    /**\n     * @param RequestInterface $request\n     * @param string           $signingSecret\n     *\n     * @return WebhookEvent\n     * @throws SignatureVerificationException\n     * @throws UnexpectedDataException\n     */\n    public function constructEventFromRequest(RequestInterface $request, string $signingSecret): WebhookEvent\n    {\n        return $this->constructEvent($request->getBody(), $request->getHeaderLine('CloudConvert-Signature'),\n            $signingSecret);\n    }\n\n}\n"
  },
  {
    "path": "src/Hydrator/HydratorInterface.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Hydrator;\n\n\nuse Psr\\Http\\Message\\ResponseInterface;\n\ninterface HydratorInterface\n{\n\n    public function hydrateObject($object, $data);\n\n    public function hydrateArray($array, string $objectClass, $data);\n\n    public function createObjectByResponse(string $class, ResponseInterface $response);\n\n    public function hydrateObjectByResponse($object, ResponseInterface $response);\n\n    public function hydrateArrayByResponse($array, string $objectClass, ResponseInterface $response);\n\n}\n"
  },
  {
    "path": "src/Hydrator/JsonMapperHydrator.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Hydrator;\n\n\nuse CloudConvert\\Exceptions\\UnexpectedDataException;\nuse CloudConvert\\Models\\ExportUrlTask;\nuse CloudConvert\\Models\\ImportUploadTask;\nuse CloudConvert\\Models\\Task;\nuse JsonMapper;\nuse Psr\\Http\\Message\\ResponseInterface;\n\nclass JsonMapperHydrator implements HydratorInterface\n{\n\n    /**\n     * @var JsonMapper\n     */\n    protected $jsonMapper;\n\n    /**\n     * JsonMapperHydrator constructor.\n     */\n    public function __construct()\n    {\n        $this->jsonMapper = new JsonMapper();\n        $this->jsonMapper->bIgnoreVisibility = true;\n    }\n\n\n    /**\n     * @param $object\n     * @param $data\n     *\n     * @return object\n     * @throws UnexpectedDataException\n     */\n    public function hydrateObject($object, $data)\n    {\n        try {\n            return $this->jsonMapper->map($data, $object);\n        } catch (\\JsonMapper_Exception $exception) {\n            throw new UnexpectedDataException($exception);\n        }\n    }\n\n\n    /**\n     * @param $array\n     * @param $objectClass\n     * @param $data\n     *\n     * @return object\n     */\n    public function hydrateArray($array, string $objectClass, $data)\n    {\n        try {\n            return $this->jsonMapper->map($data, $array, $objectClass);\n        } catch (\\JsonMapper_Exception $exception) {\n            throw new UnexpectedDataException($exception);\n        }\n    }\n\n\n    /**\n     * @param                   $object\n     * @param ResponseInterface $response\n     *\n     * @return object\n     * @throws UnexpectedDataException\n     */\n    public function hydrateObjectByResponse($object, ResponseInterface $response)\n    {\n        $body = json_decode($response->getBody());\n        try {\n            return $this->jsonMapper->map($body->data, $object);\n        } catch (\\JsonMapper_Exception $exception) {\n            throw new UnexpectedDataException($exception);\n        }\n    }\n\n\n    /**\n     * @param string            $class\n     * @param ResponseInterface $response\n     *\n     * @return object\n     * @throws UnexpectedDataException\n     */\n    public function createObjectByResponse(string $class, ResponseInterface $response)\n    {\n        return $this->hydrateObjectByResponse(new $class, $response);\n    }\n\n\n    /**\n     * @param                   $array\n     * @param string            $objectClass\n     * @param ResponseInterface $response\n     *\n     * @return mixed\n     * @throws UnexpectedDataException\n     */\n    public function hydrateArrayByResponse($array, string $objectClass, ResponseInterface $response)\n    {\n        $body = json_decode($response->getBody());\n        try {\n            return $this->jsonMapper->mapArray($body->data, $array, $objectClass);\n        } catch (\\JsonMapper_Exception $exception) {\n            throw new UnexpectedDataException($exception);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/Models/Collection.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nabstract class Collection extends \\ArrayObject\n{\n\n    public function filter(callable $callback)\n    {\n        $class = get_called_class();\n        $result = new $class();\n        foreach ($this as $k => $item) {\n            if ($callback($item)) {\n                $result[] = $item;\n            }\n        }\n        return $result;\n    }\n\n}\n"
  },
  {
    "path": "src/Models/Job.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nclass Job\n{\n\n    public const STATUS_WATING = 'waiting';\n    public const STATUS_PROCESSING = 'processing';\n    public const STATUS_ERROR = 'error';\n    public const STATUS_FINISHED = 'finished';\n\n    /**\n     * @var string\n     */\n    protected $id;\n\n    /**\n     * @var string|null\n     */\n    protected $tag;\n\n    /**\n     * @var string|null\n     */\n    protected $webhook_url;\n\n    /**\n     * @var \\DateTimeImmutable\n     */\n    protected $created_at;\n\n    /**\n     * @var \\DateTimeImmutable|null\n     */\n    protected $started_at;\n\n    /**\n     * @var \\DateTimeImmutable|null\n     */\n    protected $ended_at;\n\n    /**\n     * @var string|null\n     */\n    protected $status;\n\n    /**\n     * @var TaskCollection[Task]|null\n     */\n    protected $tasks;\n\n    /**\n     * @var object|null\n     */\n    protected $links;\n\n    /**\n     * @return string\n     */\n    public function getId(): string\n    {\n        return $this->id;\n    }\n\n    /**\n     * @return string|null\n     */\n    public function getTag(): ?string\n    {\n        return $this->tag;\n    }\n\n    /**\n     * @param string|null $tag\n     *\n     * @return Job\n     */\n    public function setTag(?string $tag): Job\n    {\n        $this->tag = $tag;\n        return $this;\n    }\n\n    /**\n     * @return string|null\n     */\n    public function getWebhookUrl(): ?string\n    {\n        return $this->webhook_url;\n    }\n\n    /**\n     * @param string|null $webhook_url\n     */\n    public function setWebhookUrl(?string $webhook_url): Job\n    {\n        $this->webhook_url = $webhook_url;\n        return $this;\n    }\n\n    /**\n     * @return \\DateTimeImmutable\n     */\n    public function getCreatedAt(): \\DateTimeImmutable\n    {\n        return $this->created_at;\n    }\n\n\n    /**\n     * @return \\DateTimeImmutable|null\n     */\n    public function getStartedAt(): ?\\DateTimeImmutable\n    {\n        return $this->started_at;\n    }\n\n\n    /**\n     * @return \\DateTimeImmutable|null\n     */\n    public function getEndedAt(): ?\\DateTimeImmutable\n    {\n        return $this->ended_at;\n    }\n\n\n    /**\n     * @return string|null\n     */\n    public function getStatus(): ?string\n    {\n        return $this->status;\n    }\n\n    /**\n     * @return TaskCollection[Task]|null\n     */\n    public function getTasks(): ?TaskCollection\n    {\n        return $this->tasks;\n    }\n\n\n    /**\n     * @return object|null\n     */\n    public function getLinks()\n    {\n        return $this->links;\n    }\n\n\n    /**\n     * @param Task $task\n     *\n     * @return Job\n     */\n    public function addTask(Task $task): Job\n    {\n        if (!$this->tasks) {\n            $this->tasks = new TaskCollection();\n        }\n        $this->tasks[] = $task;\n        return $this;\n    }\n\n\n    /*\n     * return array\n     */\n    public function getExportUrls()\n    {\n        $files = [];\n        foreach ($this->getTasks()\n                     ->status(Task::STATUS_FINISHED)\n                     ->operation('export/url') as $exportTask) {\n            $files = array_merge($files, $exportTask->getResult()->files ?? []);\n        }\n        return $files;\n    }\n\n\n    public function getPayload(): array\n    {\n        $tasks = [];\n        foreach ($this->getTasks() ?? [] as $task) {\n            $tasks[$task->getName()] = array_merge(\n                ['operation' => $task->getOperation()],\n                $task->getPayload() ?? []\n            );\n        }\n        return [\n            'tasks'       => $tasks,\n            'tag'         => $this->getTag(),\n            'webhook_url' => $this->getWebhookUrl()\n        ];\n    }\n\n\n}\n"
  },
  {
    "path": "src/Models/JobCollection.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nclass JobCollection extends Collection\n{\n\n\n    /**\n     * Filter job collection by status\n     *\n     * @param $status\n     *\n     * @return JobCollection\n     */\n    public function whereStatus($status): JobCollection\n    {\n\n        return $this->filter(function (Job $job) use ($status) {\n            return $job->getStatus() === $status;\n        });\n\n    }\n\n    /**\n     * Filter job collection by status\n     *\n     * @deprecated Use whereStatus() instead.\n     *\n     * @param $status\n     *\n     * @return JobCollection\n     */\n    public function status($status): JobCollection\n    {\n        return $this->whereStatus($status);\n    }\n\n}\n"
  },
  {
    "path": "src/Models/Task.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nclass Task\n{\n\n    public const STATUS_WATING = 'waiting';\n    public const STATUS_PROCESSING = 'processing';\n    public const STATUS_ERROR = 'error';\n    public const STATUS_FINISHED = 'finished';\n\n    /**\n     * @var string\n     */\n    protected $id;\n\n    /**\n     * @var \\DateTimeImmutable|null\n     */\n    protected $created_at;\n\n    /**\n     * @var \\DateTimeImmutable|null\n     */\n    protected $started_at;\n\n    /**\n     * @var \\DateTimeImmutable|null\n     */\n    protected $ended_at;\n\n    /**\n     * @var string|null\n     */\n    protected $status;\n\n    /**\n     * @var string|null\n     */\n    protected $message;\n\n    /**\n     * @var string|null\n     */\n    protected $code;\n\n    /**\n     * @var object|null\n     */\n    protected $payload;\n\n    /**\n     * @var object|null\n     */\n    protected $result;\n\n    /**\n     * @var TaskCollection[Task]|null\n     */\n    protected $depends_on_tasks;\n\n    /**\n     * @var string|null\n     */\n    protected $job_id;\n\n    /**\n     * @var string\n     */\n    protected $name;\n\n    /**\n     * @var string\n     */\n    protected $operation;\n\n    /**\n     * @var object|null\n     */\n    protected $links;\n\n    /**\n     * Task constructor.\n     *\n     * @param string|null $operation\n     * @param string|null $name\n     */\n    public function __construct(?string $operation = null, ?string $name = null)\n    {\n        $this->operation = $operation;\n        $this->name = $name;\n    }\n\n\n    /**\n     * @return string\n     */\n    public function getId(): string\n    {\n        return $this->id;\n    }\n\n    /**\n     * @return \\DateTimeImmutable|null\n     */\n    public function getCreatedAt(): ?\\DateTimeImmutable\n    {\n        return $this->created_at;\n    }\n\n\n    /**\n     * @return \\DateTimeImmutable|null\n     */\n    public function getStartedAt(): ?\\DateTimeImmutable\n    {\n        return $this->started_at;\n    }\n\n    /**\n     * @return \\DateTimeImmutable|null\n     */\n    public function getEndedAt(): ?\\DateTimeImmutable\n    {\n        return $this->ended_at;\n    }\n\n    /**\n     * @return string\n     */\n    public function getStatus(): ?string\n    {\n        return $this->status;\n    }\n\n    /**\n     * @return string\n     */\n    public function getMessage(): ?string\n    {\n        return $this->message;\n    }\n\n    /**\n     * @return string\n     */\n    public function getCode(): ?string\n    {\n        return $this->code;\n    }\n\n    /**\n     * @return object|null\n     */\n    public function getPayload()\n    {\n        return $this->payload;\n    }\n\n    /**\n     * @return object|null\n     */\n    public function getResult()\n    {\n        return $this->result;\n    }\n\n    /**\n     * @return TaskCollection[Task]|null\n     */\n    public function getDependsOnTasks(): ?TaskCollection\n    {\n        return $this->depends_on_tasks;\n    }\n\n    /**\n     * @return string\n     */\n    public function getJobId(): ?string\n    {\n        return $this->job_id;\n    }\n\n    /**\n     * @return string\n     */\n    public function getOperation(): string\n    {\n        return $this->operation;\n    }\n\n    /**\n     * @return object\n     */\n    public function getLinks()\n    {\n        return $this->links;\n    }\n\n    /**\n     * @return string\n     */\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n\n    /**\n     * Set a task payload parameter\n     *\n     * @param string $parameter\n     * @param        $value\n     *\n     * @return $this\n     */\n    public function set(string $parameter, $value)\n    {\n        if (!$this->payload) {\n            $this->payload = [];\n        }\n        $this->payload[$parameter] = $value;\n        return $this;\n    }\n\n\n}\n"
  },
  {
    "path": "src/Models/TaskCollection.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nclass TaskCollection extends Collection\n{\n\n    /**\n     * Filter task collection by status\n     *\n     * @param $status\n     *\n     * @return TaskCollection\n     */\n    public function whereStatus($status): TaskCollection\n    {\n\n        return $this->filter(function (Task $task) use ($status) {\n            return $task->getStatus() === $status;\n        });\n\n    }\n\n    /**\n     * Filter task collection by status\n     *\n     * @param $status\n     *\n     * @return TaskCollection\n     * @deprecated Use whereStatus() instead\n     *\n     */\n    public function status($status): TaskCollection\n    {\n        return $this->whereStatus($status);\n    }\n\n    /**\n     * Filter task collection by task name\n     *\n     * @param $name\n     *\n     * @return TaskCollection\n     */\n    public function whereName($name): TaskCollection\n    {\n\n        return $this->filter(function (Task $task) use ($name) {\n            return $task->getName() === $name;\n        });\n\n    }\n\n\n    /**\n     * Filter task collection by task name\n     *\n     * @param $name\n     *\n     * @return TaskCollection\n     * @deprecated Use whereName() instead\n     *\n     */\n    public function name($name): TaskCollection\n    {\n        return $this->whereName($name);\n    }\n\n\n    /**\n     * Filter task collection by operation\n     *\n     * @param $operation\n     *\n     * @return TaskCollection\n     */\n    public function whereOperation($operation): TaskCollection\n    {\n\n        return $this->filter(function (Task $task) use ($operation) {\n            return $task->getOperation() === $operation;\n        });\n\n    }\n\n    /**\n     * Filter task collection by operation\n     *\n     * @param $operation\n     *\n     * @return TaskCollection\n     * @deprecated Use whereOperation() instead\n     *\n     */\n    public function operation($operation): TaskCollection\n    {\n        return $this->whereOperation($operation);\n    }\n\n}\n"
  },
  {
    "path": "src/Models/User.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nclass User\n{\n\n    /**\n     * @var int\n     */\n    protected $id;\n    /**\n     * @var string\n     */\n    protected $username;\n    /**\n     * @var string\n     */\n    protected $email;\n    /**\n     * @var int\n     */\n    protected $credits;\n    /**\n     * @var \\DateTimeImmutable\n     */\n    protected $created_at;\n\n    /**\n     * @var array\n     */\n    protected $links;\n\n    /**\n     * @return int\n     */\n    public function getId(): int\n    {\n        return $this->id;\n    }\n\n\n    /**\n     * @return string\n     */\n    public function getUsername(): string\n    {\n        return $this->username;\n    }\n\n\n    /**\n     * @return string\n     */\n    public function getEmail(): string\n    {\n        return $this->email;\n    }\n\n\n    /**\n     * @return int\n     */\n    public function getCredits(): int\n    {\n        return $this->credits;\n    }\n\n\n    /**\n     * @return \\DateTimeImmutable\n     */\n    public function getCreatedAt(): \\DateTimeImmutable\n    {\n        return $this->created_at;\n    }\n\n\n    /**\n     * @return array\n     */\n    public function getLinks(): array\n    {\n        return $this->links;\n    }\n\n\n}\n"
  },
  {
    "path": "src/Models/WebhookEvent.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Models;\n\n\nclass WebhookEvent\n{\n\n    public const EVENT_JOB_FINISHED = 'job.finished';\n    public const EVENT_JOB_FAILED = 'job.failed';\n    public const EVENT_JOB_CREATED = 'job.created';\n\n\n    /**\n     * @var string\n     */\n    protected $event;\n\n    /**\n     * @var Job|null\n     */\n    protected $job;\n\n    /**\n     * @return string\n     */\n    public function getEvent(): string\n    {\n        return $this->event;\n    }\n\n    /**\n     * @return Job|null\n     */\n    public function getJob(): ?Job\n    {\n        return $this->job;\n    }\n\n\n}\n"
  },
  {
    "path": "src/Resources/AbstractResource.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Resources;\n\n\nuse CloudConvert\\Hydrator\\HydratorInterface;\nuse CloudConvert\\Transport\\HttpTransport;\n\nabstract class AbstractResource\n{\n\n    /**\n     * @var HttpTransport\n     */\n    protected $httpTransport;\n    /**\n     * @var HydratorInterface\n     */\n    protected $hydrator;\n\n    /**\n     * AbstractResource constructor.\n     *\n     * @param HttpTransport     $httpTransport\n     * @param HydratorInterface $hydrator\n     */\n    public function __construct(HttpTransport $httpTransport, HydratorInterface $hydrator)\n    {\n        $this->httpTransport = $httpTransport;\n        $this->hydrator = $hydrator;\n    }\n\n\n}\n"
  },
  {
    "path": "src/Resources/JobsResource.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Resources;\n\n\nuse CloudConvert\\Models\\Job;\nuse CloudConvert\\Models\\JobCollection;\n\nclass JobsResource extends AbstractResource\n{\n\n    /**\n     * @param string     $id\n     *\n     * @param array|null $query\n     *\n     * @return Job\n     */\n    public function get(string $id, $query = null): Job\n    {\n        $response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/jobs/' . $id, $query ?? []);\n        return $this->hydrator->createObjectByResponse(Job::class, $response);\n\n\n    }\n\n    /**\n     * @param array|null $query\n     *\n     * @return JobCollection\n     */\n    public function all($query = null): JobCollection\n    {\n        $response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/jobs', $query ?? []);\n        return $this->hydrator->hydrateArrayByResponse(new JobCollection(), Job::class, $response);\n    }\n\n\n    /**\n     * @param Job $job\n     *\n     * @return Job\n     */\n    public function create(Job $job): Job\n    {\n        $response = $this->httpTransport->post($this->httpTransport->getBaseUri() . '/jobs', $job->getPayload());\n        return $this->hydrator->hydrateObjectByResponse($job, $response);\n    }\n\n\n    /**\n     * @param Job        $job\n     *\n     * @param array|null $query\n     *\n     * @return Job\n     */\n    public function refresh(Job $job, $query = null): Job\n    {\n        $response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/jobs/' . $job->getId(),\n            $query ?? []);\n        return $this->hydrator->hydrateObjectByResponse($job, $response);\n    }\n\n    /**\n     * @param Job $job\n     *\n     * @return Job\n     */\n    public function wait(Job $job): Job\n    {\n        $response = $this->httpTransport->get($this->httpTransport->getSyncBaseUri() . '/jobs/' . $job->getId());\n        return $this->hydrator->hydrateObjectByResponse($job, $response);\n    }\n\n    /**\n     * @param Job $job\n     */\n    public function delete(Job $job): void\n    {\n        $this->httpTransport->delete($this->httpTransport->getBaseUri() . '/jobs/' . $job->getId());\n    }\n\n\n}\n"
  },
  {
    "path": "src/Resources/TasksResource.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Resources;\n\n\nuse CloudConvert\\Models\\ImportUploadTask;\nuse CloudConvert\\Models\\ImportUrlTask;\nuse CloudConvert\\Models\\Task;\nuse CloudConvert\\Models\\TaskCollection;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\StreamInterface;\n\nclass TasksResource extends AbstractResource\n{\n\n    /**\n     * @param string     $id\n     * @param array|null $query\n     *\n     * @return Task\n     */\n    public function get(string $id, $query = null): Task\n    {\n\n        $response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/tasks/' . $id, $query ?? []);\n        return $this->hydrator->createObjectByResponse(Task::class, $response);\n\n    }\n\n\n    /**\n     * @param array|null $query\n     *\n     * @return TaskCollection\n     */\n    public function all($query = null): TaskCollection\n    {\n        $response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/tasks', $query ?? []);\n        return $this->hydrator->hydrateArrayByResponse(new TaskCollection(), Task::class, $response);\n    }\n\n\n    /**\n     * @param Task $task\n     *\n     * @return Task\n     */\n    public function create(Task $task): Task\n    {\n        $response = $this->httpTransport->post($this->httpTransport->getBaseUri() . '/' . $task->getOperation(),\n            array_merge(\n                ['name' => $task->getName()],\n                $task->getPayload() ?? []\n            )\n        );\n        return $this->hydrator->hydrateObjectByResponse($task, $response);\n    }\n\n\n    /**\n     * @param Task       $task\n     * @param array|null $query\n     *\n     * @return Task\n     */\n    public function refresh(Task $task, $query = null): Task\n    {\n        $response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/tasks/' . $task->getId(),\n            $query ?? []);\n        return $this->hydrator->hydrateObjectByResponse($task, $response);\n    }\n\n    /**\n     * @param Task $task\n     *\n     * @return Task\n     */\n    public function wait(Task $task): Task\n    {\n        $response = $this->httpTransport->get($this->httpTransport->getSyncBaseUri() . '/tasks/' . $task->getId());\n        return $this->hydrator->hydrateObjectByResponse($task, $response);\n    }\n\n    /**\n     * @param Task $task\n     */\n    public function delete(Task $task): void\n    {\n        $this->httpTransport->delete($this->httpTransport->getBaseUri() . '/tasks/' . $task->getId());\n    }\n\n\n    /**\n     * @param Task                            $task\n     * @param string|resource|StreamInterface $file\n     * @param string|null                     $fileName\n     *\n     * @return ResponseInterface\n     */\n    public function upload(Task $task, $file, ?string $fileName = null): ResponseInterface\n    {\n        if ($task->getOperation() !== 'import/upload') {\n            throw new \\BadMethodCallException('The task operation is not import/upload');\n        }\n        if ($task->getStatus() !== Task::STATUS_WATING\n            || !$task->getResult()\n            || !isset($task->getResult()->form)) {\n            throw new \\BadMethodCallException('The task is not ready for uploading');\n        }\n        $form = $task->getResult()->form;\n        return $this->httpTransport->upload($form->url, $file, $fileName, (array)$form->parameters ?? []);\n    }\n\n}\n"
  },
  {
    "path": "src/Resources/UsersResource.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Resources;\n\n\nuse CloudConvert\\Models\\User;\n\nclass UsersResource extends AbstractResource\n{\n\n    /**\n     * @return User\n     * @throws \\Http\\Client\\Exception\n     */\n    public function me(): User\n    {\n\n        $response = $this->httpTransport->get($this->httpTransport->getBaseUri() . '/users/me');\n        return $this->hydrator->createObjectByResponse(User::class, $response);\n\n    }\n\n\n}\n"
  },
  {
    "path": "src/Transport/HttpTransport.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Transport;\n\n\nuse CloudConvert\\CloudConvert;\nuse CloudConvert\\Exceptions\\HttpClientException;\nuse CloudConvert\\Exceptions\\HttpServerException;\nuse Http\\Client\\Common\\Plugin\\HeaderDefaultsPlugin;\nuse Http\\Client\\Common\\Plugin\\RedirectPlugin;\nuse Http\\Client\\Common\\PluginClient;\nuse Http\\Discovery\\Psr17FactoryDiscovery;\nuse Http\\Discovery\\Psr18ClientDiscovery;\nuse Http\\Message\\MultipartStream\\MultipartStreamBuilder;\nuse Psr\\Http\\Message\\RequestFactoryInterface;\nuse Psr\\Http\\Message\\RequestInterface;\nuse Psr\\Http\\Message\\ResponseInterface;\nuse Psr\\Http\\Message\\StreamFactoryInterface;\nuse Psr\\Http\\Message\\StreamInterface;\nuse Psr\\Http\\Message\\UriFactoryInterface;\n\n\nclass HttpTransport\n{\n\n    protected $options;\n    protected $httpClient;\n\n\n    /**\n     * HttpTransport constructor.\n     *\n     * @param $options\n     */\n    public function __construct($options)\n    {\n        $this->options = $options;\n        $this->httpClient = $this->createHttpClientInstance();\n    }\n\n\n    /**\n     * Creates a new instance of the HTTP client.\n     *\n     * @return PluginClient\n     */\n    protected function createHttpClientInstance(): PluginClient\n    {\n\n        $httpClient = $this->options['http_client'] ?? Psr18ClientDiscovery::find();\n        $httpClientPlugins = [\n            new HeaderDefaultsPlugin([\n                'User-Agent' => 'cloudconvert-php/v' . CloudConvert::VERSION . ' (https://github.com/cloudconvert/cloudconvert-php)',\n            ]),\n            new RedirectPlugin()\n        ];\n\n        return new PluginClient($httpClient, $httpClientPlugins);\n    }\n\n\n    /**\n     * @return string\n     */\n    public function getBaseUri(): string\n    {\n        if ($this->options['sandbox']) {\n            return 'https://api.sandbox.cloudconvert.com/v2';\n        }\n        return isset($this->options['region']) ? 'https://' . $this->options['region'] . '.api.cloudconvert.com/v2' : 'https://api.cloudconvert.com/v2';\n    }\n\n    /**\n     * @return string\n     */\n    public function getSyncBaseUri(): string\n    {\n        if ($this->options['sandbox']) {\n            return 'https://sync.api.sandbox.cloudconvert.com/v2';\n        }\n        return isset($this->options['region']) ? 'https://' . $this->options['region'] . '.sync.api.cloudconvert.com/v2' : 'https://sync.api.cloudconvert.com/v2';\n    }\n\n    /**\n     * @return PluginClient\n     */\n    public function getHttpClient(): PluginClient\n    {\n        return $this->httpClient;\n    }\n\n    /**\n     * @return RequestFactoryInterface\n     */\n    public function getRequestFactory(): RequestFactoryInterface\n    {\n        return $this->options['request_factory'] ?? Psr17FactoryDiscovery::findRequestFactory();\n    }\n\n    /**\n     * @return UriFactoryInterface\n     */\n    public function getUriFactory(): UriFactoryInterface\n    {\n        return $this->options['uri_factory'] ?? Psr17FactoryDiscovery::findUriFactory();\n    }\n\n    /**\n     * @return StreamFactoryInterface\n     */\n    public function getStreamFactory(): StreamFactoryInterface\n    {\n        return $this->options['stream_factory'] ?? Psr17FactoryDiscovery::findStreamFactory();\n    }\n\n    /**\n     * @param       $path\n     * @param array $query\n     *\n     * @return ResponseInterface\n     * @throws \\CloudConvert\\Exceptions\\Exception\n     */\n    public function get(string $path, array $query = []): ResponseInterface\n    {\n        if (count($query) > 0) {\n            $path .= '?' . http_build_query($query);\n        }\n\n\n        return $this->sendRequest($this->getRequestFactory()->createRequest('GET', $path, [\n            'accept-encoding' => 'application/json'\n        ]));\n    }\n\n\n    /**\n     * @param string $url\n     *\n     * @return StreamInterface\n     */\n    public function download(string $url)\n    {\n        return $this->sendRequest($this->getRequestFactory()->createRequest('GET', $url), false)->getBody();\n    }\n\n\n    /**\n     * @param array<string, mixed>|string $body\n     */\n    protected function buildBody($body): StreamInterface\n    {\n        $stringBody = is_array($body) ? json_encode($body, JSON_THROW_ON_ERROR) : $body;\n\n        return $this->getStreamFactory()->createStream($stringBody);\n    }\n\n    /**\n     * @param $path\n     * @param $body\n     *\n     * @return ResponseInterface\n     */\n    public function post(string $path, array $body): ResponseInterface\n    {\n        return $this->sendRequest(\n            $this->getRequestFactory()->createRequest('POST', $path)\n                ->withHeader('content-type', 'application/json')\n                ->withHeader('accept-encoding', 'application/json')\n                ->withBody($this->buildBody($body))\n        );\n    }\n\n    /**\n     * @param $path\n     * @param $body\n     *\n     * @return ResponseInterface\n     */\n    public function put(string $path, array $body): ResponseInterface\n    {\n        return $this->sendRequest(\n            $this->getRequestFactory()->createRequest('POST', $path)\n                ->withHeader('content-type', 'application/json')\n                ->withHeader('accept-encoding', 'application/json')\n                ->withBody($this->buildBody($body))\n        );\n    }\n\n    /**\n     * @param $path\n     *\n     * @return ResponseInterface\n     */\n    public function delete(string $path): ResponseInterface\n    {\n        return $this->sendRequest($this->getRequestFactory()->createRequest('DELETE', $path, [\n            'accept-encoding' => 'application/json'\n        ]));\n    }\n\n    /**\n     * @param                                 $path\n     * @param string|resource|StreamInterface $file\n     * @param string|null                     $fileName\n     * @param array                           $additionalParameters\n     *\n     * @return ResponseInterface\n     */\n    public function upload($path, $file, ?string $fileName = null, array $additionalParameters = []): ResponseInterface\n    {\n        $builder = new MultipartStreamBuilder($this->getStreamFactory());\n        foreach ($additionalParameters as $parameter => $value) {\n            $builder->addResource($parameter, strval($value));\n        }\n\n        $resourceOptions = [];\n        if ($fileName !== null) {\n            $resourceOptions['filename'] = $fileName;\n        }\n        $builder->addResource('file', $file, $resourceOptions);\n\n        $multipartStream = $builder->build();\n        $boundary = $builder->getBoundary();\n\n        $request = $this->getRequestFactory()->createRequest(\n            'POST',\n            $path\n        )\n            ->withHeader('Content-Type', 'multipart/form-data; boundary=\"' . $boundary . '\"')\n            ->withBody($multipartStream);\n\n        return $this->sendRequest($request, false);\n    }\n\n    /**\n     * @param RequestInterface $request\n     *\n     * @param bool             $authenticate\n     *\n     * @return ResponseInterface\n     * @throws \\Exception\n     */\n    protected function sendRequest(RequestInterface $request, $authenticate = true)\n    {\n\n        try {\n            if ($authenticate) {\n                $request = $request->withHeader('Authorization', 'Bearer ' . $this->options['api_key']);\n            }\n            $response = $this->getHttpClient()->sendRequest($request);\n        } catch (\\Http\\Client\\Exception $exception) {\n            throw HttpServerException::networkError($exception);\n        }\n\n        if (!in_array($response->getStatusCode(), [200, 201, 204])) {\n            $this->handleErrors($response);\n        }\n\n        return $response;\n\n    }\n\n\n    /**\n     * Throw the correct exception for this error.\n     *\n     * @throws \\CloudConvert\\Exceptions\\Exception\n     */\n    protected function handleErrors(ResponseInterface $response)\n    {\n        $statusCode = $response->getStatusCode();\n        switch ($statusCode) {\n            case 400:\n                throw HttpClientException::badRequest($response);\n            case 401:\n                throw HttpClientException::unauthorized($response);\n            case 402:\n                throw HttpClientException::paymentRequired($response);\n            case 403:\n                throw HttpClientException::forbidden($response);\n            case 404:\n                throw HttpClientException::notFound($response);\n            case 422:\n                throw HttpClientException::unprocessable($response);\n            case 500 <= $statusCode:\n                throw HttpServerException::serverError($statusCode);\n            default:\n                throw HttpServerException::unknownHttpResponseCode($statusCode);\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "tests/Integration/JobTest.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Tests\\Integration;\n\n\nuse CloudConvert\\Models\\ConvertTask;\nuse CloudConvert\\Models\\ExportUrlTask;\nuse CloudConvert\\Models\\ImportUploadTask;\nuse CloudConvert\\Models\\ImportUrlTask;\nuse CloudConvert\\Models\\Job;\nuse CloudConvert\\Models\\Task;\n\nclass JobTest extends TestCase\n{\n\n    public function testCreateJob()\n    {\n\n        $job = (new Job())\n            ->setTag('integration-test-create-job')\n            ->addTask(\n                (new Task('import/url', 'import-it'))\n                    ->set('url', 'http://invalid.url')\n                    ->set('filename', 'test.file')\n            )\n            ->addTask(\n                (new Task('convert', 'convert-it'))\n                    ->set('input', ['import-it'])\n                    ->set('output_format', 'pdf')\n            );\n\n        $this->cloudConvert->jobs()->create($job);\n\n        $this->assertNotNull($job->getId());\n        $this->assertEquals('integration-test-create-job', $job->getTag());\n        $this->assertNotNull($job->getCreatedAt());\n        $this->assertCount(2, $job->getTasks());\n\n        $task1 = $job->getTasks()->whereOperation('convert')[0];\n        $task2 = $job->getTasks()->whereOperation('import/url')[0];\n\n        $this->assertEquals('convert-it', $task1->getName());\n\n        $this->assertEquals('import-it', $task2->getName());\n\n        $this->cloudConvert->jobs()->delete($job);\n\n    }\n\n\n    public function testUploadAndDownloadFiles()\n    {\n\n        $job = (new Job())\n            ->setTag('integration-test-upload-download')\n            ->addTask(\n                new Task('import/upload', 'import-it')\n            )\n            ->addTask(\n                (new Task('export/url', 'export-it'))\n                    ->set('input', ['import-it'])\n            );\n\n        $this->cloudConvert->jobs()->create($job);\n\n        $uploadTask = $job->getTasks()->whereName('import-it')[0];\n\n        $this->cloudConvert->tasks()->upload($uploadTask, fopen(__DIR__ . '/files/input.pdf', 'r'));\n\n        $this->cloudConvert->jobs()->wait($job);\n        $this->assertEquals(Job::STATUS_FINISHED, $job->getStatus());\n\n        $exportTask = $job->getTasks()->whereStatus(Task::STATUS_FINISHED)->whereName('export-it')[0];\n\n        $this->assertNotNull($exportTask->getResult());\n\n        $file = $job->getExportUrls()[0];\n\n        $this->assertNotEmpty($file->url);\n\n        $source = $this->cloudConvert->getHttpTransport()->download($file->url)->detach();\n\n        $dest = tmpfile();\n        $destPath = stream_get_meta_data($dest)['uri'];\n\n        stream_copy_to_stream($source, $dest);\n\n\n        $this->assertEquals(filesize($destPath), 172570);\n\n        $this->cloudConvert->jobs()->delete($job);\n\n\n    }\n\n\n}\n"
  },
  {
    "path": "tests/Integration/TaskTest.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Tests\\Integration;\n\n\nuse CloudConvert\\Models\\ImportUploadTask;\nuse CloudConvert\\Models\\ImportUrlTask;\nuse CloudConvert\\Models\\Task;\n\nclass TaskTest extends TestCase\n{\n\n    public function testCreateImportUrlTask()\n    {\n\n        $task = (new Task('import/url', 'url-test'))\n            ->set('url', 'http://invalid.url')\n            ->set('filename', 'test.file');\n\n        $this->cloudConvert->tasks()->create($task);\n\n        $this->assertNotNull($task->getId());\n        $this->assertNotNull($task->getCreatedAt());\n        $this->assertEquals('import/url', $task->getOperation());\n        $this->assertEquals([\n            'url'      => 'http://invalid.url',\n            'filename' => 'test.file'\n        ], (array)$task->getPayload());\n        $this->assertEquals(Task::STATUS_WATING, $task->getStatus());\n\n        $this->cloudConvert->tasks()->delete($task);\n\n\n    }\n\n\n    public function testUploadFile()\n    {\n\n        $task = (new Task('import/upload', 'upload-test'));\n\n        $this->cloudConvert->tasks()->create($task);\n\n        $response = $this->cloudConvert->tasks()->upload($task, fopen(__DIR__ . '/files/input.pdf', 'r'));\n\n        $this->assertEquals(201, $response->getStatusCode());\n\n        $this->cloudConvert->tasks()->wait($task);\n        $this->assertEquals(Task::STATUS_FINISHED, $task->getStatus());\n\n        $this->assertEquals('input.pdf', $task->getResult()->files[0]->filename);\n\n        $this->cloudConvert->tasks()->delete($task);\n\n\n    }\n\n\n}\n"
  },
  {
    "path": "tests/Integration/TestCase.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Tests\\Integration;\n\nuse CloudConvert\\CloudConvert;\nuse Http\\Mock\\Client;\n\nabstract class TestCase extends \\PHPUnit\\Framework\\TestCase\n{\n\n    /**\n     * @var CloudConvert\n     */\n    protected $cloudConvert;\n\n\n    public function setUp(): void\n    {\n\n        $this->cloudConvert = new CloudConvert([\n            'sandbox' => true,\n            'api_key' => getenv('CLOUDCONVERT_API_KEY')\n        ]);\n\n        parent::setUp();\n    }\n\n\n}\n"
  },
  {
    "path": "tests/Integration/UserTest.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Tests\\Integration;\n\n\nuse CloudConvert\\Models\\User;\n\n\nclass UserTest extends TestCase\n{\n\n    public function testMe()\n    {\n\n        $user = $this->cloudConvert->users()->me();\n\n        $this->assertInstanceOf(User::class, $user);\n        $this->assertNotNull($user->getId());\n        $this->assertNotNull($user->getUsername());\n        $this->assertNotNull($user->getEmail());\n        $this->assertNotNull($user->getCredits());\n\n\n    }\n\n}\n"
  },
  {
    "path": "tests/Unit/ExceptionsTest.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\n\nuse CloudConvert\\Exceptions\\HttpClientException;\nuse CloudConvert\\Exceptions\\HttpServerException;\nuse CloudConvert\\Models\\Job;\nuse GuzzleHttp\\Psr7\\Response;\nuse Http\\Message\\RequestMatcher\\RequestMatcher;\n\nclass ExceptionsTest extends TestCase\n{\n\n    public function testUnauthorized()\n    {\n\n\n        $response = new Response(401, [\n            'Content-Type' => 'application/json'\n        ], '{\n                        \"message\": \"Message!\"\n                  }');\n\n        $this->getMockClient()->on(new RequestMatcher('/v2/users/me', null, 'GET'), $response);\n\n\n        $this->expectException(HttpClientException::class);\n        $this->expectExceptionMessageRegExp(\"/Message!/\");\n\n        $this->cloudConvert->users()->me();\n\n    }\n\n\n    public function testValidationErrors()\n    {\n\n\n        $response = new Response(400, [\n            'Content-Type' => 'application/json'\n        ], file_get_contents(__DIR__ . '/responses/error400.json'));\n\n        $this->getMockClient()->on(new RequestMatcher('/v2/jobs', null, 'POST'), $response);\n\n\n        $this->expectException(HttpClientException::class);\n        $this->expectExceptionMessageRegExp(\"/The given data was invalid/\");\n        $this->expectExceptionMessageRegExp(\"/Cannot change status: task already completed/\");\n\n        $this->cloudConvert->jobs()->create(new Job());\n\n    }\n\n\n    public function test503()\n    {\n\n\n        $response = new Response(503, [\n            'Content-Type' => 'text/html'\n        ], 'error!');\n\n        $this->getMockClient()->on(new RequestMatcher('/v2/users/me', null, 'GET'), $response);\n\n\n        $this->expectException(HttpServerException::class);\n\n        $this->cloudConvert->users()->me();\n\n    }\n\n\n    public function testsCreditsExceeded()\n    {\n\n\n        $response = new Response(402, [\n            'Content-Type' => 'application/json'\n        ], file_get_contents(__DIR__ . '/responses/error402.json'));\n\n        $this->getMockClient()->on(new RequestMatcher('/v2/jobs', null, 'POST'), $response);\n\n        try {\n\n            $this->cloudConvert->jobs()->create(new Job());\n\n        } catch (HttpClientException $exception) {\n\n            $this->assertEquals('CREDITS_EXCEEDED', $exception->getErrorCode());\n\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "tests/Unit/JobResourceTest.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\n\n\nuse CloudConvert\\Models\\Job;\nuse CloudConvert\\Models\\Task;\n\n\nuse GuzzleHttp\\Psr7\\Response;\nuse Http\\Message\\RequestMatcher\\CallbackRequestMatcher;\nuse Http\\Message\\RequestMatcher\\RequestMatcher;\nuse Psr\\Http\\Message\\RequestInterface;\n\nclass JobResourceTest extends TestCase\n{\n\n    public function testGet()\n    {\n\n\n        $response = new Response(200, [\n            'Content-Type' => 'application/json'\n        ], file_get_contents(__DIR__ . '/responses/job.json'));\n\n\n        $this->getMockClient()->on(new RequestMatcher('/v2/jobs/cd82535b-0614-4b23-bbba-b24ab0e892f7', null, 'GET'),\n            $response);\n\n\n        $job = $this->cloudConvert->jobs()->get('cd82535b-0614-4b23-bbba-b24ab0e892f7');\n\n        $this->assertInstanceOf(Job::class, $job);\n        $this->assertEquals('cd82535b-0614-4b23-bbba-b24ab0e892f7', $job->getId());\n        $this->assertEquals('test-1234', $job->getTag());\n        $this->assertEquals(Job::STATUS_ERROR, $job->getStatus());\n        $this->assertInstanceOf(\\DateTimeImmutable::class, $job->getCreatedAt());\n        $this->assertInstanceOf(\\DateTimeImmutable::class, $job->getEndedAt());\n        $this->assertInstanceOf(\\DateTimeImmutable::class, $job->getStartedAt());\n        $this->assertCount(3, $job->getTasks());\n        $this->assertInstanceOf(Task::class, $job->getTasks()[0]);\n        $this->assertEquals('4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b', $job->getTasks()[0]->getId());\n\n\n    }\n\n    public function testAll()\n    {\n\n\n        $response = new Response(200, [\n            'Content-Type' => 'application/json'\n        ], file_get_contents(__DIR__ . '/responses/jobs.json'));\n\n\n        $this->getMockClient()->on(new RequestMatcher('/v2/jobs', null, 'GET'),\n            $response);\n\n\n        $jobs = $this->cloudConvert->jobs()->all();\n\n        $this->assertCount(1, $jobs);\n        $this->assertInstanceOf(Job::class, $jobs[0]);\n        $this->assertEquals('bd7d06b4-60fb-472b-b3a3-9034b273df07', $jobs[0]->getId());\n\n\n    }\n\n\n    public function testCreateJob()\n    {\n\n        $job = (new Job())\n            ->addTask(\n                (new Task('import/url', 'import-it'))\n                    ->set('url', 'http://invalid.url')\n                    ->set('filename', 'test.file')\n            )\n            ->addTask(\n                (new Task('convert', 'convert-it'))\n                    ->set('input', 'import-it')\n                    ->set('output_format', 'pdf')\n            );\n\n\n        $response = new Response(201, [\n            'Content-Type' => 'application/json'\n        ], file_get_contents(__DIR__ . '/responses/job_created.json'));\n\n\n        $this->getMockClient()->on(\n            new CallbackRequestMatcher(function (RequestInterface $request) {\n                if (strpos($request->getUri(), '/jobs') === false) {\n                    return false;\n                }\n                $body = json_decode($request->getBody(), true);\n                if (!isset($body['tasks']['import-it'])\n                    || !isset($body['tasks']['convert-it'])\n                    || $body['tasks']['import-it']['operation'] !== 'import/url'\n                    || $body['tasks']['import-it']['url'] !== 'http://invalid.url'\n                ) {\n                    return false;\n                }\n                return true;\n\n            }), $response);\n\n\n        $this->cloudConvert->jobs()->create($job);\n\n        $this->assertNotNull($job->getId());\n        $this->assertNotNull($job->getCreatedAt());\n        $this->assertCount(2, $job->getTasks());\n\n        $task1 = $job->getTasks()[0];\n        $task2 = $job->getTasks()[1];\n\n        $this->assertEquals('import/url', $task1->getOperation());\n        $this->assertEquals('import-it', $task1->getName());\n        $this->assertEquals([\n            'operation' => 'import/url',\n            'url'       => 'http://invalid.url',\n            'filename'  => 'test.file'\n        ], (array)$task1->getPayload());\n\n        $this->assertEquals('convert', $task2->getOperation());\n        $this->assertEquals('convert-it', $task2->getName());\n        $this->assertEquals([\n            'operation'     => 'convert',\n            'input'         => ['import-it'],\n            'output_format' => 'pdf',\n        ], (array)$task2->getPayload());\n\n\n    }\n\n\n    public function testGetExportUrls()\n    {\n\n\n        $response = new Response(200, [\n            'Content-Type' => 'application/json'\n        ], file_get_contents(__DIR__ . '/responses/job_export_urls.json'));\n\n\n        $this->getMockClient()->on(new RequestMatcher('/v2/jobs/cd82535b-0614-4b23-bbba-b24ab0e892f7', null, 'GET'),\n            $response);\n\n\n        $job = $this->cloudConvert->jobs()->get('cd82535b-0614-4b23-bbba-b24ab0e892f7');\n\n        $this->assertInstanceOf(Job::class, $job);\n\n        $urls = $job->getExportUrls();\n\n\n        $this->assertCount(2, $urls);\n\n        $this->assertEquals('file.mp4', $urls[0]->filename);\n        $this->assertEquals('https://storage.cloudconvert.com/file.mp4', $urls[0]->url);\n\n        $this->assertEquals('file2.mp4', $urls[1]->filename);\n\n    }\n\n\n}\n"
  },
  {
    "path": "tests/Unit/SignedUrlBuilderTest.php",
    "content": "<?php\n\nnamespace CloudConvert\\Tests\\Unit;\n\nuse CloudConvert\\Models\\Job;\nuse CloudConvert\\Models\\Task;\n\nclass SignedUrlBuilderTest extends TestCase\n{\n\n    public function testCreateSignedUrl() {\n\n\n        $job = (new Job())\n            ->addTask(\n                (new Task('import/url', 'import-it'))\n                    ->set('url', 'https://some.url')\n                    ->set('filename', 'test.png')\n            )\n            ->addTask(\n                (new Task('export/url', 'export-it'))\n                    ->set('input', 'import-it')\n                    ->set('inline', true)\n            );\n\n\n        $url =  $this->cloudConvert->signedUrlBuilder()->createFromJob('https://s.cloudconvert.com/b3d85428-584e-4639-bc11-76b7dee9c109', 'NT8dpJkttEyfSk3qlRgUJtvTkx64vhyX', $job, 'mykey');\n\n        $this->assertStringStartsWith('https://s.cloudconvert.com/', $url);\n        $this->assertStringContainsString('?job=', $url);\n        $this->assertStringContainsString('&cache_key=mykey', $url);\n        $this->assertStringContainsString('&s=fb2760b572f652316d2ca218cec980024057ff108a472425c9fcc6136709cbe8', $url);\n\n\n    }\n\n}"
  },
  {
    "path": "tests/Unit/TaskCollectionTest.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\n\nuse CloudConvert\\Models\\Task;\nuse CloudConvert\\Models\\TaskCollection;\n\nclass TaskCollectionTest extends TestCase\n{\n\n\n    public function testFilterByStatus() {\n\n\n        $task1 = new Task('import/url', 'test');\n        $reflection = new \\ReflectionClass($task1);\n        $property = $reflection->getProperty('status');\n        $property->setAccessible(true);\n        $property->setValue($task1, Task::STATUS_FINISHED);\n\n        $task2 = new Task('import/url', 'test');\n        $reflection = new \\ReflectionClass($task1);\n        $property = $reflection->getProperty('status');\n        $property->setAccessible(true);\n        $property->setValue($task2, Task::STATUS_ERROR);\n\n        $task3 = new Task('import/url', 'test');\n        $reflection = new \\ReflectionClass($task1);\n        $property = $reflection->getProperty('status');\n        $property->setAccessible(true);\n        $property->setValue($task3, Task::STATUS_WATING);\n\n        $collection = new TaskCollection([$task1, $task2, $task3]);\n\n\n        $filtered = $collection->status(Task::STATUS_ERROR);\n\n        $this->assertCount(1, $filtered);\n\n        $this->assertCount(3, $collection); // original collection not modified\n\n        $this->assertEquals($filtered[0], $task2);\n\n\n\n\n    }\n\n\n}\n"
  },
  {
    "path": "tests/Unit/TaskResourceTest.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\n\nuse CloudConvert\\Models\\ExportUrlTask;\nuse CloudConvert\\Models\\ImportUploadTask;\nuse CloudConvert\\Models\\ImportUrlTask;\nuse CloudConvert\\Models\\Task;\n\n\nuse GuzzleHttp\\Psr7\\Response;\nuse Http\\Message\\RequestMatcher\\CallbackRequestMatcher;\nuse Http\\Message\\RequestMatcher\\RequestMatcher;\nuse Psr\\Http\\Message\\RequestInterface;\n\nclass TaskResourceTest extends TestCase\n{\n\n    public function testGet()\n    {\n\n\n        $response = new Response(200, [\n            'Content-Type' => 'application/json'\n        ], file_get_contents(__DIR__ . '/responses/task.json'));\n\n\n        $this->getMockClient()->on(new RequestMatcher('/v2/tasks/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b', null, 'GET'),\n            $response);\n\n\n        $task = $this->cloudConvert->tasks()->get('4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b');\n\n        $this->assertInstanceOf(Task::class, $task);\n        $this->assertEquals('4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b', $task->getId());\n        $this->assertEquals('export-1', $task->getName());\n        $this->assertEquals(Task::STATUS_ERROR, $task->getStatus());\n        $this->assertEquals('INPUT_TASK_FAILED', $task->getCode());\n        $this->assertEquals('Input task has failed', $task->getMessage());\n        $this->assertInstanceOf(\\DateTimeImmutable::class, $task->getCreatedAt());\n        $this->assertInstanceOf(\\DateTimeImmutable::class, $task->getEndedAt());\n        $this->assertNull($task->getStartedAt());\n        $this->assertCount(1, $task->getDependsOnTasks());\n        $this->assertInstanceOf(Task::class, $task->getDependsOnTasks()[0]);\n        $this->assertEquals('6df0920a-7042-4e87-be52-f38a0a29a67e', $task->getDependsOnTasks()[0]->getId());\n\n\n    }\n\n\n    public function testAll()\n    {\n\n\n        $response = new Response(200, [\n            'Content-Type' => 'application/json'\n        ], file_get_contents(__DIR__ . '/responses/tasks.json'));\n\n\n        $this->getMockClient()->on(new RequestMatcher('/v2/tasks', null, 'GET'),\n            $response);\n\n\n        $tasks = $this->cloudConvert->tasks()->all();\n\n        $this->assertCount(1, $tasks);\n        $this->assertInstanceOf(Task::class, $tasks[0]);\n        $this->assertEquals('4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b', $tasks[0]->getId());\n\n    }\n\n\n    public function testCreateImportUrlTask()\n    {\n\n        $task = (new Task('import/url', 'test'))\n            ->set('url', 'http://invalid.url')\n            ->set('filename', 'test.file');\n\n\n        $response = new Response(201, [\n            'Content-Type' => 'application/json'\n        ], file_get_contents(__DIR__ . '/responses/task_created.json'));\n\n\n        $this->getMockClient()->on(\n            new CallbackRequestMatcher(function (RequestInterface $request) {\n                if (strpos($request->getUri(), '/import/url') === false) {\n                    return false;\n                }\n                $body = json_decode($request->getBody(), true);\n                if ($body['name'] !== 'test'\n                    || $body['url'] !== 'http://invalid.url'\n                    || $body['filename'] !== 'test.file') {\n                    return false;\n                }\n                return true;\n\n            }), $response);\n\n\n        $this->cloudConvert->tasks()->create($task);\n\n        $this->assertNotNull($task->getId());\n        $this->assertNotNull($task->getCreatedAt());\n        $this->assertEquals([\n            'name'     => 'test',\n            'url'      => 'http://invalid.url',\n            'filename' => 'test.file'\n        ], (array)$task->getPayload());\n        $this->assertEquals(Task::STATUS_WATING, $task->getStatus());\n\n\n    }\n\n\n    public function testRefresh()\n    {\n\n        $task = new Task('import/url', 'test');\n\n        $reflection = new \\ReflectionClass($task);\n        $property = $reflection->getProperty('id');\n        $property->setAccessible(true);\n        $property->setValue($task, '4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b');\n\n\n        $this->assertNull($task->getCreatedAt());\n\n        $response = new Response(200, [\n            'Content-Type' => 'application/json'\n        ], file_get_contents(__DIR__ . '/responses/task.json'));\n\n\n        $this->getMockClient()->on(new RequestMatcher('/v2/tasks/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b', null, 'GET'),\n            $response);\n\n\n        $this->cloudConvert->tasks()->refresh($task);\n\n\n        $this->assertNotNull($task->getCreatedAt());\n        $this->assertEquals(Task::STATUS_ERROR, $task->getStatus());\n        $this->assertEquals('Input task has failed', $task->getMessage());\n\n    }\n\n\n    public function testUploadFile()\n    {\n\n        $task = new Task('import/upload', 'upload-test');\n\n        $response = new Response(200, [\n            'Content-Type' => 'application/json'\n        ], file_get_contents(__DIR__ . '/responses/upload_task_created.json'));\n\n\n        $this->getMockClient()->on(new RequestMatcher('/v2/import/upload', null, 'POST'),\n            $response);\n\n\n        $this->cloudConvert->tasks()->create($task);\n\n\n        $this->getMockClient()->on(\n            new CallbackRequestMatcher(function (RequestInterface $request) use ($task) {\n                if ((string)$request->getUri() !== $task->getResult()->form->url) {\n                    return false;\n                }\n\n                $body = (string)$request->getBody();\n\n                foreach ((array)$task->getResult()->form->parameters as $parameter => $value) {\n                    $this->assertStringContainsString('name=\"' . $parameter . '\"', $body);\n                    $this->assertStringContainsString((string)$value, $body);\n                }\n\n                return true;\n\n            }), new Response(201, [], \"\"));\n\n\n        $response = $this->cloudConvert->tasks()->upload($task, \"filecontent\");\n\n        $this->assertEquals(201, $response->getStatusCode());\n\n\n    }\n\n\n}\n"
  },
  {
    "path": "tests/Unit/TestCase.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\nuse CloudConvert\\CloudConvert;\nuse GuzzleHttp\\Psr7\\Response;\nuse Http\\Mock\\Client;\n\nabstract class TestCase extends \\PHPUnit\\Framework\\TestCase\n{\n\n    /**\n     * @var CloudConvert\n     */\n    protected $cloudConvert;\n\n    /**\n     * @var Client\n     */\n    protected $mockClient;\n\n\n    public function setUp(): void\n    {\n\n        $this->cloudConvert = new CloudConvert([\n            'api_key'     => 'test_api_key',\n            'http_client' => $this->getMockClient()\n        ]);\n\n        parent::setUp();\n    }\n\n    protected function getMockClient(): Client\n    {\n\n        if ($this->mockClient === null) {\n            $this->mockClient = new Client();\n            $this->mockClient->setDefaultResponse(new Response(404, [], ''));\n        }\n\n        return $this->mockClient;\n\n    }\n\n    /**\n     * The method is here to provice BC compatibility with PHPUnit >= 9 where this method was removed.\n     *\n     * @param string $regex\n     */\n    public function expectExceptionMessageRegExp(string $regex): void\n    {\n        if (!method_exists($this, 'expectExceptionMessageMatches')) {\n            parent::expectExceptionMessageRegExp($regex);\n\n            return;\n        }\n\n        $this->expectExceptionMessageMatches($regex);\n    }\n\n}\n"
  },
  {
    "path": "tests/Unit/UsersResourceTest.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\n\nuse CloudConvert\\Models\\User;\n\n\nuse GuzzleHttp\\Psr7\\Response;\nuse Http\\Message\\RequestMatcher\\RequestMatcher;\n\nclass UsersResourceTest extends TestCase\n{\n\n    public function testMe()\n    {\n\n\n        $response = new Response(200, [\n            'Content-Type' => 'application/json'\n        ], file_get_contents(__DIR__ . '/responses/user.json'));\n\n\n        $this->getMockClient()->on(new RequestMatcher('/v2/users/me', null, 'GET'), $response);\n\n\n        $user = $this->cloudConvert->users()->me();\n\n        $this->assertInstanceOf(User::class, $user);\n        $this->assertEquals(1, $user->getId());\n        $this->assertEquals('Username', $user->getUsername());\n        $this->assertEquals('me@example.com', $user->getEmail());\n        $this->assertEquals(4434, $user->getCredits());\n\n\n\n    }\n\n}\n"
  },
  {
    "path": "tests/Unit/WebhookHandlerTest.php",
    "content": "<?php\n\n\nnamespace CloudConvert\\Tests\\Unit;\n\n\nuse CloudConvert\\Exceptions\\SignatureVerificationException;\nuse CloudConvert\\Models\\ExportUrlTask;\nuse CloudConvert\\Models\\Job;\nuse CloudConvert\\Models\\Task;\nuse CloudConvert\\Models\\WebhookEvent;\nuse GuzzleHttp\\Psr7\\Request;\n\nclass WebhookHandlerTest extends TestCase\n{\n\n    public function testInvalidSignature()\n    {\n\n        $request = new Request('POST', '/webhook', [\n            'CloudConvert-Signature' => 'invalid'\n        ], file_get_contents(__DIR__ . '/requests/webhook_job_finished_payload.json'));\n\n        $this->expectException(SignatureVerificationException::class);\n\n        $this->cloudConvert->webhookHandler()->constructEventFromRequest($request, 'secret');\n\n    }\n\n\n    public function testConstructsWebhookEvent()\n    {\n\n        $request = new Request('POST', '/webhook', [\n            'CloudConvert-Signature' => '576b653f726c85265a389532988f483b5c7d7d5f40cede5f5ddf9c3f02934f35'\n        ], file_get_contents(__DIR__ . '/requests/webhook_job_finished_payload.json'));\n\n\n        $webhookEvent = $this->cloudConvert->webhookHandler()->constructEventFromRequest($request, 'secret');\n\n\n        $this->assertInstanceOf(WebhookEvent::class, $webhookEvent);\n        $this->assertEquals(WebhookEvent::EVENT_JOB_FINISHED, $webhookEvent->getEvent());\n        $this->assertInstanceOf(Job::class, $webhookEvent->getJob());\n        $this->assertCount(3, $webhookEvent->getJob()->getTasks());\n        $this->assertInstanceOf(Task::class, $webhookEvent\n            ->getJob()\n            ->getTasks()\n            ->status(Task::STATUS_FINISHED)\n            ->name('export-it')[0]);\n\n\n    }\n\n}\n"
  },
  {
    "path": "tests/Unit/requests/webhook_job_finished_payload.json",
    "content": "{\n  \"event\": \"job.finished\",\n  \"job\": {\n    \"id\": \"c677ccf7-8876-4f48-bb96-0ab8e0d88cd7\",\n    \"status\": \"finished\",\n    \"created_at\": \"2019-06-01T00:35:33+00:00\",\n    \"started_at\": \"2019-06-01T00:35:33+00:00\",\n    \"ended_at\": \"2019-06-01T00:35:33+00:00\",\n    \"tasks\": [\n      {\n        \"id\": \"22b3d686-126b-4fe2-8238-a9781cd023d9\",\n        \"name\": \"import-it\",\n        \"job_id\": \"c677ccf7-8876-4f48-bb96-0ab8e0d88cd7\",\n        \"status\": \"finished\",\n        \"code\": null,\n        \"message\": null,\n        \"percent\": 100,\n        \"operation\": \"import\\/url\",\n        \"payload\": {\n          \"operation\": \"import\\/url\",\n          \"url\": \"http:\\/\\/invalid.url\",\n          \"filename\": \"test.file\"\n        },\n        \"result\": null,\n        \"created_at\": \"2019-06-01T00:35:33+00:00\",\n        \"started_at\": \"2019-06-01T00:35:33+00:00\",\n        \"ended_at\": \"2019-06-01T00:35:33+00:00\",\n        \"retry_of_task_id\": null,\n        \"copy_of_task_id\": null,\n        \"host_name\": \"jena\",\n        \"storage\": null,\n        \"depends_on_task_ids\": [],\n        \"links\": {\n          \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/22b3d686-126b-4fe2-8238-a9781cd023d9\"\n        }\n      },\n      {\n        \"id\": \"15281118-acc3-441f-970d-39a49457d9e5\",\n        \"name\": \"convert-it\",\n        \"job_id\": \"c677ccf7-8876-4f48-bb96-0ab8e0d88cd7\",\n        \"status\": \"finished\",\n        \"code\": null,\n        \"message\": null,\n        \"percent\": 100,\n        \"operation\": \"convert\",\n        \"engine\": null,\n        \"engine_version\": null,\n        \"payload\": {\n          \"operation\": \"convert\",\n          \"input\": [\n            \"import-it\"\n          ],\n          \"output_format\": \"pdf\"\n        },\n        \"result\": null,\n        \"created_at\": \"2019-06-01T00:35:33+00:00\",\n        \"started_at\": \"2019-06-01T00:35:33+00:00\",\n        \"ended_at\": \"2019-06-01T00:35:33+00:00\",\n        \"retry_of_task_id\": null,\n        \"copy_of_task_id\": null,\n        \"host_name\": null,\n        \"storage\": null,\n        \"depends_on_task_ids\": [\n          \"22b3d686-126b-4fe2-8238-a9781cd023d9\"\n        ],\n        \"links\": {\n          \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/15281118-acc3-441f-970d-39a49457d9e5\"\n        }\n      },\n      {\n        \"id\": \"72dc4f5f-7ef0-4f46-9811-29589a541400\",\n        \"name\": \"export-it\",\n        \"job_id\": \"c677ccf7-8876-4f48-bb96-0ab8e0d88cd7\",\n        \"status\": \"finished\",\n        \"code\": null,\n        \"message\": null,\n        \"percent\": 100,\n        \"operation\": \"export/url\",\n        \"engine\": null,\n        \"engine_version\": null,\n        \"payload\": {\n          \"operation\": \"export/url\",\n          \"input\": [\n            \"convert-it\"\n          ]\n        },\n        \"result\": {\n          \"files\": [\n            {\n              \"filename\": \"file.pdf\",\n              \"url\": \"https://storage.cloudconvert.com/eed87242-577e-4e3e-8178-9edbe51975dd/file.pdf?temp_url_sig=79c2db4d884926bbcc5476d01b4922a19137aee9&temp_url_expires=1545962104\"\n            }\n          ]\n        },\n        \"created_at\": \"2019-06-01T00:35:33+00:00\",\n        \"started_at\": \"2019-06-01T00:35:33+00:00\",\n        \"ended_at\": \"2019-06-01T00:35:33+00:00\",\n        \"retry_of_task_id\": null,\n        \"copy_of_task_id\": null,\n        \"host_name\": null,\n        \"storage\": null,\n        \"depends_on_task_ids\": [\n          \"15281118-acc3-441f-970d-39a49457d9e5\"\n        ],\n        \"links\": {\n          \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/72dc4f5f-7ef0-4f46-9811-29589a541400\"\n        }\n      }\n    ],\n    \"links\": {\n      \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/jobs\\/c677ccf7-8876-4f48-bb96-0ab8e0d88cd7\"\n    }\n  }\n}\n"
  },
  {
    "path": "tests/Unit/responses/error400.json",
    "content": "{\n  \"message\": \"The given data was invalid.\",\n  \"code\": \"INVALID_DATA\",\n  \"errors\": {\n    \"status\": [\n      \"Cannot change status: task already completed.\"\n    ]\n  }\n}\n"
  },
  {
    "path": "tests/Unit/responses/error402.json",
    "content": "{\n  \"message\": \"Your account has run out of conversion minutes\",\n  \"code\": \"CREDITS_EXCEEDED\"\n}\n"
  },
  {
    "path": "tests/Unit/responses/job.json",
    "content": "{\n  \"data\": {\n    \"id\": \"cd82535b-0614-4b23-bbba-b24ab0e892f7\",\n    \"tag\": \"test-1234\",\n    \"status\": \"error\",\n    \"created_at\": \"2019-05-30T10:53:01+00:00\",\n    \"started_at\": \"2019-05-30T10:53:05+00:00\",\n    \"ended_at\": \"2019-05-30T10:53:23+00:00\",\n    \"tasks\": [\n      {\n        \"id\": \"4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b\",\n        \"name\": \"export-1\",\n        \"job_id\": \"cd82535b-0614-4b23-bbba-b24ab0e892f7\",\n        \"status\": \"error\",\n        \"code\": \"INPUT_TASK_FAILED\",\n        \"message\": \"Input task has failed\",\n        \"percent\": 100,\n        \"operation\": \"export\\/url\",\n        \"payload\": {\n          \"operation\": \"export\\/url\",\n          \"input\": [\n            \"task-1\"\n          ]\n        },\n        \"result\": null,\n        \"created_at\": \"2019-05-30T10:53:01+00:00\",\n        \"started_at\": null,\n        \"ended_at\": \"2019-05-30T10:53:23+00:00\",\n        \"retry_of_task_id\": null,\n        \"copy_of_task_id\": null,\n        \"host_name\": null,\n        \"storage\": null,\n        \"depends_on_task_ids\": [\n          \"6df0920a-7042-4e87-be52-f38a0a29a67e\"\n        ],\n        \"links\": {\n          \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b\"\n        }\n      },\n      {\n        \"id\": \"6df0920a-7042-4e87-be52-f38a0a29a67e\",\n        \"name\": \"task-1\",\n        \"job_id\": \"cd82535b-0614-4b23-bbba-b24ab0e892f7\",\n        \"status\": \"error\",\n        \"code\": \"INPUT_TASK_FAILED\",\n        \"message\": \"Input task has failed\",\n        \"percent\": 100,\n        \"operation\": \"convert\",\n        \"engine\": null,\n        \"engine_version\": null,\n        \"payload\": {\n          \"operation\": \"convert\",\n          \"input_format\": \"mp4\",\n          \"output_format\": \"mp4\",\n          \"engine\": \"ffmpeg\",\n          \"input\": [\n            \"import-1\"\n          ],\n          \"video_codec\": \"x264\",\n          \"crf\": 0,\n          \"preset\": \"veryslow\",\n          \"profile\": \"baseline\",\n          \"width\": 1920,\n          \"height\": 1080,\n          \"audio_codec\": \"copy\",\n          \"audio_bitrate\": 320,\n          \"engine_version\": \"4.1.1\"\n        },\n        \"result\": null,\n        \"created_at\": \"2019-05-30T10:53:01+00:00\",\n        \"started_at\": null,\n        \"ended_at\": \"2019-05-30T10:53:23+00:00\",\n        \"retry_of_task_id\": null,\n        \"copy_of_task_id\": null,\n        \"host_name\": null,\n        \"storage\": null,\n        \"depends_on_task_ids\": [\n          \"22be63c2-0e3f-4909-9c2a-2261dc540aba\"\n        ],\n        \"links\": {\n          \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/6df0920a-7042-4e87-be52-f38a0a29a67e\"\n        }\n      },\n      {\n        \"id\": \"22be63c2-0e3f-4909-9c2a-2261dc540aba\",\n        \"name\": \"import-1\",\n        \"job_id\": \"cd82535b-0614-4b23-bbba-b24ab0e892f7\",\n        \"status\": \"error\",\n        \"code\": \"SANDBOX_FILE_NOT_ALLOWED\",\n        \"message\": \"The file file.mp4 is not whitelisted for sandbox use\",\n        \"percent\": 100,\n        \"operation\": \"import\\/url\",\n        \"payload\": {\n          \"operation\": \"import\\/url\",\n          \"url\": \"https:\\/\\/some.url\\/file.mp4\",\n          \"filename\": \"file.mp4\"\n        },\n        \"result\": {\n          \"files\": [\n            {\n              \"filename\": \"file.mp4\",\n              \"md5\": \"c03538f8edd84537190c264109fa2284\"\n            }\n          ]\n        },\n        \"created_at\": \"2019-05-30T10:53:01+00:00\",\n        \"started_at\": \"2019-05-30T10:53:05+00:00\",\n        \"ended_at\": \"2019-05-30T10:53:23+00:00\",\n        \"retry_of_task_id\": null,\n        \"copy_of_task_id\": null,\n        \"host_name\": \"leta\",\n        \"storage\": null,\n        \"depends_on_task_ids\": [],\n        \"links\": {\n          \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/22be63c2-0e3f-4909-9c2a-2261dc540aba\"\n        }\n      }\n    ],\n    \"links\": {\n      \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/jobs\\/cd82535b-0614-4b23-bbba-b24ab0e892f7\"\n    }\n  }\n}\n"
  },
  {
    "path": "tests/Unit/responses/job_created.json",
    "content": "{\n  \"data\": {\n    \"id\": \"c677ccf7-8876-4f48-bb96-0ab8e0d88cd7\",\n    \"status\": \"waiting\",\n    \"created_at\": \"2019-06-01T00:35:33+00:00\",\n    \"started_at\": \"2019-06-01T00:35:33+00:00\",\n    \"ended_at\": null,\n    \"tasks\": [\n      {\n        \"id\": \"22b3d686-126b-4fe2-8238-a9781cd023d9\",\n        \"name\": \"import-it\",\n        \"job_id\": \"c677ccf7-8876-4f48-bb96-0ab8e0d88cd7\",\n        \"status\": \"waiting\",\n        \"code\": null,\n        \"message\": null,\n        \"percent\": 100,\n        \"operation\": \"import\\/url\",\n        \"payload\": {\n          \"operation\": \"import\\/url\",\n          \"url\": \"http:\\/\\/invalid.url\",\n          \"filename\": \"test.file\"\n        },\n        \"result\": null,\n        \"created_at\": \"2019-06-01T00:35:33+00:00\",\n        \"started_at\": \"2019-06-01T00:35:33+00:00\",\n        \"ended_at\": null,\n        \"retry_of_task_id\": null,\n        \"copy_of_task_id\": null,\n        \"host_name\": \"jena\",\n        \"storage\": null,\n        \"depends_on_task_ids\": [],\n        \"links\": {\n          \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/22b3d686-126b-4fe2-8238-a9781cd023d9\"\n        }\n      },\n      {\n        \"id\": \"15281118-acc3-441f-970d-39a49457d9e5\",\n        \"name\": \"convert-it\",\n        \"job_id\": \"c677ccf7-8876-4f48-bb96-0ab8e0d88cd7\",\n        \"status\": \"waiting\",\n        \"code\": null,\n        \"message\": null,\n        \"percent\": 100,\n        \"operation\": \"convert\",\n        \"engine\": null,\n        \"engine_version\": null,\n        \"payload\": {\n          \"operation\": \"convert\",\n          \"input\": [\n            \"import-it\"\n          ],\n          \"output_format\": \"pdf\"\n        },\n        \"result\": null,\n        \"created_at\": \"2019-06-01T00:35:33+00:00\",\n        \"started_at\": null,\n        \"ended_at\": null,\n        \"retry_of_task_id\": null,\n        \"copy_of_task_id\": null,\n        \"host_name\": null,\n        \"storage\": null,\n        \"depends_on_task_ids\": [\n          \"22b3d686-126b-4fe2-8238-a9781cd023d9\"\n        ],\n        \"links\": {\n          \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/15281118-acc3-441f-970d-39a49457d9e5\"\n        }\n      }\n    ],\n    \"links\": {\n      \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/jobs\\/c677ccf7-8876-4f48-bb96-0ab8e0d88cd7\"\n    }\n  }\n}\n"
  },
  {
    "path": "tests/Unit/responses/job_export_urls.json",
    "content": "{\n  \"data\": {\n    \"id\": \"cd82535b-0614-4b23-bbba-b24ab0e892f7\",\n    \"tag\": \"test-1234\",\n    \"status\": \"error\",\n    \"created_at\": \"2019-05-30T10:53:01+00:00\",\n    \"started_at\": \"2019-05-30T10:53:05+00:00\",\n    \"ended_at\": \"2019-05-30T10:53:23+00:00\",\n    \"tasks\": [\n      {\n        \"id\": \"4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b\",\n        \"name\": \"export-1\",\n        \"job_id\": \"cd82535b-0614-4b23-bbba-b24ab0e892f7\",\n        \"status\": \"finished\",\n        \"percent\": 100,\n        \"operation\": \"export\\/url\",\n        \"created_at\": \"2019-05-30T10:53:01+00:00\",\n        \"started_at\": null,\n        \"ended_at\": \"2019-05-30T10:53:23+00:00\",\n        \"retry_of_task_id\": null,\n        \"copy_of_task_id\": null,\n        \"host_name\": null,\n        \"storage\": null,\n        \"result\": {\n          \"files\": [\n            {\n              \"filename\": \"file.mp4\",\n              \"url\": \"https://storage.cloudconvert.com/file.mp4\"\n            }\n          ]\n        },\n        \"links\": {\n          \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b\"\n        }\n      },\n      {\n        \"id\": \"4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b\",\n        \"name\": \"export-2\",\n        \"job_id\": \"cd82535b-0614-4b23-bbba-b24ab0e892f7\",\n        \"status\": \"finished\",\n        \"percent\": 100,\n        \"operation\": \"export\\/url\",\n        \"created_at\": \"2019-05-30T10:53:01+00:00\",\n        \"started_at\": null,\n        \"ended_at\": \"2019-05-30T10:53:23+00:00\",\n        \"retry_of_task_id\": null,\n        \"copy_of_task_id\": null,\n        \"host_name\": null,\n        \"storage\": null,\n        \"result\": {\n          \"files\": [\n            {\n              \"filename\": \"file2.mp4\",\n              \"url\": \"https://storage.cloudconvert.com/file2.mp4\"\n            }\n          ]\n        },\n        \"links\": {\n          \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b\"\n        }\n      }\n    ],\n    \"links\": {\n      \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/jobs\\/cd82535b-0614-4b23-bbba-b24ab0e892f7\"\n    }\n  }\n}\n"
  },
  {
    "path": "tests/Unit/responses/jobs.json",
    "content": "{\n  \"data\": [\n    {\n      \"id\": \"bd7d06b4-60fb-472b-b3a3-9034b273df07\",\n      \"status\": \"waiting\",\n      \"created_at\": \"2019-05-13T19:52:21+00:00\",\n      \"started_at\": null,\n      \"ended_at\": null,\n      \"links\": {\n        \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/jobs\\/bd7d06b4-60fb-472b-b3a3-9034b273df07\"\n      }\n    }\n  ],\n  \"links\": {\n    \"first\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/jobs?page=1\",\n    \"last\": null,\n    \"prev\": null,\n    \"next\": null\n  },\n  \"meta\": {\n    \"current_page\": 1,\n    \"from\": 1,\n    \"path\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/jobs\",\n    \"per_page\": 100,\n    \"to\": 1\n  }\n}\n"
  },
  {
    "path": "tests/Unit/responses/task.json",
    "content": "{\n  \"data\": {\n    \"id\": \"4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b\",\n    \"name\": \"export-1\",\n    \"job_id\": \"cd82535b-0614-4b23-bbba-b24ab0e892f7\",\n    \"status\": \"error\",\n    \"code\": \"INPUT_TASK_FAILED\",\n    \"message\": \"Input task has failed\",\n    \"percent\": 100,\n    \"operation\": \"export\\/url\",\n    \"payload\": {\n      \"operation\": \"export\\/url\",\n      \"input\": [\n        \"task-1\"\n      ]\n    },\n    \"result\": null,\n    \"created_at\": \"2019-05-30T10:53:01+00:00\",\n    \"started_at\": null,\n    \"ended_at\": \"2019-05-30T10:53:23+00:00\",\n    \"retry_of_task_id\": null,\n    \"copy_of_task_id\": null,\n    \"retries\": [],\n    \"host_name\": null,\n    \"storage\": null,\n    \"depends_on_tasks\": [\n      {\n        \"id\": \"6df0920a-7042-4e87-be52-f38a0a29a67e\",\n        \"name\": \"task-1\",\n        \"job_id\": \"cd82535b-0614-4b23-bbba-b24ab0e892f7\",\n        \"status\": \"error\",\n        \"code\": \"INPUT_TASK_FAILED\",\n        \"message\": \"Input task has failed\",\n        \"percent\": 100,\n        \"operation\": \"convert\",\n        \"engine\": null,\n        \"engine_version\": null,\n        \"payload\": {\n          \"operation\": \"convert\",\n          \"input_format\": \"mp4\",\n          \"output_format\": \"mp4\",\n          \"engine\": \"ffmpeg\",\n          \"input\": [\n            \"import-1\"\n          ],\n          \"video_codec\": \"x264\",\n          \"crf\": 0,\n          \"preset\": \"veryslow\",\n          \"profile\": \"baseline\",\n          \"width\": 1920,\n          \"height\": 1080,\n          \"audio_codec\": \"copy\",\n          \"audio_bitrate\": 320,\n          \"engine_version\": \"4.1.1\"\n        },\n        \"result\": null,\n        \"created_at\": \"2019-05-30T10:53:01+00:00\",\n        \"started_at\": null,\n        \"ended_at\": \"2019-05-30T10:53:23+00:00\",\n        \"retry_of_task_id\": null,\n        \"copy_of_task_id\": null,\n        \"host_name\": null,\n        \"storage\": null,\n        \"depends_on_task_ids\": [\n          \"22be63c2-0e3f-4909-9c2a-2261dc540aba\"\n        ],\n        \"links\": {\n          \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/6df0920a-7042-4e87-be52-f38a0a29a67e\"\n        }\n      }\n    ],\n    \"links\": {\n      \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b\"\n    }\n  }\n}\n"
  },
  {
    "path": "tests/Unit/responses/task_created.json",
    "content": "{\n  \"data\": {\n    \"id\": \"2f901289-c9fe-4c89-9c4b-98be526bdfbf\",\n    \"job_id\": null,\n    \"status\": \"waiting\",\n    \"code\": null,\n    \"message\": null,\n    \"percent\": 100,\n    \"operation\": \"import\\/url\",\n    \"payload\": {\n      \"name\": \"test\",\n      \"url\": \"http:\\/\\/invalid.url\",\n      \"filename\": \"test.file\"\n    },\n    \"result\": null,\n    \"created_at\": \"2019-05-31T23:52:39+00:00\",\n    \"started_at\": \"2019-05-31T23:52:39+00:00\",\n    \"ended_at\": \"2019-05-31T23:53:26+00:00\",\n    \"retry_of_task_id\": null,\n    \"copy_of_task_id\": null,\n    \"retries\": [],\n    \"depends_on_tasks\": [],\n    \"links\": {\n      \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/2f901289-c9fe-4c89-9c4b-98be526bdfbf\"\n    }\n  }\n}\n"
  },
  {
    "path": "tests/Unit/responses/tasks.json",
    "content": "{\n  \"data\": [\n    {\n      \"id\": \"4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b\",\n      \"name\": \"export-1\",\n      \"job_id\": \"cd82535b-0614-4b23-bbba-b24ab0e892f7\",\n      \"status\": \"error\",\n      \"code\": \"INPUT_TASK_FAILED\",\n      \"message\": \"Input task has failed\",\n      \"percent\": 100,\n      \"operation\": \"export\\/url\",\n      \"payload\": {\n        \"operation\": \"export\\/url\",\n        \"input\": [\n          \"task-1\"\n        ]\n      },\n      \"result\": null,\n      \"created_at\": \"2019-05-30T10:53:01+00:00\",\n      \"started_at\": null,\n      \"ended_at\": \"2019-05-30T10:53:23+00:00\",\n      \"retry_of_task_id\": null,\n      \"copy_of_task_id\": null,\n      \"retries\": [],\n      \"host_name\": null,\n      \"storage\": null,\n      \"depends_on_task_ids\": [],\n      \"links\": {\n        \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/4c80f1ae-5b3a-43d5-bb58-1a5c4eb4e46b\"\n      }\n    }\n  ],\n  \"links\": {\n    \"first\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks?page=1\",\n    \"last\": null,\n    \"prev\": null,\n    \"next\": null\n  },\n  \"meta\": {\n    \"current_page\": 1,\n    \"from\": 1,\n    \"path\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\",\n    \"per_page\": 100,\n    \"to\": 1\n  }\n}\n"
  },
  {
    "path": "tests/Unit/responses/upload_task_created.json",
    "content": "{\n  \"data\": {\n    \"id\": \"2f901289-c9fe-4c89-9c4b-98be526bdfbf\",\n    \"job_id\": null,\n    \"status\": \"waiting\",\n    \"code\": null,\n    \"message\": null,\n    \"percent\": 100,\n    \"operation\": \"import\\/upload\",\n    \"payload\": null,\n    \"result\": {\n      \"form\": {\n        \"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\",\n        \"parameters\": {\n          \"expires\": 1559588529,\n          \"max_file_count\": 1,\n          \"max_file_size\": 10000000000,\n          \"signature\": \"79fda6c5ffbfaa857ae9a1430641cc68c5a72297\"\n        }\n      }\n    },\n    \"created_at\": \"2019-05-31T23:52:39+00:00\",\n    \"started_at\": \"2019-05-31T23:52:39+00:00\",\n    \"ended_at\": \"2019-05-31T23:53:26+00:00\",\n    \"retry_of_task_id\": null,\n    \"copy_of_task_id\": null,\n    \"retries\": [],\n    \"depends_on_tasks\": [],\n    \"links\": {\n      \"self\": \"https:\\/\\/api.cloudconvert.com\\/v2\\/tasks\\/2f901289-c9fe-4c89-9c4b-98be526bdfbf\"\n    }\n  }\n}\n"
  },
  {
    "path": "tests/Unit/responses/user.json",
    "content": "{\n  \"data\": {\n    \"id\": 1,\n    \"username\": \"Username\",\n    \"email\": \"me@example.com\",\n    \"created_at\": \"2018-12-01T22:26:29+00:00\",\n    \"credits\": 4434,\n    \"links\": {\n      \"self\": \"https://api.cloudconvert.com/v2/users/1\"\n    }\n  }\n}\n"
  },
  {
    "path": "tests/bootstrap.php",
    "content": "<?php\nrequire __DIR__ . '/../vendor/autoload.php';"
  }
]