1<?php
2/**
3 * Userpoll Plugin: allows to create simple polls
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     Randolf Rotta <randolf.rotta@gmail.com>
7 *
8 * heavily inspired (copy-pasted) from
9 *             Esther Brunner <wikidesign@gmail.com>'s poll plugin
10 *
11 * previous version by
12 *             Stephane Chazelas <stephane@artesyncp.com>
13 *
14 * latest changes:
15 *             - added language support (german)
16 *             - fixed the error message "Security Token did not match. Possible CSRF attack."
17 *             - simplyfied source code
18 */
19
20if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
21if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
22require_once(DOKU_PLUGIN.'syntax.php');
23
24/**
25 * All DokuWiki plugins to extend the parser/rendering mechanism
26 * need to inherit from this class
27 */
28class syntax_plugin_userpoll extends DokuWiki_Syntax_Plugin {
29
30  /**
31   * return some info
32   */
33  function getInfo(){
34    return array(
35      'author' => 'Randolf Rotta',
36      'email'  => 'randolf.rotta@gmail.com',
37      'date'   => '2013-05-09',
38      'name'   => 'Userpoll Plugin',
39      'desc'   => 'allows to create simple polls',
40      'url'    => 'http://wiki.splitbrain.org/plugin:userpoll',
41    );
42  }
43
44  function getType(){ return 'substition';}
45  function getPType(){ return 'block';}
46  function getSort(){ return 167; }
47
48  /**
49   * Connect pattern to lexer
50   */
51  function connectTo($mode){
52    $this->Lexer->addSpecialPattern('<userpoll.*?>.+?</userpoll>', $mode, 'plugin_userpoll');
53  }
54
55  /**
56   * Handle the match
57   */
58  function handle($match, $state, $pos, &$handler){
59    $match = substr($match, 10, -11);  // strip markup
60    list($title, $options) = preg_split('/>/u', $match, 2);
61    $options = explode('*', $options);
62    $text = array_shift($options);
63
64    for ($i = 0; $i < count($options); $i++){
65      $options[$i] = trim($options[$i]);
66    }
67    return array(trim($title), trim($text), $options);
68  }
69
70  /**
71   * Create output
72   */
73  function render($mode, &$renderer, $data) {
74    if ($mode == 'xhtml'){
75      if (isset($_POST['vote']) && checkSecurityToken()) {
76        $this->_handlePost($data);
77      }
78      return $this->_renderPoll($renderer, $data);
79    }
80    return false;
81  }
82
83  function _handlePost($data) {
84    $options = $data[2];
85    $title   = hsc($data[0]);
86    $pollid = md5('@userpoll@'.$title);
87    $user = $_SERVER['REMOTE_USER'];
88
89    // ignore submits for other polls
90    if ($pollid != $_POST['pollid']) return;
91
92    // get poll file contents
93    $pfile = metaFN($pollid, '.userpoll');
94    $poll  = unserialize(@file_get_contents($pfile));
95
96    if ($_POST['vote']=='@FORGET@') {
97      // user changed his/her mind, wipe him from the records
98      $opt = $poll['users'][$user];
99      unset($poll['users'][$user]);
100      $poll['results'][$opt] -= 1;
101      $poll['votes'] -= 1;
102    } elseif (!isset($poll['users'][$user])) {
103      // user has just voted -> update results
104      $vote = $_POST['vote'];
105      $c = count($options);
106      for ($i = 0; $i < $c; $i++){
107        $opt = hsc($options[$i]);
108        if ($vote == $opt){
109          $poll['results'][$opt] += 1;
110          $poll['votes'] += 1;
111          $poll['users'][$user] = $opt;
112        } elseif (!isset($poll['results'][$opt])){
113          $poll['results'][$opt] = 0;
114        }
115      }
116    }
117
118    // write poll file
119    $fh = fopen($pfile, 'w');
120    fwrite($fh, serialize($poll));
121    fclose($fh);
122  }
123
124  function _renderPoll(&$renderer, $data) {
125    $options = $data[2];
126    $text    = $data[1];
127    $title   = hsc($data[0]);
128
129    // prevent caching to ensure the poll results are fresh
130    $renderer->info['cache'] = false;
131
132    // get poll file contents
133    $pollid = md5('@userpoll@'.$title);
134    $pfile = metaFN($pollid, '.userpoll');
135    $poll  = unserialize(@file_get_contents($pfile));
136
137    // output the poll
138    $renderer->doc .= '<fieldset class="userpoll">'.
139      '<legend>'.$title.'</legend>';
140    if ($text) {
141      $renderer->doc .= '<div>'.$renderer->_xmlEntities($text).'</div>';
142    }
143
144    if (isset($_SERVER['REMOTE_USER'])) {
145      $user = $_SERVER['REMOTE_USER'];
146      if (isset($poll['users']) && isset($poll['users'][$user])) {
147        // display results
148        $renderer->doc .= $this->_pollResults($poll, $pollid);
149      } else {
150        // display poll form
151        $renderer->doc .= $this->_pollForm($options, $pollid);
152      }
153    } else {
154      $renderer->doc .= '<div class="userpoll_error">' .$this->getLang('login_required') . '</div>'
155        . $this->_pollResults($poll);
156    }
157    $renderer->doc .= '</fieldset>';
158    return true;
159  }
160
161  function _pollResults($poll, $pollid = false){
162    global $ID;
163    global $lang;
164
165    $total = $poll['votes'];
166    if ($total == 0) return '';
167
168    $ret = '<table class="blind">';
169    $options = array_keys($poll['results']);
170    $votes   = array_values($poll['results']);
171    for ($i = 0; $i < count($options); $i++){
172      $absolute = $votes[$i];
173      $percent  = round(($absolute*100)/$total);
174      $ret .= '<tr><td class="leftalign">'.$options[$i].'</td><td><div class="userpoll_bar">';
175      if ($percent) $ret .= '<div class="userpoll_full" style="width:'.($percent*2).'px">&nbsp;</div>';
176      $ret .= '</div></td><td class="rightalign">'.$percent.'%</td>'.
177        '<td class="rightalign">('.$absolute.')</td></tr>';
178    }
179    if ($pollid) {
180      // user had already voted, allow him/her to changer his/her mind
181      $ret .= '<tr><td colspan="4">'
182        . '<form id="userpoll__form" method="post" action="'.script().'" accept-charset="'.$lang['encoding'].'">'
183        . '<div class="no"><input type="hidden" name="do" value="show" />'
184        . '<input type="hidden" name="pollid" value="' . $pollid . '" />'
185        . '<input type="hidden" name="id" value="'.$ID.'" />'
186        . '<input type="hidden" name="vote" value="@FORGET@">'
187        . '<input type="hidden" name="sectok" value='.getSecurityToken().' />' // einfg durch AG Server Mai 2013
188        . '<input class="button" type="submit" value="' . $this->getLang('btn_changemind') . '"></form></div></td></tr>';
189    }
190    $ret .= '</table>';
191    return $ret;
192  }
193
194  function _pollForm($options, $pollid){
195    global $lang;
196    global $ID;
197
198    $ret = '<form id="userpoll__form" method="post" action="'.script().'" accept-charset="'.$lang['encoding'].'"><div class="no">'.
199      '<input type="hidden" name="do" value="show" />'.
200      '<input type="hidden" name="pollid" value="' . $pollid . '" />'.
201      '<input type="hidden" name="id" value="'.$ID.'" />'.
202      '<input type="hidden" name="sectok" value='.getSecurityToken().' />'; // einfg durch AG Server Mai 2013
203    $i = 0;
204    foreach ($options as $option){
205      $i++;
206      $option = hsc($option);
207      $ret.= '<label class="simple" for="userpoll__option'.$i.'">'.
208        '<input type="radio" name="vote" id="userpoll__option'.$i.'" '.
209        'value="'.$option.'" /> <span>'.$option.'</span></label>';
210    }
211    $ret .= '<input class="button" type="submit" '.
212      'value="'.$this->getLang('btn_vote').'" />'.
213      '</div></form>';
214    return $ret;
215  }
216}
217
218//Setup VIM: ex: et ts=4 enc=utf-8 :
219