1 rizwank 1.1 #
2 # TWiki WikiClone ($wikiversion has version info)
3 #
4 # Copyright (C) 2002-2004 Peter Thoeny, Peter@Thoeny.com
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details, published at
15 # http://www.gnu.org/copyleft/gpl.html
16 #
17 # =========================
18 #
19 # This is the EditTablePlugin used to edit tables in place.
20 #
21 # Each plugin is a package that contains the subs:
22 rizwank 1.1 #
23 # initPlugin ( $topic, $web, $user, $installWeb )
24 # commonTagsHandler ( $text, $topic, $web )
25 # startRenderingHandler( $text, $web )
26 # outsidePREHandler ( $text )
27 # insidePREHandler ( $text )
28 # endRenderingHandler ( $text )
29 #
30 # initPlugin is required, all other are optional.
31 # For increased performance, DISABLE handlers you don't need.
32 #
33 # NOTE: To interact with TWiki use the official TWiki functions
34 # in the &TWiki::Func module. Do not reference any functions or
35 # variables elsewhere in TWiki!!
36
37
38 # =========================
39 package TWiki::Plugins::EditTablePlugin;
40
41 # =========================
42 use vars qw(
43 rizwank 1.1 $web $topic $user $installWeb $VERSION $debug
44 $query $renderingWeb
45 $preSp %params @format @formatExpanded
46 $prefsInitialized $prefCHANGEROWS $prefEDITBUTTON
47 $prefJSCALENDARDATEFORMAT $prefJSCALENDARLANGUAGE $prefJSCALENDAROPTIONS
48 $nrCols $encodeStart $encodeEnd $table
49 );
50
51 $VERSION = '1.024';
52 $encodeStart = "--EditTableEncodeStart--";
53 $encodeEnd = "--EditTableEncodeEnd--";
54 $prefsInitialized = 0;
55 undef $table;
56
57 # =========================
58 sub initPlugin
59 {
60 ( $topic, $web, $user, $installWeb ) = @_;
61
62 # check for Plugins.pm versions
63 if( $TWiki::Plugins::VERSION < 1 ) {
64 rizwank 1.1 &TWiki::Func::writeWarning( "Version mismatch between EditTablePlugin and Plugins.pm" );
65 return 0;
66 }
67
68 $query = &TWiki::Func::getCgiQuery();
69 if( ! $query ) {
70 return 0;
71 }
72
73 # Get plugin debug flag
74 $debug = &TWiki::Func::getPreferencesFlag( "EDITTABLEPLUGIN_DEBUG" );
75
76 $prefsInitialized = 0;
77 $renderingWeb = $web;
78
79 # Plugin correctly initialized
80 &TWiki::Func::writeDebug( "- TWiki::Plugins::EditTablePlugin::initPlugin( $web.$topic ) is OK" ) if $debug;
81
82 # Initialize $table such that the code will correctly detect when to
83 # read in a topic.
84 undef $table;
85 rizwank 1.1 return 1;
86 }
87
88 # =========================
89 sub extractParams
90 {
91 my( $theArgs, $theHashRef ) = @_;
92
93 my $tmp = &TWiki::Func::extractNameValuePair( $theArgs, "header" );
94 $$theHashRef{"header"} = $tmp if( $tmp );
95
96 $tmp = &TWiki::Func::extractNameValuePair( $theArgs, "footer" );
97 $$theHashRef{"footer"} = $tmp if( $tmp );
98
99 $tmp = &TWiki::Func::extractNameValuePair( $theArgs, "headerislabel" );
100 $$theHashRef{"headerislabel"} = $tmp if( $tmp );
101
102 $tmp = &TWiki::Func::extractNameValuePair( $theArgs, "format" );
103 $tmp =~ s/^\s*\|*\s*//o;
104 $tmp =~ s/\s*\|*\s*$//o;
105 $$theHashRef{"format"} = $tmp if( $tmp );
106 rizwank 1.1
107 $tmp = &TWiki::Func::extractNameValuePair( $theArgs, "changerows" );
108 $$theHashRef{"changerows"} = $tmp if( $tmp );
109
110 $tmp = &TWiki::Func::extractNameValuePair( $theArgs, "quietsave" );
111 $$theHashRef{"quietsave"} = $tmp if( $tmp );
112
113 $tmp = &TWiki::Func::extractNameValuePair( $theArgs, "helptopic" );
114 $$theHashRef{"helptopic"} = $tmp if( $tmp );
115
116 $tmp = &TWiki::Func::extractNameValuePair( $theArgs, "editbutton" );
117 $$theHashRef{"editbutton"} = $tmp if( $tmp );
118
119 return;
120 }
121
122 # =========================
123 sub commonTagsHandler
124 {
125 ### my ( $text, $topic, $web ) = @_; # do not uncomment, use $_[0], $_[1]... instead
126
127 rizwank 1.1 &TWiki::Func::writeDebug( "- EditTablePlugin::commonTagsHandler( $_[2].$_[1] )" ) if $debug;
128
129 # Add style sheet for date calendar to skin if needed.
130 # Handling of the common tags is done separately for the topic text and view skin
131 # The following if statement must be done before the early escape.
132 #
133 # NOTE:
134 # When adding a new button to the table that needs the table to be in the edit mode,
135 # be sure to add it below.
136 if( ( $_[0] =~ m/^[<][!]DOCTYPE/ ) &&
137 ( $query->param( 'etedit' ) || $query->param( 'etaddrow' ) || $query->param( 'etdelrow') ) &&
138 (!($_[0] =~ m/calendar-system/) ) ) {
139
140 my $string = " <link type=\"text/css\" rel=\"stylesheet\""
141 . " href=\"%PUBURL%/$installWeb/EditTablePlugin/calendar-system.css\" />\n";
142 $_[0] =~ s/([<]\/head[>])/$string$1/i;
143 }
144
145 return unless $_[0] =~ /%EDIT(TABLE|CELL){(.*)}%/os;
146
147 unless( $prefsInitialized ) {
148 rizwank 1.1 $prefCHANGEROWS = &TWiki::Func::getPreferencesValue("CHANGEROWS") ||
149 &TWiki::Func::getPreferencesValue("EDITTABLEPLUGIN_CHANGEROWS") || "on";
150 $prefQUIETSAVE = &TWiki::Func::getPreferencesValue("QUIETSAVE") ||
151 &TWiki::Func::getPreferencesValue("EDITTABLEPLUGIN_QUIETSAVE") || "on";
152 $prefEDITBUTTON = &TWiki::Func::getPreferencesValue("EDITBUTTON") ||
153 &TWiki::Func::getPreferencesValue("EDITTABLEPLUGIN_EDITBUTTON") || "Edit table";
154 $prefJSCALENDARDATEFORMAT = &TWiki::Func::getPreferencesValue("JSCALENDARDATEFORMAT") ||
155 &TWiki::Func::getPreferencesValue("EDITTABLEPLUGIN_JSCALENDARDATEFORMAT") || "%Y/%m/%d";
156 $prefJSCALENDARLANGUAGE = &TWiki::Func::getPreferencesValue("JSCALENDARLANGUAGE") ||
157 &TWiki::Func::getPreferencesValue("EDITTABLEPLUGIN_JSCALENDARLANGUAGE") || "en";
158 $prefJSCALENDAROPTIONS = &TWiki::Func::getPreferencesValue("JSCALENDAROPTIONS") ||
159 &TWiki::Func::getPreferencesValue("EDITTABLEPLUGIN_JSCALENDAROPTIONS") || "";
160 $prefsInitialized = 1;
161 }
162
163 my $theWeb = $_[2];
164 my $theTopic = $_[1];
165 my $result = "";
166 my $tableNr = 0;
167 my $rowNr = 0;
168 my $enableForm = 0;
169 rizwank 1.1 my $insideTable = 0;
170 my $doEdit = 0;
171 my $cgiRows = -1;
172
173 # appended stuff is a hack to handle EDITTABLE correctly if at end
174 foreach( split( /\r?\n/, "$_[0]\n<nop>\n" ) ) {
175 if( s/(\s*)%EDITTABLE{(.*)}%/&handleEditTableTag( $theWeb, $theTopic, $1, $2 )/geo ) {
176 $enableForm = 1;
177 $tableNr += 1;
178
179 my $cgiTableNr = $query->param( 'ettablenr' ) || 0;
180 $cgiRows = $query->param( 'etrows' ) || -1;
181 if( ( $cgiTableNr == $tableNr ) && ( "$theWeb.$theTopic" eq "$web.$topic" ) ) {
182
183 if( $query->param( 'etsave' ) ) {
184 # [Save table] button pressed
185 doSaveTable( $theWeb, $theTopic, $tableNr, "" ); # never return
186 return; # in case browser does not redirect
187
188 } elsif( $query->param( 'etqsave' ) ) {
189 # [Quiet save] button pressed
190 rizwank 1.1 doSaveTable( $theWeb, $theTopic, $tableNr, "on" ); # never return
191 return; # in case browser does not redirect
192
193 } elsif( $query->param( 'etcancel' ) ) {
194 # [Cancel] button pressed
195 doCancelEdit( $theWeb, $theTopic ); # never return
196 return; # in case browser does not redirect
197
198 } elsif( $query->param( 'etaddrow' ) ) {
199 # [Add row] button pressed
200 $cgiRows++ if( $cgiRows >= 0 );
201 $doEdit = doEnableEdit( $theWeb, $theTopic, 0 );
202 return unless( $doEdit );
203
204 } elsif( $query->param( 'etdelrow' ) ) {
205 # [Delete row] button pressed
206 $cgiRows-- if( $cgiRows > 1 );
207 $doEdit = doEnableEdit( $theWeb, $theTopic, 0 );
208 return unless( $doEdit );
209
210 } elsif( $query->param( 'etedit' ) ) {
211 rizwank 1.1 # [Edit table] button pressed
212 $doEdit = doEnableEdit( $theWeb, $theTopic, 1 ); # never return if locked or no permission
213 return unless( $doEdit );
214 $cgiRows = -1; # make sure to get the actual number of rows
215 }
216 }
217 }
218 if( $enableForm ) {
219 if( /^(\s*)\|.*\|\s*$/ ) {
220 # found table row
221 $result .= handleTableStart( $theWeb, $theTopic, $tableNr, $doEdit ) unless $insideTable;
222 $insideTable = 1;
223 $rowNr++;
224 if( ( $doEdit ) && ( $cgiRows >= 0 ) && ( $rowNr > $cgiRows ) ) {
225 # deleted row
226 $rowNr--;
227 next;
228 }
229 s/^(\s*)\|(.*)/&handleTableRow( $1, $2, $tableNr, $cgiRows, $rowNr, $doEdit, 0 )/eo;
230
231 } elsif( $insideTable ) {
232 rizwank 1.1 # end of table
233 $insideTable = 0;
234 if( ( $doEdit ) && ( $cgiRows >= 0 ) && ( $rowNr < $cgiRows ) ) {
235 while( $rowNr < $cgiRows ) {
236 $rowNr++;
237 $result .= handleTableRow( $theSp, "", $tableNr, $cgiRows, $rowNr, $doEdit, 0 ) . "\n";
238 }
239 }
240 $result .= handleTableEnd( $theWeb, $rowNr, $doEdit );
241 $enableForm = 0;
242 $doEdit = 0;
243 $rowNr = 0;
244 }
245 if( /^\s*$/ ) { # empty line
246 if( $enableForm ) {
247 # empty %EDITTABLE%, so create a default table
248 $result .= handleTableStart( $theWeb, $theTopic, $tableNr, $doEdit );
249 $rowNr = 0;
250 if( $doEdit ) {
251 if( $params{"header"} ) {
252 $rowNr++;
253 rizwank 1.1 $result .= handleTableRow( $preSp, "", $tableNr, $cgiRows, $rowNr, $doEdit, 0 ) . "\n";
254 }
255 do {
256 $rowNr++;
257 $result .= handleTableRow( $preSp, "", $tableNr, $cgiRows, $rowNr, $doEdit, 0 ) . "\n";
258 } while( $rowNr < $cgiRows );
259 }
260 $result .= handleTableEnd( $theWeb, $rowNr, $doEdit );
261 $enableForm = 0;
262 }
263 $doEdit = 0;
264 $rowNr = 0;
265 }
266 }
267 $result .= "$_\n";
268 }
269 $result =~ s|\n?<nop>\n$||o; # clean up hack that handles EDITTABLE correctly if at end
270 $_[0] = $result;
271 }
272
273 # =========================
274 rizwank 1.1 sub endRenderingHandler
275 {
276 ### my ( $text ) = @_; # do not uncomment, use $_[0] instead
277
278 &TWiki::Func::writeDebug( "- EditTablePlugin::endRenderingHandler( $web.$topic )" ) if $debug;
279
280 # This handler is called by getRenderedVersion just after the line loop
281
282 return unless $_[0] =~ /$encodeStart/os;
283
284 $_[0] =~ s/$encodeStart(.*?)$encodeEnd/&decodeValue($1)/geos;
285 }
286
287 # =========================
288 sub encodeValue
289 {
290 my( $theText ) = @_;
291
292 # WindRiver specific hack to remove SprPlugin rendering
293 $theText =~ s/<a href="[\w\/]*sprreport[^>]*>(.*?) (.*?)<\/a>/$1$2/goi;
294
295 rizwank 1.1 # FIXME: *very* crude encoding to escape Wiki rendering inside form fields
296 $theText =~ s/\./%dot%/gos;
297 $theText =~ s/(.)/\.$1/gos;
298
299 # convert <br /> markup to unicode linebreak character for text areas
300 $theText =~ s/.<.b.r. .\/.>/ /gos;
301 return $theText;
302 }
303
304 # =========================
305 sub decodeValue
306 {
307 my( $theText ) = @_;
308
309 $theText =~ s/\.(.)/$1/gos;
310 $theText =~ s/%dot%/\./gos;
311 $theText =~ s/\&([^#a-z])/&$1/go; # escape non-entities
312 $theText =~ s/</\</go; # change < to entity
313 $theText =~ s/>/\>/go; # change > to entity
314 $theText =~ s/\"/\"/go; # change " to entity
315
316 rizwank 1.1 return $theText;
317 }
318
319 # =========================
320 sub parseFormat
321 {
322 my( $theFormat, $theTopic, $theWeb, $doExpand ) = @_;
323
324 $theFormat =~ s/\$nop(\(\))?//gos; # remove filler
325 $theFormat =~ s/\$quot(\(\))?/\"/gos; # expand double quote
326 $theFormat =~ s/\$percnt(\(\))?/\%/gos; # expand percent
327 $theFormat =~ s/\$dollar(\(\))?/\$/gos; # expand dollar
328 if( $doExpand ) {
329 # expanded form to be able to use %-vars in format
330 $theFormat =~ s/<nop>//gos;
331 $theFormat = &TWiki::Func::expandCommonVariables( $theFormat, $theTopic, $theWeb );
332 }
333 my @aFormat = split( /\s*\|\s*/, $theFormat );
334 $aFormat[0] = "text,16" unless @aFormat;
335 return @aFormat;
336 }
337 rizwank 1.1
338 # =========================
339 sub handleEditTableTag
340 {
341 my( $theWeb, $theTopic, $thePreSpace, $theArgs ) = @_;
342
343 $preSp = $thePreSpace || "";
344 %params = (
345 "header" => "",
346 "footer" => "",
347 "headerislabel" => "1",
348 "format" => "",
349 "changerows" => $prefCHANGEROWS,
350 "quietsave" => $prefQUIETSAVE,
351 "helptopic" => "",
352 "editbutton" => "",
353 );
354
355 my $iTopic = &TWiki::Func::extractNameValuePair( $theArgs, "include" );
356 if( $iTopic ) {
357 # include topic to read definitions
358 rizwank 1.1 if( $iTopic =~ /^([^\.]+)\.(.*)$/o ) {
359 $theWeb = $1;
360 $iTopic = $2;
361 }
362 my $text = &TWiki::Func::readTopicText( $theWeb, $iTopic );
363 $text =~ /%EDITTABLE{(.*)}%/os;
364 if( $1 ) {
365 my $args = $1;
366 if( "$theWeb.$iTopic" ne "$web.$topic" ) {
367 # expand common vars, unless oneself to prevent recursion
368 $args = &TWiki::Func::expandCommonVariables( $1, $iTopic, $theWeb );
369 }
370 extractParams( $args, \%params );
371 }
372 }
373
374 extractParams( $theArgs, \%params );
375
376 $params{"header"} = "" if( $params{"header"} =~ /^(off|no)$/oi );
377 $params{"header"} =~ s/^\s*\|//o;
378 $params{"header"} =~ s/\|\s*$//o;
379 rizwank 1.1 $params{"headerislabel"} = "" if( $params{"headerislabel"} =~ /^(off|no)$/oi );
380 $params{"footer"} = "" if( $params{"footer"} =~ /^(off|no)$/oi );
381 $params{"footer"} =~ s/^\s*\|//o;
382 $params{"footer"} =~ s/\|\s*$//o;
383 $params{"changerows"} = "" if( $params{"changerows"} =~ /^(off|no)$/oi );
384 $params{"quietsave"} = "" if( $params{"quietsave"} =~ /^(off|no)$/oi );
385
386 @format = parseFormat( $params{"format"}, $theTopic, $theWeb, 0 );
387 @formatExpanded = parseFormat( $params{"format"}, $theTopic, $theWeb, 1 );
388 $nrCols = @format;
389
390 # FIXME: No handling yet of footer
391
392 return "$preSp<nop>";
393 }
394
395 # =========================
396 sub handleTableStart
397 {
398 my( $theWeb, $theTopic, $theTableNr, $doEdit ) = @_;
399
400 rizwank 1.1 my $viewUrl = &TWiki::Func::getScriptUrl( $theWeb, $theTopic, "viewauth" )
401 . "\#edittable$theTableNr";
402 my $text = "";
403 if( $doEdit ) {
404 my $dir = "%PUBURL%/$installWeb/EditTablePlugin";
405 $text .= "$preSp<script type=\"text/javascript\" src=\"$dir/calendar.js\"></script>\n";
406 $text .= "$preSp<script type=\"text/javascript\" src=\"$dir/calendar-"
407 . "$prefJSCALENDARLANGUAGE.js\"></script>\n";
408 $text .= "$preSp<script type=\"text/javascript\" src=\"$dir/calendar-setup.js\"></script>\n";
409 }
410 $text .= "$preSp<noautolink>\n" if $doEdit;
411 $text .= "$preSp<a name=\"edittable$theTableNr\"></a>\n";
412 $text .= "$preSp<form name=\"edittable$theTableNr\" action=\"$viewUrl\" method=\"post\">\n";
413 $text .= "$preSp<input type=\"hidden\" name=\"ettablenr\" value=\"$theTableNr\" />\n";
414 $text .= "$preSp<input type=\"hidden\" name=\"etedit\" value=\"on\" />\n" unless $doEdit;
415 return $text;
416 }
417
418 # =========================
419 sub handleTableEnd
420 {
421 rizwank 1.1 my( $theWeb, $theRowNr, $doEdit ) = @_;
422
423 my $text = "$preSp<input type=\"hidden\" name=\"etrows\" value=\"$theRowNr\" />\n";
424 if( $doEdit ) {
425 # Edit mode
426 $text .= "$preSp<input type=\"submit\" name=\"etsave\" value=\"Save table\" />\n";
427 if( $params{"quietsave"} ) {
428 $text .= "$preSp<input type=\"submit\" name=\"etqsave\" value=\"Quiet save\" />\n";
429 }
430 if( $params{"changerows"} ) {
431 $text .= "$preSp<input type=\"submit\" name=\"etaddrow\" value=\"Add row\" />\n";
432 $text .= "$preSp<input type=\"submit\" name=\"etdelrow\" value=\"Delete last row\" />\n"
433 unless( $params{"changerows"} =~ /^add$/oi );
434 }
435 $text .= "$preSp<input type=\"submit\" name=\"etcancel\" value=\"Cancel\" />\n";
436
437 if( $params{"helptopic"} ) {
438 # read help topic and show below the table
439 if( $params{"helptopic"} =~ /^([^\.]+)\.(.*)$/o ) {
440 $theWeb = $1;
441 $params{"helptopic"} = $2;
442 rizwank 1.1 }
443 my $helpText = &TWiki::Func::readTopicText( $theWeb, $params{"helptopic"} );
444 #Strip out the meta data so it won't be displayed.
445 $helpText =~ s/%META:[A-Za-z0-9]+{.*?}%//g;
446 if( $helpText ) {
447 $helpText =~ s/.*?%STARTINCLUDE%//os;
448 $helpText =~ s/%STOPINCLUDE%.*//os;
449 $text .= $helpText;
450 }
451 }
452
453 } else {
454 # View mode
455 if( $params{"editbutton"} eq "hide" ) {
456 # do nothing, button assumed to be in a cell
457 } else {
458 # Add edit button to end of table
459 $text .= "$preSp" . viewEditCell( "editbutton, 1, $params{'editbutton'}" );
460 }
461 }
462 $text .= "$preSp</form>\n";
463 rizwank 1.1 $text .= "$preSp</noautolink>\n" if $doEdit;
464 return $text;
465 }
466
467 # =========================
468 sub parseEditCellFormat
469 {
470 $_[1] = &TWiki::Func::extractNameValuePair( $_[0] );
471 return "";
472 }
473
474 # =========================
475 sub viewEditCell
476 {
477 my ( $theAttr ) = @_;
478 $theAttr = &TWiki::Func::extractNameValuePair( $theAttr );
479 return "" unless( $theAttr =~ /^editbutton/ );
480
481 $params{"editbutton"} = "hide" unless( $params{"editbutton"} ); # Hide below table edit button
482
483 my @bits = split( /,\s*/, $theAttr );
484 rizwank 1.1 my $value = ""; $value = $bits[2] if( @bits > 2);
485 my $img = ""; $img = $bits[3] if( @bits > 3);
486
487 unless( $value ) {
488 $value = $prefEDITBUTTON;
489 $img = "";
490 if( $value =~ s/(.+),\s*(.+)/$1/o ) {
491 $img = $2;
492 $img =~ s|%ATTACHURL%|%PUBURL%/$installWeb/EditTablePlugin|o;
493 $img =~ s|%WEB%|$installWeb|o;
494 }
495 }
496
497 if( $img ) {
498 return "<input type=\"image\" src=\"$img\" alt=\"$value\" />";
499 } else {
500 return "<input type=\"submit\" value=\"$value\" />";
501 }
502 }
503
504 # =========================
505 rizwank 1.1 sub saveEditCellFormat
506 {
507 my ( $theFormat, $theName ) = @_;
508 return "" unless( $theFormat );
509 $theName =~ s/cell/format/;
510 return "<input type=\"hidden\" name=\"$theName\" value=\"$theFormat\" />";
511 }
512
513 # =========================
514 sub inputElement
515 {
516 my ( $theTableNr, $theRowNr, $theCol, $theName, $theValue ) = @_;
517
518 my $text = "";
519 my $i = @format - 1;
520 $i = $theCol if( $theCol < $i );
521 my @bits = split( /,\s*/, $format[$i] );
522 my @bitsExpanded = split( /,\s*/, $formatExpanded[$i] );
523
524 my $cellFormat = "";
525 $theValue =~ s/\s*%EDITCELL{(.*?)}%/&parseEditCellFormat( $1, $cellFormat )/eo;
526 rizwank 1.1 $theValue = "" if( $theValue eq " " );
527 if( $cellFormat ) {
528 my @aFormat = parseFormat( $cellFormat, $theTopic, $theWeb, 0);
529 @bits = split( /,\s*/, $aFormat[0] );
530 @aFormat = parseFormat( $cellFormat, $theTopic, $theWeb, 1);
531 @bitsExpanded = split( /,\s*/, $aFormat[0] );
532 }
533
534 my $type = "text";
535 $type = $bits[0] if @bits > 0;
536 # a table header is considered a label if read only header flag set
537 $type = "label" if( ( $params{"headerislabel"} ) && ( $theValue =~ /^\s*\*.*\*\s*$/ ) );
538 $type = "label" if( $type eq "editbutton" ); # Hide [Edit table] button
539 my $size = 0;
540 $size = $bits[1] if @bits > 1;
541 my $val = "";
542 my $valExpanded = "";
543 my $sel = "";
544 my $style = "";
545 $style = " style='background:#e8e8e8'" if ($theRowNr % 2);
546 if( $type eq "select" ) {
547 rizwank 1.1 my $expandedValue = &TWiki::Func::expandCommonVariables( $theValue, $theTopic, $theWeb );
548 $size = 1 if $size < 1;
549 $text = "<select$style name=\"$theName\" size=\"$size\">";
550 $i = 2;
551 while( $i < @bits ) {
552 $val = $bits[$i] || "";
553 $valExpanded = $bitsExpanded[$i] || "";
554 if( $valExpanded eq $expandedValue ) {
555 $text .= " <option selected=\"selected\">$val</option>";
556 } else {
557 $text .= " <option>$val</option>";
558 }
559 $i++;
560 }
561 $text .= " </select>";
562 $text .= saveEditCellFormat( $cellFormat, $theName );
563
564 } elsif( $type eq "radio" ) {
565 my $expandedValue = &TWiki::Func::expandCommonVariables( $theValue, $theTopic, $theWeb );
566 $size = 1 if $size < 1;
567 my $elements = ( @bits - 2 );
568 rizwank 1.1 my $lines = $elements / $size;
569 $lines = ($lines == int($lines)) ? $lines : int($lines + 1);
570 $text .= "<table><tr><td valign=\"top\">" if( $lines > 1 );
571 $i = 2;
572 while( $i < @bits ) {
573 $val = $bits[$i] || "";
574 $valExpanded = $bitsExpanded[$i] || "";
575 $text .= " <input type=\"radio\" name=\"$theName\" value=\"$val\"";
576 $text .= " checked=\"checked\"" if( $valExpanded eq $expandedValue );
577 $text .= " /> $val";
578 if( $lines > 1 ) {
579 if( ($i-1) % $lines ) {
580 $text .= " <br />";
581 } elsif( $i-1 < $elements ) {
582 $text .= "</td><td valign=\"top\">";
583 }
584 }
585 $i++;
586 }
587 $text .= "</td></tr></table>" if( $lines > 1 );
588 $text .= saveEditCellFormat( $cellFormat, $theName );
589 rizwank 1.1
590 } elsif( $type eq "checkbox" ) {
591 my $expandedValue = &TWiki::Func::expandCommonVariables( $theValue, $theTopic, $theWeb );
592 $size = 1 if $size < 1;
593 my $elements = ( @bits - 2 );
594 my $lines = $elements / $size;
595 my $names = "Chkbx:";
596 $lines = ($lines == int($lines)) ? $lines : int($lines + 1);
597 $text .= "<table><tr><td valign=\"top\">" if( $lines > 1 );
598 $i = 2;
599 while( $i < @bits ) {
600 $val = $bits[$i] || "";
601 $valExpanded = $bitsExpanded[$i] || "";
602 $names .= " ${theName}x$i";
603 $text .= " <input type=\"checkbox\" name=\"${theName}x$i\" value=\"$val\"";
604 $text .= " checked=\"checked\"" if( $expandedValue =~ /(^|, )\Q$valExpanded\E(,|$)/ );
605 $text .= " /> $val";
606 if( $lines > 1 ) {
607 if( ($i-1) % $lines ) {
608 $text .= " <br />";
609 } elsif( $i-1 < $elements ) {
610 rizwank 1.1 $text .= "</td><td valign=\"top\">";
611 }
612 }
613 $i++;
614 }
615 $text .= "</td></tr></table>" if( $lines > 1 );
616 $text .= " <input type=\"hidden\" name=\"$theName\" value=\"$names\" />";
617 $text .= saveEditCellFormat( $cellFormat, $theName );
618
619 } elsif( $type eq "row" ) {
620 $size = $size + $theRowNr;
621 $text = "$size<input type=\"hidden\" name=\"$theName\" value=\"$size\" />";
622 $text .= saveEditCellFormat( $cellFormat, $theName );
623
624 } elsif( $type eq "label" ) {
625 # show label text as is, and add a hidden field with value
626 my $isHeader = 0;
627 $isHeader = 1 if( $theValue =~ s/^\s*\*(.*)\*\s*$/$1/o );
628 $text = $theValue;
629
630 # To optimize things, only in the case where a read-only column is
631 rizwank 1.1 # being processed (inside of this unless() statement) do we actually
632 # go out and read the original topic. Thus the reason for the
633 # following unless() so we only read the topic the first time through.
634 unless( defined $table ) {
635 # To deal with the situation where TWiki variables, like
636 # %CALC%, have already been processed and end up getting saved
637 # in the table that way (processed), we need to read in the
638 # topic page in raw format
639 my $topicContents = TWiki::Func::readTopicText( $web, $topic );
640 $table = TWiki::Plugins::Table->new( $topicContents );
641 }
642 my $cell = $table->getCell( $theTableNr, $theRowNr - 1, $theCol );
643 $theValue = $cell if( defined $cell ); # original value from file
644 $theValue = $encodeStart . encodeValue( $theValue ) . $encodeEnd unless( $theValue eq "" );
645 $theValue = "\*$theValue\*" if( $isHeader );
646 $text .= "<input$style type=\"hidden\" name=\"$theName\" value=\"$theValue\" />";
647 $text = "\*$text\*" if( $isHeader );
648
649 } elsif( $type eq "textarea" ) {
650 my ($rows, $cols) = split( /x/, $size );
651 $rows = 3 if $rows < 1;
652 rizwank 1.1 $cols = 30 if $cols < 1;
653
654 $theValue = $encodeStart . encodeValue( $theValue ) . $encodeEnd unless( $theValue eq "" );
655 $text .= "<textarea$style rows=\"$rows\" cols=\"$cols\" name=\"$theName\">$theValue</textarea>";
656 $text .= saveEditCellFormat( $cellFormat, $theName );
657
658 } elsif( $type eq "date" ) {
659 my $ifFormat = "";
660 $ifFormat = $bits[3] if( @bits > 3 );
661 $ifFormat = $ifFormat || $prefJSCALENDARDATEFORMAT;
662 $size = 10 if $size < 1;
663 $theValue = $encodeStart . encodeValue( $theValue ) . $encodeEnd unless( $theValue eq "" );
664 $text .= "<input type=\"text\" name=\"$theName\" id=\"id$theName\" size=\"$size\" value=\"$theValue\" />";
665 $text .= saveEditCellFormat( $cellFormat, $theName );
666 $text .= "<button type=\"reset\" id=\"trigger$theName\">...</button>";
667 $text .= "<script type=\"text/javascript\">Calendar.setup({inputField : \"id$theName\", ifFormat ";
668 $text .= ": \"$ifFormat\", button : \"trigger$theName\", singleClick : true $prefJSCALENDAROPTIONS });</script>";
669
670 $query->{'jscalendar'} = 1;
671
672 } else { # if( $type eq "text")
673 rizwank 1.1 $size = 16 if $size < 1;
674 $theValue = $encodeStart . encodeValue( $theValue ) . $encodeEnd unless( $theValue eq "" );
675 $text = "<input$style type=\"text\" name=\"$theName\" size=\"$size\" value=\"$theValue\"/>";
676 $text .= saveEditCellFormat( $cellFormat, $theName );
677 }
678 return $text;
679 }
680
681 # =========================
682 sub handleTableRow
683 {
684 my ( $thePre, $theRow, $theTableNr, $theRowMax, $theRowNr, $doEdit, $doSave ) = @_;
685
686 $thePre = "" unless( defined( $thePre ) );
687 my $text = "$thePre\|";
688
689 if( $doEdit ) {
690 $theRow =~ s/\|\s*$//o;
691 @cells = split( /\|/, $theRow );
692 my $tmp = @cells;
693 $nrCols = $tmp if( $tmp > $nrCols ); # expand number of cols
694 rizwank 1.1 my $val = "";
695 my $cellFormat = "";
696 my $cell = "";
697 my $cellDefined = 0;
698 my $col = 0;
699 while( $col < $nrCols ) {
700 $col += 1;
701 $cellDefined = 0;
702 $val = $query->param( "etcell${theRowNr}x$col" );
703 if( $val && $val =~ /^Chkbx: (etcell.*)/ ) {
704 # Multiple checkboxes, val has format "Chkbx: etcell4x2x2 etcell4x2x3 ..."
705 my $chkBoxeNames = $1;
706 my $chkBoxVals = "";
707 foreach( split( /\s/, $chkBoxeNames ) ) {
708 $val = $query->param( $_ );
709 $chkBoxVals .= "$val, " if( defined $val );
710 }
711 $chkBoxVals =~ s/, $//;
712 $val = $chkBoxVals;
713 }
714 $cellFormat = $query->param( "etformat${theRowNr}x$col" );
715 rizwank 1.1 $val .= " %EDITCELL{$cellFormat}%" if( $cellFormat );
716 if( defined $val ) {
717 # change any new line character sequences to <br />
718 $val =~ s/(\n\r?)|(\r\n?)+/<br \/>/gos;
719 # escape "|" to HTML entity
720 $val =~ s/\|/\&\#124;/gos;
721 $cellDefined = 1;
722 # Expand %-vars
723 $cell = $val;
724 } elsif( $col <= @cells ) {
725 $cell = $cells[$col-1];
726 $cellDefined = 1 if( length( $cell ) > 0 );
727 $cell =~ s/^\s//o;
728 $cell =~ s/\s$//o;
729 } else {
730 $cell = "";
731 }
732 if( ( $theRowNr <= 1 ) && ( $params{"header"} ) ) {
733 unless( $cell ) {
734 if( $params{"header"} =~ /^on$/i ) {
735 if( ( @format >= $col ) && ( $format[$col-1] =~ /(.*?)\,/ ) ) {
736 rizwank 1.1 $cell = $1;
737 }
738 $cell = "text" unless $cell;
739 $cell = "*$cell*";
740 } else {
741 my @hCells = split( /\|/, $params{"header"} );
742 $cell = $hCells[$col-1] if( @hCells >= $col );
743 $cell = "*text*" unless $cell;
744 }
745 }
746 $text .= "$cell\|";
747
748 } elsif( $doSave ) {
749 $text .= " $cell \|";
750
751 } else {
752 if( ( ! $cellDefined ) && ( @format >= $col )
753 && ( $format[$col-1] =~ /^\s*(.*?)\,\s*(.*?)\,\s*(.*?)\s*$/ ) ) {
754 # default value of "| text, 20, a, b, c |" cell is "a, b, c"
755 # default value of "| select, 1, a, b, c |" cell is "a"
756 $val = $1; # type
757 rizwank 1.1 $cell = $3;
758 $cell = "" unless( defined $cell && $cell ne "" ); # Proper handling of "0"
759 $cell =~ s/\,.*$//o if( $val eq "select" || $val eq "date" );
760 }
761 $text .= inputElement( $theTableNr, $theRowNr, $col-1, "etcell${theRowNr}x$col", $cell ) . " \|";
762 }
763 }
764 } else {
765 $theRow =~ s/%EDITCELL{(.*?)}%/viewEditCell($1)/geo;
766 $text .= "$theRow";
767 }
768 return $text;
769 }
770
771 # =========================
772 sub doSaveTable
773 {
774 my ( $theWeb, $theTopic, $theTableNr, $quiet ) = @_;
775
776 &TWiki::Func::writeDebug( "- EditTablePlugin::doSaveTable( $theWeb, $theTopic, $theTableNr, $quiet )" ) if $debug;
777
778 rizwank 1.1 my $text = &TWiki::Func::readTopicText( $theWeb, $theTopic );
779
780 my $cgiRows = $query->param( 'etrows' ) || 1;
781 my $tableNr = 0;
782 my $rowNr = 0;
783 my $insideTable = 0;
784 my $doSave = 0;
785 my $result = "";
786 # appended stuff is a hack to handle EDITTABLE correctly if at end
787 foreach( split( /\r?\n/, "$text\n<nop>\n" ) ) {
788 if( /%EDITTABLE{(.*)}%/o ) {
789 $tableNr += 1;
790 if( $tableNr == $theTableNr ) {
791 $doSave = 1;
792 }
793 }
794 if( $doSave ) {
795 if( /^(\s*)\|.*\|\s*$/ ) {
796 $insideTable = 1;
797 $rowNr++;
798 if( $rowNr > $cgiRows ) {
799 rizwank 1.1 # deleted row
800 $rowNr--;
801 next;
802 }
803 s/^(\s*)\|(.*)/&handleTableRow( $1, $2, $tableNr, $cgiRows, $rowNr, 1, 1 )/eo;
804
805 } elsif( $insideTable ) {
806 $insideTable = 0;
807 if( $rowNr < $cgiRows ) {
808 while( $rowNr < $cgiRows ) {
809 $rowNr++;
810 $result .= handleTableRow( $preSp, "", $tableNr, $cgiRows, $rowNr, 1, 1 ) . "\n";
811 }
812 }
813 $doSave = 0;
814 $rowNr = 0;
815 }
816 if( /^\s*$/ ) { # empty line
817 if( $doSave ) {
818 # empty %EDITTABLE%, so create a default table
819 $rowNr = 0;
820 rizwank 1.1 if( $params{"header"} ) {
821 $rowNr++;
822 $result .= handleTableRow( $preSp, "", $tableNr, $cgiRows, $rowNr,1 , 1 ) . "\n";
823 }
824 while( $rowNr < $cgiRows ) {
825 $rowNr++;
826 $result .= handleTableRow( $preSp, "", $tableNr, $cgiRows, $rowNr, 1, 1 ) . "\n";
827 }
828 }
829 $doSave = 0;
830 $rowNr = 0;
831 }
832 }
833 $result .= "$_\n";
834 }
835 $result =~ s|\n<nop>\n$||o; # clean up hack that handles EDITTABLE correctly if at end
836
837 my $error = &TWiki::Func::saveTopicText( $theWeb, $theTopic, $result, "", $quiet );
838 TWiki::Func::setTopicEditLock( $theWeb, $theTopic, 0 ); # unlock Topic
839 my $url = &TWiki::Func::getViewUrl( $theWeb, $theTopic );
840 if( $error ) {
841 rizwank 1.1 $url = &TWiki::Func::getOopsUrl( $theWeb, $theTopic, "oopssaveerr", $error );
842 }
843 &TWiki::Func::redirectCgiQuery( $query, $url );
844 }
845
846 # =========================
847 sub doCancelEdit
848 {
849 my ( $theWeb, $theTopic ) = @_;
850
851 &TWiki::Func::writeDebug( "- EditTablePlugin::doCancelEdit( $theWeb, $theTopic )" ) if $debug;
852
853 TWiki::Func::setTopicEditLock( $theWeb, $theTopic, 0 );
854
855 &TWiki::Func::redirectCgiQuery( $query, &TWiki::Func::getViewUrl( $theWeb, $theTopic ) );
856 }
857
858 # =========================
859 sub doEnableEdit
860 {
861 my ( $theWeb, $theTopic, $doCheckIfLocked ) = @_;
862 rizwank 1.1
863 &TWiki::Func::writeDebug( "- EditTablePlugin::doEnableEdit( $theWeb, $theTopic )" ) if $debug;
864
865 my $wikiUserName = &TWiki::Func::getWikiUserName();
866 if( ! &TWiki::Func::checkAccessPermission( "change", $wikiUserName, "", $theTopic, $theWeb ) ) {
867 # user has not permission to change the topic
868 my $url = &TWiki::Func::getOopsUrl( $theWeb, $theTopic, "oopsaccesschange" );
869 &TWiki::Func::redirectCgiQuery( $query, $url );
870 return 0;
871 }
872
873 my( $oopsUrl, $lockUser ) = &TWiki::Func::checkTopicEditLock( $theWeb, $theTopic );
874 if( ( $doCheckIfLocked ) && ( $lockUser ) ) {
875 # warn user that other person is editing this topic
876 &TWiki::Func::redirectCgiQuery( $query, $oopsUrl );
877 return 0;
878 }
879 TWiki::Func::setTopicEditLock( $theWeb, $theTopic, 1 );
880
881 return 1;
882 }
883 rizwank 1.1
884 # =========================
885
886 # The following code is copied from the ChartPlugin Table object.
887
888 package TWiki::Plugins::Table;
889
890 sub new
891 {
892 my ($class, $topicContents) = @_;
893 my $this = {};
894 bless $this, $class;
895 $this->_parseOutTables($topicContents);
896 return $this;
897 }
898
899 # The guts of this routine was initially copied from SpreadSheetPlugin.pm
900 # and were used in the ChartPlugin Table object which this was copied from,
901 # but this has been modified to support the functionality needed by the
902 # EditTablePlugin. One major change is to only count and save tables
903 # following an %EDITTABLE{.*}% tag.
904 rizwank 1.1 #
905 # This routine basically returns an array of hashes where each hash
906 # contains the information for a single table. Thus the first hash in the
907 # array represents the first table found on the topic page, the second hash
908 # in the array represents the second table found on the topic page, etc.
909 sub _parseOutTables
910 {
911 my ($this, $topic) = @_;
912 my $tableNum = 1; # Table number (only count tables with EDITTABLE tag)
913 my @tableMatrix; # Currently parsed table.
914
915 my $inEditTable = 0; # Flag to keep track if in an EDITTABLE table
916 my $result = "";
917 my $insidePRE = 0;
918 my $insideTABLE = 0;
919 my $line = "";
920 my @row = ();
921
922 $topic =~ s/\r//go;
923 $topic =~ s/\\\n//go; # Join lines ending in "\"
924 foreach( split( /\n/, $topic ) ) {
925 rizwank 1.1
926 # change state:
927 m|<pre>|i && ( $insidePRE = 1 );
928 m|<verbatim>|i && ( $insidePRE = 1 );
929 m|</pre>|i && ( $insidePRE = 0 );
930 m|</verbatim>|i && ( $insidePRE = 0 );
931
932 if( ! $insidePRE ) {
933 $inEditTable = 1 if (/%EDITTABLE{(.*)}%/);
934 if ($inEditTable) {
935 if( /^\s*\|.*\|\s*$/ ) {
936 # inside | table |
937 $insideTABLE = 1;
938 $line = $_;
939 $line =~ s/^(\s*\|)(.*)\|\s*$/$2/o; # Remove starting '|'
940 @row = split( /\|/o, $line, -1 );
941 _trim(\@row);
942 push (@tableMatrix, [ @row ]);
943
944 } else {
945 # outside | table |
946 rizwank 1.1 if( $insideTABLE ) {
947 # We were inside a table and are now outside of it so
948 # save the table info into the Table object.
949 $insideTABLE = 0;
950 $inEditTable = 0;
951 if (@tableMatrix != 0) {
952 # Save the table via its table number
953 $$this{"TABLE_$tableNum"} = [@tableMatrix];
954 $tableNum++;
955 }
956 undef @tableMatrix; # reset table matrix
957 }
958 }
959 }
960 }
961 $result .= "$_\n";
962 }
963 $$this{NUM_TABLES} = $tableNum;
964 }
965
966 # Trim any leading and trailing white space and/or '*'.
967 rizwank 1.1 sub _trim
968 {
969 my ($totrim) = @_;
970 for my $element (@$totrim) {
971 $element =~ s/^[\s\*]+//; # Strip of leading white/*
972 $element =~ s/[\s\*]+$//; # Strip of trailing white/*
973 }
974 }
975
976 # Return the contents of the specified cell
977 sub getCell
978 {
979 my ( $this, $tableNum, $row, $column ) = @_;
980
981 my @selectedTable = $this->getTable( $tableNum );
982 my $value = $selectedTable[$row][$column];
983 return $value;
984 }
985
986 sub getTable
987 {
988 rizwank 1.1 my ($this, $tableNumber) = @_;
989 my $table = $$this{"TABLE_$tableNumber"};
990 return @$table if defined( $table );
991 return ();
992 }
993
994 1;
|