History log of /dokuwiki/ (Results 26 – 50 of 10912)
Revision Date Author Comments
(<<< Hide modified files)
(Show modified files >>>)
315c18b107-Jul-2026 Andreas Gohr <gohr@cosmocode.de>

fix(subscription): use a literal & separator in plain-text list mail diff links

The diff link in 'list' subscription mails was built once with wl()'s
default &amp; separator and reused in both the H

fix(subscription): use a literal & separator in plain-text list mail diff links

The diff link in 'list' subscription mails was built once with wl()'s
default &amp; separator and reused in both the HTML and the plain-text
list. In a plain-text mail client the entity is not decoded, so clicking
the link sends do=diff&amp;rev=... and the revision is parsed as a bogus
amp;rev parameter, losing the intended revision. Build a separate
plain-text link with a literal & separator, matching the adjacent
subscribe link.

show more ...

42685d0d07-Jul-2026 Andreas Gohr <gohr@cosmocode.de>

fix(feed): flatten repeated HTTP response headers when fetching feeds

DokuHTTPClient stores a response header that occurs more than once (e.g.
multiple Set-Cookie headers) as a nested array. FeedPar

fix(feed): flatten repeated HTTP response headers when fetching feeds

DokuHTTPClient stores a response header that occurs more than once (e.g.
multiple Set-Cookie headers) as a nested array. FeedParserFile cast each
header value to a string, turning those into the literal "Array" and
emitting an "Array to string conversion" warning on every such fetch.
Normalize each value on its own so repeated headers keep all their values
in SimplePie's list representation.

show more ...

5cf0f76c07-Jul-2026 Andreas Gohr <gohr@cosmocode.de>

ci: enable the gd extension in the test workflows

The Windows runner had no image backend, so the media resize helpers
threw and returned the original file. This went unnoticed until
c3a14f677 added

ci: enable the gd extension in the test workflows

The Windows runner had no image backend, so the media resize helpers
threw and returned the original file. This went unnoticed until
c3a14f677 added the first test asserting real resized dimensions.
Enable gd on both the Windows and Linux workflows so the media
subsystem is actually exercised instead of relying on gd being present
by chance.

show more ...

c3a14f6706-Jul-2026 Andreas Gohr <gohr@cosmocode.de>

fix(media): don't upscale small images in the detail previews

The detail page, media manager and media diff requested a bounding-box
resize at the full box size, so an image smaller than the box was

fix(media): don't upscale small images in the detail previews

The detail page, media manager and media diff requested a bounding-box
resize at the full box size, so an image smaller than the box was enlarged
and fetch.php generated and cached the upscaled copy.

slika 1.2 adds an $upscale option to resize/crop. getDisplayDimensions()
now takes a $fit flag mirroring fetch.php one to one and predicts the
matching no-upscale dimensions, media_resize_image()/media_mod_image()
forward $upscale, and fetch.php disables upscaling for fit=1 requests.
In-page image scaling is unchanged.

show more ...

7595d8da06-Jul-2026 splitbrain <86426+splitbrain@users.noreply.github.com>

�� Rector and PHPCS fixes

5a285deb06-Jul-2026 Andreas Gohr <gohr@cosmocode.de>

fix(changelog): stop media mtime bumps from recording bogus external edits

Commit 01e8d739c ("persist external-edit detection on first read") began writing
detected external edits to the changelog a

fix(changelog): stop media mtime bumps from recording bogus external edits

Commit 01e8d739c ("persist external-edit detection on first read") began writing
detected external edits to the changelog and snapshotting the new content to the
attic via saveExternalAttic(). That is correct for pages, which archive every
revision, but wrong for media: a media file's current revision is never archived
(media only copies content to the attic on replace or delete, to save space).

So getCurrentRevisionInfo()'s external-change detection had no attic copy of the
current revision to work with: the unchanged-content guard always failed and the
old-size lookup returned 0. A mere mtime bump (touch, rsync --times, unzip) of an
unchanged media file was therefore recorded as an external edit sized as the whole
file, and since detection is now persisted, that bogus entry (plus a redundant
attic copy) became permanent.

The "before" size of an external change is now taken through a lastRevisionSize()
seam: the base reads it from the last revision's attic copy (unchanged for pages),
and MediaChangeLog reconstructs it from the previous revision's archived size plus
the size change logged for the last revision. currentContentMatchesRevision()
compares the current file size against that reconstructed size, so an unchanged
size is treated as a mere touch (mtime reset, no entry) and a changed size is a
real external edit with the correct size change.

Media no longer snapshots an external edit to the attic at all, consistent with
not archiving the current revision: it will be archived if and when the file is
later replaced. The mtime repair for an unreliable external date moved to a base
repairExternalMtime() shared by pages and media.

show more ...

6372bc8006-Jul-2026 Andreas Gohr <gohr@cosmocode.de>

fix(changelog): detect an externally restored deleted page as a re-creation

A deleted page restored outside DokuWiki with a preserved mtime predating the
deletion (cp -p from an old backup, rsync --

fix(changelog): detect an externally restored deleted page as a re-creation

A deleted page restored outside DokuWiki with a preserved mtime predating the
deletion (cp -p from an old backup, rsync --times) has content matching the
delete revision's attic copy. That copy holds the pre-delete content, so the
unchanged-content shortcut in getCurrentRevisionInfo() wrongly treated the
restore as an unchanged file, touched the mtime back to the delete revision and
kept the DELETE as the current revision. The page then stayed "deleted" on every
subsequent read.

A last recorded revision of type DELETE means the page did not exist then, so an
existing file is an external re-creation regardless of its mtime.
getCurrentRevisionInfo() now classifies the external change and delegates to
synthesizeExternalDeletion/synthesizeExternalCreate/synthesizeExternalEdit; only
the edit path keeps the unchanged-content shortcut. A re-creation records the full
file as its size change and, when the restored mtime predates the delete, is dated
just after it with an unknown date.

The getCurrentRevisionInfo() method got rather unwieldy, so it was refactored to
use three helper methods, for synthesizing the changelog data.

show more ...

9a38b8db05-Jul-2026 Andreas Gohr <andi@splitbrain.org>

fix(parser): keep a mode's directly-assigned allowedModes when it also declares categories

setModeRegistry() replaced $this->allowedModes with the category-derived list
whenever a mode declared cate

fix(parser): keep a mode's directly-assigned allowedModes when it also declares categories

setModeRegistry() replaced $this->allowedModes with the category-derived list
whenever a mode declared categories, discarding any mode names the subclass had
assigned to the property directly. The old SyntaxPlugin::accepts() merged the
category modes into the existing list instead, so a mode using the
sibling-component pattern - a non-empty getAllowedTypes() plus
$this->allowedModes[] = 'plugin_foo_bar' in its constructor - kept both and its
sibling syntax nested as intended.

Merge the category-derived modes into the directly-assigned list rather than
replacing it, then deduplicate. The no-categories path is unchanged: the
directly-assigned list is used as-is.

show more ...

945681fc05-Jul-2026 Andreas Gohr <andi@splitbrain.org>

fix(parser): keep an indented list from being swallowed by a table

The parser rework dropped the list-awareness lookahead from preformatted's
entry patterns ('\n (?![\*\-])' became '\n '). In base

fix(parser): keep an indented list from being swallowed by a table

The parser rework dropped the list-awareness lookahead from preformatted's
entry patterns ('\n (?![\*\-])' became '\n '). In base mode this is masked
by sort order - listblock sorts before preformatted and wins the tie - but
table mode connects preformatted as a protected sub-mode without connecting
any list mode. Inside a table an indented list line therefore entered
preformatted instead of ending the table, and the table rewriter discarded the
resulting call: the list vanished from the output and the table's section-edit
range grew to cover it, so saving via the inline table editor overwrote the
list text.

Restore the lookahead. For DokuWiki syntax it is the original '(?![\*\-])'
(Listblock treats a bare * or - as a marker). For Markdown-preferred syntax the
guard follows GfmListblock, rejecting -, *, + and ordered markers only when
followed by whitespace or end of line, so mixed dw+md / md+dw wikis are fixed
too while an indented code block that merely starts with such a character (e.g.
a *** line) still parses as code.

show more ...

ebaa848105-Jul-2026 Andreas Gohr <andi@splitbrain.org>

fix(search): restore histogram() as a Collection method with a BC shim

The indexer rework dropped Doku_Indexer::histogram(), which plugins reach
through idx_get_indexer()->histogram() to build tag c

fix(search): restore histogram() as a Collection method with a BC shim

The indexer rework dropped Doku_Indexer::histogram(), which plugins reach
through idx_get_indexer()->histogram() to build tag clouds (e.g. tagfilter
via the 'subject' metadata index). Calling it fatalled through
LegacyIndexer::__call as an undefined method.

Add histogram() to AbstractCollection: it sums each token's frequency across
all entities and returns them ordered by frequency, filtered by min/max/minlen,
handling both length-split (fulltext) and single-group (metadata) collections.
DirectCollection overrides it for the 1:1 title case (count shared values).
LegacyIndexer::histogram() is a deprecated shim dispatching by $key to the
matching collection.

show more ...

21d6f17605-Jul-2026 Andreas Gohr <andi@splitbrain.org>

fix(parser): restore BC for manual Handler/Parser construction

The parser rework made ModeRegistry a required constructor argument on
Handler and Parser and dropped the Doku_Parser class alias, brea

fix(parser): restore BC for manual Handler/Parser construction

The parser rework made ModeRegistry a required constructor argument on
Handler and Parser and dropped the Doku_Parser class alias, breaking the
long-documented manual sub-parsing pattern used by many plugins
(new Doku_Handler(); new Parser($handler); ...): both threw
ArgumentCountError and new Doku_Parser() fatalled as an unknown class.

Default the ModeRegistry argument on both constructors: Handler builds a
registry for the configured syntax, Parser takes it from the handler.
Re-add the Doku_Parser alias. Constructing without a registry emits a
deprecation pointing at p_get_instructions().

show more ...

e248eb6f05-Jul-2026 Andreas Gohr <andi@splitbrain.org>

fix(parser): don't drop document content after a blank indented line

Preformatted's exit pattern (?=\n[^ \t\n]) is a lookahead-only exit: it
consumes no byte so the boundary \n stays in the stream f

fix(parser): don't drop document content after a blank indented line

Preformatted's exit pattern (?=\n[^ \t\n]) is a lookahead-only exit: it
consumes no byte so the boundary \n stays in the stream for a following block
mode (an <hr> or header after an indented code block) to anchor on.

When that exit fired with nothing else consumed - a "blank" line that is empty
after its indent, followed by a column-0 line - the match was zero-width at the
current offset and the lexer's no-advance guard aborted the whole parse,
silently discarding the rest of the document. Reachable on ordinary trailing
whitespace ("abc\n \nmore") and inside footnotes.

Exempt zero-width MODE_EXIT matches from the guard: popping the mode stack is
real progress and leaves the boundary byte for the parent mode to consume on
the next iteration. The stack strictly shrinks on each such exit, so this
cannot loop; the infinite-loop protection is unchanged for every other
zero-width match. Preformatted's pattern is left as-is, so <hr>/header after an
indented code block still works, including after a blank indented line.

Add regressions for the blank-line, blank-line-after-code, and
boundary-preservation (hr after a blank indented line) cases.

show more ...

9e4de2f705-Jul-2026 Andreas Gohr <andi@splitbrain.org>

fix(search): don't let collections release a caller-owned index lock

Indexer::addPage() locks the page index once and hands the same instance to
several collections, each of which lock() and unlock(

fix(search): don't let collections release a caller-owned index lock

Indexer::addPage() locks the page index once and hands the same instance to
several collections, each of which lock() and unlock() it around their work.

AbstractCollection::lock() locked and tracked every index for later release.
When a shared AbstractIndex was already locked by the caller, locking it again
was a no-op but the collection still tracked it, so the collection's unlock()
released the caller's lock and flipped the shared index read-only. Each later
collection then re-acquired and re-released it, leaving windows in which another
process could grab the page lock mid-operation; contention could abort addPage()
with the title index updated but the fulltext index stale.

Treat a shared index that is already writable as owned by its creator: leave its
lock untouched and only manage the locks the collection acquires itself.

Add a regression test asserting a caller-held index lock survives repeated
collection lock()/unlock() cycles.

show more ...

4e61aa7105-Jul-2026 Andreas Gohr <andi@splitbrain.org>

fix(search): create index lock directories inside data/locks

Index locks were built by concatenating the configured lock directory and
the index name without a path separator.

That produced paths l

fix(search): create index lock directories inside data/locks

Index locks were built by concatenating the configured lock directory and
the index name without a path separator.

That produced paths like data/lockspage.index instead of
data/locks/page.index, so index lock directories were created in the
data root rather than inside the configured lock directory.

Fix this by joining the lock directory and index name with an explicit
slash, and update the lock test to assert the correct location and guard
against the old broken path.

show more ...

bc12b8fe05-Jul-2026 Andreas Gohr <andi@splitbrain.org>

fix(search): read split index suffixes correctly

AbstractIndex::max() used /(\d)+\.idx$/ to determine the highest split
index suffix. For filenames like w10.idx or w12.idx that regex captured
only t

fix(search): read split index suffixes correctly

AbstractIndex::max() used /(\d)+\.idx$/ to determine the highest split
index suffix. For filenames like w10.idx or w12.idx that regex captured
only the last digit, so the maximum token-length group was truncated to 9.
Wildcard searches therefore skipped all fulltext index shards for words
with 10 or more characters.

Tighten the match to the basename and require the current index name
followed immediately by digits: ^<idx>(\d+)\.idx$. This captures the full
numeric suffix and also avoids counting unrelated index families that
share the same prefix, such as treating wiki2.idx as a numbered shard of
the w index.

Add regressions for both behaviors: multi-digit suffixes are parsed
correctly, and same-prefix indexes are ignored when determining the
maximum shard number.

show more ...

b1f7ba6405-Jul-2026 Andreas Gohr <andi@splitbrain.org>

fix(search): fatals on invalid short wildcard terms

Searches such as "wiki a*" could crash in QueryEvaluator::opAnd() with
a TypeError. The query parser still emitted the wildcard term, but the
full

fix(search): fatals on invalid short wildcard terms

Searches such as "wiki a*" could crash in QueryEvaluator::opAnd() with
a TypeError. The query parser still emitted the wildcard term, but the
fulltext search later dropped it because its non-wildcard base was below
the minimum search term length.

That left the evaluator with an AND operator whose right-hand operand was
missing, causing a stack underflow during RPN evaluation.

Fix this in two places:
- filter invalid short wildcard terms already in Tokenizer::getWords()
when wildcard parsing is enabled, so they never enter the parsed query
- harden QueryEvaluator against missing operands for AND/OR/NOT to avoid
fatals if tokens disappear during later processing

Add regression tests covering the parser output for "wiki a*" and the
evaluator behavior when an operand is missing.

show more ...

e282ae5505-Jul-2026 Andreas Gohr <andi@splitbrain.org>

fix(search): search index corruption in updateTuple

Require an end-of-tuple boundary when removing an existing tuple in
TupleOps::updateTuple(), so updating row ID 17 no longer corrupts
tuples for I

fix(search): search index corruption in updateTuple

Require an end-of-tuple boundary when removing an existing tuple in
TupleOps::updateTuple(), so updating row ID 17 no longer corrupts
tuples for IDs like 170 or 171.

Add regression tests covering both replacement and deletion when one
numeric row ID is a decimal prefix of another.

show more ...

4571724203-Jul-2026 Andreas Gohr <andi@splitbrain.org>

Skip JPEG markers too short to hold the probed signature

_parseMarkerXmp() and the other signature-probing loops read a
fixed-length signature from every marker of a given type to identify it.
Since

Skip JPEG markers too short to hold the probed signature

_parseMarkerXmp() and the other signature-probing loops read a
fixed-length signature from every marker of a given type to identify it.
Since _getFixedString() throws on a short read, a valid but short APP1
segment (e.g. a 22-byte non-XMP APP1) made the XMP probe throw while
reading 29 bytes. That was logged as a damaged image and aborted the
marker loop, discarding any real XMP/EXIF segment that followed.

The issue was introduced in c9c56f8a0.

This is another symptom fix until the library is replaced as discussed
in #1970

Skip markers too short to contain the signature so probing continues
instead of failing. Fixes #4526.

show more ...

baea39e303-Jul-2026 Andreas Gohr <andi@splitbrain.org>

Handle array EXIF date values

This is a fix for a warning thrown by the EXIF library. It fixes a
specific symptom without addressing the actual issue of the library
being outdated and in need of rep

Handle array EXIF date values

This is a fix for a warning thrown by the EXIF library. It fixes a
specific symptom without addressing the actual issue of the library
being outdated and in need of replacement (as discussed in #1970)

fixes #4628

Co-authored by Zheyon <110686204+Zhey-on@users.noreply.github.com>

show more ...

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

Cache: key on resolved base URL instead of the raw Host header

The instruction (.i), render (.xhtml) and metadata cache keys embedded
the client-supplied HTTP_HOST and SERVER_PORT, as did the JS and

Cache: key on resolved base URL instead of the raw Host header

The instruction (.i), render (.xhtml) and metadata cache keys embedded
the client-supplied HTTP_HOST and SERVER_PORT, as did the JS and CSS
dispatchers. A crawler varying the Host header - or a wiki reachable
under several names - thus multiplied every cached artifact per host
value, growing the cache directory without bound.

Instructions and metadata never embed resolved URLs, so they drop the
host component entirely. Rendered output and the JS/CSS bundles key
on DOKU_BASE (plus the SSL flag for JS) instead: fixed when baseurl is
configured, and only varying for genuine multi-domain canonical setups.
Feeds keep host/port keying since their links are always absolute.

This should address the obvious cases mentioned in #4574

show more ...

3e37eccf03-Jul-2026 Andreas Gohr <andi@splitbrain.org>

Harden media manager select callback (defense-in-depth)

The media manager popup derives the select callback name and the target
edit-field id from URL parameters (onselect, edid) and calls
opener[cb

Harden media manager select callback (defense-in-depth)

The media manager popup derives the select callback name and the target
edit-field id from URL parameters (onselect, edid) and calls
opener[cb](edid, ...). Tighten this handling:

- sanitize edid to word characters and hyphens
- strip all non-word characters from the callback name (global replace,
not just the first run)
- refuse to call anything that is not a page-script function, rejecting
browser/JS built-ins such as eval, Function, setTimeout and open

This is a precaution, not a fix for an exploitable vulnerability. In core
the popup is only ever opened by the trusted, same-origin editor, which
builds the URL itself and never sets onselect; a directly loaded popup has
no opener and a cross-origin opener is blocked by the same-origin policy,
so an attacker cannot reach the sink. The change removes the reliance on
that ambient browser protection and guards plugins that open the popup
with their own callback.

show more ...

ed28f99001-Jul-2026 Andreas Gohr <gohr@cosmocode.de>

use actionOK() to gate the register action

The Register action duplicated the openregister and addUser capability
checks that actionOK('register') already performs. Delegate to it so the
action gate

use actionOK() to gate the register action

The Register action duplicated the openregister and addUser capability
checks that actionOK('register') already performs. Delegate to it so the
action gate and register() itself share a single source of truth.

See https://github.com/dokuwiki/dokuwiki/issues/4682#issuecomment-4854987500

show more ...

aa331cf229-Jun-2026 Martin Růžička <martinr@post.cz>

Translation update (cs)

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

some clarification on the syntax page

With merging the changes for the markdown support some smaller changes
in how the DokuWiki syntax is parsed were introduced. This adds the most
important rules

some clarification on the syntax page

With merging the changes for the markdown support some smaller changes
in how the DokuWiki syntax is parsed were introduced. This adds the most
important rules to the syntax page.

show more ...

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

fix duplicated TOC caused by corrupted persistent metadata (#3179)

The table of contents is appended to the metadata on every render. When a
tableofcontents entry leaks into the persistent metadata

fix duplicated TOC caused by corrupted persistent metadata (#3179)

The table of contents is appended to the metadata on every render. When a
tableofcontents entry leaks into the persistent metadata (e.g. via buggy
plugin versions or callers of p_set_metadata that write back a full
description array), document_start() copied it into the current metadata
before the headings re-appended it, permanently duplicating the TOC.

The duplication was only visible when the XHTML output was served from
cache, since a fresh render populates the global $TOC from a single pass.
This explains why purging the cache or editing the page fixed it
temporarily while a reload brought it back.

Strip the tableofcontents from the persistent metadata in document_start()
so already-corrupted .meta files heal themselves on the next render.

show more ...

12345678910>>...437