History log of /dokuwiki/_test/tests/ (Results 1 – 25 of 730)
Revision Date Author Comments
(<<< Hide modified files)
(Show modified files >>>)
7930587326-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 ...


/dokuwiki/_test/composer.lock
Feed/FeedParserTest.php
/dokuwiki/composer.lock
/dokuwiki/inc/Feed/FeedParserFile.php
/dokuwiki/inc/parser/xhtml.php
/dokuwiki/lib/plugins/config/core/Setting/Setting.php
/dokuwiki/lib/plugins/config/core/Setting/SettingArray.php
/dokuwiki/lib/plugins/config/core/Setting/SettingEmail.php
/dokuwiki/lib/plugins/config/core/Setting/SettingImConvert.php
/dokuwiki/lib/plugins/config/core/Setting/SettingMulticheckbox.php
/dokuwiki/lib/plugins/config/core/Setting/SettingMultichoice.php
/dokuwiki/lib/plugins/config/core/Setting/SettingOnoff.php
/dokuwiki/lib/plugins/config/core/Setting/SettingSavedir.php
/dokuwiki/lib/plugins/config/core/Setting/SettingString.php
/dokuwiki/lib/plugins/config/lang/fr/lang.php
/dokuwiki/vendor/composer/installed.json
/dokuwiki/vendor/composer/installed.php
/dokuwiki/vendor/phpseclib/phpseclib/README.md
/dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Crypt/DES.php
/dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php
/dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PrivateKey.php
/dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC.php
/dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php
/dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php
/dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PublicKey.php
/dokuwiki/vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php
/dokuwiki/vendor/phpseclib/phpseclib/phpseclib/File/X509.php
/dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php
/dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP.php
/dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php
/dokuwiki/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php
/dokuwiki/vendor/splitbrain/php-archive/src/Zip.php
884caed925-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 ...

40981bcc25-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 ...

c651c34b17-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 ...

2cda016617-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 ...

79dae64d17-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 ...

2ff7e61c10-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 ...

eab6268c07-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 ...

0a24532907-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 ...

2bde879a07-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 ...

b12755b007-Jun-2026 Andreas Gohr <andi@splitbrain.org>

Make common_pageinfo tests independent of execution order

The pageinfo() tests relied on running in a fixed order: test_editor_and_externaledits
mutated wiki:syntax (changelog entry + file mtime), a

Make common_pageinfo tests independent of execution order

The pageinfo() tests relied on running in a fixed order: test_editor_and_externaledits
mutated wiki:syntax (changelog entry + file mtime), and the other tests relied on
that page being pristine. Move the mutating test to its own page (wiki:dokuwiki) so
the shared per-class data dir stays clean, prime the last_change metadata within each
test instead of across tests, and fix the affected expectations. The suite now passes
in isolation and in random/reverse order.

While here, modernize the file:
- capture the pageinfo() result once instead of calling it twice
- add public/protected visibility markers to all methods
- convert array() to the short [] syntax
- replace the deprecated addLogEntry() with the PageFile API it wraps

show more ...

37bd308a06-Jun-2026 Andreas Gohr <andi@splitbrain.org>

Add unit tests for the Draft class

The Draft class had no dedicated tests. Cover the save/retrieve round
trip for whole-page and section edits, the cases where no draft is
written (drafts disabled,

Add unit tests for the Draft class

The Draft class had no dedicated tests. Cover the save/retrieve round
trip for whole-page and section edits, the cases where no draft is
written (drafts disabled, nothing edited), draft deletion, reading a
missing draft, and the constructor's purging of drafts that predate the
current page revision.

show more ...

5d8c9d4206-Jun-2026 Andreas Gohr <andi@splitbrain.org>

(security) Require a security token for the lock AJAX call

The lock AJAX call refreshes the edit lock and saves a draft, both of
which change server state. It was gated only by the write ACL and,
un

(security) Require a security token for the lock AJAX call

The lock AJAX call refreshes the edit lock and saves a draft, both of
which change server state. It was gated only by the write ACL and,
unlike the sibling draft-delete call, did not verify a security token
(low severity).

A cross-site forged POST against a logged-in user could, within that
user's own write permissions, take or hold an edit lock and store
attacker-controlled draft content under their name.

The call now verifies the security token before taking the lock or
saving the draft. Logged out users are unaffected, as no token is
issued or checked for them. The edit lock timer now always sends the
token with its refresh request, including when draft saving is
disabled.

show more ...

e8c9256a06-Jun-2026 Andreas Gohr <andi@splitbrain.org>

(security) Clean the media upload namespace in AJAX upload

The namespace passed to the AJAX backend was not cleaned correctly,
resulting in two separate issues.

1. Theoretical Reflected XSS
The

(security) Clean the media upload namespace in AJAX upload

The namespace passed to the AJAX backend was not cleaned correctly,
resulting in two separate issues.

1. Theoretical Reflected XSS
The raw namespace was reflected into the JSON response and injected
into the mediamanager DOM. However since the media manager only
passes cleaned namespaces to AJAX and the ajax backen only returns
JSON, this issue was not exploitable.
2. Cross-namespace ACL bypass (medium severity)
The uncleaned namespace was directly used to check ACLs. In a wiki
where a user has upload permission in a namespace above a namespace
where they don't have permissions (eg. upload allowed in :user:*, but
upload denied in :user:secret:*) they could pass an upper case
namespace (eg :user:SECRET) - no ACL does exist for this upper case
namespace and the acl of the namespace above applies (:user).
When the file is written a cleanID is applied to the full filename,
turning the uppercase namespace into lowercase.
This can allow users to write into a namespace they normally should
not be allowed to write to, but it does require upload permissions in
a higher namespace.

show more ...

3990423504-Jun-2026 Andreas Gohr <gohr@cosmocode.de>

Emit a guard newline in TextareaElement to preserve leading newlines

A browser's HTML parser ignores a single newline immediately after a
<textarea> start tag, so a value whose first character is a

Emit a guard newline in TextareaElement to preserve leading newlines

A browser's HTML parser ignores a single newline immediately after a
<textarea> start tag, so a value whose first character is a newline lost
it on round-trip (e.g. saving a page or section whose source starts with
a blank line silently dropped that line).

Emit a guard newline right after the start tag so the one the browser
drops is absorbed and the value stays intact.

show more ...


/dokuwiki/README
Form/TextareaElementTest.php
/dokuwiki/inc/Action/Subscribe.php
/dokuwiki/inc/Form/TextareaElement.php
/dokuwiki/inc/common.php
/dokuwiki/inc/infoutils.php
/dokuwiki/inc/lang/ro/lang.php
/dokuwiki/inc/lang/ro/locked.txt
/dokuwiki/inc/lang/ro/mailtext.txt
/dokuwiki/inc/lang/ro/newpage.txt
/dokuwiki/inc/lang/ro/onceexisted.txt
/dokuwiki/inc/lang/ro/password.txt
/dokuwiki/inc/lang/ro/registermail.txt
/dokuwiki/inc/lang/ro/resendpwd.txt
/dokuwiki/inc/lang/ro/resetpwd.txt
/dokuwiki/inc/lang/ro/subscr_digest.txt
/dokuwiki/inc/lang/ro/subscr_list.txt
/dokuwiki/inc/lang/ro/subscr_single.txt
/dokuwiki/inc/lang/ro/uploadmail.txt
/dokuwiki/inc/lang/sk/lang.php
/dokuwiki/lib/plugins/acl/lang/ro/help.txt
/dokuwiki/lib/plugins/acl/lang/ro/lang.php
/dokuwiki/lib/plugins/authad/auth.php
/dokuwiki/lib/plugins/authad/lang/ro/lang.php
/dokuwiki/lib/plugins/authad/lang/sk/settings.php
/dokuwiki/lib/plugins/authldap/lang/sk/settings.php
/dokuwiki/lib/plugins/config/lang/ro/intro.txt
/dokuwiki/lib/plugins/config/lang/ro/lang.php
/dokuwiki/lib/plugins/config/lang/sk/lang.php
/dokuwiki/lib/plugins/extension/lang/sk/lang.php
/dokuwiki/lib/plugins/logviewer/lang/sk/intro.txt
/dokuwiki/lib/plugins/logviewer/lang/sk/lang.php
/dokuwiki/lib/plugins/logviewer/lang/sk/nolog.txt
/dokuwiki/lib/plugins/popularity/lang/ro/intro.txt
/dokuwiki/lib/plugins/revert/lang/ro/intro.txt
/dokuwiki/lib/plugins/revert/lang/ro/lang.php
/dokuwiki/lib/plugins/usermanager/lang/ro/delete.txt
/dokuwiki/lib/plugins/usermanager/lang/ro/intro.txt
/dokuwiki/lib/plugins/usermanager/lang/ro/lang.php
/dokuwiki/lib/plugins/usermanager/lang/sk/lang.php
4b31eadf04-Jun-2026 Andreas Gohr <gohr@cosmocode.de>

fix (parsing): avoid newline loss on GFM section editing

The GFM header parsing returned a byte position pointing at the newline
before the actual header resulting in the observed newline eatings as

fix (parsing): avoid newline loss on GFM section editing

The GFM header parsing returned a byte position pointing at the newline
before the actual header resulting in the observed newline eatings as
reported in https://github.com/dokuwiki/dokuwiki/pull/4636#issuecomment-4491970909

Additionally this fixes an oddity of DW header parsing which
accidentally allowed text on the line before the opening = chars.
Whitespace is still allowed.

show more ...

2e43b79904-Jun-2026 Andreas Gohr <gohr@cosmocode.de>

Render locale and plugin-bundled text as DokuWiki syntax

Static "intro" text shipped with DokuWiki, templates and plugins —
inc/lang/*/*.txt, template locale files, each plugin's lang/<lc>/*.txt —
i

Render locale and plugin-bundled text as DokuWiki syntax

Static "intro" text shipped with DokuWiki, templates and plugins —
inc/lang/*/*.txt, template locale files, each plugin's lang/<lc>/*.txt —
is authored in DokuWiki syntax. It is a core/plugin asset, not user
content. When an admin configures the wiki for Markdown the DW link and
monospace modes are not loaded, so these files render as literal text:
[[wiki:syntax]] and ''Save'' pairs survive into the HTML.

Pin those entry points to the 'dw' flavour via the override added in the
previous commit:

- p_locale_xhtml() and tpl_locale_xhtml() pass 'dw'.
- PluginTrait::locale_xhtml() passes 'dw'.
- PluginTrait::render_text() / PluginInterface::render_text() gain a
$syntax parameter defaulting to 'dw'. The default is 'dw', not null,
because the method predates GFM and its callers pass DW-syntax strings;
plugins rendering user content opt back into the configured syntax with
render_text($text, 'xhtml', null).

Locale output is now deterministic across a syntax switch, so its caches
get over-invalidated but never under-invalidated — acceptable, as the
locale render path is rare and cheap.

show more ...

16999ed104-Jun-2026 Andreas Gohr <gohr@cosmocode.de>

parserutils: allow callers to override the parse syntax, key cache on it

With the registry now carrying the flavour as a parameter, expose that
to callers: p_get_instructions(), p_cached_instruction

parserutils: allow callers to override the parse syntax, key cache on it

With the registry now carrying the flavour as a parameter, expose that
to callers: p_get_instructions(), p_cached_instructions() and
p_cached_output() gain an optional $syntax argument. null (the default)
means "use the configured $conf['syntax']", preserving behaviour for
every existing call site; an explicit flavour parses under it regardless
of the wiki's configured preference.

This is what lets bundled assets render deterministically — e.g. a
plugin forcing 'dw' on a document whose configured syntax is 'md'.

Because that case renders the same file under two flavours within one
request, key both the in-request memo (the $run map) and the on-disk
cache on $syntax so the two do not collide. The syntax enters the cache
key only when passed explicitly (non-null); when null the key is
unchanged, so existing caches are untouched. Plumbed through new
optional $syntax args on CacheParser / CacheInstructions, appended to
the key string.

show more ...

47a02a1004-Jun-2026 Andreas Gohr <gohr@cosmocode.de>

Parsing: make parse syntax a per-parse value, drop ModeInterface

The active parse's syntax flavour is a per-parse question, not process-
global state: within a single request a plugin can render bun

Parsing: make parse syntax a per-parse value, drop ModeInterface

The active parse's syntax flavour is a per-parse question, not process-
global state: within a single request a plugin can render bundled
DokuWiki-syntax text inside an otherwise-Markdown page. Yet ModeRegistry
was a singleton that read $conf['syntax'] and the $PARSER_MODES global,
and every mode reached it through ModeRegistry::getInstance() — so the
flavour lived in shared mutable state that two parses in one request
would fight over.

Make the registry a short-lived value instead:

- ModeRegistry is constructed once per parse with an explicit $syntax
and injected into Parser, Handler and every mode. getSyntax() /
isDwPreferred() / isMdPreferred() consult $this->syntax; the
DOKU_UNITTEST-gated mode-list cache hack is gone (each registry is
fresh, nothing to invalidate).
- p_get_instructions() is now the single place in the pipeline where
$conf['syntax'] is read; from there the flavour travels as a
parameter. No code under inc/Parsing/ reads $conf['syntax'] directly
anymore — the five syntax-reading modes (Preformatted, GfmHr,
GfmEscape, Externallink, GfmQuote) route through $this->registry.

Keep the two concepts apart, as documented in the ModeRegistry and
AbstractMode docblocks: the user's configured *preference* stays in
$conf['syntax'] for UI code (toolbar, settings), while the active
parse's syntax is a parameter carried by the registry.

$PARSER_MODES is demoted to a deprecated, read-only mirror, published
during loadPluginModes() — third-party syntax plugins (columnlist,
alphalist2, phpwikify, skipentity) and the bundled info plugin read the
global directly, often from their constructors, so the taxonomy must
stay visible there. No core code reads the mirror.

Fold ModeInterface into AbstractMode while here: getSort()/handle() are
abstract, the connect callbacks carry defaults, and the public $Lexer
"FIXME should be done by setter" becomes setLexer()/getLexer() injected
by Parser::addMode() alongside the registry. Nested-content resolution
moves to the allowedCategories()/filterAllowedModes() hooks, resolved
once when the registry is attached.

Tests build their own parser/registry through ParserTestBase::setSyntax()
instead of mutating $conf and calling the removed ModeRegistry::reset().

show more ...


/dokuwiki/_test/bootstrap.php
Parsing/HandlerTest.php
Parsing/Lexer/RecordingHandler.php
Parsing/Markdown/GfmSpecTest.php
Parsing/ModeRegistryTest.php
Parsing/ParserMode/ExternallinkTest.php
Parsing/ParserMode/GfmBacktickDoubleTest.php
Parsing/ParserMode/GfmBacktickSingleTest.php
Parsing/ParserMode/GfmCodeTest.php
Parsing/ParserMode/GfmDeletedTest.php
Parsing/ParserMode/GfmEmphasisStrongTest.php
Parsing/ParserMode/GfmEmphasisStrongUnderscoreTest.php
Parsing/ParserMode/GfmEmphasisTest.php
Parsing/ParserMode/GfmEmphasisUnderscoreTest.php
Parsing/ParserMode/GfmEscapeTest.php
Parsing/ParserMode/GfmFileTest.php
Parsing/ParserMode/GfmHeaderTest.php
Parsing/ParserMode/GfmHrTest.php
Parsing/ParserMode/GfmLinebreakTest.php
Parsing/ParserMode/GfmLinkTest.php
Parsing/ParserMode/GfmListblockTest.php
Parsing/ParserMode/GfmMediaTest.php
Parsing/ParserMode/GfmQuoteTest.php
Parsing/ParserMode/GfmStrongUnderscoreTest.php
Parsing/ParserMode/ParserTestBase.php
Parsing/ParserMode/PreformattedTest.php
/dokuwiki/inc/Extension/SyntaxPlugin.php
/dokuwiki/inc/Parsing/Handler.php
/dokuwiki/inc/Parsing/ModeRegistry.php
/dokuwiki/inc/Parsing/Parser.php
/dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php
/dokuwiki/inc/Parsing/ParserMode/AbstractMode.php
/dokuwiki/inc/Parsing/ParserMode/Base.php
/dokuwiki/inc/Parsing/ParserMode/Eol.php
/dokuwiki/inc/Parsing/ParserMode/Externallink.php
/dokuwiki/inc/Parsing/ParserMode/Footnote.php
/dokuwiki/inc/Parsing/ParserMode/GfmBacktickSingle.php
/dokuwiki/inc/Parsing/ParserMode/GfmCode.php
/dokuwiki/inc/Parsing/ParserMode/GfmEscape.php
/dokuwiki/inc/Parsing/ParserMode/GfmHr.php
/dokuwiki/inc/Parsing/ParserMode/GfmHtmlEntity.php
/dokuwiki/inc/Parsing/ParserMode/GfmListblock.php
/dokuwiki/inc/Parsing/ParserMode/GfmQuote.php
/dokuwiki/inc/Parsing/ParserMode/GfmTable.php
/dokuwiki/inc/Parsing/ParserMode/Listblock.php
/dokuwiki/inc/Parsing/ParserMode/Preformatted.php
/dokuwiki/inc/Parsing/ParserMode/Table.php
/dokuwiki/inc/deprecated.php
/dokuwiki/inc/parserutils.php
b8c2692f29-May-2026 Andreas Gohr <andi@splitbrain.org>

fix(httputils): build a proper internal URL for nginx X-Accel-Redirect

Unlike Apache's mod_xsendfile and lighttpd's X-LIGHTTPD-send-file,
which both accept an absolute file system path and stream th

fix(httputils): build a proper internal URL for nginx X-Accel-Redirect

Unlike Apache's mod_xsendfile and lighttpd's X-LIGHTTPD-send-file,
which both accept an absolute file system path and stream the file
directly, nginx treats X-Accel-Redirect as a URL: it performs an
internal subrequest against the value and resolves it through
`internal` location blocks in its own configuration. The redirect
target therefore must be a request path nginx can map back to a file,
not the file system path itself.

The previous implementation blindly stripped DOKU_INC's length from
the file path, which produced a broken URL whenever a data directory
had been relocated outside the DokuWiki tree.

Route the file through three cases instead: files inside DOKU_INC keep
their path relative to the wiki root; files in a relocated mediadir,
mediaolddir, cachedir or savedir are mapped back to their default URL
below data/; everything else falls through an opt-in /_x_accel_redirect/
prefix.

Path segments are URL-encoded so filenames with spaces or other
special characters resolve (fixes another potential bug in the previous
implementation).

Nginx configuration:

For the default layout no extra configuration is needed - files keep
their path relative to the wiki root and the regular wiki location
already serves them. For relocated data directories admins must add an
`internal` location below the wiki's URL space pointing at the actual
file system path, e.g.:

location /data/media/ { internal; alias /srv/dokuwiki-media/; }

For the /_x_accel_redirect/ fallback (used when a plugin serves files
from outside the wiki and its data directories), admins opt in by
adding:

location /_x_accel_redirect/ { internal; alias /; }

When the wiki is installed under a sub path, prefix both locations
accordingly (e.g. /wiki/data/media/ and /wiki/_x_accel_redirect/).

fixes: #2895

show more ...

7e687fd829-May-2026 Andreas Gohr <andi@splitbrain.org>

fix(auth): scope media ACL checks to the namespace

Media files have no per-file ACLs; permissions must be evaluated against
the namespace they live in. Several call sites passed the raw media ID
to

fix(auth): scope media ACL checks to the namespace

Media files have no per-file ACLs; permissions must be evaluated against
the namespace they live in. Several call sites passed the raw media ID
to auth_quickaclcheck(), so a page-intended exact-ID rule (e.g. on
wiki:secret.png) could silently apply to a media file sharing that ID.

Introduce mediaAclPath() that builds the correct namespace wildcard
path (handling root-namespace media) and route all media-related ACL
checks through it. Also normalize the lone `:X` sentinel variant in
fetch.functions.php to the standard `:*` form.

fixes: #4647

show more ...

4f32c45b26-May-2026 Andreas Gohr <gohr@cosmocode.de>

GfmLink: allow soft line break inside link text

The label character class explicitly forbade `\n`, so a CommonMark
soft line break inside link text (e.g. `[link with<EOL>more](url)`)
fell through to

GfmLink: allow soft line break inside link text

The label character class explicitly forbade `\n`, so a CommonMark
soft line break inside link text (e.g. `[link with<EOL>more](url)`)
fell through to literal text instead of producing a link. Loosen the
class to accept a bare `\n` as long as it is not followed by a blank
line — soft breaks are spec-allowed inside link text, blank lines are
not, and refusing them also keeps `\n#`-anchored block modes (header,
hr, ...) from being swallowed by a runaway link match.

The `\n` survives into the label string and renders as a literal line
ending in HTML, which browsers display as a single space. This soft
break behavior has been checked against
https://spec.commonmark.org/dingus/

Note that this behavior differs from github where the line break is
rendered as a hard break <br>.

show more ...

65dd204226-May-2026 Andreas Gohr <gohr@cosmocode.de>

GfmEscape: defer \\<EOL> to DW Linebreak in mixed-syntax modes

Both GfmEscape (sort 5) and DW Linebreak (sort 140) can claim the
two backslashes of `\\` followed by space/tab/newline. The lexer's
ti

GfmEscape: defer \\<EOL> to DW Linebreak in mixed-syntax modes

Both GfmEscape (sort 5) and DW Linebreak (sort 140) can claim the
two backslashes of `\\` followed by space/tab/newline. The lexer's
tie-breaker picked GfmEscape, so DW's forced linebreak silently lost
its delimiter under dw+md and md+dw. Add a negative lookahead that
declines `\\[ \t\n]` whenever DW syntax is loaded — pure md keeps
GFM-spec behavior. Mid-line `\\` (UNC paths etc.) still escapes.

show more ...

36ba8ead12-May-2026 Andreas Gohr <andi@splitbrain.org>

test: add tests to catch #4637

Issue #4637 was fixed in 7686f2030b19f948d2e50df1613ef6592fa44b46 but I
didn't like that no test had caught the issue.

This adds a specific test for the issue as a Ha

test: add tests to catch #4637

Issue #4637 was fixed in 7686f2030b19f948d2e50df1613ef6592fa44b46 but I
didn't like that no test had caught the issue.

This adds a specific test for the issue as a HandlerTest. Additionally a
simple smoke test is added that renders the wiki:syntax page and
inspects the created DOM. It's not comprehensive but might help flagging
similar issue in the future.

show more ...

8a34b0d812-May-2026 Andreas Gohr <andi@splitbrain.org>

remove comment about failing tests now that the work is complete

12345678910>>...30