| 1bec8197 | 26-Jun-2026 |
splitbrain <86426+splitbrain@users.noreply.github.com> |
Rector and PHPCS fixes |
| 79305873 | 26-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
fix RSS feed aggregation broken by SimplePie 1.9 upgrade
SimplePie 1.9 added a FileClient that rejects any response whose error is non-null while the status code is zero. FeedParserFile never set a
fix RSS feed aggregation broken by SimplePie 1.9 upgrade
SimplePie 1.9 added a FileClient that rejects any response whose error is non-null while the status code is zero. FeedParserFile never set a status code and copied DokuHTTPClient's error verbatim, which is an empty string (not null) on success - so every successful fetch was misclassified as a failed request and feeds rendered the 'rssfailed' message.
Populate the response through SimplePie's Response interface methods backed by our own properties (status code, body, normalized headers, requested URL) and report a missing error as null, instead of writing File's deprecated backwards-compatibility properties.
show more ...
|
| 946266a3 | 26-Jun-2026 |
Schplurtz le Déboulonné <Schplurtz@laposte.net> |
Translation update (fr) |
| 2848662b | 25-Jun-2026 |
splitbrain <86426+splitbrain@users.noreply.github.com> |
Rector and PHPCS fixes |
| 7a48b45e | 25-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
Updated dependencies |
| 884caed9 | 25-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
Remote: restrict core.aclCheck for other users to superusers
aclCheck() let any API-enabled user pass an arbitrary username and learn that user's effective permission level on any page/namespace, en
Remote: restrict core.aclCheck for other users to superusers
aclCheck() let any API-enabled user pass an arbitrary username and learn that user's effective permission level on any page/namespace, enabling ACL-posture enumeration of other accounts. Checking another user's permissions is now limited to superusers; users may still check their own. The deprecated legacyAclCheck() delegates here and is covered too.
Not really a big security concern, but there is no reason to enable it.
Note: arbitrary groups can still be checked by anyone.
show more ...
|
| 40981bcc | 25-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
Ip: validate CIDR mask to prevent fatal error and over-broad proxy trust
A non-numeric or empty mask in trustedproxies (e.g. 10.0.0.0/abc or 10.0.0.0/) threw an uncaught TypeError on the IPv4 path,
Ip: validate CIDR mask to prevent fatal error and over-broad proxy trust
A non-numeric or empty mask in trustedproxies (e.g. 10.0.0.0/abc or 10.0.0.0/) threw an uncaught TypeError on the IPv4 path, and a negative mask (10.0.0.0/-1) passed the bounds check and produced a bitmask that matched every IPv4, silently trusting all proxies.
Validate the mask as a non-negative integer in ipInRange() and broaden the ipMatches() catch to Throwable so an invalid range degrades to 'no match' instead of a 500.
show more ...
|
| 9af82229 | 25-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
extension: validate base name to prevent path traversal on install
The extension base name flowed unsanitized from an uploaded archive's plugin.info.txt (or an extension id) into getInstallDir(), wh
extension: validate base name to prevent path traversal on install
The extension base name flowed unsanitized from an uploaded archive's plugin.info.txt (or an extension id) into getInstallDir(), where fullpath() collapses '..' segments. A crafted base such as '../../../../evil' therefore resolved outside lib/plugins/, letting dircopy() write the archive contents to an arbitrary web-writable path.
Route every base assignment through a new setBase() that rejects anything other than a bare extension name.
Note: this is not really a security concern since plugins can execute arbitrary code by design. The is mostly to make sure, plugin code is not installed accidentally at the wrong location.
show more ...
|
| 15751e18 | 25-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
Preserve edid and onselect across media manager search (#4608)
The media manager popup search reloads the whole page, dropping the edid and onselect parameters that plugins (e.g. struct, prosemirror
Preserve edid and onselect across media manager search (#4608)
The media manager popup search reloads the whole page, dropping the edid and onselect parameters that plugins (e.g. struct, prosemirror) rely on to insert media and run their callback. Carry them over in the search form action URL so they survive the reload, and match them in media.js regardless of their position in the query string.
show more ...
|
| 195a3a7f | 25-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
make getUserData() fail safely on non-string input
When removing multiple users at once, plugins listening on the AUTH_USER_CHANGE event may pass the array of user names (params[0] for 'delete') to
make getUserData() fail safely on non-string input
When removing multiple users at once, plugins listening on the AUTH_USER_CHANGE event may pass the array of user names (params[0] for 'delete') to getUserData(), which crashed authplain with an "Array to string conversion" / illegal offset error.
Guard the bundled auth backends so a non-string user fails safely by returning false, and document that AUTH_USER_CHANGE params[0] is a string for create/modify but a string[] for delete.
Fixes #4567
show more ...
|
| 6436b0ab | 25-Jun-2026 |
Schplurtz le Déboulonné <Schplurtz@laposte.net> |
Translation update (fr) |
| a7e10216 | 25-Jun-2026 |
splitbrain <86426+splitbrain@users.noreply.github.com> |
Rector and PHPCS fixes |
| 6c7ec050 | 25-Jun-2026 |
splitbrain <86426+splitbrain@users.noreply.github.com> |
Update deleted files |
| 75364f13 | 25-Jun-2026 |
Marek Adamski <fevbew@wp.pl> |
Translation update (pl) |
| 43162c2f | 23-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
Search: give SEARCH_RESULT_FULLPAGE a default action
The fulltext result row is now built by buildPageResultRow(), registered as the event's default action, so plugins can preventDefault() to render
Search: give SEARCH_RESULT_FULLPAGE a default action
The fulltext result row is now built by buildPageResultRow(), registered as the event's default action, so plugins can preventDefault() to render a results differently instead of only amending the row.
The event data gains 'score', 'highlight', a 'showsnippet' flag and a 'class' for the result wrapper. Plugins amending the row must now use the AFTER hook, since the row no longer exists when BEFORE handlers run.
show more ...
|
| c651c34b | 17-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
BacklinksTest: give testLinksInDeletedPages its own page
testLinksInDeletedPages reused test:internallinks, the same page testInternallink already saves and indexes. Since the data dir is shared acr
BacklinksTest: give testLinksInDeletedPages its own page
testLinksInDeletedPages reused test:internallinks, the same page testInternallink already saves and indexes. Since the data dir is shared across the class, when the re-save and the earlier index land in the same second, needsIndexing() now (correctly) reports the page as up to date and addPage() skips reindexing, leaving stale link data. backlinks('test:internallink') then returned an empty array.
Use a dedicated page (test:deletedlinks) with its own link targets so the test no longer collides with testInternallink's index state.
show more ...
|
| 2cda0166 | 17-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
Indexer: signal nothing-to-do via boolean return instead of void
The TaskRunner runs indexing, sitemap, digest and changelog-trim tasks in sequence and relies on each task returning false when it di
Indexer: signal nothing-to-do via boolean return instead of void
The TaskRunner runs indexing, sitemap, digest and changelog-trim tasks in sequence and relies on each task returning false when it did no work so the next one is tried. The indexer rewrite changed addPage(), deletePage() and renamePage() to return void and only abort via exceptions, breaking that contract: indexing always looked like work was done and the following tasks never ran.
Restore the boolean return on these three methods (true when work was done, false when there was nothing to do) while still using exceptions to signal errors, and propagate it through TaskRunner::runIndexer(). runIndexer() also no longer forces reindexing on every call.
The legacy compatibility layer is adjusted to match: LegacyIndexer and idx_addPage() forward the boolean, mapping SearchExceptions back to the historic error-message/false returns. LegacyIndexer::renamePage() restores the 'page is not in index' message that the move plugin expects.
Closes #4661
show more ...
|
| 79dae64d | 17-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
Indexer: treat same-second save and index as up to date
needsIndexing() compared the .indexed tag mtime against the page mtime with <=, so a page that was saved and indexed within the same second wa
Indexer: treat same-second save and index as up to date
needsIndexing() compared the .indexed tag mtime against the page mtime with <=, so a page that was saved and indexed within the same second was always reported as still needing indexing. Require the page to be strictly newer than the index tag instead, so an equal mtime correctly counts as up to date.
show more ...
|
| 5d71952d | 16-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
Translation update (de) |
| f4daad6a | 13-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
AI Policy document added |
| 2ff7e61c | 10-Jun-2026 |
Andreas Gohr <gohr@cosmocode.de> |
fix(indexer): explicitly handle renames
In an attempt to simplify the index handling, the newly refactored indexer implemented a rename as delete+add sequence.
This had unintended consequences for
fix(indexer): explicitly handle renames
In an attempt to simplify the index handling, the newly refactored indexer implemented a rename as delete+add sequence.
This had unintended consequences for the move plugin which may move several pages at once, requiring a working index even while some pages have already been moved while others still remain at their old location.
Related to #4646
show more ...
|
| eab6268c | 07-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
fix(changelog): keep out-of-order external edits out of recent changes (#4634)
A detected external edit is recorded both in the page's own changelog and in the global recent-changes feed. When its d
fix(changelog): keep out-of-order external edits out of recent changes (#4634)
A detected external edit is recorded both in the page's own changelog and in the global recent-changes feed. When its date is older than the most recent change already in the global changelog — an old file surfacing after the feed has moved on — appending it placed it at the top of recent changes with an old date, above genuinely newer entries.
writeLogEntry() now delegates the global-feed append to writeGlobalLogEntry(), which skips an external edit whose date predates the global changelog's last-modified time. The entry still lands in the page's own changelog; it is only kept out of the cross-page feed. Normal edits are always appended.
show more ...
|
| 0a245329 | 07-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
fix(changelog): don't record an external edit when the content is unchanged (#4634)
A current revision's file mtime can change without its content changing — a backup restore, a git checkout, an in-
fix(changelog): don't record an external edit when the content is unchanged (#4634)
A current revision's file mtime can change without its content changing — a backup restore, a git checkout, an in-place rewrite. Such a bump was detected as an external edit and logged with a 0-byte size change, cluttering history.
getCurrentRevisionInfo() now compares the current content against the last recorded revision before treating an mtime change as an external edit. When the content is identical no external edit happened: the file mtime is reset to the recorded revision date and the last revision is returned. The comparison is an abstract currentContentMatchesRevision() — pages compare the decompressed text, media compare file size then md5_file().
show more ...
|
| 2bde879a | 07-Jun-2026 |
Andreas Gohr <andi@splitbrain.org> |
Fix diff view comparing a deleted page with itself (#4635)
When opening the diff of a deleted page without explicit rev parameters (?do=diff), Diff::handle() resolved the older side via getRevisions
Fix diff view comparing a deleted page with itself (#4635)
When opening the diff of a deleted page without explicit rev parameters (?do=diff), Diff::handle() resolved the older side via getRevisions(0, 1). That helper only skips the current revision when the item file still exists, so for a deleted page it returned the deletion entry itself and the view ended up comparing the current revision with itself ("no way to compare when less than two revisions", empty diff).
Use getRelativeRevision($rev2, -1) instead, which returns the revision immediately before the current one regardless of whether the page file is still present. This covers both pages deleted through DokuWiki and externally deleted pages (whose synthesized deletion entry is now persisted to the changelog).
Add PageDiffTest and PageChangeLogTest covering the resolved revision pair and the underlying changelog walk-back for both deletion kinds.
show more ...
|
| c959e5ab | 07-Jun-2026 |
splitbrain <86426+splitbrain@users.noreply.github.com> |
Rector and PHPCS fixes |