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">&nbsp;</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