PageRenderTime 52ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/index.php

https://github.com/cfg/photon
PHP | 754 lines | 505 code | 101 blank | 148 comment | 98 complexity | 1957dc6019c6d4e4e631649ee840028b MD5 | raw file
Possible License(s): GPL-2.0
  1. <?php
  2. define( 'PHOTON__ALLOW_ANY_EXTENSION', 1 );
  3. define( 'PHOTON__ALLOW_QUERY_STRINGS', 2 );
  4. require dirname( __FILE__ ) . '/plugin.php';
  5. if ( file_exists( dirname( __FILE__ ) . '/../config.php' ) )
  6. require dirname( __FILE__ ) . '/../config.php';
  7. else if ( file_exists( dirname( __FILE__ ) . '/config.php' ) )
  8. require dirname( __FILE__ ) . '/config.php';
  9. // Explicit Configuration
  10. $allowed_functions = apply_filters( 'allowed_functions', array(
  11. // 'q' => RESERVED
  12. // 'zoom' => global resolution multiplier (argument filter)
  13. 'h' => 'setheight', // done
  14. 'w' => 'setwidth', // done
  15. 'crop' => 'crop', // done
  16. 'resize' => 'resize_and_crop', // done
  17. 'fit' => 'fit_in_box', // done
  18. 'lb' => 'letterbox', // done
  19. 'ulb' => 'unletterbox', // compat
  20. 'filter' => 'filter', // compat
  21. 'brightness' => 'brightness', // compat
  22. 'contrast' => 'contrast', // compat
  23. 'colorize' => 'colorize', // compat
  24. 'smooth' => 'smooth', // compat
  25. ) );
  26. unset( $allowed_functions['q'] );
  27. $allowed_types = apply_filters( 'allowed_types', array(
  28. 'gif',
  29. 'jpg',
  30. 'jpeg',
  31. 'png',
  32. ) );
  33. // Expects a trailing slash
  34. $tmpdir = apply_filters( 'tmpdir', '/tmp/' );
  35. $remote_image_max_size = apply_filters( 'remote_image_max_size', 55 * 1024 * 1024 );
  36. /* Array of domains exceptions
  37. * Keys are domain name
  38. * Values are bitmasks with the following options:
  39. * PHOTON__ALLOW_ANY_EXTENSION: Allow any extension (including none) in the path of the URL
  40. * PHOTON__ALLOW_QUERY_STRINGS: Append the string found in the 'q' query string parameter as the query string of the remote URL
  41. */
  42. $origin_domain_exceptions = apply_filters( 'origin_domain_exceptions', array() );
  43. // You can override this by defining it in config.php
  44. if ( ! defined( 'PHOTON__UPSCALE_MAX_PIXELS' ) )
  45. define( 'PHOTON__UPSCALE_MAX_PIXELS', 1000 );
  46. require dirname( __FILE__ ) . '/libjpeg.php';
  47. // Implicit configuration
  48. if ( file_exists( '/usr/local/bin/optipng' ) )
  49. define( 'OPTIPNG', '/usr/local/bin/optipng' );
  50. else
  51. define( 'OPTIPNG', false );
  52. if ( file_exists( '/usr/local/bin/jpegoptim' ) )
  53. define( 'JPEGOPTIM', '/usr/local/bin/jpegoptim' );
  54. else
  55. define( 'JPEGOPTIM', false );
  56. /**
  57. * zoom - ( "zoom" function via the uri ) - Intended for improving visuals
  58. * on high pixel ratio devices and browsers when zoomed in. No zoom in crop.
  59. *
  60. * Valid zoom levels are 1,1.5,2-10.
  61. */
  62. function zoom( $arguments, $function_name, $image ) {
  63. static $zoom;
  64. if ( !isset( $zoom ) ) {
  65. if ( isset( $_GET['zoom'] ) ) {
  66. $zoom = floatval( $_GET['zoom'] );
  67. // Clamp to 1-10
  68. $zoom = max( 1, $zoom );
  69. $zoom = min( 10, $zoom );
  70. if ( $zoom < 2 ) {
  71. // Round UP to the nearest half
  72. $zoom = ceil( $zoom * 2 ) / 2;
  73. } else {
  74. // Round UP to the nearest integer
  75. $zoom = ceil( $zoom );
  76. }
  77. } else {
  78. $zoom = false;
  79. }
  80. }
  81. if ( $zoom <= 1 )
  82. return $arguments;
  83. $w = $image->getimagewidth();
  84. $h = $image->getimageheight();
  85. switch ( $function_name ) {
  86. case 'setheight' :
  87. case 'setwidth' :
  88. $new_arguments = $arguments * $zoom;
  89. if ( substr( $arguments, -1 ) == '%' )
  90. $new_arguments .= '%';
  91. break;
  92. case 'fit_in_box' :
  93. case 'resize_and_crop' :
  94. list( $width, $height ) = explode( ',', $arguments );
  95. $new_width = $width * $zoom;
  96. $new_height = $height * $zoom;
  97. // Avoid dimensions larger than original.
  98. while ( ( $new_width > $w || $new_height > $h ) && $zoom > 1 ) {
  99. // Step down to the next lower zoom level.
  100. if ( $zoom > 2 ) {
  101. $zoom -= 1;
  102. } else {
  103. $zoom -= 0.5;
  104. }
  105. $new_width = $width * $zoom;
  106. $new_height = $height * $zoom;
  107. }
  108. $new_arguments = "$new_width,$new_height";
  109. break;
  110. default :
  111. $new_arguments = $arguments;
  112. }
  113. return $new_arguments;
  114. }
  115. add_filter( 'arguments', 'zoom', 10, 3 );
  116. /**
  117. * crop - ("crop" function via the uri) - crop an image
  118. *
  119. * @param (resource)image the source gd image resource
  120. * @param (string)args "x,y,w,h" widh each csv column being /^[0-9]+(px)?$/
  121. * all values in percentages by default, but can be set
  122. * to absolute pixel values by specifying px ie 25px
  123. *
  124. * @return (resource)image the resulting image gd resource
  125. *
  126. **/
  127. function crop( &$image, $args ) {
  128. $args = explode( ',', $args );
  129. $w = $image->getimagewidth();
  130. $h = $image->getimageheight();
  131. if ( substr( $args[2], -2 ) == 'px' )
  132. $new_w = max( 0, min( $w, intval( $args[2] ) ) );
  133. else
  134. $new_w = round( $w * abs( intval( $args[2] ) ) / 100 );
  135. if ( substr( $args[3], -2 ) == 'px' )
  136. $new_h = max( 0, min( $h, intval( $args[3] ) ) );
  137. else
  138. $new_h = round( $h * abs( intval( $args[3] ) ) / 100 );
  139. if ( substr( $args[0], -2 ) == 'px' )
  140. $s_x = intval( $args[0] );
  141. else
  142. $s_x = round( $w * abs( intval( $args[0] ) ) / 100 );
  143. if ( substr( $args[1], -2 ) == 'px' )
  144. $s_y = intval( $args[1] );
  145. else
  146. $s_y = round( $h * abs( intval( $args[1] ) ) / 100 );
  147. $image->cropimage( $new_w, $new_h, $s_x, $s_y );
  148. }
  149. /**
  150. * setheight - ( "h" function via the uri ) - resize the image to an explicit height, maintaining its aspect ratio
  151. *
  152. * @param (resource)image the source gd image resource
  153. * @param (string)args "/^[0-9]+%?$/" the new height in pixels, or as a percentage if suffixed with an %
  154. * @param boolean $upscale Whether to allow upscaling or not, defaults to not allowing.
  155. *
  156. * @return (resource) the resulting gs image resource
  157. **/
  158. function setheight( &$image, $args, $upscale = false ) {
  159. $w = $image->getimagewidth();
  160. $h = $image->getimageheight();
  161. if ( substr( $args, -1 ) == '%' )
  162. $new_height = round( $h * abs( intval( $args ) ) / 100 );
  163. else
  164. $new_height = intval( $args );
  165. // New height can't be calculated, then bail
  166. if ( ! $new_height )
  167. return;
  168. // New height is greater than original image, but we don't have permission to upscale
  169. if ( $new_height > $h && ! $upscale )
  170. return;
  171. // Sane limit when upscaling, defaults to 1000
  172. if ( $new_height > $h && $upscale && $new_height > PHOTON__UPSCALE_MAX_PIXELS )
  173. return;
  174. $ratio = $h / $new_height;
  175. $new_w = round( $w / $ratio );
  176. $new_h = round( $h / $ratio );
  177. $s_x = $s_y = 0;
  178. $image->scaleimage( $new_w, $new_h );
  179. }
  180. /**
  181. * setwidth - ( "w" function via the uri ) - resize the image to an explicit width, maintaining its aspect ratio
  182. *
  183. * @param (resource)image the source gd image resource
  184. * @param (string)args "/^[0-9]+%?$/" the new width in pixels, or as a percentage if suffixed with an %
  185. * @param boolean $upscale Whether to allow upscaling or not, defaults to not allowing.
  186. *
  187. * @return (resource) the resulting gs image resource
  188. **/
  189. function setwidth( &$image, $args, $upscale = false ) {
  190. $w = $image->getimagewidth();
  191. $h = $image->getimageheight();
  192. if ( substr( $args, -1 ) == '%' )
  193. $new_width = round( $w * abs( intval( $args ) ) / 100 );
  194. else
  195. $new_width = intval( $args );
  196. // New width can't be calculated, then bail
  197. if ( ! $new_width )
  198. return;
  199. // New height is greater than original image, but we don't have permission to upscale
  200. if ( $new_width > $w && ! $upscale )
  201. return;
  202. // Sane limit when upscaling, defaults to 1000
  203. if ( $new_width > $w && $upscale && $new_width > PHOTON__UPSCALE_MAX_PIXELS )
  204. return;
  205. $ratio = $w / $new_width;
  206. $new_w = round( $w / $ratio );
  207. $new_h = round( $h / $ratio );
  208. $s_x = $s_y = 0;
  209. $image->scaleimage( $new_w, $new_h );
  210. }
  211. /**
  212. * fit_in_box - ( "fit" function via the uri ) - resize the image to fit it in the dimensions provided, maintaining its aspect ratio
  213. *
  214. * @param (resource)image the source gd image resource
  215. * @param (string)args "(int),(int)" width and height of the box
  216. *
  217. * @return (resource) the resulting gs image resource
  218. **/
  219. function fit_in_box( &$image, $args ) {
  220. $w = $image->getimagewidth();
  221. $h = $image->getimageheight();
  222. list( $end_w, $end_h ) = explode( ',', $args );
  223. $end_w = abs( intval( $end_w ) );
  224. $end_h = abs( intval( $end_h ) );
  225. if ( ( $w == $end_w && $h == $end_h ) ||
  226. ! $end_w || ! $end_h || ( $w < $end_w && $h < $end_h )
  227. ) {
  228. return;
  229. }
  230. $image->scaleimage( $end_w, $end_h, true );
  231. }
  232. /**
  233. * resize_and_crop - ("resize" function via the uri) - originally by Alex M.
  234. *
  235. * Differs from setwidth, setheight, and crop in that you provide a width/height and it resizes to that and then crops off excess
  236. *
  237. * @param (resource) image the source gd image resource
  238. * @param (string) args "w,h" width,height in pixels
  239. *
  240. * @return (resource)image the resulting image gd resource
  241. *
  242. **/
  243. function resize_and_crop( &$image, $args ) {
  244. $w = $image->getimagewidth();
  245. $h = $image->getimageheight();
  246. list( $end_w, $end_h ) = explode( ',', $args );
  247. $end_w = (int) $end_w;
  248. $end_h = (int) $end_h;
  249. if ( 0 == $end_w || 0 == $end_h )
  250. return;
  251. $ratio_orig = $w / $h;
  252. $ratio_end = $end_w / $end_h;
  253. // If the original and new images are proportional (no cropping needed), just do a standard resize
  254. if ( $ratio_orig == $ratio_end )
  255. setwidth( $image, $end_w, true );
  256. // If we need to crop off the sides
  257. elseif ( $ratio_orig > $ratio_end ) {
  258. setheight( $image, $end_h, true );
  259. $x = floor( ( $image->getimagewidth() - $end_w ) / 2 );
  260. crop( $image, "{$x}px,0px,{$end_w}px,{$end_h}px" );
  261. }
  262. // If we need to crop off the top/bottom
  263. elseif ( $ratio_orig < $ratio_end ) {
  264. setwidth( $image, $end_w, true );
  265. $y = floor( ( $image->getimageheight() - $end_h ) / 2 );
  266. crop( $image, "0px,{$y}px,{$end_w}px,{$end_h}px" );
  267. }
  268. }
  269. /**
  270. * unletterbox - ("ulb" function via the uri) - originally by Demitrious K.
  271. *
  272. * Removes black letterboxing bands from the top and bottom of an image
  273. *
  274. * $param (resource) img the source gd image resource
  275. * $param (string) args true is the only acceptable argument
  276. *
  277. * @return (resource)image the resulting image gd resource
  278. **/
  279. function unletterbox( &$img, $args ) {
  280. if ( 'true' !== $args )
  281. return $img;
  282. gmagick_to_gd( $img );
  283. // rgb values averaged per pixel, and then those averaged for the entire row
  284. $max_value_considered_black = 3;
  285. $width = imagesx( $img );
  286. $height = imagesy( $img );
  287. $first_nonblack_line = null;
  288. for( $h=0; $h < $height; $h++ ) {
  289. $line_value = 0;
  290. for( $w=0; $w < $width; $w++ ) {
  291. $rgb = imagecolorat( $img, $w, $h );
  292. $r = ( $rgb >> 16 ) & 0xFF;
  293. $g = ( $rgb >> 8 ) & 0xFF;
  294. $b = $rgb & 0xFF;
  295. $line_value += round( ( $r + $g + $b ) / 3 );
  296. }
  297. if ( round( $line_value/$width ) > $max_value_considered_black ) {
  298. $first_nonblack_line = $h + 1;
  299. break;
  300. }
  301. }
  302. if ( ! $first_nonblack_line ) {
  303. gd_to_gmagick( $img );
  304. return;
  305. }
  306. $last_nonblack_line = null;
  307. for( $h = $height - 1; $h >= 0; $h-- ) {
  308. $line_value = 0;
  309. for( $w=0; $w < $width; $w++ ) {
  310. $rgb = imagecolorat( $img, $w, $h );
  311. $r = ( $rgb >> 16 ) & 0xFF;
  312. $g = ( $rgb >> 8 ) & 0xFF;
  313. $b = $rgb & 0xFF;
  314. $line_value += round( ( $r + $g + $b ) / 3 );
  315. }
  316. if ( round( $line_value / $width ) > $max_value_considered_black ) {
  317. $last_nonblack_line = $h;
  318. break;
  319. }
  320. }
  321. if ( ! $last_nonblack_line || $last_nonblack_line <= $first_nonblack_line ) {
  322. gd_to_gmagick( $img );
  323. return;
  324. }
  325. $args = implode( ',',
  326. array(
  327. '0px',
  328. $first_nonblack_line . 'px',
  329. $width . 'px',
  330. ( $last_nonblack_line - $first_nonblack_line ) . 'px',
  331. )
  332. );
  333. gd_to_gmagick( $img );
  334. crop( $img, $args );
  335. }
  336. // {{{ filter($image,$filter)
  337. /**
  338. * Box resizes an image and fills the background with black
  339. *
  340. * @param object $image
  341. * @param array $args
  342. */
  343. function letterbox( &$image, $args ) {
  344. $w = $image->getimagewidth();
  345. $h = $image->getimageheight();
  346. list( $end_w, $end_h ) = explode( ',', $args );
  347. $end_w = abs( intval( $end_w ) );
  348. $end_h = abs( intval( $end_h ) );
  349. if ( ( $w == $end_w && $h == $end_h ) ||
  350. ! $end_w || ! $end_h || ( $w < $end_w && $h < $end_h )
  351. ) {
  352. return;
  353. }
  354. $image->scaleimage( $end_w, $end_h, true );
  355. $new_w = $image->getimagewidth();
  356. $new_h = $image->getimageheight();
  357. $border_h = round( ( $end_h - $new_h ) / 2 );
  358. $border_w = round( ( $end_w - $new_w ) / 2 );
  359. if ( $border_h > PHOTON__UPSCALE_MAX_PIXELS ||
  360. $border_w > PHOTON__UPSCALE_MAX_PIXELS )
  361. {
  362. return;
  363. }
  364. $image->borderimage('#000', $border_w, $border_h );
  365. // Since we create the borders with rounded values
  366. // we have to chop any excessive pixels off.
  367. $crop_x = $border_w * 2 + $new_w - $end_w;
  368. $crop_y = $border_h * 2 + $new_h - $end_h;
  369. if ( $crop_x || $crop_y )
  370. $image->cropimage( $end_w, $end_h, $crop_x, $crop_y );
  371. }
  372. /**
  373. * filter - ("filter" via the uri) - originally by Alex M.
  374. *
  375. * Performs various filters on the image such as grayscale
  376. * This is only for filters that accept no args
  377. *
  378. * @param resource $image The source GD image resource
  379. * @param string $filter The filter name
  380. * @return resource The resulting GD imageresource
  381. **/
  382. function filter( &$image, $filter ) {
  383. $args = explode( ',', $filter );
  384. $filter = array_shift( $args );
  385. gmagick_to_gd( $image );
  386. switch ( $filter ) {
  387. case 'negate':
  388. do_action( 'bump_stats', 'filter_negate' );
  389. imagefilter( $image, IMG_FILTER_NEGATE );
  390. break;
  391. case 'grayscale':
  392. case 'greyscale':
  393. do_action( 'bump_stats', 'filter_grayscale' );
  394. imagefilter( $image, IMG_FILTER_GRAYSCALE );
  395. break;
  396. case 'sepia':
  397. do_action( 'bump_stats', 'filter_sepia' );
  398. imagefilter( $image, IMG_FILTER_GRAYSCALE );
  399. imagefilter( $image, IMG_FILTER_COLORIZE, 90, 60, 40 );
  400. break;
  401. case 'edgedetect':
  402. do_action( 'bump_stats', 'filter_edgedetect' );
  403. imagefilter( $image, IMG_FILTER_EDGEDETECT );
  404. break;
  405. case 'emboss':
  406. do_action( 'bump_stats', 'filter_emboss' );
  407. imagefilter( $image, IMG_FILTER_EMBOSS );
  408. break;
  409. case 'blurgaussian':
  410. do_action( 'bump_stats', 'filter_blurgaussian' );
  411. imagefilter( $image, IMG_FILTER_GAUSSIAN_BLUR );
  412. break;
  413. case 'blurselective':
  414. do_action( 'bump_stats', 'filter_blurselective' );
  415. imagefilter( $image, IMG_FILTER_SELECTIVE_BLUR );
  416. break;
  417. case 'meanremoval':
  418. do_action( 'bump_stats', 'filter_meanremoval' );
  419. imagefilter( $image, IMG_FILTER_MEAN_REMOVAL );
  420. break;
  421. }
  422. gd_to_gmagick( $image );
  423. }
  424. // }}}
  425. /**
  426. * brightness - ("brightness" via the uri) - originally by Alex M.
  427. *
  428. * Adjusts image brightness (-255 through 255)
  429. *
  430. * @param resource $original The source GD image resource
  431. * @param resource $brightness The brightness adjustment value
  432. * @return resource The resulting GD imageresource
  433. **/
  434. function brightness( &$image, $brightness ) {
  435. $brightness = (int) $brightness;
  436. gmagick_to_gd( $image );
  437. imagefilter( $image, IMG_FILTER_BRIGHTNESS, $brightness );
  438. gd_to_gmagick( $image );
  439. }
  440. /**
  441. * contrast - ("contrast" via the uri) - originally by Alex M.
  442. *
  443. * Adjusts image contrast (-100 through 100)
  444. *
  445. * @param resource $original The source GD image resource
  446. * @param resource $contrast The contrast adjustment value
  447. * @return resource The resulting GD imageresource
  448. **/
  449. function contrast( &$image, $contrast ) {
  450. $contrast = (int) $contrast;
  451. gmagick_to_gd( $image );
  452. imagefilter( $image, IMG_FILTER_CONTRAST, $contrast * -1 ); // Make +value increase contrast
  453. gd_to_gmagick( $image );
  454. }
  455. /**
  456. * colorize - ("colorize" via the uri) - originally by Alex M.
  457. *
  458. * Hues the image to a certain color: red,green,blue
  459. *
  460. * @param resource $original The source GD image resource
  461. * @param resource $colors A comma seperated rgb value (255,255,255 = white)
  462. * @return resource The resulting GD imageresource
  463. **/
  464. function colorize( &$image, $colors ) {
  465. $colors = explode( ',', $colors );
  466. $color = array_map( 'intval', $colors );
  467. $red = ( !empty($color[0]) ) ? $color[0] : 0;
  468. $green = ( !empty($color[1]) ) ? $color[1] : 0;
  469. $blue = ( !empty($color[2]) ) ? $color[2] : 0;
  470. gmagick_to_gd( $image );
  471. imagefilter( $image, IMG_FILTER_COLORIZE, $red, $green, $blue );
  472. gd_to_gmagick( $image );
  473. }
  474. /**
  475. * smooth - ("smooth" via the uri) - originally by Alex M.
  476. *
  477. * Adjusts image smoothness
  478. *
  479. * @param resource $original The source GD image resource
  480. * @param resource $smoothness The smoothness adjustment value
  481. * @return resource The resulting GD imageresource
  482. **/
  483. function smooth( &$image, $smoothness ) {
  484. gmagick_to_gd( $image );
  485. imagefilter( $image, IMG_FILTER_SMOOTH, (float) $smoothness );
  486. gd_to_gmagick( $image );
  487. }
  488. function httpdie( $code='404 Not Found', $message='Error: 404 Not Found' ) {
  489. $numerical_error_code = preg_replace( '/[^\\d]/', '', $code );
  490. do_action( 'bump_stats', "http_error-$numerical_error_code" );
  491. header( 'HTTP/1.1 ' . $code );
  492. die( $message );
  493. }
  494. function gmagick_to_gd( &$image ) {
  495. global $type;
  496. if ( $type == "JPEG" )
  497. $image->setcompressionquality( 100 );
  498. $image = imagecreatefromstring( $image->getimageblob() );
  499. }
  500. function gd_to_gmagick( &$image ) {
  501. global $type;
  502. ob_start();
  503. switch( strtolower( $type ) ) {
  504. case 'gif':
  505. imagegif( $image, null );
  506. break;
  507. case 'png':
  508. imagepng( $image, null, 0 );
  509. break;
  510. default:
  511. imagejpeg( $image, null, 100 );
  512. break;
  513. }
  514. $image = new Gmagick();
  515. $image->readimageblob( ob_get_clean() );
  516. }
  517. function fetch_raw_data( $url, $timeout = 10, $connect_timeout = 2 ) {
  518. $ch = curl_init( $url );
  519. curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $connect_timeout );
  520. curl_setopt( $ch, CURLOPT_TIMEOUT, $timeout );
  521. curl_setopt( $ch, CURLOPT_SSLVERSION, 3 );
  522. curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
  523. curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, true );
  524. curl_setopt( $ch, CURLOPT_MAXREDIRS, 3 );
  525. curl_setopt( $ch, CURLOPT_USERAGENT, 'Photon/1.0' );
  526. curl_setopt( $ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
  527. curl_setopt( $ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS );
  528. curl_setopt( $ch, CURLOPT_WRITEFUNCTION, function( $curl_handle, $data ) {
  529. global $raw_data, $raw_data_size, $remote_image_max_size;
  530. $data_size = strlen( $data );
  531. $raw_data .= $data;
  532. $raw_data_size += $data_size;
  533. if ( $raw_data_size > $remote_image_max_size )
  534. httpdie( '400 Bad Request', "You can only process images up to $remote_image_max_size bytes." );
  535. return $data_size;
  536. } );
  537. return curl_exec( $ch );
  538. }
  539. function do_a_filter( $function_name, $arguments ) {
  540. global $image, $allowed_functions;
  541. if ( ! isset( $allowed_functions[$function_name] ) )
  542. return;
  543. $function_name = $allowed_functions[$function_name];
  544. if ( function_exists( $function_name ) && is_callable( $function_name ) ) {
  545. do_action( 'bump_stats', $function_name );
  546. $arguments = apply_filters( 'arguments', $arguments, $function_name, $image );
  547. $function_name( $image, $arguments );
  548. }
  549. }
  550. function photon_cache_headers( $expires=63115200 ) {
  551. header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $expires ) . ' GMT' );
  552. header( 'Cache-Control: public, max-age='.$expires );
  553. header( 'X-Content-Type-Options: nosniff' );
  554. }
  555. $image = new Gmagick();
  556. $parsed = parse_url( $_SERVER['REQUEST_URI'] );
  557. $exploded = explode( '/', $_SERVER['REQUEST_URI'] );
  558. $origin_domain = strtolower( $exploded[1] );
  559. $origin_domain_exception = array_key_exists( $origin_domain, $origin_domain_exceptions ) ? $origin_domain_exceptions[$origin_domain] : 0;
  560. $scheme = 'http' . ( array_key_exists( 'ssl', $_GET ) ? 's' : '' ) . '://';
  561. parse_str( ( empty( $parsed['query'] ) ? '' : $parsed['query'] ), $_GET );
  562. $ext = strtolower( pathinfo( $parsed['path'], PATHINFO_EXTENSION ) );
  563. if ( ! in_array( $ext, $allowed_types ) && !( $origin_domain_exception & PHOTON__ALLOW_ANY_EXTENSION ) )
  564. httpdie( '400 Bad Request', 'The type of image you are trying to process is not allowed' );
  565. $url = $scheme . substr( $parsed['path'], 1 );
  566. $url = preg_replace( '/#.*$/', '', $url );
  567. $url = apply_filters( 'url', $url );
  568. if ( isset( $_GET['q'] ) ) {
  569. if ( $origin_domain_exception & PHOTON__ALLOW_QUERY_STRINGS ) {
  570. $url .= '?' . preg_replace( '/#.*$/', '', (string) $_GET['q'] );
  571. unset( $_GET['q'] );
  572. } else {
  573. httpdie( '400 Bad Request', "Sorry, the parameters you provided were not valid" );
  574. }
  575. }
  576. if ( false === filter_var( $url, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED ) )
  577. httpdie( '400 Bad Request', "Sorry, the parameters you provided were not valid" );
  578. $raw_data = '';
  579. $raw_data_size = 0;
  580. $fetched = fetch_raw_data( $url );
  581. if ( ! $fetched || empty( $raw_data ) )
  582. httpdie( '504 Gateway Timeout', 'We cannot complete this request, remote data could not be fetched' );
  583. try {
  584. $image->readimageblob( $raw_data );
  585. $type = $image->getimageformat();
  586. } catch ( GmagickException $e ) {
  587. httpdie( '400 Bad Request', 'We cannot complete this request, remote data was invalid' );
  588. }
  589. if ( !in_array( strtolower( $type ), $allowed_types ) )
  590. httpdie( '400 Bad Request', 'The type of image you are trying to process is not allowed' );
  591. if ( $type == 'JPEG' )
  592. $quality = get_jpeg_quality( $raw_data, $raw_data_size );
  593. else
  594. $quality = 90;
  595. unset( $raw_data );
  596. try {
  597. // Run through all uri supplied functions which are valid and allowed
  598. foreach( $_GET as $function_name => $arguments ) {
  599. if ( !is_array( $arguments ) )
  600. $arguments = array( $arguments );
  601. foreach ( $arguments as $argument ) {
  602. if ( $argument === apply_filters( $function_name, $argument ) )
  603. do_a_filter( $function_name, $argument );
  604. }
  605. }
  606. switch ( strtolower( $image->getimageformat() ) ) {
  607. case 'png':
  608. do_action( 'bump_stats', 'image_png' );
  609. header( 'Content-Type: image/png' );
  610. $image->setcompressionquality( $quality );
  611. $tmp = tempnam( $tmpdir, 'OPTIPNG-' );
  612. $image->write( $tmp );
  613. $og = filesize( $tmp );
  614. exec( OPTIPNG . " $tmp" );
  615. clearstatcache();
  616. $save = $og - filesize( $tmp );
  617. do_action( 'bump_stats', 'png_bytes_saved', $save );
  618. $fp = fopen( $tmp, 'r' );
  619. photon_cache_headers();
  620. header( 'Content-Length: ' . filesize( $tmp ) );
  621. header( 'X-Bytes-Saved: ' . $save );
  622. unlink( $tmp );
  623. fpassthru( $fp );
  624. break;
  625. case 'gif':
  626. do_action( 'bump_stats', 'image_gif' );
  627. header( 'Content-Type: image/gif' );
  628. $image->setcompressionquality( $quality );
  629. photon_cache_headers();
  630. echo $image->getimageblob();
  631. break;
  632. default:
  633. do_action( 'bump_stats', 'image_jpeg' );
  634. header( 'Content-Type: image/jpeg' );
  635. $image->setcompressionquality( $quality );
  636. $tmp = tempnam( $tmpdir, 'JPEGOPTIM-' );
  637. $image->write( $tmp );
  638. $og = filesize( $tmp );
  639. exec( JPEGOPTIM . " -p $tmp" );
  640. clearstatcache();
  641. $save = $og - filesize( $tmp );
  642. do_action( 'bump_stats', 'jpg_bytes_saved', $save );
  643. $fp = fopen( $tmp, 'r' );
  644. photon_cache_headers();
  645. header( 'Content-Length: ' . filesize( $tmp ) );
  646. header( 'X-Bytes-Saved: ' . $save );
  647. unlink( $tmp );
  648. fpassthru( $fp );
  649. break ;
  650. }
  651. } catch ( GmagickException $e ) {
  652. httpdie( '400 Bad Request', "Sorry, the parameters you provided were not valid" );
  653. }