1<?php
2
3/**
4 * DokuWiki Plugin doxycode (Admin Component)
5 *
6 * @license     GPL 2 (http://www.gnu.org/licenses/gpl.html)
7 * @author      Lukas Probsthain <lukas.probsthain@gmail.com>
8 */
9
10use dokuwiki\Extension\AdminPlugin;
11use dokuwiki\Form\Form;
12
13/**
14 * Class admin_plugin_doxycode
15 *
16 * This admin plugin implements the management of tag files from differen doxygen
17 * documentations for building cross referenced code snippets.
18 *
19 * It lists all currently configured tag files and all tag files present in the file system
20 * of the plugin. The user can add new tag file configurations via upload or by defining a new configuration.
21 *
22 * The admin interface uses the helper_plugin_doxycode_tagmanager helper plugin for loading the current tag file
23 * list. On save it also uses the helper for storing the configuration in a json file.
24 *
25 * On save and update it also checks if a configuration is valid and can stay enabled.
26 *
27 * If a new remote config was defined, the action component of this plugin tries to download the tag file.
28 *
29 * @author      Lukas Probsthain <lukas.probsthain@gmail.com>
30 */
31class admin_plugin_doxycode extends AdminPlugin
32{
33    /** @var helper_plugin_doxycode_tagmanager $helper */
34    private $helper;
35    private $tag_config;
36
37    // TODO: these should be minimum widths
38    private $conf_column_widths = array(
39        'local_name' => 16,
40        'docu_url' => 35,
41        'remote_url' => 35,
42        'update_period' => 5,
43        'description' => 40
44    );
45
46    public function __construct()
47    {
48        $this->helper = plugin_load('helper', 'doxycode_tagmanager');
49
50        // load files
51        $tag_files = $this->helper->listTagFiles();
52
53        // load tag_config
54        $tag_config = $this->helper->loadTagFileConfig();
55
56        // merge both arrays for rendering
57        // prioritize the tag_config and overwrite elements from files!
58        $this->tag_config =  array_merge($tag_files, $tag_config);
59    }
60
61    /**
62     * handle user request
63     */
64    public function handle()
65    {
66        global $INPUT;
67        global $_FILES;
68
69        if (!$INPUT->has('cmd')) return; // first time - nothing to do
70
71        if (!checkSecurityToken()) return;
72        if (!is_array($INPUT->param('cmd'))) return;
73
74        $cmd = $INPUT->arr('cmd');
75
76        $new_tag_config = $INPUT->arr('tag_config');
77
78        // handle upload command
79        // if a new file was added, we move the file to the tagfile directory
80        // and add the file to the tagfile configuration for rendering
81        // on the next load of the page the tag file will be loaded to configuration
82        // from the tag file list from the directory
83        if ($cmd['update'] && isset($_FILES['upload']) && $_FILES['upload']['error'] != UPLOAD_ERR_NO_FILE) {
84            if ($_FILES['upload']['error'] == 0) {
85                if ('xml' != pathinfo($_FILES['upload']['name'], PATHINFO_EXTENSION)) {
86                    msg(sprintf($this->getLang('admin_err_no_xml_file'), $_FILES['upload']['name']), 2);
87                } else {
88                    // if tag file directory does not exist, create it!
89                    $this->helper->createTagFileDir();
90
91                    move_uploaded_file(
92                        $_FILES['upload']['tmp_name'],
93                        DOKU_PLUGIN . 'doxycode/tagfiles/' . $_FILES['upload']['name']
94                    );
95                    msg(sprintf($this->getLang('admin_info_upload_success'), $_FILES['upload']['name']), 1);
96                    $this->tag_config[pathinfo($_FILES['upload']['name'], PATHINFO_FILENAME)] = [];
97                }
98            } else {
99                msg($this->getLang('admin_err_upload'), 2);
100            }
101        }
102
103        // add new element from form
104        if (isset($new_tag_config['new'])) {
105            // do we have a valid new entry && is this entry name not already set?
106            if (
107                strlen($new_tag_config['new']['new_name']) > 0
108                && !isset($this->tag_config[$new_tag_config['new']['new_name']])
109            ) {
110                $newKey = $new_tag_config['new']['new_name'];
111
112                // unset the temporary new name that otherwise would mean a rename/move
113                unset($new_tag_config['new']['new_name']);
114
115                // add new tag_config element to global config
116                $this->tag_config[$newKey] = $new_tag_config['new'];
117                msg(sprintf($this->getLang('admin_info_new_tag_config'), $newKey), 1);
118            }
119            unset($new_tag_config['new']); // Remove the 'new' placeholder
120        }
121
122        // update our configuration from the input data
123        if ($cmd['save'] || $cmd['update']) {
124            foreach ($new_tag_config as $key => $tag_conf) {
125                $this->tag_config[$key] = $tag_conf;
126            }
127        }
128
129        // check if settings are valid for the enabled state
130        // TODO: implement tagmanager functions for checking if a config can be enabled!
131        if ($cmd['save'] || $cmd['update']) {
132            foreach ($this->tag_config as $key => &$tag_conf) {
133                // if element is disable continue
134                if (!isset($tag_conf['enabled']) || !$tag_conf['enabled']) continue;
135
136                // if docu_url is missing
137                if (strlen($tag_conf['docu_url']) <= 0) {
138                    $tag_conf['enabled'] = false;
139                    continue;
140                }
141
142                if (strlen($tag_conf['remote_url']) > 0 && strlen($tag_conf['update_period']) <= 0) {
143                    $tag_conf['enabled'] = false;
144                    continue;
145                }
146            }
147
148            // TODO: really necessary here?
149            unset($tag_conf);
150        }
151
152        if ($cmd['save']) {
153            // delete entries that are marked for deletion
154            foreach ($this->tag_config as $key => $tag_conf) {
155                if (isset($tag_conf['delete']) && $tag_conf['delete']) {
156                    unset($this->tag_config[$key]);
157
158                    // delete the tag file if it exists!
159                    $filename = $this->helper->getTagFileDir() . $key . '.xml';
160                    if (file_exists($filename)) {
161                        unlink($filename);
162                        msg(sprintf(
163                            $this->getLang('admin_info_tag_deleted'),
164                            pathinfo($filename, PATHINFO_BASENAME)
165                        ), 1);
166                    }
167                }
168            }
169
170            // handle renames
171            foreach ($this->tag_config as $key => $tag_conf) {
172                if (isset($tag_conf['new_name']) && $key !== $tag_conf['new_name']) {
173                    // TODO: check if an entry with this newName already exists!
174                    // if it already exists we can't rename it -> show msg to user!
175                    $newName = $tag_conf['new_name'];
176                    unset($this->tag_config[$key]);
177                    $this->tag_config[$newName] = $tag_conf;
178                    unset($this->tag_config[$newName]['new_name']);
179
180                    rename($this->helper->getTagFileDir()
181                        . $key . 'xml', $this->helper->getTagFileDir() . $newName . '.xml');
182
183                    // TODO: rename tag in page!
184                    // I looked into the move plugin
185                    // it might be possible to handle renaming
186                    // if the move plugin supports custom types (currently only media and pages)
187
188                    // TODO: notify user through msg that the tag file was renamed!
189                }
190            }
191
192            $this->helper->saveTagFileConfig($this->tag_config);
193        }
194    }
195
196    /**
197     * output appropriate html
198     */
199    public function html()
200    {
201        global $ID;#
202        global $conf;
203        global $lang;
204
205        // form header
206        echo '<div id="doxycode__tagmanager">';
207
208        $form = new Form(['enctype' => 'multipart/form-data']);
209
210        // new file
211        $form->addElement(new dokuwiki\Form\InputElement('file', 'upload', $this->getLang('admin_upload')));
212
213        $form->addHTML('<br>');
214        $form->addHTML('<br>');
215
216        // start table for existing configurations
217        $form->addTagOpen('div')->addClass('table');
218        $form->addTagOpen('table')->addClass('inline');
219
220        // add header
221        $form->addTagOpen('thead');
222
223        $form->addTagOpen('tr');
224
225        $form->addHTML('<th>' . $this->getLang('admin_conf_delete') . '</th>');
226        $form->addHTML('<th>' . $this->getLang('admin_conf_enabled') . '</th>');
227        $form->addHTML('<th>' . $this->getLang('admin_conf_force_runner') . '</th>');
228        $form->addHTML('<th>' . $this->getLang('admin_conf_local_name') . '</th>');
229        $form->addHTML('<th>' . $this->getLang('admin_conf_mtime') . '</th>');
230        $form->addHTML('<th>' . $this->getLang('admin_conf_docu_url') . '</th>');
231        $form->addHTML('<th>' . $this->getLang('admin_conf_remote_url') . '</th>');
232        $form->addHTML('<th>' . $this->getLang('admin_conf_update_period') . '</th>');
233        $form->addHTML('<th>' . $this->getLang('admin_conf_description') . '</th>');
234
235        $form->addTagClose('tr');
236
237        $form->addTagClose('thead');
238
239        // add body
240        $form->addTagOpen('tbody');
241
242        foreach ($this->tag_config as $key => $tag_conf) {
243            $form->addTagOpen('tr');
244
245            $form->addTagOpen('td');
246            $checkbox = $form->addCheckbox('tag_config[' . $key . '][delete]')
247                ->useInput(false);
248                if ($tag_conf['delete']) $checkbox->attrs(['checked' => 'checked']);
249            $form->addTagClose('td');
250
251            $form->addTagOpen('td');
252            $checkbox = $form->addCheckbox('tag_config[' . $key . '][enabled]')
253                ->useInput(false);
254            if ($tag_conf['enabled']) $checkbox->attrs(['checked' => 'checked']);
255            $form->addTagClose('td');
256
257            $form->addTagOpen('td');
258            $checkbox = $form->addCheckbox('tag_config[' . $key . '][force_runner]')
259                ->useInput(false);
260            if ($tag_conf['force_runner']) $checkbox->attrs(['checked' => 'checked']);
261            $form->addTagClose('td');
262
263            $form->addTagOpen('td');
264            $new_name = $form->addTextInput('tag_config[' . $key . '][new_name]')
265                ->attrs(['size' => $this->conf_column_widths['local_name']])
266                ->useInput(false);
267
268            // add red highlight if this file does not exist
269            if (file_exists($this->helper->getFileName($key))) {
270                $new_name->attrs(['style' => 'background-color: LightGreen']);
271            } else {
272                $new_name->attrs(['style' => 'background-color: LightCoral']);
273            }
274
275            if (isset($tag_conf['new_name'])) {
276                $new_name->val($tag_conf['new_name']);
277            } else {
278                $new_name->val($key);
279            }
280            $form->addTagClose('td');
281
282            // print file mtime for better understanding of update mechanism by admin
283            $form->addTagOpen('td');
284            if (file_exists($this->helper->getFileName($key))) {
285                $form->addLabel(dformat(@filemtime($this->helper->getFileName($key))));
286            }
287            $form->addTagClose('td');
288
289            $form->addTagOpen('td');
290            $form->addTextInput('tag_config[' . $key . '][docu_url]')
291                ->attrs(['size' => $this->conf_column_widths['docu_url']])
292                ->useInput(false)
293                ->val($tag_conf['docu_url']);
294            $form->addTagClose('td');
295
296            $form->addTagOpen('td');
297            $form->addTextInput('tag_config[' . $key . '][remote_url]')
298                ->attrs(['size' => $this->conf_column_widths['remote_url']])
299                ->useInput(false)
300                ->val($tag_conf['remote_url']);
301            $form->addTagClose('td');
302
303            $form->addTagOpen('td');
304            $period = $form->addTextInput('tag_config[' . $key . '][update_period]')
305                ->attrs(['size' => $this->conf_column_widths['update_period']])
306                ->useInput(false)
307                ->val($tag_conf['update_period']);
308
309            if ($tag_conf['update_period'] > 0) {
310                $timestamp = $conf['last_update'] ? $conf['last_update'] : 0;
311                $now = time();
312
313                if ($now - $tag_conf['update_period'] >= $timestamp) {
314                    $period->attrs(['style' => 'background-color: LightGreen']);
315                } else {
316                    $period->attrs(['style' => 'background-color: LightCoral']);
317                }
318            }
319            $form->addTagClose('td');
320
321            $form->addTagOpen('td');
322            $form->addTextInput('tag_config[' . $key . '][description]')
323                ->attrs(['size' => $this->conf_column_widths['description']])
324                ->useInput(false)
325                ->val($tag_conf['description']);
326            $form->addTagClose('td');
327
328            $form->addTagClose('tr');
329        }
330
331        // add 'create new' entry
332
333        // TODO: break table so 'create new' stands out more clearly
334
335        $form->addTagOpen('tr');
336            $form->addTagOpen('td')
337                ->attrs(['colspan' => 6]);
338            $form->addHTML($this->getLang('admin_new_entry'));
339            $form->addTagClose('td');
340        $form->addTagClose('tr');
341
342        $form->addTagOpen('tr');
343            $form->addHTML('<td></td>');
344
345            $form->addTagOpen('td');
346            $form->addCheckbox('tag_config[new][enabled]')
347                ->useInput(false);
348            $form->addTagClose('td');
349
350            $form->addTagOpen('td');
351            $form->addCheckbox('tag_config[new][force_runner]')
352                ->useInput(false);
353            $form->addTagClose('td');
354
355            $form->addTagOpen('td');
356            $form->addTextInput('tag_config[new][new_name]')
357                ->attrs(['size' => $this->conf_column_widths['local_name']])
358                ->useInput(false);
359            $form->addTagClose('td');
360
361            $form->addTagOpen('td');
362            $form->addTagClose('td');
363
364            $form->addTagOpen('td');
365            $form->addTextInput('tag_config[new][docu_url]')
366                ->attrs(['size' => $this->conf_column_widths['docu_url']])
367                ->useInput(false);
368            $form->addTagClose('td');
369
370            $form->addTagOpen('td');
371            $form->addTextInput('tag_config[new][remote_url]')
372                ->attrs(['size' => $this->conf_column_widths['remote_url']])
373                ->useInput(false);
374            $form->addTagClose('td');
375
376            $form->addTagOpen('td');
377            $form->addTextInput('tag_config[new][update_period]')
378                ->attrs(['size' => $this->conf_column_widths['update_period']])
379                ->useInput(false);
380            $form->addTagClose('td');
381
382            $form->addTagOpen('td');
383            $form->addTextInput('tag_config[new][description]')
384                ->attrs(['size' => $this->conf_column_widths['description']])
385                ->useInput(false);
386            $form->addTagClose('td');
387        $form->addTagClose('tr');
388
389
390        $form->addTagClose('tbody');
391
392        // end table
393        $form->addTagClose('table');
394        $form->addTagClose('div');
395
396        $form->addButton('cmd[save]', $lang['btn_save'])->attrs(['accesskey' => 's']);
397        $form->addButton('cmd[update]', $lang['btn_update']);
398
399        echo $form->toHTML();
400
401
402        echo '</div>';  // id=doxycode__tagmanager
403    }
404
405    /**
406     * Return true for access only by admins (config:superuser) or false if managers are allowed as well
407     *
408     * @return bool
409     */
410    public function forAdminOnly()
411    {
412        return false;
413    }
414}
415