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