Changeset 3086793
- Timestamp:
- 05/15/2024 03:06:12 AM (3 months ago)
- Location:
- nextgenthemes-jsdelivr-this
- Files:
-
- 2 deleted
- 6 edited
- 1 copied
Legend:
- Unmodified
- Added
- Removed
-
nextgenthemes-jsdelivr-this/tags/1.1.0/composer.json
r2148782 r3086793 3 3 "type": "wordpress-plugin", 4 4 "require": { 5 "php": ">=5.6" 6 }, 7 "require-dev": { 8 "php": ">=7.0", 9 "phpunit/phpunit": "4.8.* || 5.7.*", 10 "mobiledetect/mobiledetectlib": "*", 11 "wp-coding-standards/wpcs": "*", 12 "dealerdirect/phpcodesniffer-composer-installer": "*" 5 "php": ">=7.4" 13 6 }, 14 7 "license": "GPL-3.0", -
nextgenthemes-jsdelivr-this/tags/1.1.0/nextgenthemes-jsdelivr-this.php
r2155264 r3086793 5 5 * Plugin URI: https://nextgenthemes.com 6 6 * Description: Makes your site load all WP Core and plugin assets from jsDelivr CDN 7 * Version: 1.0.0 7 * Version: 1.1.0 8 * Requres PHP: 7.4 8 9 * Author: Nicolas Jonas 9 10 * Author URI: https://nextgenthemes.com/donate … … 11 12 * License URI: http://www.gnu.org/licenses/gpl-3.0.html 12 13 */ 13 namespace Nextgenthemes\JSdelivrThis; 14 15 const VERSION = '1.0.0'; 16 17 add_filter( 'script_loader_src', __NAMESPACE__ . '\\filter_script_loader_src', 10, 2 ); 18 add_filter( 'style_loader_src', __NAMESPACE__ . '\\filter_style_loader_src', 10, 2 ); 19 20 function filter_script_loader_src( $src, $handle ) { 21 return maybe_replace_src( 'js', $src, $handle ); 22 }; 23 function filter_style_loader_src( $src, $handle ) { 24 return maybe_replace_src( 'css', $src, $handle ); 25 }; 26 27 function maybe_replace_src( $ext, $src, $handle ) { 28 29 static $ran_already = false; 30 31 // We only run this once per page generation to avoid a bunch of API calls to slow the site down 32 if ( ! $ran_already ) { 33 $src = detect_by_hash( $ext, $src, $handle ); 34 $src = detect_plugin_asset( $ext, $src, $handle ); 35 36 $ran_already = true; 37 } 38 14 namespace Nextgenthemes\jsDelivrThis; 15 16 const VERSION = '1.1.0'; 17 18 add_filter( 'script_loader_src', __NAMESPACE__ . '\filter_script_loader_src', 10, 2 ); 19 add_filter( 'style_loader_src', __NAMESPACE__ . '\filter_style_loader_src', 10, 2 ); 20 21 add_filter( 22 'plugin_action_links_' . plugin_basename( __FILE__ ), 23 function ( array $links ) { 24 25 $links['donate'] = sprintf( 26 '<a href="https://nextgenthemes.com/donate/"><strong style="display: inline;">%s</strong></a>', 27 esc_html__( 'Donate', 'jsdelivr-this' ) 28 ); 29 30 return $links; 31 } 32 ); 33 34 function filter_script_loader_src( string $src, string $handle ): string { 35 return maybe_replace_src( 'script', $src, $handle ); 36 } 37 function filter_style_loader_src( string $src, string $handle ): string { 38 return maybe_replace_src( 'style', $src, $handle ); 39 } 40 41 function maybe_replace_src( string $type, string $src, string $handle ): string { 42 $src = detect_by_hash( $type, $src, $handle ); 43 $src = detect_plugin_asset( $type, $src, $handle ); 39 44 return $src; 40 45 } 41 46 42 function get_plugin_dir_file( $plugin_slug ){47 function get_plugin_dir_file( { 43 48 44 49 $active_plugins = get_option( 'active_plugins' ); … … 50 55 foreach ( $active_plugins as $key => $value ) { 51 56 52 if ( st arts_with( $value, $plugin_slug ) ) {57 if ( starts_with( $value, $plugin_slug ) ) { 53 58 return $value; 54 59 } 55 60 } 56 61 57 return false;58 } 59 60 function detect_plugin_asset( $ext, $src, $handle ){61 62 if ( st arts_with( $src, 'https://cdn.jsdelivr.net' ) ) {62 return ; 63 } 64 65 function detect_plugin_asset( { 66 67 if ( starts_with( $src, 'https://cdn.jsdelivr.net' ) ) { 63 68 return $src; 64 69 } 70 65 71 66 72 preg_match( "#/plugins/(?<plugin_slug>[^/]+)/(?<path>.*\.$ext)#", $src, $matches ); … … 74 80 } 75 81 76 $plugin_ver = get_plugin_version( $plugin_dir_file ); 77 $cdn_file = "https://cdn.jsdelivr.net/wp/{$matches['plugin_slug']}/tags/$plugin_ver/{$matches['path']}"; 78 $transient_name = "jsdelivr_this_{$cdn_file}_exists"; 79 $file_exists = get_transient( $transient_name ); 80 81 if ( false === $file_exists ) { 82 83 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged 84 $file_headers = @get_headers( $cdn_file ); 85 86 if ( 'HTTP/1.1 404 Not Found' === $file_headers[0] ) { 87 $file_exists = 'no'; 88 } else { 89 $file_exists = 'yes'; 82 static $ran_already = false; 83 $plugin_ver = get_plugin_version( $plugin_dir_file ); 84 $cdn_file = "https://cdn.jsdelivr.net/wp/{$matches['plugin_slug']}/tags/$plugin_ver/{$matches['path']}"; 85 $transient_name = 'ngt_jsdelivr_this_' . $cdn_file; 86 $data = get_transient( $transient_name ); 87 88 if ( false === $data && ! $ran_already ) { 89 90 $opts['http']['timeout'] = 2; 91 92 $ran_already = true; 93 $data = new \stdClass(); 94 $file_headers = ngt_headers( $cdn_file ); 95 96 if ( ! empty( $file_headers[0] ) && 'HTTP/1.1 200 OK' === $file_headers[0] ) { 97 $data->file_exists = true; 98 $path = path_from_url( $src ); 99 100 if ( $path ) { 101 $data->integrity = gen_integrity( file_get_contents( $path ) ); 102 } 90 103 } 91 104 92 105 // Random time between 24 and 48h to avoid calls getting made every pageload (if only one lonely visitor) 93 set_transient( $transient_name, $ file_exists, wp_rand( DAY_IN_SECONDS, DAY_IN_SECONDS * 2 ) );94 } 95 96 if ( 'yes' === $file_exists) {106 set_transient( $transient_name, $, wp_rand( DAY_IN_SECONDS, DAY_IN_SECONDS * 2 ) ); 107 } 108 109 if ( ) { 97 110 $src = $cdn_file; 111 98 112 } 99 113 … … 101 115 } 102 116 103 function detect_by_hash( $ext, $src, $handle ) { 104 105 if ( starts_with( $src, 'https://cdn.jsdelivr.net' ) ) { 117 /** 118 * Retrieves headers for the given URL. 119 * 120 * @param string $url The URL for which to retrieve headers. 121 * @return array|false Returns an array of headers on success or FALSE on failure. 122 */ 123 function ngt_headers( string $url ) { 124 125 $opts['http']['timeout'] = 2; 126 127 $context = stream_context_create( $opts ); 128 return @get_headers( $url, 0, $context ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged 129 } 130 131 /** 132 * Adds integrity and crossorigin attributes to assets based on type. 133 * 134 * @param string $type The type of the asset ('script' or 'style'). 135 * @param string $handle The handle of the asset. 136 * @param string $integrity The integrity value to be added. 137 */ 138 function add_integrity_to_asset( string $type, string $handle, string $integrity ): void { 139 140 if ( 'script' === $type ) { 141 add_filter( 142 'wp_script_attributes', 143 function ( array $attr ) use ( $handle, $integrity ) { 144 145 if ( ! empty( $attr['src'] ) && 146 ! empty( $attr['id'] ) && 147 $attr['id'] === $handle . '-js' 148 ) { 149 $attr['integrity'] = $integrity; 150 $attr['crossorigin'] = 'anonymous'; 151 } 152 153 return $attr; 154 } 155 ); 156 } else { 157 add_filter( 158 'style_loader_tag', 159 function ( $html, $fn_handle ) use ( $handle, $integrity ) { 160 161 if ( $fn_handle === $handle ) { 162 163 $p = new \WP_HTML_Tag_Processor( $html ); 164 165 if ( $p->next_tag( 'link' ) && $p->get_attribute( 'href' ) ) { 166 167 $p->set_attribute( 'integrity', $integrity ); 168 $p->set_attribute( 'crossorigin', 'anonymous' ); 169 $html = $p->get_updated_html(); 170 } 171 } 172 173 return $html; 174 }, 175 10, 176 2 177 ); 178 } 179 } 180 181 function get_jsdelivr_hash_api_data( string $file_path, string $handle, string $src ): ?object { 182 183 static $ran_already = false; 184 $transient_name = "ngt_jsdelivr_this_{$handle}_{$src}_wp{$GLOBALS['wp_version']}"; 185 $result = get_transient( $transient_name ); 186 187 if ( false === $result && ! $ran_already ) { 188 189 $ran_already = true; 190 $result = new \stdClass(); 191 $file_content = file_get_contents( $file_path ); 192 193 if ( $file_content ) { 194 $sha256 = hash( 'sha256', $file_content ); 195 $data = wp_safe_remote_get( 196 "https://data.jsdelivr.com/v1/lookup/hash/$sha256", 197 array( 198 'user-agent' => 'https://nextgenthemes.com/plugins/jsdelivr-this', 199 'timeout' => 2, 200 ) 201 ); 202 203 if ( ! is_wp_error( $data ) ) { 204 $result = (object) json_decode( wp_remote_retrieve_body( $data ) ); 205 $result->integrity = gen_integrity( $file_content ); 206 } 207 } 208 209 // Random time between 24 and 48h to avoid calls getting made every pageload (if only one lonely visitor) 210 set_transient( $transient_name, $result, wp_rand( DAY_IN_SECONDS, DAY_IN_SECONDS * 2 ) ); 211 } 212 213 if ( false === $result ) { 214 $result = null; 215 } 216 217 return $result; 218 } 219 220 function detect_by_hash( string $type, string $src, string $handle ): string { 221 222 if ( str_starts_with( $src, 'https://cdn.jsdelivr.net' ) ) { 106 223 return $src; 107 224 } 108 225 109 $parsed_url = wp_parse_url( $src ); 226 $path = path_from_url( $src ); 227 228 if ( $path ) { 229 $data = get_jsdelivr_hash_api_data( $path, $handle, $src ); 230 } 231 232 $ver = get_url_arg( $src, 'ver' ); 233 $wp_gh_asset = ( ! empty( $data->type ) && 'gh' === $data->type && 'WordPress/WordPress' === $data->name ); 234 $ver_not_wp = ( ! empty( $data->type ) && $ver && $GLOBALS['wp_version'] !== $ver ); 235 236 if ( $wp_gh_asset || $ver_not_wp ) { 237 $src = sprintf( 238 'https://cdn.jsdelivr.net/%s/%s@%s', 239 $data->type, 240 $data->name, 241 $data->version . $data->file 242 ); 243 add_integrity_to_asset( $type, $handle, $data->integrity ); 244 } 245 246 return $src; 247 } 248 249 /** 250 * Retrieves the value of a specific query argument from the given URL. 251 * 252 * @param string $url The URL containing the query parameters. 253 * @param string $arg The name of the query argument to retrieve. 254 * @return string|null The value of the specified query argument, or null if it is not found. 255 */ 256 function get_url_arg( string $url, string $arg ): ?string { 257 258 $query_string = parse_url( $url, PHP_URL_QUERY ); 259 260 if ( empty( $query_string ) || ! is_string( $query_string ) ) { 261 return null; 262 } 263 264 parse_str( $query_string, $query_args ); 265 266 return $query_args[ $arg ] ?? null; 267 } 268 269 function gen_integrity( string $input ): string { 270 $hash = hash( 'sha384', $input, true ); 271 $hash_base64 = base64_encode( $hash ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode 272 return "sha384-$hash_base64"; 273 } 274 275 function path_from_url( string $url ): ?string { 276 $parsed_url = wp_parse_url( $url ); 110 277 $file = rtrim( ABSPATH, '/' ) . $parsed_url['path']; 111 278 $file_alt = rtrim( dirname( ABSPATH ), '/' ) . $parsed_url['path']; 112 279 113 280 if ( is_file( $file ) ) { 114 $data = get_jsdeliver_hash_api_data( $file ); 115 }; 116 if ( is_file( $file_alt ) ) { 117 $data = get_jsdeliver_hash_api_data( $file_alt ); 118 }; 119 120 if ( isset( $data['type'] ) && 'gh' === $data['type'] ) { 121 $src = "https://cdn.jsdelivr.net/{$data['type']}/{$data['name']}@{$data['version']}{$data['file']}"; 122 } 123 124 return $src; 125 } 126 127 function get_jsdeliver_hash_api_data( $file_path ) { 128 129 $transient_name = "jsdelivr_this_hashapi_wp{$GLOBALS['wp_version']}_$file_path"; 130 $result = get_transient( $transient_name ); 131 132 if ( false === $result ) { 133 134 // Local file, no need for wp_remote_get 135 // phpcs:disable WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents 136 $result = array(); 137 $file_content = file_get_contents( $file_path ); 138 // phpcs:enable WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents 139 140 if ( $file_content ) { 141 $sha256 = hash( 'sha256', $file_content ); 142 $data = wp_safe_remote_get( "https://data.jsdelivr.com/v1/lookup/hash/$sha256", array() ); 143 144 if ( ! is_wp_error( $data ) ) { 145 $result = (array) json_decode( wp_remote_retrieve_body( $data ), true ); 146 } 147 } 148 149 set_transient( $transient_name, $result, wp_rand( DAY_IN_SECONDS, DAY_IN_SECONDS * 2 ) ); 150 } 151 152 return $result; 153 } 154 155 function contains( $haystack, $needle ) { 156 return strpos( $haystack, $needle ) !== false; 157 } 158 159 function starts_with( $haystack, $needle ) { 160 return $haystack[0] === $needle[0] ? strncmp( $haystack, $needle, strlen( $needle ) ) === 0 : false; 161 } 162 163 function get_plugin_version( $plugin_file ) { 164 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . "/$plugin_file", false, false ); 281 return $file; 282 } elseif ( is_file( $file_alt ) ) { 283 return $file_alt; 284 } 285 286 return null; 287 } 288 289 function get_plugin_version( string $plugin_file ): string { 290 $plugin_data = get_file_data( WP_PLUGIN_DIR . "/$plugin_file", array( 'Version' => 'Version' ), 'plugin' ); 165 291 return $plugin_data['Version']; 166 292 } 167 168 /**169 * Parses the plugin contents to retrieve plugin's metadata.170 *171 * The metadata of the plugin's data searches for the following in the plugin's172 * header. All plugin data must be on its own line. For plugin description, it173 * must not have any newlines or only parts of the description will be displayed174 * and the same goes for the plugin data. The below is formatted for printing.175 *176 * /*177 * Plugin Name: Name of Plugin178 * Plugin URI: Link to plugin information179 * Description: Plugin Description180 * Author: Plugin author's name181 * Author URI: Link to the author's web site182 * Version: Must be set in the plugin for WordPress 2.3+183 * Text Domain: Optional. Unique identifier, should be same as the one used in184 * load_plugin_textdomain()185 * Domain Path: Optional. Only useful if the translations are located in a186 * folder above the plugin's base path. For example, if .mo files are187 * located in the locale folder then Domain Path will be "/locale/" and188 * must have the first slash. Defaults to the base folder the plugin is189 * located in.190 * Network: Optional. Specify "Network: true" to require that a plugin is activated191 * across all sites in an installation. This will prevent a plugin from being192 * activated on a single site when Multisite is enabled.193 * * / # Remove the space to close comment194 *195 * Some users have issues with opening large files and manipulating the contents196 * for want is usually the first 1kiB or 2kiB. This function stops pulling in197 * the plugin contents when it has all of the required plugin data.198 *199 * The first 8kiB of the file will be pulled in and if the plugin data is not200 * within that first 8kiB, then the plugin author should correct their plugin201 * and move the plugin data headers to the top.202 *203 * The plugin file is assumed to have permissions to allow for scripts to read204 * the file. This is not checked however and the file is only opened for205 * reading.206 *207 * @since 1.5.0208 *209 * @param string $plugin_file Path to the main plugin file.210 * @param bool $markup Optional. If the returned data should have HTML markup applied.211 * Default true.212 * @param bool $translate Optional. If the returned data should be translated. Default true.213 * @return array {214 * Plugin data. Values will be empty if not supplied by the plugin.215 *216 * @type string $Name Name of the plugin. Should be unique.217 * @type string $Title Title of the plugin and link to the plugin's site (if set).218 * @type string $Description Plugin description.219 * @type string $Author Author's name.220 * @type string $AuthorURI Author's website address (if set).221 * @type string $Version Plugin version.222 * @type string $TextDomain Plugin textdomain.223 * @type string $DomainPath Plugins relative directory path to .mo files.224 * @type bool $Network Whether the plugin can only be activated network-wide.225 * }226 */227 function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {228 229 $default_headers = array(230 'Name' => 'Plugin Name',231 'PluginURI' => 'Plugin URI',232 'Version' => 'Version',233 'Description' => 'Description',234 'Author' => 'Author',235 'AuthorURI' => 'Author URI',236 'TextDomain' => 'Text Domain',237 'DomainPath' => 'Domain Path',238 'Network' => 'Network',239 // Site Wide Only is deprecated in favor of Network.240 '_sitewide' => 'Site Wide Only',241 );242 243 $plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' );244 245 // Site Wide Only is the old header for Network246 if ( ! $plugin_data['Network'] && $plugin_data['_sitewide'] ) {247 // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped248 // phpcs:disable WordPress.WP.I18n.MissingArgDomain249 /* translators: 1: Site Wide Only: true, 2: Network: true */250 _deprecated_argument( __FUNCTION__, '3.0.0', sprintf( __( 'The %1$s plugin header is deprecated. Use %2$s instead.' ), '<code>Site Wide Only: true</code>', '<code>Network: true</code>' ) );251 // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped252 // phpcs:enable WordPress.WP.I18n.MissingArgDomain253 $plugin_data['Network'] = $plugin_data['_sitewide'];254 }255 // phpcs:disable WordPress.PHP.StrictComparisons.LooseComparison256 $plugin_data['Network'] = ( 'true' == strtolower( $plugin_data['Network'] ) );257 // phpcs:enable WordPress.PHP.StrictComparisons.LooseComparison258 unset( $plugin_data['_sitewide'] );259 260 // If no text domain is defined fall back to the plugin slug.261 if ( ! $plugin_data['TextDomain'] ) {262 $plugin_slug = dirname( plugin_basename( $plugin_file ) );263 if ( '.' !== $plugin_slug && false === strpos( $plugin_slug, '/' ) ) {264 $plugin_data['TextDomain'] = $plugin_slug;265 }266 }267 268 if ( $markup || $translate ) {269 $plugin_data = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup, $translate );270 } else {271 $plugin_data['Title'] = $plugin_data['Name'];272 $plugin_data['AuthorName'] = $plugin_data['Author'];273 }274 275 return $plugin_data;276 } -
nextgenthemes-jsdelivr-this/tags/1.1.0/readme.txt
r2155264 r3086793 1 1 === NGT jsDelivr CDN === 2 2 Contributors: nico23 3 Tags: CDN, JS, JavaScript, j Query, Performance, minimalistic, minimal3 Tags: CDN, JS, JavaScript, j 4 4 Donate link: https://nextgenthemes.com/donate 5 Requires at least: 4.3.16 Requires PHP: 5.67 Tested up to: 5.2.28 Stable tag: 1. 0.05 Requires at least: 6 Requires PHP: 7 Tested up to: 8 Stable tag: 1..0 9 9 License: GPL 3.0 10 10 License URI: http://www.gnu.org/licenses/gpl-3.0.html … … 14 14 == Changelog == 15 15 16 17 18 16 19 = 2019-09-07 1.0.0 = 17 * Run only once per page-load, better function names and some useful comments. 20 * Run only once per page-load. 21 * Better function names and some useful comments. 22 * Send this plugins url as user-agent to jsDelivr knows how its used. (They asked for this). This also means more privacy as the `wp_remote_get` referrer sends your site URL (I really do not like that) 18 23 19 24 = 2019-08-31 0.9.4 = -
nextgenthemes-jsdelivr-this/trunk/composer.json
r2148782 r3086793 3 3 "type": "wordpress-plugin", 4 4 "require": { 5 "php": ">=5.6" 6 }, 7 "require-dev": { 8 "php": ">=7.0", 9 "phpunit/phpunit": "4.8.* || 5.7.*", 10 "mobiledetect/mobiledetectlib": "*", 11 "wp-coding-standards/wpcs": "*", 12 "dealerdirect/phpcodesniffer-composer-installer": "*" 5 "php": ">=7.4" 13 6 }, 14 7 "license": "GPL-3.0", -
nextgenthemes-jsdelivr-this/trunk/nextgenthemes-jsdelivr-this.php
r2155264 r3086793 5 5 * Plugin URI: https://nextgenthemes.com 6 6 * Description: Makes your site load all WP Core and plugin assets from jsDelivr CDN 7 * Version: 1.0.0 7 * Version: 1.1.0 8 * Requres PHP: 7.4 8 9 * Author: Nicolas Jonas 9 10 * Author URI: https://nextgenthemes.com/donate … … 11 12 * License URI: http://www.gnu.org/licenses/gpl-3.0.html 12 13 */ 13 namespace Nextgenthemes\JSdelivrThis; 14 15 const VERSION = '1.0.0'; 16 17 add_filter( 'script_loader_src', __NAMESPACE__ . '\\filter_script_loader_src', 10, 2 ); 18 add_filter( 'style_loader_src', __NAMESPACE__ . '\\filter_style_loader_src', 10, 2 ); 19 20 function filter_script_loader_src( $src, $handle ) { 21 return maybe_replace_src( 'js', $src, $handle ); 22 }; 23 function filter_style_loader_src( $src, $handle ) { 24 return maybe_replace_src( 'css', $src, $handle ); 25 }; 26 27 function maybe_replace_src( $ext, $src, $handle ) { 28 29 static $ran_already = false; 30 31 // We only run this once per page generation to avoid a bunch of API calls to slow the site down 32 if ( ! $ran_already ) { 33 $src = detect_by_hash( $ext, $src, $handle ); 34 $src = detect_plugin_asset( $ext, $src, $handle ); 35 36 $ran_already = true; 37 } 38 14 namespace Nextgenthemes\jsDelivrThis; 15 16 const VERSION = '1.1.0'; 17 18 add_filter( 'script_loader_src', __NAMESPACE__ . '\filter_script_loader_src', 10, 2 ); 19 add_filter( 'style_loader_src', __NAMESPACE__ . '\filter_style_loader_src', 10, 2 ); 20 21 add_filter( 22 'plugin_action_links_' . plugin_basename( __FILE__ ), 23 function ( array $links ) { 24 25 $links['donate'] = sprintf( 26 '<a href="https://nextgenthemes.com/donate/"><strong style="display: inline;">%s</strong></a>', 27 esc_html__( 'Donate', 'jsdelivr-this' ) 28 ); 29 30 return $links; 31 } 32 ); 33 34 function filter_script_loader_src( string $src, string $handle ): string { 35 return maybe_replace_src( 'script', $src, $handle ); 36 } 37 function filter_style_loader_src( string $src, string $handle ): string { 38 return maybe_replace_src( 'style', $src, $handle ); 39 } 40 41 function maybe_replace_src( string $type, string $src, string $handle ): string { 42 $src = detect_by_hash( $type, $src, $handle ); 43 $src = detect_plugin_asset( $type, $src, $handle ); 39 44 return $src; 40 45 } 41 46 42 function get_plugin_dir_file( $plugin_slug ){47 function get_plugin_dir_file( { 43 48 44 49 $active_plugins = get_option( 'active_plugins' ); … … 50 55 foreach ( $active_plugins as $key => $value ) { 51 56 52 if ( st arts_with( $value, $plugin_slug ) ) {57 if ( starts_with( $value, $plugin_slug ) ) { 53 58 return $value; 54 59 } 55 60 } 56 61 57 return false;58 } 59 60 function detect_plugin_asset( $ext, $src, $handle ){61 62 if ( st arts_with( $src, 'https://cdn.jsdelivr.net' ) ) {62 return ; 63 } 64 65 function detect_plugin_asset( { 66 67 if ( starts_with( $src, 'https://cdn.jsdelivr.net' ) ) { 63 68 return $src; 64 69 } 70 65 71 66 72 preg_match( "#/plugins/(?<plugin_slug>[^/]+)/(?<path>.*\.$ext)#", $src, $matches ); … … 74 80 } 75 81 76 $plugin_ver = get_plugin_version( $plugin_dir_file ); 77 $cdn_file = "https://cdn.jsdelivr.net/wp/{$matches['plugin_slug']}/tags/$plugin_ver/{$matches['path']}"; 78 $transient_name = "jsdelivr_this_{$cdn_file}_exists"; 79 $file_exists = get_transient( $transient_name ); 80 81 if ( false === $file_exists ) { 82 83 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged 84 $file_headers = @get_headers( $cdn_file ); 85 86 if ( 'HTTP/1.1 404 Not Found' === $file_headers[0] ) { 87 $file_exists = 'no'; 88 } else { 89 $file_exists = 'yes'; 82 static $ran_already = false; 83 $plugin_ver = get_plugin_version( $plugin_dir_file ); 84 $cdn_file = "https://cdn.jsdelivr.net/wp/{$matches['plugin_slug']}/tags/$plugin_ver/{$matches['path']}"; 85 $transient_name = 'ngt_jsdelivr_this_' . $cdn_file; 86 $data = get_transient( $transient_name ); 87 88 if ( false === $data && ! $ran_already ) { 89 90 $opts['http']['timeout'] = 2; 91 92 $ran_already = true; 93 $data = new \stdClass(); 94 $file_headers = ngt_headers( $cdn_file ); 95 96 if ( ! empty( $file_headers[0] ) && 'HTTP/1.1 200 OK' === $file_headers[0] ) { 97 $data->file_exists = true; 98 $path = path_from_url( $src ); 99 100 if ( $path ) { 101 $data->integrity = gen_integrity( file_get_contents( $path ) ); 102 } 90 103 } 91 104 92 105 // Random time between 24 and 48h to avoid calls getting made every pageload (if only one lonely visitor) 93 set_transient( $transient_name, $ file_exists, wp_rand( DAY_IN_SECONDS, DAY_IN_SECONDS * 2 ) );94 } 95 96 if ( 'yes' === $file_exists) {106 set_transient( $transient_name, $, wp_rand( DAY_IN_SECONDS, DAY_IN_SECONDS * 2 ) ); 107 } 108 109 if ( ) { 97 110 $src = $cdn_file; 111 98 112 } 99 113 … … 101 115 } 102 116 103 function detect_by_hash( $ext, $src, $handle ) { 104 105 if ( starts_with( $src, 'https://cdn.jsdelivr.net' ) ) { 117 /** 118 * Retrieves headers for the given URL. 119 * 120 * @param string $url The URL for which to retrieve headers. 121 * @return array|false Returns an array of headers on success or FALSE on failure. 122 */ 123 function ngt_headers( string $url ) { 124 125 $opts['http']['timeout'] = 2; 126 127 $context = stream_context_create( $opts ); 128 return @get_headers( $url, 0, $context ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged 129 } 130 131 /** 132 * Adds integrity and crossorigin attributes to assets based on type. 133 * 134 * @param string $type The type of the asset ('script' or 'style'). 135 * @param string $handle The handle of the asset. 136 * @param string $integrity The integrity value to be added. 137 */ 138 function add_integrity_to_asset( string $type, string $handle, string $integrity ): void { 139 140 if ( 'script' === $type ) { 141 add_filter( 142 'wp_script_attributes', 143 function ( array $attr ) use ( $handle, $integrity ) { 144 145 if ( ! empty( $attr['src'] ) && 146 ! empty( $attr['id'] ) && 147 $attr['id'] === $handle . '-js' 148 ) { 149 $attr['integrity'] = $integrity; 150 $attr['crossorigin'] = 'anonymous'; 151 } 152 153 return $attr; 154 } 155 ); 156 } else { 157 add_filter( 158 'style_loader_tag', 159 function ( $html, $fn_handle ) use ( $handle, $integrity ) { 160 161 if ( $fn_handle === $handle ) { 162 163 $p = new \WP_HTML_Tag_Processor( $html ); 164 165 if ( $p->next_tag( 'link' ) && $p->get_attribute( 'href' ) ) { 166 167 $p->set_attribute( 'integrity', $integrity ); 168 $p->set_attribute( 'crossorigin', 'anonymous' ); 169 $html = $p->get_updated_html(); 170 } 171 } 172 173 return $html; 174 }, 175 10, 176 2 177 ); 178 } 179 } 180 181 function get_jsdelivr_hash_api_data( string $file_path, string $handle, string $src ): ?object { 182 183 static $ran_already = false; 184 $transient_name = "ngt_jsdelivr_this_{$handle}_{$src}_wp{$GLOBALS['wp_version']}"; 185 $result = get_transient( $transient_name ); 186 187 if ( false === $result && ! $ran_already ) { 188 189 $ran_already = true; 190 $result = new \stdClass(); 191 $file_content = file_get_contents( $file_path ); 192 193 if ( $file_content ) { 194 $sha256 = hash( 'sha256', $file_content ); 195 $data = wp_safe_remote_get( 196 "https://data.jsdelivr.com/v1/lookup/hash/$sha256", 197 array( 198 'user-agent' => 'https://nextgenthemes.com/plugins/jsdelivr-this', 199 'timeout' => 2, 200 ) 201 ); 202 203 if ( ! is_wp_error( $data ) ) { 204 $result = (object) json_decode( wp_remote_retrieve_body( $data ) ); 205 $result->integrity = gen_integrity( $file_content ); 206 } 207 } 208 209 // Random time between 24 and 48h to avoid calls getting made every pageload (if only one lonely visitor) 210 set_transient( $transient_name, $result, wp_rand( DAY_IN_SECONDS, DAY_IN_SECONDS * 2 ) ); 211 } 212 213 if ( false === $result ) { 214 $result = null; 215 } 216 217 return $result; 218 } 219 220 function detect_by_hash( string $type, string $src, string $handle ): string { 221 222 if ( str_starts_with( $src, 'https://cdn.jsdelivr.net' ) ) { 106 223 return $src; 107 224 } 108 225 109 $parsed_url = wp_parse_url( $src ); 226 $path = path_from_url( $src ); 227 228 if ( $path ) { 229 $data = get_jsdelivr_hash_api_data( $path, $handle, $src ); 230 } 231 232 $ver = get_url_arg( $src, 'ver' ); 233 $wp_gh_asset = ( ! empty( $data->type ) && 'gh' === $data->type && 'WordPress/WordPress' === $data->name ); 234 $ver_not_wp = ( ! empty( $data->type ) && $ver && $GLOBALS['wp_version'] !== $ver ); 235 236 if ( $wp_gh_asset || $ver_not_wp ) { 237 $src = sprintf( 238 'https://cdn.jsdelivr.net/%s/%s@%s', 239 $data->type, 240 $data->name, 241 $data->version . $data->file 242 ); 243 add_integrity_to_asset( $type, $handle, $data->integrity ); 244 } 245 246 return $src; 247 } 248 249 /** 250 * Retrieves the value of a specific query argument from the given URL. 251 * 252 * @param string $url The URL containing the query parameters. 253 * @param string $arg The name of the query argument to retrieve. 254 * @return string|null The value of the specified query argument, or null if it is not found. 255 */ 256 function get_url_arg( string $url, string $arg ): ?string { 257 258 $query_string = parse_url( $url, PHP_URL_QUERY ); 259 260 if ( empty( $query_string ) || ! is_string( $query_string ) ) { 261 return null; 262 } 263 264 parse_str( $query_string, $query_args ); 265 266 return $query_args[ $arg ] ?? null; 267 } 268 269 function gen_integrity( string $input ): string { 270 $hash = hash( 'sha384', $input, true ); 271 $hash_base64 = base64_encode( $hash ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode 272 return "sha384-$hash_base64"; 273 } 274 275 function path_from_url( string $url ): ?string { 276 $parsed_url = wp_parse_url( $url ); 110 277 $file = rtrim( ABSPATH, '/' ) . $parsed_url['path']; 111 278 $file_alt = rtrim( dirname( ABSPATH ), '/' ) . $parsed_url['path']; 112 279 113 280 if ( is_file( $file ) ) { 114 $data = get_jsdeliver_hash_api_data( $file ); 115 }; 116 if ( is_file( $file_alt ) ) { 117 $data = get_jsdeliver_hash_api_data( $file_alt ); 118 }; 119 120 if ( isset( $data['type'] ) && 'gh' === $data['type'] ) { 121 $src = "https://cdn.jsdelivr.net/{$data['type']}/{$data['name']}@{$data['version']}{$data['file']}"; 122 } 123 124 return $src; 125 } 126 127 function get_jsdeliver_hash_api_data( $file_path ) { 128 129 $transient_name = "jsdelivr_this_hashapi_wp{$GLOBALS['wp_version']}_$file_path"; 130 $result = get_transient( $transient_name ); 131 132 if ( false === $result ) { 133 134 // Local file, no need for wp_remote_get 135 // phpcs:disable WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents 136 $result = array(); 137 $file_content = file_get_contents( $file_path ); 138 // phpcs:enable WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents 139 140 if ( $file_content ) { 141 $sha256 = hash( 'sha256', $file_content ); 142 $data = wp_safe_remote_get( "https://data.jsdelivr.com/v1/lookup/hash/$sha256", array() ); 143 144 if ( ! is_wp_error( $data ) ) { 145 $result = (array) json_decode( wp_remote_retrieve_body( $data ), true ); 146 } 147 } 148 149 set_transient( $transient_name, $result, wp_rand( DAY_IN_SECONDS, DAY_IN_SECONDS * 2 ) ); 150 } 151 152 return $result; 153 } 154 155 function contains( $haystack, $needle ) { 156 return strpos( $haystack, $needle ) !== false; 157 } 158 159 function starts_with( $haystack, $needle ) { 160 return $haystack[0] === $needle[0] ? strncmp( $haystack, $needle, strlen( $needle ) ) === 0 : false; 161 } 162 163 function get_plugin_version( $plugin_file ) { 164 $plugin_data = get_plugin_data( WP_PLUGIN_DIR . "/$plugin_file", false, false ); 281 return $file; 282 } elseif ( is_file( $file_alt ) ) { 283 return $file_alt; 284 } 285 286 return null; 287 } 288 289 function get_plugin_version( string $plugin_file ): string { 290 $plugin_data = get_file_data( WP_PLUGIN_DIR . "/$plugin_file", array( 'Version' => 'Version' ), 'plugin' ); 165 291 return $plugin_data['Version']; 166 292 } 167 168 /**169 * Parses the plugin contents to retrieve plugin's metadata.170 *171 * The metadata of the plugin's data searches for the following in the plugin's172 * header. All plugin data must be on its own line. For plugin description, it173 * must not have any newlines or only parts of the description will be displayed174 * and the same goes for the plugin data. The below is formatted for printing.175 *176 * /*177 * Plugin Name: Name of Plugin178 * Plugin URI: Link to plugin information179 * Description: Plugin Description180 * Author: Plugin author's name181 * Author URI: Link to the author's web site182 * Version: Must be set in the plugin for WordPress 2.3+183 * Text Domain: Optional. Unique identifier, should be same as the one used in184 * load_plugin_textdomain()185 * Domain Path: Optional. Only useful if the translations are located in a186 * folder above the plugin's base path. For example, if .mo files are187 * located in the locale folder then Domain Path will be "/locale/" and188 * must have the first slash. Defaults to the base folder the plugin is189 * located in.190 * Network: Optional. Specify "Network: true" to require that a plugin is activated191 * across all sites in an installation. This will prevent a plugin from being192 * activated on a single site when Multisite is enabled.193 * * / # Remove the space to close comment194 *195 * Some users have issues with opening large files and manipulating the contents196 * for want is usually the first 1kiB or 2kiB. This function stops pulling in197 * the plugin contents when it has all of the required plugin data.198 *199 * The first 8kiB of the file will be pulled in and if the plugin data is not200 * within that first 8kiB, then the plugin author should correct their plugin201 * and move the plugin data headers to the top.202 *203 * The plugin file is assumed to have permissions to allow for scripts to read204 * the file. This is not checked however and the file is only opened for205 * reading.206 *207 * @since 1.5.0208 *209 * @param string $plugin_file Path to the main plugin file.210 * @param bool $markup Optional. If the returned data should have HTML markup applied.211 * Default true.212 * @param bool $translate Optional. If the returned data should be translated. Default true.213 * @return array {214 * Plugin data. Values will be empty if not supplied by the plugin.215 *216 * @type string $Name Name of the plugin. Should be unique.217 * @type string $Title Title of the plugin and link to the plugin's site (if set).218 * @type string $Description Plugin description.219 * @type string $Author Author's name.220 * @type string $AuthorURI Author's website address (if set).221 * @type string $Version Plugin version.222 * @type string $TextDomain Plugin textdomain.223 * @type string $DomainPath Plugins relative directory path to .mo files.224 * @type bool $Network Whether the plugin can only be activated network-wide.225 * }226 */227 function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {228 229 $default_headers = array(230 'Name' => 'Plugin Name',231 'PluginURI' => 'Plugin URI',232 'Version' => 'Version',233 'Description' => 'Description',234 'Author' => 'Author',235 'AuthorURI' => 'Author URI',236 'TextDomain' => 'Text Domain',237 'DomainPath' => 'Domain Path',238 'Network' => 'Network',239 // Site Wide Only is deprecated in favor of Network.240 '_sitewide' => 'Site Wide Only',241 );242 243 $plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' );244 245 // Site Wide Only is the old header for Network246 if ( ! $plugin_data['Network'] && $plugin_data['_sitewide'] ) {247 // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped248 // phpcs:disable WordPress.WP.I18n.MissingArgDomain249 /* translators: 1: Site Wide Only: true, 2: Network: true */250 _deprecated_argument( __FUNCTION__, '3.0.0', sprintf( __( 'The %1$s plugin header is deprecated. Use %2$s instead.' ), '<code>Site Wide Only: true</code>', '<code>Network: true</code>' ) );251 // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped252 // phpcs:enable WordPress.WP.I18n.MissingArgDomain253 $plugin_data['Network'] = $plugin_data['_sitewide'];254 }255 // phpcs:disable WordPress.PHP.StrictComparisons.LooseComparison256 $plugin_data['Network'] = ( 'true' == strtolower( $plugin_data['Network'] ) );257 // phpcs:enable WordPress.PHP.StrictComparisons.LooseComparison258 unset( $plugin_data['_sitewide'] );259 260 // If no text domain is defined fall back to the plugin slug.261 if ( ! $plugin_data['TextDomain'] ) {262 $plugin_slug = dirname( plugin_basename( $plugin_file ) );263 if ( '.' !== $plugin_slug && false === strpos( $plugin_slug, '/' ) ) {264 $plugin_data['TextDomain'] = $plugin_slug;265 }266 }267 268 if ( $markup || $translate ) {269 $plugin_data = _get_plugin_data_markup_translate( $plugin_file, $plugin_data, $markup, $translate );270 �� } else {271 $plugin_data['Title'] = $plugin_data['Name'];272 $plugin_data['AuthorName'] = $plugin_data['Author'];273 }274 275 return $plugin_data;276 } -
nextgenthemes-jsdelivr-this/trunk/readme.txt
r2155264 r3086793 1 1 === NGT jsDelivr CDN === 2 2 Contributors: nico23 3 Tags: CDN, JS, JavaScript, j Query, Performance, minimalistic, minimal3 Tags: CDN, JS, JavaScript, j 4 4 Donate link: https://nextgenthemes.com/donate 5 Requires at least: 4.3.16 Requires PHP: 5.67 Tested up to: 5.2.28 Stable tag: 1. 0.05 Requires at least: 6 Requires PHP: 7 Tested up to: 8 Stable tag: 1..0 9 9 License: GPL 3.0 10 10 License URI: http://www.gnu.org/licenses/gpl-3.0.html … … 14 14 == Changelog == 15 15 16 17 18 16 19 = 2019-09-07 1.0.0 = 17 * Run only once per page-load, better function names and some useful comments. 20 * Run only once per page-load. 21 * Better function names and some useful comments. 22 * Send this plugins url as user-agent to jsDelivr knows how its used. (They asked for this). This also means more privacy as the `wp_remote_get` referrer sends your site URL (I really do not like that) 18 23 19 24 = 2019-08-31 0.9.4 =
Note: See TracChangeset
for help on using the changeset viewer.