1<?php
2
3use dokuwiki\Extension\SyntaxPlugin;
4
5/**
6 * DokuWiki Plugin dbquery (Syntax Component)
7 *
8 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
9 * @author  Andreas Gohr <dokuwiki@cosmocode.de>
10 */
11class syntax_plugin_dbquery_query extends SyntaxPlugin
12{
13    /** @inheritDoc */
14    public function getType()
15    {
16        return 'substition';
17    }
18
19    /** @inheritDoc */
20    public function getPType()
21    {
22        return 'block';
23    }
24
25    /** @inheritDoc */
26    public function getSort()
27    {
28        return 135;
29    }
30
31    /** @inheritDoc */
32    public function connectTo($mode)
33    {
34        $this->Lexer->addSpecialPattern('{{QUERY:\w+}}', $mode, 'plugin_dbquery_query');
35    }
36
37    /** @inheritDoc */
38    public function handle($match, $state, $pos, Doku_Handler $handler)
39    {
40        return ['name' => substr($match, 8, -2)];
41    }
42
43    /** @inheritDoc */
44    public function render($mode, Doku_Renderer $renderer, $data)
45    {
46        if ($mode !== 'xhtml') {
47            return false;
48        }
49
50        /** @var helper_plugin_dbquery $hlp */
51        $hlp = plugin_load('helper', 'dbquery');
52        try {
53            $qdata = $hlp->loadDataFromPage($data['name']);
54            $result = $hlp->executeQuery($qdata['codeblocks']['_'], $qdata['macros']['dsn'] ?? null);
55        } catch (\Exception $e) {
56            msg(hsc($e->getMessage()), -1);
57            return true;
58        }
59
60        if (count($result) === 1 && isset($result[0]['status']) && isset($qdata['codeblocks'][$result[0]['status']])) {
61            $this->renderStatus($result, $qdata['codeblocks'][$result[0]['status']], $renderer);
62        } elseif ($qdata['macros']['transpose']) {
63            $this->renderTransposedResultTable($result, $renderer);
64        } else {
65            $this->renderResultTable($result, $renderer);
66        }
67
68        return true;
69    }
70
71    /**
72     * Render given result via the given status HTML
73     *
74     * @param string[][] $result
75     * @param string $html
76     * @param Doku_Renderer $R
77     */
78    public function renderStatus($result, $html, Doku_Renderer $R)
79    {
80        $value = $result[0]['result'] ?? '';
81        $html = str_replace(':result', hsc($value), $html);
82        $R->doc .= $html;
83    }
84
85    /**
86     * Render the given result as a table
87     *
88     * @param string[][] $result
89     * @param Doku_Renderer $R
90     */
91    public function renderResultTable($result, Doku_Renderer $R)
92    {
93        global $lang;
94
95        if (!count($result)) {
96            $R->cdata($lang['nothingfound']);
97            return;
98        }
99
100        $R->table_open();
101        $R->tablethead_open();
102        $R->tablerow_open();
103        foreach (array_keys($result[0]) as $header) {
104            $header = preg_replace('/_wiki$/', ' ', $header); // remove _wiki type suffix
105            $R->tableheader_open();
106            $R->cdata($header);
107            $R->tableheader_close();
108        }
109        $R->tablerow_close();
110        $R->tablethead_close();
111
112        $R->tabletbody_open();
113        foreach ($result as $row) {
114            $R->tablerow_open();
115            foreach ($row as $col =>  $cell) {
116                $R->tablecell_open();
117                $this->cellFormat($cell, $R, $col);
118                $R->tablecell_close();
119            }
120            $R->tablerow_close();
121        }
122        $R->tabletbody_close();
123        $R->table_close();
124    }
125
126    /**
127     * Render the given result as a table, but turned 90 degrees
128     *
129     * @param string[][] $result
130     * @param Doku_Renderer $R
131     */
132    public function renderTransposedResultTable($result, Doku_Renderer $R)
133    {
134        global $lang;
135
136        if (!count($result)) {
137            $R->cdata($lang['nothingfound']);
138            return;
139        }
140
141        $width = count($result[0]);
142        $height = count($result);
143
144        $R->table_open();
145        for ($x = 0; $x < $width; $x++) {
146            $col = array_keys($result[0])[$x];
147            $header = preg_replace('/_wiki$/', ' ', $col); // remove _wiki type suffix
148
149            $R->tablerow_open();
150            $R->tableheader_open();
151            $R->cdata($header);
152            $R->tableheader_close();
153
154            for ($y = 0; $y < $height; $y++) {
155                $R->tablecell_open();
156                $this->cellFormat(array_values($result[$y])[$x], $R, $col);
157                $R->tablecell_close();
158            }
159            $R->tablerow_close();
160        }
161        $R->table_close();
162    }
163
164    /**
165     * Pass the given cell content to the correct renderer call
166     *
167     * Detects a subset of the wiki link syntax
168     *
169     * @param string $content
170     * @param Doku_Renderer $R
171     * @param string $name Name of the selected column
172     * @return void
173     */
174    protected function cellFormat($content, Doku_Renderer $R, $name)
175    {
176        if(trim($content) === '') {
177            return;
178        }
179
180        // parse wiki syntax
181        if(str_ends_with($name, '_wiki')) {
182            $this->renderInject($R, $content);
183            return;
184        }
185
186        // external urls
187        if (preg_match('/^\[\[(https?:\/\/[^|\]]+)(|.*?)?]]$/', $content, $m)) {
188            $url = $m[1];
189            $title = $m[2] ?? '';
190            $title = trim($title, '|');
191            $R->externallink($url, $title);
192            return;
193        }
194
195        // internal urls
196        if (preg_match('/^\[\[([^|\]]+)(|.*?)?]]$/', $content, $m)) {
197            $page = cleanID($m[1]);
198            $title = $m[2] ?? '';
199            $title = trim($title, '|');
200            $R->internallink($page, $title);
201            return;
202        }
203
204        $R->cdata($content);
205    }
206
207    /**
208     * Injects the given syntax into the current renderer
209     *
210     * @param Doku_Renderer $R
211     * @param string $syntax
212     * @return void
213     */
214    protected function renderInject(Doku_Renderer $R, $syntax)
215    {
216        $instructions = p_get_instructions($syntax);
217        foreach ($instructions as $instruction) {
218            // not these
219            if(in_array($instruction[0], ['document_start', 'document_end'])) {
220                continue;
221            }
222
223            // no headers
224            if($instruction[0] === 'header') {
225                $R->p_open();
226                $R->strong_open();
227                $R->cdata($instruction[1][0]);
228                $R->strong_close();
229                $R->p_close();
230                continue;
231            }
232
233            call_user_func_array([$R, $instruction[0]], $instruction[1]);
234        }
235    }
236}
237