This PR replaces the legacy Zend-based login form and authentication
controller with their ipl equivalents, and extracts the login page view
script markup into a reusable widget.
### Convert `LoginForm` to `CompatForm`
`LoginForm` now extends `CompatForm`, replacing
`init()`/`createElements()` with `__construct()`/`assemble()` and Zend
decorator arrays with inline ipl decorator objects. The
`CsrfCounterMeasure` and `FormUid` traits are added.
`getRedirectUrl()` is renamed to `createRedirectUrl()` to avoid
overriding `CompatForm::getRedirectUrl()`, using `hasBeenAssembled`
instead of `$this->created` and `str_contains()` instead of `strpos()`.
`onSuccess()` is rewritten for the CompatForm lifecycle: void return,
explicit `setRedirectUrl()` call, `Icinga::app()->getResponse()` instead
of `$this->getResponse()`, and `addMessage()` instead of `addError()`.
### New `LoginPage` widget to replace view script
Extracts the login page structure (logo, footer, social links,
decorative orbs) from `login.phtml` into a new `LoginPage` widget
extending `HtmlDocument`, so the markup is defined in one place and no
longer lives in a view script. Additional login buttons provided by
`LoginButtonHook` are now grouped in a `div.login-buttons` flex
container, rendered only when buttons are actually present.
### Convert `AuthenticationController` to `CompatController`
`AuthenticationController` now extends `CompatController`, dropping
`login.phtml` in favour of `LoginPage` and `addContent()`. The form is
built with an `ON_SUBMIT` handler for the redirect and an `ON_REQUEST`
handler delegating to `LoginForm::onRequest()`. Uses
`$this->getServerRequest()` instead of `ServerRequest::fromGlobals()`.
### CSS / JS fixes
- `login.less`:
- `height: 100%` -> `100vh` on `#login` (now a grandchild of `#layout`
via `.content`)
- `.login-buttons` styled as a flex column with a row gap and top margin
- per-`li` error styling, including spacing inside `.login-buttons`
- `.icinga-form` width and control-group spacing
- `align-items: center` on `.remember-me-box`
- label `margin-right` reset for the disabled-state description
- `history.js`: `#layout > #login` -> `#layout #login` (direct-child
selector broke with the new nesting)
- `login-orbs.less`: fixes long-standing typo `#orb-notifactions` ->
`#orb-notifications`
Ensure `.errors` inside `.login-buttons` has no extra padding and that
the last error item retains a bottom margin, matching the layout used
for the primary login form errors.
Adjust login.less to match the HTML structure emitted by CompatForm:
- Add width: unset and margin on `.icinga-form` inside `.login-form` so the ipl
form does not overflow its container and control groups have spacing
- Restyle `.errors` at the li level rather than the container level, giving each
error message its own red box with margin between them
- Remove the padding from `.errors`/`.form-errors` that was set at the container
level (now on each li)
- Change `.remember-me-box` align-items from `flex-start` to `center` so the
toggle switch and label sit at the same baseline
Also fix a pre-existing typo in `login-orbs.less`: `#orb-notifactions` ->
`#orb-notifications`
Convert `AuthenticationController` from the legacy Zend Controller to
`CompatController`, dropping `login.phtml` in favour of the new `LoginPage`
widget and `addContent()`. The view variable assignments are replaced by
`setTitle()` and `addContent(new LoginPage(...))`. Improve `httpBadRequest()`
message for external redirect attempts.
In `CompatController`, `$this->controls` is the tab bar area rendered
above the page content. When no tabs are added it still emits an empty
`<div class="controls">` wrapper. Setting `$this->view->compact = true`
suppresses that wrapper entirely, keeping the login page markup clean.
Handle the redirect on success in an `ON_SUBMIT` event handler. Delegate
the external-backend-only check to `LoginForm::onRequest()` via `ON_REQUEST`.
Use `$this->getServerRequest()` instead of `ServerRequest::fromGlobals()`.
Two structural fixes required by the changed DOM nesting:
- `login.less`: height `100%` -> `100vh` (`#login` is now inside `.content`
which has no explicit height, so percentage inheritance breaks)
- `history.js`: `#layout > #login` -> `#layout #login` (direct-child selector
breaks because `#login` is now a grandchild of `#layout` through `.content`)
Extract the login page structure (logo, footer, social links, decorative orbs)
from `login.phtml` into a reusable ipl-html widget. Extending `HtmlDocument`
rather than `BaseHtmlElement` lets it emit `#login` and the seven `.orb` sibling
divs without a wrapping root tag.
Both the login action and the upcoming 2FA challenge action can now share the
same visual scaffolding without duplicating markup and without using view
scripts anymore.
Replace the legacy Zend-based `LoginForm` with an ipl `CompatForm`-based form:
- Add `CsrfCounterMeasure` and `FormUid` traits
- Replace `init()`/`createElements()` with `__construct()´/`assemble()` using
inline ipl decorator objects instead of Zend decorator arrays
- Replace `getRedirectUrl()` with `createRedirectUrl()` because
`getRedirectUrl()` would override `ipl\Html\Form::getRedirectUrl()`
- use `hasBeenAssembled()` instead of `$this->created`
- use `str_contains()` instead of `strpos()`
- Rewrite `onSuccess()` for `CompatForm`:
- change visibility from `public` to `protected`
- void return type
- use `Icinga::app()->getResponse()` instead of `$this->getResponse()`
- use `addMessage()` instead of `addError()`
- call `setRedirectUrl()` explicitly
This tracks the user's interactions by observing the events
`keydown`, `paste` and `change` to detect changes to forms
inside a modal. Upon any change, the modal cannot be closed
anymore by pushing Escape or clicking outside the modal.
Instead, the modal will *wobble* for a short period.
resolves#5307
The native attribute `autofocus` is nowadays the better alternative to
our legacy `.autofocus` implementation. Also this was seldomly used
elsewhere. Except in the director, which [handles it
manually](5fc103ffc3/public/js/module.js (L713))
anyway. It is now gone for good.
In case we ever need something similar, we should re-introduce it but
properly integrate it with the default focus handling. But now it's not
the time and no real need for this.
A small bonus of this change is that error messages are now copyable to
clipboard with a single click.
The related issue is fixed because our default focus preservation now
doesn't kick in because the login form uses the native attribute on the
username field.
fixes#5503
If a form uses the native autofocus, it seems it wants to guide
the user. So the first visit respects the autofocus, so the
element is either still focused upon submit or another one.
Either way, the default focus preservation ensures that whatever
is in focus will retain it if necessary and if not, the content
will probably fundamentally change and no force focus is required.
With the only remaining cases, login and setup, this is true.
Another upcoming case is the 2FA flow, where it is true as well.
It didn't work anyway and is now obsolete given that there's
a `autofocus` attribute widely supported by browsers. The catch
however is, that this only works upon page load. But all still
relevant cases where autofocus is suited are in fact page loads.
In case we ever need something similar, we should re-introduce
it but properly integrate it with the default focus handling.
But now it's not the time and no real need for this.
There are none anymore and the implementation is going
to be removed from the framework until we have a better
idea how or whether to still support autofocusing.
Hopefully users will now always include the first message
in reports and not only the stacktrace
Hopefully users will now always include the first message
in reports and not only the stacktrace……
Besides that this is a destructive operation which shouldn't
get automatic focus… our native way to adhere to WCAG standards
is by focusing the newly rendered container which suffices.
Because the in-tree Less visitor requires `wikimedia/less.php`, that
dependency has to live in `icinga-php-thirdparty`. `icinga-php-library`
already carries Less-related code with the same dependency, so this PR
consolidates everything into `icinga-php-library` and removes the
duplicate in-tree implementation.
- Remove the in-tree `Icinga\Less\` visitor layer in favor of the shared
`ipl\Web\Less` library, which maintains modernized equivalent CSS var
replacement and light-mode functionality.
- Strip unused variable-export support from `LessCompiler`. The feature
was never used by any our modules or community modules.
- Update `LessCompiler` to delegate to `ipl\Web\Less`, assembling
sources via `@import (less)` directives; pass the `$minify` flag
directly to `render()` instead of via a separate `compress()` call.
- Modernize `LessCompiler` with PHP type declarations, short array
syntax, and tightened PHPDoc.
Here is the test summary:
The stylesheets are nearly identical. Most changes are normalization
noise introduced by the new version of the wikimedia Less parser:
- Decimal canonicalization: `.5` → `0.5`, `2.0em` → `2em`
- Hex color shortening: `#dddddd` → `#ddd`
- Hex color case kept as is: `#535353` → `#535353`, `#c4c4c4` → `#c4c4c4`
- `calc()` math preserved: `calc(14.28571429%)` → `calc(100% / 7)`
Starting with Less.php 5.0, arithmetic is no longer evaluated inside
`calc()`. This required changing one occurrence in our Less files
directly; no other changes were necessary, and no known publicly
available Icinga Web module is affected by similar changes in this
regard.
There is one functional difference: the light-mode background color
gains an extra level of indirection via a new CSS var call (because it
uses `@light-body-bg-color`):
```diff
- --body-bg-color: #f5f9fa;
+ --body-bg-color: var(--light-body-bg-color, #f5f9fa);
```
The new CSS var replacement visitor from `ipl\Web` runs independently
from the light mode call visitor, and therefore also adds var overrides
to such detached rulesets. Everything else is normalization noise.
Previously, `addLessFile` and `addModuleLessFile` called `realpath()` but
skipped the readability check, silently appending `false` when a path did
not resolve. `setTheme` and `setThemeMode` checked readability but stored
the unresolved path and swallowed errors via `Logger::error`.
`resolveReadableFile()` centralizes both steps: it calls `realpath()` and
verifies the result is a readable file, throwing a `RuntimeException` on
failure so no invalid path can be registered silently.
Starting with Less.php version 5.0, arithmetic is no longer evaluated
inside `calc()` the way this code relied on[^1]. Compute the division in
a Less variable first, then use that value in `calc()` and drop the
unnecessary escaping.
[^1]: https://gerrit.wikimedia.org/r/c/mediawiki/libs/less.php/+/1049149
Add missing PHP type declarations to properties and methods, replace long-form
`array()` syntax with short `[]` syntax, tighten PHPDoc comments, and correct
"LESS" to "Less".
Replace the in-tree `Icinga\Less\` visitor layer with its equivalents
from `ipl\Web\Less`, where the same color-variable and light-mode
functionality is maintained as a shared library.
`LessCompiler` is updated to delegate to `WikimediaLessCompiler`,
assembling source via `@import (less)` directives and accepting a
`$minify` flag directly on `render()` rather than through a separate
`compress()` call.
This reverts commit 7c58b3ced1.
Remove variable exports as they were never used in our modules or community
modules, and using this feature is discouraged.
The `monitoring` module was removed in v2.13.0 and its references
remained in the docs. Remove these references and replace them with
`icingadb` where appropriate.