Cropping is optional: an upload is used whole until somebody crops it.
A crop is a new file cut from the upload with SimpleImage, which the
watermark already uses; the upload is kept (logo_original_path) with
the box (logo_crop), so cropping again starts from the whole picture and
restoring points back at the upload. A box covering the whole image is
a restore. The box must lie inside the image, and an image over 25
million pixels is refused before GD decodes it.
A new upload, or removing the logo, deletes both files.
The sign-in, password reset, setup and share-link pages drew a custom logo
48 pixels tall, so a square logo was a 48x48 stamp. It now gets a box 80
pixels tall and up to 240 wide, so a square logo reads and a wide one still
fits a phone. ProjectSend's own logo is unchanged.
The site name appeared nowhere on those pages except as the image's alt
text. A new switch on Branding → Logo prints it under the logo. It is off
by default, because many logos already say the name and would then say it
twice. It is stored on the branding row, gated like the rest of the
screen (staff, edit_settings, branding.customize), and a withheld
capability takes it off the pages along with the logo. The branding API's
logo endpoint reports it as show_site_name.
The Logo tab now says what size to use and which formats are accepted.
SVG stays refused: it can carry script, and the logo is served from the
site's own origin. The crop tool the issue also asks for is not part of
this.
Reported by @jiits (#1798)
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.