init.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. <?php
  2. class Api_Resize_Media extends Plugin {
  3. const MAX_WIDTH = 1024;
  4. /* @var PluginHost $host */
  5. private $host;
  6. /* @var DiskCache $cache */
  7. private $cache;
  8. function about() {
  9. return array(1.0,
  10. "Resizes images as requested by API clients",
  11. "fox");
  12. }
  13. function is_public_method($method) {
  14. return $method === "api_resize";
  15. }
  16. function init($host) {
  17. $this->host = $host;
  18. $this->cache = new DiskCache("images");
  19. $host->add_hook($host::HOOK_RENDER_ARTICLE_API, $this, 999);
  20. $host->add_hook($host::HOOK_ARTICLE_IMAGE, $this, 999);
  21. }
  22. private function make_thumbnail($input_filename, $output_filename, $dim_max_x = 600, $dim_max_y = 600,
  23. $content_type = "", $force_stamp = false) {
  24. if ($content_type == "image/webp") {
  25. $o_im = @imagecreatefromwebp($input_filename);
  26. } else {
  27. $o_im = @imagecreatefromstring(file_get_contents($input_filename));
  28. }
  29. if ($o_im) {
  30. $imageinfo = @getimagesize($input_filename);
  31. $o_width = imagesx($o_im) ;
  32. $o_height = imagesy($o_im) ;
  33. if (max($o_width, $o_height) < max($dim_max_x, $dim_max_y)) {
  34. $t_height = $o_height;
  35. $t_width = $o_width;
  36. } else {
  37. if ($o_height > $o_width) {
  38. $t_height = $dim_max_x;
  39. $t_width = round($o_width/$o_height * $t_height);
  40. } else {
  41. $t_width = $dim_max_y;
  42. $t_height = round($o_height/$o_width * $t_width);
  43. }
  44. }
  45. // print "$o_file : $t_file : $o_height * $o_width -> $t_height * $t_width<br>";
  46. $t_im = imageCreateTrueColor($t_width, $t_height);
  47. $need_stamp = false;
  48. if ($force_stamp /*|| ($enable_stamp && $imageinfo && $imageinfo["mime"] == "image/gif")*/) {
  49. $need_stamp = true;
  50. imageFill($t_im, 0, 0, 0xffffff);
  51. /*} else { -- this might make resized image significantly larger
  52. imagealphablending($t_im, false);
  53. imagesavealpha($t_im, true); */
  54. }
  55. imageCopyResampled($t_im, $o_im, 0, 0, 0, 0,
  56. $t_width, $t_height, $o_width, $o_height);
  57. if ($need_stamp) {
  58. $stamp = imagecreatefrompng(__DIR__ . '/images/play-outline.png');
  59. if ($stamp) {
  60. $sx = imagesx($stamp);
  61. $sy = imagesy($stamp);
  62. imagecopy($t_im, $stamp,
  63. imagesx($t_im)/2 - $sx/2,
  64. imagesy($t_im)/2 - $sy/2,
  65. 0, 0,
  66. imagesx($stamp), imagesy($stamp));
  67. }
  68. }
  69. /*if ($need_stamp || !$need_alpha)
  70. @imageJpeg($t_im, $output_filename, 75);
  71. else
  72. @imagePng($t_im, $output_filename, 5);*/
  73. imagewebp($t_im, $output_filename, 80);
  74. imageDestroy($o_im);
  75. imageDestroy($t_im);
  76. }
  77. }
  78. public function api_resize() {
  79. $url = rewrite_relative_url(get_self_url_prefix(), $_REQUEST["url"]);
  80. $width = (int) $_REQUEST["width"];
  81. $force_stamp = sql_bool_to_bool($_REQUEST["force_stamp"]);
  82. $local_filename = sha1($url);
  83. $flag_filename = sha1($url) . ".flag";
  84. $resized_filename = sha1($url) . "-$width";
  85. if (!$this->cache->exists($flag_filename) || $width <= 0) {
  86. header("Location: $url");
  87. return;
  88. }
  89. if ($width > Api_Resize_Media::MAX_WIDTH)
  90. $width = Api_Resize_Media::MAX_WIDTH;
  91. if ($this->cache->exists($local_filename)) {
  92. if ($this->cache->exists($resized_filename)) {
  93. header("Location: " . $this->cache->getUrl($resized_filename));
  94. return;
  95. } else {
  96. $this->make_thumbnail(
  97. $this->cache->getFullPath($local_filename),
  98. $this->cache->getFullPath($resized_filename),
  99. $width,
  100. $width,
  101. $this->cache->getMimeType($local_filename),
  102. $force_stamp);
  103. if ($this->cache->exists($resized_filename)) {
  104. header("Location: " . $this->cache->getUrl($resized_filename));
  105. return;
  106. } else {
  107. header("Location: " . $this->cache->getUrl($local_filename));
  108. return;
  109. }
  110. }
  111. } else {
  112. $data = fetch_file_contents(["url" => $url, "max_size" => MAX_CACHE_FILE_SIZE]);
  113. if ($data) {
  114. if ($this->cache->put($local_filename, $data)) {
  115. if ($this->cache->exists($resized_filename)) {
  116. header("Location: " . $this->cache->getUrl($resized_filename));
  117. return;
  118. } else {
  119. $this->make_thumbnail(
  120. $this->cache->getFullPath($local_filename),
  121. $this->cache->getFullPath($resized_filename),
  122. $width,
  123. $width,
  124. $this->cache->getMimeType($local_filename),
  125. $force_stamp);
  126. if ($this->cache->exists($resized_filename)) {
  127. header("Location: " . $this->cache->getUrl($resized_filename));
  128. return;
  129. } else {
  130. header("Location: " . $this->cache->getUrl($local_filename));
  131. return;
  132. }
  133. }
  134. } else {
  135. print $data;
  136. }
  137. } else {
  138. global $fetch_last_error;
  139. global $fetch_last_error_code;
  140. global $fetch_last_error_content;
  141. if (function_exists("imagecreate") && !isset($_REQUEST["text"])) {
  142. $img = imagecreate(450, 75);
  143. /*$bg =*/ imagecolorallocate($img, 255, 255, 255);
  144. $textcolor = imagecolorallocate($img, 255, 0, 0);
  145. imagerectangle($img, 0, 0, 450-1, 75-1, $textcolor);
  146. imagestring($img, 5, 5, 5, "Proxy request failed", $textcolor);
  147. imagestring($img, 5, 5, 30, truncate_middle($url, 46, "..."), $textcolor);
  148. imagestring($img, 5, 5, 55, "HTTP Code: $fetch_last_error_code", $textcolor);
  149. header("Content-type: image/png");
  150. print imagepng($img);
  151. imagedestroy($img);
  152. } else {
  153. header("Content-type: text/html");
  154. http_response_code(400);
  155. print "<h1>Proxy request failed.</h1>";
  156. print "<p>Fetch error $fetch_last_error ($fetch_last_error_code)</p>";
  157. print "<p>URL: $url</p>";
  158. print "<textarea cols='80' rows='25'>" . htmlspecialchars($fetch_last_error_content) . "</textarea>";
  159. }
  160. }
  161. }
  162. }
  163. private function rewrite_url_if_needed($url, $width, $force_stamp = false) {
  164. if (strpos($url, "data:") !== 0) {
  165. $local_filename = sha1($url) . "-$width";
  166. if ($this->cache->exists($local_filename)) {
  167. return $this->cache->getUrl($local_filename);
  168. } else {
  169. if ($this->cache->touch(sha1($url) . ".flag")) {
  170. return $this->host->get_public_method_url($this, "api_resize",
  171. ["url" => $url, "width" => $width, "force_stamp" => $force_stamp]);
  172. }
  173. }
  174. }
  175. return $url;
  176. }
  177. function hook_article_image($enclosures, $content, $site_url) {
  178. $article = $this->hook_render_article_api(["headline" => ["content" => $content]]);
  179. return ["", "", $article["content"]];
  180. }
  181. function hook_render_article_api($row) {
  182. $need_saving = false;
  183. $width = (int) clean($_REQUEST["resize_width"]);
  184. $article = isset($row['headline']) ? $row['headline'] : $row['article'];
  185. if ($width <= 0)
  186. return $article;
  187. $doc = new DOMDocument();
  188. if (@$doc->loadHTML('<?xml encoding="UTF-8">' . $article["content"])) {
  189. $xpath = new DOMXPath($doc);
  190. $imgs = $xpath->query("//img[@src]");
  191. foreach ($imgs as $img) {
  192. $new_src = $this->rewrite_url_if_needed($img->getAttribute("src"), $width);
  193. if ($new_src != $img->getAttribute("src")) {
  194. $img->setAttribute("src", $new_src);
  195. $img->removeAttribute("srcset");
  196. $need_saving = true;
  197. }
  198. }
  199. $vids = $xpath->query("//video[@poster]");
  200. foreach ($vids as $vid) {
  201. $new_poster = $this->rewrite_url_if_needed($vid->getAttribute("poster"), $width, true);
  202. if ($new_poster != $vid->getAttribute("poster")) {
  203. $vid->setAttribute("poster", $new_poster);
  204. $need_saving = true;
  205. }
  206. }
  207. $psrcs = $xpath->query("//picture/source[@src]");
  208. foreach ($psrcs as $src) {
  209. $new_src = $this->rewrite_url_if_needed($src->getAttribute("src"), $width);
  210. if ($new_src != $src->getAttribute("src")) {
  211. $src->setAttribute("src", $new_src);
  212. $need_saving = true;
  213. }
  214. }
  215. }
  216. if (is_array($article["attachments"])) {
  217. $tmp =& $article["attachments"];
  218. for ($i = 0; $i < count($tmp); $i++) {
  219. if (preg_match("/image/", $tmp[$i]["content_type"])) {
  220. $tmp[$i]["content_url"] = $this->rewrite_url_if_needed($tmp[$i]["content_url"], $width);
  221. }
  222. }
  223. }
  224. if ($need_saving)
  225. $article["content"] = $doc->saveHTML();
  226. return $article;
  227. }
  228. function api_version() {
  229. return 2;
  230. }
  231. }