1<?php
2
3use dokuwiki\Extension\SyntaxPlugin;
4use dokuwiki\Form\Form;
5use dokuwiki\plugin\questionnaire\miniYAML;
6
7/**
8 * DokuWiki Plugin questionnaire (Syntax Component)
9 *
10 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
11 * @author Andreas Gohr <dokuwiki@cosmocode.de>
12 */
13class syntax_plugin_questionnaire extends SyntaxPlugin
14{
15    /** @inheritDoc */
16    public function getType()
17    {
18        return 'substition';
19    }
20
21    /** @inheritDoc */
22    public function getSort()
23    {
24        return 155;
25    }
26
27    /** @inheritDoc */
28    public function connectTo($mode)
29    {
30        $this->Lexer->addSpecialPattern('<questionnaire>.*?(?:</questionnaire>)', $mode, 'plugin_questionnaire');
31    }
32
33
34    /** @inheritDoc */
35    public function handle($match, $state, $pos, Doku_Handler $handler)
36    {
37        $yaml = substr($match, 15, -16);
38        return miniYAML::Load($yaml);
39    }
40
41    /** @inheritDoc */
42    public function render($mode, Doku_Renderer $renderer, $data)
43    {
44        if ($mode !== 'xhtml') {
45            return false;
46        }
47
48
49        global $INPUT;
50        global $ID;
51        global $INFO;
52        global $ACT;
53
54        $renderer->nocache();
55
56        if ($data === null) {
57            msg($this->getLang('invalidyaml'), -1);
58            return true;
59        }
60
61        /** @var helper_plugin_questionnaire $helper */
62        $helper = plugin_load('helper', 'questionnaire');
63        $user = $INPUT->server->str('REMOTE_USER');
64
65        // handle the inputs
66        if($user) {
67            try {
68                if ($INPUT->has('questionnaire')) {
69                    $this->validateInput($data, $INPUT->arr('questionnaire'));
70                    $this->saveInput($data, $INPUT->arr('questionnaire'));
71                    msg($this->getLang('success'), 1);
72                }
73                if ($INPUT->has('questionnaire-admin') && $INFO['isadmin']) {
74                    switch ($INPUT->str('questionnaire-admin')) {
75                        case 'enable':
76                            $helper->activateQuestionnaire($ID, $user);
77                            break;
78                        case 'disable':
79                            $helper->deactivateQuestionnaire($ID, $user);
80                            break;
81                    }
82                }
83            } catch (Exception $e) {
84                msg($e->getMessage(), -1);
85            }
86        }
87
88        $quest = $helper->getQuestionnaire($ID);
89
90        $renderer->doc .= '<div class="plugin_questionnaire">';
91        if ($ACT === 'show' && $helper->hasUserAnswered($ID, $user)) {
92            $renderer->doc .= '<p class="answered">' . $this->getLang('answered') . '</p>';
93        } elseif ($ACT === 'show' && $quest && $quest['deactivated_on']) {
94            $renderer->doc .= '<p class="deactivated">' . $this->getLang('deactivated') . '</p>';
95        } else {
96            $renderer->doc .= $this->addSubmitButton($this->createForm($data), $quest)->toHTML();
97        }
98        if($INFO['isadmin']) {
99            $renderer->doc .= $this->adminPanel($quest)->toHTML();
100        }
101        $renderer->doc .= '</div>';
102
103        return true;
104    }
105
106    /**
107     * Create a DokuWiki Form for the questionnaire
108     *
109     * @param array $data The questionnaire configuration
110     * @return Form
111     */
112    protected function createForm($data)
113    {
114        $form = new Form(['method' => 'post']);
115
116        foreach ($data as $question => $q) {
117            $form->addTagOpen('div')->addClass('question');
118            $form->addTagOpen('p');
119            $form->addHTML(hsc($q['q']));
120            $form->addTagClose('p');
121
122            switch ($q['t']) {
123                case 'multi':
124                    foreach ($q['a'] as $num => $answer) {
125                        $form->addCheckbox('questionnaire[' . $question . '][' . $num . ']', $answer)->val($answer);
126                    }
127                    break;
128                case 'single':
129                    foreach ($q['a'] as $answer) {
130                        $form->addRadioButton('questionnaire[' . $question . ']', $answer)->val($answer);
131                    }
132                    break;
133                case 'text':
134                default:
135                    $form->addTextarea('questionnaire[' . $question . ']');
136                    break;
137            }
138            $form->addTagClose('div');
139        }
140
141
142        return $form;
143    }
144
145    /**
146     * Decide if the submit button should be shown and add it to the form
147     *
148     * @param Form $form
149     * @param array $quest The questionnaire data
150     * @return Form
151     */
152    protected function addSubmitButton($form, $quest)
153    {
154        global $ACT;
155        global $INPUT;
156        if ($ACT !== 'show') return $form;
157        if (!$quest) {
158            $form->addHTML('<p class="nosubmit">' . $this->getLang('inactive') . '</p>');
159        } elseif ($INPUT->server->str('REMOTE_USER') == '') {
160            $form->addHTML('<p class="nosubmit">' . $this->getLang('notloggedin') . '</p>');
161        } else {
162            $form->addButton('questionnaire[submit]', $this->getLang('submit'));
163        }
164
165        return $form;
166    }
167
168    /**
169     * @return Form
170     */
171    protected function adminPanel($quest)
172    {
173        /** @var helper_plugin_questionnaire $helper */
174        $helper = plugin_load('helper', 'questionnaire');
175
176        $form = new Form(['method' => 'post']);
177        $form->addFieldsetOpen($this->getLang('administration'));
178
179        if ($quest) {
180            $form->addHTML(
181                '<p>' .
182                sprintf($this->getLang('responses'), $helper->numberOfResponses($quest['page'])) .
183                '</p>'
184            );
185
186            if ($quest['deactivated_on']) {
187                $form->addButton('questionnaire-admin', $this->getLang('enable'))->val('enable');
188            } else {
189                $form->addButton('questionnaire-admin', $this->getLang('disable'))->val('disable');
190            }
191
192            $url = DOKU_BASE . 'lib/plugins/questionnaire/dl.php?id=' . $quest['page'];
193
194            $form->addHTML('<a href="' . $url . '" class="button">' . $this->getLang('download') . '</a>');
195        } else {
196            $form->addButton('questionnaire-admin', $this->getLang('enable'))->val('enable');
197        }
198        $form->addFieldsetClose();
199
200        return $form;
201    }
202
203
204    /**
205     * Validate the input data
206     *
207     * @param array $data The questionnaire configuration
208     * @param array $input The questionnaire input
209     * @throws Exception
210     * @todo could check if received data is matching the available answers
211     */
212    protected function validateInput($data, $input)
213    {
214        $validationError = $this->getLang('validationerror');
215
216        foreach (array_keys($data) as $question) {
217            if (!isset($input[$question])) {
218                throw new Exception($validationError);
219            }
220
221            if (is_array($input[$question])) {
222                if (array_filter(array_map('trim', $input[$question])) === []) {
223                    throw new Exception($validationError);
224                }
225            } elseif (trim($input[$question]) === '') {
226                throw new Exception($validationError);
227            }
228        }
229    }
230
231    /**
232     * Save the input data
233     *
234     * @param array $data The questionnaire configuration
235     * @param array $input The questionnaire input
236     * @throws Exception
237     */
238    protected function saveInput($data, $input)
239    {
240        global $INPUT;
241        global $ID;
242
243        $record = [
244            'page' => $ID,
245            'answered_on' => time(),
246            'answered_by' => $INPUT->server->str('REMOTE_USER'),
247        ];
248
249        /** @var helper_plugin_questionnaire $helper */
250        $helper = plugin_load('helper', 'questionnaire');
251        $db = $helper->getDB();
252        if (!$db) throw new \Exception($this->getLang('nodb'));
253
254        if (!$helper->isActive($ID)) {
255            throw new \Exception($this->getLang('inactive'));
256        }
257
258        if ($helper->hasUserAnswered($ID, $record['answered_by'])) {
259            throw new \Exception($this->getLang('answered'));
260        }
261
262        try {
263            $db->getPdo()->beginTransaction();
264            foreach (array_keys($data) as $question) {
265                $record['question'] = $question;
266
267                if (is_array($input[$question])) {
268                    $answers = array_filter(array_map('trim', $input[$question]));
269                    foreach ($answers as $answer) {
270                        $record['answer'] = $answer;
271                        $db->saveRecord('answers', $record);
272                    }
273                } else {
274                    $record['answer'] = trim($input[$question]);
275                    $db->saveRecord('answers', $record);
276                }
277            }
278            $db->getPdo()->commit();
279        } catch (\Exception $e) {
280            $db->getPdo()->rollBack();
281            throw new \Exception($this->getLang('saveerror'), 0, $e);
282        }
283    }
284}
285