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 = ' '; 396 397 } else { 398 $class = 'notokay'; 399 $title = ' '; 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ö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> </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