Media.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. <?php
  2. /**
  3. * @package Grav\Common\Page
  4. *
  5. * @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Common\Page;
  9. use FilesystemIterator;
  10. use Grav\Common\Config\Config;
  11. use Grav\Common\Grav;
  12. use Grav\Common\Media\Interfaces\MediaObjectInterface;
  13. use Grav\Common\Yaml;
  14. use Grav\Common\Page\Medium\AbstractMedia;
  15. use Grav\Common\Page\Medium\GlobalMedia;
  16. use Grav\Common\Page\Medium\MediumFactory;
  17. use RocketTheme\Toolbox\File\File;
  18. use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
  19. use function in_array;
  20. /**
  21. * Class Media
  22. * @package Grav\Common\Page
  23. */
  24. class Media extends AbstractMedia
  25. {
  26. /** @var GlobalMedia */
  27. protected static $global;
  28. /** @var array */
  29. protected $standard_exif = ['FileSize', 'MimeType', 'height', 'width'];
  30. /**
  31. * @param string $path
  32. * @param array|null $media_order
  33. * @param bool $load
  34. */
  35. public function __construct($path, array $media_order = null, $load = true)
  36. {
  37. $this->setPath($path);
  38. $this->media_order = $media_order;
  39. $this->__wakeup();
  40. if ($load) {
  41. $this->init();
  42. }
  43. }
  44. /**
  45. * Initialize static variables on unserialize.
  46. */
  47. public function __wakeup()
  48. {
  49. if (null === static::$global) {
  50. // Add fallback to global media.
  51. static::$global = GlobalMedia::getInstance();
  52. }
  53. }
  54. /**
  55. * Return raw route to the page.
  56. *
  57. * @return string|null Route to the page or null if media isn't for a page.
  58. */
  59. public function getRawRoute(): ?string
  60. {
  61. $path = $this->getPath();
  62. if ($path) {
  63. /** @var Pages $pages */
  64. $pages = $this->getGrav()['pages'];
  65. $page = $pages->get($path);
  66. if ($page) {
  67. return $page->rawRoute();
  68. }
  69. }
  70. return null;
  71. }
  72. /**
  73. * Return page route.
  74. *
  75. * @return string|null Route to the page or null if media isn't for a page.
  76. */
  77. public function getRoute(): ?string
  78. {
  79. $path = $this->getPath();
  80. if ($path) {
  81. /** @var Pages $pages */
  82. $pages = $this->getGrav()['pages'];
  83. $page = $pages->get($path);
  84. if ($page) {
  85. return $page->route();
  86. }
  87. }
  88. return null;
  89. }
  90. /**
  91. * @param string $offset
  92. * @return bool
  93. */
  94. #[\ReturnTypeWillChange]
  95. public function offsetExists($offset)
  96. {
  97. return parent::offsetExists($offset) ?: isset(static::$global[$offset]);
  98. }
  99. /**
  100. * @param string $offset
  101. * @return MediaObjectInterface|null
  102. */
  103. #[\ReturnTypeWillChange]
  104. public function offsetGet($offset)
  105. {
  106. return parent::offsetGet($offset) ?: static::$global[$offset];
  107. }
  108. /**
  109. * Initialize class.
  110. *
  111. * @return void
  112. */
  113. protected function init()
  114. {
  115. $path = $this->getPath();
  116. // Handle special cases where page doesn't exist in filesystem.
  117. if (!$path || !is_dir($path)) {
  118. return;
  119. }
  120. $grav = Grav::instance();
  121. /** @var UniformResourceLocator $locator */
  122. $locator = $grav['locator'];
  123. /** @var Config $config */
  124. $config = $grav['config'];
  125. $exif_reader = isset($grav['exif']) ? $grav['exif']->getReader() : null;
  126. $media_types = array_keys($config->get('media.types', []));
  127. $iterator = new FilesystemIterator($path, FilesystemIterator::UNIX_PATHS | FilesystemIterator::SKIP_DOTS);
  128. $media = [];
  129. foreach ($iterator as $file => $info) {
  130. // Ignore folders and Markdown files.
  131. $filename = $info->getFilename();
  132. if (!$info->isFile() || $info->getExtension() === 'md' || $filename === 'frontmatter.yaml' || $filename === 'media.json' || strpos($filename, '.') === 0) {
  133. continue;
  134. }
  135. // Find out what type we're dealing with
  136. [$basename, $ext, $type, $extra] = $this->getFileParts($filename);
  137. if (!in_array(strtolower($ext), $media_types, true)) {
  138. continue;
  139. }
  140. if ($type === 'alternative') {
  141. $media["{$basename}.{$ext}"][$type][$extra] = ['file' => $file, 'size' => $info->getSize()];
  142. } else {
  143. $media["{$basename}.{$ext}"][$type] = ['file' => $file, 'size' => $info->getSize()];
  144. }
  145. }
  146. foreach ($media as $name => $types) {
  147. // First prepare the alternatives in case there is no base medium
  148. if (!empty($types['alternative'])) {
  149. /**
  150. * @var string|int $ratio
  151. * @var array $alt
  152. */
  153. foreach ($types['alternative'] as $ratio => &$alt) {
  154. $alt['file'] = $this->createFromFile($alt['file']);
  155. if (empty($alt['file'])) {
  156. unset($types['alternative'][$ratio]);
  157. } else {
  158. $alt['file']->set('size', $alt['size']);
  159. }
  160. }
  161. unset($alt);
  162. }
  163. $file_path = null;
  164. // Create the base medium
  165. if (empty($types['base'])) {
  166. if (!isset($types['alternative'])) {
  167. continue;
  168. }
  169. $max = max(array_keys($types['alternative']));
  170. $medium = $types['alternative'][$max]['file'];
  171. $file_path = $medium->path();
  172. $medium = MediumFactory::scaledFromMedium($medium, $max, 1)['file'];
  173. } else {
  174. $medium = $this->createFromFile($types['base']['file']);
  175. if ($medium) {
  176. $medium->set('size', $types['base']['size']);
  177. $file_path = $medium->path();
  178. }
  179. }
  180. if (empty($medium)) {
  181. continue;
  182. }
  183. // metadata file
  184. $meta_path = $file_path . '.meta.yaml';
  185. if (file_exists($meta_path)) {
  186. $types['meta']['file'] = $meta_path;
  187. } elseif ($file_path && $exif_reader && $medium->get('mime') === 'image/jpeg' && empty($types['meta']) && $config->get('system.media.auto_metadata_exif')) {
  188. $meta = $exif_reader->read($file_path);
  189. if ($meta) {
  190. $meta_data = $meta->getData();
  191. $meta_trimmed = array_diff_key($meta_data, array_flip($this->standard_exif));
  192. if ($meta_trimmed) {
  193. if ($locator->isStream($meta_path)) {
  194. $file = File::instance($locator->findResource($meta_path, true, true));
  195. } else {
  196. $file = File::instance($meta_path);
  197. }
  198. $file->save(Yaml::dump($meta_trimmed));
  199. $types['meta']['file'] = $meta_path;
  200. }
  201. }
  202. }
  203. if (!empty($types['meta'])) {
  204. $medium->addMetaFile($types['meta']['file']);
  205. }
  206. if (!empty($types['thumb'])) {
  207. // We will not turn it into medium yet because user might never request the thumbnail
  208. // not wasting any resources on that, maybe we should do this for medium in general?
  209. $medium->set('thumbnails.page', $types['thumb']['file']);
  210. }
  211. // Build missing alternatives
  212. if (!empty($types['alternative'])) {
  213. $alternatives = $types['alternative'];
  214. $max = max(array_keys($alternatives));
  215. for ($i=$max; $i > 1; $i--) {
  216. if (isset($alternatives[$i])) {
  217. continue;
  218. }
  219. $types['alternative'][$i] = MediumFactory::scaledFromMedium($alternatives[$max]['file'], $max, $i);
  220. }
  221. foreach ($types['alternative'] as $altMedium) {
  222. if ($altMedium['file'] != $medium) {
  223. $altWidth = $altMedium['file']->get('width');
  224. $medWidth = $medium->get('width');
  225. if ($altWidth && $medWidth) {
  226. $ratio = $altWidth / $medWidth;
  227. $medium->addAlternative($ratio, $altMedium['file']);
  228. }
  229. }
  230. }
  231. }
  232. $this->add($name, $medium);
  233. }
  234. }
  235. /**
  236. * @return string|null
  237. * @deprecated 1.6 Use $this->getPath() instead.
  238. */
  239. public function path(): ?string
  240. {
  241. return $this->getPath();
  242. }
  243. }