| 1 |
<?php |
| 2 |
|
| 3 |
declare(strict_types=1); |
| 4 |
|
| 5 |
/* |
| 6 |
* This file is part of Optimole PHP SDK. |
| 7 |
* |
| 8 |
* (c) Optimole Team <friends@optimole.com> |
| 9 |
* |
| 10 |
* For the full copyright and license information, please view the LICENSE |
| 11 |
* file that was distributed with this source code. |
| 12 |
*/ |
| 13 |
|
| 14 |
namespace Optimole\Sdk\Offload; |
| 15 |
|
| 16 |
use Optimole\Sdk\Exception\BadResponseException; |
| 17 |
use Optimole\Sdk\Exception\InvalidArgumentException; |
| 18 |
use Optimole\Sdk\Exception\InvalidDashboardApiResponseException; |
| 19 |
use Optimole\Sdk\Exception\InvalidUploadApiResponseException; |
| 20 |
use Optimole\Sdk\Exception\RuntimeException; |
| 21 |
use Optimole\Sdk\Exception\UploadApiException; |
| 22 |
use Optimole\Sdk\Exception\UploadFailedException; |
| 23 |
use Optimole\Sdk\Exception\UploadLimitException; |
| 24 |
use Optimole\Sdk\Http\ClientInterface; |
| 25 |
use Optimole\Sdk\ValueObject\OffloadUsage; |
| 26 |
|
| 27 |
class Manager |
| 28 |
{ |
| 29 |
/** |
| 30 |
* The HTTP client. |
| 31 |
*/ |
| 32 |
private ClientInterface $httpClient; |
| 33 |
|
| 34 |
/** |
| 35 |
* The manager options. |
| 36 |
*/ |
| 37 |
private array $options; |
| 38 |
|
| 39 |
/** |
| 40 |
* Constructor. |
| 41 |
*/ |
| 42 |
public function __construct(ClientInterface $httpClient, array $options = []) |
| 43 |
{ |
| 44 |
if (empty($options['dashboard_api_url'])) { |
| 45 |
throw new InvalidArgumentException('Missing "dashboard_api_url" option'); |
| 46 |
} elseif (empty($options['upload_api_url'])) { |
| 47 |
throw new InvalidArgumentException('Missing "upload_api_url" option'); |
| 48 |
} |
| 49 |
|
| 50 |
$this->httpClient = $httpClient; |
| 51 |
$this->options = array_merge([ |
| 52 |
'upload_api_credentials' => [], |
| 53 |
], $options); |
| 54 |
|
| 55 |
$this->options['dashboard_api_url'] = rtrim($this->options['dashboard_api_url'], '/'); |
| 56 |
$this->options['upload_api_url'] = rtrim($this->options['upload_api_url'], '/'); |
| 57 |
} |
| 58 |
|
| 59 |
/** |
| 60 |
* Delete the image with the given image ID. |
| 61 |
*/ |
| 62 |
public function deleteImage(string $imageId): void |
| 63 |
{ |
| 64 |
try { |
| 65 |
$this->requestToUploadApi([ |
| 66 |
'id' => $imageId, |
| 67 |
'deleteUrl' => 'true', |
| 68 |
]); |
| 69 |
} catch (BadResponseException $exception) { |
| 70 |
} |
| 71 |
} |
| 72 |
|
| 73 |
/** |
| 74 |
* Get the image URL for the given image ID. |
| 75 |
*/ |
| 76 |
public function getImageUrl(string $imageId): ?string |
| 77 |
{ |
| 78 |
try { |
| 79 |
$response = $this->requestToUploadApi([ |
| 80 |
'id' => $imageId, |
| 81 |
'getUrl' => 'true', |
| 82 |
]); |
| 83 |
} catch (BadResponseException $exception) { |
| 84 |
return null; |
| 85 |
} |
| 86 |
|
| 87 |
if (empty($response['getUrl'])) { |
| 88 |
throw new InvalidUploadApiResponseException('Unable to get image URL from upload API'); |
| 89 |
} |
| 90 |
|
| 91 |
return (string) $response['getUrl']; |
| 92 |
} |
| 93 |
|
| 94 |
/** |
| 95 |
* Get the offload service usage. |
| 96 |
*/ |
| 97 |
public function getUsage(): OffloadUsage |
| 98 |
{ |
| 99 |
$response = $this->requestToDashboardApi(); |
| 100 |
|
| 101 |
if (!isset($response['data']['offload_limit'], $response['data']['offloaded_images'])) { |
| 102 |
throw new InvalidDashboardApiResponseException('Dashboard API did not return details about the offload service usage'); |
| 103 |
} |
| 104 |
|
| 105 |
return new OffloadUsage((int) $response['data']['offloaded_images'], (int) $response['data']['offload_limit']); |
| 106 |
} |
| 107 |
|
| 108 |
/** |
| 109 |
* Update the metadata of the image with the given ID. |
| 110 |
*/ |
| 111 |
public function updateImageMetadata(string $imageId, int $fileSize = 0, $height = 'auto', $width = 'auto', $originalUrl = ''): void |
| 112 |
{ |
| 113 |
if ('auto' !== $height && !is_int($height)) { |
| 114 |
throw new InvalidArgumentException('Image height must be "auto" or an integer.'); |
| 115 |
} elseif ('auto' !== $width && !is_int($width)) { |
| 116 |
throw new InvalidArgumentException('Image width must be "auto" or an integer.'); |
| 117 |
} |
| 118 |
|
| 119 |
$this->requestToUploadApi([ |
| 120 |
'id' => $imageId, |
| 121 |
'originalFileSize' => $fileSize, |
| 122 |
'height' => is_int($height) ? max(0, $height) : $height, |
| 123 |
'width' => is_int($width) ? max(0, $width) : $width, |
| 124 |
'originalUrl' => $originalUrl, |
| 125 |
'updateDynamo' => 'success', |
| 126 |
]); |
| 127 |
} |
| 128 |
|
| 129 |
/** |
| 130 |
* Upload an image to Optimole and return its image ID. |
| 131 |
*/ |
| 132 |
public function uploadImage(string $filename, string $imageUrl): string |
| 133 |
{ |
| 134 |
if (!file_exists($filename)) { |
| 135 |
throw new InvalidArgumentException(sprintf('File "%s" does not exist', $filename)); |
| 136 |
} elseif (!is_readable($filename)) { |
| 137 |
throw new InvalidArgumentException(sprintf('File "%s" is not readable', $filename)); |
| 138 |
} |
| 139 |
|
| 140 |
$fileMimeType = $this->getMimeType($filename); |
| 141 |
|
| 142 |
try { |
| 143 |
$response = $this->requestToUploadApi([ |
| 144 |
'originalUrl' => $imageUrl, |
| 145 |
]); |
| 146 |
} catch (BadResponseException $exception) { |
| 147 |
throw new UploadApiException('Unable to get signed URL from upload API', 0, $exception); |
| 148 |
} |
| 149 |
|
| 150 |
if (isset($response['error']) && 'limit_exceeded' === $response['error']) { |
| 151 |
throw new UploadLimitException(new OffloadUsage((int) $response['count'], (int) $response['limit'])); |
| 152 |
} elseif (isset($response['error'])) { |
| 153 |
throw new UploadApiException(sprintf('Upload API returned an error: %s', $response['error'])); |
| 154 |
} elseif (isset($response['count'], $response['limit']) && $response['count'] >= $response['limit']) { |
| 155 |
throw new UploadLimitException(new OffloadUsage((int) $response['count'], (int) $response['limit'])); |
| 156 |
} elseif (!isset($response['tableId'], $response['uploadUrl'])) { |
| 157 |
throw new InvalidUploadApiResponseException('Upload API did not return the table ID and upload URL'); |
| 158 |
} |
| 159 |
|
| 160 |
$imageId = (string) $response['tableId']; |
| 161 |
$uploadUrl = (string) $response['uploadUrl']; |
| 162 |
|
| 163 |
$image = file_get_contents($filename); |
| 164 |
|
| 165 |
if (false === $image) { |
| 166 |
throw new RuntimeException(sprintf('Unable to get file "%s" content', $filename)); |
| 167 |
} |
| 168 |
|
| 169 |
try { |
| 170 |
$this->httpClient->sendRequest('PUT', $uploadUrl, $image, [ |
| 171 |
'Content-Type' => $fileMimeType, |
| 172 |
]); |
| 173 |
} catch (BadResponseException $exception) { |
| 174 |
throw new UploadFailedException(sprintf('Unable to upload file "%s": %s', $filename, $exception->getMessage()), 0, $exception); |
| 175 |
} |
| 176 |
|
| 177 |
$imagesize = getimagesize($filename); |
| 178 |
|
| 179 |
$this->updateImageMetadata($imageId, filesize($filename) ?: 0, $imagesize && !empty($imagesize[1]) ? $imagesize[1] : 'auto', $imagesize && !empty($imagesize[0]) ? $imagesize[0] : 'auto', $imageUrl); |
| 180 |
|
| 181 |
return $imageId; |
| 182 |
} |
| 183 |
|
| 184 |
/** |
| 185 |
* Get the MIME type of the given file. |
| 186 |
*/ |
| 187 |
private function getMimeType(string $filename): string |
| 188 |
{ |
| 189 |
$finfo = finfo_open(FILEINFO_MIME_TYPE); |
| 190 |
|
| 191 |
if (false === $finfo) { |
| 192 |
throw new RuntimeException('Unable to open fileinfo database'); |
| 193 |
} |
| 194 |
|
| 195 |
$mimeType = finfo_file($finfo, $filename); |
| 196 |
|
| 197 |
finfo_close($finfo); |
| 198 |
|
| 199 |
if (false === $mimeType) { |
| 200 |
throw new RuntimeException(sprintf('Unable to get MIME type for file "%s"', $filename)); |
| 201 |
} |
| 202 |
|
| 203 |
return $mimeType; |
| 204 |
} |
| 205 |
|
| 206 |
/** |
| 207 |
* Get the upload API credentials from the dashboard API. |
| 208 |
*/ |
| 209 |
private function getUploadApiCredentialsFromDashboardApi(): array |
| 210 |
{ |
| 211 |
$response = $this->requestToDashboardApi(); |
| 212 |
|
| 213 |
if (!isset($response['data']['cdn_key'], $response['data']['cdn_secret'])) { |
| 214 |
throw new InvalidDashboardApiResponseException('Dashboard API did not return upload API credentials'); |
| 215 |
} |
| 216 |
|
| 217 |
return [ |
| 218 |
'userKey' => $response['data']['cdn_key'], |
| 219 |
'secret' => $response['data']['cdn_secret'], |
| 220 |
]; |
| 221 |
} |
| 222 |
|
| 223 |
/** |
| 224 |
* Make a request to the dashboard API. |
| 225 |
*/ |
| 226 |
private function requestToDashboardApi(): array |
| 227 |
{ |
| 228 |
return $this->httpClient->sendRequest('POST', sprintf('%s/optml/v2/account/details', $this->options['dashboard_api_url']), null, [ |
| 229 |
'Authorization' => sprintf('Bearer %s', $this->options['dashboard_api_key']), |
| 230 |
'Content-Type' => 'application/json', |
| 231 |
]); |
| 232 |
} |
| 233 |
|
| 234 |
/** |
| 235 |
* Make a request to the upload API. |
| 236 |
*/ |
| 237 |
private function requestToUploadApi(array $body): array |
| 238 |
{ |
| 239 |
if (!isset($this->options['upload_api_credentials']['userKey'], $this->options['upload_api_credentials']['secret'])) { |
| 240 |
$this->options['upload_api_credentials'] = $this->getUploadApiCredentialsFromDashboardApi(); |
| 241 |
} |
| 242 |
|
| 243 |
return $this->httpClient->sendRequest('POST', $this->options['upload_api_url'], array_merge($this->options['upload_api_credentials'], $body), [ |
| 244 |
'Content-Type' => 'application/json', |
| 245 |
]); |
| 246 |
} |
| 247 |
} |
| 248 |
|