automagic-images.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. <?php
  2. namespace Grav\Plugin;
  3. use Grav\Common\Plugin;
  4. use Grav\Common\Page\Page;
  5. use Grav\Common\Flex\Types\Pages\PageObject;
  6. use RocketTheme\Toolbox\Event\Event;
  7. use Grav\Common\Data;
  8. use Grav\Common\Grav;
  9. use PHPHtmlParser\Dom;
  10. use PHPHtmlParser\Options;
  11. require_once 'adapters/imagick.php';
  12. require_once 'adapters/gd.php';
  13. /**
  14. * Class AutomagicImagesPlugin
  15. * @package Grav\Plugin
  16. */
  17. class AutomagicImagesPlugin extends Plugin
  18. {
  19. /**
  20. * @var string
  21. */
  22. protected $adapter;
  23. /**
  24. * @var array
  25. */
  26. protected $sizes;
  27. /**
  28. * @return array
  29. */
  30. public static function getSubscribedEvents()
  31. {
  32. return [
  33. 'onAdminSave' => ['onAdminSave', 0],
  34. 'onOutputGenerated' => ['onOutputGenerated', 0]
  35. ];
  36. }
  37. /**
  38. * Determine whether a particular dependency is installed.
  39. * @param string $adapter Either 'gd' or 'imagick'
  40. * @return bool
  41. */
  42. protected function dependencyCheck($adapter = 'gd')
  43. {
  44. if ($adapter === 'gd') {
  45. return extension_loaded('gd');
  46. }
  47. if ($adapter === 'imagick') {
  48. return class_exists('\Imagick');
  49. }
  50. }
  51. /**
  52. * Determine which adapter is preferred and whether or not it's available.
  53. * Construct an instance of that adapter and return it.
  54. * @param string $source - Source image path
  55. * @return mixed - Either an instance of ImagickAdapter, GDAdapter or false if none of the extensions were available
  56. */
  57. protected function getImageAdapter($source)
  58. {
  59. $imagick_exists = $this->dependencyCheck('imagick');
  60. $gd_exists = $this->dependencyCheck('gd');
  61. if ($this->adapter === 'imagick') {
  62. if ($imagick_exists) {
  63. return new ImagickAdapter($source);
  64. } else if ($gd_exists) {
  65. return new GDAdapter($source);
  66. }
  67. } else if ($this->adapter === 'gd') {
  68. if ($gd_exists) {
  69. return new GDAdapter($source);
  70. } else if ($imagick_exists) {
  71. return new ImagickAdapter($source);
  72. }
  73. }
  74. }
  75. /**
  76. * Resizes an image using either Imagick or GD
  77. * @param string $source - Source image path
  78. * @param string $target - Target image path
  79. * @param float $width - Target width
  80. * @param float $height - Target height
  81. * @param int [$quality=95] - Compression quality for target image
  82. * @return bool - Returns true on success, otherwise false
  83. */
  84. protected function resizeImage($source, $target, $width, $height, $quality = 95)
  85. {
  86. $adapter = $this->getImageAdapter($source);
  87. $adapter->resize($width, $height);
  88. $adapter->setQuality($quality);
  89. return $adapter->save($target);
  90. }
  91. /**
  92. * Called when a page is saved from the admin plugin. Will generate
  93. * responsive image alternatives for images that don't have any.
  94. */
  95. public function onAdminSave($event)
  96. {
  97. $page = $event['object'];
  98. if (!($page instanceof Page || $page instanceof PageObject)) {
  99. return false;
  100. }
  101. if (!$this->dependencyCheck('imagick') && !$this->dependencyCheck('gd')) {
  102. $this->grav['admin']->setMessage('Neither Imagick nor GD seem to be installed. Automagic Images needs one of them to work.', 'warning');
  103. return;
  104. }
  105. $this->sizes = (array) $this->config->get('plugins.automagic-images.sizes');
  106. $this->adapter = $this->config->get('plugins.automagic-images.adapter', 'imagick');
  107. foreach ($page->media()->images() as $filename => $medium) {
  108. $srcset = $medium->srcset(false);
  109. if ($srcset != '') {
  110. continue;
  111. }
  112. // We can't rely on the path returned from the image's own path
  113. // method, since it points to the directory where the image is saved
  114. // rather than where the original is stored. This means it could
  115. // point to the global image cache directory.
  116. $page_path = $page->path();
  117. $source_path = "$page_path/$filename";
  118. $info = pathinfo($source_path);
  119. $count = 0;
  120. foreach ($this->sizes as $i => $size) {
  121. if ($size['width'] >= $medium->width) {
  122. continue;
  123. }
  124. $count++;
  125. $basename = str_replace(" ", "-", $info['filename']);
  126. $ext = strtolower($info['extension']);
  127. $dest_path = "{$info['dirname']}/{$basename}@{$count}x.{$ext}";
  128. $width = $size['width'];
  129. $quality = $size['quality'];
  130. $height = ($width / $medium->width) * $medium->height;
  131. $this->resizeImage($source_path, $dest_path, $width, $height, $quality, $medium->width, $medium->height);
  132. }
  133. $remove_original = $this->config->get('plugins.automagic-images.remove_original');
  134. if ($count > 0) {
  135. $original_index = $count + 1;
  136. if ($remove_original) {
  137. unlink($source_path);
  138. } else {
  139. rename($source_path, "{$info['dirname']}/{$basename}@{$original_index}x.{$ext}");
  140. }
  141. $fixed_source = str_replace($info['filename'], $basename, $source_path);
  142. $fixed_source = str_replace($info['extension'], $ext, $fixed_source);
  143. rename("{$info['dirname']}/{$basename}@1x.{$ext}", $fixed_source);
  144. }
  145. $message = "Resized $filename $count times";
  146. if ($remove_original) {
  147. $message .= ' (and removed the original image)';
  148. }
  149. $this->grav['admin']->setMessage($message, 'info');
  150. }
  151. }
  152. /**
  153. * Iterates over images in page content that was generated via twig and adds
  154. * sizes attribute (not cacheable)
  155. *
  156. * @return void
  157. */
  158. public function onOutputGenerated()
  159. {
  160. if ($this->isAdmin()) {
  161. return;
  162. }
  163. $config = (array) $this->config->get('plugins.automagic-images');
  164. $page = $this->grav['page'];
  165. // dump($this->grav->output); exit;
  166. $config = $this->mergeConfig($page);
  167. if ($config['enabled']) {
  168. include __DIR__ . '/vendor/autoload.php';
  169. $dom = new Dom;
  170. $dom->loadStr($this->grav->output,
  171. (new Options())->setCleanupInput(false)
  172. );
  173. $images = $dom->find('img');
  174. $arrClasses = [];
  175. foreach ($config['sizesattr'] as $array) {
  176. $arrClasses[$array['class']] = $array['directive'];
  177. }
  178. foreach ($images as $image) {
  179. $sizesattr = "";
  180. $classes = explode(" ", $image->getAttribute('class'));
  181. foreach ($classes as $class) {
  182. if (array_key_exists($class, $arrClasses)) {
  183. $sizesattr = $arrClasses[$class];
  184. }
  185. }
  186. if ($sizesattr == "") {
  187. if (array_key_exists('default', $arrClasses)) {
  188. $sizesattr = $arrClasses['default'];
  189. }
  190. }
  191. if ($sizesattr != "") {
  192. $image->setAttribute('sizes', $sizesattr);
  193. }
  194. }
  195. $this->grav->output = $dom->outerHtml;
  196. }
  197. }
  198. }