1<?php
2/**
3 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
4 * @author     Brend Wanders <b.wanders@utwente.nl>
5 */
6// must be run within Dokuwiki
7if(!defined('DOKU_INC')) die('Meh.');
8
9use dokuwiki\Parsing\Parser;
10use dokuwiki\Parsing\Handler\Block;
11
12
13/**
14 * The 'render as wiki text' type.
15 */
16class plugin_strata_type_wiki extends plugin_strata_type {
17    function normalize($value, $hint) {
18        $ins = $this->_instructions($value);
19
20        $value = "\n".str_replace("\r\n","\n",$value)."\n";
21
22        for($i=count($ins)-1;$i>=0;$i--) {
23            switch($ins[$i][0]) {
24                case 'internallink':
25                    $replacement = $this->_normalize_internallink($ins[$i][1]);
26                    break;
27                case 'locallink':
28                    $replacement = $this->_normalize_locallink($ins[$i][1]);
29                    break;
30                case 'internalmedia':
31                    $replacement = $this->_normalize_media($ins[$i][1]);
32                    break;
33                case 'externallink':
34                    $replacement = $this->_linkSyntax($ins[$i][1], $ins[$i][1][0]);
35                    break;
36                default:
37                    continue 2;
38            }
39
40            $value = substr_replace($value, $replacement, $ins[$i][2], $ins[$i+1][2] - $ins[$i][2]);
41        }
42
43        // strip off only the inserted newlines
44        return substr($value,1,-1);
45    }
46
47    /**
48     * Normalizes an internal link.
49     */
50    function _normalize_internallink($instruction) {
51        global $ID;
52
53        // split off query string
54        $parts = explode('?', $instruction[0] ,2);
55
56        $id = $parts[0];
57
58		list($id,$hash) = explode('#', $id, 2);
59        // normalize selflink
60        if($id === '') {
61            $id = $ID;
62        }
63
64        // actually resolve the page
65        resolve_pageid(getNS($ID), $id, $exists);
66        // make the resolved pageid absolute, so it does not re-resolve to something else later on
67        $id = ':'.$id;
68
69        // render the link
70        return $this->_linkSyntax($instruction, $id.'#'.$hash);
71    }
72
73    /**
74     * Normalizes a local link.
75     */
76    function _normalize_locallink($instruction) {
77        global $ID;
78
79        // simply prefix the current page
80        return $this->_linkSyntax($instruction, $ID.'#'.$instruction[0]);
81    }
82
83    /**
84     * Normalizes a media array.
85     */
86    function _normalize_media($instruction) {
87        global $ID;
88
89        // construct media structure based on input
90        if(isset($instruction['type'])) {
91            $media = $instruction;
92        } else {
93            list($src, $title, $align, $width, $height, $cache, $linking) = $instruction;
94            $media = compact('src','title','align','width','height');
95            $media['type']= 'internalmedia';
96        }
97
98        // normalize internal media links
99        if($media['type'] == 'internalmedia') {
100            list($src,$hash) = explode('#',$media['src'],2);
101            resolve_mediaid(getNS($ID),$src, $exists);
102            if($hash) $src.='#'.$hash;
103            $media['src'] = ':'.$src;
104        }
105
106        // render the media structure
107        return $this->_mediaSyntax($media);
108    }
109
110    /**
111     * Renders the media syntax.
112     */
113    function _mediaSyntax($media) {
114        // the source
115        $src = $media['src'];
116
117        // the resizing part
118        if(isset($media['width'])) {
119            $size = '?'.$media['width'];
120            if(isset($media['height'])) {
121                $size .= 'x'.$media['height'];
122            }
123        } else {
124            $size = '';
125        }
126
127        // the title part
128        if(isset($media['title'])) {
129            $title = '|'.$media['title'];
130        } else {
131            $title = '';
132        }
133
134        // the alignment parts
135        if(isset($media['align'])) {
136            switch($media['align']) {
137            case 'left':
138                $al = ''; $ar = ' '; break;
139            case 'right':
140                $al = ' '; $ar = ''; break;
141            case 'center':
142                $al = ' '; $ar = ' '; break;
143            }
144        }
145
146        // construct the syntax
147        return '{{'.$al.$src.$size.$ar.$title.'}}';
148    }
149
150    /**
151     * Renders the link syntax, invoking media normalization
152     * if required.
153     */
154    function _linkSyntax($instruction, $newLink) {
155        // fetch params from old link
156        $parts = explode('?', $instruction[0],2);
157        if(count($parts) === 2) {
158            $params = '?'.$parts[1];
159        } else {
160            $params = '';
161        }
162
163        // determine title
164        $title = '';
165        if(isset($instruction[1])) {
166            if(is_array($instruction[1])) {
167                if($instruction[1]['type'] == 'internalmedia') {
168                    $title='|'.$this->_normalize_media($instruction[1]);
169                } else {
170                    $title='|'.$this->_mediaSyntax($instruction[1]);
171                }
172            } else {
173                $title = '|'.$instruction[1];
174            }
175        }
176
177        // construct a new link string
178        return '[['.$newLink.$params.$title.']]';
179
180    }
181
182    function render($mode, &$R, &$T, $value, $hint) {
183        // though this breaks backlink functionality, we really do not want
184        // metadata renders of included pieces of wiki.
185        if($mode == 'xhtml' || $mode == 'odt') {
186            $instructions = $this->_instructions($value);
187            $instructions = array_slice($instructions, 2, -2);
188
189            // last-minute fix of newline in front of content
190            if(!empty($instructions[0][0]) && $instructions[0][0]=='cdata') {
191                $instructions[0][1][0] = ltrim($instructions[0][1][0]);
192            }
193
194            // actual render of content
195            $R->nest($instructions);
196        }
197    }
198
199    function getInfo() {
200        return array(
201            'desc'=>'Allows the use of dokuwiki syntax; only non-block syntax is allowed (only links, formatting, etc.; no tables, headers, and other large stuff). The hint is ignored.',
202            'tags'=>array()
203        );
204    }
205
206    function _instructions($text) {
207        // determine all parser modes that are allowable as inline modes
208        // (i.e., those that are allowed inside a table cell, minus those
209        // that have a paragraph type other than 'normal')
210
211        // determine all modes allowed inside a table cell or list item
212
213        // import parser classes and mode definitions to make the $PARSER_MODES global available to us
214        require_once(DOKU_INC . 'inc/parser/parser.php');
215        global $PARSER_MODES;
216        $allowedModes = array_merge(
217            $PARSER_MODES['formatting'],
218            $PARSER_MODES['substition'],
219            $PARSER_MODES['disabled'],
220            $PARSER_MODES['protected']
221        );
222
223        // determine all modes that are not allowed either due to paragraph
224        // handling, or because they're blacklisted as they don't make sense.
225        $blockHandler = new Block();
226        $disallowedModes = array_merge(
227            // inlined from Block::blockOpen due to being protected:
228            array(
229                'header',
230                'listu_open','listo_open','listitem_open','listcontent_open',
231                'table_open','tablerow_open','tablecell_open','tableheader_open','tablethead_open',
232                'quote_open',
233                'code','file','hr','preformatted','rss',
234                'htmlblock','phpblock',
235                'footnote_open',
236            ),
237            // inlined from Block::stackOpen due to being protected:
238            array(
239                'section_open',
240            ),
241            array('notoc', 'nocache')
242        );
243
244        $allowedModes = array_diff($allowedModes, $disallowedModes);
245
246        $parser = new Parser(new Doku_Handler());
247
248        foreach(p_get_parsermodes() as $mode) {
249            if(!in_array($mode['mode'], $allowedModes)) continue;
250            $parser->addMode($mode['mode'], $mode['obj']);
251        }
252
253        trigger_event('PARSER_WIKITEXT_PREPROCESS', $text);
254        $p = $parser->parse($text);
255        return $p;
256    }
257}
258