Outputting Canonical Resource URLs Across a Multisite Network?

Say a theme “foo” is used on across a network of sites. In each site, all the theme resources (.css, .js, etc.) will have distinct URLs:

I want these to all be:

Read More

What’s the best way to do this? I can fix URLs directly output in the theme, but how do I normalize the URLs in enqueued scripts/stylesheets?

Aside: What’s the benefit of having expressions like get_bloginfo('template_url') create URLs that differ across sites?

Related posts

Leave a Reply

1 comment

  1. Hi @mrclay:

    Good questions.

    WordPress Multisite is Many Independant Sites, not Many Dependant Sites

    To answer your aside, WordPress multisite was designed to be a collections of independent WordPress installs collected into one where each site is likely to be very different, unlike your use-case. So each site has it’s own theme and WordPress wants those themes to work on both single site installs and for sites in a multisite install.

    WordPress Out-of-the-Box: Decisions, not Options, but Change with Plugins

    While WordPress could have had an option for doing what you want built in the philosophy at WordPress is to make decisions for you instead of giving you a thousand options you’ll feel you need to understand, and then letting plugin developers changes those decisions if needed.

    Yes: Central Resources = Better Load Times

    Still, it would be nice to be able to do what you are asking as it would improve average load times by leveraging HTTP GET caching for multisites with common themes.

    No Need to “Rewrite” URLs

    To start, there’s no need to “rewrite” the URLs, just make sure the stylesheet and other resources you want are part of the theme assigned to the main site and then they will be located at the URLs you want.

    Creating the “Hook” for style.css

    In WordPress you change the out-of-the-box decisions by adding “hooks”; i.e. references to functions that will let you modify values. One such hook is 'stylesheet_uri' which modifies the URL for the 'style.css' file that you explicitly referenced. You can copy this code to your theme’s functions.php file or you can put into the .PHP file of a WordPress plugin you might be writing. Note that I coded this to support either subdomain installs or subdirectory installs:

    function normalize_resource_url($url) {
      if (MULTISITE) {
        $site_url = get_site_url(BLOG_ID_CURRENT_SITE); 
        if (SUBDOMAIN_INSTALL) {
          $url = preg_replace("#^(https?://[^/]+)(/wp-.*.(css|js))?$#","{$site_url}\2",$url);
        } else {
          $url = preg_replace("#^({$site_url})(/[^/]+)(/wp-.*.(css|js))?$#",'13',$url);
        }
      }
      return $url;
    }
    

    Other Resource URLs May Need Their Own Hooks

    The code above only modifies the main stylesheet URL; if you need other URLs modified you may not to use other hooks. For example, you may end up needing to use 'style_loader_src' and/or 'plugins_url' too but I didn’t have my test system set up with enough use-cases to verify if it’s needed or not:

    add_filter('style_loader_src','normalize_resource_url');
    add_filter('plugins_url','normalize_resource_url');
    

    Enqueued Scripts and Styles use a base_url Property

    And it turns out that the enqueued scripts and styles use a property named base_url of the global variables $wp_scripts and $wp_styles, respectively to determine their location if a full URL is not passed explicitly by the developer. The property of base_url is only set once for scripts and left blank for styles; the former is set upon instantiation of a WP_Scripts object that is then assigned to the global variable $wp_scripts.

    Set the base_url Property in an Early 'init' Hook

    If you instantiate and assign those early before any calls to wp_enqueue_script() or wp_enqueue_style() you can then set the base_url property immediately after which you can do with an init hook with a priority of 1 (1 priorities run very early, before most hooks). Here’s the 'init' hook to accomplish this:

    add_filter('init','normalize_base_urls',1);
    function normalize_base_urls() {
      $GLOBALS['wp_scripts'] = new WP_Scripts();
      $GLOBALS['wp_styles'] = new WP_Styles();
    
      $base_url = normalize_resource_url(get_site_url(BLOG_ID_CURRENT_SITE));
    
      $GLOBALS['wp_scripts']->base_url = $base_url;
      $GLOBALS['wp_styles']->base_url = $base_url;
    }
    

    Or Set the base_url Property in a Late 'init' Hook

    It’s also possible to use a very high number for the priority for the 'init' hook (such as 100) after all wp_enqueue_script() or wp_enqueue_style() have been called by (higher numbered priority hooks run after those with lower numbers). If so you don’t need to instantiate the globals, just assign the base_url property:

    add_filter('init','normalize_base_urls',100);
    function normalize_base_urls() {
      $base_url = normalize_resource_url(get_site_url(BLOG_ID_CURRENT_SITE));
      $GLOBALS['wp_scripts']->base_url = $base_url;
      $GLOBALS['wp_styles']->base_url = $base_url;
    }
    

    Might Need to Adjust 'init' Hook Priorities if Conflicting Plugins

    Whichever your preference, I believe either of those two (2) 'init' hooks will work for you, but if you have plugins that use conflicting priorities you might need to lower the priority number to 0 or less with the first approach, or raise the priority above 100 for the second approach. Again I didn’t have enough use-case test scenarios to 100% verify so let me know if some use cases are not working for you.