PageRenderTime 48ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://gitlab.com/thisishayat/itv-2016
PHP | 841 lines | 400 code | 129 blank | 312 comment | 129 complexity | 6a9ba021e23cb46107ac9a640d8e4323 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[^>]*?\s+?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. // Set this to true later when we know we have size meta.
  375. $has_size_meta = false;
  376. if ( $image_url ) {
  377. // Check if image URL should be used with Photon
  378. if ( ! self::validate_image_url( $image_url ) )
  379. return $image;
  380. $intermediate = true; // For the fourth array item returned by the image_downsize filter.
  381. // If an image is requested with a size known to WordPress, use that size's settings with Photon
  382. if ( ( is_string( $size ) || is_int( $size ) ) && array_key_exists( $size, self::image_sizes() ) ) {
  383. $image_args = self::image_sizes();
  384. $image_args = $image_args[ $size ];
  385. $photon_args = array();
  386. $image_meta = image_get_intermediate_size( $attachment_id, $size );
  387. // 'full' is a special case: We need consistent data regardless of the requested size.
  388. if ( 'full' == $size ) {
  389. $image_meta = wp_get_attachment_metadata( $attachment_id );
  390. $intermediate = false;
  391. } elseif ( ! $image_meta ) {
  392. // If we still don't have any image meta at this point, it's probably from a custom thumbnail size
  393. // for an image that was uploaded before the custom image was added to the theme. Try to determine the size manually.
  394. $image_meta = wp_get_attachment_metadata( $attachment_id );
  395. if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
  396. $image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $image_args['width'], $image_args['height'], $image_args['crop'] );
  397. if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
  398. $image_meta['width'] = $image_resized[6];
  399. $image_meta['height'] = $image_resized[7];
  400. }
  401. }
  402. }
  403. if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
  404. $image_args['width'] = $image_meta['width'];
  405. $image_args['height'] = $image_meta['height'];
  406. list( $image_args['width'], $image_args['height'] ) = image_constrain_size_for_editor( $image_args['width'], $image_args['height'], $size, 'display' );
  407. $has_size_meta = true;
  408. }
  409. // Expose determined arguments to a filter before passing to Photon
  410. $transform = $image_args['crop'] ? 'resize' : 'fit';
  411. // Check specified image dimensions and account for possible zero values; photon fails to resize if a dimension is zero.
  412. if ( 0 == $image_args['width'] || 0 == $image_args['height'] ) {
  413. if ( 0 == $image_args['width'] && 0 < $image_args['height'] ) {
  414. $photon_args['h'] = $image_args['height'];
  415. } elseif ( 0 == $image_args['height'] && 0 < $image_args['width'] ) {
  416. $photon_args['w'] = $image_args['width'];
  417. }
  418. } else {
  419. if ( ( 'resize' === $transform ) && $image_meta = wp_get_attachment_metadata( $attachment_id ) ) {
  420. if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
  421. // Lets make sure that we don't upscale images since wp never upscales them as well
  422. $smaller_width = ( ( $image_meta['width'] < $image_args['width'] ) ? $image_meta['width'] : $image_args['width'] );
  423. $smaller_height = ( ( $image_meta['height'] < $image_args['height'] ) ? $image_meta['height'] : $image_args['height'] );
  424. $photon_args[ $transform ] = $smaller_width . ',' . $smaller_height;
  425. }
  426. } else {
  427. $photon_args[ $transform ] = $image_args['width'] . ',' . $image_args['height'];
  428. }
  429. }
  430. /**
  431. * Filter the Photon Arguments added to an image when going through Photon, when that image size is a string.
  432. * Image size will be a string (e.g. "full", "medium") when it is known to WordPress.
  433. *
  434. * @module photon
  435. *
  436. * @since 2.0.0
  437. *
  438. * @param array $photon_args Array of Photon arguments.
  439. * @param array $args {
  440. * Array of image details.
  441. *
  442. * @type $image_args Array of Image arguments (width, height, crop).
  443. * @type $image_url Image URL.
  444. * @type $attachment_id Attachment ID of the image.
  445. * @type $size Image size. Can be a string (name of the image size, e.g. full) or an integer.
  446. * @type $transform Value can be resize or fit.
  447. * @see https://developer.wordpress.com/docs/photon/api
  448. * }
  449. */
  450. $photon_args = apply_filters( 'jetpack_photon_image_downsize_string', $photon_args, compact( 'image_args', 'image_url', 'attachment_id', 'size', 'transform' ) );
  451. // Generate Photon URL
  452. $image = array(
  453. jetpack_photon_url( $image_url, $photon_args ),
  454. $has_size_meta ? $image_args['width'] : false,
  455. $has_size_meta ? $image_args['height'] : false,
  456. $intermediate
  457. );
  458. } elseif ( is_array( $size ) ) {
  459. // Pull width and height values from the provided array, if possible
  460. $width = isset( $size[0] ) ? (int) $size[0] : false;
  461. $height = isset( $size[1] ) ? (int) $size[1] : false;
  462. // Don't bother if necessary parameters aren't passed.
  463. if ( ! $width || ! $height ) {
  464. return $image;
  465. }
  466. $image_meta = wp_get_attachment_metadata( $attachment_id );
  467. if ( isset( $image_meta['width'], $image_meta['height'] ) ) {
  468. $image_resized = image_resize_dimensions( $image_meta['width'], $image_meta['height'], $width, $height );
  469. if ( $image_resized ) { // This could be false when the requested image size is larger than the full-size image.
  470. $width = $image_resized[6];
  471. $height = $image_resized[7];
  472. } else {
  473. $width = $image_meta['width'];
  474. $height = $image_meta['height'];
  475. }
  476. $has_size_meta = true;
  477. }
  478. list( $width, $height ) = image_constrain_size_for_editor( $width, $height, $size );
  479. // Expose arguments to a filter before passing to Photon
  480. $photon_args = array(
  481. 'fit' => $width . ',' . $height
  482. );
  483. /**
  484. * Filter the Photon Arguments added to an image when going through Photon,
  485. * when the image size is an array of height and width values.
  486. *
  487. * @module photon
  488. *
  489. * @since 2.0.0
  490. *
  491. * @param array $photon_args Array of Photon arguments.
  492. * @param array $args {
  493. * Array of image details.
  494. *
  495. * @type $width Image width.
  496. * @type height Image height.
  497. * @type $image_url Image URL.
  498. * @type $attachment_id Attachment ID of the image.
  499. * }
  500. */
  501. $photon_args = apply_filters( 'jetpack_photon_image_downsize_array', $photon_args, compact( 'width', 'height', 'image_url', 'attachment_id' ) );
  502. // Generate Photon URL
  503. $image = array(
  504. jetpack_photon_url( $image_url, $photon_args ),
  505. $has_size_meta ? $width : false,
  506. $has_size_meta ? $height : false,
  507. $intermediate
  508. );
  509. }
  510. }
  511. return $image;
  512. }
  513. /**
  514. * Filters an array of image `srcset` values, replacing each URL with its Photon equivalent.
  515. *
  516. * @since 3.8.0
  517. * @param array $sources An array of image urls and widths.
  518. * @uses self::validate_image_url, jetpack_photon_url
  519. * @return array An array of Photon image urls and widths.
  520. */
  521. public function filter_srcset_array( $sources, $size_array, $image_src, $image_meta ) {
  522. $upload_dir = wp_upload_dir();
  523. foreach ( $sources as $i => $source ) {
  524. if ( ! self::validate_image_url( $source['url'] ) ) {
  525. continue;
  526. }
  527. $url = $source['url'];
  528. list( $width, $height ) = Jetpack_Photon::parse_dimensions_from_filename( $url );
  529. // It's quicker to get the full size with the data we have already, if available
  530. if ( isset( $image_meta['file'] ) ) {
  531. $url = trailingslashit( $upload_dir['baseurl'] ) . $image_meta['file'];
  532. } else {
  533. $url = Jetpack_Photon::strip_image_dimensions_maybe( $url );
  534. }
  535. $args = array();
  536. if ( 'w' === $source['descriptor'] ) {
  537. if ( $height && ( $source['value'] == $width ) ) {
  538. $args['resize'] = $width . ',' . $height;
  539. } else {
  540. $args['w'] = $source['value'];
  541. }
  542. }
  543. $sources[ $i ]['url'] = jetpack_photon_url( $url, $args );
  544. }
  545. return $sources;
  546. }
  547. /**
  548. ** GENERAL FUNCTIONS
  549. **/
  550. /**
  551. * Ensure image URL is valid for Photon.
  552. * 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.
  553. *
  554. * @param string $url
  555. * @uses wp_parse_args
  556. * @return bool
  557. */
  558. protected static function validate_image_url( $url ) {
  559. $parsed_url = @parse_url( $url );
  560. if ( ! $parsed_url )
  561. return false;
  562. // Parse URL and ensure needed keys exist, since the array returned by `parse_url` only includes the URL components it finds.
  563. $url_info = wp_parse_args( $parsed_url, array(
  564. 'scheme' => null,
  565. 'host' => null,
  566. 'port' => null,
  567. 'path' => null
  568. ) );
  569. // Bail if scheme isn't http or port is set that isn't port 80
  570. if (
  571. ( 'http' != $url_info['scheme'] || ! in_array( $url_info['port'], array( 80, null ) ) ) &&
  572. /**
  573. * Allow Photon to fetch images that are served via HTTPS.
  574. *
  575. * @module photon
  576. *
  577. * @since 2.4.0
  578. * @since 3.9.0 Default to false.
  579. *
  580. * @param bool $reject_https Should Photon ignore images using the HTTPS scheme. Default to false.
  581. */
  582. apply_filters( 'jetpack_photon_reject_https', false )
  583. ) {
  584. return false;
  585. }
  586. // Bail if no host is found
  587. if ( is_null( $url_info['host'] ) )
  588. return false;
  589. // Bail if the image alredy went through Photon
  590. if ( preg_match( '#^i[\d]{1}.wp.com$#i', $url_info['host'] ) )
  591. return false;
  592. // Bail if no path is found
  593. if ( is_null( $url_info['path'] ) )
  594. return false;
  595. // Ensure image extension is acceptable
  596. if ( ! in_array( strtolower( pathinfo( $url_info['path'], PATHINFO_EXTENSION ) ), self::$extensions ) )
  597. return false;
  598. // If we got this far, we should have an acceptable image URL
  599. // But let folks filter to decline if they prefer.
  600. /**
  601. * Overwrite the results of the validation steps an image goes through before to be considered valid to be used by Photon.
  602. *
  603. * @module photon
  604. *
  605. * @since 3.0.0
  606. *
  607. * @param bool true Is the image URL valid and can it be used by Photon. Default to true.
  608. * @param string $url Image URL.
  609. * @param array $parsed_url Array of information about the image.
  610. */
  611. return apply_filters( 'photon_validate_image_url', true, $url, $parsed_url );
  612. }
  613. /**
  614. * Checks if the file exists before it passes the file to photon
  615. *
  616. * @param string $src The image URL
  617. * @return string
  618. **/
  619. protected static function strip_image_dimensions_maybe( $src ){
  620. $stripped_src = $src;
  621. // Build URL, first removing WP's resized string so we pass the original image to Photon
  622. if ( preg_match( '#(-\d+x\d+)\.(' . implode('|', self::$extensions ) . '){1}$#i', $src, $src_parts ) ) {
  623. $stripped_src = str_replace( $src_parts[1], '', $src );
  624. $upload_dir = wp_upload_dir();
  625. // Extracts the file path to the image minus the base url
  626. $file_path = substr( $stripped_src, strlen ( $upload_dir['baseurl'] ) );
  627. if( file_exists( $upload_dir["basedir"] . $file_path ) )
  628. $src = $stripped_src;
  629. }
  630. return $src;
  631. }
  632. /**
  633. * Provide an array of available image sizes and corresponding dimensions.
  634. * Similar to get_intermediate_image_sizes() except that it includes image sizes' dimensions, not just their names.
  635. *
  636. * @global $wp_additional_image_sizes
  637. * @uses get_option
  638. * @return array
  639. */
  640. protected static function image_sizes() {
  641. if ( null == self::$image_sizes ) {
  642. global $_wp_additional_image_sizes;
  643. // Populate an array matching the data structure of $_wp_additional_image_sizes so we have a consistent structure for image sizes
  644. $images = array(
  645. 'thumb' => array(
  646. 'width' => intval( get_option( 'thumbnail_size_w' ) ),
  647. 'height' => intval( get_option( 'thumbnail_size_h' ) ),
  648. 'crop' => (bool) get_option( 'thumbnail_crop' )
  649. ),
  650. 'medium' => array(
  651. 'width' => intval( get_option( 'medium_size_w' ) ),
  652. 'height' => intval( get_option( 'medium_size_h' ) ),
  653. 'crop' => false
  654. ),
  655. 'large' => array(
  656. 'width' => intval( get_option( 'large_size_w' ) ),
  657. 'height' => intval( get_option( 'large_size_h' ) ),
  658. 'crop' => false
  659. ),
  660. 'full' => array(
  661. 'width' => null,
  662. 'height' => null,
  663. 'crop' => false
  664. )
  665. );
  666. // Compatibility mapping as found in wp-includes/media.php
  667. $images['thumbnail'] = $images['thumb'];
  668. // Update class variable, merging in $_wp_additional_image_sizes if any are set
  669. if ( is_array( $_wp_additional_image_sizes ) && ! empty( $_wp_additional_image_sizes ) )
  670. self::$image_sizes = array_merge( $images, $_wp_additional_image_sizes );
  671. else
  672. self::$image_sizes = $images;
  673. }
  674. return is_array( self::$image_sizes ) ? self::$image_sizes : array();
  675. }
  676. /**
  677. * Pass og:image URLs through Photon
  678. *
  679. * @param array $tags
  680. * @param array $parameters
  681. * @uses jetpack_photon_url
  682. * @return array
  683. */
  684. function filter_open_graph_tags( $tags, $parameters ) {
  685. if ( empty( $tags['og:image'] ) ) {
  686. return $tags;
  687. }
  688. $photon_args = array(
  689. 'fit' => sprintf( '%d,%d', 2 * $parameters['image_width'], 2 * $parameters['image_height'] ),
  690. );
  691. if ( is_array( $tags['og:image'] ) ) {
  692. $images = array();
  693. foreach ( $tags['og:image'] as $image ) {
  694. $images[] = jetpack_photon_url( $image, $photon_args );
  695. }
  696. $tags['og:image'] = $images;
  697. } else {
  698. $tags['og:image'] = jetpack_photon_url( $tags['og:image'], $photon_args );
  699. }
  700. return $tags;
  701. }
  702. /**
  703. * Enqueue Photon helper script
  704. *
  705. * @uses wp_enqueue_script, plugins_url
  706. * @action wp_enqueue_script
  707. * @return null
  708. */
  709. public function action_wp_enqueue_scripts() {
  710. wp_enqueue_script( 'jetpack-photon', plugins_url( 'modules/photon/photon.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), 20130122, true );
  711. }
  712. }