1<?php
2
3/**
4 * Plugin RefNotes: Reference collector/renderer
5 *
6 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
7 * @author     Mykola Ostrovskyy <dwpforge@gmail.com>
8 */
9
10require_once(DOKU_PLUGIN . 'refnotes/core.php');
11require_once(DOKU_PLUGIN . 'refnotes/bibtex.php');
12
13////////////////////////////////////////////////////////////////////////////////////////////////////
14class syntax_plugin_refnotes_references extends DokuWiki_Syntax_Plugin {
15    use refnotes_localization_plugin;
16
17    private $mode;
18    private $entryPattern;
19    private $exitPattern;
20    private $handlePattern;
21    private $noteCapture;
22
23    /**
24     * Constructor
25     */
26    public function __construct() {
27        refnotes_localization::initialize($this);
28
29        $this->mode = substr(get_class($this), 7);
30        $this->noteCapture = new refnotes_note_capture();
31
32        $this->initializePatterns();
33    }
34
35    /**
36     *
37     */
38    private function initializePatterns() {
39        if (refnotes_configuration::getSetting('replace-footnotes')) {
40            $entry = '(?:\(\(|\[\()';
41            $exit = '(?:\)\)|\)\])';
42            $id = '@@FNT\d+|#\d+';
43        }
44        else {
45            $entry = '\[\(';
46            $exit = '\)\]';
47            $id = '#\d+';
48        }
49
50        $strictName = refnotes_note::getNamePattern('strict');
51        $extendedName = refnotes_note::getNamePattern('extended');
52        $namespace = refnotes_namespace::getNamePattern('optional');
53
54        $text = '.*?';
55
56        $strictName = '(?:' . $id . '|' . $strictName . ')';
57        $fullName = '\s*(?:' . $namespace . $strictName . '|:' . $namespace . $extendedName . ')\s*';
58        $lookaheadExit = '(?=' . $exit . ')';
59        $nameEntry = $fullName . $lookaheadExit;
60
61        $extendedName = '(?:' . $id . '|' . $extendedName . ')';
62        $optionalFullName = $namespace . $extendedName . '?';
63        $structuredEntry = '\s*' . $optionalFullName . '\s*>>' . $text  . $lookaheadExit;
64
65        $define = '\s*' . $optionalFullName . '\s*>\s*';
66        $optionalDefine = '(?:' . $define . ')?';
67        $lookaheadExit = '(?=' . $text . $exit . ')';
68        $defineEntry = $optionalDefine . $lookaheadExit;
69
70        $this->entryPattern = $entry . '(?:' . $nameEntry . '|' . $structuredEntry . '|' . $defineEntry . ')';
71        $this->exitPattern = $exit;
72        $this->handlePattern = '/' . $entry . '\s*(' . $optionalFullName . ')\s*(?:>>(.*))?(.*)/s';
73    }
74
75    /**
76     * What kind of syntax are we?
77     */
78    public function getType() {
79        return 'formatting';
80    }
81
82    /**
83     * What modes are allowed within our mode?
84     */
85    public function getAllowedTypes() {
86        return array (
87            'formatting',
88            'substition',
89            'protected',
90            'disabled'
91        );
92    }
93
94    /**
95     * Where to sort in?
96     */
97    public function getSort() {
98        return 145;
99    }
100
101    public function connectTo($mode) {
102        refnotes_parser_core::getInstance()->registerLexer($this->Lexer);
103
104        $this->Lexer->addEntryPattern($this->entryPattern, $mode, $this->mode);
105    }
106
107    public function postConnect() {
108        $this->Lexer->addExitPattern($this->exitPattern, $this->mode);
109    }
110
111    /**
112     * Handle the match
113     */
114    public function handle($match, $state, $pos, Doku_Handler $handler) {
115        $result = refnotes_parser_core::getInstance()->canHandle($state);
116
117        if ($result) {
118            switch ($state) {
119                case DOKU_LEXER_ENTER:
120                    $result = $this->handleEnter($match);
121                    break;
122
123                case DOKU_LEXER_EXIT:
124                    $result = $this->handleExit();
125                    break;
126            }
127        }
128
129        if ($result === false) {
130            $handler->addCall('cdata', array($match), $pos);
131        }
132
133        return $result;
134    }
135
136    /**
137     * Create output
138     */
139    public function render($mode, Doku_Renderer $renderer, $data) {
140        $result = false;
141
142        try {
143            switch ($mode) {
144                case 'xhtml':
145                case 'odt':
146                    $result = $this->renderReferences($mode, $renderer, $data);
147                    break;
148
149                case 'metadata':
150                    $result = $this->renderMetadata($renderer, $data);
151                    break;
152            }
153        }
154        catch (Exception $error) {
155            msg($error->getMessage(), -1);
156        }
157
158        return $result;
159    }
160
161    /**
162     *
163     */
164    private function handleEnter($syntax) {
165        if (preg_match($this->handlePattern, $syntax, $match) == 0) {
166            return false;
167        }
168
169        refnotes_parser_core::getInstance()->enterReference($match[1], $match[2]);
170
171        return array('start');
172    }
173
174    /**
175     *
176     */
177    private function handleExit() {
178        $reference = refnotes_parser_core::getInstance()->exitReference();
179
180        if ($reference->hasData()) {
181            return array('render', $reference->getAttributes(), $reference->getData());
182        }
183        else {
184            return array('render', $reference->getAttributes());
185        }
186    }
187
188    /**
189     *
190     */
191    public function renderReferences($mode, $renderer, $data) {
192        switch ($data[0]) {
193            case 'start':
194                $this->noteCapture->start($renderer);
195                break;
196
197            case 'render':
198                $this->renderReference($mode, $renderer, $data[1], (count($data) > 2) ? $data[2] : array());
199                break;
200        }
201
202        return true;
203    }
204
205    /**
206     * Stops renderer output capture and renders the reference link
207     */
208    private function renderReference($mode, $renderer, $attributes, $data) {
209        $reference = refnotes_renderer_core::getInstance()->addReference($attributes, $data);
210        $text = $this->noteCapture->stop();
211
212        if ($text != '') {
213            $reference->getNote()->setText($text);
214        }
215
216        $renderer->doc .= $reference->render($mode);
217    }
218
219    /**
220     *
221     */
222    public function renderMetadata($renderer, $data) {
223        if ($data[0] == 'render') {
224            $source = '';
225
226            if (array_key_exists('source', $data[1])) {
227                $source = $data[1]['source'];
228            }
229
230            if (($source != '') && ($source != '{configuration}')) {
231                $renderer->meta['plugin']['refnotes']['dbref'][wikiFN($source)] = true;
232            }
233        }
234
235        return true;
236    }
237}
238
239////////////////////////////////////////////////////////////////////////////////////////////////////
240class refnotes_note_capture {
241
242    private $renderer;
243    private $note;
244    private $doc;
245
246    /**
247     * Constructor
248     */
249    public function __construct() {
250        $this->initialize();
251    }
252
253    /**
254     *
255     */
256    private function initialize() {
257        $this->renderer = NULL;
258        $this->doc = '';
259    }
260
261    /**
262     *
263     */
264    private function resetCapture() {
265        $this->renderer->doc = '';
266    }
267
268    /**
269     *
270     */
271    public function start($renderer) {
272        $this->renderer = $renderer;
273        $this->doc = $renderer->doc;
274
275        $this->resetCapture();
276    }
277
278    /**
279     *
280     */
281    public function restart() {
282        $text = trim($this->renderer->doc);
283
284        $this->resetCapture();
285
286        return $text;
287    }
288
289    /**
290     *
291     */
292    public function stop() {
293        $text = trim($this->renderer->doc);
294
295        $this->renderer->doc = $this->doc;
296
297        $this->initialize();
298
299        return $text;
300    }
301}
302
303////////////////////////////////////////////////////////////////////////////////////////////////////
304class refnotes_nested_call_writer extends \dokuwiki\Parsing\Handler\Nest {
305
306    private $handler;
307    private $callWriterBackup;
308
309    /**
310     * Constructor
311     *
312     * HACK: Fix compatibility with PHP versions before 7.2 by passing handler as second optional
313     * argument. This makes constructor signature compatible with one defined in ReWriterInterface.
314     * Starting from PHP 7.2 this is not needed because arguments without type hint are compatible
315     * with any type since they have a wider type (any type).
316     * https://wiki.php.net/rfc/parameter-no-type-variance
317     */
318    public function __construct(\dokuwiki\Parsing\Handler\CallWriterInterface $callWriter, $handler = NULL) {
319        $this->handler = $handler;
320
321        parent::__construct($this->handler->getCallWriter());
322    }
323
324    /**
325     *
326     */
327    public function connect() {
328        $this->callWriterBackup = $this->handler->getCallWriter();
329
330        $this->handler->setCallWriter($this);
331    }
332
333    /**
334     *
335     */
336    public function disconnect() {
337        $this->handler->setCallWriter($this->callWriterBackup);
338    }
339}
340