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 = ' '; 339 340 } else { 341 $class = 'notokay'; 342 $title = ' '; 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