Client.php
6 months ago
Exception.php
4 years ago
Result.php
4 years ago
ResultMeta.php
1 year ago
Source.php
6 months ago
Client.php
215 lines
| 1 | <?php |
| 2 | |
| 3 | namespace Tinify; |
| 4 | |
| 5 | class Client { |
| 6 | const API_ENDPOINT = "https://api.tinify.com"; |
| 7 | |
| 8 | const RETRY_COUNT = 1; |
| 9 | const RETRY_DELAY = 500; |
| 10 | |
| 11 | protected $options; |
| 12 | |
| 13 | public static function userAgent() { |
| 14 | $curl = curl_version(); |
| 15 | return "Tinify/" . VERSION . " PHP/" . PHP_VERSION . " curl/" . $curl["version"]; |
| 16 | } |
| 17 | |
| 18 | private static function caBundle() { |
| 19 | return __DIR__ . "/../data/cacert.pem"; |
| 20 | } |
| 21 | |
| 22 | function __construct($key, $app_identifier = NULL, $proxy = NULL) { |
| 23 | $curl = curl_version(); |
| 24 | |
| 25 | if (!($curl["features"] & CURL_VERSION_SSL)) { |
| 26 | throw new ClientException("Your curl version does not support secure connections"); |
| 27 | } |
| 28 | |
| 29 | if ($curl["version_number"] < 0x071201) { |
| 30 | $version = $curl["version"]; |
| 31 | throw new ClientException("Your curl version {$version} is outdated; please upgrade to 7.18.1 or higher"); |
| 32 | } |
| 33 | |
| 34 | # Set minimum TLS version to 1.2, CURL_SSLVERSION_TLSv1_2 is not available in curl < 7.34.0 |
| 35 | # Additionally old PHP versions may not support this constant |
| 36 | $tlsVersion = ($curl["version_number"] < 0x072200) |
| 37 | ? 6 |
| 38 | : (defined('CURL_SSLVERSION_TLSv1_2') ? CURL_SSLVERSION_TLSv1_2 : 6); |
| 39 | $this->options = array( |
| 40 | CURLOPT_RETURNTRANSFER => true, |
| 41 | CURLOPT_HEADER => true, |
| 42 | CURLOPT_USERPWD => $key ? ("api:" . $key) : NULL, |
| 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 ( isset( $headers["compression-count-remaining"] ) ) { |
| 129 | Tinify::setRemainingCredits( intval( $headers["compression-count-remaining"] ) ); |
| 130 | } |
| 131 | |
| 132 | if ( isset( $headers["paying-state"] ) ) { |
| 133 | Tinify::setPayingState( $headers["paying-state"] ); |
| 134 | } |
| 135 | |
| 136 | $details = json_decode($responseBody); |
| 137 | if (!$details) { |
| 138 | $message = sprintf("Error while parsing response: %s (#%d)", |
| 139 | PHP_VERSION_ID >= 50500 ? json_last_error_msg() : "Error", |
| 140 | json_last_error()); |
| 141 | $details = (object) array( |
| 142 | "message" => $message, |
| 143 | "error" => "ParseError" |
| 144 | ); |
| 145 | } |
| 146 | |
| 147 | if ( isset( $headers["email-address"] ) ) { |
| 148 | Tinify::setEmailAddress( $headers["email-address"] ); |
| 149 | } |
| 150 | |
| 151 | $isJson = false; |
| 152 | if (isset($headers["content-type"])) { |
| 153 | /* Parse JSON response bodies. */ |
| 154 | list($contentType) = explode(";", $headers["content-type"], 2); |
| 155 | if (strtolower(trim($contentType)) == "application/json") { |
| 156 | $isJson = true; |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | /* 1xx and 3xx are unexpected and will be treated as error. */ |
| 161 | $isError = $status <= 199 || $status >= 300; |
| 162 | |
| 163 | if ($isJson || $isError) { |
| 164 | /* Parse JSON bodies, always interpret errors as JSON. */ |
| 165 | $responseBody = json_decode($responseBody); |
| 166 | if (!$responseBody) { |
| 167 | $message = sprintf("Error while parsing response: %s (#%d)", |
| 168 | PHP_VERSION_ID >= 50500 ? json_last_error_msg() : "Error", |
| 169 | json_last_error()); |
| 170 | if ($retries > 0 && $status >= 500) continue; |
| 171 | throw Exception::create($message, "ParseError", $status); |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | if ($isError) { |
| 176 | if ($retries > 0 && $status >= 500) continue; |
| 177 | /* When the key doesn't exist a 404 response is given. */ |
| 178 | if ($status == 404) { |
| 179 | throw Exception::create(null, null, $status); |
| 180 | } else { |
| 181 | throw Exception::create($responseBody->message, $responseBody->error, $status); |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | return (object) array("body" => $responseBody, "headers" => $headers); |
| 186 | } else { |
| 187 | $message = sprintf("%s (#%d)", curl_error($request), curl_errno($request)); |
| 188 | if (PHP_VERSION_ID < 80000) { |
| 189 | curl_close($request); |
| 190 | } else { |
| 191 | unset($request); |
| 192 | } |
| 193 | if ($retries > 0) continue; |
| 194 | throw new ConnectionException("Error while connecting: " . $message); |
| 195 | } |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | protected static function parseHeaders($headers) { |
| 200 | if (!is_array($headers)) { |
| 201 | $headers = explode("\r\n", $headers); |
| 202 | } |
| 203 | |
| 204 | $result = array(); |
| 205 | foreach ($headers as $header) { |
| 206 | if (empty($header)) continue; |
| 207 | $split = explode(":", $header, 2); |
| 208 | if (count($split) === 2) { |
| 209 | $result[strtolower($split[0])] = trim($split[1]); |
| 210 | } |
| 211 | } |
| 212 | return $result; |
| 213 | } |
| 214 | } |
| 215 |