vendor/league/glide/src/Server.php line 517

Open in your IDE?
  1. <?php
  2. namespace League\Glide;
  3. use InvalidArgumentException;
  4. use League\Flysystem\FilesystemException as FilesystemV2Exception;
  5. use League\Flysystem\FilesystemOperator;
  6. use League\Glide\Api\ApiInterface;
  7. use League\Glide\Filesystem\FileNotFoundException;
  8. use League\Glide\Filesystem\FilesystemException;
  9. use League\Glide\Responses\ResponseFactoryInterface;
  10. class Server
  11. {
  12. /**
  13. * Source file system.
  14. *
  15. * @var FilesystemOperator
  16. */
  17. protected $source;
  18. /**
  19. * Source path prefix.
  20. *
  21. * @var string
  22. */
  23. protected $sourcePathPrefix;
  24. /**
  25. * Cache file system.
  26. *
  27. * @var FilesystemOperator
  28. */
  29. protected $cache;
  30. /**
  31. * Cache path prefix.
  32. *
  33. * @var string
  34. */
  35. protected $cachePathPrefix;
  36. /**
  37. * Temporary EXIF data directory.
  38. *
  39. * @var string
  40. */
  41. protected $tempDir;
  42. /**
  43. * Whether to group cache in folders.
  44. *
  45. * @var bool
  46. */
  47. protected $groupCacheInFolders = true;
  48. /**
  49. * Whether to cache with file extensions.
  50. *
  51. * @var bool
  52. */
  53. protected $cacheWithFileExtensions = false;
  54. /**
  55. * Image manipulation API.
  56. *
  57. * @var ApiInterface
  58. */
  59. protected $api;
  60. /**
  61. * Response factory.
  62. *
  63. * @var ResponseFactoryInterface|null
  64. */
  65. protected $responseFactory;
  66. /**
  67. * Base URL.
  68. *
  69. * @var string
  70. */
  71. protected $baseUrl;
  72. /**
  73. * Default image manipulations.
  74. *
  75. * @var array
  76. */
  77. protected $defaults = [];
  78. /**
  79. * Preset image manipulations.
  80. *
  81. * @var array
  82. */
  83. protected $presets = [];
  84. /**
  85. * Create Server instance.
  86. *
  87. * @param FilesystemOperator $source Source file system.
  88. * @param FilesystemOperator $cache Cache file system.
  89. * @param ApiInterface $api Image manipulation API.
  90. */
  91. public function __construct(FilesystemOperator $source, FilesystemOperator $cache, ApiInterface $api)
  92. {
  93. $this->setSource($source);
  94. $this->setCache($cache);
  95. $this->setApi($api);
  96. $this->tempDir = sys_get_temp_dir();
  97. }
  98. /**
  99. * Set source file system.
  100. *
  101. * @param FilesystemOperator $source Source file system.
  102. *
  103. * @return void
  104. */
  105. public function setSource(FilesystemOperator $source)
  106. {
  107. $this->source = $source;
  108. }
  109. /**
  110. * Get source file system.
  111. *
  112. * @return FilesystemOperator Source file system.
  113. */
  114. public function getSource()
  115. {
  116. return $this->source;
  117. }
  118. /**
  119. * Set source path prefix.
  120. *
  121. * @param string $sourcePathPrefix Source path prefix.
  122. *
  123. * @return void
  124. */
  125. public function setSourcePathPrefix($sourcePathPrefix)
  126. {
  127. $this->sourcePathPrefix = trim($sourcePathPrefix, '/');
  128. }
  129. /**
  130. * Get source path prefix.
  131. *
  132. * @return string Source path prefix.
  133. */
  134. public function getSourcePathPrefix()
  135. {
  136. return $this->sourcePathPrefix;
  137. }
  138. /**
  139. * Get source path.
  140. *
  141. * @param string $path Image path.
  142. *
  143. * @return string The source path.
  144. *
  145. * @throws FileNotFoundException
  146. */
  147. public function getSourcePath($path)
  148. {
  149. $path = trim($path, '/');
  150. $baseUrl = $this->baseUrl.'/';
  151. if (substr($path, 0, strlen($baseUrl)) === $baseUrl) {
  152. $path = trim(substr($path, strlen($baseUrl)), '/');
  153. }
  154. if ('' === $path) {
  155. throw new FileNotFoundException('Image path missing.');
  156. }
  157. if ($this->sourcePathPrefix) {
  158. $path = $this->sourcePathPrefix.'/'.$path;
  159. }
  160. return rawurldecode($path);
  161. }
  162. /**
  163. * Check if a source file exists.
  164. *
  165. * @param string $path Image path.
  166. *
  167. * @return bool Whether the source file exists.
  168. */
  169. public function sourceFileExists($path)
  170. {
  171. try {
  172. return $this->source->fileExists($this->getSourcePath($path));
  173. } catch (FilesystemV2Exception $exception) {
  174. return false;
  175. }
  176. }
  177. /**
  178. * Set base URL.
  179. *
  180. * @param string $baseUrl Base URL.
  181. *
  182. * @return void
  183. */
  184. public function setBaseUrl($baseUrl)
  185. {
  186. $this->baseUrl = trim($baseUrl, '/');
  187. }
  188. /**
  189. * Get base URL.
  190. *
  191. * @return string Base URL.
  192. */
  193. public function getBaseUrl()
  194. {
  195. return $this->baseUrl;
  196. }
  197. /**
  198. * Set cache file system.
  199. *
  200. * @param FilesystemOperator $cache Cache file system.
  201. *
  202. * @return void
  203. */
  204. public function setCache(FilesystemOperator $cache)
  205. {
  206. $this->cache = $cache;
  207. }
  208. /**
  209. * Get cache file system.
  210. *
  211. * @return FilesystemOperator Cache file system.
  212. */
  213. public function getCache()
  214. {
  215. return $this->cache;
  216. }
  217. /**
  218. * Set cache path prefix.
  219. *
  220. * @param string $cachePathPrefix Cache path prefix.
  221. *
  222. * @return void
  223. */
  224. public function setCachePathPrefix($cachePathPrefix)
  225. {
  226. $this->cachePathPrefix = trim($cachePathPrefix, '/');
  227. }
  228. /**
  229. * Get cache path prefix.
  230. *
  231. * @return string Cache path prefix.
  232. */
  233. public function getCachePathPrefix()
  234. {
  235. return $this->cachePathPrefix;
  236. }
  237. /**
  238. * Get temporary EXIF data directory.
  239. *
  240. * @return string
  241. */
  242. public function getTempDir()
  243. {
  244. return $this->tempDir;
  245. }
  246. /**
  247. * Set temporary EXIF data directory. This directory must be a local path and exists on the filesystem.
  248. *
  249. * @param string $tempDir
  250. *
  251. * @return void
  252. *
  253. * @throws InvalidArgumentException
  254. */
  255. public function setTempDir($tempDir)
  256. {
  257. if (!$tempDir || !is_dir($tempDir)) {
  258. throw new InvalidArgumentException(sprintf('Invalid temp dir provided: "%s" does not exist.', $tempDir));
  259. }
  260. $this->tempDir = rtrim($tempDir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR;
  261. }
  262. /**
  263. * Set the group cache in folders setting.
  264. *
  265. * @param bool $groupCacheInFolders Whether to group cache in folders.
  266. *
  267. * @return void
  268. */
  269. public function setGroupCacheInFolders($groupCacheInFolders)
  270. {
  271. $this->groupCacheInFolders = $groupCacheInFolders;
  272. }
  273. /**
  274. * Get the group cache in folders setting.
  275. *
  276. * @return bool Whether to group cache in folders.
  277. */
  278. public function getGroupCacheInFolders()
  279. {
  280. return $this->groupCacheInFolders;
  281. }
  282. /**
  283. * Set the cache with file extensions setting.
  284. *
  285. * @param bool $cacheWithFileExtensions Whether to cache with file extensions.
  286. *
  287. * @return void
  288. */
  289. public function setCacheWithFileExtensions($cacheWithFileExtensions)
  290. {
  291. $this->cacheWithFileExtensions = $cacheWithFileExtensions;
  292. }
  293. /**
  294. * Get the cache with file extensions setting.
  295. *
  296. * @return bool Whether to cache with file extensions.
  297. */
  298. public function getCacheWithFileExtensions()
  299. {
  300. return $this->cacheWithFileExtensions;
  301. }
  302. /**
  303. * Get cache path.
  304. *
  305. * @param string $path Image path.
  306. * @param array $params Image manipulation params.
  307. *
  308. * @return string Cache path.
  309. */
  310. public function getCachePath($path, array $params = [])
  311. {
  312. $sourcePath = $this->getSourcePath($path);
  313. if ($this->sourcePathPrefix) {
  314. $sourcePath = substr($sourcePath, strlen($this->sourcePathPrefix) + 1);
  315. }
  316. $params = $this->getAllParams($params);
  317. unset($params['s'], $params['p']);
  318. ksort($params);
  319. $md5 = md5($sourcePath.'?'.http_build_query($params));
  320. $cachedPath = $this->groupCacheInFolders ? $sourcePath.'/'.$md5 : $md5;
  321. if ($this->cachePathPrefix) {
  322. $cachedPath = $this->cachePathPrefix.'/'.$cachedPath;
  323. }
  324. if ($this->cacheWithFileExtensions) {
  325. $ext = (isset($params['fm']) ? $params['fm'] : pathinfo($path)['extension']);
  326. $ext = ('pjpg' === $ext) ? 'jpg' : $ext;
  327. $cachedPath .= '.'.$ext;
  328. }
  329. return $cachedPath;
  330. }
  331. /**
  332. * Check if a cache file exists.
  333. *
  334. * @param string $path Image path.
  335. * @param array $params Image manipulation params.
  336. *
  337. * @return bool Whether the cache file exists.
  338. */
  339. public function cacheFileExists($path, array $params)
  340. {
  341. try {
  342. return $this->cache->fileExists(
  343. $this->getCachePath($path, $params)
  344. );
  345. } catch (FilesystemV2Exception $exception) {
  346. return false;
  347. }
  348. }
  349. /**
  350. * Delete cached manipulations for an image.
  351. *
  352. * @param string $path Image path.
  353. *
  354. * @return bool Whether the delete succeeded.
  355. */
  356. public function deleteCache($path)
  357. {
  358. if (!$this->groupCacheInFolders) {
  359. throw new InvalidArgumentException('Deleting cached image manipulations is not possible when grouping cache into folders is disabled.');
  360. }
  361. try {
  362. $this->cache->deleteDirectory(
  363. dirname($this->getCachePath($path))
  364. );
  365. return true;
  366. } catch (FilesystemV2Exception $exception) {
  367. return false;
  368. }
  369. }
  370. /**
  371. * Set image manipulation API.
  372. *
  373. * @param ApiInterface $api Image manipulation API.
  374. *
  375. * @return void
  376. */
  377. public function setApi(ApiInterface $api)
  378. {
  379. $this->api = $api;
  380. }
  381. /**
  382. * Get image manipulation API.
  383. *
  384. * @return ApiInterface Image manipulation API.
  385. */
  386. public function getApi()
  387. {
  388. return $this->api;
  389. }
  390. /**
  391. * Set default image manipulations.
  392. *
  393. * @param array $defaults Default image manipulations.
  394. *
  395. * @return void
  396. */
  397. public function setDefaults(array $defaults)
  398. {
  399. $this->defaults = $defaults;
  400. }
  401. /**
  402. * Get default image manipulations.
  403. *
  404. * @return array Default image manipulations.
  405. */
  406. public function getDefaults()
  407. {
  408. return $this->defaults;
  409. }
  410. /**
  411. * Set preset image manipulations.
  412. *
  413. * @param array $presets Preset image manipulations.
  414. *
  415. * @return void
  416. */
  417. public function setPresets(array $presets)
  418. {
  419. $this->presets = $presets;
  420. }
  421. /**
  422. * Get preset image manipulations.
  423. *
  424. * @return array Preset image manipulations.
  425. */
  426. public function getPresets()
  427. {
  428. return $this->presets;
  429. }
  430. /**
  431. * Get all image manipulations params, including defaults and presets.
  432. *
  433. * @param array $params Image manipulation params.
  434. *
  435. * @return array All image manipulation params.
  436. */
  437. public function getAllParams(array $params)
  438. {
  439. $all = $this->defaults;
  440. if (isset($params['p'])) {
  441. foreach (explode(',', $params['p']) as $preset) {
  442. if (isset($this->presets[$preset])) {
  443. $all = array_merge($all, $this->presets[$preset]);
  444. }
  445. }
  446. }
  447. return array_merge($all, $params);
  448. }
  449. /**
  450. * Set response factory.
  451. *
  452. * @param ResponseFactoryInterface|null $responseFactory Response factory.
  453. *
  454. * @return void
  455. */
  456. public function setResponseFactory(ResponseFactoryInterface $responseFactory = null)
  457. {
  458. $this->responseFactory = $responseFactory;
  459. }
  460. /**
  461. * Get response factory.
  462. *
  463. * @return ResponseFactoryInterface|null Response factory.
  464. */
  465. public function getResponseFactory()
  466. {
  467. return $this->responseFactory;
  468. }
  469. /**
  470. * Generate and return image response.
  471. *
  472. * @param string $path Image path.
  473. * @param array $params Image manipulation params.
  474. *
  475. * @return mixed Image response.
  476. *
  477. * @throws InvalidArgumentException
  478. */
  479. public function getImageResponse($path, array $params)
  480. {
  481. if (is_null($this->responseFactory)) {
  482. throw new InvalidArgumentException('Unable to get image response, no response factory defined.');
  483. }
  484. $path = $this->makeImage($path, $params);
  485. return $this->responseFactory->create($this->cache, $path);
  486. }
  487. /**
  488. * Generate and return Base64 encoded image.
  489. *
  490. * @param string $path Image path.
  491. * @param array $params Image manipulation params.
  492. *
  493. * @return string Base64 encoded image.
  494. *
  495. * @throws FilesystemException
  496. */
  497. public function getImageAsBase64($path, array $params)
  498. {
  499. $path = $this->makeImage($path, $params);
  500. try {
  501. $source = $this->cache->read($path);
  502. return 'data:'.$this->cache->mimeType($path).';base64,'.base64_encode($source);
  503. } catch (FilesystemV2Exception $exception) {
  504. throw new FilesystemException('Could not read the image `'.$path.'`.');
  505. }
  506. }
  507. /**
  508. * Generate and output image.
  509. *
  510. * @param string $path Image path.
  511. * @param array $params Image manipulation params.
  512. *
  513. * @throws InvalidArgumentException
  514. *
  515. * @return void
  516. */
  517. public function outputImage($path, array $params)
  518. {
  519. $path = $this->makeImage($path, $params);
  520. try {
  521. header('Content-Type:'.$this->cache->mimeType($path));
  522. header('Content-Length:'.$this->cache->fileSize($path));
  523. header('Cache-Control:'.'max-age=31536000, public');
  524. header('Expires:'.date_create('+1 years')->format('D, d M Y H:i:s').' GMT');
  525. $stream = $this->cache->readStream($path);
  526. if (0 !== ftell($stream)) {
  527. rewind($stream);
  528. }
  529. fpassthru($stream);
  530. fclose($stream);
  531. } catch (FilesystemV2Exception $exception) {
  532. throw new FilesystemException('Could not read the image `'.$path.'`.');
  533. }
  534. }
  535. /**
  536. * Generate manipulated image.
  537. *
  538. * @param string $path Image path.
  539. * @param array $params Image manipulation params.
  540. *
  541. * @return string Cache path.
  542. *
  543. * @throws FileNotFoundException
  544. * @throws FilesystemException
  545. */
  546. public function makeImage($path, array $params)
  547. {
  548. $sourcePath = $this->getSourcePath($path);
  549. $cachedPath = $this->getCachePath($path, $params);
  550. if (true === $this->cacheFileExists($path, $params)) {
  551. return $cachedPath;
  552. }
  553. if (false === $this->sourceFileExists($path)) {
  554. throw new FileNotFoundException('Could not find the image `'.$sourcePath.'`.');
  555. }
  556. try {
  557. $source = $this->source->read(
  558. $sourcePath
  559. );
  560. } catch (FilesystemV2Exception $exception) {
  561. throw new FilesystemException('Could not read the image `'.$sourcePath.'`.');
  562. }
  563. // We need to write the image to the local disk before
  564. // doing any manipulations. This is because EXIF data
  565. // can only be read from an actual file.
  566. $tmp = tempnam($this->tempDir, 'Glide');
  567. if (false === file_put_contents($tmp, $source)) {
  568. throw new FilesystemException('Unable to write temp file for `'.$sourcePath.'`.');
  569. }
  570. try {
  571. $this->cache->write(
  572. $cachedPath,
  573. $this->api->run($tmp, $this->getAllParams($params))
  574. );
  575. } catch (FilesystemV2Exception $exception) {
  576. throw new FilesystemException('Could not write the image `'.$cachedPath.'`.');
  577. } finally {
  578. unlink($tmp);
  579. }
  580. return $cachedPath;
  581. }
  582. }