PageRenderTime 56ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/blog/wp-content/plugins/jetpack/class.photon.php

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