暫無描述

photon-cdn.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <?php
  2. /**
  3. * Module Name: Asset CDN
  4. * Module Description: Jetpack’s Site Accelerator loads your site faster by optimizing your images and serving your images and static files from our global network of servers.
  5. * Sort Order: 26
  6. * Recommendation Order: 1
  7. * First Introduced: 6.6
  8. * Requires Connection: No
  9. * Auto Activate: No
  10. * Module Tags: Photos and Videos, Appearance, Recommended
  11. * Feature: Recommended, Appearance
  12. * Additional Search Queries: site accelerator, accelerate, static, assets, javascript, css, files, performance, cdn, bandwidth, content delivery network, pagespeed, combine js, optimize css
  13. */
  14. use Automattic\Jetpack\Assets;
  15. $GLOBALS['concatenate_scripts'] = false;
  16. Assets::add_resource_hint( '//c0.wp.com', 'dns-prefetch' );
  17. class Jetpack_Photon_Static_Assets_CDN {
  18. const CDN = 'https://c0.wp.com/';
  19. /**
  20. * Sets up action handlers needed for Jetpack CDN.
  21. */
  22. public static function go() {
  23. add_action( 'wp_print_scripts', array( __CLASS__, 'cdnize_assets' ) );
  24. add_action( 'wp_print_styles', array( __CLASS__, 'cdnize_assets' ) );
  25. add_action( 'admin_print_scripts', array( __CLASS__, 'cdnize_assets' ) );
  26. add_action( 'admin_print_styles', array( __CLASS__, 'cdnize_assets' ) );
  27. add_action( 'wp_footer', array( __CLASS__, 'cdnize_assets' ) );
  28. add_filter( 'load_script_textdomain_relative_path', array( __CLASS__, 'fix_script_relative_path' ), 10, 2 );
  29. add_filter( 'load_script_translation_file', array( __CLASS__, 'fix_local_script_translation_path' ), 10, 3 );
  30. }
  31. /**
  32. * Sets up CDN URLs for assets that are enqueued by the WordPress Core.
  33. */
  34. public static function cdnize_assets() {
  35. global $wp_scripts, $wp_styles, $wp_version;
  36. /*
  37. * Short-circuit if AMP since not relevant as custom JS is not allowed and CSS is inlined.
  38. * Note that it is not suitable to use the jetpack_force_disable_site_accelerator filter for this
  39. * because it will be applied before the wp action, which is the point at which the queried object
  40. * is available and we know whether the response will be AMP or not. This is particularly important
  41. * for AMP-first (native AMP) pages where there are no AMP-specific URLs.
  42. */
  43. if ( Jetpack_AMP_Support::is_amp_request() ) {
  44. return;
  45. }
  46. /**
  47. * Filters Jetpack CDN's Core version number and locale. Can be used to override the values
  48. * that Jetpack uses to retrieve assets. Expects the values to be returned in an array.
  49. *
  50. * @module photon-cdn
  51. *
  52. * @since 6.6.0
  53. *
  54. * @param array $values array( $version = core assets version, i.e. 4.9.8, $locale = desired locale )
  55. */
  56. list( $version, $locale ) = apply_filters(
  57. 'jetpack_cdn_core_version_and_locale',
  58. array( $wp_version, get_locale() )
  59. );
  60. if ( self::is_public_version( $version ) ) {
  61. $site_url = trailingslashit( site_url() );
  62. foreach ( $wp_scripts->registered as $handle => $thing ) {
  63. if ( wp_startswith( $thing->src, self::CDN ) ) {
  64. continue;
  65. }
  66. $src = ltrim( str_replace( $site_url, '', $thing->src ), '/' );
  67. if ( self::is_js_or_css_file( $src ) && in_array( substr( $src, 0, 9 ), array( 'wp-admin/', 'wp-includ' ) ) ) {
  68. $wp_scripts->registered[ $handle ]->src = sprintf( self::CDN . 'c/%1$s/%2$s', $version, $src );
  69. $wp_scripts->registered[ $handle ]->ver = null;
  70. }
  71. }
  72. foreach ( $wp_styles->registered as $handle => $thing ) {
  73. if ( wp_startswith( $thing->src, self::CDN ) ) {
  74. continue;
  75. }
  76. $src = ltrim( str_replace( $site_url, '', $thing->src ), '/' );
  77. if ( self::is_js_or_css_file( $src ) && in_array( substr( $src, 0, 9 ), array( 'wp-admin/', 'wp-includ' ) ) ) {
  78. $wp_styles->registered[ $handle ]->src = sprintf( self::CDN . 'c/%1$s/%2$s', $version, $src );
  79. $wp_styles->registered[ $handle ]->ver = null;
  80. }
  81. }
  82. }
  83. self::cdnize_plugin_assets( 'jetpack', JETPACK__VERSION );
  84. if ( class_exists( 'WooCommerce' ) ) {
  85. self::cdnize_plugin_assets( 'woocommerce', WC_VERSION );
  86. }
  87. }
  88. /**
  89. * Ensure use of the correct relative path when determining the JavaScript file names.
  90. *
  91. * @param string $relative The relative path of the script. False if it could not be determined.
  92. * @param string $src The full source url of the script.
  93. * @return string The expected relative path for the CDN-ed URL.
  94. */
  95. public static function fix_script_relative_path( $relative, $src ) {
  96. // Note relevant in AMP responses. See note above.
  97. if ( Jetpack_AMP_Support::is_amp_request() ) {
  98. return $relative;
  99. }
  100. $strpos = strpos( $src, '/wp-includes/' );
  101. // We only treat URLs that have wp-includes in them. Cases like language textdomains
  102. // can also use this filter, they don't need to be touched because they are local paths.
  103. if ( false !== $strpos ) {
  104. return substr( $src, 1 + $strpos );
  105. }
  106. // Get the local path from a URL which was CDN'ed by cdnize_plugin_assets().
  107. if ( preg_match( '#^' . preg_quote( self::CDN, '#' ) . 'p/[^/]+/[^/]+/(.*)$#', $src, $m ) ) {
  108. return $m[1];
  109. }
  110. return $relative;
  111. }
  112. /**
  113. * Ensure use of the correct local path when loading the JavaScript translation file for a CDN'ed asset.
  114. *
  115. * @param string|false $file Path to the translation file to load. False if there isn't one.
  116. * @param string $handle The script handle.
  117. * @param string $domain The text domain.
  118. *
  119. * @return string The transformed local languages path.
  120. */
  121. public static function fix_local_script_translation_path( $file, $handle, $domain ) {
  122. global $wp_scripts;
  123. // This is a rewritten plugin URL, so load the language file from the plugins path.
  124. if ( $file && isset( $wp_scripts->registered[ $handle ] ) && wp_startswith( $wp_scripts->registered[ $handle ]->src, self::CDN . 'p' ) ) {
  125. return WP_LANG_DIR . '/plugins/' . basename( $file );
  126. }
  127. return $file;
  128. }
  129. /**
  130. * Sets up CDN URLs for supported plugin assets.
  131. *
  132. * @param String $plugin_slug plugin slug string.
  133. * @param String $current_version plugin version string.
  134. * @return null|bool
  135. */
  136. public static function cdnize_plugin_assets( $plugin_slug, $current_version ) {
  137. global $wp_scripts, $wp_styles;
  138. /**
  139. * Filters Jetpack CDN's plugin slug and version number. Can be used to override the values
  140. * that Jetpack uses to retrieve assets. For example, when testing a development version of Jetpack
  141. * the assets are not yet published, so you may need to override the version value to either
  142. * trunk, or the latest available version. Expects the values to be returned in an array.
  143. *
  144. * @module photon-cdn
  145. *
  146. * @since 6.6.0
  147. *
  148. * @param array $values array( $slug = the plugin repository slug, i.e. jetpack, $version = the plugin version, i.e. 6.6 )
  149. */
  150. list( $plugin_slug, $current_version ) = apply_filters(
  151. 'jetpack_cdn_plugin_slug_and_version',
  152. array( $plugin_slug, $current_version )
  153. );
  154. $assets = self::get_plugin_assets( $plugin_slug, $current_version );
  155. $plugin_directory_url = plugins_url() . '/' . $plugin_slug . '/';
  156. if ( is_wp_error( $assets ) || ! is_array( $assets ) ) {
  157. return false;
  158. }
  159. foreach ( $wp_scripts->registered as $handle => $thing ) {
  160. if ( wp_startswith( $thing->src, self::CDN ) ) {
  161. continue;
  162. }
  163. if ( wp_startswith( $thing->src, $plugin_directory_url ) ) {
  164. $local_path = substr( $thing->src, strlen( $plugin_directory_url ) );
  165. if ( in_array( $local_path, $assets, true ) ) {
  166. $wp_scripts->registered[ $handle ]->src = sprintf( self::CDN . 'p/%1$s/%2$s/%3$s', $plugin_slug, $current_version, $local_path );
  167. $wp_scripts->registered[ $handle ]->ver = null;
  168. }
  169. }
  170. }
  171. foreach ( $wp_styles->registered as $handle => $thing ) {
  172. if ( wp_startswith( $thing->src, self::CDN ) ) {
  173. continue;
  174. }
  175. if ( wp_startswith( $thing->src, $plugin_directory_url ) ) {
  176. $local_path = substr( $thing->src, strlen( $plugin_directory_url ) );
  177. if ( in_array( $local_path, $assets, true ) ) {
  178. $wp_styles->registered[ $handle ]->src = sprintf( self::CDN . 'p/%1$s/%2$s/%3$s', $plugin_slug, $current_version, $local_path );
  179. $wp_styles->registered[ $handle ]->ver = null;
  180. }
  181. }
  182. }
  183. }
  184. /**
  185. * Returns cdn-able assets for a given plugin.
  186. *
  187. * @param string $plugin plugin slug string.
  188. * @param string $version plugin version number string.
  189. * @return array|bool Will return false if not a public version.
  190. */
  191. public static function get_plugin_assets( $plugin, $version ) {
  192. if ( 'jetpack' === $plugin && JETPACK__VERSION === $version ) {
  193. if ( ! self::is_public_version( $version ) ) {
  194. return false;
  195. }
  196. $assets = array(); // The variable will be redefined in the included file.
  197. include JETPACK__PLUGIN_DIR . 'modules/photon-cdn/jetpack-manifest.php';
  198. return $assets;
  199. }
  200. /**
  201. * Used for other plugins to provide their bundled assets via filter to
  202. * prevent the need of storing them in an option or an external api request
  203. * to w.org.
  204. *
  205. * @module photon-cdn
  206. *
  207. * @since 6.6.0
  208. *
  209. * @param array $assets The assets array for the plugin.
  210. * @param string $version The version of the plugin being requested.
  211. */
  212. $assets = apply_filters( "jetpack_cdn_plugin_assets-{$plugin}", null, $version );
  213. if ( is_array( $assets ) ) {
  214. return $assets;
  215. }
  216. if ( ! self::is_public_version( $version ) ) {
  217. return false;
  218. }
  219. $cache = Jetpack_Options::get_option( 'static_asset_cdn_files', array() );
  220. if ( isset( $cache[ $plugin ][ $version ] ) ) {
  221. if ( is_array( $cache[ $plugin ][ $version ] ) ) {
  222. return $cache[ $plugin ][ $version ];
  223. }
  224. if ( is_numeric( $cache[ $plugin ][ $version ] ) ) {
  225. // Cache an empty result for up to 24h.
  226. if ( (int) $cache[ $plugin ][ $version ] + DAY_IN_SECONDS > time() ) {
  227. return array();
  228. }
  229. }
  230. }
  231. $url = sprintf( 'http://downloads.wordpress.org/plugin-checksums/%s/%s.json', $plugin, $version );
  232. if ( wp_http_supports( array( 'ssl' ) ) ) {
  233. $url = set_url_scheme( $url, 'https' );
  234. }
  235. $response = wp_remote_get( $url );
  236. $body = trim( wp_remote_retrieve_body( $response ) );
  237. $body = json_decode( $body, true );
  238. $return = time();
  239. if ( is_array( $body ) ) {
  240. $return = array_filter( array_keys( $body['files'] ), array( __CLASS__, 'is_js_or_css_file' ) );
  241. }
  242. $cache[ $plugin ] = array();
  243. $cache[ $plugin ][ $version ] = $return;
  244. Jetpack_Options::update_option( 'static_asset_cdn_files', $cache, true );
  245. return $return;
  246. }
  247. /**
  248. * Checks a path whether it is a JS or CSS file.
  249. *
  250. * @param String $path file path.
  251. * @return Boolean whether the file is a JS or CSS.
  252. */
  253. public static function is_js_or_css_file( $path ) {
  254. return ( false === strpos( $path, '?' ) ) && in_array( substr( $path, -3 ), array( 'css', '.js' ), true );
  255. }
  256. /**
  257. * Checks whether the version string indicates a production version.
  258. *
  259. * @param String $version the version string.
  260. * @param Boolean $include_beta_and_rc whether to count beta and RC versions as production.
  261. * @return Boolean
  262. */
  263. public static function is_public_version( $version, $include_beta_and_rc = false ) {
  264. if ( preg_match( '/^\d+(\.\d+)+$/', $version ) ) {
  265. // matches `1` `1.2` `1.2.3`.
  266. return true;
  267. } elseif ( $include_beta_and_rc && preg_match( '/^\d+(\.\d+)+(-(beta|rc|pressable)\d?)$/i', $version ) ) {
  268. // matches `1.2.3` `1.2.3-beta` `1.2.3-pressable` `1.2.3-beta1` `1.2.3-rc` `1.2.3-rc2`.
  269. return true;
  270. }
  271. // unrecognized version.
  272. return false;
  273. }
  274. }
  275. /**
  276. * Allow plugins to short-circuit the Asset CDN, even when the module is on.
  277. *
  278. * @module photon-cdn
  279. *
  280. * @since 6.7.0
  281. *
  282. * @param false bool Should the Asset CDN be blocked? False by default.
  283. */
  284. if ( true !== apply_filters( 'jetpack_force_disable_site_accelerator', false ) ) {
  285. Jetpack_Photon_Static_Assets_CDN::go();
  286. }