Short version

There seems to be some ambiguity about the meaning of the $base_path global which has led to incorrect usage elsewhere in the code. Certain of these misuses cause site problems when the site's domain-relative base URL does not match the site's public_html-relative file path.

I have some preliminary recommendations about how to fix this, but would like input from more experienced Drupal coders. (If there has been previous discussion of these issues, please point me at it; I searched briefly but was not able to find any.)

Exposition

After getting Drupal working within a sub-folder of my domain's public_html folder (http://domain.name/d) for testing and preliminary setup, I wanted to move it to the root (http://domain.name) for production. However, the hosting service used by my customer is set up in such a way that I didn't want to have Drupal's actual files right under the public_html folder; I wanted to keep Drupal in a /drupal sub-folder and use .htaccess to serve them from there.

[exposition about why this was needed -- ignore if uninterested]

The reason for this is that the hosting service inexplicably treats one domain as the "master domain" and all the others as "add-ons". The aspect of this which causes problems for Drupal (or pretty much any complex web application) is that the folders for the "add on" domains are all directly underneath the "master domain"'s public_html, like this:

  • /home/myaccount/public_html/ - "master domain" public HTML goes here
    • /home/myaccount/public_html/add-on-1.com/ -- "add-on-1.com" public HTML goes here
    • /home/myaccount/public_html/add-on-2.com/ -- "add-on-2.com" public HTML goes here

...so if I put Drupal's files directly in /home/myaccount/public_html/, all of Drupal's folders would be mixed in with the folders for the various add-on domains, which struck me as likely to create technical debt (aka "a mess").

[/exposition]

So I got that working (after some considerable futzing around with .htaccess) -- Drupal was displaying properly at http://domain.name while its files were actually in public_html/drupal -- but then I found that whenever I submitted a form, I got an error message:

Redirects to external URLs are not allowed by default, use \Drupal\Core\Routing\TrustedRedirectResponse for it.

I searched for this message (and pieces thereof), and found several posts which included it but none that addressed my problem -- so I went digging in the code.

What I found is that the global $base_path, which is derived from the path to the current script (e.g. my site was setting it to "/drupal/"), was being used to construct what Drupal presumed to be the site's base URL (http://domain.name/drupal), which was then being compared to the requested redirect URL (http://domain.name/admin/something) to see if the redirect was "internal" or "external" -- and because /admin/something is clearly not inside /drupal, it decided the redirect was "external" and raised the appropriate exception.

So I went in and made two changes to my local copy of the Drupal code -- code is posted here because this editor seemed to be having issues with the TABs in the paste. (Note that I'm not recommending this as a patch yet; it just works.)

In short, the existing code tries to guess the base URL from the script's filename -- which normally works, but fails when the site's base folder is being modified by .htaccess. So I created a $base_path_override global -- if it is set, $base_url is calculated from protocol://domain.name/$base_path_override rather than by the usual method. Then I set $base_path_override to '/' in the site's settings.php.

On further investigation, I also found that $base_path is being used to construct URLs in a number of places. Again, this breaks if Drupal's location is being aliased; links should always be based on $base_url, which is (after this modification) set correctly regardless.

Preliminary Recommendations

1. I'm not sure that there is any legitimate use for $base_path as a global. It seems to be returning the public_html-relative path to the Drupal installation -- which might be useful when constructing complete filenames for upgrading modules, but I can't see how it is useful by itself. All uses of this as a global should be reviewed to see if they don't mean to be retrieving $base_url instead (from a brief search, at least one instance should be).

2. De-globalize $base_url as well. There is a base_path() function which returns the $base_path global; this is a slight improvement over having a global, but still requires using the global to set the value. The minimal change I would recommend is to have global [get|set]_base_url() functions (and, if there are legitimate uses for $base_path, [get|set]_base_path()). Better yet would be to have these accessed via static methods in a global Settings or App class (maybe something suitable already exists?) which could then encapsulate URL/path calculations.

3. Change the function names to make it clearer what is being requested or set. Suggestions:

  • [get|set]_site_base_web_url_absolute_noslash()
    • scheme://domain.name for Drupal in the root web folder
    • scheme://domain.name/subfolder for Drupal in a subfolder
  • [get|set]_site_base_file_path_absolute_noslash()
    • /home/myaccount/public_html if Drupal is immediately in the public folder
    • /home/myaccount/public_html/drupal if Drupal is in the "drupal" folder
  • [get|set]_site_base_file_path_from_domain_wslash()
    • / if Drupal is at scheme://domain.name
    • /drupal/ if Drupal is at scheme://domain.name/drupal

$base_path currently seems to mean what I'd call [get|set]site_base_path_from_domain_wslash(), and $base_url seems to mean [get|set]site_base_web_url_absolute_noslash(), so I'd start by renaming those two throughout the code -- although as I noted earlier, there seem to be some mistaken uses of $base_path and I'm not sure if there are any legitimate ones.

4. I'd be kind of surprised if Symfony (which I gather is now used by Drupal) doesn't have some classes for managing URL construction and deconstruction. Drupal should be using those where possible.

(Posted in more haste than I'd like -- I've spent about 2 hours putting this together, but it is a complex issue and I would have preferred having more time to research it. Hopefully it's useful anyway. Thanks.)