Instead of falling back to returning storage_path('app/public') in assetRoot(), throw an exception *at the start of the method* -- tenant assets shouldn't be served in central context.
Make it clear that both diskRoot and publicPath get the same prefix appended.
In the possibleTenantSymlinks() docblock, correct the array example (the values are not just 'disk root' anymore -- if the disk has a prefix, it will be appended to the configured root).
In removeLink(), return early if the symlink doesn't exist.
The nested directory deletion is now controlled by the $removeNestedDirectories static property. It's disabled by default.
public_path() and dirname($publicPath) are now normalized using realpath() before the nested dir deletion.
The delete loop now checks if the directory-to-be-deleted is *inside* the public root instead of checking if it's not equal to to the public root.
Disk prefixes are no longer ignored by tenants:link. possibleTenantSymlinks() now appends them to both the public path and the disk root.
CreateStorageSymlinksAction now creates parent directories for the symlinks in the public/ directory (e.g. for a disk with 'abc/def' prefix, the 'abc/def' subdirectory will be created inside public/<url_override>).
RemoveStorageSymlinksAction removes the directories that CreateStorageSymlinksAction creates for the symlinks.
On one hand, this test covered the TenantAssetController's fallback. On the other hand, using tenant asset routes in central context is not a valid use case (also, the fallback isn't exactly a new thing)
"test asset controller returns a 404 when accessing a file outside the storage root" tested very similar things to the new test (which had some redundant config anyway). Merged these tests into one -- "tenant asset controller only serves files inside the asset root"
"adding a scoped disk to tenancy.filesystem.disks throws an exception if its base disk is not listed" doesn't use a dataset and deal with inline baes disks anymore.
Added a separate test for scoped disks with inline base ("adding a scoped disk with an inline base disk to tenancy.filesystem.disks throws an exception").
Removed the "adding a scoped disk to tenancy.filesystem.disks has no effect on the disk when its base disk is listed too" test, it was mostly redundant.
Use the *original* 'tenant storage gets deleted during tenant deletion when the DeletingTenant pipeline contains DeleteTenantStorage' test and remove what's not necessary anymore. Also make it clear that enabling FS bootstrapper is not required for the deletion to work -- the tenant directory just has to exist.
Delete the nonsensical 'DeleteTenantStorage does not delete the central storage directory when the filesystem bootstrapper is disabled' test. That one was there to test that the central dir never gets deleted, but it was wrong. Added 'DeleteTenantStorage never deletes the central storage directory' which actually makes the job's realpath() comparison check pass and the job just returns.
Refrain from dealing with the impossible "self-referencing" scoped disk case. Instead of that, test the inline parent behavior.
Also update the exception message in forgetDisks() so that it's a bit less vague.
In FSBootstrapper::forgetDisks():
- `tenancy.filesystem.disks => ['scoped']` throws
- `tenancy.filesystem.disks => ['scoped', 'parent']` does NOT throw
- `tenancy.filesystem.disks => ['scoped_with_scoped_parent', 'scoped_parent']` (invalid config where a scoped disk's base disk doesn't actually exist because the scoped disks just reference themselves) throws
At first glance, it could look weird that $attemptedPath uses "/" but the check in abortIf below uses DIRECTORY_SEPARATOR. Add comment that explains this.
If diskRoot() somehow ended up receiving a scoped disk (e.g. in case the scoped disk was listed in tenancy.filesystem.disks), its root would get configured, and it'd be completely redundant. It wouldn't break anything since scoped disk's configured root is ignored -- its parent's root is always used. Even though not adding this skipping code would essentially do no harm, it prevents the method from doing redundant work and defines the behavior a bit more clearly.
diskUrl() is similar in that regard, but that method already has a strict "disk driver has to be 'local'" -- scoped disks never made it through so nothing to change there.
This includes moving the TenantAssetController baseDiskName() method to FSBootstrapper and making it public static, since the same logic is used in two places now. Also cover the edge case where a scoped disk A has a scoped disk B as its parent, and B has A as its parent -- in that case, the method would be stuck in an infinite loop (also added separate test for this, commenting out the $visited-related code in baseDiskName will make the test fail).
Also updated the assetRoot's unnamed disk exception message.
The test fails with the nested disk dataset because we resolve a disk first, then initialize tenancy, and because the nested scoped disks aren't forgotten, so the disk config changes that the FS bootstrapper applies aren't reflected on the already-resolved disk instance.
Instead of just saying that the publicDisk *should* be listed in tenancy.filesystem.disks, enforce that -- if the disk isn't tenant-aware, throw an exception.
Also update comments accordingly. E.g. since scoped disks don't have to have a single "parent disk" (the parent can also be a scoped disk and have another parent, and so on), use "base disk".
Since the tenant storage path can now be grabbed using FilesystemTenancyBootstrapper::getBoundTenantStoragePath(), the log bootstrapper doesn't need to depend on the FSBootstrapper being enabled and storage_path() being suffixed.
Instead of adding a regression test, just delete FilesystemTenancyBootstrapper from the config settings in the log bootstrapper tests (and in tests that did use storage_path() in tenant context assertions, use explicitly "hardcoded" paths instead).
Also, instead of throwing the "no root path configured" exception, just throw an exception if the disk is not local (i.e. is not instanceof LocalFilesystemAdapter). A local disk HAS to have a string root, otherwise, Laravel throws an exception while instantiating that disk.
Test that tenant assets can be served from scoped disks, and that tenant asset roots respect the disk's configured prefix.
Currently, the tests fail because TenantAssetController grabs the root from the config instead of resolving it via Storage::disk(...)->path('').
The docblock said that the FSBootstrapper was required for this job to work at all, but that's not fully true since the job just uses the FSBootstrapper's public static methods to get the storage paths.
Correct misleading ones, add ones that were missing (e.g. the TenantAssetController's docblock, the FSBootstrapper dependency should be mentioned there)
Briefly document the root_override placeholders, make the links point to v4 docs instead of the v3 ones. Also in the url_override comments, mention that local disks must have a valid root in order for the override to work correctly.
Previously, we only skipped disks with `null` override. But an override with an empty string is also incorrect, and simply checking if $this->app['config']["tenancy.filesystem.url_override.{$disk}"]) is falsy instead of strictly null takes care of that.