1<?php
2/**
3 * Boolean Table Plugin 2 (Extended/Modified Doodle Plugin)
4 *
5 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6 * @author     jolZ <jolz@freenet.de>
7 *             Oliver Horst <oliver.horst@uni-dortmund.de>
8 */
9
10if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
11if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
12require_once(DOKU_PLUGIN.'syntax.php');
13
14/**
15 * All DokuWiki plugins to extend the parser/rendering mechanism
16 * need to inherit from this class
17 */
18class syntax_plugin_btable2 extends DokuWiki_Syntax_Plugin {
19
20    /**
21     * return some info
22     */
23     /*
24    function getInfo(){
25        return array(
26            'author' => 'Jolz',
27            'email'  => 'jolz@freenet.de',
28            'date'   => '2019-06-20',
29            'name'   => 'Boolean Table 2 (modified Doodle Plugin)',
30            'desc'   => 'Successor of btable plugin, doodle-like polls without authentication.',
31            'url'    => 'http://wiki.splitbrain.org/plugin:btable2',
32        );
33    }
34    */
35
36    function getType(){ return 'substition';}
37    function getPType(){ return 'block';}
38    function getSort(){ return 167; }
39
40    /**
41     * Connect pattern to lexer
42     */
43    function connectTo($mode){
44        $this->Lexer->addSpecialPattern('<btable.*?>.+?</btable>', $mode, 'plugin_btable2');
45    }
46
47
48    /**
49     * Handle the match
50     */
51    function handle($match, $state, $pos, Doku_Handler $handler){
52        // strip markup
53        $match = substr($match, 8, -9);
54
55        // split into title and options
56        list($title, $options) = preg_split('/>/u', $match, 2);
57
58        // check if no title was specified
59        if (!$options){
60            $options = $title;
61            $title   = NULL;
62        }
63
64        $matches = array();
65        $opt = "";
66        if (preg_match('#<opt.*?>(.*?)</opt>#u', $options, $matches)) {
67            $opt = $matches[1];
68        }
69
70
71        // split into ids and dates part
72        list($first, $second) = preg_split('#(\s|\n|\r)*<\/columns>(\s|\n|\r)*<rows>(\s|\n|\r)*#u', $options);
73
74        // get ids and dates
75        list(, $ids) = preg_split('#(\S|\s|\n|\r)*<columns>(\s|\n|\r)*#u', $first);
76        list($dates) = preg_split('#(\s|\n|\r)*<\/rows>(\s|\n|\r)*#u', $second);
77
78        // $ids = explode('^', $ids);
79        $ids = preg_split('/[\^\n]/', $ids);
80        // $dates = explode('^', $dates);
81        $dates =preg_split('/[\^\n]/', $dates); # seperate by "^" or newline
82
83        // remove whitespaces
84        for($i = 0; $i < count($ids); $i++) {
85            $ids[$i] = trim($ids[$i]);
86        }
87
88        for($i = 0; $i < count($dates); $i++) {
89            $dates[$i] = trim($dates[$i]);
90        }
91
92        return array(trim($title), $ids, $dates, $opt);
93    }
94
95
96    /**
97     * Create output
98     */
99    function render($mode, Doku_Renderer $renderer, $data) {
100
101        if ($mode == 'xhtml') {
102
103            global $ID;
104            global $INFO;
105
106
107            $conf_groups = trim($this->getConf('btable_groups'));
108
109            $user_groups = $INFO['userinfo']['grps'];
110            $plugin_groups = explode(';', $conf_groups);
111
112            if ((strlen($conf_groups) > 0) && (count($plugin_groups) > 0)) {
113                if (isset($user_groups) && is_array($user_groups)) {
114                    $write_access = count(array_intersect($plugin_groups, $user_groups));
115                } else {
116                    $write_access = 0;
117                }
118            } else {
119                $write_access = 1;
120            }
121
122
123
124            $title = $renderer->_xmlEntities($data[0]);
125            $dID = cleanID($title);
126
127            $rows = $data[2];
128            $columns = $data[1];
129            $opt = $data[3];
130
131            $showempty = preg_match("#\bshowempty\b#", $opt);
132            $colongroups = preg_match("#\bcolongroups\b#", $opt);
133            $closed = preg_match("#\bclosed\b#", $opt);
134            if ($closed) { $write_access = 0; }
135
136
137
138            $rows_count = count($rows);
139            $columns_count = count($columns);
140
141
142            // prevent caching to ensure the poll results are fresh
143            $renderer->info['cache'] = false;
144
145            // get doodle file contents
146            $dfile = metaFN(md5($dID), '.btable');
147            $doodle = unserialize(@file_get_contents($dfile));
148
149            if ($columns_count == 0) {
150                // no rows given: reset the doodle
151                $doodle = NULL;
152            }
153
154            // render form
155            $renderer->doc  .= '<form id="btable__form__'.$dID.'" '.
156                                    'method="post" '.
157                                    'action="'.script().'#btable_scroll" '.
158                                    'accept-charset="'.$this->getLang('encoding').'">' . "\n";
159
160            // see https://www.dokuwiki.org/devel:security?#prevent_csrf
161            $renderer->doc .= '     <input type="hidden" name="sectok" value="' . getSecurityToken() . '" />' . "\n";
162
163            $renderer->doc .= '    <input type="hidden" name="do" value="show" />' . "\n";
164            $renderer->doc .= '    <input type="hidden" name="id" value="'.$ID.'" />' . "\n";
165
166            if (($submit = $_REQUEST[$dID.'-add']) && $write_access &&  checkSecurityToken()) {
167
168                // user has changed/added values -> update results
169
170                $row = trim($_REQUEST['row']);
171                $change_row = "";
172
173                if (!empty($row)){
174
175                    for ($i = 0; $i < $columns_count; $i++) {
176
177                        $column = $renderer->_xmlEntities($columns[$i]);
178
179                        if ($_REQUEST[$dID.'-column'.$i]) {
180                            $doodle[$row][$column] = true;
181                        } else {
182                            $doodle[$row][$column] = false;
183                        }
184                    }
185                }
186
187                // write back changes
188                $fh = fopen($dfile, 'w');
189                fwrite($fh, serialize($doodle));
190                fclose($fh);
191
192            } else if (($submit = $_REQUEST[$dID.'-delete']) && $write_access && checkSecurityToken()) {
193
194                // user has just deleted a row -> update results
195                $row = trim($submit);
196                $change_row = "";
197
198                if (!empty($row)){
199                    unset($doodle[$row]);
200                }
201
202                // write back changes
203                $fh = fopen($dfile, 'w');
204                fwrite($fh, serialize($doodle));
205                fclose($fh);
206
207            } else if (($submit = $_REQUEST[$dID.'-change']) && $write_access && checkSecurityToken()) {
208
209                // user want to change a row
210                $change_row = trim($submit);
211            }
212
213            // sort rows
214            if ($doodle) {
215                ksort($doodle);
216            }
217
218            // start outputing the data
219            $renderer->table_open();
220
221            if ($doodle && count($doodle) >= 1) {
222
223                $add_delete_row = 1;
224
225                if ($write_access) {
226                    $colspan = $columns_count + 2;
227                } else {
228                    $colspan = $columns_count + 1;
229                }
230
231            } else {
232
233                $add_delete_row = 0;
234
235                if ($write_access) {
236                    $colspan = $columns_count + 1;
237                } else {
238                    $colspan = $columns_count;
239                }
240            }
241
242
243            // render title if not null
244            if ($title) {
245                $renderer->tablerow_open();
246                $renderer->tableheader_open($colspan);
247                $renderer->doc .= $title;
248                $renderer->tableheader_close();
249                $renderer->tablerow_close();
250            }
251
252
253            // render column titles
254            $renderer->tablerow_open();
255
256            if ($write_access || ($doodle && (count($doodle) >= 1))) {
257                $renderer->tableheader_open();
258                $renderer->doc .= $this->getLang('btable_header');
259                $renderer->tableheader_close();
260            }
261
262            foreach ($columns as $column) {
263                $renderer->tableheader_open();
264                $renderer->doc .= $renderer->_xmlEntities($column);
265                $renderer->tableheader_close();
266            }
267
268            if ($write_access && ($doodle && (count($doodle) >= 1))) {
269                $renderer->tableheader_open();
270                $renderer->doc .= $this->getLang('btable_header_del');
271                $renderer->tableheader_close();
272            }
273
274            $renderer->tablerow_close();
275
276            // display results
277            if (is_array($doodle) && count($doodle) >= 1) {
278
279                $i = 0;
280                foreach($rows as $row) {
281                    if (!isset($doodle[$row])) {
282                        $selectable_rows[$i] = $row;
283                        $i++;
284                    }
285                    if ($showempty && $row && !$doodle[$row]) {
286                        $doodle[$row] = array();
287                    }
288                }
289                if ($showempty) { ksort($doodle); }
290                $renderer->doc .= $this->_doodleResults($dID, $doodle, $columns, $columns_count, $rows_count, $change_row, $write_access, $colspan, $colongroups);
291
292            } else {
293
294                $selectable_rows = $rows;
295
296                if (!$write_access) {
297
298                    $renderer->doc .= '<tr>';
299                    $renderer->doc .= '  <td class="centeralign" colspan="'.$colspan.'">';
300                    $renderer->doc .= '    '.$this->getLang('btable_no_entries');
301                    $renderer->doc .= '  </td>';
302                    $renderer->doc .= '</tr>';
303                }
304            }
305
306
307            // display input form and export link
308            $renderer->doc .= $this->_doodleForm($dID, $columns, $columns_count, $selectable_rows, $change_row, $write_access, $colspan, $add_delete_row);
309
310            $renderer->table_close();
311
312
313            // close input form
314            $renderer->doc .= '</form>';
315
316            return true;
317        }
318        return false;
319    }
320
321
322    function _doodleResults($dID, $doodle, $columns, $columns_count, $total_rows, $change_row, $allow_changes, $colspan, $colongroups) {
323
324        global $ID;
325
326        $ret   = '';
327        $count = array();
328        $rows  = array_keys($doodle);
329
330        $lastcolgroup = "";
331        // render table entrys
332        foreach ($rows as $row) {
333
334            // seperate groups names with ":" of option "colongroups" is set.
335           $colg = ""; $name = $row;
336           $groupchange = FALSE;
337            if ($colongroups) {
338                list($colg, $name) = explode(":", $row, 2);
339                if ($colg && !$name) { $name = $colg; } /* no ":" in name */
340
341                if ($colg == $lastcolgroup) {
342                    $colg = ""; // do not repeat.
343                    $groupchange = FALSE;
344                } else {
345                    $lastcolgroup = $colg;
346                    $groupchange = TRUE;
347                }
348            }
349
350
351            if ($colongroups && $groupchange) {
352            $ret .= '<tr>' . "\n";
353            $ret .= '  <th colspan="' . $colspan . '" style="padding-bottom: 0;">' . $colg . '</td>' . "\n";
354            $ret .= '</tr>' . "\n";
355
356            }
357
358            $ret .= '<tr>' . "\n";
359
360            $ret .= '  <td class="rightalign">';
361
362            if ($allow_changes) {
363            /*
364                $ret .= '<input class="button" '.
365                               'type="submit" '.
366                               'name="'.$dID.'-change" '.
367                               'value="'.$name.'" >';
368            */
369                $ret .= '<button class="button" style="width:100%" '.
370                               'type="submit" '.
371                               'name="'.$dID.'-change" '.
372                               'value="'.$row.'" >' . $name . "</button>";
373            } else {
374                $ret .= $name;
375            }
376            $ret .= "  </td>\n";
377
378
379            if (($row != $change_row) || !$allow_changes) {
380
381                foreach ($columns as $column) {
382
383                    if ($doodle[$row][$column]) {
384
385                        $class = 'okay';
386                        $title = '<img src="'.DOKU_BASE.'lib/images/success.png" '.
387                                      'alt="Okay" '.
388                                      'width="16" '.
389                                      'height="16" />';
390                        $count[$column] += 1;
391
392                    } elseif (!isset($doodle[$row][$column])) {
393
394                        $class = 'centeralign';
395                        $title = '&nbsp;';
396
397                    } else {
398                        $class = 'notokay';
399                        $title = '&nbsp;';
400                    }
401
402                    $ret .= '  <td class="'.$class.'">'.$title."</td>\n";
403                }
404
405            } else {
406
407                for ($i = 0; $i < $columns_count; $i++) {
408
409                    $column = $columns[$i];
410
411                    if ($doodle[$row][$column]) {
412
413                        $class = 'centeralign';
414                        $value = 'checked="checked"';
415                        $count[$column] += 1;
416
417                    } else {
418                        $class = 'centeralign';
419                        $value = '';
420                    }
421
422                    $ret .= '  <td id="btable_scroll" class="'.$class.'">';
423                    $ret .= '    <input type="checkbox" '.
424                                       'name="'.$dID.'-column'.$i.'" '.
425                                       'value="1" '.
426                                       $value.' />';
427                    $ret .= "</td>\n";
428                }
429            }
430
431            if (($row == $change_row) && $allow_changes) {
432                $ret .= '  <td>';
433                $ret  .= "<input type='hidden' name='$dID-delete' value='$row'>";
434                $ret .= '      <input class="button" '.
435                                     'type="submit" '.
436                                     'name="'.$dID.'-add" '.
437                                     'value="'.$this->getLang('btable_btn_change').'" />';
438                $ret .= '        oder l&ouml;schen: <input type="image" '.
439                                       'name="'.$dID.'-deletebutton" '.
440                                       'value="'.$row.'" '.
441                                       'src="'.DOKU_BASE.'lib/plugins/btable2/del.png'.'" '.
442                                       'alt="'.$this->getLang('btable_btn_delete').'" />';
443                $ret .= "    </td>\n";
444            } else {
445                $ret .= "  <td>&nbsp;</td>\n";
446            }
447            $ret .= "</tr>\n";
448        }
449
450        if ($this->getConf('btable_show_ratio') == true) {
451
452            // render attendance factor
453            $ret .= '<tr>';
454            $ret .= "  <td>".$this->getLang('btable_summary').'</td>';
455
456            $rows_count = count($rows);
457
458            foreach ($columns as $column) {
459
460                $ccount = isset($count[$column]) ? $count[$column] : 0;
461                $attendence = $count[$column] / $rows_count;
462                $attendance_factor = $this->getConf('btable_ratio') / 100;
463
464                if ($attendance_factor < 0 || $attendance_factor > 1) {
465                    $attendance_factor = 0.7;
466                }
467
468                if ($attendence >= $attendance_factor) {
469                    $class = 'okay';
470                } else {
471                    $class = 'notokay';
472                }
473
474                $ret .= '<td class="'.$class.'">';
475                $ret .=    $ccount."/".$rows_count;
476                $ret .= '</td>';
477            }
478
479            if ($allow_changes) {
480                $ret .= '<td></td>';
481            }
482
483            $ret .= '</tr>';
484        }
485
486        return $ret;
487    }
488
489
490    function _doodleForm($dID, $columns, $columns_count, $rows, $change_row, $allow_changes, $colspan, $add_delete_row) {
491
492        global $ID;
493        global $INFO;
494
495
496        $rows_count = count($rows);
497
498        $max_row_length = 0;
499        for ($i = 0; $i < $rows_count; $i++) {
500            $length = strlen($rows[$i]);
501            if ($length > $max_row_length) {
502                $max_row_length = $length;
503            }
504        }
505
506        if ($allow_changes) {
507            if ($rows_count > 0) {
508
509                $count = array();
510
511                if (empty($change_row)) {
512                    $ret .= '  <tr>';
513
514                    // row selection (combobox)
515
516                    $ret .= "    <td class='rightalign'>";
517                    $ret .= '      <select name="row" size="1">';
518                    // $ret .= '      <select name="row" size="1" style="width: '.$max_row_length.'em;">';
519
520                    for ($i = 0; $i < $rows_count; $i++) {
521                        if ($i == 0) {
522                            $ret .= '<option selected="selected">'.$rows[$i].'</option>';
523                        } else {
524                            $ret .= '<option>'.$rows[$i].'</option>';
525                        }
526                    }
527
528                    $ret .= '      </select>';
529                    $ret .= '    </td>';
530
531
532                    // render column inputs (checkboxes)
533                    for ($i = 0; $i < $columns_count; $i++) {
534
535                        $ret .= '    <td class="centeralign">';
536                        $ret .= '      <input type="checkbox" '.
537                                             'name="'.$dID.'-column'.$i.'" '.
538                                             'value="1" />';
539                        $ret .= '    </td>';
540                    }
541                    if ($add_delete_row) {
542                        $ret .= '    <td></td>';
543                    }
544                    $ret .= '  </tr>';
545                }
546            }
547
548            if (($rows_count > 0) || (!empty($change_row))) {
549
550                // render sumbit button
551                $ret .= '  <tr>';
552                $ret .= '    <td class="centeralign" colspan="'.$colspan.'">';
553
554                if (!empty($change_row)) {
555                    $ret .= '    <input type="hidden" name="row" value="'.$change_row.'" />';
556                }
557
558                $ret .= '      <input class="button" '.
559                                     'type="submit" '.
560                                     'name="'.$dID.'-add" '.
561                                     'value="'.$this->getLang('btable_btn_submit').'" />';
562                $ret .= '    </td>';
563                $ret .= '  </tr>';
564            }
565        }
566
567        if ($this->getConf('btable_show_export') == true) {
568
569            // render export link
570            $ret .= '  <tr>';
571            $ret .= '    <td class="rightalign" colspan="'.$colspan.'">';
572            $ret .= '      <a href="'.DOKU_BASE.'/lib/plugins/btable/export.php?id='.$dID.'">';
573            $ret .=          $this->getLang('btable_export');
574            $ret .= '      </a>';
575            $ret .= '    </td>';
576            $ret .= '  </tr>';
577        }
578
579        return $ret;
580    }
581}
582
583//Setup VIM: ex: et ts=4 enc=utf-8 :
584