├── .github └── workflows │ └── ci-cd.yaml ├── .gitignore ├── .travis.yml ├── CHANGES.md ├── LICENSE ├── README.md ├── composer.json ├── lib ├── Tinify.php ├── Tinify │ ├── Client.php │ ├── Exception.php │ ├── Result.php │ ├── ResultMeta.php │ └── Source.php └── data │ └── cacert.pem ├── phpunit.xml ├── test ├── TinifyClientTest.php ├── TinifyResultMetaTest.php ├── TinifyResultTest.php ├── TinifySourceTest.php ├── TinifyTest.php ├── base │ ├── test_case_1.php │ └── test_case_2.php ├── bootstrap.php ├── curl_mock.php ├── examples │ ├── dummy.png │ └── voormedia.png └── integration.php └── update-cacert.sh /.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.4", 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .phpunit.result.cache 3 | composer.lock 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | ## 1.6.3 2 | * Add minimum TLS 1.2 version to curl options as protocol negotiation on certain openssl/libcurl versions is flaky. 3 | 4 | ## 1.6.2 5 | * Remove deprecated curl constant (https://php.watch/versions/8.4/CURLOPT_BINARYTRANSFER-deprecated) 6 | 7 | ## 1.6.1 8 | * Fixed string interpolation for php 8.2: https://wiki.php.net/rfc/deprecate_dollar_brace_string_interpolation 9 | 10 | ## 1.6.0 11 | * Support to run the unittests on newer versions of PHP (5.5 +) 12 | * Add API methods for converting/transcoding and transformation 13 | * Add helper function for returning the compressed file extension 14 | 15 | ## 1.5.2 16 | * Fail early if version of curl/openssl is too old. 17 | 18 | ## 1.5.1 19 | * Expose status of exceptions. 20 | 21 | ## 1.5.0 22 | * Retry failed requests by default. 23 | * Throw clearer errors when curl is installed but disabled. 24 | 25 | ## 1.4.0 26 | * Support for HTTP proxies. 27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MIT License](http://img.shields.io/badge/license-MIT-green.svg) ](https://github.com/tinify/tinify-php/blob/main/LICENSE) 2 | [![CI_CD](https://github.com/tinify/tinify-php/actions/workflows/ci-cd.yaml/badge.svg?branch=master)](https://github.com/tinify/tinify-php/actions/workflows/ci-cd.yaml) 3 | [![Packagist Version](https://img.shields.io/packagist/v/tinify/tinify)](https://packagist.org/packages/tinify/tinify) 4 | [![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/tinify/tinify)](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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | curl_close($request); 116 | 117 | $headers = self::parseHeaders(substr($response, 0, $headerSize)); 118 | $responseBody = substr($response, $headerSize); 119 | 120 | if (isset($headers["compression-count"])) { 121 | Tinify::setCompressionCount(intval($headers["compression-count"])); 122 | } 123 | 124 | if ($status >= 200 && $status <= 299) { 125 | return (object) array("body" => $responseBody, "headers" => $headers); 126 | } 127 | 128 | $details = json_decode($responseBody); 129 | if (!$details) { 130 | $message = sprintf("Error while parsing response: %s (#%d)", 131 | PHP_VERSION_ID >= 50500 ? json_last_error_msg() : "Error", 132 | json_last_error()); 133 | $details = (object) array( 134 | "message" => $message, 135 | "error" => "ParseError" 136 | ); 137 | } 138 | 139 | if ($retries > 0 && $status >= 500) continue; 140 | throw Exception::create($details->message, $details->error, $status); 141 | } else { 142 | $message = sprintf("%s (#%d)", curl_error($request), curl_errno($request)); 143 | curl_close($request); 144 | if ($retries > 0) continue; 145 | throw new ConnectionException("Error while connecting: " . $message); 146 | } 147 | } 148 | } 149 | 150 | protected static function parseHeaders($headers) { 151 | if (!is_array($headers)) { 152 | $headers = explode("\r\n", $headers); 153 | } 154 | 155 | $res = array(); 156 | foreach ($headers as $header) { 157 | if (empty($header)) continue; 158 | $split = explode(":", $header, 2); 159 | if (count($split) === 2) { 160 | $res[strtolower($split[0])] = trim($split[1]); 161 | } 162 | } 163 | return $res; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/Tinify/ResultMeta.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 | -------------------------------------------------------------------------------- /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 | $response = Tinify::getClient()->request("get", $this->url, $this->commands); 57 | return new Result($response->headers, $response->body); 58 | } 59 | 60 | public function toFile($path) { 61 | return $this->result()->toFile($path); 62 | } 63 | 64 | public function toBuffer() { 65 | return $this->result()->toBuffer(); 66 | } 67 | 68 | private static function flatten($options) { 69 | $flattened = array(); 70 | foreach ($options as $option) { 71 | if (is_array($option)) { 72 | $flattened = array_merge($flattened, $option); 73 | } else { 74 | array_push($flattened, $option); 75 | } 76 | } 77 | return $flattened; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test 5 | 6 | 7 | 8 | 9 | lib 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | } 136 | 137 | public function testPreserveShouldReturnSource() { 138 | Tinify\setKey("valid"); 139 | 140 | CurlMock::register("https://api.tinify.com/shrink", array( 141 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location") 142 | )); 143 | 144 | CurlMock::register("https://api.tinify.com/some/location", array( 145 | "status" => 200, "body" => "copyrighted file" 146 | )); 147 | 148 | $this->assertInstanceOf("Tinify\Source", Tinify\Source::fromBuffer("png file")->preserve("copyright", "location")); 149 | $this->assertSame("png file", CurlMock::last(CURLOPT_POSTFIELDS)); 150 | } 151 | 152 | public function testPreserveShouldReturnSourceWithData() { 153 | Tinify\setKey("valid"); 154 | 155 | CurlMock::register("https://api.tinify.com/shrink", array( 156 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location") 157 | )); 158 | 159 | CurlMock::register("https://api.tinify.com/some/location", array( 160 | "status" => 200, "body" => "copyrighted file" 161 | )); 162 | 163 | $this->assertSame("copyrighted file", Tinify\Source::fromBuffer("png file")->preserve("copyright", "location")->toBuffer()); 164 | $this->assertSame("{\"preserve\":[\"copyright\",\"location\"]}", CurlMock::last(CURLOPT_POSTFIELDS)); 165 | } 166 | 167 | public function testPreserveShouldReturnSourceWithDataForArray() { 168 | Tinify\setKey("valid"); 169 | 170 | CurlMock::register("https://api.tinify.com/shrink", array( 171 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location") 172 | )); 173 | 174 | CurlMock::register("https://api.tinify.com/some/location", array( 175 | "status" => 200, "body" => "copyrighted file" 176 | )); 177 | 178 | $this->assertSame("copyrighted file", Tinify\Source::fromBuffer("png file")->preserve(array("copyright", "location"))->toBuffer()); 179 | $this->assertSame("{\"preserve\":[\"copyright\",\"location\"]}", CurlMock::last(CURLOPT_POSTFIELDS)); 180 | } 181 | 182 | public function testPreserveShouldIncludeOtherOptionsIfSet() { 183 | Tinify\setKey("valid"); 184 | 185 | CurlMock::register("https://api.tinify.com/shrink", array( 186 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location") 187 | )); 188 | 189 | CurlMock::register("https://api.tinify.com/some/location", array( 190 | "status" => 200, "body" => "copyrighted resized file" 191 | )); 192 | 193 | $source = Tinify\Source::fromBuffer("png file")->resize(array("width" => 400))->preserve(array("copyright", "location")); 194 | 195 | $this->assertSame("copyrighted resized file", $source->toBuffer()); 196 | $this->assertSame("{\"resize\":{\"width\":400},\"preserve\":[\"copyright\",\"location\"]}", CurlMock::last(CURLOPT_POSTFIELDS)); 197 | } 198 | 199 | public function testResizeShouldReturnSource() { 200 | Tinify\setKey("valid"); 201 | 202 | CurlMock::register("https://api.tinify.com/shrink", array( 203 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location") 204 | )); 205 | 206 | CurlMock::register("https://api.tinify.com/some/location", array( 207 | "status" => 200, "body" => "small file" 208 | )); 209 | 210 | $this->assertInstanceOf("Tinify\Source", Tinify\Source::fromBuffer("png file")->resize(array("width" => 400))); 211 | $this->assertSame("png file", CurlMock::last(CURLOPT_POSTFIELDS)); 212 | } 213 | 214 | public function testResizeShouldReturnSourceWithData() { 215 | Tinify\setKey("valid"); 216 | 217 | CurlMock::register("https://api.tinify.com/shrink", array( 218 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location") 219 | )); 220 | 221 | CurlMock::register("https://api.tinify.com/some/location", array( 222 | "status" => 200, "body" => "small file" 223 | )); 224 | 225 | $this->assertSame("small file", Tinify\Source::fromBuffer("png file")->resize(array("width" => 400))->toBuffer()); 226 | $this->assertSame("{\"resize\":{\"width\":400}}", CurlMock::last(CURLOPT_POSTFIELDS)); 227 | } 228 | 229 | public function testConvertShouldReturnSource() { 230 | Tinify\setKey("valid"); 231 | 232 | CurlMock::register("https://api.tinify.com/shrink", array( 233 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location") 234 | )); 235 | 236 | CurlMock::register("https://api.tinify.com/some/location", array( 237 | "status" => 200, "body" => "Convertd file" 238 | )); 239 | 240 | $this->assertInstanceOf("Tinify\Source", Tinify\Source::fromBuffer("png file")->Convert(array("type" =>"image/webp"))); 241 | $this->assertSame("png file", CurlMock::last(CURLOPT_POSTFIELDS)); 242 | } 243 | 244 | public function testConvertShouldReturnSourceWithData() { 245 | Tinify\setKey("valid"); 246 | 247 | CurlMock::register("https://api.tinify.com/shrink", array( 248 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location") 249 | )); 250 | 251 | CurlMock::register("https://api.tinify.com/some/location", array( 252 | "status" => 200, "body" => "Convertd file" 253 | )); 254 | 255 | $this->assertSame("Convertd file", Tinify\Source::fromBuffer("png file")->convert(array("type" => "image/webp"))->toBuffer()); 256 | $this->assertSame("{\"convert\":{\"type\":\"image\/webp\"}}", CurlMock::last(CURLOPT_POSTFIELDS)); 257 | } 258 | 259 | public function testTransformShouldReturnSource() { 260 | Tinify\setKey("valid"); 261 | 262 | CurlMock::register("https://api.tinify.com/shrink", array( 263 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location") 264 | )); 265 | 266 | CurlMock::register("https://api.tinify.com/some/location", array( 267 | "status" => 200, "body" => "transformed file" 268 | )); 269 | 270 | $this->assertInstanceOf("Tinify\Source", Tinify\Source::fromBuffer("png file")->transform(array("background" => "black"))); 271 | $this->assertSame("png file", CurlMock::last(CURLOPT_POSTFIELDS)); 272 | } 273 | 274 | public function testTransformShouldReturnSourceWithData() { 275 | Tinify\setKey("valid"); 276 | 277 | CurlMock::register("https://api.tinify.com/shrink", array( 278 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location") 279 | )); 280 | 281 | CurlMock::register("https://api.tinify.com/some/location", array( 282 | "status" => 200, "body" => "transformd file" 283 | )); 284 | 285 | $this->assertSame("transformd file", Tinify\Source::fromBuffer("png file")->transform(array("background" => "black"))->toBuffer()); 286 | $this->assertSame("{\"transform\":{\"background\":\"black\"}}", CurlMock::last(CURLOPT_POSTFIELDS)); 287 | } 288 | 289 | public function testStoreShouldReturnResultMeta() { 290 | Tinify\setKey("valid"); 291 | 292 | CurlMock::register("https://api.tinify.com/shrink", array( 293 | "status" => 201, 294 | "headers" => array("Location" => "https://api.tinify.com/some/location"), 295 | )); 296 | 297 | CurlMock::register("https://api.tinify.com/some/location", array( 298 | "body" => '{"store":{"service":"s3","aws_secret_access_key":"abcde"}}' 299 | ), array("status" => 200)); 300 | 301 | $options = array("service" => "s3", "aws_secret_access_key" => "abcde"); 302 | $this->assertInstanceOf("Tinify\Result", Tinify\Source::fromBuffer("png file")->store($options)); 303 | $this->assertSame("{\"store\":{\"service\":\"s3\",\"aws_secret_access_key\":\"abcde\"}}", CurlMock::last(CURLOPT_POSTFIELDS)); 304 | } 305 | 306 | public function testStoreShouldReturnResultMetaWithLocation() { 307 | Tinify\setKey("valid"); 308 | 309 | CurlMock::register("https://api.tinify.com/shrink", array( 310 | "status" => 201, 311 | "headers" => array("Location" => "https://api.tinify.com/some/location"), 312 | )); 313 | 314 | CurlMock::register("https://api.tinify.com/some/location", array( 315 | "body" => '{"store":{"service":"s3"}}' 316 | ), array( 317 | "status" => 201, 318 | "headers" => array("Location" => "https://bucket.s3.amazonaws.com/example"), 319 | )); 320 | 321 | $location = Tinify\Source::fromBuffer("png file")->store(array("service" => "s3"))->location(); 322 | $this->assertSame("https://bucket.s3.amazonaws.com/example", $location); 323 | $this->assertSame("{\"store\":{\"service\":\"s3\"}}", CurlMock::last(CURLOPT_POSTFIELDS)); 324 | } 325 | 326 | public function testStoreShouldIncludeOtherOptionsIfSet() { 327 | Tinify\setKey("valid"); 328 | 329 | CurlMock::register("https://api.tinify.com/shrink", array( 330 | "status" => 201, 331 | "headers" => array("Location" => "https://api.tinify.com/some/location"), 332 | )); 333 | 334 | CurlMock::register("https://api.tinify.com/some/location", array( 335 | "body" => '{"resize":{"width":300},"store":{"service":"s3","aws_secret_access_key":"abcde"}}' 336 | ), array("status" => 200)); 337 | 338 | $options = array("service" => "s3", "aws_secret_access_key" => "abcde"); 339 | $this->assertInstanceOf("Tinify\Result", Tinify\Source::fromBuffer("png file")->resize(array("width" => 300))->store($options)); 340 | $this->assertSame("{\"resize\":{\"width\":300},\"store\":{\"service\":\"s3\",\"aws_secret_access_key\":\"abcde\"}}", CurlMock::last(CURLOPT_POSTFIELDS)); 341 | } 342 | 343 | public function testToBufferShouldReturnImageData() { 344 | Tinify\setKey("valid"); 345 | 346 | CurlMock::register("https://api.tinify.com/shrink", array( 347 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location") 348 | )); 349 | CurlMock::register("https://api.tinify.com/some/location", array( 350 | "status" => 200, "body" => "compressed file" 351 | )); 352 | 353 | $this->assertSame("compressed file", Tinify\Source::fromBuffer("png file")->toBuffer()); 354 | } 355 | 356 | public function testToFileShouldStoreImageData() { 357 | Tinify\setKey("valid"); 358 | 359 | CurlMock::register("https://api.tinify.com/shrink", array( 360 | "status" => 201, "headers" => array("Location" => "https://api.tinify.com/some/location") 361 | )); 362 | 363 | CurlMock::register("https://api.tinify.com/some/location", array( 364 | "status" => 200, "body" => "compressed file" 365 | )); 366 | 367 | $path = tempnam(sys_get_temp_dir(), "tinify-php"); 368 | Tinify\Source::fromBuffer("png file")->toFile($path); 369 | $this->assertSame("compressed file", file_get_contents($path)); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /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/base/test_case_1.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 | -------------------------------------------------------------------------------- /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 $response; 21 | public $closed = false; 22 | 23 | public static function version_info() { 24 | return self::$version; 25 | } 26 | 27 | public static function set_version_info_key($key, $value) { 28 | self::$version[$key] = $value; 29 | } 30 | 31 | public static function register($url, $request, $response = NULL) { 32 | if (!$response) { 33 | $response = $request; 34 | $request = NULL; 35 | } 36 | 37 | if (!isset(self::$urls[$url])) { 38 | self::$urls[$url] = array(); 39 | } 40 | 41 | array_push(self::$urls[$url], array($request, $response)); 42 | } 43 | 44 | public static function reset() { 45 | self::$requests = array(); 46 | self::$urls = array(); 47 | self::$version = self::$defaultVersion; 48 | } 49 | 50 | public static function last_has($key) { 51 | $lastReq = self::$requests[count(self::$requests) - 1]; 52 | return array_key_exists($key, $lastReq->options); 53 | } 54 | 55 | public static function last($key = null) { 56 | $lastReq = self::$requests[count(self::$requests) - 1]; 57 | if ($key) { 58 | return $lastReq->options[$key]; 59 | } else { 60 | return $lastReq; 61 | } 62 | } 63 | 64 | public function close() { 65 | $this->closed = true; 66 | } 67 | 68 | public function exec() { 69 | if ($this->closed) { 70 | throw new CurlMockException("Curl already closed"); 71 | } 72 | array_push(self::$requests, $this); 73 | 74 | $queue = &self::$urls[$this->options[CURLOPT_URL]]; 75 | list($this->request, $this->response) = $queue[0]; 76 | 77 | /* Keep last request as fallback. */ 78 | if (count($queue) > 1) array_shift($queue); 79 | 80 | if ($this->request) { 81 | if ($this->request["body"]) { 82 | if ($this->options[CURLOPT_POSTFIELDS] != $this->request["body"]) { 83 | throw new Exception("Body '" . $this->options[CURLOPT_POSTFIELDS] . 84 | "' does not match expected '" . $this->request["body"] . "'"); 85 | } 86 | } 87 | } 88 | 89 | if (isset($this->response["headers"])) { 90 | $headers = ""; 91 | foreach ($this->response["headers"] as $header => $value) { 92 | $headers .= $header . ": " . $value . "\r\n"; 93 | } 94 | $this->response["headers"] = $headers . "\r\n"; 95 | } else { 96 | $this->response["headers"] = "\r\n"; 97 | } 98 | 99 | if (!isset($this->response["body"])) { 100 | $this->response["body"] = ""; 101 | } 102 | 103 | if (array_key_exists("return", $this->response)) { 104 | return $this->response["return"]; 105 | } else if (isset($this->response["status"])) { 106 | return $this->response["headers"] . $this->response["body"]; 107 | } else { 108 | return false; 109 | } 110 | } 111 | 112 | public function setopt_array($array) { 113 | if ($this->closed) { 114 | throw new CurlMockException("Curl already closed"); 115 | } 116 | foreach ($array as $key => $value) { 117 | $this->options[$key] = $value; 118 | } 119 | } 120 | 121 | public function setopt($key, $value) { 122 | if ($this->closed) { 123 | throw new CurlMockException("Curl already closed"); 124 | } 125 | $this->options[$key] = $value; 126 | } 127 | 128 | public function getinfo($key) { 129 | if ($this->closed) { 130 | throw new CurlMockException("Curl already closed"); 131 | } 132 | switch ($key) { 133 | case CURLINFO_HTTP_CODE: 134 | return isset($this->response["status"]) ? $this->response["status"] : 0; 135 | case CURLINFO_HEADER_SIZE: 136 | return strlen($this->response["headers"]); 137 | default: 138 | throw new Exception("Bad key $key"); 139 | } 140 | } 141 | 142 | public function error() { 143 | if ($this->closed) { 144 | throw new CurlMockException("Curl already closed"); 145 | } 146 | return $this->response["error"]; 147 | } 148 | 149 | public function errno() { 150 | if ($this->closed) { 151 | throw new CurlMockException("Curl already closed"); 152 | } 153 | return $this->response["errno"]; 154 | } 155 | } 156 | 157 | function curl_version() { 158 | return CurlMock::version_info(); 159 | } 160 | 161 | function curl_init() { 162 | return new CurlMock(); 163 | } 164 | 165 | function curl_exec($mock) { 166 | return $mock->exec(); 167 | } 168 | 169 | function curl_close($mock) { 170 | $mock->close(); 171 | } 172 | 173 | function curl_setopt_array($mock, $array) { 174 | return $mock->setopt_array($array); 175 | } 176 | 177 | function curl_setopt($mock, $key, $value) { 178 | return $mock->setopt($key, $value); 179 | } 180 | 181 | function curl_getinfo($mock, $key) { 182 | return $mock->getinfo($key); 183 | } 184 | 185 | function curl_error($mock) { 186 | return $mock->error(); 187 | } 188 | 189 | function curl_errno($mock) { 190 | return $mock->errno(); 191 | } 192 | -------------------------------------------------------------------------------- /test/examples/dummy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinify/tinify-php/aee1582a9b8874ca5ecead0aa54b294058c464df/test/examples/dummy.png -------------------------------------------------------------------------------- /test/examples/voormedia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinify/tinify-php/aee1582a9b8874ca5ecead0aa54b294058c464df/test/examples/voormedia.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------