PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/serendipity_event_responsiveimages/serendipity_event_responsiveimages.php

http://github.com/s9y/Serendipity
PHP | 241 lines | 223 code | 11 blank | 7 comment | 7 complexity | 2f09825e65d5c6742938969ac3132eb6 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, Apache-2.0
  1. <?php
  2. if (IN_serendipity !== true) {
  3. die ("Don't hack!");
  4. }
  5. @serendipity_plugin_api::load_language(dirname(__FILE__));
  6. class serendipity_event_responsiveimages extends serendipity_event
  7. {
  8. var $title = PLUGIN_EVENT_RESPONSIVE_NAME;
  9. function introspect(&$propbag)
  10. {
  11. global $serendipity;
  12. $propbag->add('name', PLUGIN_EVENT_RESPONSIVE_NAME);
  13. $propbag->add('description', PLUGIN_EVENT_RESPONSIVE_DESC);
  14. $propbag->add('stackable', false);
  15. $propbag->add('author', 'Serendipity Team');
  16. $propbag->add('version', '0.5.2');
  17. $propbag->add('requirements', array(
  18. 'serendipity' => '2.2',
  19. ));
  20. $propbag->add('cachable_events', array('frontend_display' => true));
  21. $propbag->add('event_hooks', array('frontend_display' => true,
  22. 'backend_media_makethumb' => true,
  23. 'frontend_display:unknown:per-entry' => true,
  24. 'frontend_display:opml-1.0:per_entry' => true,
  25. 'frontend_display:rss-0.91:per_entry' => true,
  26. 'frontend_display:rss-1.0:per_entry' => true,
  27. 'frontend_display:rss-2.0:per_entry' => true,
  28. 'frontend_display:atom-0.3:per_entry' => true,
  29. 'frontend_display:atom-1.0:per_entry' => true,
  30. ));
  31. $propbag->add('groups', array('MARKUP'));
  32. $this->markup_elements = array(
  33. array(
  34. 'name' => 'ENTRY_BODY',
  35. 'element' => 'body',
  36. ),
  37. array(
  38. 'name' => 'EXTENDED_BODY',
  39. 'element' => 'extended',
  40. )
  41. );
  42. $conf_array = array();
  43. foreach($this->markup_elements as $element) {
  44. $conf_array[] = $element['name'];
  45. }
  46. $propbag->add('configuration', $conf_array);
  47. }
  48. function install()
  49. {
  50. serendipity_plugin_api::hook_event('backend_cache_entries', $this->title);
  51. }
  52. function uninstall(&$propbag)
  53. {
  54. serendipity_plugin_api::hook_event('backend_cache_purge', $this->title);
  55. serendipity_plugin_api::hook_event('backend_cache_entries', $this->title);
  56. }
  57. function generate_content(&$title)
  58. {
  59. $title = $this->title;
  60. }
  61. function introspect_config_item($name, &$propbag)
  62. {
  63. $propbag->add('type', 'boolean');
  64. $propbag->add('name', constant($name));
  65. $propbag->add('description', sprintf(APPLY_MARKUP_TO, constant($name)));
  66. $propbag->add('default', 'true');
  67. return true;
  68. }
  69. function event_hook($event, &$bag, &$eventData, $addData = null)
  70. {
  71. global $serendipity;
  72. $hooks = &$bag->get('event_hooks');
  73. if (!isset($serendipity['smarty']) || ! is_object($serendipity['smarty'])) {
  74. serendipity_smarty_init(); // if not set to avoid member function assign() on a non-object error, start Smarty templating
  75. }
  76. $this->breakpoints = $serendipity['smarty']->getTemplateVars('template_option')['breakpoints'] ?? false;
  77. if (! $this->breakpoints) {
  78. $this->breakpoints = [1600, 1200, 600]; # This can be overwritten by the theme
  79. }
  80. $this->thumbWidths = [1200, 800, 400];
  81. if (isset($hooks[$event])) {
  82. switch($event) {
  83. case 'frontend_display':
  84. foreach ($this->markup_elements as $temp) {
  85. if (serendipity_db_bool($this->get_config($temp['name'], true)) && isset($eventData[$temp['element']]) &&
  86. !($eventData['properties']['ep_disable_markup_' . $this->instance] ?? null) &&
  87. !isset($serendipity['POST']['properties']['disable_markup_' . $this->instance])) {
  88. $element = $temp['element'];
  89. $eventData[$element] = $this->_responsive_markup($eventData[$element]);
  90. }
  91. }
  92. break;
  93. case 'backend_media_makethumb':
  94. // $eventData is usually defined as:
  95. // array(array(
  96. // 'thumbSize' => $serendipity['thumbSize'],
  97. // 'thumb' => $serendipity['thumbSuffix']
  98. // ));
  99. // We now just need to add the additional array elements, with the new sizes and suffix.
  100. // We can use $addData, containing the path to the full size file, to get the starting width
  101. $origSize = serendipity_getimagesize($addData);
  102. for ($i = 0; $i < count($this->thumbWidths); $i++) {
  103. $thumbWidth = $this->thumbWidths[$i];
  104. if ($thumbWidth < $origSize[0]) {
  105. $eventData[] = array(
  106. 'thumbSize' => ['width' => $thumbWidth, 'height' => $origSize[1] * ($thumbWidth / $origSize[0])],
  107. 'thumb' => $thumbWidth . 'W.' . $serendipity['thumbSuffix']
  108. );
  109. }
  110. }
  111. break;
  112. case 'frontend_display:unknown:per-entry':
  113. case 'frontend_display:opml-1.0:per_entry':
  114. case 'frontend_display:rss-0.91:per_entry':
  115. case 'frontend_display:rss-1.0:per_entry':
  116. case 'frontend_display:rss-2.0:per_entry':
  117. case 'frontend_display:atom-0.3:per_entry':
  118. case 'frontend_display:atom-1.0:per_entry':
  119. // We need to rewrite relative srcsets to absolute urls in the RSS feed, otherwise images won't be shown
  120. $pattern = '@srcset=(["\'][^"\']+)@i';
  121. $eventData['feed_body'] = preg_replace_callback($pattern, function($matches) {
  122. global $serendipity;
  123. $matches[1] = str_ireplace('"' . $serendipity['serendipityHTTPPath'], '"' . $serendipity['baseURL'], $matches[1]);
  124. $matches[1] = str_ireplace(',' . $serendipity['serendipityHTTPPath'], ',' . $serendipity['baseURL'], $matches[1]);
  125. return 'srcset=' . $matches[1];
  126. }, $eventData['feed_body']);
  127. break;
  128. default:
  129. return false;
  130. }
  131. return true;
  132. } else {
  133. return false;
  134. }
  135. }
  136. /* Given an entry text, replace each image linked to the ML with an img element containing
  137. * an srcset.
  138. * */
  139. function _responsive_markup($text)
  140. {
  141. preg_match_all('@<!-- s9ymdb:(?<id>\d+) -->@', $text, $matches);
  142. foreach ($matches['id'] as $imgId) {
  143. preg_match('@<!-- s9ymdb:\d+ --><img[^>]+width=["\'](\d+)["\']@', $text, $matches);
  144. if (count($matches) > 0) {
  145. $srcset = $this->createSrcset($imgId, $matches[1]);
  146. } else {
  147. $srcset = $this->createSrcset($imgId);
  148. }
  149. $callback = function($matches) use ($srcset) {
  150. if (strpos($matches[1], "srcset") === false) {
  151. // the image has not yet an srcset, at least at the position where we insert it normally
  152. return "{$matches[1]} $srcset src=";
  153. } else {
  154. return "{$matches[1]} src=";
  155. }
  156. };
  157. $text = preg_replace_callback("@(<!-- s9ymdb:$imgId -->.*?)src=@", $callback, $text);
  158. }
  159. return $text;
  160. }
  161. /* Given an id for a image in the ML, create an srcset using smaller thumbnail images and their width.
  162. * Don't worry over thumbnail creation here, that's done on image upload and thumbnail creation.
  163. * */
  164. function createSrcset($id, $maxWidth = 20000) {
  165. global $serendipity;
  166. $origImage = serendipity_fetchImageFromDatabase($id);
  167. if (! $origImage) {
  168. return '';
  169. }
  170. $imagePath = $serendipity['serendipityHTTPPath'] . $serendipity['uploadHTTPPath'] . $origImage['path'] . $origImage['realname'];
  171. $thumbnails = serendipity_getThumbnails($id);
  172. $srcset = "srcset=\"$imagePath {$origImage['dimensions_width']}w,";
  173. if ($origImage['dimensions_width'] <= $this->breakpoints[0]) {
  174. // don't set the original image as srcset source if its breakpoint would be too small
  175. $srcset = 'srcset="';
  176. }
  177. for ($i = 0; $i < count($this->thumbWidths); $i++) {
  178. $thumbWidth = $this->thumbWidths[$i];
  179. $matchedThumbnail = false;
  180. foreach ($thumbnails as $thumbnail) {
  181. if (strpos($thumbnail, $thumbWidth . 'W') !== false) {
  182. $matchedThumbnail = $thumbnail;
  183. break;
  184. }
  185. }
  186. if ($matchedThumbnail) {
  187. $thumbnailHttp = str_replace($serendipity['serendipityPath'], $serendipity['serendipityHTTPPath'], $matchedThumbnail);
  188. $breakpoint = $this->breakpoints[$i];
  189. $srcset .= "{$thumbnailHttp} {$breakpoint}w,";
  190. }
  191. }
  192. // 2 == there is the original thumbnail without a dimension, and one thumbnail for the smallest breakpoint
  193. if (count($thumbnails) == 2) {
  194. // When the smallest thumbnail is the only responsive thumbnail our original image will be needed to as part of the srcset, otherwise we too often upscale the small thumbnail
  195. $srcset .= "$imagePath {$origImage['dimensions_width']}w,";
  196. }
  197. if (substr($srcset, -strlen(',')) === ',') {
  198. // we don't want to have the trailing comma
  199. $srcset = substr($srcset, 0, -1);
  200. }
  201. $srcset .= '"';
  202. return $srcset;
  203. }
  204. }
  205. /* vim: set sts=4 ts=4 expandtab : */
  206. ?>