You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(modules): prevent module deletion from wiping entire database
Bumps version to 0.31.10.0.
Critical:
- ModuleInstaller::rollbackModuleMigrations() called
MigrationRunner::regress(0), which ignores the namespace filter in
CI4 (regress() nulls $this->namespace internally and
getBatches()/getBatchHistory() do not filter by namespace). Any
single-module uninstall therefore downed every registered module's
migrations and dropped the entire database. Replaced with a
namespace-scoped getHistory() walk that calls force() per migration
in reverse order; finally{} resets the shared runner's namespace.
Sitemap:
- BlogModel/PagesModel sitemapItems(): return path-only loc values.
ci4seopro's SitemapBuilder prepends baseUrl, so site_url() produced
duplicate scheme+host inside <loc> entries.
- Both models now LEFT JOIN their *_langs tables so localized records
are not filtered out.
CSRF (backend):
- setCsrfHash() in be-assets/js/ci4ms.js also rewrites every
csrf_field() hidden input after a token refresh, so non-AJAX form
submits following an AJAX call no longer hit 403.
- ajaxPrefilter sends a pre-encoded URL string for empty POST bodies
instead of building an object jQuery would later re-serialize away.
Frontend:
- templates/default/assets/ci4ms.js gates captchaF() on the presence
of .captcha elements, removing the per-page-load /commentCaptcha
POST.
- funcs_helper.php switches the captcha refresh button from inline
onclick="captchaF()" to a .captcha-refresh class bound via the
existing delegated handler.
Backend UX:
- modules/Methods/Views/update.php: fix broken route_to('list') →
route_to('methodList'); cast inNavigation/isBackoffice/hasChild to
bool so checkboxes render active for stored 1/0 values.
elFinder:
- pageImgelfinderDialog / pageMultipleImgelfinderDialog now cache
their jQuery wrapper and reopen the same instance instead of
rebuilding on every call. sync interval exec() is wrapped in
try/catch so a destroyed instance no longer throws every second.
Misc:
- .gitignore: exclude CLAUDE.md and ci4ms-specs/.
- Install.php / Ci4msSetup.php: app.version → 0.31.10.0.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+23Lines changed: 23 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,6 +4,28 @@ All notable changes to this project will be documented in this file.
4
4
5
5
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) conventions adapted to the existing four-component version numbers.
6
6
7
+
## [0.31.10.0] - 2026-05-23
8
+
9
+
### Fixed
10
+
11
+
-**CRITICAL — Single Module Deletion Wiped Entire Database:**`ModuleInstaller::rollbackModuleMigrations()` called `MigrationRunner::regress(0)` under the assumption that `setNamespace()` scoped the rollback to a single module. CI4's `regress()` ignores the namespace filter (it nulls `$this->namespace` internally, and `getBatches()`/`getBatchHistory()` do not filter by namespace), so any uninstall would down every registered module's migrations and drop the entire database. Replaced with a namespace-filtered `getHistory()` walk that calls `force()` per migration, guaranteeing only the target module's tables are dropped and only its `migrations` table rows are removed. Added a `finally` block to reset the shared MigrationRunner singleton's namespace.
12
+
-**Sitemap URL Duplication:**`BlogModel::sitemapItems()` and `PagesModel::sitemapItems()` returned fully-qualified URLs via `site_url()`, but `ci4seopro\Libraries\Seo\Search\SitemapBuilder` prepends `Seo::$baseUrl` to every `loc`, producing malformed `<loc>https://host.tldhttps://host.tld/...</loc>` entries. Models now return paths only (`'/' . ltrim($seflink, '/')`), matching the package contract.
13
+
-**Sitemap Multilingual Coverage:** Both sitemap models now `LEFT JOIN` their `*_langs` tables so localized records are included instead of being filtered out by the primary-table-only query.
14
+
-**Backend CSRF Hidden Input Stale After AJAX:**`setCsrfHash()` in `public/be-assets/js/ci4ms.js` only updated the `<meta name="X-CSRF-TOKEN">` tag after token regeneration, leaving every `csrf_field()` hidden input in the page bound to the previous token. Classic (non-AJAX) form submissions following an AJAX request received `403 Forbidden`. The setter now also writes the new hash to every `input[name="csrf_token_ci4ms"]` on the page.
15
+
-**Backend CSRF Empty-Body POST Token Loss:** When an AJAX POST was sent with `data: null` or no `data` at all, `ajaxPrefilter` created a fresh object and assigned the token to it, but jQuery later re-serialized that object into an empty body, stripping the CSRF parameter. The prefilter now writes a pre-encoded URL-encoded string instead, so the token survives to the wire.
16
+
-**Frontend Captcha Auto-Fire on All Pages:**`public/templates/default/assets/ci4ms.js` invoked `captchaF()` at file scope, firing a `POST /commentCaptcha` request on every public-side page load regardless of whether a captcha image was rendered. Wrapped the call in a DOM-ready guard that runs only when `.captcha` elements exist.
17
+
-**Methods Update View Broken Route:** "Back to list" link in `modules/Methods/Views/update.php` referenced the non-existent route alias `list`; updated to the correct `methodList` alias.
18
+
-**Methods Update View Checkbox Active State:**`inNavigation`, `isBackoffice`, and `hasChild` flags are stored as integers (`1` / `0`) but the view used strict `=== true` comparison, never marking checkboxes as active for existing records. Added `(bool)` casts so the `active` class and `checked` attribute apply correctly.
19
+
20
+
### Changed
21
+
22
+
-**elFinder Dialog Reuse:**`pageImgelfinderDialog()` and `pageMultipleImgelfinderDialog()` no longer create a fresh dialog on every call. The first invocation builds the dialog and caches the jQuery wrapper in a module-scoped variable (or keyed map for the multi-image variant); subsequent invocations call `dialogelfinder("open")` on the existing instance. Removes the `destroyOnClose: true` flag on these variants, prevents leaked event handlers, and stops repeated `cssAutoLoad` HTTP requests. The `sync` interval is wrapped in `try/catch` so a destroyed instance no longer throws a polling exception every second.
23
+
-**Captcha Refresh Button Wiring:** The "New Captcha" button in `app/Helpers/templates/default/funcs_helper.php` switched from inline `onclick="captchaF()"` to a `.captcha-refresh` class that the existing jQuery delegation in `captchaF()` already binds. Cleaner separation between markup and behavior.
24
+
25
+
### Added
26
+
27
+
-**`.gitignore` Entries:** Excluded `CLAUDE.md` and `ci4ms-specs/` so per-developer agent tooling artifacts stay out of version control.
28
+
7
29
## [0.31.9.0] - 2026-05-08
8
30
9
31
### Security
@@ -314,6 +336,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
314
336
315
337
- Expanded database migrations and introduced new supporting libraries.
0 commit comments