History log of /dokuwiki/inc/ (Results 1 – 25 of 6760)
Revision Date Author Comments
(<<< Hide modified files)
(Show modified files >>>)
6d10fb8614-Jul-2026 Andreas Gohr <gohr@cosmocode.de>

style(changelog): fix overlong line

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

fix(parser): guard row_counter in tablerow_open()

The row counter used for row CSS classes was only initialized in
table_open(). Renderers that open a row without a preceding table_open()
call (such

fix(parser): guard row_counter in tablerow_open()

The row counter used for row CSS classes was only initialized in
table_open(). Renderers that open a row without a preceding table_open()
call (such as the data plugin's empty-result list) triggered an
'Undefined array key row_counter' warning on PHP 8. Initialize it
defensively, mirroring the existing cell_counter handling.

show more ...

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

fix(parser): stop group-quantifier body patterns from exhausting memory

Several patterns repeat a group — (?:…)* / (?:…)+ — over a body that can
span a large region. On the non-JIT PCRE engine each

fix(parser): stop group-quantifier body patterns from exhausting memory

Several patterns repeat a group — (?:…)* / (?:…)+ — over a body that can
span a large region. On the non-JIT PCRE engine each iteration of a
group repetition keeps its own backtracking frame, so a big body retains
one frame per byte: a linear, unbounded memory spike that can fatal a
request (and, with JIT on, trips pcre.jit_stacklimit so the lexer dumps
the rest of the document as literal text). A repetition over a single
item (.* , [^x]+) is optimized by PCRE and unaffected.

Make each body possessive: the repeated group cannot match the delimiter
that follows it, so it always stops at the first closer and never needs
to backtrack.

- media {{…}}, windowssharelink, gfm_quote, gfm_link: possessive body.
- email validation (used by emaillink and mail_isvalid): possessive
local-part and domain groups; a long dotted `a.a.a…` local part or
domain was a ReDoS vector. Match results are unchanged.

show more ...

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

fix(parser): stop unclosed GFM fenced code and emphasis from exhausting memory

Both modes repeat a group over their body, and on the non-JIT PCRE
engine each iteration of a group repetition keeps it

fix(parser): stop unclosed GFM fenced code and emphasis from exhausting memory

Both modes repeat a group over their body, and on the non-JIT PCRE
engine each iteration of a group repetition keeps its own backtracking
frame. On unclosed input the body runs to end of input (or a paragraph
break), the required closer fails, and the engine unwinds frame by
frame: a linear, unbounded memory spike that can fatal a request once
the body grows past the memory limit (and, with JIT on, trips
pcre.jit_stacklimit so the lexer dumps the rest of the document as
literal text).

gfm_code/gfm_file: the fence body (?:(?!CLOSE).)* already stops at the
first valid closer, so no backtracking into it is ever legitimate. Make
it possessive so the no-closer case fails immediately.

gfm_emphasis: its closer lookahead ended with a character the body had
to backtrack to reach (the [^\s*] before the closing *). Verify that
flanking-closer rule with a lookbehind instead, so the [^*] body — which
cannot match the * it stops at — can be possessive too.

Matching is unchanged for well-formed input. The fenced-code patterns
were introduced in b1c59bed2.

show more ...

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

fix(parser): validate inline formatting closers with a single memoized scan

The inline formatting modes only open a span when a valid closer exists
ahead. That check was a lookahead built on CONTENT

fix(parser): validate inline formatting closers with a single memoized scan

The inline formatting modes only open a span when a valid closer exists
ahead. That check was a lookahead built on CONTENT_UNTIL_PARA, tested
character by character up to the next paragraph break and re-evaluated
from scratch for every opener candidate — openers times paragraph
length. With pcre.jit=0 a crafted 32KB page took 16s and an ordinary
34KB page with long paragraphs 37s; with the JIT on (the PHP default)
the per-character lookahead exhausted the JIT stack, the match silently
failed, and the formatting — or everything after it — rendered as plain
text.

The check also decided the wrong thing. It scanned raw text, so a closer
lookalike inside content the lexer consumes atomically — a nowiki or %%
span, a backtick code span, a link, a URL — counted as a real closer
even though the mode's exit pattern can never fire there. And it ignored
the enclosing span: an inner delimiter whose only closer lay past the
closer of the mode it sits in was entered anyway, so a stray delimiter
paired with one in a following sibling span and dragged the boundary
along — the `*` in ''glob/*.conf'' joined the `*` of the next ''...''
span and corrupted the paragraph; the same held for //, ** and __ inside
monospace, and an emphasis opened inside ((...)) ran past the footnote's
)) and the enclosing bold's **.

Each formatting mode now declares its closer through
Lexer::addCloserPattern(), mirroring addExitPattern(), and the lexer
answers "does a valid closer exist ahead" with one anchored possessive
scan per range instead of a lookahead per opener:

- The scan runs left to right from the opener, hopping over opaque spans
derived from already-registered patterns — a plain or special match is
consumed in one step, an entry into a verbatim mode (nowiki, the
backtick code spans) extends to that mode's first exit — so a closer
lookalike inside consumed content is never mistaken for a closer. Each
hop finds the earliest of boundary, closer, or opaque span in a single
leftmost search, keeping the check linear.
- An opener is rejected when the nearest enclosing mode that has a closer
of its own would close before the opener's own closer, so a delimiter
that can never close within its span stays literal. That ancestor is
found by walking the mode stack past modes that declare no closer
(plugins, footnotes); the nearest guarded ancestor suffices, as it was
itself validated against its own when it opened.
- Both verdicts are memoized and reset per parse() run: a proven closer
validates every earlier candidate, and a proven closer-free range
rejects every later candidate before the next boundary. With the lexer
consuming each opened span, the whole parse is linear in document size.

Closer patterns match the closing delimiter itself with flanking context
in lookarounds — the convention exit patterns already follow — so closer
positions compare exactly across modes and a closer directly after an
inner opener is seen. AbstractFormatting derives the closer from the exit
pattern and registers it with the paragraph break as the boundary,
preserving the rule that formatting never spans paragraphs; a mode with
other needs can pass a different boundary or none.

Footnote declares its )) as a closer rather than guarding its (( entry
with a (?=.*)) lookahead, so the footnote becomes a boundary the scan
sees and formatting inside it no longer pairs across the )); its closer
takes no paragraph boundary, as footnotes are block-level. GfmEmphasis
gains a closer pattern so single * emphasis is validated the same way,
while its entry lookahead still enforces CommonMark nearest-delimiter
pairing. GfmEmphasis and GfmStrong span bodies cannot contain their
delimiter, so their in-pattern lookaheads stay linear on their own; the
GFM backtick span bodies get deterministic alternatives with possessive
quantifiers, removing their per-character backtracking.

CONTENT_UNTIL_PARA is removed: any entry pattern built with it recreates
the quadratic scan. ParallelRegex gains escapePattern() so embedded
closer fragments follow the lexer's bare-parenthesis convention, reports
PREG_JIT_STACKLIMIT_ERROR so a future JIT exhaustion surfaces instead of
silently truncating, and no longer rewrites its registered patterns in
place while compiling the compound regex.

The adversarial 32KB page drops to 0.1s, the 37-second benign page to
0.1s, and a 128KB variant stays under 0.6s.

show more ...

1ed5d39808-Jul-2026 splitbrain <86426+splitbrain@users.noreply.github.com>

�� Rector and PHPCS fixes

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

fix(search): lock the metadata registry read-modify-write

updateMetadataRegistry() read metadata.idx, merged in new keys and wrote
the file back without holding a lock across the sequence. Two index

fix(search): lock the metadata registry read-modify-write

updateMetadataRegistry() read metadata.idx, merged in new keys and wrote
the file back without holding a lock across the sequence. Two indexer
processes each registering a different new key could both read the same
registry and the later writer would clobber the other's key, dropping it.
A dropped key is not cleared from its collection on deletePage(), leaving
orphaned index entries.

Guard the whole read-merge-write with a dedicated metadata lock so
concurrent registrations can no longer lose a key.

show more ...

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

fix(search): make DirectCollection::getEntitiesWithData() linear

The method resolved each entity name with a separate retrieveRow() call,
each of which rescans the entity index from the start, makin

fix(search): make DirectCollection::getEntitiesWithData() linear

The method resolved each entity name with a separate retrieveRow() call,
each of which rescans the entity index from the start, making the whole
operation quadratic in the number of entities. This runs on
MetadataSearch::getPages('title') and hurts large wikis. Collect the entity
IDs in one pass and resolve their names with a single batched retrieveRows()
read instead.

show more ...

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

fix(search): stop FileIndex::retrieveRows() scanning past the last row

The early-exit check compared the array_shift() result against false, but
array_shift() returns null on an empty array, so the

fix(search): stop FileIndex::retrieveRows() scanning past the last row

The early-exit check compared the array_shift() result against false, but
array_shift() returns null on an empty array, so the break never fired and
every call read the index file to EOF after collecting the last requested
row. Compare against null and skip the file entirely when nothing is
requested.

show more ...

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

fix(search): wait for a contended index lock and apply dperm

The Lock rewrite (c66b5ec65) made Lock::acquire() fail immediately when the
lock directory already existed, where the old indexer lock re

fix(search): wait for a contended index lock and apply dperm

The Lock rewrite (c66b5ec65) made Lock::acquire() fail immediately when the
lock directory already existed, where the old indexer lock retried until the
holder released it. Concurrent saves or indexer runs then aborted indexing
instead of serializing (recoverable only via the .indexed tag on the next
edit), and the lock directory was created without the configured directory
permissions, unlike every other directory DokuWiki creates.

Mirror the io_lock() convention: wait for a contended lock, bounded by a
tunable wait timeout, before throwing; keep clearing locks older than five
minutes as stale; and chmod the new lock directory to $conf['dperm']. The
give-up path still throws IndexLockException, so the self-healing retry via
the .indexed tag is unchanged.

show more ...

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

fix(remote): make aclCheck self-check case-insensitive-backend aware

The check gating aclCheck() for other users compared the requested user
against REMOTE_USER verbatim. On a case-insensitive auth

fix(remote): make aclCheck self-check case-insensitive-backend aware

The check gating aclCheck() for other users compared the requested user
against REMOTE_USER verbatim. On a case-insensitive auth backend a user
naming themselves in a different case than their login was treated as a
different user and wrongly denied checking their own ACL. Normalize both
names the way auth_isMember() does before comparing.

The self-check was introduced in 884caed92.

show more ...

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

fix(infoutils): use git --pretty=reference to avoid Windows escaping

On Windows escapeshellarg() replaces percent signs with spaces, so the
--pretty=format:%h %cd argument reached git as literal tex

fix(infoutils): use git --pretty=reference to avoid Windows escaping

On Windows escapeshellarg() replaces percent signs with spaces, so the
--pretty=format:%h %cd argument reached git as literal text. Git then
echoed the format string verbatim, a truthy value that made the version
display report an empty hash and the date "h" while skipping the working
.git-directory fallback.

The named --pretty=reference format carries no percent signs on the
command line, so the shell path works on Windows too. Its "hash
(subject, date)" output is validated before being trusted.

Introduced in b9e35b2f0.

show more ...

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

fix(feed): surface real fetch error instead of "unsupported status code"

DokuHTTPClient reports transport failures with negative pseudo statuses
while keeping the descriptive message in its error fi

fix(feed): surface real fetch error instead of "unsupported status code"

DokuHTTPClient reports transport failures with negative pseudo statuses
while keeping the descriptive message in its error field. SimplePie only
turns a file error into a reported error when the status code is zero, so
connection, SSL and timeout failures surfaced as "unsupported status code
-100" under allowdebug, hiding the real cause. Clamp the pseudo statuses
to zero so the descriptive error reaches the feed output.

show more ...

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

fix(httputils): canonicalize paths when building X-Accel-Redirect URLs

http_xaccel_url() compared the fullpath()-collapsed target file against a
non-canonicalized DOKU_INC and the raw savedir config

fix(httputils): canonicalize paths when building X-Accel-Redirect URLs

http_xaccel_url() compared the fullpath()-collapsed target file against a
non-canonicalized DOKU_INC and the raw savedir config value. The lib/exe entry
points define DOKU_INC as __DIR__.'/../../' and savedir defaults to the relative
'./data', so the in-tree prefix check never matched: nginx installs using
X-Accel-Redirect served in-tree files behind the /_x_accel_redirect/ escape
hatch and returned 404 without extra location config.

Canonicalize DOKU_INC and every configured root with fullpath() before the
prefix comparisons so they match the already-canonicalized file path.

The comparison lost its fullpath() wrapping in b8c2692f7, which replaced the
earlier substr($file, strlen(fullpath(DOKU_INC)) + 1) with a raw DOKU_INC
prefix check.

show more ...

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

docs(parser): fix GfmListblock sub-parser docblock

The class docblock claimed the per-item loop runs inside a
ModeRegistry::withSubParser() callback, but the code acquires a pooled
sub-parser direct

docs(parser): fix GfmListblock sub-parser docblock

The class docblock claimed the per-item loop runs inside a
ModeRegistry::withSubParser() callback, but the code acquires a pooled
sub-parser directly and releases it after the loop. Describe the actual
mechanism.

show more ...

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

chore(changelog): rename $clogRev to $recordedRev

It's a bit easier to decipher

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

fix(parser): treat quotes next to bold/underline markup as smart quotes

With typography=2 a single-quoted phrase wrapped in strong or underline
markup (**'x'** / __'x'__) rendered apostrophes instea

fix(parser): treat quotes next to bold/underline markup as smart quotes

With typography=2 a single-quoted phrase wrapped in strong or underline
markup (**'x'** / __'x'__) rendered apostrophes instead of opening and
closing quotes: the lexer matches against the full subject at a byte
offset, so the singlequoteopening lookbehind sees the consumed * or _
delimiter and fails, letting apostrophe win. Add * and _ to the quote
boundary set, as / (emphasis) already was, so the surrounding markup is
recognised as a word boundary.

show more ...

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

fix(info): read parser modes from the registry, not the deprecated global

The info plugin's ~~INFO:syntaxtypes~~ read the $PARSER_MODES global
directly. Since the mode taxonomy moved into ModeRegist

fix(info): read parser modes from the registry, not the deprecated global

The info plugin's ~~INFO:syntaxtypes~~ read the $PARSER_MODES global
directly. Since the mode taxonomy moved into ModeRegistry (c8dd1b9d2),
that global is only populated as a side effect of a parse, so on a warm
instruction cache with no parse in the request it is unset and the
syntax-types table renders empty. ~~INFO:syntaxmodes~~ used the
deprecated p_get_parsermodes(), logging a deprecation on every render.

Both now build a ModeRegistry and read the categories and modes from it
directly, independent of parse or cache state.

show more ...

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

feat(admin): show template and syntax on admin info

two more values we always ask for when debugging

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

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

12345678910>>...271