1<?php 2/** 3 * Syntax Plugin: 4 * A basic voting system between multiple candidates. 5 * 6 * the result is determined by the schulze method 7 * 8 * syntax: 9 * <vote right 2010-04-10> 10 * candidate A 11 * candidate B 12 * candidate C 13 * </vote> 14 * 15 * @author Dominik Eckelmann <eckelmann@cosmocode.de> 16 * @author Laurent Forthomme <lforthomme.protonmail.com> 17 */ 18 19// must be run within Dokuwiki 20if(!defined('DOKU_INC')) die(); 21if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); 22 23class syntax_plugin_schulzevote_vote extends DokuWiki_Syntax_Plugin { 24 25 function getType(){ return 'substition'; } 26 function getPType(){ return 'block'; } 27 function getSort(){ return 155; } 28 29 function connectTo($mode) { 30 $this->Lexer->addSpecialPattern('<vote\b.*?>\n.*?\n</vote>',$mode,'plugin_schulzevote_vote'); 31 } 32 33 function handle($match, $state, $pos, Doku_Handler $handler){ 34 $lines = explode("\n", $match); 35 36 $opts = array(); 37 $opts['hide_results'] = false; 38 if (preg_match('/ hideResults/', $lines[0], $hide_results)) { 39 $opts['hide_results'] = true; 40 } 41 42 // Determine date from syntax 43 $opts['date'] = null; 44 45 if (preg_match('/ \d{4}-\d{2}-\d{2}/', $lines[0], $opts['date'])) { 46 47 $opts['date'] = strtotime($opts['date'][0]); 48 if ($opts['date'] === false || $opts['date'] === -1) { 49 $opts['date'] = null; 50 } 51 } 52 53 $opts['admin_users'] = array(); 54 if (preg_match('/ adminUsers=([a-zA-Z0-9,]+)/', $lines[0], $admins)) { 55 $opts['admin_users'] = explode(',', $admins[1]); 56 } 57 58 $opts['admin_groups'] = array(); 59 if (preg_match('/ adminGroups=([a-zA-Z0-9,]+)/', $lines[0], $admins)) { 60 $opts['admin_groups'] = explode(',', $admins[1]); 61 } 62 63 // Determine align informations 64 $opts['align'] = 'left'; 65 if (preg_match('/(left|right|center)/',$lines[0], $align)) { 66 $opts['align'] = $align[0]; 67 } 68 69 unset($lines[count($lines)-1]); 70 unset($lines[0]); 71 72 $candidates = array(); 73 74 foreach ($lines as $line) { 75 $line = trim($line); 76 if (!empty($line)) { 77 $candidates[] = $line; 78 } 79 } 80 81 $hlp = plugin_load('helper', 'schulzevote'); 82 $hlp->createVote($candidates); 83 84 return array('candy' => $candidates, 'opts' => $opts); 85 } 86 87 function render($mode, Doku_Renderer $renderer, $data) { 88 89 if ($mode != 'xhtml') return false; 90 91 global $INPUT; 92 93 if ($INPUT->post->int('vote_cancel') && checkSecurityToken()) { 94 $this->_handleunvote($data); 95 } 96 if (isset($_POST['vote']) && checkSecurityToken()) { 97 $this->_handlepost($data); 98 } 99 $this->_html($renderer, $data); 100 } 101 102 function _html(&$renderer, $data) { 103 104 global $ID; 105 106 // set alignment 107 $align = $data['opts']['align']; 108 109 $hlp = plugin_load('helper', 'schulzevote'); 110# dbg($hlp); 111 112 // check if the vote is over or outdated. 113 $open = ($data['opts']['date'] !== null) && ($data['opts']['date'] > time()); 114 if ($hlp->outdated) { 115 $open = false; 116 msg($this->getLang('outdated_poll'), 0); 117 } 118 if ($open) { 119 $renderer->info['cache'] = false; 120 if (!isset($_SERVER['REMOTE_USER'])) { 121 $open = false; 122 $closemsg = $this->getLang('no_remote_user'); 123 } elseif ($hlp->hasVoted()) { 124 $closemsg = $this->getLang('already_voted'); 125 } 126 } else { 127 $closemsg = $this->getLang('vote_over').'<br />'. 128 $this->_winnerMsg($hlp, 'has_won'); 129 } 130 131 if ($open) { 132 $form = new dokuwiki\Form\Form(array('id'=>'plugin__schulzevote', 'class' => 'plugin_schulzevote_'.$align)); 133 $form->addFieldsetOpen($this->getLang('cast')); 134 $form->setHiddenField('id', $ID); 135 $form->addTagOpen('table'); 136 $proposals = $this->_buildProposals($data); 137 foreach ($data['candy'] as $n => $candy) { 138 $form->addTagOpen('tr'); 139 $form->addTagOpen('td'); 140 $form->addLabel($this->_render($candy)); 141 $form->addTagClose('td'); 142 if ($open) { 143 $form->addTagOpen('td'); 144 if ($hlp->hasVoted()) { 145 $form->addLabel($hlp->getVote()[$candy]); 146 } else { 147 $form->addDropdown('vote[' . $n . ']', $proposals)->addClass('plugin__schulzevote__vote_selector'); 148 } 149 $form->addTagClose('td'); 150 } 151 $form->addTagClose('tr'); 152 } 153 $form->addTagClose('table'); 154 155 if (!$hlp->hasVoted()) { 156 $form->addHTML('<p>'.$this->getLang('howto').'</p>'); 157 $form->addButton('submit', $this->getLang('vote')); 158 } else { 159 $form->addButton('vote_cancel', $this->getLang('vote_cancel')); 160 $form->addHTML('<p>' . $closemsg . '</p>'); 161 } 162 163 $form->addFieldsetClose(); 164 $renderer->doc .= $form->toHTML(); 165 } 166 167 // if admin or results not hidden 168 if (!$data['opts']['hide_results'] || $this->_isInSuperUsers($data)) { 169 $form = new dokuwiki\Form\Form(array('class' => 'plugin_schulzevote_'.$align)); 170 $form->addTagOpen('div')->attr('id', 'plugin__schulzevote'); 171 $ranks = array(); 172 foreach($hlp->getRanking() as $rank => $items) { 173 foreach($items as $item) { 174 $ranks[$item] = '<span class="votebar" style="width: ' . (80 / ($rank + 1)) . 'px"> </span>'; 175 } 176 } 177 178 $form->addFieldsetOpen($this->getLang('intermediate_results')); 179 $form->addHTML('<p>' . $this->_winnerMsg($hlp, 'leading') . '</p>'); 180 $form->addTagOpen('table'); 181 foreach ($data['candy'] as $n => $candy) { 182 $form->addTagOpen('tr'); 183 $form->addTagOpen('td'); 184 $form->addLabel($this->_render($candy)); 185 $form->addTagClose('td'); 186 $form->addTagOpen('td'); 187 $form->addHTML($ranks[$candy]); 188 $form->addTagClose('td'); 189 $form->addTagClose('tr'); 190 } 191 $form->addTagClose('table'); 192 $form->addFieldsetClose(); 193 $form->addTagClose('div'); 194 $renderer->doc .= $form->toHTML(); 195 } 196 197 return true; 198 } 199 200 function _winnerMsg($hlp, $lang) { 201 $winner = $hlp->getWinner(); 202 return !is_null($winner) ? sprintf($this->getLang($lang), $this->_render($winner)) : ''; 203 } 204 205 function _handlepost($data) { 206 $err = false; 207 $err_str = ""; 208 $max_vote = null; 209 foreach($_POST['vote'] as $n => &$vote) { 210 if ($vote !== '') { 211 $vote = explode(' ', $vote)[0]; 212 if (!is_numeric($vote)) { 213 $err_str .= "<li>" . $this->render_text(sprintf($this->getLang('invalid_vote'), $data['candy'][$n]), 'xhtml') . "</li>"; 214 $vote = ''; 215 $err = true; 216 } else { 217 $vote = (int) $vote; 218 $max_vote = max($vote, $max_vote); 219 } 220 } 221 } 222 unset($vote); 223 if ($err_str != "") 224 msg(sprintf($this->getLang('error_found'), $err_str), -1); 225 if ($err || count(array_filter($_POST['vote'])) === 0) return; 226 227 foreach($_POST['vote'] as &$vote) { 228 if ($vote === '') { 229 $vote = $max_vote + 1; 230 } 231 } 232 233 $hlp = plugin_load('helper', 'schulzevote'); 234 if (!$hlp->vote(array_combine($data['candy'], $_POST['vote']))) { 235 msg($this->getLang('invalidated_vote'), -1); 236 return; 237 } 238 msg($this->getLang('voted'), 1); 239 } 240 241 function _handleunvote($data) { 242 $hlp = plugin_load('helper', 'schulzevote'); 243 $hlp->deleteVote(); 244 msg($this->getLang('unvoted'), 1); 245 } 246 247 function _render($str) { 248 return p_render('xhtml', array_slice(p_get_instructions($str), 2, -2), $notused); 249 } 250 251 function _isInSuperUsers($data) { 252 global $INFO; 253 254 if (!isset($data['opts']['admin_users']) || !isset($data['opts']['admin_groups'])) 255 return false; // ensure backward-compatibility with former polls 256 foreach ($data['opts']['admin_users'] as $su_user) 257 if ($_SERVER['REMOTE_USER'] === $su_user) 258 return true; 259 foreach ($data['opts']['admin_groups'] as $su_group) 260 foreach ($INFO['userinfo']['grps'] as $user_group) 261 if ($user_group === $su_group) 262 return true; 263 return false; 264 } 265 266 function _buildProposals($data) { 267 $candy = $data['candy']; 268 $proposals = range(0, sizeof($candy)); 269 $proposals[0] = '-'; 270 if (sizeof($candy) > 0) { 271 $proposals[1] = sprintf($this->getLang('first_choice'), $proposals[1]); 272 $proposals[sizeof($candy)] = sprintf($this->getLang('last_choice'), $proposals[sizeof($candy)]); 273 } 274 return $proposals; 275 } 276} 277 278//Setup VIM: ex: et ts=4 enc=utf-8 : 279