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