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