/class.photon.php

  1. <?php 
  2.  
  3. class Jetpack_Photon { 
  4. /** 
  5. * Class variables 
  6. */ 
  7. // Oh look, a singleton 
  8. private static $__instance = null; 
  9.  
  10. // Allowed extensions must match http://code.trac.wordpress.org/browser/photon/index.php#L31 
  11. protected static $extensions = array( 
  12. 'gif',  
  13. 'jpg',  
  14. 'jpeg',  
  15. 'png' 
  16. ); 
  17.  
  18. // Don't access this directly. Instead, use self::image_sizes() so it's actually populated with something. 
  19. protected static $image_sizes = null; 
  20.  
  21. /** 
  22. * Singleton implementation 
  23. * 
  24. * @return object 
  25. */ 
  26. public static function instance() { 
  27. if ( ! is_a( self::$__instance, 'Jetpack_Photon' ) ) { 
  28. self::$__instance = new Jetpack_Photon; 
  29. self::$__instance->setup(); 
  30.  
  31. return self::$__instance; 
  32.  
  33. /** 
  34. * Silence is golden. 
  35. */ 
  36. private function __construct() {} 
  37.  
  38. /** 
  39. * Register actions and filters, but only if basic Photon functions are available. 
  40. * The basic functions are found in ./functions.photon.php. 
  41. * 
  42. * @uses add_action, add_filter 
  43. * @return null 
  44. */ 
  45. private function setup() { 
  46. // Display warning if site is private 
  47. add_action( 'jetpack_activate_module_photon', array( $this, 'action_jetpack_activate_module_photon' ) ); 
  48.  
  49. if ( ! function_exists( 'jetpack_photon_url' ) ) 
  50. return; 
  51.  
  52. // Images in post content and galleries 
  53. add_filter( 'the_content', array( __CLASS__, 'filter_the_content' ), 999999 ); 
  54. add_filter( 'get_post_galleries', array( __CLASS__, 'filter_the_galleries' ), 999999 ); 
  55.  
  56. // Core image retrieval 
  57. add_filter( 'image_downsize', array( $this, 'filter_image_downsize' ), 10, 3 ); 
  58.  
  59. // Responsive image srcset substitution 
  60. add_filter( 'wp_calculate_image_srcset', array( $this, 'filter_srcset_array' ) ); 
  61.  
  62. // Helpers for maniuplated images 
  63. add_action( 'wp_enqueue_scripts', array( $this, 'action_wp_enqueue_scripts' ), 9 ); 
  64.  
  65. /** 
  66. * Check if site is private and warn user if it is 
  67. * 
  68. * @uses Jetpack::check_privacy 
  69. * @action jetpack_activate_module_photon 
  70. * @return null 
  71. */ 
  72. public function action_jetpack_activate_module_photon() { 
  73. Jetpack::check_privacy( __FILE__ ); 
  74.  
  75. /** 
  76. ** IN-CONTENT IMAGE MANIPULATION FUNCTIONS 
  77. **/ 
  78.  
  79. /** 
  80. * Match all images and any relevant <a> tags in a block of HTML. 
  81. * 
  82. * @param string $content Some HTML. 
  83. * @return array An array of $images matches, where $images[0] is 
  84. * an array of full matches, and the link_url, img_tag,  
  85. * and img_url keys are arrays of those matches. 
  86. */ 
  87. public static function parse_images_from_html( $content ) { 
  88. $images = array(); 
  89.  
  90. if ( preg_match_all( '#(?:<a[^>]+?href=["|\'](?P<link_url>[^\s]+?)["|\'][^>]*?>\s*)?(?P<img_tag><img[^>]+?src=["|\'](?P<img_url>[^\s]+?)["|\'].*?>) {1}(?:\s*</a>)?#is', $content, $images ) ) { 
  91. foreach ( $images as $key => $unused ) { 
  92. // Simplify the output as much as possible, mostly for confirming test results. 
  93. if ( is_numeric( $key ) && $key > 0 ) 
  94. unset( $images[$key] ); 
  95.  
  96. return $images; 
  97.  
  98. return array(); 
  99.  
  100. /** 
  101. * Try to determine height and width from strings WP appends to resized image filenames. 
  102. * 
  103. * @param string $src The image URL. 
  104. * @return array An array consisting of width and height. 
  105. */ 
  106. public static function parse_dimensions_from_filename( $src ) { 
  107. $width_height_string = array(); 
  108.  
  109. if ( preg_match( '#-(\d+)x(\d+)\.(?:' . implode('|', self::$extensions ) . ') {1}$#i', $src, $width_height_string ) ) { 
  110. $width = (int) $width_height_string[1]; 
  111. $height = (int) $width_height_string[2]; 
  112.  
  113. if ( $width && $height ) 
  114. return array( $width, $height ); 
  115.  
  116. return array( false, false ); 
  117.  
  118. /** 
  119. * Identify images in post content, and if images are local (uploaded to the current site), pass through Photon. 
  120. * 
  121. * @param string $content 
  122. * @uses self::validate_image_url, apply_filters, jetpack_photon_url, esc_url 
  123. * @filter the_content 
  124. * @return string 
  125. */ 
  126. public static function filter_the_content( $content ) { 
  127. $images = Jetpack_Photon::parse_images_from_html( $content ); 
  128.  
  129. if ( ! empty( $images ) ) { 
  130. $content_width = Jetpack::get_content_width(); 
  131.  
  132. $image_sizes = self::image_sizes(); 
  133. $upload_dir = wp_upload_dir(); 
  134.  
  135. foreach ( $images[0] as $index => $tag ) { 
  136. // Default to resize, though fit may be used in certain cases where a dimension cannot be ascertained 
  137. $transform = 'resize'; 
  138.  
  139. // Start with a clean attachment ID each time 
  140. $attachment_id = false; 
  141.  
  142. // Flag if we need to munge a fullsize URL 
  143. $fullsize_url = false; 
  144.  
  145. // Identify image source 
  146. $src = $src_orig = $images['img_url'][ $index ]; 
  147.  
  148. /** 
  149. * Allow specific images to be skipped by Photon. 
  150. * 
  151. * @module photon 
  152. * 
  153. * @since 2.0.3 
  154. * 
  155. * @param bool false Should Photon ignore this image. Default to false. 
  156. * @param string $src Image URL. 
  157. * @param string $tag Image Tag (Image HTML output). 
  158. */ 
  159. if ( apply_filters( 'jetpack_photon_skip_image', false, $src, $tag ) ) 
  160. continue; 
  161.  
  162. // Support Automattic's Lazy Load plugin 
  163. // Can't modify $tag yet as we need unadulterated version later 
  164. if ( preg_match( '#data-lazy-src=["|\'](.+?)["|\']#i', $images['img_tag'][ $index ], $lazy_load_src ) ) { 
  165. $placeholder_src = $placeholder_src_orig = $src; 
  166. $src = $src_orig = $lazy_load_src[1]; 
  167. } elseif ( preg_match( '#data-lazy-original=["|\'](.+?)["|\']#i', $images['img_tag'][ $index ], $lazy_load_src ) ) { 
  168. $placeholder_src = $placeholder_src_orig = $src; 
  169. $src = $src_orig = $lazy_load_src[1]; 
  170.  
  171. // Check if image URL should be used with Photon 
  172. if ( self::validate_image_url( $src ) ) { 
  173. // Find the width and height attributes 
  174. $width = $height = false; 
  175.  
  176. // First, check the image tag 
  177. if ( preg_match( '#width=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $width_string ) ) 
  178. $width = $width_string[1]; 
  179.  
  180. if ( preg_match( '#height=["|\']?([\d%]+)["|\']?#i', $images['img_tag'][ $index ], $height_string ) ) 
  181. $height = $height_string[1]; 
  182.  
  183. // Can't pass both a relative width and height, so unset the height in favor of not breaking the horizontal layout. 
  184. if ( false !== strpos( $width, '%' ) && false !== strpos( $height, '%' ) ) 
  185. $width = $height = false; 
  186.  
  187. // Detect WP registered image size from HTML class 
  188. if ( preg_match( '#class=["|\']?[^"\']*size-([^"\'\s]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $size ) ) { 
  189. $size = array_pop( $size ); 
  190.  
  191. if ( false === $width && false === $height && 'full' != $size && array_key_exists( $size, $image_sizes ) ) { 
  192. $width = (int) $image_sizes[ $size ]['width']; 
  193. $height = (int) $image_sizes[ $size ]['height']; 
  194. $transform = $image_sizes[ $size ]['crop'] ? 'resize' : 'fit'; 
  195. } else { 
  196. unset( $size ); 
  197.  
  198. // WP Attachment ID, if uploaded to this site 
  199. if ( 
  200. preg_match( '#class=["|\']?[^"\']*wp-image-([\d]+)[^"\']*["|\']?#i', $images['img_tag'][ $index ], $attachment_id ) && 
  201. 0 === strpos( $src, $upload_dir['baseurl'] ) || 
  202. /** 
  203. * Filter whether an image using an attachment ID in its class has to be uploaded to the local site to go through Photon. 
  204. * 
  205. * @module photon 
  206. * 
  207. * @since 2.0.3 
  208. * 
  209. * @param bool false Was the image uploaded to the local site. Default to false. 
  210. * @param array $args { 
  211. * Array of image details. 
  212. * 
  213. * @type $src Image URL. 
  214. * @type tag Image tag (Image HTML output). 
  215. * @type $images Array of information about the image. 
  216. * @type $index Image index. 
  217. * } 
  218. */ 
  219. apply_filters( 'jetpack_photon_image_is_local', false, compact( 'src', 'tag', 'images', 'index' ) ) 
  220. ) { 
  221. $attachment_id = intval( array_pop( $attachment_id ) ); 
  222.  
  223. if ( $attachment_id ) { 
  224. $attachment = get_post( $attachment_id ); 
  225.  
  226. // Basic check on returned post object 
  227. if ( is_object( $attachment ) && ! is_wp_error( $attachment ) && 'attachment' == $attachment->post_type ) { 
  228. $src_per_wp = wp_get_attachment_image_src( $attachment_id, isset( $size ) ? $size : 'full' ); 
  229.  
  230. if ( self::validate_image_url( $src_per_wp[0] ) ) { 
  231. $src = $src_per_wp[0]; 
  232. $fullsize_url = true; 
  233.  
  234. // Prevent image distortion if a detected dimension exceeds the image's natural dimensions 
  235. if ( ( false !== $width && $width > $src_per_wp[1] ) || ( false !== $height && $height > $src_per_wp[2] ) ) { 
  236. $width = false == $width ? false : min( $width, $src_per_wp[1] ); 
  237. $height = false == $height ? false : min( $height, $src_per_wp[2] ); 
  238.  
  239. // If no width and height are found, max out at source image's natural dimensions 
  240. // Otherwise, respect registered image sizes' cropping setting 
  241. if ( false == $width && false == $height ) { 
  242. $width = $src_per_wp[1]; 
  243. $height = $src_per_wp[2]; 
  244. $transform = 'fit'; 
  245. } elseif ( isset( $size ) && array_key_exists( $size, $image_sizes ) && isset( $image_sizes[ $size ]['crop'] ) ) { 
  246. $transform = (bool) $image_sizes[ $size ]['crop'] ? 'resize' : 'fit'; 
  247. } else { 
  248. unset( $attachment_id ); 
  249. unset( $attachment ); 
  250.  
  251. // If image tag lacks width and height arguments, try to determine from strings WP appends to resized image filenames. 
  252. if ( false === $width && false === $height ) { 
  253. list( $width, $height ) = Jetpack_Photon::parse_dimensions_from_filename( $src ); 
  254.  
  255. // If width is available, constrain to $content_width 
  256. if ( false !== $width && false === strpos( $width, '%' ) && is_numeric( $content_width ) ) { 
  257. if ( $width > $content_width && false !== $height && false === strpos( $height, '%' ) ) { 
  258. $height = round( ( $content_width * $height ) / $width ); 
  259. $width = $content_width; 
  260. } elseif ( $width > $content_width ) { 
  261. $width = $content_width; 
  262.  
  263. // Set a width if none is found and $content_width is available 
  264. // If width is set in this manner and height is available, use `fit` instead of `resize` to prevent skewing 
  265. if ( false === $width && is_numeric( $content_width ) ) { 
  266. $width = (int) $content_width; 
  267.  
  268. if ( false !== $height ) 
  269. $transform = 'fit'; 
  270.  
  271. // Detect if image source is for a custom-cropped thumbnail and prevent further URL manipulation. 
  272. if ( ! $fullsize_url && preg_match_all( '#-e[a-z0-9]+(-\d+x\d+)?\.(' . implode('|', self::$extensions ) . ') {1}$#i', basename( $src ), $filename ) ) 
  273. $fullsize_url = true; 
  274.  
  275. // Build URL, first maybe removing WP's resized string so we pass the original image to Photon 
  276. if ( ! $fullsize_url ) { 
  277. $src = self::strip_image_dimensions_maybe( $src ); 
  278.  
  279. // Build array of Photon args and expose to filter before passing to Photon URL function 
  280. $args = array(); 
  281.  
  282. if ( false !== $width && false !== $height && false === strpos( $width, '%' ) && false === strpos( $height, '%' ) ) 
  283. $args[ $transform ] = $width . ', ' . $height; 
  284. elseif ( false !== $width ) 
  285. $args['w'] = $width; 
  286. elseif ( false !== $height ) 
  287. $args['h'] = $height; 
  288.  
  289. /** 
  290. * Filter the array of Photon arguments added to an image when it goes through Photon. 
  291. * By default, only includes width and height values. 
  292. * @see https://developer.wordpress.com/docs/photon/api/ 
  293. * 
  294. * @module photon 
  295. * 
  296. * @since 2.0.0 
  297. * 
  298. * @param array $args Array of Photon Arguments. 
  299. * @param array $args { 
  300. * Array of image details. 
  301. * 
  302. * @type $tag Image tag (Image HTML output). 
  303. * @type $src Image URL. 
  304. * @type $src_orig Original Image URL. 
  305. * @type $width Image width. 
  306. * @type $height Image height. 
  307. * } 
  308. */ 
  309. $args = apply_filters( 'jetpack_photon_post_image_args', $args, compact( 'tag', 'src', 'src_orig', 'width', 'height' ) ); 
  310.  
  311. $photon_url = jetpack_photon_url( $src, $args ); 
  312.  
  313. // Modify image tag if Photon function provides a URL 
  314. // Ensure changes are only applied to the current image by copying and modifying the matched tag, then replacing the entire tag with our modified version. 
  315. if ( $src != $photon_url ) { 
  316. $new_tag = $tag; 
  317.  
  318. // If present, replace the link href with a Photoned URL for the full-size image. 
  319. if ( ! empty( $images['link_url'][ $index ] ) && self::validate_image_url( $images['link_url'][ $index ] ) ) 
  320. $new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . jetpack_photon_url( $images['link_url'][ $index ] ) . '\2', $new_tag, 1 ); 
  321.  
  322. // Supplant the original source value with our Photon URL 
  323. $photon_url = esc_url( $photon_url ); 
  324. $new_tag = str_replace( $src_orig, $photon_url, $new_tag ); 
  325.  
  326. // If Lazy Load is in use, pass placeholder image through Photon 
  327. if ( isset( $placeholder_src ) && self::validate_image_url( $placeholder_src ) ) { 
  328. $placeholder_src = jetpack_photon_url( $placeholder_src ); 
  329.  
  330. if ( $placeholder_src != $placeholder_src_orig ) 
  331. $new_tag = str_replace( $placeholder_src_orig, esc_url( $placeholder_src ), $new_tag ); 
  332.  
  333. unset( $placeholder_src ); 
  334.  
  335. // Remove the width and height arguments from the tag to prevent distortion 
  336. $new_tag = preg_replace( '#(?<=\s)(width|height)=["|\']?[\d%]+["|\']?\s?#i', '', $new_tag ); 
  337.  
  338. // Tag an image for dimension checking 
  339. $new_tag = preg_replace( '#(\s?/)?>(\s*</a>)?$#i', ' data-recalc-dims="1"\1>\2', $new_tag ); 
  340.  
  341. // Replace original tag with modified version 
  342. $content = str_replace( $tag, $new_tag, $content ); 
  343. } elseif ( preg_match( '#^http(s)?://i[\d]{1}.wp.com#', $src ) && ! empty( $images['link_url'][ $index ] ) && self::validate_image_url( $images['link_url'][ $index ] ) ) { 
  344. $new_tag = preg_replace( '#(href=["|\'])' . $images['link_url'][ $index ] . '(["|\'])#i', '\1' . jetpack_photon_url( $images['link_url'][ $index ] ) . '\2', $tag, 1 ); 
  345.  
  346. $content = str_replace( $tag, $new_tag, $content ); 
  347.  
  348. return $content; 
  349.  
  350. public static function filter_the_galleries( $galleries ) { 
  351. if ( empty( $galleries ) || ! is_array( $galleries ) ) { 
  352. return $galleries; 
  353.  
  354. // Pass by reference, so we can modify them in place. 
  355. foreach ( $galleries as &$this_gallery ) { 
  356. if ( is_string( $this_gallery ) ) { 
  357. $this_gallery = self::filter_the_content( $this_gallery ); 
  358. // LEAVING COMMENTED OUT as for the moment it doesn't seem 
  359. // necessary and I'm not sure how it would propagate through. 
  360. // } elseif ( is_array( $this_gallery ) 
  361. // && ! empty( $this_gallery['src'] ) 
  362. // && ! empty( $this_gallery['type'] ) 
  363. // && in_array( $this_gallery['type'], array( 'rectangle', 'square', 'circle' ) ) ) { 
  364. // $this_gallery['src'] = array_map( 'jetpack_photon_url', $this_gallery['src'] ); 
  365. unset( $this_gallery ); // break the reference. 
  366.  
  367. return $galleries; 
  368.  
  369. /** 
  370. ** CORE IMAGE RETRIEVAL 
  371. **/ 
  372.  
  373. /** 
  374. * Filter post thumbnail image retrieval, passing images through Photon 
  375. * 
  376. * @param string|bool $image 
  377. * @param int $attachment_id 
  378. * @param string|array $size 
  379. * @uses is_admin, apply_filters, wp_get_attachment_url, self::validate_image_url, this::image_sizes, jetpack_photon_url 
  380. * @filter image_downsize 
  381. * @return string|bool 
  382. */ 
  383. public function filter_image_downsize( $image, $attachment_id, $size ) { 
  384. // Don't foul up the admin side of things, and provide plugins a way of preventing Photon from being applied to images. 
  385. if ( 
  386. is_admin() || 
  387. /** 
  388. * Provide plugins a way of preventing Photon from being applied to images retrieved from WordPress Core. 
  389. * 
  390. * @module photon 
  391. * 
  392. * @since 2.0.0 
  393. * 
  394. * @param bool false Stop Photon from being applied to the image. Default to false. 
  395. * @param array $args { 
  396. * Array of image details. 
  397. * 
  398. * @type $image Image URL. 
  399. * @type $attachment_id Attachment ID of the image. 
  400. * @type $size Image size. Can be a string (name of the image size, e.g. full) or an integer. 
  401. * } 
  402. */ 
  403. apply_filters( 'jetpack_photon_override_image_downsize', false, compact( 'image', 'attachment_id', 'size' ) ) 
  404. return $image; 
  405.  
  406. // Get the image URL and proceed with Photon-ification if successful 
  407. $image_url = wp_get_attachment_url( $attachment_id ); 
  408.  
  409. if ( $image_url ) { 
  410. // Check if image URL should be used with Photon 
  411. if ( ! self::validate_image_url( $image_url ) ) 
  412. return $image; 
  413.  
  414. // If an image is requested with a size known to WordPress, use that size's settings with Photon 
  415. if ( ( is_string( $size ) || is_int( $size ) ) && array_key_exists( $size, self::image_sizes() ) ) { 
  416. $image_args = self::image_sizes(); 
  417. $image_args = $image_args[ $size ]; 
  418.  
  419. $photon_args = array(); 
  420.  
  421. // `full` is a special case in WP 
  422. // To ensure filter receives consistent data regardless of requested size, `$image_args` is overridden with dimensions of original image. 
  423. if ( 'full' == $size ) { 
  424. $image_meta = wp_get_attachment_metadata( $attachment_id ); 
  425. if ( isset( $image_meta['width'], $image_meta['height'] ) ) { 
  426. // 'crop' is true so Photon's `resize` method is used 
  427. $image_args = array( 
  428. 'width' => $image_meta['width'],  
  429. 'height' => $image_meta['height'],  
  430. 'crop' => true 
  431. ); 
  432.  
  433. // Expose determined arguments to a filter before passing to Photon 
  434. $transform = $image_args['crop'] ? 'resize' : 'fit'; 
  435.  
  436. // Check specified image dimensions and account for possible zero values; photon fails to resize if a dimension is zero. 
  437. if ( 0 == $image_args['width'] || 0 == $image_args['height'] ) { 
  438. if ( 0 == $image_args['width'] && 0 < $image_args['height'] ) { 
  439. $photon_args['h'] = $image_args['height']; 
  440. } elseif ( 0 == $image_args['height'] && 0 < $image_args['width'] ) { 
  441. $photon_args['w'] = $image_args['width']; 
  442. } else { 
  443. if ( ( 'resize' === $transform ) && $image_meta = wp_get_attachment_metadata( $attachment_id ) ) { 
  444. // Lets make sure that we don't upscale images since wp never upscales them as well 
  445. $smaller_width = ( ( $image_meta['width'] < $image_args['width'] ) ? $image_meta['width'] : $image_args['width'] ); 
  446. $smaller_height = ( ( $image_meta['height'] < $image_args['height'] ) ? $image_meta['height'] : $image_args['height'] ); 
  447.  
  448. $photon_args[ $transform ] = $smaller_width . ', ' . $smaller_height; 
  449. } else { 
  450. $photon_args[ $transform ] = $image_args['width'] . ', ' . $image_args['height']; 
  451.  
  452.  
  453. /** 
  454. * Filter the Photon Arguments added to an image when going through Photon, when that image size is a string. 
  455. * Image size will be a string (e.g. "full", "medium") when it is known to WordPress. 
  456. * 
  457. * @module photon 
  458. * 
  459. * @since 2.0.0 
  460. * 
  461. * @param array $photon_args Array of Photon arguments. 
  462. * @param array $args { 
  463. * Array of image details. 
  464. * 
  465. * @type $image_args Array of Image arguments (width, height, crop). 
  466. * @type $image_url Image URL. 
  467. * @type $attachment_id Attachment ID of the image. 
  468. * @type $size Image size. Can be a string (name of the image size, e.g. full) or an integer. 
  469. * @type $transform Value can be resize or fit. 
  470. * @see https://developer.wordpress.com/docs/photon/api 
  471. * } 
  472. */ 
  473. $photon_args = apply_filters( 'jetpack_photon_image_downsize_string', $photon_args, compact( 'image_args', 'image_url', 'attachment_id', 'size', 'transform' ) ); 
  474.  
  475. // Generate Photon URL 
  476. $image = array( 
  477. jetpack_photon_url( $image_url, $photon_args ),  
  478. false,  
  479. false 
  480. ); 
  481. } elseif ( is_array( $size ) ) { 
  482. // Pull width and height values from the provided array, if possible 
  483. $width = isset( $size[0] ) ? (int) $size[0] : false; 
  484. $height = isset( $size[1] ) ? (int) $size[1] : false; 
  485.  
  486. // Don't bother if necessary parameters aren't passed. 
  487. if ( ! $width || ! $height ) 
  488. return $image; 
  489.  
  490. // Expose arguments to a filter before passing to Photon 
  491. $photon_args = array( 
  492. 'fit' => $width . ', ' . $height 
  493. ); 
  494.  
  495. /** 
  496. * Filter the Photon Arguments added to an image when going through Photon,  
  497. * when the image size is an array of height and width values. 
  498. * 
  499. * @module photon 
  500. * 
  501. * @since 2.0.0 
  502. * 
  503. * @param array $photon_args Array of Photon arguments. 
  504. * @param array $args { 
  505. * Array of image details. 
  506. * 
  507. * @type $width Image width. 
  508. * @type height Image height. 
  509. * @type $image_url Image URL. 
  510. * @type $attachment_id Attachment ID of the image. 
  511. * } 
  512. */ 
  513. $photon_args = apply_filters( 'jetpack_photon_image_downsize_array', $photon_args, compact( 'width', 'height', 'image_url', 'attachment_id' ) ); 
  514.  
  515. // Generate Photon URL 
  516. $image = array( 
  517. jetpack_photon_url( $image_url, $photon_args ),  
  518. false,  
  519. false 
  520. ); 
  521.  
  522. return $image; 
  523.  
  524. /** 
  525. * Filters an array of image `srcset` values, replacing each URL with its Photon equivalent. 
  526. * 
  527. * @since 3.8.0 
  528. * @param array $sources An array of image urls and widths. 
  529. * @uses self::validate_image_url, jetpack_photon_url 
  530. * @return array An array of Photon image urls and widths. 
  531. */ 
  532. public function filter_srcset_array( $sources ) { 
  533. foreach ( $sources as $i => $source ) { 
  534. if ( ! self::validate_image_url( $source['url'] ) ) { 
  535. continue; 
  536.  
  537. $url = Jetpack_Photon::strip_image_dimensions_maybe( $source['url'] ); 
  538.  
  539. $args = array(); 
  540. if ( 'w' === $source['descriptor'] ) { 
  541. $args['w'] = $source['value']; 
  542.  
  543. $sources[ $i ]['url'] = jetpack_photon_url( $url, $args ); 
  544.  
  545. return $sources; 
  546.  
  547. /** 
  548. ** GENERAL FUNCTIONS 
  549. **/ 
  550.  
  551. /** 
  552. * Ensure image URL is valid for Photon. 
  553. * Though Photon functions address some of the URL issues, we should avoid unnecessary processing if we know early on that the image isn't supported. 
  554. * 
  555. * @param string $url 
  556. * @uses wp_parse_args 
  557. * @return bool 
  558. */ 
  559. protected static function validate_image_url( $url ) { 
  560. $parsed_url = @parse_url( $url ); 
  561.  
  562. if ( ! $parsed_url ) 
  563. return false; 
  564.  
  565. // Parse URL and ensure needed keys exist, since the array returned by `parse_url` only includes the URL components it finds. 
  566. $url_info = wp_parse_args( $parsed_url, array( 
  567. 'scheme' => null,  
  568. 'host' => null,  
  569. 'port' => null,  
  570. 'path' => null 
  571. ) ); 
  572.  
  573. // Bail if scheme isn't http or port is set that isn't port 80 
  574. if ( 
  575. ( 'http' != $url_info['scheme'] || ! in_array( $url_info['port'], array( 80, null ) ) ) && 
  576. /** 
  577. * Allow Photon to fetch images that are served via HTTPS. 
  578. * 
  579. * @module photon 
  580. * 
  581. * @since 2.4.0 
  582. * 
  583. * @param bool true Should Photon ignore images using the HTTPS scheme. Default to true. 
  584. */ 
  585. apply_filters( 'jetpack_photon_reject_https', true ) 
  586. ) { 
  587. return false; 
  588.  
  589. // Bail if no host is found 
  590. if ( is_null( $url_info['host'] ) ) 
  591. return false; 
  592.  
  593. // Bail if the image alredy went through Photon 
  594. if ( preg_match( '#^i[\d]{1}.wp.com$#i', $url_info['host'] ) ) 
  595. return false; 
  596.  
  597. // Bail if no path is found 
  598. if ( is_null( $url_info['path'] ) ) 
  599. return false; 
  600.  
  601. // Ensure image extension is acceptable 
  602. if ( ! in_array( strtolower( pathinfo( $url_info['path'], PATHINFO_EXTENSION ) ), self::$extensions ) ) 
  603. return false; 
  604.  
  605. // If we got this far, we should have an acceptable image URL 
  606. // But let folks filter to decline if they prefer. 
  607. /** 
  608. * Overwrite the results of the validation steps an image goes through before to be considered valid to be used by Photon. 
  609. * 
  610. * @module photon 
  611. * 
  612. * @since 3.0.0 
  613. * 
  614. * @param bool true Is the image URL valid and can it be used by Photon. Default to true. 
  615. * @param string $url Image URL. 
  616. * @param array $parsed_url Array of information about the image. 
  617. */ 
  618. return apply_filters( 'photon_validate_image_url', true, $url, $parsed_url ); 
  619.  
  620. /** 
  621. * Checks if the file exists before it passes the file to photon 
  622. * 
  623. * @param string $src The image URL 
  624. * @return string 
  625. **/ 
  626. protected static function strip_image_dimensions_maybe( $src ) { 
  627. $stripped_src = $src; 
  628.  
  629. // Build URL, first removing WP's resized string so we pass the original image to Photon 
  630. if ( preg_match( '#(-\d+x\d+)\.(' . implode('|', self::$extensions ) . ') {1}$#i', $src, $src_parts ) ) { 
  631. $stripped_src = str_replace( $src_parts[1], '', $src ); 
  632. $upload_dir = wp_upload_dir(); 
  633.  
  634. // Extracts the file path to the image minus the base url 
  635. $file_path = substr( $stripped_src, strlen ( $upload_dir['baseurl'] ) ); 
  636.  
  637. if( file_exists( $upload_dir["basedir"] . $file_path ) ) 
  638. $src = $stripped_src; 
  639.  
  640. return $src; 
  641.  
  642. /** 
  643. * Provide an array of available image sizes and corresponding dimensions. 
  644. * Similar to get_intermediate_image_sizes() except that it includes image sizes' dimensions, not just their names. 
  645. * 
  646. * @global $wp_additional_image_sizes 
  647. * @uses get_option 
  648. * @return array 
  649. */ 
  650. protected static function image_sizes() { 
  651. if ( null == self::$image_sizes ) { 
  652. global $_wp_additional_image_sizes; 
  653.  
  654. // Populate an array matching the data structure of $_wp_additional_image_sizes so we have a consistent structure for image sizes 
  655. $images = array( 
  656. 'thumb' => array( 
  657. 'width' => intval( get_option( 'thumbnail_size_w' ) ),  
  658. 'height' => intval( get_option( 'thumbnail_size_h' ) ),  
  659. 'crop' => (bool) get_option( 'thumbnail_crop' ) 
  660. ),  
  661. 'medium' => array( 
  662. 'width' => intval( get_option( 'medium_size_w' ) ),  
  663. 'height' => intval( get_option( 'medium_size_h' ) ),  
  664. 'crop' => false 
  665. ),  
  666. 'large' => array( 
  667. 'width' => intval( get_option( 'large_size_w' ) ),  
  668. 'height' => intval( get_option( 'large_size_h' ) ),  
  669. 'crop' => false 
  670. ),  
  671. 'full' => array( 
  672. 'width' => null,  
  673. 'height' => null,  
  674. 'crop' => false 
  675. ); 
  676.  
  677. // Compatibility mapping as found in wp-includes/media.php 
  678. $images['thumbnail'] = $images['thumb']; 
  679.  
  680. // Update class variable, merging in $_wp_additional_image_sizes if any are set 
  681. if ( is_array( $_wp_additional_image_sizes ) && ! empty( $_wp_additional_image_sizes ) ) 
  682. self::$image_sizes = array_merge( $images, $_wp_additional_image_sizes ); 
  683. else 
  684. self::$image_sizes = $images; 
  685.  
  686. return is_array( self::$image_sizes ) ? self::$image_sizes : array(); 
  687.  
  688. /** 
  689. * Pass og:image URLs through Photon 
  690. * 
  691. * @param array $tags 
  692. * @param array $parameters 
  693. * @uses jetpack_photon_url 
  694. * @return array 
  695. */ 
  696. function filter_open_graph_tags( $tags, $parameters ) { 
  697. if ( empty( $tags['og:image'] ) ) { 
  698. return $tags; 
  699.  
  700. $photon_args = array( 
  701. 'fit' => sprintf( '%d, %d', 2 * $parameters['image_width'], 2 * $parameters['image_height'] ),  
  702. ); 
  703.  
  704. if ( is_array( $tags['og:image'] ) ) { 
  705. $images = array(); 
  706. foreach ( $tags['og:image'] as $image ) { 
  707. $images[] = jetpack_photon_url( $image, $photon_args ); 
  708. $tags['og:image'] = $images; 
  709. } else { 
  710. $tags['og:image'] = jetpack_photon_url( $tags['og:image'], $photon_args ); 
  711.  
  712. return $tags; 
  713.  
  714. /** 
  715. * Enqueue Photon helper script 
  716. * 
  717. * @uses wp_enqueue_script, plugins_url 
  718. * @action wp_enqueue_script 
  719. * @return null 
  720. */ 
  721. public function action_wp_enqueue_scripts() { 
  722. wp_enqueue_script( 'jetpack-photon', plugins_url( 'modules/photon/photon.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), 20130122, true ); 
.