├── test
├── examples
│ ├── dummy.png
│ └── voormedia.png
├── base
│ ├── test_case_1.php
│ └── test_case_2.php
├── TinifyResultMetaTest.php
├── bootstrap.php
├── TinifyResultTest.php
├── integration.php
├── TinifyTest.php
├── curl_mock.php
├── TinifyClientTest.php
└── TinifySourceTest.php
├── .gitignore
├── phpunit.xml
├── update-cacert.sh
├── lib
├── Tinify
│ ├── ResultMeta.php
│ ├── Result.php
│ ├── Exception.php
│ ├── Source.php
│ └── Client.php
└── Tinify.php
├── composer.json
├── CHANGES.md
├── LICENSE
├── .github
└── workflows
│ └── ci-cd.yaml
├── README.md
└── .travis.yml
/test/examples/dummy.png:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | .phpunit.result.cache
3 | composer.lock
4 |
--------------------------------------------------------------------------------
/test/examples/voormedia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinify/tinify-php/master/test/examples/voormedia.png
--------------------------------------------------------------------------------
/test/base/test_case_1.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | test
5 |
6 |
7 |
8 |
9 | lib
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/update-cacert.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | dir=lib/data
3 |
4 | cert=0
5 | curl --silent --fail https://curl.se/ca/cacert.pem | while read -r line; do
6 | if [ "-----BEGIN CERTIFICATE-----" == "$line" ]; then
7 | cert=1
8 | echo "$line"
9 | elif [ "-----END CERTIFICATE-----" == "$line" ]; then
10 | cert=0
11 | echo "$line"
12 | else
13 | if [ $cert == 1 ]; then
14 | echo "$line"
15 | fi
16 | fi
17 | done > "$dir/cacert.pem"
18 |
--------------------------------------------------------------------------------
/test/base/test_case_2.php:
--------------------------------------------------------------------------------
1 | meta = $meta;
10 | }
11 |
12 | public function width() {
13 | return intval($this->meta["image-width"]);
14 | }
15 |
16 | public function height() {
17 | return intval($this->meta["image-height"]);
18 | }
19 |
20 | public function location() {
21 | return isset($this->meta["location"]) ? $this->meta["location"] : null;
22 | }
23 |
24 | public function extension() {
25 | if (isset($this->meta["content-type"])) {
26 | $parts = explode("/", $this->meta["content-type"]);
27 | return end($parts);
28 | }
29 | return null;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test/TinifyResultMetaTest.php:
--------------------------------------------------------------------------------
1 | "100"));
8 | $this->assertSame(100, $result->width());
9 | }
10 |
11 | public function testWithMetadataHeightShouldReturnImageHeight() {
12 | $result = new Tinify\ResultMeta(array("image-height" => "60"));
13 | $this->assertSame(60, $result->height());
14 | }
15 |
16 | public function testWithMetadataLocationShouldReturnImageLocation() {
17 | $result = new Tinify\ResultMeta(array("location" => "https://example.com/image.png"));
18 | $this->assertSame("https://example.com/image.png", $result->location());
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/test/bootstrap.php:
--------------------------------------------------------------------------------
1 | = 0) {
14 | require_once("base" . DIRECTORY_SEPARATOR . "test_case_2.php");
15 | class_alias('TestCase_2', 'TestCase');
16 | } else {
17 | require_once("base" . DIRECTORY_SEPARATOR . "test_case_1.php");
18 | class_alias('TestCase_1', 'TestCase');
19 | }
20 |
21 | define('DUMMY_FILE_LOCATION', dirname(__DIR__) . DIRECTORY_SEPARATOR . 'test' . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . 'dummy.png');
22 |
--------------------------------------------------------------------------------
/lib/Tinify/Result.php:
--------------------------------------------------------------------------------
1 | meta = $meta;
10 | $this->data = $data;
11 | }
12 |
13 | public function data() {
14 | return $this->data;
15 | }
16 |
17 | public function toBuffer() {
18 | return $this->data;
19 | }
20 |
21 | public function toFile($path) {
22 | return file_put_contents($path, $this->toBuffer());
23 | }
24 |
25 | public function size() {
26 | return intval($this->meta["content-length"]);
27 | }
28 |
29 | public function mediaType() {
30 | return $this->meta["content-type"];
31 | }
32 |
33 | public function contentType() {
34 | return $this->mediaType();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tinify/tinify",
3 | "description": "PHP client for the Tinify API. Tinify compresses your images intelligently. Read more at https://tinify.com.",
4 | "keywords": [
5 | "tinify",
6 | "tinypng",
7 | "tinyjpg",
8 | "compress",
9 | "images",
10 | "api"
11 | ],
12 |
13 | "homepage": "https://tinify.com/developers",
14 | "license": "MIT",
15 |
16 | "support": {
17 | "email": "support@tinify.com"
18 | },
19 |
20 | "authors": [{
21 | "name": "Rolf Timmermans",
22 | "email": "rolftimmermans@voormedia.com"
23 | }],
24 |
25 | "require": {
26 | "php": ">=5.3.0",
27 | "ext-curl": "*",
28 | "ext-json": "*",
29 | "lib-curl": ">=7.20.0"
30 | },
31 |
32 | "require-dev": {
33 | "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4 || ^9.3"
34 | },
35 |
36 | "autoload": {
37 | "files": ["lib/Tinify.php", "lib/Tinify/Exception.php"],
38 | "psr-4": {"Tinify\\": "lib/Tinify/"}
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | ## 1.6.4
2 | * Will use POST instead of GET when retrieving result.
3 | * Remove deprecated `curl_close` for PHP version 8. https://www.php.net/manual/en/function.curl-close.php.
4 | * Support PHP `8.5`.
5 |
6 | ## 1.6.3
7 | * Add minimum TLS 1.2 version to curl options as protocol negotiation on certain openssl/libcurl versions is flaky.
8 |
9 | ## 1.6.2
10 | * Remove deprecated curl constant (https://php.watch/versions/8.4/CURLOPT_BINARYTRANSFER-deprecated)
11 |
12 | ## 1.6.1
13 | * Fixed string interpolation for php 8.2: https://wiki.php.net/rfc/deprecate_dollar_brace_string_interpolation
14 |
15 | ## 1.6.0
16 | * Support to run the unittests on newer versions of PHP (5.5 +)
17 | * Add API methods for converting/transcoding and transformation
18 | * Add helper function for returning the compressed file extension
19 |
20 | ## 1.5.2
21 | * Fail early if version of curl/openssl is too old.
22 |
23 | ## 1.5.1
24 | * Expose status of exceptions.
25 |
26 | ## 1.5.0
27 | * Retry failed requests by default.
28 | * Throw clearer errors when curl is installed but disabled.
29 |
30 | ## 1.4.0
31 | * Support for HTTP proxies.
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2013-2018 Tinify
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/Tinify/Exception.php:
--------------------------------------------------------------------------------
1 | = 400 && $status <= 499) {
12 | $klass = "Tinify\ClientException";
13 | } else if($status >= 500 && $status <= 599) {
14 | $klass = "Tinify\ServerException";
15 | } else {
16 | $klass = "Tinify\Exception";
17 | }
18 |
19 | if (empty($message)) $message = "No message was provided";
20 | return new $klass($message, $type, $status);
21 | }
22 |
23 | function __construct($message, $type = NULL, $status = NULL) {
24 | $this->status = $status;
25 | if ($status) {
26 | parent::__construct($message . " (HTTP " . $status . "/" . $type . ")");
27 | } else {
28 | parent::__construct($message);
29 | }
30 | }
31 | }
32 |
33 | class AccountException extends Exception {}
34 | class ClientException extends Exception {}
35 | class ServerException extends Exception {}
36 | class ConnectionException extends Exception {}
37 |
--------------------------------------------------------------------------------
/.github/workflows/ci-cd.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | name: PHP CI/CD
3 |
4 | on: [push, pull_request]
5 |
6 | permissions: {}
7 | jobs:
8 | Unit_tests:
9 | runs-on: ${{ matrix.os }}
10 | timeout-minutes: 10
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | php-version: [
15 | "5.4",
16 | "5.6",
17 | "7.4",
18 | "8.0",
19 | "8.5",
20 | ]
21 | os: [ubuntu-latest, macOS-latest, windows-latest]
22 | steps:
23 | - uses: shivammathur/setup-php@2.32.0
24 | with:
25 | php-version: ${{ matrix.php-version }}
26 | tools: composer:v2
27 | - uses: actions/checkout@v3
28 | - name: Validate composer.json and composer.lock
29 | run: composer validate --strict
30 | - name: Install Dependencies
31 | run: composer install --no-ansi --no-interaction --no-progress
32 | - name: Run test suite
33 | run: vendor/bin/phpunit
34 | Integration_tests:
35 | if: github.event_name == 'push'
36 | runs-on: ${{ matrix.os }}
37 | timeout-minutes: 10
38 | needs: Unit_tests
39 | strategy:
40 | fail-fast: false
41 | matrix:
42 | php-version: [
43 | "7.4",
44 | "8.4",
45 | ]
46 | os: [ubuntu-latest, macOS-latest, windows-latest]
47 | steps:
48 | - uses: shivammathur/setup-php@2.32.0
49 | with:
50 | php-version: ${{ matrix.php-version }}
51 | tools: composer:v2
52 | - uses: actions/checkout@v3
53 | - name: Install Dependencies
54 | run: composer install --no-ansi --no-interaction --no-progress
55 | - name: Run test suite
56 | env:
57 | TINIFY_KEY: ${{ secrets.TINIFY_KEY }}
58 | run: vendor/bin/phpunit --no-configuration test/integration.php
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [ ](https://github.com/tinify/tinify-php/blob/main/LICENSE)
2 | [](https://github.com/tinify/tinify-php/actions/workflows/ci-cd.yaml)
3 | [](https://packagist.org/packages/tinify/tinify)
4 | [](https://packagist.org/packages/tinify/tinify)
5 |
6 | # Tinify API client for PHP
7 |
8 | PHP client for the Tinify API, used for [TinyPNG](https://tinypng.com) and [TinyJPG](https://tinyjpg.com). Tinify compresses your images intelligently. Read more at [http://tinify.com](http://tinify.com).
9 |
10 | ## Documentation
11 |
12 | [Go to the documentation for the PHP client](https://tinypng.com/developers/reference/php).
13 |
14 | ## Installation
15 |
16 | Install the API client with Composer. Add this to your `composer.json`:
17 |
18 | ```json
19 | {
20 | "require": {
21 | "tinify/tinify": "*"
22 | }
23 | }
24 | ```
25 |
26 | Then install with:
27 |
28 | ```
29 | composer install
30 | ```
31 |
32 | Use autoloading to make the client available in PHP:
33 |
34 | ```php
35 | require_once("vendor/autoload.php");
36 | ```
37 |
38 | ## Usage
39 |
40 | ```php
41 | Tinify\setKey("YOUR_API_KEY");
42 | Tinify\fromFile("unoptimized.png")->toFile("optimized.png");
43 | ```
44 |
45 | ## Running tests
46 |
47 | ```
48 | composer install
49 | vendor/bin/phpunit
50 | ```
51 |
52 | ### Integration tests
53 |
54 | ```
55 | composer install
56 | TINIFY_KEY=$YOUR_API_KEY vendor/bin/phpunit --no-configuration test/integration.php
57 | ```
58 |
59 | ## License
60 |
61 | This software is licensed under the MIT License. [View the license](LICENSE).
62 |
--------------------------------------------------------------------------------
/test/TinifyResultTest.php:
--------------------------------------------------------------------------------
1 | "100"), "image data");
8 | $this->assertSame(100, $result->width());
9 | }
10 |
11 | public function testWithMetaAndDataHeightShouldReturnImageHeight() {
12 | $result = new Tinify\Result(array("image-height" => "60"), "image data");
13 | $this->assertSame(60, $result->height());
14 | }
15 |
16 | public function testWithMetaAndDataLocationShouldReturnNull() {
17 | $result = new Tinify\ResultMeta(array(), "image data");
18 | $this->assertSame(null, $result->location());
19 | }
20 |
21 | public function testWithMetaAndDataSizeShouldReturnContentLength() {
22 | $result = new Tinify\Result(array("content-length" => "450"), "image data");
23 | $this->assertSame(450, $result->size());
24 | }
25 |
26 | public function testWithMetaAndDataContentTypeShouldReturnMimeType() {
27 | $result = new Tinify\Result(array("content-type" => "image/png"), "image data");
28 | $this->assertSame("image/png", $result->contentType());
29 | }
30 |
31 | public function testWithMetaAndDataToBufferShouldReturnImageData() {
32 | $result = new Tinify\Result(array(), "image data");
33 | $this->assertSame("image data", $result->toBuffer());
34 | }
35 |
36 | public function testWithMetadataReturnsExtension() {
37 | $result = new Tinify\Result(array("content-type" => "image/png"), "image data");
38 | $this->assertSame($result->extension(), "png");
39 | }
40 |
41 | public function testWithoutMetadataReturnsExtensionAsNull() {
42 | $result = new Tinify\ResultMeta(array(), "image data");
43 | $this->assertSame($result->extension(), null);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | php:
3 | - 5.4
4 | - 5.5
5 | - 5.6
6 | - 7.0
7 | - 7.1
8 | - 7.2
9 | - nightly
10 | env:
11 | global:
12 | secure: BFdcLqU/07l3/NGaSZA5+VSB5FSVSwTL9wAyrJSD1N7jN2HzFTE4xxxmcVrAqq6t+jhZqajXcSf8/DTUKyLcmWXGw3JkseHx3ofHw5p2E4Mp+dUvNMh+3KgAABCPm5zEBUgnhNGwFqL+WA/cgXIJY1/sv87u/mg63ojvf3orKJ3KMxFFsVbGo553XYVsiSUXfzvV3foqPdcV9D0SZ0v7IiLcsuwtDRtfJ/udgSZ2C2LFiOek1dLwbjM3inMBPkfLcpuvC2l8Z1CyW5ptxYRrHHJqCMbdRmmwGH04hxRju54EJKX5PGTyDhfG9Kyiu8/hQCtDL4jNQlVNAKbvHrRTnLogoMKhHMRaAIDMNI+lcQB9OHHuureYAqL7gJvRmE2sfN/OAbzizYJ2z+yXvjaKJsd2htrHff8hgvl0M3guhP+mA5C9ubibzGiCD66kdeqS5tJi3EH7wk9GO75lFm3tlZEWZ4FJCwgyFQRZGMGSRJC5cWvW4QlGLHeVnWwnf9CM9/B2oD21LraccgCZhpIbib5/XJ2vTQO0V2ke3kXT1D9E3P8E2eClxnge1aShUSy+d5kMTNeLxkfalgGEviWqA236XLzS8+MjWzNWy3+roBb63Vh0OPp4GyTO810U16Y5lwCMegVrxusnz9bCo1HbK1w2k8D7Ueb/O7LEFYaZSD4=
13 | matrix:
14 | allow_failures:
15 | - php: nightly
16 | include:
17 | - dist: precise
18 | php: 5.3
19 | - dist: trusty
20 | php: hhvm
21 | - dist: trusty
22 | php: 5.6
23 | env: INTEGRATION_TESTS=true
24 | script: "if [ \"$TRAVIS_PULL_REQUEST\" == \"false\" ]; then vendor/bin/phpunit --no-configuration test/integration.php; fi"
25 | before_script: composer install
26 | script: vendor/bin/phpunit
27 | notifications:
28 | email: false
29 | slack:
30 | secure: W1cEp+ctAD+in3hA/gansbUYrFZsR3EMd7s/XiK2mfAxRRXPCPgCvv0PWpYelJ5F2AnHXkmBP3USKFDhKN+JLmOOxrZpjzh4ve9QSNs4IPgcrU4meQYSgJ3uovS7h4fqAmqhQKL+FpAnrXAmXWGXVPCPtneySsMTyU6kNdmVfulxXRMJ+kLHtFQlT+U7cPYG0f8FkKR2u5CbRsdjUvoZHwbK7jmWpUcCi2O0A1vvMMEMEOG+iuayWW/RbfSfKIgM/SZG0pGYpoAB7I8XfYns7IyLhkJAw18jIWnowF4DuBusNRdr3zIfwC7bSlLKMFYJYpEsiLFCaeGZgp1NbYJ77DvqpwNp9xmoPz9rFGNe7Re+i13pjNWhEnwc5N3St7x8DYNhUC3L0upQ2FWZSomMM7ghlc6GIVtlXYrRJ5AnxIjSWyLRcTRU5Pf/PPRckOTiuLnVgUZNMVIlAHlRV7c/pUVDjbPvjcOajdXnpcd3+5cuKyzCYpXugIfJ9Hzsozq0yCHDKjo8ssAU+5H3mEfB7M25Rtd2awObb3zqOyUPm+J3M/dzql95cF+zj2SWQsBF/ViwkHo+9SoAr/FYqCZveiS0w9vzoKzbGXVDYCfFD+GlEMJ2b8zw4d2sqmIbw24jGq1ZPgczrpD5fcbI2TaheKBkgWvsBJFFO5KbGWqzHIM=
31 |
--------------------------------------------------------------------------------
/lib/Tinify.php:
--------------------------------------------------------------------------------
1 | request("post", "/shrink");
90 | } catch (AccountException $err) {
91 | if ($err->status == 429) return true;
92 | throw $err;
93 | } catch (ClientException $err) {
94 | return true;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/lib/Tinify/Source.php:
--------------------------------------------------------------------------------
1 | request("post", "/shrink", $string);
14 | return new self($response->headers["location"]);
15 | }
16 |
17 | public static function fromUrl($url) {
18 | $body = array("source" => array("url" => $url));
19 | $response = Tinify::getClient()->request("post", "/shrink", $body);
20 | return new self($response->headers["location"]);
21 | }
22 |
23 | public function __construct($url, $commands = array()) {
24 | $this->url = $url;
25 | $this->commands = $commands;
26 | }
27 |
28 | public function preserve() {
29 | $options = $this->flatten(func_get_args());
30 | $commands = array_merge($this->commands, array("preserve" => $options));
31 | return new self($this->url, $commands);
32 | }
33 |
34 | public function resize($options) {
35 | $commands = array_merge($this->commands, array("resize" => $options));
36 | return new self($this->url, $commands);
37 | }
38 |
39 | public function store($options) {
40 | $response = Tinify::getClient()->request("post", $this->url,
41 | array_merge($this->commands, array("store" => $options)));
42 | return new Result($response->headers, $response->body);
43 | }
44 |
45 | public function convert($options) {
46 | $commands = array_merge($this->commands, array("convert" => $options));
47 | return new self($this->url, $commands);
48 | }
49 |
50 | public function transform($options) {
51 | $commands = array_merge($this->commands, array("transform" => $options));
52 | return new self($this->url, $commands);
53 | }
54 |
55 | public function result() {
56 | $has_commands = !empty($this->commands);
57 | $method = $has_commands ? "post" : "get";
58 | $body = $has_commands ? $this->commands : null;
59 | $response = Tinify::getClient()->request($method, $this->url, $body);
60 | return new Result($response->headers, $response->body);
61 | }
62 |
63 | public function toFile($path) {
64 | return $this->result()->toFile($path);
65 | }
66 |
67 | public function toBuffer() {
68 | return $this->result()->toBuffer();
69 | }
70 |
71 | private static function flatten($options) {
72 | $flattened = array();
73 | foreach ($options as $option) {
74 | if (is_array($option)) {
75 | $flattened = array_merge($flattened, $option);
76 | } else {
77 | array_push($flattened, $option);
78 | }
79 | }
80 | return $flattened;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/test/integration.php:
--------------------------------------------------------------------------------
1 | toFile($path);
23 |
24 | $size = filesize($path);
25 | $contents = fread(fopen($path, "rb"), $size);
26 |
27 | $this->assertGreaterThan(1000, $size);
28 | $this->assertLessThan(1500, $size);
29 |
30 | /* width == 137 */
31 | $this->assertStringContainsString("\0\0\0\x89", $contents);
32 | $this->assertStringNotContainsString("Copyright Voormedia", $contents);
33 | }
34 |
35 | public function testShouldCompressFromUrl() {
36 | $path = tempnam(sys_get_temp_dir(), "tinify-php");
37 | $source = \Tinify\fromUrl("https://raw.githubusercontent.com/tinify/tinify-php/master/test/examples/voormedia.png");
38 | $source->toFile($path);
39 |
40 | $size = filesize($path);
41 | $contents = fread(fopen($path, "rb"), $size);
42 |
43 | $this->assertGreaterThan(1000, $size);
44 | $this->assertLessThan(1500, $size);
45 |
46 | /* width == 137 */
47 | $this->assertStringContainsString("\0\0\0\x89", $contents);
48 | $this->assertStringNotContainsString("Copyright Voormedia", $contents);
49 | }
50 |
51 | public function testShouldResize() {
52 | $path = tempnam(sys_get_temp_dir(), "tinify-php");
53 | self::$optimized->resize(array("method" => "fit", "width" => 50, "height" => 20))->toFile($path);
54 |
55 | $size = filesize($path);
56 | $contents = fread(fopen($path, "rb"), $size);
57 |
58 | $this->assertGreaterThan(500, $size);
59 | $this->assertLessThan(1000, $size);
60 |
61 | /* width == 50 */
62 | $this->assertStringContainsString("\0\0\0\x32", $contents);
63 | $this->assertStringNotContainsString("Copyright Voormedia", $contents);
64 | }
65 |
66 | public function testShouldPreserveMetadata() {
67 | $path = tempnam(sys_get_temp_dir(), "tinify-php");
68 | self::$optimized->preserve("copyright", "creation")->toFile($path);
69 |
70 | $size = filesize($path);
71 | $contents = fread(fopen($path, "rb"), $size);
72 |
73 | $this->assertGreaterThan(1000, $size);
74 | $this->assertLessThan(2000, $size);
75 |
76 | /* width == 137 */
77 | $this->assertStringContainsString("\0\0\0\x89", $contents);
78 | $this->assertStringContainsString("Copyright Voormedia", $contents);
79 | }
80 |
81 | public function testShouldConvert() {
82 | $path = tempnam(sys_get_temp_dir(), "tinify-php");
83 | self::$optimized->convert(array("type" => ["image/webp"]))->toFile($path);
84 |
85 | $size = filesize($path);
86 | $contents = fread(fopen($path, "rb"), $size);
87 |
88 | $this->assertEquals(substr($contents, 0, 4), "RIFF");
89 | $this->assertEquals(substr($contents, 8, 4), "WEBP");
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/test/TinifyTest.php:
--------------------------------------------------------------------------------
1 | 200));
8 | Tinify\setKey("abcde");
9 | Tinify\Tinify::getClient();
10 | Tinify\setKey("fghij");
11 | $client = Tinify\Tinify::getClient();
12 | $client->request("get", "/");
13 |
14 | $this->assertSame("api:fghij", CurlMock::last(CURLOPT_USERPWD));
15 | }
16 |
17 | public function testAppIdentifierShouldResetClientWithNewAppIdentifier() {
18 | CurlMock::register("https://api.tinify.com/", array("status" => 200));
19 | Tinify\setKey("abcde");
20 | Tinify\setAppIdentifier("MyApp/1.0");
21 | Tinify\Tinify::getClient();
22 | Tinify\setAppIdentifier("MyApp/2.0");
23 | $client = Tinify\Tinify::getClient();
24 | $client->request("get", "/");
25 |
26 | $this->assertSame(Tinify\Client::userAgent() . " MyApp/2.0", CurlMock::last(CURLOPT_USERAGENT));
27 | }
28 |
29 | public function testProxyShouldResetClientWithNewProxy() {
30 | CurlMock::register("https://api.tinify.com/", array("status" => 200));
31 | Tinify\setKey("abcde");
32 | Tinify\setProxy("http://localhost");
33 | Tinify\Tinify::getClient();
34 | Tinify\setProxy("http://user:pass@localhost:8080");
35 | $client = Tinify\Tinify::getClient();
36 | $client->request("get", "/");
37 |
38 | $this->assertSame(Tinify\Client::userAgent() . " MyApp/2.0", CurlMock::last(CURLOPT_USERAGENT));
39 | }
40 |
41 | public function testClientWithKeyShouldReturnClient() {
42 | Tinify\setKey("abcde");
43 | $this->assertInstanceOf("Tinify\Client", Tinify\Tinify::getClient());
44 | }
45 |
46 | public function testClientWithoutKeyShouldThrowException() {
47 | $this->setExpectedException("Tinify\AccountException");
48 | Tinify\Tinify::getClient();
49 | }
50 |
51 | public function testClientWithInvalidProxyShouldThrowException() {
52 | $this->setExpectedException("Tinify\ConnectionException");
53 | Tinify\setKey("abcde");
54 | Tinify\setProxy("http-bad-url");
55 | Tinify\Tinify::getClient();
56 | }
57 |
58 | public function testSetClientShouldReplaceClient() {
59 | Tinify\setKey("abcde");
60 | Tinify\Tinify::setClient("foo");
61 | $this->assertSame("foo", Tinify\Tinify::getClient());
62 | }
63 |
64 | public function testValidateWithValidKeyShouldReturnTrue() {
65 | Tinify\setKey("valid");
66 | CurlMock::register("https://api.tinify.com/shrink", array(
67 | "status" => 400, "body" => '{"error":"Input missing","message":"No input"}'
68 | ));
69 | $this->assertTrue(Tinify\validate());
70 | }
71 |
72 | public function testValidateWithLimitedKeyShouldReturnTrue() {
73 | Tinify\setKey("invalid");
74 | CurlMock::register("https://api.tinify.com/shrink", array(
75 | "status" => 429, "body" => '{"error":"Too many requests","message":"Your monthly limit has been exceeded"}'
76 | ));
77 | $this->assertTrue(Tinify\validate());
78 | }
79 |
80 | public function testValidateWithErrorShouldThrowException() {
81 | Tinify\setKey("invalid");
82 | CurlMock::register("https://api.tinify.com/shrink", array(
83 | "status" => 401, "body" => '{"error":"Unauthorized","message":"Credentials are invalid"}'
84 | ));
85 | $this->setExpectedException("Tinify\AccountException");
86 | Tinify\validate();
87 | }
88 |
89 | public function testFromFileShouldReturnSource() {
90 | CurlMock::register("https://api.tinify.com/shrink", array(
91 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
92 | ));
93 | Tinify\setKey("valid");
94 | $this->assertInstanceOf("Tinify\Source", Tinify\fromFile(DUMMY_FILE_LOCATION));
95 | }
96 |
97 | public function testFromBufferShouldReturnSource() {
98 | CurlMock::register("https://api.tinify.com/shrink", array(
99 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
100 | ));
101 | Tinify\setKey("valid");
102 | $this->assertInstanceOf("Tinify\Source", Tinify\fromBuffer("png file"));
103 | }
104 |
105 | public function testFromUrlShouldReturnSource() {
106 | CurlMock::register("https://api.tinify.com/shrink", array(
107 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
108 | ));
109 | Tinify\setKey("valid");
110 | $this->assertInstanceOf("Tinify\Source", Tinify\fromUrl("http://example.com/test.jpg"));
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/test/curl_mock.php:
--------------------------------------------------------------------------------
1 | 471808,
11 | "version" => "7.51.0",
12 | "features" => 951197,
13 | );
14 |
15 | private static $urls = array();
16 | private static $requests = array();
17 | private static $version = array();
18 |
19 | public $options = array();
20 | public $request;
21 | public $response;
22 | public $closed = false;
23 |
24 | public static function version_info() {
25 | return self::$version;
26 | }
27 |
28 | public static function set_version_info_key($key, $value) {
29 | self::$version[$key] = $value;
30 | }
31 |
32 | public static function register($url, $request, $response = NULL) {
33 | if (!$response) {
34 | $response = $request;
35 | $request = NULL;
36 | }
37 |
38 | if (!isset(self::$urls[$url])) {
39 | self::$urls[$url] = array();
40 | }
41 |
42 | array_push(self::$urls[$url], array($request, $response));
43 | }
44 |
45 | public static function reset() {
46 | self::$requests = array();
47 | self::$urls = array();
48 | self::$version = self::$defaultVersion;
49 | }
50 |
51 | public static function last_has($key) {
52 | $lastReq = self::$requests[count(self::$requests) - 1];
53 | return array_key_exists($key, $lastReq->options);
54 | }
55 |
56 | public static function last($key = null) {
57 | $lastReq = self::$requests[count(self::$requests) - 1];
58 | if ($key) {
59 | return $lastReq->options[$key];
60 | } else {
61 | return $lastReq;
62 | }
63 | }
64 |
65 | public function close() {
66 | $this->closed = true;
67 | }
68 |
69 | public function exec() {
70 | if ($this->closed) {
71 | throw new CurlMockException("Curl already closed");
72 | }
73 | array_push(self::$requests, $this);
74 |
75 | $queue = &self::$urls[$this->options[CURLOPT_URL]];
76 | list($this->request, $this->response) = $queue[0];
77 |
78 | /* Keep last request as fallback. */
79 | if (count($queue) > 1) array_shift($queue);
80 |
81 | if ($this->request) {
82 | if ($this->request["body"]) {
83 | if ($this->options[CURLOPT_POSTFIELDS] != $this->request["body"]) {
84 | throw new Exception("Body '" . $this->options[CURLOPT_POSTFIELDS] .
85 | "' does not match expected '" . $this->request["body"] . "'");
86 | }
87 | }
88 | }
89 |
90 | if (isset($this->response["headers"])) {
91 | $headers = "";
92 | foreach ($this->response["headers"] as $header => $value) {
93 | $headers .= $header . ": " . $value . "\r\n";
94 | }
95 | $this->response["headers"] = $headers . "\r\n";
96 | } else {
97 | $this->response["headers"] = "\r\n";
98 | }
99 |
100 | if (!isset($this->response["body"])) {
101 | $this->response["body"] = "";
102 | }
103 |
104 | if (array_key_exists("return", $this->response)) {
105 | return $this->response["return"];
106 | } else if (isset($this->response["status"])) {
107 | return $this->response["headers"] . $this->response["body"];
108 | } else {
109 | return false;
110 | }
111 | }
112 |
113 | public function setopt_array($array) {
114 | if ($this->closed) {
115 | throw new CurlMockException("Curl already closed");
116 | }
117 | foreach ($array as $key => $value) {
118 | $this->options[$key] = $value;
119 | }
120 | }
121 |
122 | public function setopt($key, $value) {
123 | if ($this->closed) {
124 | throw new CurlMockException("Curl already closed");
125 | }
126 | $this->options[$key] = $value;
127 | }
128 |
129 | public function getinfo($key) {
130 | if ($this->closed) {
131 | throw new CurlMockException("Curl already closed");
132 | }
133 | switch ($key) {
134 | case CURLINFO_HTTP_CODE:
135 | return isset($this->response["status"]) ? $this->response["status"] : 0;
136 | case CURLINFO_HEADER_SIZE:
137 | return strlen($this->response["headers"]);
138 | default:
139 | throw new Exception("Bad key $key");
140 | }
141 | }
142 |
143 | public function error() {
144 | if ($this->closed) {
145 | throw new CurlMockException("Curl already closed");
146 | }
147 | return $this->response["error"];
148 | }
149 |
150 | public function errno() {
151 | if ($this->closed) {
152 | throw new CurlMockException("Curl already closed");
153 | }
154 | return $this->response["errno"];
155 | }
156 | }
157 |
158 | function curl_version() {
159 | return CurlMock::version_info();
160 | }
161 |
162 | function curl_init() {
163 | return new CurlMock();
164 | }
165 |
166 | function curl_exec($mock) {
167 | return $mock->exec();
168 | }
169 |
170 | function curl_close($mock) {
171 | $mock->close();
172 | }
173 |
174 | function curl_setopt_array($mock, $array) {
175 | return $mock->setopt_array($array);
176 | }
177 |
178 | function curl_setopt($mock, $key, $value) {
179 | return $mock->setopt($key, $value);
180 | }
181 |
182 | function curl_getinfo($mock, $key) {
183 | return $mock->getinfo($key);
184 | }
185 |
186 | function curl_error($mock) {
187 | return $mock->error();
188 | }
189 |
190 | function curl_errno($mock) {
191 | return $mock->errno();
192 | }
193 |
--------------------------------------------------------------------------------
/lib/Tinify/Client.php:
--------------------------------------------------------------------------------
1 | options = array(
40 | CURLOPT_RETURNTRANSFER => true,
41 | CURLOPT_HEADER => true,
42 | CURLOPT_USERPWD => "api:" . $key,
43 | CURLOPT_CAINFO => self::caBundle(),
44 | CURLOPT_SSL_VERIFYPEER => true,
45 | CURLOPT_USERAGENT => join(" ", array_filter(array(self::userAgent(), $app_identifier))),
46 | CURLOPT_SSLVERSION => $tlsVersion,
47 | );
48 |
49 | if ($proxy) {
50 | $parts = parse_url($proxy);
51 | if (isset($parts["host"])) {
52 | $this->options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP;
53 | $this->options[CURLOPT_PROXY] = $parts["host"];
54 | } else {
55 | throw new ConnectionException("Invalid proxy");
56 | }
57 |
58 | if (isset($parts["port"])) {
59 | $this->options[CURLOPT_PROXYPORT] = $parts["port"];
60 | }
61 |
62 | $creds = "";
63 | if (isset($parts["user"])) $creds .= $parts["user"];
64 | if (isset($parts["pass"])) $creds .= ":" . $parts["pass"];
65 |
66 | if ($creds) {
67 | $this->options[CURLOPT_PROXYAUTH] = CURLAUTH_ANY;
68 | $this->options[CURLOPT_PROXYUSERPWD] = $creds;
69 | }
70 | }
71 | }
72 |
73 | function request($method, $url, $body = NULL) {
74 | $header = array();
75 | if (is_array($body)) {
76 | if (!empty($body)) {
77 | $body = json_encode($body);
78 | array_push($header, "Content-Type: application/json");
79 | } else {
80 | $body = NULL;
81 | }
82 | }
83 |
84 | for ($retries = self::RETRY_COUNT; $retries >= 0; $retries--) {
85 | if ($retries < self::RETRY_COUNT) {
86 | usleep(self::RETRY_DELAY * 1000);
87 | }
88 |
89 | $request = curl_init();
90 | if ($request === false || $request === null) {
91 | throw new ConnectionException(
92 | "Error while connecting: curl extension is not functional or disabled."
93 | );
94 | }
95 |
96 | curl_setopt_array($request, $this->options);
97 |
98 | $url = strtolower(substr($url, 0, 6)) == "https:" ? $url : self::API_ENDPOINT . $url;
99 | curl_setopt($request, CURLOPT_URL, $url);
100 | curl_setopt($request, CURLOPT_CUSTOMREQUEST, strtoupper($method));
101 |
102 | if (count($header) > 0) {
103 | curl_setopt($request, CURLOPT_HTTPHEADER, $header);
104 | }
105 |
106 | if ($body) {
107 | curl_setopt($request, CURLOPT_POSTFIELDS, $body);
108 | }
109 |
110 | $response = curl_exec($request);
111 |
112 | if (is_string($response)) {
113 | $status = curl_getinfo($request, CURLINFO_HTTP_CODE);
114 | $headerSize = curl_getinfo($request, CURLINFO_HEADER_SIZE);
115 | if (PHP_VERSION_ID < 80000) {
116 | curl_close($request);
117 | } else {
118 | unset($request);
119 | }
120 |
121 | $headers = self::parseHeaders(substr($response, 0, $headerSize));
122 | $responseBody = substr($response, $headerSize);
123 |
124 | if (isset($headers["compression-count"])) {
125 | Tinify::setCompressionCount(intval($headers["compression-count"]));
126 | }
127 |
128 | if ($status >= 200 && $status <= 299) {
129 | return (object) array("body" => $responseBody, "headers" => $headers);
130 | }
131 |
132 | $details = json_decode($responseBody);
133 | if (!$details) {
134 | $message = sprintf("Error while parsing response: %s (#%d)",
135 | PHP_VERSION_ID >= 50500 ? json_last_error_msg() : "Error",
136 | json_last_error());
137 | $details = (object) array(
138 | "message" => $message,
139 | "error" => "ParseError"
140 | );
141 | }
142 |
143 | if ($retries > 0 && $status >= 500) continue;
144 | throw Exception::create($details->message, $details->error, $status);
145 | } else {
146 | $message = sprintf("%s (#%d)", curl_error($request), curl_errno($request));
147 | if (PHP_VERSION_ID < 80000) {
148 | curl_close($request);
149 | } else {
150 | unset($request);
151 | }
152 | if ($retries > 0) continue;
153 | throw new ConnectionException("Error while connecting: " . $message);
154 | }
155 | }
156 | }
157 |
158 | protected static function parseHeaders($headers) {
159 | if (!is_array($headers)) {
160 | $headers = explode("\r\n", $headers);
161 | }
162 |
163 | $res = array();
164 | foreach ($headers as $header) {
165 | if (empty($header)) continue;
166 | $split = explode(":", $header, 2);
167 | if (count($split) === 2) {
168 | $res[strtolower($split[0])] = trim($split[1]);
169 | }
170 | }
171 | return $res;
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/test/TinifyClientTest.php:
--------------------------------------------------------------------------------
1 | 200));
8 | $client = new Tinify\Client("key");
9 | $client->request("get", "/");
10 |
11 | $this->assertSame("https://api.tinify.com/", CurlMock::last(CURLOPT_URL));
12 | $this->assertSame("api:key", CurlMock::last(CURLOPT_USERPWD));
13 | }
14 |
15 | public function testRequestWhenValidShouldIssueRequestWithoutBodyWhenOptionsAreEmpty() {
16 | CurlMock::register("https://api.tinify.com/", array("status" => 200));
17 | $client = new Tinify\Client("key");
18 | $client->request("get", "/", array());
19 |
20 | $this->assertFalse(CurlMock::last_has(CURLOPT_POSTFIELDS));
21 | }
22 |
23 | public function testRequestWhenValidShouldIssueRequestWithoutContentTypeWhenOptionsAreEmpty() {
24 | CurlMock::register("https://api.tinify.com/", array("status" => 200));
25 | $client = new Tinify\Client("key");
26 | $client->request("get", "/", array());
27 |
28 | $this->assertFalse(CurlMock::last_has(CURLOPT_HTTPHEADER));
29 | }
30 |
31 | public function testRequestWhenValidShouldIssueRequestWithJSONBody() {
32 | CurlMock::register("https://api.tinify.com/", array("status" => 200));
33 | $client = new Tinify\Client("key");
34 | $client->request("get", "/", array("hello" => "world"));
35 |
36 | $this->assertSame(array("Content-Type: application/json"), CurlMock::last(CURLOPT_HTTPHEADER));
37 | $this->assertSame('{"hello":"world"}', CurlMock::last(CURLOPT_POSTFIELDS));
38 | }
39 |
40 | public function testRequestWhenValidShouldIssueRequestWithUserAgent() {
41 | CurlMock::register("https://api.tinify.com/", array("status" => 200));
42 | $client = new Tinify\Client("key");
43 | $client->request("get", "/");
44 |
45 | $this->assertSame(Tinify\Client::userAgent(), CurlMock::last(CURLOPT_USERAGENT));
46 | }
47 |
48 | public function testRequestWhenValidShouldUpdateCompressionCount() {
49 | CurlMock::register("https://api.tinify.com/", array(
50 | "status" => 200, "headers" => array("Compression-Count" => "12")
51 | ));
52 | $client = new Tinify\Client("key");
53 | $client->request("get", "/");
54 |
55 | $this->assertSame(12, Tinify\getCompressionCount());
56 | }
57 |
58 | public function testRequestWhenValidWithAppIdShouldIssueRequestWithUserAgent() {
59 | CurlMock::register("https://api.tinify.com/", array("status" => 200));
60 | $client = new Tinify\Client("key", "TestApp/0.1");
61 | $client->request("get", "/");
62 |
63 | $this->assertSame(Tinify\Client::userAgent() . " TestApp/0.1", CurlMock::last(CURLOPT_USERAGENT));
64 | }
65 |
66 | public function testRequestWhenValidWithProxyShouldIssueRequestWithProxyAuthorization() {
67 | CurlMock::register("https://api.tinify.com/", array("status" => 200));
68 | $client = new Tinify\Client("key", NULL, "http://user:pass@localhost:8080");
69 | $client->request("get", "/");
70 |
71 | $this->assertSame("localhost", CurlMock::last(CURLOPT_PROXY));
72 | $this->assertSame(8080, CurlMock::last(CURLOPT_PROXYPORT));
73 | $this->assertSame("user:pass", CurlMock::last(CURLOPT_PROXYUSERPWD));
74 | }
75 |
76 | public function testRequestWithUnexpectedErrorOnceShouldReturnResponse() {
77 | CurlMock::register("https://api.tinify.com/", array(
78 | "error" => "Failed!", "errno" => 2
79 | ));
80 |
81 | CurlMock::register("https://api.tinify.com/", array("status" => 201));
82 |
83 | $client = new Tinify\Client("key");
84 | $response = $client->request("get", "/");
85 | $this->assertEquals("", $response->body);
86 | }
87 |
88 | public function testRequestWithUnexpectedErrorRepeatedlyShouldThrowConnectionException() {
89 | CurlMock::register("https://api.tinify.com/", array(
90 | "error" => "Failed!", "errno" => 2
91 | ));
92 |
93 | $this->setExpectedException("Tinify\ConnectionException");
94 | $client = new Tinify\Client("key");
95 | $client->request("get", "/");
96 | }
97 |
98 | public function testRequestWithUnexpectedErrorRepeatedlyShouldThrowExceptionWithMessage() {
99 | CurlMock::register("https://api.tinify.com/", array(
100 | "error" => "Failed!", "errno" => 2
101 | ));
102 |
103 | $this->setExpectedExceptionRegExp("Tinify\ConnectionException",
104 | "/Error while connecting: Failed! \(#2\)/");
105 | $client = new Tinify\Client("key");
106 | $client->request("get", "/");
107 | }
108 |
109 | public function testRequestWithCurlErrorOnceShouldReturnResponse() {
110 | CurlMock::register("https://api.tinify.com/", array(
111 | "errno" => 7, "error" => "Something failed", "return" => null
112 | ));
113 |
114 | CurlMock::register("https://api.tinify.com/", array("status" => 201));
115 |
116 | $client = new Tinify\Client("key");
117 | $response = $client->request("get", "/");
118 | $this->assertEquals("", $response->body);
119 | }
120 |
121 | public function testRequestWithCurlErrorRepeatedlyShouldThrowConnectionExeption() {
122 | CurlMock::register("https://api.tinify.com/", array(
123 | "errno" => 7, "error" => "Something failed", "return" => null
124 | ));
125 |
126 | $this->setExpectedException("Tinify\ConnectionException");
127 | $client = new Tinify\Client("key");
128 | $client->request("get", "/");
129 | }
130 |
131 | public function testRequestWithCurlErrorRepeatedlyShouldThrowExceptionWithMessage() {
132 | CurlMock::register("https://api.tinify.com/", array(
133 | "errno" => 7, "error" => "Something failed", "return" => null
134 | ));
135 |
136 | $this->setExpectedExceptionRegExp("Tinify\ConnectionException",
137 | "/Error while connecting/");
138 | $client = new Tinify\Client("key");
139 | $client->request("get", "/");
140 | }
141 |
142 | public function testRequestWithServerErrorOnceShouldReturnResponse() {
143 | CurlMock::register("https://api.tinify.com/", array(
144 | "status" => 584, "body" => '{"error":"InternalServerError","message":"Oops!"}'
145 | ));
146 |
147 | CurlMock::register("https://api.tinify.com/", array("status" => 201));
148 |
149 | $client = new Tinify\Client("key");
150 | $response = $client->request("get", "/");
151 | $this->assertEquals("", $response->body);
152 | }
153 |
154 | public function testRequestWithServerErrorRepeatedlyShouldThrowServerException() {
155 | CurlMock::register("https://api.tinify.com/", array(
156 | "status" => 584, "body" => '{"error":"InternalServerError","message":"Oops!"}'
157 | ));
158 |
159 | $this->setExpectedException("Tinify\ServerException");
160 | $client = new Tinify\Client("key");
161 | $client->request("get", "/");
162 | }
163 |
164 | public function testRequestWithBodyAndServerErrorRepeatedlyShouldThrowServerException() {
165 | CurlMock::register("https://api.tinify.com/", array(
166 | "body" => '{"email":"user@gmail.com"}'
167 | ), array(
168 | "status" => 500, "body" => '{"error":"InternalServerError","message":"Oops!"}'
169 | ));
170 |
171 | $this->setExpectedException("Tinify\ServerException");
172 | $client = new Tinify\Client("key");
173 | $response = $client->request("post", "/", array("email"=> "user@gmail.com"));
174 | $this->assertEquals("", $response->body);
175 | }
176 |
177 | public function testRequestWithServerErrorRepeatedlyShouldThrowExceptionWithMessage() {
178 | CurlMock::register("https://api.tinify.com/", array(
179 | "status" => 584, "body" => '{"error":"InternalServerError","message":"Oops!"}'
180 | ));
181 |
182 | $this->setExpectedExceptionRegExp("Tinify\ServerException",
183 | "/Oops! \(HTTP 584\/InternalServerError\)/");
184 | $client = new Tinify\Client("key");
185 | $client->request("get", "/");
186 | }
187 |
188 | public function testRequestWithBadServerResponseOnceShouldReturnResponse() {
189 | CurlMock::register("https://api.tinify.com/", array(
190 | "status" => 543, "body" => ''
191 | ));
192 |
193 | CurlMock::register("https://api.tinify.com/", array("status" => 201));
194 |
195 | $client = new Tinify\Client("key");
196 | $response = $client->request("get", "/");
197 | $this->assertEquals("", $response->body);
198 | }
199 |
200 | public function testRequestWithBadServerResponseRepeatedlyShouldThrowServerException() {
201 | CurlMock::register("https://api.tinify.com/", array(
202 | "status" => 543, "body" => ''
203 | ));
204 |
205 | $this->setExpectedException("Tinify\ServerException");
206 | $client = new Tinify\Client("key");
207 | $client->request("get", "/");
208 | }
209 |
210 | public function testRequestWithBadServerResponseRepeatedlyShouldThrowExceptionWithMessage() {
211 | CurlMock::register("https://api.tinify.com/", array(
212 | "status" => 543, "body" => ''
213 | ));
214 |
215 | if (PHP_VERSION_ID >= 50500) {
216 | $this->setExpectedExceptionRegExp("Tinify\ServerException",
217 | "/Error while parsing response: Syntax error \(#4\) \(HTTP 543\/ParseError\)/");
218 | } else {
219 | $this->setExpectedExceptionRegExp("Tinify\ServerException",
220 | "/Error while parsing response: Error \(#4\) \(HTTP 543\/ParseError\)/");
221 | }
222 |
223 | $client = new Tinify\Client("key");
224 | $client->request("get", "/");
225 | }
226 |
227 | public function testRequestWithClientErrorShouldThrowClientException() {
228 | CurlMock::register("https://api.tinify.com/", array(
229 | "status" => 492, "body" => '{"error":"BadRequest","message":"Oops!"}')
230 | );
231 |
232 | CurlMock::register("https://api.tinify.com/", array("status" => 201));
233 |
234 | $this->setExpectedException("Tinify\ClientException");
235 | $client = new Tinify\Client("key");
236 | $client->request("get", "/");
237 | }
238 |
239 | public function testRequestWithClientErrorShouldThrowExceptionWithMessage() {
240 | CurlMock::register("https://api.tinify.com/", array(
241 | "status" => 492, "body" => '{"error":"BadRequest","message":"Oops!"}'
242 | ));
243 |
244 | CurlMock::register("https://api.tinify.com/", array("status" => 201));
245 |
246 | $this->setExpectedExceptionRegExp("Tinify\ClientException",
247 | "/Oops! \(HTTP 492\/BadRequest\)/");
248 | $client = new Tinify\Client("key");
249 | $client->request("get", "/");
250 | }
251 |
252 | public function testRequestWithBadCredentialsShouldThrowAccountException() {
253 | CurlMock::register("https://api.tinify.com/", array(
254 | "status" => 401, "body" => '{"error":"Unauthorized","message":"Oops!"}'
255 | ));
256 |
257 | CurlMock::register("https://api.tinify.com/", array("status" => 201));
258 |
259 | $this->setExpectedException("Tinify\AccountException");
260 | $client = new Tinify\Client("key");
261 | $client->request("get", "/");
262 | }
263 |
264 | public function testRequestWithBadCredentialsShouldThrowExceptionWithMessage() {
265 | CurlMock::register("https://api.tinify.com/", array(
266 | "status" => 401, "body" => '{"error":"Unauthorized","message":"Oops!"}'
267 | ));
268 |
269 | CurlMock::register("https://api.tinify.com/", array("status" => 201));
270 |
271 | $this->setExpectedExceptionRegExp("Tinify\AccountException",
272 | "/Oops! \(HTTP 401\/Unauthorized\)/");
273 | $client = new Tinify\Client("key");
274 | $client->request("get", "/");
275 | }
276 |
277 | public function testRequestWithNoSSLCurlShouldThrowExceptionWithMessage() {
278 | CurlMock::register("https://api.tinify.com/", array("status" => 200));
279 | CurlMock::set_version_info_key("features", (CURL_VERSION_LIBZ | CURL_VERSION_IPV6));
280 | $this->setExpectedException("Tinify\ClientException",
281 | "Your curl version does not support secure connections");
282 | $client = new Tinify\Client("key");
283 | $client->request("get", "/");
284 | }
285 |
286 | public function testRequestWithOutdatedCurlShouldThrowExceptionWithMessage() {
287 | CurlMock::register("https://api.tinify.com/", array("status" => 200));
288 | CurlMock::set_version_info_key("version_number", 0x070f05);
289 | CurlMock::set_version_info_key("version", "7.15.5");
290 | $this->setExpectedException("Tinify\ClientException",
291 | "Your curl version 7.15.5 is outdated; please upgrade to 7.18.1 or higher");
292 | $client = new Tinify\Client("key");
293 | $client->request("get", "/");
294 | }
295 | }
296 |
--------------------------------------------------------------------------------
/test/TinifySourceTest.php:
--------------------------------------------------------------------------------
1 | 401, "body" => '{"error":"Unauthorized","message":"Credentials are invalid"}'
11 | ));
12 |
13 | $this->setExpectedException("Tinify\AccountException");
14 | Tinify\Source::fromFile(DUMMY_FILE_LOCATION);
15 | }
16 |
17 | public function testWithInvalidApiKeyFromBufferShouldThrowAccountException() {
18 | Tinify\setKey("invalid");
19 |
20 | CurlMock::register("https://api.tinify.com/shrink", array(
21 | "status" => 401, "body" => '{"error":"Unauthorized","message":"Credentials are invalid"}'
22 | ));
23 |
24 | $this->setExpectedException("Tinify\AccountException");
25 | Tinify\Source::fromBuffer("png file");
26 | }
27 |
28 | public function testWithInvalidApiKeyFromUrlShouldThrowAccountException() {
29 | Tinify\setKey("invalid");
30 |
31 | CurlMock::register("https://api.tinify.com/shrink", array(
32 | "status" => 401, "body" => '{"error":"Unauthorized","message":"Credentials are invalid"}'
33 | ));
34 |
35 | $this->setExpectedException("Tinify\AccountException");
36 | Tinify\Source::fromUrl("http://example.com/test.jpg");
37 | }
38 |
39 | public function testFromFileShouldReturnSource() {
40 | Tinify\setKey("valid");
41 |
42 | CurlMock::register("https://api.tinify.com/shrink", array(
43 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
44 | ));
45 |
46 | $this->assertInstanceOf("Tinify\Source", Tinify\Source::fromFile(DUMMY_FILE_LOCATION));
47 | }
48 |
49 | public function testFromFileShouldReturnSourceWithData() {
50 | Tinify\setKey("valid");
51 |
52 | CurlMock::register("https://api.tinify.com/shrink", array(
53 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
54 | ));
55 |
56 | CurlMock::register("https://api.tinify.com/some/location", array(
57 | "status" => 200, "body" => "compressed file"
58 | ));
59 |
60 | $this->assertSame("compressed file", Tinify\Source::fromFile(DUMMY_FILE_LOCATION)->toBuffer());
61 | }
62 |
63 | public function testFromBufferShouldReturnSource() {
64 | Tinify\setKey("valid");
65 |
66 | CurlMock::register("https://api.tinify.com/shrink", array(
67 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
68 | ));
69 |
70 | $this->assertInstanceOf("Tinify\Source", Tinify\Source::fromBuffer("png file"));
71 | }
72 |
73 | public function testFromBufferShouldReturnSourceWithData() {
74 | Tinify\setKey("valid");
75 |
76 | CurlMock::register("https://api.tinify.com/shrink", array(
77 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
78 | ));
79 |
80 | CurlMock::register("https://api.tinify.com/some/location", array(
81 | "status" => 200, "body" => "compressed file"
82 | ));
83 |
84 | $this->assertSame("compressed file", Tinify\Source::fromBuffer("png file")->toBuffer());
85 | }
86 |
87 | public function testFromUrlShouldReturnSource() {
88 | Tinify\setKey("valid");
89 |
90 | CurlMock::register("https://api.tinify.com/shrink", array(
91 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
92 | ));
93 |
94 | $this->assertInstanceOf("Tinify\Source", Tinify\Source::fromUrl("http://example.com/test.jpg"));
95 | }
96 |
97 | public function testFromUrlShouldReturnSourceWithData() {
98 | Tinify\setKey("valid");
99 |
100 | CurlMock::register("https://api.tinify.com/shrink", array(
101 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
102 | ));
103 |
104 | CurlMock::register("https://api.tinify.com/some/location", array(
105 | "status" => 200, "body" => "compressed file"
106 | ));
107 |
108 | $this->assertSame("compressed file", Tinify\Source::fromUrl("http://example.com/test.jpg")->toBuffer());
109 | }
110 |
111 | public function testFromUrlShouldThrowExceptionIfRequestIsNotOK() {
112 | Tinify\setKey("valid");
113 |
114 | CurlMock::register("https://api.tinify.com/shrink", array(
115 | "status" => 400, "body" => '{"error":"Source not found","message":"Cannot parse URL"}'
116 | ));
117 |
118 | $this->setExpectedException("Tinify\ClientException");
119 | Tinify\Source::fromUrl("file://wrong");
120 | }
121 |
122 | public function testResultShouldReturnResult() {
123 | Tinify\setKey("valid");
124 |
125 | CurlMock::register("https://api.tinify.com/shrink", array(
126 | "status" => 201,
127 | "headers" => array("Location" => "https://api.tinify.com/some/location"),
128 | ));
129 |
130 | CurlMock::register("https://api.tinify.com/some/location", array(
131 | "status" => 200, "body" => "compressed file"
132 | ));
133 |
134 | $this->assertInstanceOf("Tinify\Result", Tinify\Source::fromBuffer("png file")->result());
135 | $this->assertSame("GET", CurlMock::last(CURLOPT_CUSTOMREQUEST));
136 | }
137 |
138 | /**
139 | * When request does not contain commands, it should use method GET
140 | * when it contains commands, it should have a body and method POST
141 | */
142 | public function testResultWithCommandsShouldReturnResultUsingPost() {
143 | Tinify\setKey("valid");
144 |
145 | CurlMock::register("https://api.tinify.com/shrink", array(
146 | "status" => 201,
147 | "headers" => array("Location" => "https://api.tinify.com/some/location"),
148 | ));
149 |
150 | CurlMock::register("https://api.tinify.com/some/location", array(
151 | "status" => 200, "body" => "resized file"
152 | ));
153 |
154 | $source = Tinify\Source::fromBuffer("png file")->resize(array("width" => 400));
155 | $result = $source->result();
156 |
157 | $this->assertInstanceOf("Tinify\Result", $result);
158 | $this->assertSame("POST", CurlMock::last(CURLOPT_CUSTOMREQUEST));
159 | $this->assertSame('{"resize":{"width":400}}', CurlMock::last(CURLOPT_POSTFIELDS));
160 | }
161 |
162 | public function testPreserveShouldReturnSource() {
163 | Tinify\setKey("valid");
164 |
165 | CurlMock::register("https://api.tinify.com/shrink", array(
166 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
167 | ));
168 |
169 | CurlMock::register("https://api.tinify.com/some/location", array(
170 | "status" => 200, "body" => "copyrighted file"
171 | ));
172 |
173 | $this->assertInstanceOf("Tinify\Source", Tinify\Source::fromBuffer("png file")->preserve("copyright", "location"));
174 | $this->assertSame("png file", CurlMock::last(CURLOPT_POSTFIELDS));
175 | }
176 |
177 | public function testPreserveShouldReturnSourceWithData() {
178 | Tinify\setKey("valid");
179 |
180 | CurlMock::register("https://api.tinify.com/shrink", array(
181 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
182 | ));
183 |
184 | CurlMock::register("https://api.tinify.com/some/location", array(
185 | "status" => 200, "body" => "copyrighted file"
186 | ));
187 |
188 | $this->assertSame("copyrighted file", Tinify\Source::fromBuffer("png file")->preserve("copyright", "location")->toBuffer());
189 | $this->assertSame("{\"preserve\":[\"copyright\",\"location\"]}", CurlMock::last(CURLOPT_POSTFIELDS));
190 | $this->assertSame("POST", CurlMock::last(CURLOPT_CUSTOMREQUEST));
191 | }
192 |
193 | public function testPreserveShouldReturnSourceWithDataForArray() {
194 | Tinify\setKey("valid");
195 |
196 | CurlMock::register("https://api.tinify.com/shrink", array(
197 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
198 | ));
199 |
200 | CurlMock::register("https://api.tinify.com/some/location", array(
201 | "status" => 200, "body" => "copyrighted file"
202 | ));
203 |
204 | $this->assertSame("copyrighted file", Tinify\Source::fromBuffer("png file")->preserve(array("copyright", "location"))->toBuffer());
205 | $this->assertSame("{\"preserve\":[\"copyright\",\"location\"]}", CurlMock::last(CURLOPT_POSTFIELDS));
206 | }
207 |
208 | public function testPreserveShouldIncludeOtherOptionsIfSet() {
209 | Tinify\setKey("valid");
210 |
211 | CurlMock::register("https://api.tinify.com/shrink", array(
212 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
213 | ));
214 |
215 | CurlMock::register("https://api.tinify.com/some/location", array(
216 | "status" => 200, "body" => "copyrighted resized file"
217 | ));
218 |
219 | $source = Tinify\Source::fromBuffer("png file")->resize(array("width" => 400))->preserve(array("copyright", "location"));
220 |
221 | $this->assertSame("copyrighted resized file", $source->toBuffer());
222 | $this->assertSame("{\"resize\":{\"width\":400},\"preserve\":[\"copyright\",\"location\"]}", CurlMock::last(CURLOPT_POSTFIELDS));
223 | }
224 |
225 | public function testResizeShouldReturnSource() {
226 | Tinify\setKey("valid");
227 |
228 | CurlMock::register("https://api.tinify.com/shrink", array(
229 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
230 | ));
231 |
232 | CurlMock::register("https://api.tinify.com/some/location", array(
233 | "status" => 200, "body" => "small file"
234 | ));
235 |
236 | $this->assertInstanceOf("Tinify\Source", Tinify\Source::fromBuffer("png file")->resize(array("width" => 400)));
237 | $this->assertSame("png file", CurlMock::last(CURLOPT_POSTFIELDS));
238 | }
239 |
240 | public function testResizeShouldReturnSourceWithData() {
241 | Tinify\setKey("valid");
242 |
243 | CurlMock::register("https://api.tinify.com/shrink", array(
244 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
245 | ));
246 |
247 | CurlMock::register("https://api.tinify.com/some/location", array(
248 | "status" => 200, "body" => "small file"
249 | ));
250 |
251 | $this->assertSame("small file", Tinify\Source::fromBuffer("png file")->resize(array("width" => 400))->toBuffer());
252 | $this->assertSame("{\"resize\":{\"width\":400}}", CurlMock::last(CURLOPT_POSTFIELDS));
253 | }
254 |
255 | public function testConvertShouldReturnSource() {
256 | Tinify\setKey("valid");
257 |
258 | CurlMock::register("https://api.tinify.com/shrink", array(
259 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
260 | ));
261 |
262 | CurlMock::register("https://api.tinify.com/some/location", array(
263 | "status" => 200, "body" => "Convertd file"
264 | ));
265 |
266 | $this->assertInstanceOf("Tinify\Source", Tinify\Source::fromBuffer("png file")->Convert(array("type" =>"image/webp")));
267 | $this->assertSame("png file", CurlMock::last(CURLOPT_POSTFIELDS));
268 | }
269 |
270 | public function testConvertShouldReturnSourceWithData() {
271 | Tinify\setKey("valid");
272 |
273 | CurlMock::register("https://api.tinify.com/shrink", array(
274 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
275 | ));
276 |
277 | CurlMock::register("https://api.tinify.com/some/location", array(
278 | "status" => 200, "body" => "Convertd file"
279 | ));
280 |
281 | $this->assertSame("Convertd file", Tinify\Source::fromBuffer("png file")->convert(array("type" => "image/webp"))->toBuffer());
282 | $this->assertSame("{\"convert\":{\"type\":\"image\/webp\"}}", CurlMock::last(CURLOPT_POSTFIELDS));
283 | }
284 |
285 | public function testTransformShouldReturnSource() {
286 | Tinify\setKey("valid");
287 |
288 | CurlMock::register("https://api.tinify.com/shrink", array(
289 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
290 | ));
291 |
292 | CurlMock::register("https://api.tinify.com/some/location", array(
293 | "status" => 200, "body" => "transformed file"
294 | ));
295 |
296 | $this->assertInstanceOf("Tinify\Source", Tinify\Source::fromBuffer("png file")->transform(array("background" => "black")));
297 | $this->assertSame("png file", CurlMock::last(CURLOPT_POSTFIELDS));
298 | }
299 |
300 | public function testTransformShouldReturnSourceWithData() {
301 | Tinify\setKey("valid");
302 |
303 | CurlMock::register("https://api.tinify.com/shrink", array(
304 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
305 | ));
306 |
307 | CurlMock::register("https://api.tinify.com/some/location", array(
308 | "status" => 200, "body" => "transformd file"
309 | ));
310 |
311 | $this->assertSame("transformd file", Tinify\Source::fromBuffer("png file")->transform(array("background" => "black"))->toBuffer());
312 | $this->assertSame("{\"transform\":{\"background\":\"black\"}}", CurlMock::last(CURLOPT_POSTFIELDS));
313 | }
314 |
315 | public function testStoreShouldReturnResultMeta() {
316 | Tinify\setKey("valid");
317 |
318 | CurlMock::register("https://api.tinify.com/shrink", array(
319 | "status" => 201,
320 | "headers" => array("Location" => "https://api.tinify.com/some/location"),
321 | ));
322 |
323 | CurlMock::register("https://api.tinify.com/some/location", array(
324 | "body" => '{"store":{"service":"s3","aws_secret_access_key":"abcde"}}'
325 | ), array("status" => 200));
326 |
327 | $options = array("service" => "s3", "aws_secret_access_key" => "abcde");
328 | $this->assertInstanceOf("Tinify\Result", Tinify\Source::fromBuffer("png file")->store($options));
329 | $this->assertSame("{\"store\":{\"service\":\"s3\",\"aws_secret_access_key\":\"abcde\"}}", CurlMock::last(CURLOPT_POSTFIELDS));
330 | }
331 |
332 | public function testStoreShouldReturnResultMetaWithLocation() {
333 | Tinify\setKey("valid");
334 |
335 | CurlMock::register("https://api.tinify.com/shrink", array(
336 | "status" => 201,
337 | "headers" => array("Location" => "https://api.tinify.com/some/location"),
338 | ));
339 |
340 | CurlMock::register("https://api.tinify.com/some/location", array(
341 | "body" => '{"store":{"service":"s3"}}'
342 | ), array(
343 | "status" => 201,
344 | "headers" => array("Location" => "https://bucket.s3.amazonaws.com/example"),
345 | ));
346 |
347 | $location = Tinify\Source::fromBuffer("png file")->store(array("service" => "s3"))->location();
348 | $this->assertSame("https://bucket.s3.amazonaws.com/example", $location);
349 | $this->assertSame("{\"store\":{\"service\":\"s3\"}}", CurlMock::last(CURLOPT_POSTFIELDS));
350 | }
351 |
352 | public function testStoreShouldIncludeOtherOptionsIfSet() {
353 | Tinify\setKey("valid");
354 |
355 | CurlMock::register("https://api.tinify.com/shrink", array(
356 | "status" => 201,
357 | "headers" => array("Location" => "https://api.tinify.com/some/location"),
358 | ));
359 |
360 | CurlMock::register("https://api.tinify.com/some/location", array(
361 | "body" => '{"resize":{"width":300},"store":{"service":"s3","aws_secret_access_key":"abcde"}}'
362 | ), array("status" => 200));
363 |
364 | $options = array("service" => "s3", "aws_secret_access_key" => "abcde");
365 | $this->assertInstanceOf("Tinify\Result", Tinify\Source::fromBuffer("png file")->resize(array("width" => 300))->store($options));
366 | $this->assertSame("{\"resize\":{\"width\":300},\"store\":{\"service\":\"s3\",\"aws_secret_access_key\":\"abcde\"}}", CurlMock::last(CURLOPT_POSTFIELDS));
367 | }
368 |
369 | public function testToBufferShouldReturnImageData() {
370 | Tinify\setKey("valid");
371 |
372 | CurlMock::register("https://api.tinify.com/shrink", array(
373 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
374 | ));
375 | CurlMock::register("https://api.tinify.com/some/location", array(
376 | "status" => 200, "body" => "compressed file"
377 | ));
378 |
379 | $this->assertSame("compressed file", Tinify\Source::fromBuffer("png file")->toBuffer());
380 | }
381 |
382 | public function testToFileShouldStoreImageData() {
383 | Tinify\setKey("valid");
384 |
385 | CurlMock::register("https://api.tinify.com/shrink", array(
386 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location")
387 | ));
388 |
389 | CurlMock::register("https://api.tinify.com/some/location", array(
390 | "status" => 200, "body" => "compressed file"
391 | ));
392 |
393 | $path = tempnam(sys_get_temp_dir(), "tinify-php");
394 | Tinify\Source::fromBuffer("png file")->toFile($path);
395 | $this->assertSame("compressed file", file_get_contents($path));
396 | }
397 | }
398 |
--------------------------------------------------------------------------------