Multisite inheritance question

I’ve been playing around with the multisite features (which are awesome btw) and noticed that each “child” site has its own plugins and themes directories. This makes total sense if you had a lot of different types of sites you were building and needed different versions of themes/plugins.

I’d love to know if it would be possible to configure Grav to have the child sites inherit themes and plugins from the root user folder. This would be ideal if you wanted to offer sites that all had the same features and theme choices, because it would allow us to update all themes/plugins in one central location and would avoid duplication of a ton of files over and over again when creating new sites. Thoughts? Is this something that could be set up using some Stream configuration in the setup.php file file?

Hey there, just troubleshooted this on gitter with @Sommerregen as i currently have som problems myself. But for the inheritance regarding themes he posted the snippet:

$themes = ["user/themes"];
if (is_dir(__DIR__ . "/user/sites/{$folder}/themes")) {
  array_unshift($themes, "user/sites/{$folder}/themes");
}

return [
    'environment' => $name,
    'streams' => [
        'schemes' => [
            'user' => [
               'type' => 'ReadOnlyStream',
               'prefixes' => [
                   '' => ["user/{$folder}"],
               ]
            ],
	    'themes' => [
               'type' => 'ReadOnlyStream',
               'prefixes' => ['' => $themes] 
            ]
        ]
    ]
];

It basically looks if there is a sub site theme folder present, else inherits the base.
But had no luck to make this work yet, not either site.dev/subsite/admin after login or subsite url like subsite.site.dev (instead of just subdirectory that works). If you got hints on any of these matters i would be delighted to hear! But basically the snippet maybe will get you going :slight_smile:

Thanks for posting that, I will check it out this evening and report back if I can get it to work. Were you able to get basic multisite working?

Ok hi! @getluke As you already figured out, the example in the docs uses separate folders for subsites. You can of course setup Grav in this way to share certain aspects/folder between each install. The docs is already giving you a hint how to do that. The easiest way of course is to use one of the examples setup_*.php and modify

return [
    'environment' => $environment,
    'streams' => [
        'schemes' => [
            'user' => [
               'type' => 'ReadOnlyStream',
               'prefixes' => [
                   '' => ["user/{$folder}", 'user'],
               ]
            ]
        ]
    ]
];

Since the plugins and themes streams are already defined to be user://plugins or user://themes, Grav will first look into to your subsite folder (user/subsite) and then fall back if not found to the general and shared default folder (user).

Concerning Admin it should be clear that only the setup_ subdomain.php work, since any path at the beginning would be interpreted as the name of a subsite though. If you do it right you should see all plugins and themes installed in the user folder, too.

Otherwise, please check whether Grav works with your subsite folder and/or with your root folder, before using both.

Thanks @Sommerregen for the additional info. I have thoroughly looked through the docs but maybe I missed something about theme inheritance. This also motivates me to learn more about streams because clearly they are extremely powerful ways to map paths.

I was hoping that this is the way things would work (look in child folder first then fall back to “root” user folder). Are you saying that the way to force the use of the general/shared default /user/themes and /user/plugins is to exclude the creation of these folders for the subsites? I didn’t think of that because the docs said that these folders were required, along with /pages and /config. I think what I tried was to have an empty /user/subsite/themes/ folder while keeping antimatter in the general /user/themes/ folder and Grav threw an error saying that antimatter didn’t exist.

I will try deleting the subsite themes folder tonight when I get home from work to see if that will force the fallback. Thanks for all the help!

@Sommerregen The system doesn’t seem to be falling back to the parent user folder’s themes or plugins when not found. If I rename the child /themes folder to _themes (to avoid fully deleting), the admin still works but the themes page is empty and shows (0) themes in the nav. If I do the same for /plugins the admin doesn’t load at all and outputs “Theme ‘antimatter’ does not exist, unable to display page.”

Thoughts? Do I need to explicitly set the themes and plugins folders to the parent in the setup.php array streams?

So sorry, I misunderstood…the fallback works with the code that @-j-j- posted, not out of the box with the default setup.php. Everything works now, thanks so much for your time! The streams concept is amazing.

The docs will explain allot. However I still have problems. @getluke, do you mind sharing your system.php so i can troubleshoot my code in a wider spectrum? Have not found anything yet as my subpage only inherit, but does still not override

Sure, posted below with some comments.

Hopefully @Sommerregen or someone else can help us with this one last piece of the puzzle:

Using the setup recommended (and my plugins addition) works great except for one thing: when I remove the plugins folder from the subsite it does appear to still pick up the global /user/plugins folder (the plugins page shows a list of the plugins) but all of the admin theme asset paths are wrong:

<link href="/css-compiled/nucleus.css?4023aed80d" type="text/css" rel="stylesheet" />

It appears that the {{theme_url}} used in the admin Twig templates is not getting set properly.

Here is the relevant portion of my setup.php, I replaced the docs code starting at the return statement:

// default paths for themes and plugins
$themes = ["user/themes"];
$plugins = ["user/plugins"];
// is there a themes folder in the subsite? if so, prepend default array with subsite themes path
if (is_dir(__DIR__ . "/user/{$folder}/themes")) {
  array_unshift($themes, "user/sites/{$folder}/themes");
}
// is there a plugins folder in the subsite? i f so, prepend default array with subsite plugins path
if (is_dir(__DIR__ . "/user/{$folder}/plugins")) {
  array_unshift($plugins, "user/sites/{$folder}/plugins");
}

return [
    'environment' => $environment,
    'streams' => [
        'schemes' => [
            'user' => [
               'type' => 'ReadOnlyStream',
               'prefixes' => [
                   '' => ["user/{$folder}"],
               ]
            ],
            // set theme path for subsite
            'themes' => [
              'type' => 'ReadOnlyStream',
              'prefixes' => ['' => $themes]
            ],
            // set plugins path for subsite
            'plugins' => [
              'type' => 'ReadOnlyStream',
              'prefixes' => ['' => $plugins]
            ]
        ]
    ]
];

Any help on the admin asset path issue would be appreciated.

One correction, the array_unshift lines should have the string user/{$folder}/themes like the line above. I changed this because in the docs the $folder variable already has the "sites/" in the path.

Hi @getluke and @-l-l-,

well done your setup.php looks quite good! However, concerning the last piece of puzzle I need more informations. Having multisites means having more troubles = need more infos. What do you mean, that the admin theme asset paths are wrong. Did you installed admin globally or in the subsite folder? What do you expect? Are you using directories for subsites or subdomains? Sorry, for asking so many questions, but those are crucial to find out a problem.

As far as I can see from admin.php#L450 is that the Twig variable theme_url is based on the resolution of the plugin:// stream. In other words I suggest adding this stream to the setup.php, too (like the way you already did for plugins and themes).

In crucial is that multi-site works well, but (and that’s the biggest caveat IMHO) every theme and plugin do things differently (using different streams, or even specific environment variables in order to work) and you have taken care of s uch subtleties. ATM not all plugins are using streams and some like e.g. Data manager hardcode paths. Even the core has some hardcoded paths, the logs for example but also the pages. However some are not that easy to replace (see issue https://github.com/getgrav/grav/issues/714 ).

Thank you both for info!
However cannot see any difference for my issues from testing your snippet @getluke, quite too bad actually :stuck_out_tongue: Noted the $folder var path too.
Also tested different scenarios like renaming themes/plugins folders in subsite and my result is:

For subsite/themes:
The main user/themes is always loaded, regardless if the subsite has the themes folder or not. Thinking if this is related to what you, @sommerregen said about streams as in my base template i got eg {% do assets.addCss('theme://css/style.css',100) %} and i trie to avoid hardcoded paths at all times. But honestly, became a little bit confused bout adding plugin:// or theme:// in setup.php like the way previously mentioned. In best case i get “Invalid resource” so pointers to correct stream setting would be awesome :slight_smile:

For subsite/plugins:
The subsite only gets the plugins from subsite/plugins, but not setting the path to user/plugins if the folder is not present in subsite directory. So kind of reverse here. Probably is related to setting the plugin:// stream correct, r ight?

And related to admin:
My admin assets are working when accessing it from subsite as the admin plugin is located in the subsite plugins. But when logging in i get redirected to site admin with “invalid security token” on dashboard. Ofc expecting to access subsite admin.

In my case i’m only testing subsite with directories as subdomain will not play at all.

In all, this is a brilliant function, I’m glad for every message posted so final will be less quirks for anyone else that stumble on same issues!

@Sommerregen, here are the specifics on the asset issue:

  • using subdomains
  • admin (and all plugins) are installed in the root /user/plugins folder, and the subsite does not have a plugins folder. My overall goal is to have all of my subsites share themes and plugins (actually share everything they can to avoid file duplication).
  • example of a bad asset path: <link href="*/css-compiled/nucleus.css" type="text/css" rel="stylesheet" />. *Note that the base path that should be here is totally missing so this breaks all asset links. The path should be /user/plugins/admin/themes/grav/css-compiled/nucleus.css

I will look into adding the plugin stream to see if that works. Hopefully all of this stuff @-l-l- and I are raising will help to iron out things for most multisite use cases.

@-l-l-, I’m sorry that I don’t know enough to help with your specific case. The only thing I can think of, are you sure your subsite folder is named properly? user/sites/.example.com, using the full subdomain as the folder name?

Indeed handy with the ironing!

Regarding {{theme_url}} for in your case css, as @Sommerregen mentioned many plugins & themes does things differently, like i use the theme:// for all assets possible, and therefore i think Grav looks towards my user/themes directly in every case. So when getting the streams overriding right this will probably make the scenario work, hopefully.

But here i’m a bit lost, and only way i could use the subpage theme for the subpage was to add direct paths (just for test) as

$theme = "user/themes/gallery";

if (is_dir(__DIR__ . "/user/{$folder}/themes")) {
	$theme = "user/{$folder}/themes/gallery";
}

and then add

   'theme' => [
      'type' => 'ReadOnlyStream',
      'paths' => [
         "{$theme}",
      ]
   ] 

in return [].
But it only works if i disable/rename the user/themes/gallery folder… So, a bit lost, tho whit a good feeling.

@getluke, as i’m testing subdirectories i named it as followng user/sites/subsite and containing all content related to a standard grav setup.

A thought, do you need to set the full subdomain name on the folder for testing subdomains? Sounds weird to have user/sites/subsite.example.comas a folder name when it all depends on the location.

Had a bit of progress.
When disabling subsite/config/system.yamlwhile using previous direct paths for $theme, i do not longer need to disable/rename the user/themes/gallery folder. :+1: Feels reasonable to not have two system config files. And when plugins stream works for me i hope that admin only needs to be added in root site :slight_smile: If i missed something else, pleas let me know.

Ok, so digged a bit in plugin:// and added:

$plugin = "user/plugins";

if (is_dir(__DIR__ . "/user/{$folder}/plugins")) {
	$plugin = "user/{$folder}/plugins";
}

Then in return []

'plugin' => [
    'type' => 'Stream',
    'paths' => [
        "{$plugin}",
    ]
]            

So, do not know if this is the way to go, but it now can check if subsite plugins folder is present/not present and do accordingly.
However this evolved to the question, if there is a possibility to itterate over active subsite plugins, to see if the plugin exists in subsite/plugins - else use site/plugins path for that plugin. Wold that be a handy thing to have?

Also, the same way regarding themes, like how Grav works with plugin assets override from theme - eg when you simply can override one plugin template file from a theme just by adding and edit it, but in this scenario override site themes/plugins from subsite in a very familiar manner!

@-l-l-
A thought, do you need to set the full subdomain name on the folder for testing subdomains? Sounds weird to have user/sites/subsite.example.comas a folder name when it all depends on the location.

Yep, I’ll bet that is related to your problem. The setup.php file actually looks for a folder with the exact same string as your full subdomain. So for example if your subdomain is sub.example.com, your folder should be users/sites/sub.example.com.

But i do not use subdomain, i use subdirectory - as subdomain do not play at all on my setup. Even tested your approach on subdomain just for fun and named the folder sub.example.com - even if i would never do this just because of too much renaming on deploy.

Hi @getluke and @-l-l-,

indeed (otherwise noted in the docs - a typo) you need the full domain as the sub-directory. BTW I troubleshooted your problem @getluke . I come up with the following setup.php (please not the additions; with them Admin plugin plays nicely):

<?php
/**
 * Multisite setup for subsites accessible via sub-domains.
 *
 * DO NOT EDIT UNLESS YOU KNOW WHAT YOU ARE DOING!
 */

use Grav\Common\Utils;

// Get subsite name from sub-domain
$environment = isset($_SERVER['HTTP_HOST'])
    ? $_SERVER['HTTP_HOST']
    : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost');
// Remove port from HTTP_HOST generated $environment
$environment = strtolower(Utils::substrToString($environment, ':'));
$folder = "sites/{$environment}"; 

if ($environment === 'localhost' || !is_dir(ROOT_DIR . "user/{$folder}")) {
     return [];
}

$themes = ["user/themes"];
if (is_dir(__DIR__ . "/user/{$folder}/themes")) {
    array_unshift($themes, "user/{$folder}/themes");
}

$plugins = ["user/plugins"];
if (is_dir(__DIR__ . "/user/{$folder}/plugins")) {
    array_unshift($plugins, "user/{$folder}/plugins");
}

return [
    'environment' => $environment,
    'streams' => [
        'schemes' => [
            'user' => [
               'type' => 'ReadOnlyStream',
               'prefixes' => [
                   '' => ["user/{$folder}"],
               ]
            ],
            'environment' => [
                'type' => 'ReadOnlyStream',
                'prefixes' => [
                    '' => ["user/{$folder}"],
                ]
            ],
            'themes' => [
                'type' => 'ReadOnlyStream',
                'prefixes' => [
                    '' => $themes
                ]
            ],
            'plugins' => [
                'type' => 'ReadOnlyStream',
                'prefixes' => [
                    '' => $plugins
                ]
            ],
            'plugin' => [
                'type' => 'ReadOnlyStream',
                'prefixes' => [
                    '' => ['plugins://']
                ]
            ]
        ]
    ]
];

@-l-l- For the moment you can’t use the sub-directory approach with the Admin plugin. this is because you have to rewrite the full URL before Grav is actually initialized. This can be done. To be honest I already have a powerful setup.php, which does this. However although this part is finished it needs to be more polished. My plan is once Admin Pro is out to release it as an addition. Then it will become easy to setup new sites with just a few clicks.

It also addresses the naming of the folders. The examples in the docs are just examples. If you don’t like them you can alter the way how the $environment variable is constructed.

1 Like