Plugin Directory

Changeset 3203528


Ignore:
Timestamp:
12/06/2024 10:34:20 AM (13 months ago)
Author:
nico23
Message:

Update plugin to version 1.2.1 with NextgenThemes WordPress Plugin Deploy

Location:
nextgenthemes-jsdelivr-this
Files:
2 added
4 edited
1 copied

Legend:

Unmodified
Added
Removed
  • nextgenthemes-jsdelivr-this/tags/1.2.1/nextgenthemes-jsdelivr-this.php

    r3203494 r3203528  
    22/**
    33 * @wordpress-plugin
    4  * Plugin Name:       NGT jsDelivr CDN
     4 * Plugin Name:       Free jsDelivr CDN
    55 * Plugin URI:        https://nextgenthemes.com
    6  * Description:       Makes your site load all WP Core and plugin assets from jsDelivr CDN
    7  * Version:           1.1.1
     6 * Description:       Serves all available assets from free jsDelivr CDN
     7 * Version:           1.2.1
    88 * Requres PHP:       7.4
    99 * Author:            Nicolas Jonas
     
    1212 * License URI:       http://www.gnu.org/licenses/gpl-3.0.html
    1313 */
     14
     15declare(strict_types = 1);
     16
    1417namespace Nextgenthemes\jsDelivrThis;
    1518
    16 const VERSION = '1.1.1';
    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' )
     19const VERSION = '1.2.1';
     20
     21add_action( 'plugins_loaded', __NAMESPACE__ . '\init' );
     22
     23function init(): void {
     24
     25    add_filter( 'wp_script_attributes', __NAMESPACE__ . '\filter_script_attributes', 10, 1 );
     26    add_filter( 'style_loader_tag', __NAMESPACE__ . '\filter_style_loader_tag', 10, 1 );
     27
     28    add_action( 'admin_bar_menu', __NAMESPACE__ . '\add_item_to_admin_bar', 33 );
     29
     30    add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\enqueue_assets' );
     31    add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_assets' );
     32
     33    add_action( 'admin_footer', __NAMESPACE__ . '\admin_bar_html' );
     34    add_action( 'wp_footer', __NAMESPACE__ . '\admin_bar_html' );
     35
     36    add_action(
     37        'init',
     38        function (): void {
     39            wp_register_script_module(
     40                'ngt-jsdelivr-dialog',
     41                plugins_url( 'dialog.js', __FILE__ ),
     42                array(),
     43                VERSION
     44            );
     45        }
     46    );
     47
     48    add_filter(
     49        'plugin_row_meta',
     50        function ( array $links, string $file ): array {
     51
     52            if ( 'nextgenthemes-jsdelivr-this/nextgenthemes-jsdelivr-this.php' !== $file ) {
     53                return $links;
     54            }
     55            $links[] = '<strong>' . arve_links() . '</strong>';
     56
     57            return $links;
     58        },
     59        10,
     60        2
     61    );
     62
     63    add_filter(
     64        'plugin_action_links_' . plugin_basename( __FILE__ ),
     65        function ( array $links ) {
     66
     67            $links['donate'] = sprintf(
     68                '<a href="https://nextgenthemes.com/donate/">%s</a>',
     69                esc_html__( 'Donate', 'nextgenthemes-jsdelivr-this' )
     70            );
     71
     72            return $links;
     73        }
     74    );
     75}
     76
     77function enqueue_assets(): void {
     78
     79    if ( is_admin_bar_showing() ) {
     80        wp_enqueue_script_module( 'ngt-jsdelivr-dialog' );
     81    }
     82}
     83
     84function add_item_to_admin_bar( object $admin_bar ): void {
     85    // Add a new item to the admin bar
     86    $admin_bar->add_node(
     87        array(
     88            'id'     => 'ngt-jsdelivr',
     89            'title'  => '&nbsp;',
     90            'href'   => '#',
     91            'parent' => 'top-secondary',
     92        )
     93    );
     94}
     95
     96function arve_links(): string {
     97    return wp_kses(
     98        sprintf(
     99                // translators: %1$s: link, %2$s: link
     100            __( 'Level up your video embeds with <a href="%1$s">ARVE</a> or <a href="%2$s">ARVE Pro</a>', 'nextgenthemes-jsdelivr-this' ),
     101            esc_url( 'https://wordpress.org/plugins/advanced-responsive-video-embedder/' ),
     102            esc_url( 'https://nextgenthemes.com/plugins/arve-pro/' )
     103        ),
     104        array( 'a' => array( 'href' => array() ) )
     105    );
     106}
     107
     108
     109function admin_bar_html(): void {
     110
     111    if ( ! is_admin_bar_showing() ) {
     112        return;
     113    }
     114
     115    wp_enqueue_style( 'media-views' );
     116
     117    ?>
     118<dialog class="ngt-jsdelivr-dialog">
     119    <div class="ngt-jsdelivr-dialog__header">
     120        <button type="button" class="media-modal-close">
     121            <span class="media-modal-icon">
     122                <span class="screen-reader-text">Close dialog</span>
     123            </span>
     124        </button>
     125    </div>
     126    <h3><?= esc_html__( 'jsDelivr CDN plugin by Nextgenthemes', 'nextgenthemes-jsdelivr-this' ); ?></h3>
     127    <p>
     128        <?php
     129        esc_html_e(
     130            'These are the assets loaded from jsDelivr CDN. Do not worry about old WP versions in the URLs, this is simply because the files were not modified. A sha384 hash check is used so you can be 100% sure the files loaded from jsDelivr are the exact same files that would be served from your server.',
     131            'nextgenthemes-jsdelivr-this'
    28132        );
    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 );
    44     return $src;
    45 }
    46 
     133        ?>
     134    </p>
     135    <pre></pre>
     136    <p>
     137        <?php
     138        echo wp_kses(
     139            sprintf(
     140                // translators: %1$s: link, %2$s: link
     141                __( 'Level up your video embeds with <a href="%1$s">ARVE</a> or <a href="%2$s">ARVE Pro</a>', 'nextgenthemes-jsdelivr-this' ),
     142                esc_url( 'https://wordpress.org/plugins/advanced-responsive-video-embedder/' ),
     143                esc_url( 'https://nextgenthemes.com/plugins/arve-pro/' )
     144            ),
     145            array( 'a' => array( 'href' => array() ) )
     146        );
     147        ?>
     148    </p>
     149</dialog>
     150<style>
     151#wp-admin-bar-ngt-jsdelivr a {
     152    cursor: pointer;
     153
     154    &:hover {
     155        background-color: darkred !important;
     156    }
     157}
     158.ngt-jsdelivr-dialog {
     159    --dialog-padding: 1.2rem;
     160
     161    border: none;
     162    border-radius: 2px;
     163    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
     164    padding: 0 var(--dialog-padding);
     165    width: 100dvw;
     166    max-width: 50rem;
     167    font-size: 1rem;
     168    &::backdrop {
     169        /* Style the backdrop */
     170        background-color: rgba(0, 0, 0, .9);
     171    }
     172    pre {
     173        font-size: 14px;
     174        overflow-x: auto;
     175    }
     176}
     177
     178.ngt-jsdelivr-dialog__header {
     179    position: relative;
     180
     181    > button {
     182        --btn-bg: oklch(0.91 0.01 281.07);
     183
     184        position: absolute;
     185        top: 0;
     186        right: calc(var(--dialog-padding) * -1);
     187        border-radius: 0;
     188        background: var(--btn-bg);
     189        border-width: 0;
     190
     191        &:hover {
     192            background-color: oklch(from var(--btn-bg) calc(l - 0.1) c h);
     193        }
     194    }
     195}
     196</style>
     197    <?php
     198}
     199
     200function filter_script_attributes( array $attributes ): array {
     201
     202    $by_hash = detect_by_hash( $attributes['src'] );
     203
     204    if ( $by_hash ) {
     205        $attributes['src']         = $by_hash['src'];
     206        $attributes['integrity']   = $by_hash['integrity'];
     207        $attributes['crossorigin'] = 'anonymous';
     208
     209        // we already got what we wanted, so exit early
     210        return $attributes;
     211    }
     212
     213    $by_plugin = detect_plugin_asset( $attributes['src'], 'js' );
     214
     215    if ( $by_plugin ) {
     216        $attributes['src']         = $by_plugin['src'];
     217        $attributes['integrity']   = $by_plugin['integrity'];
     218        $attributes['crossorigin'] = 'anonymous';
     219    }
     220
     221    return $attributes;
     222}
     223
     224function filter_style_loader_tag( string $html ): string {
     225
     226    $p = new \WP_HTML_Tag_Processor( $html );
     227
     228    // we may have multiple links here, like with rel="preload" and regular
     229    while ( $p->next_tag( 'link' ) ) {
     230
     231        $href = $p->get_attribute( 'href' );
     232
     233        if ( ! $href ) {
     234            continue;
     235        }
     236
     237        $by_hash = detect_by_hash( $href );
     238
     239        if ( $by_hash ) {
     240            $p->set_attribute( 'href', $by_hash['src'] );
     241            $p->set_attribute( 'integrity', $by_hash['integrity'] );
     242            $p->set_attribute( 'crossorigin', 'anonymous' );
     243
     244            // we already got what we wanted, so exit early
     245            return $p->get_updated_html();
     246        }
     247
     248        $by_plugin = detect_plugin_asset( $href, 'css' );
     249
     250        if ( $by_plugin ) {
     251            $p->set_attribute( 'href', $by_plugin['src'] );
     252            $p->set_attribute( 'integrity', $by_plugin['integrity'] );
     253            $p->set_attribute( 'crossorigin', 'anonymous' );
     254        }
     255    }
     256
     257    return $html;
     258}
     259
     260/**
     261 * Checks for a active plugin file based on a slug.
     262 *
     263 * @param string $plugin_slug The plugin slug to search for.
     264 *
     265 * @return string|null The path to the main plugin file if found, null otherwise.
     266 */
    47267function get_plugin_dir_file( string $plugin_slug ): ?string {
    48268
     
    63283}
    64284
    65 function detect_plugin_asset( string $type, string $src, string $handle ): string {
     285/**
     286 * Detects if file can be served from CDN
     287 *
     288 * Given a <link href="..."> or <script src="..."> it detects CDN files
     289 *
     290 * Plugins hosted on wp.org need some trickery by this plugin as the jsDelivr API does not detect them by hash.
     291 * #1 For wp.org assets the src URL most have `/plugins/plugin-slug/` in them and end with `.js` or `.css` (excluding cash busting `?ver=1.2.3`).
     292 * #2 wp.org assets need to have its current version published as a tag on the wp.org plugins SVN, `trunk` will not work.
     293 *
     294 * @param string $src     The src to detect.
     295 * @param string $extension The extension of the file (css or js).
     296 *
     297 * @return array|null The array contains 'src' and 'integrity' if file and hash can be detected on the server and the file exists on the CDN.
     298 */
     299function detect_plugin_asset( string $src, string $extension ): ?array {
    66300
    67301    if ( str_starts_with( $src, 'https://cdn.jsdelivr.net' ) ) {
    68302        return $src;
    69303    }
    70     $ext = ( 'style' === $type ) ? 'css' : 'js';
    71 
    72     preg_match( "#/plugins/(?<plugin_slug>[^/]+)/(?<path>.*\.$ext)#", $src, $matches );
     304
     305    preg_match( "#/plugins/(?<plugin_slug>[^/]+)/(?<path>.*\.$extension)#", $src, $matches );
    73306
    74307    if ( ! empty( $matches['plugin_slug'] ) ) {
     
    77310
    78311    if ( empty( $plugin_dir_file ) ) {
    79         return $src;
    80     }
    81 
    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;
     312        return null;
     313    }
     314
     315    $plugin_ver     = get_plugin_version( $plugin_dir_file );
     316    $cdn_file       = 'https://cdn.jsdelivr.net/wp/' . $matches['plugin_slug'] . '/tags/' . $plugin_ver . '/' . $matches['path'];
     317    $transient_name = shorten_transient_name( 'ngt-jsd_' . $cdn_file );
     318
     319    $data = get_transient( $transient_name );
     320
     321    if ( false === $data && ! call_limit() ) {
     322
    93323        $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] ) {
     324        $file_headers = remote_get_head($cdn_file, [ 'timeout' => 2 ]);
     325
     326        if ( ! is_wp_error( $file_headers ) ) {
    97327            $data->file_exists = true;
    98             $path              = path_from_url( $src );
    99 
    100             if ( $path ) {
    101                 $data->integrity = gen_integrity( file_get_contents( $path ) );
    102             }
     328            $data->integrity   = integrity_for_src( $src );
    103329        }
    104330
     
    108334
    109335    if ( ! empty( $data->file_exists ) && ! empty( $data->integrity ) ) {
    110         $src = $cdn_file;
    111         add_integrity_to_asset( $type, $handle, $data->integrity );
    112     }
    113 
    114     return $src;
    115 }
    116 
    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;
     336        return [
     337            'src'        => $cdn_file,
     338            'integrity'  => $data->integrity,
     339        ];
     340    }
     341
     342    return null;
     343}
     344
     345function integrity_for_src( string $src ): ?string {
     346    $path = path_from_url( $src );
     347
     348    if ( $path ) {
     349        $file_content = file_get_contents( $path );
     350
     351        if ( ! $file_content ) {
     352            wp_trigger_error( __FUNCTION__, 'Could not read file: ' . $path );
     353        } else {
     354            return gen_integrity( $file_content );
     355        }
     356    }
     357
     358    return null;
     359}
     360
     361function get_jsdelivr_hash_api_data( string $file_path, string $src ): ?object {
     362
     363    $transient_name = shorten_transient_name( 'ngt-jsd_' . $src);
     364    $result         = get_transient( $transient_name );
     365
     366    if ( false === $result && ! call_limit() ) {
     367
    190368        $result       = new \stdClass();
    191369        $file_content = file_get_contents( $file_path );
     
    194372            $sha256 = hash( 'sha256', $file_content );
    195373            $data   = wp_safe_remote_get(
    196                 "https://data.jsdelivr.com/v1/lookup/hash/$sha256",
     374                'https://data.jsdelivr.com/v1/lookup/hash/' . $sha256,
    197375                array(
    198376                    'user-agent' => 'https://nextgenthemes.com/plugins/jsdelivr-this',
     
    202380
    203381            if ( ! is_wp_error( $data ) ) {
    204                 $result            = (object) json_decode( wp_remote_retrieve_body( $data ) );
     382
     383                $body = wp_remote_retrieve_body( $data );
     384
     385                if ( '' === $body ) {
     386                    wp_trigger_error( __FUNCTION__, 'Empty body' );
     387                } else {
     388
     389                    try {
     390                        $result = (object) json_decode( $body, false, 5, JSON_THROW_ON_ERROR );
     391                    } catch ( \Exception $e ) {
     392                        wp_trigger_error( __FUNCTION__, $e->getMessage() );
     393                    }
     394                }
     395
    205396                $result->integrity = gen_integrity( $file_content );
    206397            }
     
    211402    }
    212403
     404    // So we can used nulled return type on php 7.4. Union types require 8.0
    213405    if ( false === $result ) {
    214406        $result = null;
     
    218410}
    219411
    220 function detect_by_hash( string $type, string $src, string $handle ): string {
     412function detect_by_hash( string $src ): ?array {
    221413
    222414    if ( str_starts_with( $src, 'https://cdn.jsdelivr.net' ) ) {
     
    227419
    228420    if ( $path ) {
    229         $data = get_jsdelivr_hash_api_data( $path, $handle, $src );
     421        $data = get_jsdelivr_hash_api_data( $path, $src );
    230422    }
    231423
     
    241433            $data->version . $data->file
    242434        );
    243         add_integrity_to_asset( $type, $handle, $data->integrity );
    244     }
    245 
    246     return $src;
     435
     436        return [
     437            'src'        => $src,
     438            'integrity'  => $data->integrity,
     439        ];
     440    }
     441
     442    return null;
    247443}
    248444
     
    273469}
    274470
     471/**
     472 * Retrieves the file path for a given URL, relative to the WordPress root directory.
     473 *
     474 * First checks if the file exists in the WordPress root directory, and if not, then
     475 * checks the parent directory of the WordPress root directory.
     476 *
     477 * @param string $url The URL to retrieve the file path for.
     478 * @return string|null The file path if it exists, or null otherwise.
     479 */
    275480function path_from_url( string $url ): ?string {
    276481    $parsed_url = wp_parse_url( $url );
     
    291496    return $plugin_data['Version'];
    292497}
     498
     499/**
     500 * @return mixed|WP_Error
     501 */
     502function remote_get_head( string $url, array $args = array() ) {
     503
     504    $response = wp_safe_remote_head( $url, $args );
     505
     506    if ( is_wp_error( $response ) ) {
     507        return $response;
     508    }
     509
     510    $response_code = wp_remote_retrieve_response_code( $response );
     511
     512    if ( 200 !== $response_code ) {
     513
     514        return new \WP_Error(
     515            $response_code,
     516            sprintf(
     517                // Translators: 1 URL 2 HTTP response code.
     518                __( 'url: %1$s Status code 200 expected but was %2$s.', 'advanced-responsive-video-embedder' ),
     519                $url,
     520                $response_code
     521            )
     522        );
     523    }
     524
     525    return $response;
     526}
     527
     528function shorten_transient_name( string $transient_name ): string {
     529
     530    $transient_name = str_replace( 'https://', '', $transient_name );
     531
     532    if ( strlen($transient_name) > 172 ) {
     533        $transient_name = preg_replace( '/[^a-zA-Z0-9_]/', '', $transient_name );
     534    }
     535
     536    if ( strlen($transient_name) > 172 ) {
     537        $transient_name = substr($transient_name, 0, 107) . '_' . hash( 'sha256', $transient_name ); // 107 + 1 + 64
     538    }
     539
     540    return $transient_name;
     541}
     542
     543/**
     544 * Limit the number of remote requests to jsDelivr in a short timespan.
     545 * In THEORY after the plugin is activated having this unlimited could
     546 * take the first php page generation of a page a long time, so we limit
     547 * it to 2 (this) x 2 (wp_safe_remote_get timeout) seconds at the time
     548 * of writing.
     549 *
     550 * @return bool True if the limit is reached, false otherwise.
     551 */
     552function call_limit(): bool {
     553
     554    static $limit = 2;
     555
     556    if ( 0 === $limit ) {
     557        return true;
     558    }
     559
     560    --$limit;
     561
     562    return false;
     563}
  • nextgenthemes-jsdelivr-this/tags/1.2.1/readme.txt

    r3203494 r3203528  
     1
    12=== NGT jsDelivr CDN ===
    23Contributors: nico23
     
    67Requires PHP: 7.4
    78Tested up to: 6.5.3
    8 Stable tag: 1.1.1
     9Stable tag: 1.2.1
    910License: GPL 3.0
    1011License URI: http://www.gnu.org/licenses/gpl-3.0.html
    1112
    12 Free CDN for WordPress Core and Plugin assets.
     13Free CDN for for all assets from wordpress.org Github and NPM.
    1314
    1415== Changelog ==
    1516
    16 = 2024-12-06 1.1.1 =
    17 * 1.2 was broken. Revert release.
     17= 2024-12-06 1.2.1 =
     18* Fix: Code mistake caused `integrity` attribute to be wrong, plugin files would get blocked.
     19
     20= 2024-12-04 1.2.0 =
     21* New: A info dialog was added that is only loaded when the admin bar is visible.
     22* New/Fix: Support for script modules.
     23* Improved: Shorten potentially too long transient names.
     24* Improved: Replaced `get_headers` with `wp_safe_remote_head`. WP coding standards and more efficient.
     25* Improved: Simplified and improved the code.
    1826
    1927= 2024-05-14 1.1.0 =
     
    2331* Run only once per page-load.
    2432* Better function names and some useful comments.
    25 * 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)
     33* Send this plugins url as user-agent to jsDelivr knows how it's used. (They asked for this). This also means more privacy as the `wp_remote_get` referrer by default would send your site URL (I really do not like that)
    2634
    2735= 2019-08-31 0.9.4 =
     
    3543
    3644== Description ==
    37 It replaces all WP Core assets with versions hosted on jsDelivr.
     45It replaces all assets with versions available on jsDelivr. No options, nothing to configure, just works.
    3846
    39 The following conditions need to be met that plugins assets will be served from jsDeliver:
     47The code needs to be openly hosted on NPM, Github or wordpress.org.
    4048
    41 1. The plugin needs to be hosted on wp.org. Commercial plugins and plugins from elsewhere will not work because jsDelivr mirrors the wp.org plugin dir and nothing else.
    42 1. The asset src URL most have `/plugins/plugin-slug/` in them and end with `.js` or `.css` (excluding cash busting `?ver=1.2.3`).
    43 1. It needs to have its current version published as a tag on the wp.org plugins SVN. Some plugins may only push to /trunk/ (like my own at the time of writing) or do not have their latest version published as tags.
     49This plugin adds a little a invisible button on the admin bar on the top right, left of "Howdy, Name". You can click that and see the assets loaded from jsDelivr.
    4450
    45 = Donations are really appreciated =
     51= Support me =
    4652
    47 It took me a lot of time to come up with this plugin and I had many iterations over various different approaches how to do this until I came up with this working solution that also does not need much code. I know the official plugin was abandoned years ago and I looked at complicated bloated code and did not even feel like learning what its doing and never looked at it again and started from scratch. [Please donate here](https://nextgenthemes.com/donate/).
     53It took me a lot of time to come up with this plugin and I had many iterations over various different approaches how to do this until I came up with this working solution that also does not need much code. I know the official plugin was abandoned years ago and I looked at complicated bloated code and did not even feel like learning what its doing and never looked at it again and started from scratch.
     54
     55Please check out my commercial plugin and level up your video embeds with [ARVE Pro](https://nextgenthemes.com/plugins/arve-pro/) or [Donate here](https://nextgenthemes.com/donate/)
  • nextgenthemes-jsdelivr-this/trunk/nextgenthemes-jsdelivr-this.php

    r3203494 r3203528  
    22/**
    33 * @wordpress-plugin
    4  * Plugin Name:       NGT jsDelivr CDN
     4 * Plugin Name:       Free jsDelivr CDN
    55 * Plugin URI:        https://nextgenthemes.com
    6  * Description:       Makes your site load all WP Core and plugin assets from jsDelivr CDN
    7  * Version:           1.1.1
     6 * Description:       Serves all available assets from free jsDelivr CDN
     7 * Version:           1.2.1
    88 * Requres PHP:       7.4
    99 * Author:            Nicolas Jonas
     
    1212 * License URI:       http://www.gnu.org/licenses/gpl-3.0.html
    1313 */
     14
     15declare(strict_types = 1);
     16
    1417namespace Nextgenthemes\jsDelivrThis;
    1518
    16 const VERSION = '1.1.1';
    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' )
     19const VERSION = '1.2.1';
     20
     21add_action( 'plugins_loaded', __NAMESPACE__ . '\init' );
     22
     23function init(): void {
     24
     25    add_filter( 'wp_script_attributes', __NAMESPACE__ . '\filter_script_attributes', 10, 1 );
     26    add_filter( 'style_loader_tag', __NAMESPACE__ . '\filter_style_loader_tag', 10, 1 );
     27
     28    add_action( 'admin_bar_menu', __NAMESPACE__ . '\add_item_to_admin_bar', 33 );
     29
     30    add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\enqueue_assets' );
     31    add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\enqueue_assets' );
     32
     33    add_action( 'admin_footer', __NAMESPACE__ . '\admin_bar_html' );
     34    add_action( 'wp_footer', __NAMESPACE__ . '\admin_bar_html' );
     35
     36    add_action(
     37        'init',
     38        function (): void {
     39            wp_register_script_module(
     40                'ngt-jsdelivr-dialog',
     41                plugins_url( 'dialog.js', __FILE__ ),
     42                array(),
     43                VERSION
     44            );
     45        }
     46    );
     47
     48    add_filter(
     49        'plugin_row_meta',
     50        function ( array $links, string $file ): array {
     51
     52            if ( 'nextgenthemes-jsdelivr-this/nextgenthemes-jsdelivr-this.php' !== $file ) {
     53                return $links;
     54            }
     55            $links[] = '<strong>' . arve_links() . '</strong>';
     56
     57            return $links;
     58        },
     59        10,
     60        2
     61    );
     62
     63    add_filter(
     64        'plugin_action_links_' . plugin_basename( __FILE__ ),
     65        function ( array $links ) {
     66
     67            $links['donate'] = sprintf(
     68                '<a href="https://nextgenthemes.com/donate/">%s</a>',
     69                esc_html__( 'Donate', 'nextgenthemes-jsdelivr-this' )
     70            );
     71
     72            return $links;
     73        }
     74    );
     75}
     76
     77function enqueue_assets(): void {
     78
     79    if ( is_admin_bar_showing() ) {
     80        wp_enqueue_script_module( 'ngt-jsdelivr-dialog' );
     81    }
     82}
     83
     84function add_item_to_admin_bar( object $admin_bar ): void {
     85    // Add a new item to the admin bar
     86    $admin_bar->add_node(
     87        array(
     88            'id'     => 'ngt-jsdelivr',
     89            'title'  => '&nbsp;',
     90            'href'   => '#',
     91            'parent' => 'top-secondary',
     92        )
     93    );
     94}
     95
     96function arve_links(): string {
     97    return wp_kses(
     98        sprintf(
     99                // translators: %1$s: link, %2$s: link
     100            __( 'Level up your video embeds with <a href="%1$s">ARVE</a> or <a href="%2$s">ARVE Pro</a>', 'nextgenthemes-jsdelivr-this' ),
     101            esc_url( 'https://wordpress.org/plugins/advanced-responsive-video-embedder/' ),
     102            esc_url( 'https://nextgenthemes.com/plugins/arve-pro/' )
     103        ),
     104        array( 'a' => array( 'href' => array() ) )
     105    );
     106}
     107
     108
     109function admin_bar_html(): void {
     110
     111    if ( ! is_admin_bar_showing() ) {
     112        return;
     113    }
     114
     115    wp_enqueue_style( 'media-views' );
     116
     117    ?>
     118<dialog class="ngt-jsdelivr-dialog">
     119    <div class="ngt-jsdelivr-dialog__header">
     120        <button type="button" class="media-modal-close">
     121            <span class="media-modal-icon">
     122                <span class="screen-reader-text">Close dialog</span>
     123            </span>
     124        </button>
     125    </div>
     126    <h3><?= esc_html__( 'jsDelivr CDN plugin by Nextgenthemes', 'nextgenthemes-jsdelivr-this' ); ?></h3>
     127    <p>
     128        <?php
     129        esc_html_e(
     130            'These are the assets loaded from jsDelivr CDN. Do not worry about old WP versions in the URLs, this is simply because the files were not modified. A sha384 hash check is used so you can be 100% sure the files loaded from jsDelivr are the exact same files that would be served from your server.',
     131            'nextgenthemes-jsdelivr-this'
    28132        );
    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 );
    44     return $src;
    45 }
    46 
     133        ?>
     134    </p>
     135    <pre></pre>
     136    <p>
     137        <?php
     138        echo wp_kses(
     139            sprintf(
     140                // translators: %1$s: link, %2$s: link
     141                __( 'Level up your video embeds with <a href="%1$s">ARVE</a> or <a href="%2$s">ARVE Pro</a>', 'nextgenthemes-jsdelivr-this' ),
     142                esc_url( 'https://wordpress.org/plugins/advanced-responsive-video-embedder/' ),
     143                esc_url( 'https://nextgenthemes.com/plugins/arve-pro/' )
     144            ),
     145            array( 'a' => array( 'href' => array() ) )
     146        );
     147        ?>
     148    </p>
     149</dialog>
     150<style>
     151#wp-admin-bar-ngt-jsdelivr a {
     152    cursor: pointer;
     153
     154    &:hover {
     155        background-color: darkred !important;
     156    }
     157}
     158.ngt-jsdelivr-dialog {
     159    --dialog-padding: 1.2rem;
     160
     161    border: none;
     162    border-radius: 2px;
     163    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
     164    padding: 0 var(--dialog-padding);
     165    width: 100dvw;
     166    max-width: 50rem;
     167    font-size: 1rem;
     168    &::backdrop {
     169        /* Style the backdrop */
     170        background-color: rgba(0, 0, 0, .9);
     171    }
     172    pre {
     173        font-size: 14px;
     174        overflow-x: auto;
     175    }
     176}
     177
     178.ngt-jsdelivr-dialog__header {
     179    position: relative;
     180
     181    > button {
     182        --btn-bg: oklch(0.91 0.01 281.07);
     183
     184        position: absolute;
     185        top: 0;
     186        right: calc(var(--dialog-padding) * -1);
     187        border-radius: 0;
     188        background: var(--btn-bg);
     189        border-width: 0;
     190
     191        &:hover {
     192            background-color: oklch(from var(--btn-bg) calc(l - 0.1) c h);
     193        }
     194    }
     195}
     196</style>
     197    <?php
     198}
     199
     200function filter_script_attributes( array $attributes ): array {
     201
     202    $by_hash = detect_by_hash( $attributes['src'] );
     203
     204    if ( $by_hash ) {
     205        $attributes['src']         = $by_hash['src'];
     206        $attributes['integrity']   = $by_hash['integrity'];
     207        $attributes['crossorigin'] = 'anonymous';
     208
     209        // we already got what we wanted, so exit early
     210        return $attributes;
     211    }
     212
     213    $by_plugin = detect_plugin_asset( $attributes['src'], 'js' );
     214
     215    if ( $by_plugin ) {
     216        $attributes['src']         = $by_plugin['src'];
     217        $attributes['integrity']   = $by_plugin['integrity'];
     218        $attributes['crossorigin'] = 'anonymous';
     219    }
     220
     221    return $attributes;
     222}
     223
     224function filter_style_loader_tag( string $html ): string {
     225
     226    $p = new \WP_HTML_Tag_Processor( $html );
     227
     228    // we may have multiple links here, like with rel="preload" and regular
     229    while ( $p->next_tag( 'link' ) ) {
     230
     231        $href = $p->get_attribute( 'href' );
     232
     233        if ( ! $href ) {
     234            continue;
     235        }
     236
     237        $by_hash = detect_by_hash( $href );
     238
     239        if ( $by_hash ) {
     240            $p->set_attribute( 'href', $by_hash['src'] );
     241            $p->set_attribute( 'integrity', $by_hash['integrity'] );
     242            $p->set_attribute( 'crossorigin', 'anonymous' );
     243
     244            // we already got what we wanted, so exit early
     245            return $p->get_updated_html();
     246        }
     247
     248        $by_plugin = detect_plugin_asset( $href, 'css' );
     249
     250        if ( $by_plugin ) {
     251            $p->set_attribute( 'href', $by_plugin['src'] );
     252            $p->set_attribute( 'integrity', $by_plugin['integrity'] );
     253            $p->set_attribute( 'crossorigin', 'anonymous' );
     254        }
     255    }
     256
     257    return $html;
     258}
     259
     260/**
     261 * Checks for a active plugin file based on a slug.
     262 *
     263 * @param string $plugin_slug The plugin slug to search for.
     264 *
     265 * @return string|null The path to the main plugin file if found, null otherwise.
     266 */
    47267function get_plugin_dir_file( string $plugin_slug ): ?string {
    48268
     
    63283}
    64284
    65 function detect_plugin_asset( string $type, string $src, string $handle ): string {
     285/**
     286 * Detects if file can be served from CDN
     287 *
     288 * Given a <link href="..."> or <script src="..."> it detects CDN files
     289 *
     290 * Plugins hosted on wp.org need some trickery by this plugin as the jsDelivr API does not detect them by hash.
     291 * #1 For wp.org assets the src URL most have `/plugins/plugin-slug/` in them and end with `.js` or `.css` (excluding cash busting `?ver=1.2.3`).
     292 * #2 wp.org assets need to have its current version published as a tag on the wp.org plugins SVN, `trunk` will not work.
     293 *
     294 * @param string $src     The src to detect.
     295 * @param string $extension The extension of the file (css or js).
     296 *
     297 * @return array|null The array contains 'src' and 'integrity' if file and hash can be detected on the server and the file exists on the CDN.
     298 */
     299function detect_plugin_asset( string $src, string $extension ): ?array {
    66300
    67301    if ( str_starts_with( $src, 'https://cdn.jsdelivr.net' ) ) {
    68302        return $src;
    69303    }
    70     $ext = ( 'style' === $type ) ? 'css' : 'js';
    71 
    72     preg_match( "#/plugins/(?<plugin_slug>[^/]+)/(?<path>.*\.$ext)#", $src, $matches );
     304
     305    preg_match( "#/plugins/(?<plugin_slug>[^/]+)/(?<path>.*\.$extension)#", $src, $matches );
    73306
    74307    if ( ! empty( $matches['plugin_slug'] ) ) {
     
    77310
    78311    if ( empty( $plugin_dir_file ) ) {
    79         return $src;
    80     }
    81 
    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;
     312        return null;
     313    }
     314
     315    $plugin_ver     = get_plugin_version( $plugin_dir_file );
     316    $cdn_file       = 'https://cdn.jsdelivr.net/wp/' . $matches['plugin_slug'] . '/tags/' . $plugin_ver . '/' . $matches['path'];
     317    $transient_name = shorten_transient_name( 'ngt-jsd_' . $cdn_file );
     318
     319    $data = get_transient( $transient_name );
     320
     321    if ( false === $data && ! call_limit() ) {
     322
    93323        $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] ) {
     324        $file_headers = remote_get_head($cdn_file, [ 'timeout' => 2 ]);
     325
     326        if ( ! is_wp_error( $file_headers ) ) {
    97327            $data->file_exists = true;
    98             $path              = path_from_url( $src );
    99 
    100             if ( $path ) {
    101                 $data->integrity = gen_integrity( file_get_contents( $path ) );
    102             }
     328            $data->integrity   = integrity_for_src( $src );
    103329        }
    104330
     
    108334
    109335    if ( ! empty( $data->file_exists ) && ! empty( $data->integrity ) ) {
    110         $src = $cdn_file;
    111         add_integrity_to_asset( $type, $handle, $data->integrity );
    112     }
    113 
    114     return $src;
    115 }
    116 
    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;
     336        return [
     337            'src'        => $cdn_file,
     338            'integrity'  => $data->integrity,
     339        ];
     340    }
     341
     342    return null;
     343}
     344
     345function integrity_for_src( string $src ): ?string {
     346    $path = path_from_url( $src );
     347
     348    if ( $path ) {
     349        $file_content = file_get_contents( $path );
     350
     351        if ( ! $file_content ) {
     352            wp_trigger_error( __FUNCTION__, 'Could not read file: ' . $path );
     353        } else {
     354            return gen_integrity( $file_content );
     355        }
     356    }
     357
     358    return null;
     359}
     360
     361function get_jsdelivr_hash_api_data( string $file_path, string $src ): ?object {
     362
     363    $transient_name = shorten_transient_name( 'ngt-jsd_' . $src);
     364    $result         = get_transient( $transient_name );
     365
     366    if ( false === $result && ! call_limit() ) {
     367
    190368        $result       = new \stdClass();
    191369        $file_content = file_get_contents( $file_path );
     
    194372            $sha256 = hash( 'sha256', $file_content );
    195373            $data   = wp_safe_remote_get(
    196                 "https://data.jsdelivr.com/v1/lookup/hash/$sha256",
     374                'https://data.jsdelivr.com/v1/lookup/hash/' . $sha256,
    197375                array(
    198376                    'user-agent' => 'https://nextgenthemes.com/plugins/jsdelivr-this',
     
    202380
    203381            if ( ! is_wp_error( $data ) ) {
    204                 $result            = (object) json_decode( wp_remote_retrieve_body( $data ) );
     382
     383                $body = wp_remote_retrieve_body( $data );
     384
     385                if ( '' === $body ) {
     386                    wp_trigger_error( __FUNCTION__, 'Empty body' );
     387                } else {
     388
     389                    try {
     390                        $result = (object) json_decode( $body, false, 5, JSON_THROW_ON_ERROR );
     391                    } catch ( \Exception $e ) {
     392                        wp_trigger_error( __FUNCTION__, $e->getMessage() );
     393                    }
     394                }
     395
    205396                $result->integrity = gen_integrity( $file_content );
    206397            }
     
    211402    }
    212403
     404    // So we can used nulled return type on php 7.4. Union types require 8.0
    213405    if ( false === $result ) {
    214406        $result = null;
     
    218410}
    219411
    220 function detect_by_hash( string $type, string $src, string $handle ): string {
     412function detect_by_hash( string $src ): ?array {
    221413
    222414    if ( str_starts_with( $src, 'https://cdn.jsdelivr.net' ) ) {
     
    227419
    228420    if ( $path ) {
    229         $data = get_jsdelivr_hash_api_data( $path, $handle, $src );
     421        $data = get_jsdelivr_hash_api_data( $path, $src );
    230422    }
    231423
     
    241433            $data->version . $data->file
    242434        );
    243         add_integrity_to_asset( $type, $handle, $data->integrity );
    244     }
    245 
    246     return $src;
     435
     436        return [
     437            'src'        => $src,
     438            'integrity'  => $data->integrity,
     439        ];
     440    }
     441
     442    return null;
    247443}
    248444
     
    273469}
    274470
     471/**
     472 * Retrieves the file path for a given URL, relative to the WordPress root directory.
     473 *
     474 * First checks if the file exists in the WordPress root directory, and if not, then
     475 * checks the parent directory of the WordPress root directory.
     476 *
     477 * @param string $url The URL to retrieve the file path for.
     478 * @return string|null The file path if it exists, or null otherwise.
     479 */
    275480function path_from_url( string $url ): ?string {
    276481    $parsed_url = wp_parse_url( $url );
     
    291496    return $plugin_data['Version'];
    292497}
     498
     499/**
     500 * @return mixed|WP_Error
     501 */
     502function remote_get_head( string $url, array $args = array() ) {
     503
     504    $response = wp_safe_remote_head( $url, $args );
     505
     506    if ( is_wp_error( $response ) ) {
     507        return $response;
     508    }
     509
     510    $response_code = wp_remote_retrieve_response_code( $response );
     511
     512    if ( 200 !== $response_code ) {
     513
     514        return new \WP_Error(
     515            $response_code,
     516            sprintf(
     517                // Translators: 1 URL 2 HTTP response code.
     518                __( 'url: %1$s Status code 200 expected but was %2$s.', 'advanced-responsive-video-embedder' ),
     519                $url,
     520                $response_code
     521            )
     522        );
     523    }
     524
     525    return $response;
     526}
     527
     528function shorten_transient_name( string $transient_name ): string {
     529
     530    $transient_name = str_replace( 'https://', '', $transient_name );
     531
     532    if ( strlen($transient_name) > 172 ) {
     533        $transient_name = preg_replace( '/[^a-zA-Z0-9_]/', '', $transient_name );
     534    }
     535
     536    if ( strlen($transient_name) > 172 ) {
     537        $transient_name = substr($transient_name, 0, 107) . '_' . hash( 'sha256', $transient_name ); // 107 + 1 + 64
     538    }
     539
     540    return $transient_name;
     541}
     542
     543/**
     544 * Limit the number of remote requests to jsDelivr in a short timespan.
     545 * In THEORY after the plugin is activated having this unlimited could
     546 * take the first php page generation of a page a long time, so we limit
     547 * it to 2 (this) x 2 (wp_safe_remote_get timeout) seconds at the time
     548 * of writing.
     549 *
     550 * @return bool True if the limit is reached, false otherwise.
     551 */
     552function call_limit(): bool {
     553
     554    static $limit = 2;
     555
     556    if ( 0 === $limit ) {
     557        return true;
     558    }
     559
     560    --$limit;
     561
     562    return false;
     563}
  • nextgenthemes-jsdelivr-this/trunk/readme.txt

    r3203494 r3203528  
     1
    12=== NGT jsDelivr CDN ===
    23Contributors: nico23
     
    67Requires PHP: 7.4
    78Tested up to: 6.5.3
    8 Stable tag: 1.1.1
     9Stable tag: 1.2.1
    910License: GPL 3.0
    1011License URI: http://www.gnu.org/licenses/gpl-3.0.html
    1112
    12 Free CDN for WordPress Core and Plugin assets.
     13Free CDN for for all assets from wordpress.org Github and NPM.
    1314
    1415== Changelog ==
    1516
    16 = 2024-12-06 1.1.1 =
    17 * 1.2 was broken. Revert release.
     17= 2024-12-06 1.2.1 =
     18* Fix: Code mistake caused `integrity` attribute to be wrong, plugin files would get blocked.
     19
     20= 2024-12-04 1.2.0 =
     21* New: A info dialog was added that is only loaded when the admin bar is visible.
     22* New/Fix: Support for script modules.
     23* Improved: Shorten potentially too long transient names.
     24* Improved: Replaced `get_headers` with `wp_safe_remote_head`. WP coding standards and more efficient.
     25* Improved: Simplified and improved the code.
    1826
    1927= 2024-05-14 1.1.0 =
     
    2331* Run only once per page-load.
    2432* Better function names and some useful comments.
    25 * 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)
     33* Send this plugins url as user-agent to jsDelivr knows how it's used. (They asked for this). This also means more privacy as the `wp_remote_get` referrer by default would send your site URL (I really do not like that)
    2634
    2735= 2019-08-31 0.9.4 =
     
    3543
    3644== Description ==
    37 It replaces all WP Core assets with versions hosted on jsDelivr.
     45It replaces all assets with versions available on jsDelivr. No options, nothing to configure, just works.
    3846
    39 The following conditions need to be met that plugins assets will be served from jsDeliver:
     47The code needs to be openly hosted on NPM, Github or wordpress.org.
    4048
    41 1. The plugin needs to be hosted on wp.org. Commercial plugins and plugins from elsewhere will not work because jsDelivr mirrors the wp.org plugin dir and nothing else.
    42 1. The asset src URL most have `/plugins/plugin-slug/` in them and end with `.js` or `.css` (excluding cash busting `?ver=1.2.3`).
    43 1. It needs to have its current version published as a tag on the wp.org plugins SVN. Some plugins may only push to /trunk/ (like my own at the time of writing) or do not have their latest version published as tags.
     49This plugin adds a little a invisible button on the admin bar on the top right, left of "Howdy, Name". You can click that and see the assets loaded from jsDelivr.
    4450
    45 = Donations are really appreciated =
     51= Support me =
    4652
    47 It took me a lot of time to come up with this plugin and I had many iterations over various different approaches how to do this until I came up with this working solution that also does not need much code. I know the official plugin was abandoned years ago and I looked at complicated bloated code and did not even feel like learning what its doing and never looked at it again and started from scratch. [Please donate here](https://nextgenthemes.com/donate/).
     53It took me a lot of time to come up with this plugin and I had many iterations over various different approaches how to do this until I came up with this working solution that also does not need much code. I know the official plugin was abandoned years ago and I looked at complicated bloated code and did not even feel like learning what its doing and never looked at it again and started from scratch.
     54
     55Please check out my commercial plugin and level up your video embeds with [ARVE Pro](https://nextgenthemes.com/plugins/arve-pro/) or [Donate here](https://nextgenthemes.com/donate/)
Note: See TracChangeset for help on using the changeset viewer.