1<?php
2
3/**
4 * Plugin RefNotes: Core functionality
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/locale.php');
11require_once(DOKU_PLUGIN . 'refnotes/config.php');
12require_once(DOKU_PLUGIN . 'refnotes/refnote.php');
13require_once(DOKU_PLUGIN . 'refnotes/reference.php');
14require_once(DOKU_PLUGIN . 'refnotes/note.php');
15require_once(DOKU_PLUGIN . 'refnotes/namespace.php');
16require_once(DOKU_PLUGIN . 'refnotes/scope.php');
17require_once(DOKU_PLUGIN . 'refnotes/rendering.php');
18require_once(DOKU_PLUGIN . 'refnotes/database.php');
19
20////////////////////////////////////////////////////////////////////////////////////////////////////
21class refnotes_parser_core {
22
23    private static $instance = NULL;
24
25    private $context;
26    private $lexer;
27    private $handler;
28
29    /**
30     *
31     */
32    public static function getInstance() {
33        if (self::$instance == NULL) {
34            self::$instance = new refnotes_parser_core();
35        }
36
37        return self::$instance;
38    }
39
40    /**
41     * Constructor
42     */
43    public function __construct() {
44        /* Default context. Should never be used, but just in case... */
45        $this->context = array(new refnotes_parsing_context());
46        $this->lexer = NULL;
47        $this->handler = NULL;
48    }
49
50    /**
51     *
52     */
53    public function registerLexer($lexer) {
54        $this->lexer = $lexer;
55    }
56
57    /**
58     *
59     */
60    public function enterParsingContext() {
61        $this->context[] = new refnotes_parsing_context();
62    }
63
64    /**
65     *
66     */
67    public function exitParsingContext($handler) {
68        $this->handler = $handler;
69
70        unset($this->context[count($this->context) - 1]);
71    }
72
73    /**
74     *
75     */
76    public function getInstructions($text) {
77        $this->callWriter = new refnotes_nested_call_writer($this->handler->getCallWriter(), $this->handler);
78
79        $this->callWriter->connect();
80        $this->lexer->parse($text);
81        $this->callWriter->disconnect();
82
83        return $this->callWriter->calls;
84    }
85
86    /**
87     *
88     */
89    private function getCurrentContext() {
90        return end($this->context);
91    }
92
93    /**
94     *
95     */
96    public function canHandle($state) {
97        return $this->getCurrentContext()->canHandle($state);
98    }
99
100    /**
101     *
102     */
103    public function enterReference($name, $data) {
104        $this->getCurrentContext()->enterReference($name, $data);
105    }
106
107    /**
108     *
109     */
110    public function exitReference() {
111        return $this->getCurrentContext()->exitReference();
112    }
113}
114
115////////////////////////////////////////////////////////////////////////////////////////////////////
116class refnotes_parsing_context {
117
118    private $handling;
119    private $reference;
120
121    /**
122     * Constructor
123     */
124    public function __construct() {
125        $this->reset();
126    }
127
128    /**
129     *
130     */
131    private function reset() {
132        $this->handling = false;
133        $this->reference = NULL;
134    }
135
136    /**
137     *
138     */
139    public function canHandle($state) {
140        switch ($state) {
141            case DOKU_LEXER_ENTER:
142                $result = !$this->handling;
143                break;
144
145            case DOKU_LEXER_EXIT:
146                $result = $this->handling;
147                break;
148
149            default:
150                $result = false;
151                break;
152        }
153
154        return $result;
155    }
156
157    /**
158     *
159     */
160    public function enterReference($name, $data) {
161        $this->handling = true;
162        $this->reference = new refnotes_parser_reference($name, $data);
163    }
164
165    /**
166     *
167     */
168    public function exitReference() {
169        $reference = $this->reference;
170
171        $this->reset();
172
173        return $reference;
174    }
175}
176
177////////////////////////////////////////////////////////////////////////////////////////////////////
178abstract class refnotes_core {
179
180    protected $presetStyle;
181    protected $namespace;
182    protected $mapping;
183
184    /**
185     * Constructor
186     */
187    public function __construct() {
188        $this->presetStyle = refnotes_configuration::load('namespaces');
189        $this->namespace = array();
190        $this->mapping = array();
191    }
192
193    /**
194     *
195     */
196    public function getNamespaceCount() {
197        return count($this->namespace);
198    }
199
200    /**
201     * Returns a namespace given it's name. The namespace is created if it doesn't exist yet.
202     */
203    public function getNamespace($name) {
204        $result = $this->findNamespace($name);
205
206        if ($result == NULL) {
207            $result = $this->createNamespace($name);
208        }
209
210        return $result;
211    }
212
213    /**
214     * Finds a namespace given it's name
215     */
216    protected function findNamespace($name) {
217        $result = NULL;
218
219        if (array_key_exists($name, $this->namespace)) {
220            $result = $this->namespace[$name];
221        }
222
223        return $result;
224    }
225
226    /**
227     *  Finds a namespace or it's parent
228     */
229    public function findParentNamespace($name) {
230        while (($name != '') && !array_key_exists($name, $this->namespace)) {
231            $name = refnotes_namespace::getParentName($name);
232        }
233
234        return ($name != '') ? $this->namespace[$name] : NULL;
235    }
236
237    /**
238     *
239     */
240    public function styleNamespace($namespaceName, $style) {
241        $namespace = $this->getNamespace($namespaceName);
242
243        if (array_key_exists('inherit', $style)) {
244            $source = $this->getNamespace($style['inherit']);
245            $namespace->inheritStyle($source);
246        }
247
248        $namespace->setStyle($style);
249    }
250
251    /**
252     *
253     */
254    public function setNamespaceMapping($namespaceName, $map) {
255        foreach ($map as $ns) {
256            $this->mapping[$ns] = $namespaceName;
257        }
258    }
259
260    /**
261     *
262     */
263    protected function clearNamespaceMapping($namespaceName) {
264        $this->mapping = array_diff($this->mapping, array($namespaceName));
265    }
266
267    /**
268     *
269     */
270    protected function createNamespace($name) {
271        if ($name != ':') {
272            $parentName = refnotes_namespace::getParentName($name);
273            $parent = $this->getNamespace($parentName);
274            $this->namespace[$name] = new refnotes_namespace($name, $parent);
275        }
276        else {
277            $this->namespace[$name] = new refnotes_namespace($name);
278        }
279
280        if (array_key_exists($name, $this->presetStyle)) {
281            $this->namespace[$name]->setStyle($this->presetStyle[$name]);
282        }
283
284        return $this->namespace[$name];
285    }
286
287    /**
288     *
289     */
290    protected function getNote($namespaceName, $noteName) {
291        $scope = $this->getNamespace($namespaceName)->getActiveScope();
292        $note = $scope->findNote($namespaceName, $noteName);
293
294        if (($note == NULL) && array_key_exists($namespaceName, $this->mapping)) {
295            $scope = $this->getNamespace($this->mapping[$namespaceName])->getActiveScope();
296            $note = $scope->findNote($namespaceName, $noteName);
297        }
298
299        if ($note == NULL) {
300            if (!is_int($noteName)) {
301                $note = $this->createNote($scope, $namespaceName, $noteName);
302
303                $scope->addNote($note);
304            }
305            else {
306                $note = new refnotes_note_mock();
307            }
308        }
309
310        return $note;
311    }
312
313    /**
314     *
315     */
316    abstract protected function createNote($scope, $namespaceName, $noteName);
317}
318
319////////////////////////////////////////////////////////////////////////////////////////////////////
320class refnotes_renderer_core extends refnotes_core {
321
322    private static $instance = NULL;
323    private $leftoversRenderingBlocks = 0;
324
325    /**
326     * Renderer core is used by both references and notes syntax plugins during the rendering
327     * stage. The instance has to be shared between the plugins, and since there should be no
328     * more than one rendering pass during a DW page request, a single instance of the syntax
329     * core should be enough.
330     */
331    public static function getInstance() {
332        if (self::$instance == NULL) {
333            self::$instance = new refnotes_renderer_core();
334        }
335
336        return self::$instance;
337    }
338
339    /**
340     *
341     */
342    public function addReference($attributes, $data) {
343        $note = $this->getNote($attributes['ns'], $attributes['name']);
344        $reference = new refnotes_renderer_reference($note, $attributes, $data);
345
346        $note->addReference($reference);
347
348        return $reference;
349    }
350
351    /**
352     *
353     */
354    public function renderNotes($mode, $namespaceName, $limit) {
355        $html = '';
356
357        if ($namespaceName == '*') {
358            if ($this->leftoversRenderingBlocks == 0) {
359                foreach ($this->namespace as $namespace) {
360                    $html .= $namespace->renderNotes($mode);
361                }
362            }
363        }
364        else {
365            $this->clearNamespaceMapping($namespaceName);
366
367            $namespace = $this->findNamespace($namespaceName);
368            if ($namespace != NULL) {
369                $html = $namespace->renderNotes($mode, $limit);
370            }
371        }
372
373        return $html;
374    }
375
376    /**
377     * Keep track of leftover note rendering blocking on included pages
378     */
379    public function updateRenderingBlocks($block) {
380        switch ($block) {
381            case 'enter':
382                ++$this->leftoversRenderingBlocks;
383                break;
384
385            case 'exit':
386                if ($this->leftoversRenderingBlocks > 0) {
387                    --$this->leftoversRenderingBlocks;
388                }
389                break;
390        }
391    }
392
393    /**
394     *
395     */
396    protected function createNote($scope, $namespaceName, $noteName) {
397        return new refnotes_renderer_note($scope, $namespaceName, $noteName);
398    }
399}
400
401////////////////////////////////////////////////////////////////////////////////////////////////////
402class refnotes_action_core extends refnotes_core {
403
404    private $styleStash;
405    private $mappingStash;
406
407    /**
408     * Constructor
409     */
410    public function __construct() {
411        parent::__construct();
412
413        $this->styleStash = new refnotes_namespace_style_stash($this);
414        $this->mappingStash = new refnotes_namespace_mapping_stash();
415    }
416
417    /**
418     *
419     */
420    public function markScopeStart($namespaceName, $callIndex) {
421        $this->getNamespace($namespaceName)->markScopeStart($callIndex);
422    }
423
424    /**
425     *
426     */
427    public function markScopeEnd($namespaceName, $callIndex) {
428        $this->getNamespace($namespaceName)->markScopeEnd($callIndex);
429    }
430
431    /**
432     * Collect styling information from the page
433     */
434    public function addStyle($namespaceName, $style) {
435        $this->styleStash->add($this->getNamespace($namespaceName), $style);
436    }
437
438    /**
439     *
440     */
441    public function getStyles() {
442        return $this->styleStash;
443    }
444
445    /**
446     * Collect mapping information from the page
447     */
448    public function addMapping($namespaceName, $map) {
449        $this->mappingStash->add($this->getNamespace($namespaceName), $map);
450    }
451
452    /**
453     *
454     */
455    public function getMappings() {
456        return $this->mappingStash;
457    }
458
459    /**
460     *
461     */
462    public function reset() {
463        $this->namespace = array();
464    }
465
466    /**
467     *
468     */
469    public function addReference($attributes, $data, $call) {
470        $note = $this->getNote($attributes['ns'], $attributes['name']);
471        $reference = new refnotes_action_reference($note, $attributes, $data, $call);
472
473        $note->addReference($reference);
474
475        return $reference;
476    }
477
478    /**
479     *
480     */
481    public function rewriteReferences($namespaceName, $limit) {
482        $this->clearNamespaceMapping($namespaceName);
483
484        if ($namespaceName == '*') {
485            foreach ($this->namespace as $namespace) {
486                $namespace->rewriteReferences();
487            }
488        }
489        else {
490            $namespace = $this->findNamespace($namespaceName);
491            if ($namespace != NULL) {
492                $namespace->rewriteReferences($limit);
493            }
494        }
495    }
496
497    /**
498     *
499     */
500    protected function createNote($scope, $namespaceName, $noteName) {
501        return new refnotes_action_note($scope, $namespaceName, $noteName);
502    }
503}
504