From 0f2f9525e106cd6ff2d6574bb3eb1a2b344ca409 Mon Sep 17 00:00:00 2001 From: E <2974895+eduardomozart@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:01:02 -0300 Subject: [PATCH 01/10] Update bibtex.php --- bibtex.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bibtex.php b/bibtex.php index 04b335f..4987e30 100644 --- a/bibtex.php +++ b/bibtex.php @@ -180,6 +180,12 @@ public function postConnect() { $this->Lexer->addExitPattern($pattern, $this->name); } } + /** + * Required by AbstractMode + */ + public function handle($match, $state, $pos, Doku_Handler $handler) { + return false; + } } //////////////////////////////////////////////////////////////////////////////////////////////////// From aabcafe780394ac043a04c2a522e7c95c6643a36 Mon Sep 17 00:00:00 2001 From: E <2974895+eduardomozart@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:44:44 -0300 Subject: [PATCH 02/10] Fix DokuWiki Mort compatibility errors --- bibtex.php | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/bibtex.php b/bibtex.php index 4987e30..0699430 100644 --- a/bibtex.php +++ b/bibtex.php @@ -49,6 +49,15 @@ private function addBibtexMode($mode) { $this->addMode($mode->getName(), $mode); } + /** + * Override addMode to avoid DokuWiki Mort dependency injection + * which expects a DokuWiki ModeRegistry and Handler. + */ + public function addMode($name, \dokuwiki\Parsing\ParserMode\AbstractMode $Mode) { + $Mode->setLexer($this->lexer); + $this->modes[$name] = $Mode; + } + /** * */ @@ -83,26 +92,32 @@ class refnotes_bibtex_lexer extends \dokuwiki\Parsing\Lexer\Lexer { */ public function parse($text) { $lastMode = ''; + // Offset tracking is required by DokuWiki Mort Lexer which doesn't slice the input string + $offset = 0; - while (is_array($parsed = $this->reduce($text))) { + while (is_array($parsed = $this->reduce($text, $offset))) { list($unmatched, $matched, $mode) = $parsed; + $matchPos = $offset + strlen($unmatched); - if (!$this->dispatchTokens($unmatched, $matched, $mode, 0, 0)) { + if (!$this->dispatchTokens($unmatched, $matched, $mode, $offset, $matchPos)) { return false; } + $newOffset = $matchPos + strlen($matched); + if (empty($unmatched) && empty($matched) && ($lastMode == $this->modeStack->getCurrent())) { return false; } $lastMode = $this->modeStack->getCurrent(); + $offset = $newOffset; } if (!$parsed) { return false; } - return $this->invokeHandler($text, DOKU_LEXER_UNMATCHED, 0); + return $this->invokeHandler(substr($text, $offset), DOKU_LEXER_UNMATCHED, $offset); } /** @@ -141,6 +156,13 @@ public function __construct() { $this->exitPattern = array(); } + /** + * Required by AbstractMode but not used by refnotes custom parser. + */ + public function handle($match, $state, $pos, Doku_Handler $handler) { + return false; + } + /** * */ @@ -180,12 +202,6 @@ public function postConnect() { $this->Lexer->addExitPattern($pattern, $this->name); } } - /** - * Required by AbstractMode - */ - public function handle($match, $state, $pos, Doku_Handler $handler) { - return false; - } } //////////////////////////////////////////////////////////////////////////////////////////////////// From cf592719f636316815ceea456268b8b6e3327fc0 Mon Sep 17 00:00:00 2001 From: E <2974895+eduardomozart@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:12:55 -0300 Subject: [PATCH 03/10] fix(mort): fix Attempt to modify property calls on null by using ModeRegistry::withSubParser --- core.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/core.php b/core.php index 887579a..31e2dc6 100644 --- a/core.php +++ b/core.php @@ -74,6 +74,29 @@ public function exitParsingContext($handler) { * */ public function getInstructions($text) { + if (method_exists($this->handler, 'getModeRegistry')) { + $registry = $this->handler->getModeRegistry(); + $calls = $registry->withSubParser( + array(), + array(), + function ($subParser) use ($text) { + $subParser->getHandler()->reset(); + $subParser->parse($text); + return $subParser->getHandler()->calls; + } + ); + + $filtered = array(); + if (is_array($calls)) { + foreach ($calls as $call) { + if ($call[0] == 'document_start' || $call[0] == 'document_end') continue; + if ($call[0] == 'p_open' || $call[0] == 'p_close') continue; + $filtered[] = $call; + } + } + return $filtered; + } + $this->callWriter = new refnotes_nested_call_writer($this->handler->getCallWriter(), $this->handler); $this->callWriter->connect(); From 36e4ad67bebcbda03c53f677ccc1394c6de2725c Mon Sep 17 00:00:00 2001 From: E <2974895+eduardomozart@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:13:07 -0300 Subject: [PATCH 04/10] fix(mort): fix Fatal error Failed opening required inc/indexer.php --- database.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/database.php b/database.php index 89324c3..c854a89 100644 --- a/database.php +++ b/database.php @@ -106,7 +106,9 @@ private function loadPages() { global $conf; if (file_exists($conf['indexdir'] . '/page.idx')) { - require_once(DOKU_INC . 'inc/indexer.php'); + if (file_exists(DOKU_INC . 'inc/indexer.php')) { + require_once(DOKU_INC . 'inc/indexer.php'); + } $pageIndex = idx_getIndex('page', ''); $namespace = refnotes_configuration::getSetting('reference-db-namespace'); From 7614a0a6d12ff10390fe36370196c76451e05a9e Mon Sep 17 00:00:00 2001 From: E <2974895+eduardomozart@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:20:31 -0300 Subject: [PATCH 05/10] fix(mort): fix Context imbalance causing Call to a member function canHandle() on false --- action.php | 22 ++++++++++++++++++++++ core.php | 23 +++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/action.php b/action.php index 71f02a1..609d948 100644 --- a/action.php +++ b/action.php @@ -59,6 +59,10 @@ public function register($controller) { * */ public function handle($event, $param) { + if ($this->isSubParser()) { + return; + } + refnotes_parser_core::getInstance()->exitParsingContext($event->data); /* We need a new instance of mangler for each event because we can trigger it recursively @@ -68,6 +72,24 @@ public function handle($event, $param) { $mangler->process(); } + + private function isSubParser() { + if (!class_exists('dokuwiki\Parsing\ModeRegistry')) return false; + + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + foreach ($trace as $i => $frame) { + if (isset($frame['function']) && ($frame['function'] === 'withSubParser' || $frame['function'] === 'acquireSubParser')) { + return true; + } + if (isset($frame['class']) && $frame['class'] === 'dokuwiki\Parsing\Parser' && $frame['function'] === 'parse') { + $caller = $trace[$i + 1] ?? array(); + if (isset($caller['function']) && $caller['function'] !== 'p_get_instructions') { + return true; + } + } + } + return false; + } } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core.php b/core.php index 887579a..31e2dc6 100644 --- a/core.php +++ b/core.php @@ -74,6 +74,29 @@ public function exitParsingContext($handler) { * */ public function getInstructions($text) { + if (method_exists($this->handler, 'getModeRegistry')) { + $registry = $this->handler->getModeRegistry(); + $calls = $registry->withSubParser( + array(), + array(), + function ($subParser) use ($text) { + $subParser->getHandler()->reset(); + $subParser->parse($text); + return $subParser->getHandler()->calls; + } + ); + + $filtered = array(); + if (is_array($calls)) { + foreach ($calls as $call) { + if ($call[0] == 'document_start' || $call[0] == 'document_end') continue; + if ($call[0] == 'p_open' || $call[0] == 'p_close') continue; + $filtered[] = $call; + } + } + return $filtered; + } + $this->callWriter = new refnotes_nested_call_writer($this->handler->getCallWriter(), $this->handler); $this->callWriter->connect(); From 0307af1fbb6ee54a14075d6213a06a36a78fbe89 Mon Sep 17 00:00:00 2001 From: E <2974895+eduardomozart@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:30:10 -0300 Subject: [PATCH 06/10] fix(mort): fix Fatal error Failed opening required inc/indexer.php --- database.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/database.php b/database.php index 89324c3..c854a89 100644 --- a/database.php +++ b/database.php @@ -106,7 +106,9 @@ private function loadPages() { global $conf; if (file_exists($conf['indexdir'] . '/page.idx')) { - require_once(DOKU_INC . 'inc/indexer.php'); + if (file_exists(DOKU_INC . 'inc/indexer.php')) { + require_once(DOKU_INC . 'inc/indexer.php'); + } $pageIndex = idx_getIndex('page', ''); $namespace = refnotes_configuration::getSetting('reference-db-namespace'); From b727939329fa3b66866c3960899fc178972bc375 Mon Sep 17 00:00:00 2001 From: E <2974895+eduardomozart@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:36:54 -0300 Subject: [PATCH 07/10] fix(mort): improve sub-parser detection and add fail-safe against empty context array --- action.php | 9 ++++++--- core.php | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/action.php b/action.php index 609d948..0c2a918 100644 --- a/action.php +++ b/action.php @@ -78,12 +78,15 @@ private function isSubParser() { $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); foreach ($trace as $i => $frame) { - if (isset($frame['function']) && ($frame['function'] === 'withSubParser' || $frame['function'] === 'acquireSubParser')) { + $func = $frame['function'] ?? ''; + if ($func === 'withSubParser' || $func === 'acquireSubParser') { return true; } - if (isset($frame['class']) && $frame['class'] === 'dokuwiki\Parsing\Parser' && $frame['function'] === 'parse') { + $class = $frame['class'] ?? ''; + if (($class === 'dokuwiki\Parsing\Parser' || $class === 'Doku_Parser') && $func === 'parse') { $caller = $trace[$i + 1] ?? array(); - if (isset($caller['function']) && $caller['function'] !== 'p_get_instructions') { + $callerFunc = $caller['function'] ?? ''; + if ($callerFunc !== 'p_get_instructions' && $callerFunc !== 'getInstructions') { return true; } } diff --git a/core.php b/core.php index 31e2dc6..5d9d0e0 100644 --- a/core.php +++ b/core.php @@ -67,7 +67,10 @@ public function enterParsingContext() { public function exitParsingContext($handler) { $this->handler = $handler; - unset($this->context[count($this->context) - 1]); + // Fail-safe: NEVER pop the base context, guaranteeing we always have a context to fallback to. + if (count($this->context) > 1) { + unset($this->context[count($this->context) - 1]); + } } /** From fe6cb9c9d120dfee4294a5a3bded9e316ea9a172 Mon Sep 17 00:00:00 2001 From: E <2974895+eduardomozart@users.noreply.github.com> Date: Tue, 21 Jul 2026 14:26:17 -0300 Subject: [PATCH 08/10] Fix Lexer offsets and new Indexer compatibility --- bibtex.php | 6 +++--- database.php | 14 +++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/bibtex.php b/bibtex.php index 0699430..b7696d6 100644 --- a/bibtex.php +++ b/bibtex.php @@ -240,7 +240,7 @@ public function __construct($type) { list($open, $close) = ($type == 'parented') ? array('\(', '\)') : array('{', '}'); - $this->entryPattern[] = '^@\w+\s*' . $open . '(?=.*' . $close . ')'; + $this->entryPattern[] = '@\w+\s*' . $open . '(?=.*' . $close . ')'; $this->exitPattern[] = '\s*(?:' . $close . '|(?=@))'; $this->allowedModes = array('field'); @@ -256,7 +256,7 @@ class refnotes_bibtex_field_mode extends refnotes_bibtex_mode { public function __construct() { parent::__construct(); - $this->entryPattern[] = '^\s*\w[\w-]+\s*=\s*'; + $this->entryPattern[] = '\s*\w[\w-]+\s*=\s*'; $this->exitPattern[] = '\s*(?:,|(?=[\)}@]))'; $this->allowedModes = array('integer_value', 'string_value_quoted', 'string_value_braced', 'concatenation'); @@ -290,7 +290,7 @@ public function __construct($type) { list($open, $close, $exit) = ($type == 'quoted') ? array('"', '"', '"') : array('{', '}', '(?:}|(?=@))'); - $this->entryPattern[] = '^' . $open . '(?=.*' . $close . ')'; + $this->entryPattern[] = $open . '(?=.*' . $close . ')'; $this->exitPattern[] = $exit; $this->allowedModes = array('nested_braces_' . $type); diff --git a/database.php b/database.php index c854a89..c2ebb28 100644 --- a/database.php +++ b/database.php @@ -105,12 +105,24 @@ private function normalizeKeyText($text) { private function loadPages() { global $conf; - if (file_exists($conf['indexdir'] . '/page.idx')) { + $pageIndex = array(); + + if (class_exists('\dokuwiki\Search\Indexer')) { + $indexer = new \dokuwiki\Search\Indexer(); + if (method_exists($indexer, 'getAllPages')) { + $pageIndex = $indexer->getAllPages(); + } elseif (method_exists($indexer, 'getPages')) { + $pageIndex = $indexer->getPages(); + } + } elseif (file_exists($conf['indexdir'] . '/page.idx')) { if (file_exists(DOKU_INC . 'inc/indexer.php')) { require_once(DOKU_INC . 'inc/indexer.php'); } $pageIndex = idx_getIndex('page', ''); + } + + if (!empty($pageIndex)) { $namespace = refnotes_configuration::getSetting('reference-db-namespace'); $namespacePattern = '/^' . trim($namespace, ':') . ':/'; $cache = new refnotes_reference_database_cache(); From cd3a4482c1f45a6b2f44537e9927d3d4c96712c3 Mon Sep 17 00:00:00 2001 From: E <2974895+eduardomozart@users.noreply.github.com> Date: Wed, 22 Jul 2026 18:30:04 -0300 Subject: [PATCH 09/10] Reset lexer mode stack before tokenizing Reinitialize the BibTeX lexer mode stack to the `base` state at the start of parsing. This prevents stale parser state from previous runs in DokuWiki's Mort lexer and ensures each parse begins with a clean, consistent lexer state. --- bibtex.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bibtex.php b/bibtex.php index b7696d6..e7b428f 100644 --- a/bibtex.php +++ b/bibtex.php @@ -95,6 +95,9 @@ public function parse($text) { // Offset tracking is required by DokuWiki Mort Lexer which doesn't slice the input string $offset = 0; + // Reset the mode stack for DokuWiki Mort lexer + $this->modeStack = new \dokuwiki\Parsing\Lexer\StateStack('base'); + while (is_array($parsed = $this->reduce($text, $offset))) { list($unmatched, $matched, $mode) = $parsed; $matchPos = $offset + strlen($unmatched); From bf309d06a7aef32ef249a0d1d794cef678194b21 Mon Sep 17 00:00:00 2001 From: E <2974895+eduardomozart@users.noreply.github.com> Date: Wed, 22 Jul 2026 18:30:04 -0300 Subject: [PATCH 10/10] Reset lexer mode stack before tokenizing Reinitialize the BibTeX lexer mode stack to the `base` state at the start of parsing. This prevents stale parser state from previous runs in DokuWiki's Mort lexer and ensures each parse begins with a clean, consistent lexer state. --- bibtex.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bibtex.php b/bibtex.php index b7696d6..bb52851 100644 --- a/bibtex.php +++ b/bibtex.php @@ -95,6 +95,9 @@ public function parse($text) { // Offset tracking is required by DokuWiki Mort Lexer which doesn't slice the input string $offset = 0; + // Reset the mode stack for DokuWiki Mort lexer + $this->modeStack = new \dokuwiki\Parsing\Lexer\StateStack('base'); + while (is_array($parsed = $this->reduce($text, $offset))) { list($unmatched, $matched, $mode) = $parsed; $matchPos = $offset + strlen($unmatched); @@ -272,7 +275,7 @@ class refnotes_bibtex_integer_value_mode extends refnotes_bibtex_mode { public function __construct() { parent::__construct(); - $this->specialPattern[] = '^\d+'; + $this->specialPattern[] = '\G\d+'; } }