Skip to content

build(deps): bump symfony/cache to ^8.0#2

Merged
eilandert merged 3 commits into
masterfrom
deps/symfony-cache-8
Jun 5, 2026
Merged

build(deps): bump symfony/cache to ^8.0#2
eilandert merged 3 commits into
masterfrom
deps/symfony-cache-8

Conversation

@eilandert
Copy link
Copy Markdown
Owner

Splits the safe half out of Dependabot #1.

What

  • Widen symfony/cache constraint to ^6.4 || ^7.0 || ^8.0 (lock: v7.4.13 → v8.1.0).
  • doctrine/orm stays on 2.x (2.20.13) — deliberately not bumped.

Why split

Dependabot #1 grouped this with doctrine/orm ^3.6. ORM3 removes APIs the codebase still uses (EntityManager::create(), set*CacheImpl(), Doctrine\\Common\\ClassLoader, old XmlDriver ctor) — would fatal at boot. That migration is deferred; this PR takes the risk-free cache bump for the longer support window (8.x is the current release line).

Impact

symfony/cache only backs Doctrine's PSR-6 metadata/query/result caches via DoctrineProvider::wrap() in OSS_Resource_Doctrine2cache. ApcuAdapter / RedisAdapter / ArrayAdapter are unchanged across 6/7/8 → no code change. php >=8.4.1 already required, satisfies symfony 8 floor.

Verified locally (PHP 8.4)

  • composer validate --strict
  • composer update symfony/cache --with-all-dependencies → 1 update, orm untouched ✅
  • php -l across application/library/public/bin ✅
  • composer audit — no advisories ✅
  • All 4 cache classes resolve under v8.1.0 ✅

eilandert added 2 commits June 5, 2026 14:09
Widen the constraint to allow symfony/cache 8.x (locked at v8.1.0) while
keeping doctrine/orm on 2.x. The Dependabot php-deps group bundled this
bump with doctrine/orm ^3.6, which removes ORM2-only APIs the codebase
still relies on (EntityManager::create, set*CacheImpl, Common\ClassLoader);
that part is deferred to a dedicated migration.

symfony/cache only backs Doctrine's PSR-6 metadata/query/result caches via
DoctrineProvider::wrap() in OSS_Resource_Doctrine2cache. The ApcuAdapter,
RedisAdapter and ArrayAdapter classes are unchanged across 6/7/8, so no
code change is required. php >=8.4.1 is already required, satisfying the
symfony 8 PHP floor.

Verified: composer validate --strict, php -l, composer audit, and class
resolution of all four cache classes under v8.1.0.
ORM 3.x removes APIs the codebase relies on; the grouped major bump would
fatal at boot. Ignore semver-major for doctrine/orm so Dependabot stops
re-proposing ORM3; minor/patch still flow.
@eilandert eilandert merged commit 649d60d into master Jun 5, 2026
9 of 10 checks passed
@eilandert eilandert deleted the deps/symfony-cache-8 branch June 5, 2026 12:44
eilandert added a commit that referenced this pull request Jun 6, 2026
First foundational slice toward dropping the ZF1 bootstrap (WALL #2,
docs/ZF1-REMOVAL.md). The native Container has until now reused the merged
options array the ZF1 bootstrap built; to stand the kernel up without
Zend_Application it must read application.ini itself.

src/Kernel/Config/IniConfig.php reproduces exactly the three transforms the
ZF1 INI config applied and nothing else:
  - section inheritance ([child : parent], single parent, transitive)
  - dotted-key nesting (a.b.c = v -> ['a']['b']['c'])
  - constant concatenation (APPLICATION_PATH "/x") delegated to PHP's NORMAL
    INI scanner, which also gives ZF1's boolean coercion (true->'1', false->'')

Output is value-for-value identical to getOptions(), so the Container and every
native controller read it unchanged. INERT: nothing wires it yet (mirrors how
each prior wall started with an inert core).

tests/test-kernel-config.php (18 asserts: nesting, inheritance, transitive
chain, constant expansion, bool coercion, error cases, + a smoke test against
the shipped application.ini.dist) wired into the regression unit job. Guard
green (src/ stays zero-Zend_).
eilandert added a commit that referenced this pull request Jun 6, 2026
* WALL #2 slice 2: native Doctrine EM factory (inert)

Second foundational slice toward dropping the ZF1 bootstrap (WALL #2,
docs/ZF1-REMOVAL.md). The native Container has until now reused the EM the ZF1
`doctrine2` resource built; to stand the kernel up without Zend_Application it
must build the same EM itself, from the same options array (now produced
framework-free by IniConfig, slice 1 #74).

src/Kernel/Doctrine/EntityManagerFactory.php is a verbatim framework-free port
of OSS_Resource_Doctrine2 + OSS_Resource_Doctrine2cache, minus the
registry/logger side effects that only made sense inside the framework:
  - create(): Configuration + XML metadata driver over xml_schema_path + proxy
    dir/namespace/autogen + ORM 2.x EntityManager::create() over the connection
    options. Connection-lazy (no DB touched until first query).
  - buildCache(): PSR-6 pool (Apcu/Redis/per-request Array) wrapped by
    DoctrineProvider, degrading to Array when an extension/server is absent.
  - registerEntityAutoloaders(): PSR-0 loaders for Entities/Repositories
    (not in Composer's map; the resource pushed Doctrine ClassLoaders onto the
    ZF1 autoloader). Proxies stay with Doctrine's own ProxyFactory.

INERT: nothing wires it yet; the native bootstrap (a later slice) will, where
it gets full runtime validation against the dev MariaDB.

tests/test-kernel-em-factory.php builds a lazy EM from a synthetic config
against the real XML mapping dir and asserts the wiring (EM type, XML driver,
proxy settings, cache, a real Admin metadata load, autoloader). Added to the
cache-wiring CI job (which has vendor + doctrine/orm); added the simplexml
extension that job needs for the XML driver. The driver-dependent asserts skip
gracefully where simplexml is absent (apcu-skip idiom). Guard green.

* fixup: register entity autoloaders before metadata-load assert
eilandert added a commit that referenced this pull request Jun 6, 2026
Third foundational slice toward dropping the ZF1 bootstrap (WALL #2,
docs/ZF1-REMOVAL.md). Throughout the migration the native side reached
per-session UI state through the legacy ZF1 namespace object the bootstrap
built (Container::session(), templates reading $session->domain, the Auth
identity bridge wrapping the auth namespace in MagicPropertyStorage), all via
magic-property access over a slice of $_SESSION.

src/Kernel/Session/SessionNamespace.php provides that shape natively: a
magic-property view of $_SESSION[$namespace] (__get/__set/__isset/__unset), so
$ns->domain reads/writes $_SESSION['Application']['domain']. It drops straight
into both call sites once the ZF1 bootstrap is gone:
  - Container::session() -> new SessionNamespace('Application') (templates +
    FlashMessages keep the same $_SESSION['Application'][...] keys);
  - Auth bridge -> new MagicPropertyStorage(new SessionNamespace($authNs)).

Because it exposes data through magic properties it satisfies
MagicPropertyStorage's object contract with no adapter, so the security/auth
services stay unchanged. INERT (no call site rewired yet).

tests/test-kernel-session-namespace.php (14 asserts: read/write/isset/unset,
namespace isolation, the MagicPropertyStorage wrap that the Auth bridge will
use, default namespace) wired into the regression unit job. Guard green.
eilandert added a commit that referenced this pull request Jun 6, 2026
Fourth foundational slice toward dropping the ZF1 bootstrap (WALL #2,
docs/ZF1-REMOVAL.md). The native controllers render through
Container::getResource('smarty'), which until now returned the ZF1
OSS_View_Smarty — a Smarty-5 wrapper that only extends the ZF1 view base for
the framework's view-renderer integration (which native rendering never uses).

src/Kernel/View/SmartyView.php reproduces exactly the part the kernel relies on,
WITHOUT the framework base, so the native bootstrap can build a view with no
ZF1 application present. Faithful subset of OSS_View_Smarty:
  - same \Smarty\Smarty engine with setEscapeHtml(true);
  - same bare-function modifiers (strlen/count/in_array/is_array) so the
    existing templates compile under Smarty 5;
  - template/compile/cache/config dirs + the OSS plugin dir from options;
  - __set($k,$v) -> assign (the shape AbstractController::view() uses);
  - render($name) -> fetch(resolveTemplate($name)) with the same skin lookup
    (a _skins/<skin>/ copy wins over the default).
  - fromOptions() reads resources.smarty.* like the ZF1 smarty resource.

INERT: nothing wires it yet; the native bootstrap slice will, where the full
chrome/OSS-plugin render is image-validated.

tests/test-kernel-smarty-view.php (8 asserts: magic-set+render, auto-escape,
compile-dir creation, skin override/fallback, unknown-skin throw, fromOptions)
renders real templates through the real Smarty engine; added to the cache-wiring
CI job (vendor present). Guard green.
eilandert added a commit that referenced this pull request Jun 6, 2026
Fourth foundational slice toward dropping the ZF1 bootstrap (WALL #2,
docs/ZF1-REMOVAL.md). The native controllers render through
Container::getResource('smarty'), which until now returned the ZF1
OSS_View_Smarty — a Smarty-5 wrapper that only extends the ZF1 view base for
the framework's view-renderer integration (which native rendering never uses).

src/Kernel/View/SmartyView.php reproduces exactly the part the kernel relies on,
WITHOUT the framework base, so the native bootstrap can build a view with no
ZF1 application present. Faithful subset of OSS_View_Smarty:
  - same \Smarty\Smarty engine with setEscapeHtml(true);
  - same bare-function modifiers (strlen/count/in_array/is_array) so the
    existing templates compile under Smarty 5;
  - template/compile/cache/config dirs + the OSS plugin dir from options;
  - __set($k,$v) -> assign (the shape AbstractController::view() uses);
  - render($name) -> fetch(resolveTemplate($name)) with the same skin lookup
    (a _skins/<skin>/ copy wins over the default).
  - fromOptions() reads resources.smarty.* like the ZF1 smarty resource.

INERT: nothing wires it yet; the native bootstrap slice will, where the full
chrome/OSS-plugin render is image-validated.

tests/test-kernel-smarty-view.php (8 asserts: magic-set+render, auto-escape,
compile-dir creation, skin override/fallback, unknown-skin throw, fromOptions)
renders real templates through the real Smarty engine; added to the cache-wiring
CI job (vendor present). Guard green.
eilandert added a commit that referenced this pull request Jun 6, 2026
… UI) (#78)

The keystone of WALL #2 (docs/ZF1-REMOVAL.md): the kernel can now run with the
ZF1 application ABSENT. The four inert builders (#74 config, #75 EM, #76 session,
#77 view) are assembled into a ready Container WITHOUT Zend_Application.

- src/Kernel/Bootstrap.php — framework-free boot(appPath, env, authNs): load
  options via IniConfig, register entity autoloaders, configure + start the PHP
  session, build the EM, Smarty view, session namespace and Auth identity
  bridge, return a Container. Pure baseUrl()/skinCss() from $_SERVER.
- src/Kernel/NativeResources.php — presents the EM/view/session through the same
  getResource()/getOptions() shape the Container expected from the ZF1 bootstrap,
  so Container/Dispatcher/controllers are reused unchanged.
- public/index.php — new VIMBADMIN_NATIVE_BOOTSTRAP=1 path (default off) that
  boots natively and serves the UI with NO Zend_Application; the non-UI tail
  falls through to the ZF1 path. A thin Zend autoloader + the residual
  Zend_Registry/front-controller-base-URL shims the OSS template helpers still
  read (genUrl) live here, in the ZF1-aware entry point, so Bootstrap stays pure.

Two gotchas found + fixed via the image harness:
- configureSession(): must apply resources.session.* (save_path=var/session,
  name=VIMBADMIN3, cookie flags) before session_start(). PHP's default
  /var/lib/php/sessions is not readable by the FPM user on the locked-down
  container, so a default session_start silently lost the identity between
  requests.
- registerEntityAutoloaders() must run BEFORE session_start(): the identity
  array in the session holds an Entities\Admin object; session_start unserialises
  it immediately, so the class must be loadable or it rehydrates as
  __PHP_Incomplete_Class and admin/list's $identity.user->getId() fatals.

IMAGE-VALIDATED (vimbadmin:dev, VIMBADMIN_NATIVE_BOOTSTRAP=1): login 302 + session
persists; domain/admin/mailbox/alias/log/queue/archive/maintenance lists + index/
about all 200 with the Logout chrome; csrf-masked diff vs the native-kernel
baseline IDENTICAL on all 8 lists (only wall-clock timestamps differ). EM
read+write + flash (login-error alert) exercised. Zero fatals.

tests/test-kernel-bootstrap.php (9 asserts: baseUrl derivation, NativeResources
shape) in the regression unit job; full boot is image-validated. Guard green.
eilandert added a commit that referenced this pull request Jun 6, 2026
Flip the entry point so the native bootstrap (no Zend_Application) is the
default path; set VIMBADMIN_NATIVE_BOOTSTRAP=0 to force the legacy ZF1 path.
The whole interactive admin UI now runs framework-free in production.

Two things were required to make the flip safe — once the native bootstrap owns
the PHP session, any route that punts to ZF1 mid-request would make
Zend_Session::start() fatal on the already-defined SID constant:

1. Route BEFORE booting. New Kernel::canHandle(path) decides servability with no
   resource at all — router match + built-in handler keys + method_exists on the
   mapped controller class. The entry point boots the native stack only for a
   path it can fully serve; the non-UI tail (mailer emailSettings /
   lost-password/reset, cli-*, queue trigger) reaches ZF1 with NOTHING started.

2. Native controllers must not punt a route they own by returning null at
   runtime. Converted the remaining conditional null-returns to native handling:
   - domain/mailbox/alias addAction with an id in the URL now redirect to the
     native editAction instead of falling back to ZF1;
   - domain editAction on an invalid did flashes + redirects (was a ZF1 punt);
   - auth setup with the salt unset renders a native first-run salt screen
     (new application/views/auth/native-setup-salt.phtml) instead of punting.

IMAGE-VALIDATED (vimbadmin:dev, NO flag = new default): anon about + login + all
list pages + add/edit forms 200 native with the Logout chrome; the ZF1 tail
(/auth/lost-password, /mailbox/email-settings) falls through to ZF1 cleanly (200,
no SID fatal); the de-punted edge cases (/domain/edit/did/<bad> -> 302 flash,
/domain/add/did/N -> 302 to native edit) are native; zero fatals.

tests/test-kernel-http.php gains canHandle asserts (built-in + router-miss; the
method_exists branch is image-validated). Guard green.
eilandert added a commit that referenced this pull request Jun 6, 2026
Honouring resources.auth.oss.lost_password.use_captcha on the native
lost-password page surfaced two native-bootstrap gaps in the reused ZF1 captcha
(OSS_Captcha_Image extends Zend_Captcha_Image). Both fixed in the ZF1-zone entry
point / the unscanned OSS lib; slice 6c de-Zends the captcha and removes these.

- OSS_Utils::getIniOption(): null-guard the ZF1 front-controller bootstrap param
  (null under the native bootstrap) and fall back to the merged options the
  native entry publishes in Zend_Registry. Without it OSS_Captcha_Image's ctor
  fatals (getTempDir -> getIniOption -> getApplication() on null).
- public/index.php (native zone): bridge Zend_Session to the already-open native
  PHP session — set _sessionStarted/_readable/_writable so a ZF1
  Zend_Session_Namespace (the captcha word store) attaches to the live session
  instead of fataling in Zend_Session::start()/Namespace ("already started" /
  "not marked as readable").

Image-validated (vimbadmin:dev + a mailpit SMTP sink): captcha-on GET renders the
image (auth/captcha-image 200 image/png), the RIGHT word sends the reset mail,
the WRONG word shows the inline "does not match that of the image" error and
sends nothing; captcha-off lost-password + the full reset round-trip also send +
verify (new password logs in, the spent token is rejected).
eilandert added a commit that referenced this pull request Jun 6, 2026
Foundation for porting the mailer-dependent tail (auth lost-password /
reset-password, mailbox email-settings) off the ZF1 Zend_Mail path. Inert:
nothing calls it yet, mirroring how each WALL #2 resource builder landed.

- composer: require symfony/mailer ^8.0 (matches the locked symfony 8.1
  band; pulls symfony/mime + event-dispatcher + egulias/email-validator).
- src/Kernel/Mail/Mailer.php: framework-free sender. Turns the
  resources.mail.transport.* ini block (type smtp|sendmail, host, port,
  ssl tls|ssl|none, username/password, verify_peer[_name]) into a configured
  symfony transport and sends a Mime\Email. Transport-building decision is a
  pure, testable resolveConfig(); buildTransport() makes the real transport.
  ssl=ssl -> implicit TLS (465); ssl=tls/starttls -> opportunistic STARTTLS
  (587); omitted -> plaintext. verify_peer/_name routed through
  FILTER_VALIDATE_BOOLEAN so the ini "0" string disables verification.
- Container::mailer(): lazily builds the Mailer from
  options[resources][mail][transport]. No Bootstrap change (reads options()).
- tests/test-kernel-mailer.php (26 asserts) + regression.yml step.

src/ stays free of the legacy framework prefix (guard green).
eilandert added a commit that referenced this pull request Jun 6, 2026
Ports OSS_Controller_Trait_Auth::lostPasswordAction/resetPasswordAction onto
the native kernel, sending through the slice-6b-1 Mailer instead of Zend_Mail.
The last two browser auth routes leave ZF1.

- AbstractController: mailer() (native getMailer) + renderEmail() (body-only
  Smarty render, no page chrome) for the auth/email/* templates.
- AuthController::lostPasswordAction: anti-enumeration (identical response
  whether or not the username exists); 40-char token stored as an expiring
  (2h, max 5) tokens.password_reset indexed preference; reset link mailed via
  the html email template. Captcha honoured when
  resources.auth.oss.lost_password.use_captcha is on — fresh OSS_Captcha_Image
  per render, typed text validated (as a field rule) against the SUBMITTED
  captcha id; image-click requests a new one (requestnewimage) and
  short-circuits before validation. The captcha PNG route (auth/captcha-image)
  stays ZF1 via the dispatcher fallback (de-Zended in 6c).
- AuthController::resetPasswordAction: GET (from the emailed link) prefills
  username + token from the path; valid POST checks the token against the live
  reset-token preferences, sets the new hash, clears all tokens, zeroes any
  failed-login counter, mails a confirmation, redirects to login. All failure
  paths share one generic message.
- sendAuthEmail() honours resources.auth.oss.email_format (html|plaintext|both)
  and sets From/To from identity.mailer.* + the admin email/formatted-name,
  mirroring resolveTemplate() + the legacy actions.
- New native views auth/native-{lost,reset}-password.phtml (FormRenderer +
  captcha image/refresh; no Zend_Form JS validator).

No NATIVE_CONTROLLERS / Kernel change (auth already native; the dispatcher
invokes the new action methods). src/ stays free of the legacy prefix.
eilandert added a commit that referenced this pull request Jun 6, 2026
Honouring resources.auth.oss.lost_password.use_captcha on the native
lost-password page surfaced two native-bootstrap gaps in the reused ZF1 captcha
(OSS_Captcha_Image extends Zend_Captcha_Image). Both fixed in the ZF1-zone entry
point / the unscanned OSS lib; slice 6c de-Zends the captcha and removes these.

- OSS_Utils::getIniOption(): null-guard the ZF1 front-controller bootstrap param
  (null under the native bootstrap) and fall back to the merged options the
  native entry publishes in Zend_Registry. Without it OSS_Captcha_Image's ctor
  fatals (getTempDir -> getIniOption -> getApplication() on null).
- public/index.php (native zone): bridge Zend_Session to the already-open native
  PHP session — set _sessionStarted/_readable/_writable so a ZF1
  Zend_Session_Namespace (the captcha word store) attaches to the live session
  instead of fataling in Zend_Session::start()/Namespace ("already started" /
  "not marked as readable").

Image-validated (vimbadmin:dev + a mailpit SMTP sink): captcha-on GET renders the
image (auth/captcha-image 200 image/png), the RIGHT word sends the reset mail,
the WRONG word shows the inline "does not match that of the image" error and
sends nothing; captcha-off lost-password + the full reset round-trip also send +
verify (new password logs in, the spent token is rejected).
eilandert added a commit that referenced this pull request Jun 6, 2026
* WALL #2 slice 6b-1: native mail sender (symfony/mailer, inert) (#80)

Foundation for porting the mailer-dependent tail (auth lost-password /
reset-password, mailbox email-settings) off the ZF1 Zend_Mail path. Inert:
nothing calls it yet, mirroring how each WALL #2 resource builder landed.

- composer: require symfony/mailer ^8.0 (matches the locked symfony 8.1
  band; pulls symfony/mime + event-dispatcher + egulias/email-validator).
- src/Kernel/Mail/Mailer.php: framework-free sender. Turns the
  resources.mail.transport.* ini block (type smtp|sendmail, host, port,
  ssl tls|ssl|none, username/password, verify_peer[_name]) into a configured
  symfony transport and sends a Mime\Email. Transport-building decision is a
  pure, testable resolveConfig(); buildTransport() makes the real transport.
  ssl=ssl -> implicit TLS (465); ssl=tls/starttls -> opportunistic STARTTLS
  (587); omitted -> plaintext. verify_peer/_name routed through
  FILTER_VALIDATE_BOOLEAN so the ini "0" string disables verification.
- Container::mailer(): lazily builds the Mailer from
  options[resources][mail][transport]. No Bootstrap change (reads options()).
- tests/test-kernel-mailer.php (26 asserts) + regression.yml step.

src/ stays free of the legacy framework prefix (guard green).

* WALL #2 slice 6b-2: native auth lost-password / reset-password

Ports OSS_Controller_Trait_Auth::lostPasswordAction/resetPasswordAction onto
the native kernel, sending through the slice-6b-1 Mailer instead of Zend_Mail.
The last two browser auth routes leave ZF1.

- AbstractController: mailer() (native getMailer) + renderEmail() (body-only
  Smarty render, no page chrome) for the auth/email/* templates.
- AuthController::lostPasswordAction: anti-enumeration (identical response
  whether or not the username exists); 40-char token stored as an expiring
  (2h, max 5) tokens.password_reset indexed preference; reset link mailed via
  the html email template. Captcha honoured when
  resources.auth.oss.lost_password.use_captcha is on — fresh OSS_Captcha_Image
  per render, typed text validated (as a field rule) against the SUBMITTED
  captcha id; image-click requests a new one (requestnewimage) and
  short-circuits before validation. The captcha PNG route (auth/captcha-image)
  stays ZF1 via the dispatcher fallback (de-Zended in 6c).
- AuthController::resetPasswordAction: GET (from the emailed link) prefills
  username + token from the path; valid POST checks the token against the live
  reset-token preferences, sets the new hash, clears all tokens, zeroes any
  failed-login counter, mails a confirmation, redirects to login. All failure
  paths share one generic message.
- sendAuthEmail() honours resources.auth.oss.email_format (html|plaintext|both)
  and sets From/To from identity.mailer.* + the admin email/formatted-name,
  mirroring resolveTemplate() + the legacy actions.
- New native views auth/native-{lost,reset}-password.phtml (FormRenderer +
  captcha image/refresh; no Zend_Form JS validator).

No NATIVE_CONTROLLERS / Kernel change (auth already native; the dispatcher
invokes the new action methods). src/ stays free of the legacy prefix.

* WALL #2 6b-2: make the ZF1 image captcha work under the native bootstrap

Honouring resources.auth.oss.lost_password.use_captcha on the native
lost-password page surfaced two native-bootstrap gaps in the reused ZF1 captcha
(OSS_Captcha_Image extends Zend_Captcha_Image). Both fixed in the ZF1-zone entry
point / the unscanned OSS lib; slice 6c de-Zends the captcha and removes these.

- OSS_Utils::getIniOption(): null-guard the ZF1 front-controller bootstrap param
  (null under the native bootstrap) and fall back to the merged options the
  native entry publishes in Zend_Registry. Without it OSS_Captcha_Image's ctor
  fatals (getTempDir -> getIniOption -> getApplication() on null).
- public/index.php (native zone): bridge Zend_Session to the already-open native
  PHP session — set _sessionStarted/_readable/_writable so a ZF1
  Zend_Session_Namespace (the captcha word store) attaches to the live session
  instead of fataling in Zend_Session::start()/Namespace ("already started" /
  "not marked as readable").

Image-validated (vimbadmin:dev + a mailpit SMTP sink): captcha-on GET renders the
image (auth/captcha-image 200 image/png), the RIGHT word sends the reset mail,
the WRONG word shows the inline "does not match that of the image" error and
sends nothing; captcha-off lost-password + the full reset round-trip also send +
verify (new password logs in, the spent token is rejected).
eilandert added a commit that referenced this pull request Jun 6, 2026
* WALL #2 slice 6b-2: native auth lost-password / reset-password

Ports OSS_Controller_Trait_Auth::lostPasswordAction/resetPasswordAction onto
the native kernel, sending through the slice-6b-1 Mailer instead of Zend_Mail.
The last two browser auth routes leave ZF1.

- AbstractController: mailer() (native getMailer) + renderEmail() (body-only
  Smarty render, no page chrome) for the auth/email/* templates.
- AuthController::lostPasswordAction: anti-enumeration (identical response
  whether or not the username exists); 40-char token stored as an expiring
  (2h, max 5) tokens.password_reset indexed preference; reset link mailed via
  the html email template. Captcha honoured when
  resources.auth.oss.lost_password.use_captcha is on — fresh OSS_Captcha_Image
  per render, typed text validated (as a field rule) against the SUBMITTED
  captcha id; image-click requests a new one (requestnewimage) and
  short-circuits before validation. The captcha PNG route (auth/captcha-image)
  stays ZF1 via the dispatcher fallback (de-Zended in 6c).
- AuthController::resetPasswordAction: GET (from the emailed link) prefills
  username + token from the path; valid POST checks the token against the live
  reset-token preferences, sets the new hash, clears all tokens, zeroes any
  failed-login counter, mails a confirmation, redirects to login. All failure
  paths share one generic message.
- sendAuthEmail() honours resources.auth.oss.email_format (html|plaintext|both)
  and sets From/To from identity.mailer.* + the admin email/formatted-name,
  mirroring resolveTemplate() + the legacy actions.
- New native views auth/native-{lost,reset}-password.phtml (FormRenderer +
  captcha image/refresh; no Zend_Form JS validator).

No NATIVE_CONTROLLERS / Kernel change (auth already native; the dispatcher
invokes the new action methods). src/ stays free of the legacy prefix.

* WALL #2 6b-2: make the ZF1 image captcha work under the native bootstrap

Honouring resources.auth.oss.lost_password.use_captcha on the native
lost-password page surfaced two native-bootstrap gaps in the reused ZF1 captcha
(OSS_Captcha_Image extends Zend_Captcha_Image). Both fixed in the ZF1-zone entry
point / the unscanned OSS lib; slice 6c de-Zends the captcha and removes these.

- OSS_Utils::getIniOption(): null-guard the ZF1 front-controller bootstrap param
  (null under the native bootstrap) and fall back to the merged options the
  native entry publishes in Zend_Registry. Without it OSS_Captcha_Image's ctor
  fatals (getTempDir -> getIniOption -> getApplication() on null).
- public/index.php (native zone): bridge Zend_Session to the already-open native
  PHP session — set _sessionStarted/_readable/_writable so a ZF1
  Zend_Session_Namespace (the captcha word store) attaches to the live session
  instead of fataling in Zend_Session::start()/Namespace ("already started" /
  "not marked as readable").

Image-validated (vimbadmin:dev + a mailpit SMTP sink): captcha-on GET renders the
image (auth/captcha-image 200 image/png), the RIGHT word sends the reset mail,
the WRONG word shows the inline "does not match that of the image" error and
sends nothing; captcha-off lost-password + the full reset round-trip also send +
verify (new password logs in, the spent token is rejected).
eilandert added a commit that referenced this pull request Jun 6, 2026
Ports the ZF1 emailSettingsAction + _sendSettingsEmail onto the native kernel —
the last mailer-dependent browser action. Sends through the slice-6b-1 Mailer.

- MailboxController::emailSettingsAction (GET|POST): the ajax "send settings"
  modal. GET returns the chrome-less modal (type select username/alt_email/other
  + an "other" free-text box, CSRF-guarded); POST send=1 resolves the
  recipient(s) and mails the per-user settings, returning the literal ok/error
  the modal JS expects, or re-rendering the modal with an inline error (the JS
  detects the <div class="modal-header"> prefix and swaps it in). "other" splits
  on comma and validates each address (ZF1 parity).
- sendSettingsEmail(): From = server.email.*; body = mailbox/email/settings.phtml
  rendered with the per-user-substituted server.* display settings
  (Entities\Mailbox::substitute). The mailbox/sendSettingsEmail/preSetBody notify
  hook has no listeners, so it is not replicated; the welcome-email variant + cc
  are dropped (consistent with the other native mailbox actions, no mailer there).
- AbstractController: renderEmail() generalised to renderPartial() (chrome-less
  template render for both email bodies AND ajax partials like this modal);
  AuthController updated to match.
- New native view mailbox/native-email-settings.phtml (modal + type-toggle/ajax JS).

No NATIVE_CONTROLLERS / Kernel change (mailbox already native; the dispatcher
invokes the new action). Image-validated (vimbadmin:dev + mailpit): username ->
mail to the mailbox; other with a bad address -> modal re-render, no send; other
with two valid addresses -> mail to both; CSRF enforced. src/ stays free of the
legacy prefix.
eilandert added a commit that referenced this pull request Jun 6, 2026
…te) (#85)

Ports the ZF1 emailSettingsAction + _sendSettingsEmail onto the native kernel —
the last mailer-dependent browser action. Sends through the slice-6b-1 Mailer.

- MailboxController::emailSettingsAction (GET|POST): the ajax "send settings"
  modal. GET returns the chrome-less modal (type select username/alt_email/other
  + an "other" free-text box, CSRF-guarded); POST send=1 resolves the
  recipient(s) and mails the per-user settings, returning the literal ok/error
  the modal JS expects, or re-rendering the modal with an inline error (the JS
  detects the <div class="modal-header"> prefix and swaps it in). "other" splits
  on comma and validates each address (ZF1 parity).
- sendSettingsEmail(): From = server.email.*; body = mailbox/email/settings.phtml
  rendered with the per-user-substituted server.* display settings
  (Entities\Mailbox::substitute). The mailbox/sendSettingsEmail/preSetBody notify
  hook has no listeners, so it is not replicated; the welcome-email variant + cc
  are dropped (consistent with the other native mailbox actions, no mailer there).
- AbstractController: renderEmail() generalised to renderPartial() (chrome-less
  template render for both email bodies AND ajax partials like this modal);
  AuthController updated to match.
- New native view mailbox/native-email-settings.phtml (modal + type-toggle/ajax JS).

No NATIVE_CONTROLLERS / Kernel change (mailbox already native; the dispatcher
invokes the new action). Image-validated (vimbadmin:dev + mailpit): username ->
mail to the mailbox; other with a bad address -> modal re-render, no send; other
with two valid addresses -> mail to both; CSRF enforced. src/ stays free of the
legacy prefix.
eilandert added a commit that referenced this pull request Jun 6, 2026
Native port of the ZF1 QueueController::triggerAction — the last WEB route still
served by the ZF1 fallback. Unauthenticated machine endpoint: Bearer-key
(SHA-256, constant-time) + source-IP-allowlist gated, then spawns a background
runner via the framework-free ViMbAdmin_QueueRunner::triggerCheck and returns
JSON. Empty queue.runner.key => 404 (disabled). No NATIVE_CONTROLLERS change
(queue already native; the dispatcher invokes the new action) — but this makes
canHandle('/queue/trigger') true, so the native stack now serves it instead of
falling through to ZF1.

Image-validated (vimbadmin:dev): empty key -> 404; no bearer -> 401; bad key ->
403; correct Bearer + allowed IP -> 200 {"triggered":false}. src/ stays free of
the legacy prefix (calls \ViMbAdmin_Net + \ViMbAdmin_QueueRunner, both
framework-free).
eilandert added a commit that referenced this pull request Jun 6, 2026
Native port of the ZF1 QueueController::triggerAction — the last WEB route still
served by the ZF1 fallback. Unauthenticated machine endpoint: Bearer-key
(SHA-256, constant-time) + source-IP-allowlist gated, then spawns a background
runner via the framework-free ViMbAdmin_QueueRunner::triggerCheck and returns
JSON. Empty queue.runner.key => 404 (disabled). No NATIVE_CONTROLLERS change
(queue already native; the dispatcher invokes the new action) — but this makes
canHandle('/queue/trigger') true, so the native stack now serves it instead of
falling through to ZF1.

Image-validated (vimbadmin:dev): empty key -> 404; no bearer -> 401; bad key ->
403; correct Bearer + allowed IP -> 200 {"triggered":false}. src/ stays free of
the legacy prefix (calls \ViMbAdmin_Net + \ViMbAdmin_QueueRunner, both
framework-free).
eilandert added a commit that referenced this pull request Jun 6, 2026
First slice of the cli-* migration off ZF1. Adds a framework-free CLI dispatcher
mirroring the web kernel's opt-in strangler: bin/vimbtool.php tries the native
path first and falls through to the ZF1 Zend_Application path for any command not
yet migrated.

- src/Kernel/Cli/CliCommand.php — a native CLI command (controller.action name,
  runs against the native Container, returns an exit code).
- src/Kernel/Cli/Command/QueueRunCommand.php — queue.cli-run: drains the
  mailbox-task queue via the already-extracted, framework-free
  ViMbAdmin_Service_QueueRunner (#64) — the same engine runNow/runTask + the
  trigger endpoint use. The every-2-minutes cron entrypoint.
- src/Kernel/Cli/CliKernel.php — registry + canHandle()/run(); boots via
  Bootstrap::boot() (which already skips the session under PHP_SAPI === 'cli', so
  only config + the Doctrine EM are built — no web scaffolding).
- bin/vimbtool.php — native path before Zend_Application: parse argv with
  getopt(), and if the action is migrated, register a tiny Zend-free PSR-0
  autoloader for the OSS_/ViMbAdmin_ library classes (not in the composer
  classmap; the ZF1 path's Zend_Loader runs only later) and run it.
- tests/test-kernel-cli.php (6 asserts) + regression.yml.

Image-validated (vimbadmin:dev): `vimbtool.php -a queue.cli-run -v` claims + runs
a real PENDING task through the native runner (QUOTA_RECALC -> doveadm, FAILED
only because dev dovecot has no quota plugin, same as ZF1), "Processed 1 task(s)",
exit 0; an unmigrated command (maintenance.cli-schema-update) + -h still run via
the ZF1 fallback. src/ stays free of the legacy prefix.

Remaining cli-* (cli-reset-totp, cli-delete-pending, cli-schema-update,
mcp.cli-token-*) follow as CliCommands; then 6c drops the ZF1 fallback.
eilandert added a commit that referenced this pull request Jun 6, 2026
Ports the remaining ZF1 cli*Actions onto native CliCommands (registered in
CliKernel; bin/vimbtool.php dispatches them natively, ZF1 fallback for any not
mapped — now none of the cli-* are unmapped):

- admin.cli-reset-totp      -> ResetTotpCommand     (ViMbAdmin_TwoFactor, --username/--all)
- mailbox.cli-delete-pending-> DeletePendingCommand (pendingDelete + rm_rf maildir/homedir, escapeshellarg)
- maintenance.cli-schema-update -> SchemaUpdateCommand (ViMbAdmin_Schema::migrate, same as web #72)
- mcp.cli-token-generate|list|revoke -> McpToken{Generate,List,Revoke}Command (McpToken CRUD)

bin/vimbtool.php native block: register the ZF1 autoloader (Zend_/OSS_/ViMbAdmin_)
+ the residual legacy registry glue (Zend_Registry d2em/options) around the booted
native Container — exactly as public/index.php does for the web. Needed because
the entity-preference layer (OSS_Doctrine2_WithPreferences) and OSS_Utils read the
EM/options from the registry (reset-totp hit this). Replaces the 6c-1 hand-rolled
PSR-0 shim. CliKernel split into boot() + run(Container) so the entry point wires
that glue between. Added --domains to the native getopt (mcp token generate).

Image-validated (vimbadmin:dev): schema-update (up to date, v4), delete-pending
(no pending), reset-totp --all (exit 0, no Zend_Registry fatal), mcp token
generate -> list (active) -> revoke -> list (revoked). test-kernel-cli asserts all
7 registered names. src/ free of the legacy prefix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant