mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-16 16:45:07 +00:00
479dc61d2d
Logo and watermark belonged in the private package for one reason: that is where they were written. Nothing about them needs a hosted platform, and an installation wanting its own mark on the pages it serves is the ordinary case rather than the exotic one. They are core's now, and every installation has them. Hiding "Powered by ProjectSend" did not come. That is what a hosted customer pays for, and its gate is not a capability key but the absence of the code: cloud-modules keeps the listener, so an installation without that package holds the column and has nothing able to read it. Flipping an edition variable buys nothing, which was true before and stays true. Core renders the switch where Capability::AttributionHide is held and has no route that can save it -- there is a test asserting exactly that, which fails the day white-labelling quietly becomes free. The migrations move with their original filenames on purpose. A Cloud tenant already ran them under those names, so Laravel skips them there and the table and its data are untouched; a fresh install or a community one runs them from here for the first time. What got better on the way rather than merely moving: The watermark listeners take core's real RenderingImage and ResolvingImageRendering instead of duck-typed `object` payloads, and the tests construct the genuine events rather than anonymous stand-ins that imitated their shape. The package had to do it that way -- it builds with no host present -- so three PHPStan ignore entries existed to describe what the type system could not see. They are gone. ModuleBoundaryTest asserted "branding is cloud-only, and the suite runs as community", which was never what it was testing. It now reads the capability off the route and subtracts it, so the invariant holds for whichever module is installed. The 43 branding strings arrived in all sixteen locales from the package's own catalogues rather than being retranslated, and the package's are pruned to the one string it still uses. A hosted plan without branding subtracts branding.customize and attribution.hide from the instance's environment. The row is never deleted by that: a downgrade is usually an expired card rather than a decision, and wiping somebody's artwork over a billing event is a loss they would find weeks later with no way to know what it used to be. Hiding reverses; deleting does not.
83 lines
3.7 KiB
PHP
83 lines
3.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Modules\Platform\Branding;
|
|
|
|
use App\Modules\Api\Events\RegisteringApiModules;
|
|
use App\Modules\Files\Thumbnails\Events\RenderingImage;
|
|
use App\Modules\Files\Thumbnails\Events\ResolvingImageRendering;
|
|
use App\Modules\Platform\Branding\Models\BrandingSetting;
|
|
use App\Modules\Platform\Branding\Watermark\ThumbnailWatermarker;
|
|
use App\Modules\Platform\Capabilities\Capability;
|
|
use App\Modules\Platform\Capabilities\CapabilityRegistry;
|
|
use Illuminate\Support\Facades\Event;
|
|
use Illuminate\Support\ServiceProvider;
|
|
use Inertia\Inertia;
|
|
|
|
/**
|
|
* An installation dressed in its own logo, and a watermark on what its
|
|
* clients and visitors see.
|
|
*
|
|
* Lived in the private cloud-modules package until 2026-08-28, gated
|
|
* Cloud-only. That was a fact about where the code had been written
|
|
* rather than about who should have it: nothing here needs a hosted
|
|
* platform, and a self-hosted installation wanting its own mark on the
|
|
* pages it serves is the ordinary case rather than the exotic one.
|
|
*
|
|
* **What did not move.** Hiding the "Powered by ProjectSend" line is the
|
|
* white-label half, and white-labelling is one of the things a hosted
|
|
* customer pays for. Its listener still ships only in cloud-modules, so
|
|
* an installation without that package has no code able to answer "hide
|
|
* it" — flipping an edition variable buys nothing. This module carries
|
|
* the column, because it owns the table, and no way to set it.
|
|
*
|
|
* **What a plan withholds is a separate question.** A free hosted plan
|
|
* has branding subtracted from its environment, which the capability
|
|
* registry applies; see PROJECTSEND_CAPABILITIES_DISABLED. The row is
|
|
* never deleted by that, so a plan that lapses and resumes restores what
|
|
* the customer had rather than asking them to build it again.
|
|
*/
|
|
class BrandingServiceProvider extends ServiceProvider
|
|
{
|
|
public function boot(): void
|
|
{
|
|
// Registered unconditionally. The listeners ask whether branding
|
|
// is available each time they fire, so an edition change, or a
|
|
// plan change that subtracts the capability, takes effect on the
|
|
// next request rather than needing a restart.
|
|
// Through the module registry rather than routes/api.php, so the
|
|
// URL stays /api/v1/modules/branding/* exactly as it was when this
|
|
// shipped in a package. A caller's integration does not care which
|
|
// repository the code moved to, and moving the path would be a
|
|
// breaking change dressed up as a refactor.
|
|
Event::listen(RegisteringApiModules::class, function (RegisteringApiModules $event): void {
|
|
$event->register(
|
|
slug: 'branding',
|
|
routes: __DIR__.'/api-routes.php',
|
|
capability: Capability::Branding->value,
|
|
);
|
|
});
|
|
|
|
Event::listen(RenderingImage::class, [ThumbnailWatermarker::class, 'handle']);
|
|
Event::listen(ResolvingImageRendering::class, [ThumbnailWatermarker::class, 'resolve']);
|
|
|
|
// Gated like the screen that sets it. Without the capability there
|
|
// is no branding page to reach, so a row that outlived a gate
|
|
// change — a downgraded plan, a restored backup — would put
|
|
// somebody's logo on every page of an installation offering no way
|
|
// to see it, change it or take it off. Evaluated per request, so
|
|
// uploading a logo or changing plan takes effect on the next one.
|
|
Inertia::share('branding', fn (): array => [
|
|
'logo_url' => $this->available()
|
|
? BrandingSetting::query()->first()?->logoUrl()
|
|
: null,
|
|
]);
|
|
}
|
|
|
|
private function available(): bool
|
|
{
|
|
return $this->app->make(CapabilityRegistry::class)->has(Capability::Branding);
|
|
}
|
|
}
|