1 <?php
2 
3 use 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  */
11 class 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