1<?php
2/**
3 * DokuWiki Plugin jsongendoc (Action Component)
4 *
5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
6 * @author  Janez Paternoster <janez.paternoster@siol.net>
7 */
8
9// must be run within Dokuwiki
10if (!defined('DOKU_INC')) {
11    die();
12}
13
14//Resolve to absolute page ID
15use dokuwiki\File\PageResolver;
16
17class action_plugin_jsongendoc extends DokuWiki_Action_Plugin
18{
19
20    /**
21     * Registers a callback function for a given event
22     *
23     * @param Doku_Event_Handler $controller DokuWiki's event controller object
24     *
25     * @return void
26     */
27    public function register(Doku_Event_Handler $controller) {
28        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax_call_unknown');
29    }
30
31
32    /**
33     * Handle event AJAX_CALL_UNKNOWN for json_plugin_save_inline and
34     * json_plugin_archive call
35     *
36     * @param Doku_Event $event  event object by reference
37     * @param mixed      $param  [the parameters passed as fifth argument to register_hook() when this
38     *                           handler was registered]
39     *
40     * @return Ajax response as json
41     */
42    public function handle_ajax_call_unknown(Doku_Event $event, $param) {
43        if ($event->data !== 'jsongendoc_plugin') {
44            return;
45        }
46
47        //no other ajax call handlers needed
48        $event->stopPropagation();
49        $event->preventDefault();
50
51        //access additional request variables
52        global $INPUT;
53        $new_page_id = cleanID($INPUT->str('name'));
54        $data = json_decode($INPUT->str('data'), true);
55        $parent_id = strval($data['parent_id']);
56        $mime = strval($data['mime']);
57        $docname_meta = strval($data['docname_meta']);
58        $namespace = strval($data['namespace']);
59        $template_id = cleanID($data['template']);
60        $json = $data['json'];
61        $new_page_exist = $template_exist = false;
62        $resolver = new PageResolver($namespace.':page'); // ':page' will be stripped
63
64        //resolve tempalate_id
65        if($template_id) {
66            $template_id = $resolver->resolveId($template_id);
67            $template_exist = page_exists($template_id);
68            $template_link = '<a href="'.wl($template_id).'">'.$template_id.'</a>';
69        }
70        //resolve page_id, if page will be stored locally (mime undefined)
71        if(!$mime && $new_page_id) {
72            $new_page_id = $resolver->resolveId($new_page_id);
73            $new_page_exist = page_exists($new_page_id);
74            $new_page_link = '<a href="'.wl($new_page_id).'">'.$new_page_id.'</a>';
75            $new_page_id_for_template = $new_page_id;
76        }
77        else {
78            //document will be downloaded to the client machine
79            $new_page_link = $new_page_id;
80            $new_page_id_for_template = $template_id;
81        }
82
83        //verify parameters, existances and rights
84        $err = '';
85        if(!isset($json)) {
86            $err = $this->getLang('err_json');
87        }
88        else if($data['template'] && $template_exist === false) {
89            $err = sprintf($this->getLang('err_template_not_exist'), $template_link);
90        }
91        else if($template_id && auth_quickaclcheck($template_id) < AUTH_READ) {
92            $err = sprintf($this->getLang('permision_denied_template'), $template_id);
93        }
94        else if(!$mime) {
95            if(!$new_page_id) {
96                $err = $this->getLang('err_no_page_name');
97            }
98            else if($new_page_exist === true) {
99                $err = sprintf($this->getLang('err_page_exists'), $new_page_link);
100            }
101            else if(auth_quickaclcheck($new_page_id) < AUTH_CREATE) {
102                $err = sprintf($this->getLang('permision_denied_write'), $new_page_link);
103            }
104        }
105
106        //load template text from user or default namespace template
107        //and replace standard replacement patterns
108        //https://www.dokuwiki.org/namespace_templates
109        if($err === '') {
110            if($template_exist === true) {
111                //load user template
112                $parsePageTemplate_arg = array('id'=>$new_page_id_for_template, 'tpl'=>rawWiki($template_id));
113                //perform common page template replacements
114                $new_page_text = parsePageTemplate($parsePageTemplate_arg);
115            }
116            else {
117                //load default template
118                $template_id = 'Namespace Template'; //for info
119                $new_page_text = pageTemplate($new_page_id_for_template);
120                $template_link = $this->external_link('https://www.dokuwiki.org/namespace_templates', 'Namespace Template', 'urlextern');
121            }
122            if(!$new_page_text) {
123                $err = sprintf($this->getLang('err_empty_template'), $template_link, $new_page_link);
124            }
125        }
126
127        //statistics
128        $cnt = array(
129            'sections shown'    => 0,
130            'sections removed'  => 0,
131            'scalars extracted' => 0,
132            'json extracted'    => 0,
133            'null values'       => 0
134        );
135
136        //helper functions from JSON plugin
137        $json_o = $this->loadHelper('json');
138
139        //display or remove sections @%$-start (filter)% ... %$end%@
140        if($err === '') {
141            $new_page_text = preg_replace_callback(
142                '/@(\%\$-start\s*\(([^[\]{}%]*?)\)%)(.*?)%\$end\%@/s',
143                function($matches) use($json_o, $json, &$cnt, &$err) {
144                    if($err === '') {
145                        list($match, $pattern, $filter, $contents) = array_pad($matches, 4, '');
146
147                        $filter = $json_o->parse_filter($filter);
148                        if(count($filter) > 0) {
149                            if($json_o->filter($json, $filter)) {
150                                $cnt['sections shown'] ++;
151                                return $contents;
152                            }
153                            else {
154                                $cnt['sections removed'] ++;
155                                return '';
156                            }
157                        }
158                        else {
159                            $err = printf($this->getLang('err_tpl_section'), $template_link, $pattern);
160                        }
161                    }
162                    return '';
163                },
164                $new_page_text
165            );
166        }
167
168        //replace patterns @%$ ... %@
169        if($err === '') {
170            $new_page_text = preg_replace_callback(
171                '/@%\$(.*?)%@/s',
172                function($matches) use($json_o, $json, &$cnt) {
173                    list(, $path) = $matches;
174
175                    $var = $json;
176                    $tokens = $json_o->parse_tokens($path);
177
178                    //get variable on specified path of the json data
179                    foreach($tokens as $tok) {
180                        if(is_array($var) && isset($var[$tok])) {
181                            $var = $var[$tok];
182                        }
183                        else {
184                            $var = null;
185                            break;
186                        }
187                    }
188
189                    //return extraced data
190                    if(is_bool($var)) {
191                        $cnt['scalars extracted'] ++;
192                        return $var ? 'true' : 'false';
193                    }
194                    else if(is_scalar($var)) {
195                        $cnt['scalars extracted'] ++;
196                        return strval($var);
197                    }
198                    else if(is_array($var)) {
199                        $cnt['json extracted'] ++;
200                        return json_encode($var);
201                    }
202                    else {
203                        $cnt['null values'] ++;
204                        return 'null';
205                    }
206                },
207                $new_page_text
208            );
209        }
210
211        //create new file or send error
212        if($err === '') {
213            $response = array(
214                'response' => 'OK',
215                'mime' => '',
216                'msg' => sprintf($this->getLang('file_created_msg'), $new_page_link, $template_link)
217                        .'<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'
218                        .sprintf($this->getLang('file_created_stat'),
219                            $cnt['sections shown'], $cnt['sections removed'],
220                            $cnt['scalars extracted'], $cnt['json extracted'], $cnt['null values']
221                        )
222                        .'<br/>'
223            );
224
225            if(!$mime) {
226                //save document as new Dokuwiki page
227                saveWikiText($new_page_id, $new_page_text, sprintf($this->getLang('file_created_summary'), $template_id));
228            }
229            else {
230                //send data, so they can be downloaded
231                $response['mime'] = $mime;
232                $response['document'] = $new_page_text;
233            }
234
235            //store autoincrement into metadata of the caller
236            if(strlen($docname_meta) > 0 && preg_match('/^(.+?)(\d+)$/', $new_page_id, $matches)) {
237                list(, $base, $number) = $matches;
238                $base .= str_repeat('0', strlen($number));
239                if($docname_meta === $base && strlen($parent_id) > 0) {
240                    p_set_metadata($parent_id, array('jsongendoc_'.$docname_meta => $number + 1));
241                }
242            }
243        }
244        else {
245            $response = array(
246                'response' => 'error',
247                'msg' => '<span class="json-error">'.$this->getLang('err').'</span>: '.$err.'<br/>'
248            );
249        }
250
251        //send response
252        header('Content-Type: application/json');
253        echo json_encode($response);
254    }
255}
256