~kosova/+junk/tuxfamily-twiki

« back to all changes in this revision

Viewing changes to foswiki/lib/Foswiki/Plugins/EditTablePlugin/Core.pm

  • Committer: James Michael DuPont
  • Date: 2009-07-18 19:58:49 UTC
  • Revision ID: jamesmikedupont@gmail.com-20090718195849-vgbmaht2ys791uo2
added foswiki

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
 
 
2
=pod
 
3
 
 
4
Work in progress: changing the table based on form parameters
 
5
 
 
6
(sub handleTableChangeParams)
 
7
 
 
8
Test in topic:
 
9
 
 
10
%EDITTABLE{}%
 
11
| *AAA* |
 
12
 
 
13
---++ Add rows from table 1
 
14
<form name="edittable1" action="%SCRIPTURL{"viewauth"}%/Sandbox/EditTablePluginTest2#edittable1" method="post">
 
15
<input type="hidden" name="ettablenr" value="1" />
 
16
Position: <input type="text" class="foswikiInputField" size="8" name="etaddrows_position" value="1" />
 
17
Count: <input type="text" class="foswikiInputField" size="8" name="etaddrows_count" value="5" />
 
18
<input type="submit" name="etsave" id="etsave" value="Add rows" class="foswikiSubmit" />
 
19
</form>
 
20
 
 
21
---++ Delete rows from table 1
 
22
<form name="edittable1" action="%SCRIPTURL{"viewauth"}%/Sandbox/EditTablePluginTest2#edittable1" method="post">
 
23
<input type="hidden" name="ettablenr" value="1" />
 
24
Position: <input type="text" class="foswikiInputField" size="8" name="etdeleterows_position" value="1" />
 
25
Count: <input type="text" class="foswikiInputField" size="8" name="etdeleterows_count" value="5" />
 
26
<input type="submit" name="etsave" id="etsave" value="Delete rows" class="foswikiSubmit" />
 
27
</form>
 
28
 
 
29
=cut
 
30
 
 
31
package Foswiki::Plugins::EditTablePlugin::Core;
 
32
 
 
33
use strict;
 
34
use warnings;
 
35
use Assert;
 
36
use Foswiki::Func;
 
37
use CGI qw( :all );
 
38
use Foswiki::Plugins::EditTablePlugin::Data;
 
39
use Foswiki::Plugins::EditTablePlugin::EditTableData;
 
40
 
 
41
my $DEFAULT_FIELD_SIZE           = 16;
 
42
my $PLACEHOLDER_BUTTONROW_TOP    = 'PLACEHOLDER_BUTTONROW_TOP';
 
43
my $PLACEHOLDER_BUTTONROW_BOTTOM = 'PLACEHOLDER_BUTTONROW_BOTTOM';
 
44
my $PLACEHOLDER_SEPARATOR_SEARCH_RESULTS =
 
45
  'PLACEHOLDER_SEPARATOR_SEARCH_RESULTS';
 
46
my $HTML_TAGS =
 
47
qr'var|ul|u|tt|tr|th|td|table|sup|sub|strong|strike|span|small|samp|s|pre|p|ol|li|kbd|ins|img|i|hr|h|font|em|div|dfn|del|code|cite|center|br|blockquote|big|b|address|acronym|abbr|a'o;
 
48
 
 
49
my $prefCHANGEROWS;
 
50
my $prefEDIT_BUTTON;
 
51
my $prefSAVE_BUTTON;
 
52
my $prefQUIET_SAVE_BUTTON;
 
53
my $prefADD_ROW_BUTTON;
 
54
my $prefDELETE_LAST_ROW_BUTTON;
 
55
my $prefCANCEL_BUTTON;
 
56
my $prefMESSAGE_INCLUDED_TOPIC_DOES_NOT_EXIST;
 
57
my $prefQUIETSAVE;
 
58
my $preSp;
 
59
my %params;
 
60
my @format;
 
61
my @formatExpanded;
 
62
my $nrCols;
 
63
my $warningMessage;
 
64
 
 
65
my $PATTERN_EDITTABLEPLUGIN =
 
66
  $Foswiki::Plugins::EditTablePlugin::Data::PATTERN_EDITTABLEPLUGIN;
 
67
my $PATTERN_TABLEPLUGIN =
 
68
  $Foswiki::Plugins::EditTablePlugin::Data::PATTERN_TABLEPLUGIN;
 
69
my $PATTERN_EDITCELL               = qr'%EDITCELL{(.*?)}%'o;
 
70
my $PATTERN_TABLE_ROW_FULL         = qr'^(\s*)\|.*\|\s*$'o;
 
71
my $PATTERN_TABLE_ROW              = qr'^(\s*)\|(.*)'o;
 
72
my $PATTERN_SPREADSHEETPLUGIN_CALC = qr'%CALC(?:{(.*?)})?%'o;
 
73
my $SPREADSHEETPLUGIN_CALC_SUBSTITUTION = "<span class='editTableCalc'>CALC</span>";
 
74
my $MODE                           = {
 
75
    READ           => ( 1 << 1 ),
 
76
    EDIT           => ( 1 << 2 ),
 
77
    SAVE           => ( 1 << 3 ),
 
78
    SAVEQUIET      => ( 1 << 4 ),
 
79
    CANCEL         => ( 1 << 5 ),
 
80
    EDITNOTALLOWED => ( 1 << 6 ),
 
81
};
 
82
my %tableMatrix;
 
83
my $query;
 
84
 
 
85
=begin TML
 
86
 
 
87
Initializes variables.
 
88
 
 
89
=cut
 
90
 
 
91
sub init {
 
92
    $preSp                      = '';
 
93
    %params                     = ();
 
94
    @format                     = ();
 
95
    @formatExpanded             = ();
 
96
    $prefCHANGEROWS             = undef;
 
97
    $prefEDIT_BUTTON            = undef;
 
98
    $prefSAVE_BUTTON            = undef;
 
99
    $prefQUIET_SAVE_BUTTON      = undef;
 
100
    $prefADD_ROW_BUTTON         = undef;
 
101
    $prefDELETE_LAST_ROW_BUTTON = undef;
 
102
    $prefDELETE_LAST_ROW_BUTTON = undef;
 
103
    $prefQUIETSAVE              = undef;
 
104
    $nrCols                     = undef;
 
105
    $query                      = undef;
 
106
    $warningMessage             = '';
 
107
    %tableMatrix                = ();
 
108
 
 
109
    getPreferencesValues();
 
110
}
 
111
 
 
112
=begin TML
 
113
 
 
114
Init variables again. If called from INCLUDE this is the first time we init
 
115
 
 
116
=cut
 
117
 
 
118
sub initIncludedTopic {
 
119
    $preSp = '' unless $preSp;
 
120
    getPreferencesValues();
 
121
}
 
122
 
 
123
=begin TML
 
124
 
 
125
StaticMethod parseTables($text, $topic, $web)
 
126
 
 
127
Read and parse table data once for each topic.
 
128
Stores data in hash $tableMatrix{webname}{topicname}.
 
129
Even if we are just viewing table data (not editing), we can deal with text inside edit tables in a special way. For instance by calling handleTmlInTables on the table text.
 
130
 
 
131
=cut
 
132
 
 
133
sub parseTables {
 
134
    my ( $inText, $inTopic, $inWeb ) = @_;
 
135
 
 
136
    # my $text = $_[0]
 
137
 
 
138
    return if defined $tableMatrix{$inWeb}{$inTopic};
 
139
 
 
140
    my $tableData = Foswiki::Plugins::EditTablePlugin::Data->new();
 
141
    $_[0] = $tableData->parseText($inText);
 
142
 
 
143
    _debug("EditTablePlugin::Core::parseTables - after parseText, text=$_[0]");
 
144
 
 
145
    $tableMatrix{$inWeb}{$inTopic} = $tableData;
 
146
}
 
147
 
 
148
=begin TML
 
149
 
 
150
---+++ process( $text, $topic, $web, $includingTopic, $includingWeb )
 
151
 
 
152
Called from commonTagsHandler. Pass over to processText in 'no Save' mode.
 
153
 
 
154
=cut
 
155
 
 
156
sub process {
 
157
 
 
158
    # my $text = $_[0]
 
159
    # my $topic = $_[1]
 
160
    # my $web = $_[2]
 
161
    # my $includingTopic = $_[3]
 
162
    # my $includingWeb = $_[4]
 
163
 
 
164
    my $mode        = $MODE->{READ};
 
165
    my $saveTableNr = 0;
 
166
    processText( $mode, $saveTableNr, @_ );
 
167
}
 
168
 
 
169
=begin TML
 
170
 
 
171
---+++ processText( $mode, $saveTableNr, $text, $topic, $web, $includingTopic, $includingWeb )
 
172
 
 
173
Process the text line by line.
 
174
When a EditTablePlugin table is encountered, its contents is rendered according to the view:
 
175
   * View mode - default
 
176
   * Edit mode - when an Edit button is clicked, renders the rest of the table in edit mode
 
177
   * Save mode - when called from a Save button: calls processText again, only renders the selected table number, then saves the topic text
 
178
 
 
179
=cut
 
180
 
 
181
sub processText {
 
182
    my ( $inMode, $inSaveTableNr, $inText, $inTopic, $inWeb, $inIncludingTopic,
 
183
        $inIncludingWeb )
 
184
      = @_;
 
185
 
 
186
    my $mode = $inMode;
 
187
    my $doSave = ( $mode & $MODE->{SAVE} ) ? 1 : 0;
 
188
    $query = Foswiki::Func::getCgiQuery();
 
189
 
 
190
    # Item1458 ignore all saving unless it happened using POST method.
 
191
    $doSave = 0
 
192
      if ( $query && $query->method() && uc( $query->method() ) ne 'POST' );
 
193
 
 
194
    _debug(
 
195
"EditTablePlugin::Core::processText( inSaveTableNr=$inSaveTableNr; inText=$inText\ninTopic=$inTopic\ninWeb=$inWeb\ninIncludingTopic=$inIncludingTopic\ninIncludingWeb=$inIncludingWeb\nmode="
 
196
          . _modeToString($mode) );
 
197
    _debug( "query params=" . Dumper( $query->{param} ) );
 
198
 
 
199
    my $topic = $query->param('ettabletopic') || $inTopic;
 
200
    my $web   = $query->param('ettableweb')   || $inWeb;
 
201
 
 
202
    my $paramTableNr = $query->param('ettablenr') || 0;
 
203
 
 
204
    my $meta;
 
205
    my $topicText = $inText;
 
206
 
 
207
    if ($doSave) {
 
208
        ( $meta, $topicText ) = Foswiki::Func::readTopic( $web, $topic );
 
209
 
 
210
        # fill the matrix with fresh new table
 
211
        undef $tableMatrix{$web}{$topic};
 
212
        parseTables( $topicText, $topic, $web );
 
213
    }
 
214
    else {
 
215
        parseTables( $inText, $topic, $web );
 
216
    }
 
217
    my $tableData = $tableMatrix{$web}{$topic};
 
218
 
 
219
    # ========================================
 
220
    # LOOP THROUGH TABLES
 
221
 
 
222
    my $tableNr = 0;    # current EditTable table
 
223
    foreach my Foswiki::Plugins::EditTablePlugin::EditTableData $editTableData (
 
224
        @{ $tableData->{editTableDataList} } )
 
225
    {
 
226
 
 
227
        if ($Foswiki::Plugins::EditTablePlugin::debug) {
 
228
            use Data::Dumper;
 
229
            _debug( "EditTablePlugin::Core::processText; editTableData="
 
230
                  . Dumper($editTableData) );
 
231
        }
 
232
 
 
233
        my $isEditingTable = 0;
 
234
        my $editTableTag   = $editTableData->{'tagline'};
 
235
 
 
236
       # store processed lines of this tableText
 
237
       # the list of lines will be put back into the topic text after processing
 
238
        my @result = ();
 
239
 
 
240
        $tableNr++;
 
241
 
 
242
        # ========================================
 
243
        # START HANDLE EDITTABLE TAG
 
244
 
 
245
        if ( $mode & $MODE->{READ} ) {
 
246
 
 
247
            # process the tag contents
 
248
            handleEditTableTag( $web, $topic, $editTableData->{'params'} );
 
249
 
 
250
            # remove the original EDITTABLE{} in the tag pre_EDITTABLE{}_post
 
251
            # so we just have pre__post
 
252
            $editTableTag =
 
253
              $editTableData->{'pretag'} . $editTableData->{'posttag'};
 
254
 
 
255
            # expand macros in tagline without creating infinite recursion:
 
256
            $editTableTag =~ s/%EDITTABLE{/%TMP_ETP_STUB_TAG{/o;
 
257
            $editTableTag = Foswiki::Func::expandCommonVariables($editTableTag);
 
258
 
 
259
            # put tag back
 
260
            $editTableTag =~ s/TMP_ETP_STUB_TAG/EDITTABLE/o;
 
261
        }
 
262
 
 
263
        # END HANDLE EDITTABLE TAG
 
264
        # ========================================
 
265
 
 
266
        # ========================================
 
267
        # START FOOTER AND HEADER ROW COUNT
 
268
 
 
269
        ( $editTableData->{headerRowCount}, $editTableData->{footerRowCount} ) =
 
270
          getHeaderAndFooterCount($editTableTag);
 
271
 
 
272
        _debug(
 
273
"EditTablePlugin::Core::processText; headerRowCount=$editTableData->{headerRowCount}; footerRowCount=$editTableData->{footerRowCount}; tableText="
 
274
              . join( "\n", @{ $editTableData->{'lines'} } ) );
 
275
 
 
276
        # END FOOTER AND HEADER ROW COUNT
 
277
        # ========================================
 
278
 
 
279
        # ========================================
 
280
        # START HANDLE TABLE CHANGE PARAMETERS
 
281
 
 
282
        my $tableChanges =
 
283
          Foswiki::Plugins::EditTablePlugin::EditTableData::createTableChangesMap(
 
284
            $query->param('ettablechanges') )
 
285
          ; # a mapping of rows and their changed state; keys are row numbers (starting with 0), values are row states: 0 (not changed), 1 (row to be added), -1 (row to be deleted); the map is created using the param 'ettablechanges'; the map is created and updated in EDIT mode, and used in SAVE mode.
 
286
 
 
287
        my $tableStats = $editTableData->getTableStatistics($tableChanges);
 
288
 
 
289
        if ( ( $mode & $MODE->{READ} ) || ( $tableNr == $inSaveTableNr ) ) {
 
290
 
 
291
            my $allowedToEdit = 0;
 
292
 
 
293
            if ( $tableNr == $inSaveTableNr ) {
 
294
                $mode =
 
295
                  handleTableChangeParams( $mode, $editTableData, $tableStats,
 
296
                    $tableChanges, $web, $topic );
 
297
                $tableStats = $editTableData->getTableStatistics($tableChanges);
 
298
            }
 
299
 
 
300
            if (
 
301
                ( $paramTableNr == $tableNr )
 
302
                && (  $web . '.'
 
303
                    . $topic eq
 
304
"$Foswiki::Plugins::EditTablePlugin::web.$Foswiki::Plugins::EditTablePlugin::topic"
 
305
                )
 
306
              )
 
307
            {
 
308
                $isEditingTable = 1;
 
309
 
 
310
                # handle button actions
 
311
                if ( $mode & $MODE->{READ} ) {
 
312
 
 
313
                    $mode =
 
314
                      handleButtonActions( $mode, $editTableData, $tableStats,
 
315
                        $tableChanges, $web, $topic );
 
316
 
 
317
                    if (   ( $mode & $MODE->{SAVE} )
 
318
                        || ( $mode & $MODE->{SAVEQUIET} ) )
 
319
                    {
 
320
                        return processText( $mode, $tableNr, $inText, $topic,
 
321
                            $web, $inIncludingTopic, $inIncludingWeb );
 
322
                    }
 
323
                    elsif ( $mode & $MODE->{CANCEL} ) {
 
324
                        return;    # in case browser does not redirect
 
325
                    }
 
326
                    elsif ( $mode & $MODE->{EDITNOTALLOWED} ) {
 
327
                        return;
 
328
                    }
 
329
                }
 
330
            }
 
331
        }   # if ( ( $mode & $MODE->{READ} ) || ( $tableNr == $inSaveTableNr ) )
 
332
 
 
333
        # END HANDLE TABLE CHANGE PARAMETERS
 
334
        # ========================================
 
335
 
 
336
        # ========================================
 
337
        # START FORM TOP
 
338
 
 
339
        my $doEdit = $isEditingTable ? 1 : 0;
 
340
 
 
341
        if ( ( $mode & $MODE->{READ} ) ) {
 
342
            my $tableStart = handleTableStart( $web, $topic, $inIncludingWeb,
 
343
                $inIncludingTopic, $tableNr, $doEdit );
 
344
            push( @result, $tableStart );
 
345
        }
 
346
 
 
347
        # END FORM TOP
 
348
        # ========================================
 
349
 
 
350
        # ========================================
 
351
        # START PROCESSING ROWS
 
352
 
 
353
        if ($isEditingTable) {
 
354
            ( my $processedTableData, $tableChanges ) =
 
355
              processTableData( $tableNr, $editTableData, $tableChanges,
 
356
                $doEdit, $doSave, $web, $topic );
 
357
            push( @result, @{$processedTableData} );
 
358
        }
 
359
        else {
 
360
 
 
361
            my $lines = $editTableData->{'lines'};
 
362
 
 
363
            # render the row: EDITCELL and format tokens
 
364
            my $rowNr = 0;
 
365
            for ( @{$lines} ) {
 
366
                my $isNewRow = 0;
 
367
s/$PATTERN_TABLE_ROW/handleTableRow( $1, $2, $tableNr, $isNewRow, $rowNr++, $doEdit, $doSave, $web, $topic )/eo;
 
368
            }
 
369
            @{$lines} = map { $_ .= "\n" } @{$lines};
 
370
            handleTmlInTables($lines) if !$doSave;
 
371
 
 
372
            push( @result, @{$lines} );
 
373
        }
 
374
 
 
375
        # END PROCESSING ROWS
 
376
        # ========================================
 
377
 
 
378
        # ========================================
 
379
        # START PUT PROCESSED TABLE BACK IN TEXT
 
380
 
 
381
        my $resultText = join( "", @result );
 
382
 
 
383
        # We do not want TablePlugin to sort the table we are editing
 
384
        # So we use the special setting disableallsort which is added
 
385
        # to TABLE for exactly this purpose. Please do not remove this
 
386
        # feature again.
 
387
        if (  !$doSave
 
388
            && $isEditingTable
 
389
            && ( $editTableTag !~ /%TABLE{.*?disableallsort="on".*?}%/ ) )
 
390
        {
 
391
            $editTableTag =~ s/(%TABLE{.*?)(}%)/$1 disableallsort="on"$2/;
 
392
        }
 
393
 
 
394
        # The Data::parseText merges a TABLE and EDITTABLE to one line
 
395
        # We split it again to make editing easier for the user
 
396
        # If the two were originally one line - they now become two unless
 
397
        # there was white space between them
 
398
        $editTableTag =~ s/(%EDITTABLE{.*?}%)(%TABLE{.*?}%)/$1\n$2/;
 
399
        $editTableTag =~ s/(%TABLE{.*?}%)(%EDITTABLE{.*?}%)/$1\n$2/;
 
400
 
 
401
        $resultText = "$editTableTag\n$resultText";
 
402
 
 
403
        # END PUT PROCESSED TABLE BACK IN TEXT
 
404
        # ========================================
 
405
 
 
406
        # ========================================
 
407
        # START FORM BOTTOM
 
408
 
 
409
        if ( ( $mode & $MODE->{READ} ) ) {
 
410
            my $tableEnd = handleTableEnd(
 
411
                $doEdit, $tableChanges,
 
412
                $tableStats->{headerRowCount},
 
413
                $tableStats->{footerRowCount}
 
414
            );
 
415
            $resultText .= $tableEnd;
 
416
        }
 
417
        chomp $resultText;    # remove spurious newline at end
 
418
 
 
419
        # END FORM BOTTOM
 
420
        # ========================================
 
421
 
 
422
        # ========================================
 
423
        # START BUTTON ROWS
 
424
 
 
425
        # button row at top or bottom
 
426
        if ( ( $mode & $MODE->{READ} ) ) {
 
427
            my $pos = $params{'buttonrow'} || 'bottom';
 
428
            my $buttonRow =
 
429
              createButtonRow( $web, $topic, $inIncludingWeb, $inIncludingTopic,
 
430
                $doEdit );
 
431
            if ( $pos eq 'top' ) {
 
432
                $resultText =~ s/$PLACEHOLDER_BUTTONROW_BOTTOM//go;    # remove
 
433
                $resultText =~ s/$PLACEHOLDER_BUTTONROW_TOP/$buttonRow/go;
 
434
            }
 
435
            else {
 
436
                $resultText =~ s/$PLACEHOLDER_BUTTONROW_TOP//go;       # remove
 
437
                $resultText =~ s/$PLACEHOLDER_BUTTONROW_BOTTOM/$buttonRow/go;
 
438
            }
 
439
        }
 
440
 
 
441
        # render variables (only in view mode)
 
442
        $resultText = Foswiki::Func::expandCommonVariables($resultText)
 
443
          if ( $mode & $MODE->{READ} );
 
444
 
 
445
        _debug("After parsing, resultText=$resultText");
 
446
        _debug("After parsing, tableNr=$tableNr");
 
447
        
 
448
        $topicText =~ s/<!--%EDITTABLESTUB\{$tableNr\}%-->/$resultText/g;
 
449
 
 
450
        # END BUTTON ROWS
 
451
        # ========================================
 
452
 
 
453
    }    # foreach
 
454
 
 
455
    # ========================================
 
456
    # START SAVE
 
457
 
 
458
    if ($doSave) {
 
459
        my $error =
 
460
          Foswiki::Func::saveTopic( $web, $topic, $meta, $topicText,
 
461
            { dontlog => ( $mode & $MODE->{SAVEQUIET} ) } );
 
462
 
 
463
        Foswiki::Func::setTopicEditLock( $web, $topic, 0 );    # unlock Topic
 
464
        my $url = Foswiki::Func::getViewUrl( $web, $topic );
 
465
        $url .= "#edittable$inSaveTableNr";
 
466
        if ($error) {
 
467
            $url =
 
468
              Foswiki::Func::getOopsUrl( $web, $topic, 'oopssaveerr', $error );
 
469
        }
 
470
        Foswiki::Func::redirectCgiQuery( $query, $url );
 
471
        return;
 
472
    }
 
473
 
 
474
    # END SAVE
 
475
    # ========================================
 
476
 
 
477
    # update the text
 
478
    $_[2] = $topicText;
 
479
    
 
480
    _debug("After parsing, topic text=$_[2]");
 
481
}
 
482
 
 
483
=begin TML
 
484
 
 
485
NOT FULLY IMPLEMENTED YET
 
486
 
 
487
Change table by means of parameters:
 
488
   1 Adding rows:
 
489
      * param etaddrows_position
 
490
      * param etaddrows_count
 
491
   1 Deleting rows
 
492
      * param etdeleterows_position
 
493
      * param etdeleterows_count
 
494
   
 
495
TODO:
 
496
   * addRows: existing rows need to shift down
 
497
   * deleteRows: check limit start and end of table
 
498
   * create unit test 
 
499
   * write documentation
 
500
        
 
501
=cut
 
502
 
 
503
sub handleTableChangeParams {
 
504
    my ( $inMode, $inEditTableData, $inTableStats, $inTableChanges, $inWeb,
 
505
        $inTopic )
 
506
      = @_;
 
507
 
 
508
    return $inMode;    # until fully implemented
 
509
 
 
510
    # add rows
 
511
    {
 
512
        my $position = $query->param('etaddrows_position');
 
513
        my $count    = $query->param('etaddrows_count');
 
514
        if ( defined $position && defined $count ) {
 
515
            if ( !doEnableEdit( $inWeb, $inTopic, 1 ) ) {
 
516
                my $mode = $MODE->{EDITNOTALLOWED};
 
517
                return $mode;
 
518
            }
 
519
            addRows( $inTableStats, $inTableChanges, $position, $count );
 
520
        }
 
521
    }
 
522
 
 
523
    # delete rows
 
524
    {
 
525
        my $position = $query->param('etdeleterows_position');
 
526
        my $count    = $query->param('etdeleterows_count');
 
527
        if ( defined $position && defined $count ) {
 
528
            if ( !doEnableEdit( $inWeb, $inTopic, 1 ) ) {
 
529
                my $mode = $MODE->{EDITNOTALLOWED};
 
530
                return $mode;
 
531
            }
 
532
            deleteRows( $inTableStats, $inTableChanges, $position, $count );
 
533
        }
 
534
    }
 
535
}
 
536
 
 
537
=begin TML
 
538
 
 
539
StaticMethod handleButtonActions( $mode, $editTableData, $tableStats, $tableChanges, $web, $topic ) -> $mode
 
540
 
 
541
Handles button interaction; for each state updates the $mode to a value of $MODE.
 
542
 
 
543
=cut
 
544
 
 
545
sub handleButtonActions {
 
546
    my ( $inMode, $inEditTableData, $inTableStats, $inTableChanges, $inWeb,
 
547
        $inTopic )
 
548
      = @_;
 
549
 
 
550
    my $mode = $inMode;
 
551
 
 
552
    if ( $query->param('etcancel') ) {
 
553
 
 
554
        # [Cancel] button pressed
 
555
        doCancelEdit( $inWeb, $inTopic );
 
556
        $mode = $MODE->{CANCEL};
 
557
        return $mode;
 
558
    }
 
559
 
 
560
    # else
 
561
    if ( !doEnableEdit( $inWeb, $inTopic, 1 ) ) {
 
562
        $mode = $MODE->{EDITNOTALLOWED};
 
563
        return $mode;
 
564
    }
 
565
 
 
566
    # else
 
567
    if ( $query->param('etsave') ) {
 
568
 
 
569
        # [Save table] button pressed
 
570
        $mode = $MODE->{SAVE};
 
571
    }
 
572
    elsif ( $query->param('etqsave') ) {
 
573
 
 
574
        # [Quiet save] button pressed
 
575
        $mode = $MODE->{SAVE} | $MODE->{SAVEQUIET};
 
576
    }
 
577
    elsif ( $query->param('etaddrow') ) {
 
578
 
 
579
        # [Add row] button pressed
 
580
        my $rowNum =
 
581
          $inTableStats->{rowCount} - $inTableStats->{footerRowCount} + 1;
 
582
        addRows( $inTableStats, $inTableChanges, $rowNum, 1 );
 
583
    }
 
584
    elsif ( $query->param('etdelrow') ) {
 
585
 
 
586
        # [Delete row] button pressed
 
587
        my $rowNum =
 
588
          $inTableStats->{rowCount} - $inTableStats->{footerRowCount};
 
589
        deleteRows( $inTableStats, $inTableChanges, $rowNum, 1 );
 
590
    }
 
591
    elsif ( $query->param('etedit') ) {
 
592
 
 
593
        # [Edit table] button pressed
 
594
        # just continue
 
595
    }
 
596
 
 
597
    return $mode;
 
598
}
 
599
 
 
600
=begin TML
 
601
 
 
602
StaticMethod addRows( $tableStats, $tableChanges, $position, $count )
 
603
 
 
604
Adds one or more rows.
 
605
 
 
606
=cut
 
607
 
 
608
sub addRows {
 
609
    my ( $inTableStats, $inTableChanges, $inPosition, $inCount ) = @_;
 
610
 
 
611
    my $row = $inPosition;
 
612
 
 
613
_debug("ADD ROW:$row");
 
614
 
 
615
    while ( $inCount-- ) {
 
616
 
 
617
# if we have set a row to 'delete' before, reset it to 2 first; otherwise set an existing row to 'add'
 
618
        $inTableChanges->{$row} =
 
619
          ( defined $inTableChanges->{$row} && $inTableChanges->{$row} == -1 )
 
620
          ? 2
 
621
          : 1;
 
622
        $row++;
 
623
    }
 
624
}
 
625
 
 
626
=begin TML
 
627
 
 
628
StaticMethod deleteRows( $tableStats, $tableChanges, $position, $count )
 
629
 
 
630
Deletes one or more rows.
 
631
 
 
632
=cut
 
633
 
 
634
sub deleteRows {
 
635
    my ( $inTableStats, $inTableChanges, $inPosition, $inCount ) = @_;
 
636
 
 
637
    my $row = $inPosition;
 
638
 
 
639
    # run backwards in rows to see which row has not been deleted yet
 
640
    while ($row) {
 
641
        last
 
642
          if ( !defined $inTableChanges->{$row}
 
643
            || $inTableChanges->{$row} != -1 );
 
644
        $row--;
 
645
    }
 
646
 
 
647
    while ( $inCount-- ) {
 
648
 
 
649
# if we have set a row to 'add' before, reset it to 0 first; otherwise set an existing row to 'delete'
 
650
        $inTableChanges->{$row} =
 
651
          ( defined $inTableChanges->{$row} && $inTableChanges->{$row} == 1 )
 
652
          ? 0
 
653
          : -1;
 
654
        $row--;
 
655
    }
 
656
}
 
657
 
 
658
=begin TML
 
659
 
 
660
StaticMethod processTableData( $tableNr, $editTableData, $tableChanges, $doEdit, $doSave, $web, $topic ) -> (\@processedText, \%tableChanges)
 
661
 
 
662
=cut
 
663
 
 
664
sub processTableData {
 
665
    my ( $inTableNr, $inEditTableData, $inTableChanges, $inDoEdit, $inDoSave,
 
666
        $inWeb, $inTopic )
 
667
      = @_;
 
668
 
 
669
    my @rows         = ();
 
670
    my $tableChanges = $inTableChanges;
 
671
    my @result       = ();
 
672
    my $tableStats   = $inEditTableData->getTableStatistics($tableChanges);
 
673
 
 
674
    _debug( "EditTablePlugin::processTableData - inEditTableData at start="
 
675
          . Dumper($inEditTableData) );
 
676
    _debug( "EditTablePlugin::processTableData - tableStats at start="
 
677
          . Dumper($tableStats) );
 
678
    _debug( "EditTablePlugin::processTableData - tableChanges at start="
 
679
          . Dumper($tableChanges) );
 
680
 
 
681
    my @headerRows = ();
 
682
    my @footerRows = ();
 
683
    my @bodyRows   = ();
 
684
 
 
685
    # ========================================
 
686
    # START GET THE ROW TYPE
 
687
 
 
688
    my $ROW_TYPE = {
 
689
        NONE   => ( 1 << 0 ),
 
690
        HEADER => ( 1 << 1 ),
 
691
        FOOTER => ( 1 << 2 ),
 
692
        BODY   => ( 1 << 3 ),
 
693
    };
 
694
    my $bodyRowCount =
 
695
      $inEditTableData->{'rowCount'} -
 
696
      $inEditTableData->{'footerRowCount'} -
 
697
      $inEditTableData->{'headerRowCount'};
 
698
    $bodyRowCount = 0 if $bodyRowCount < 0;
 
699
 
 
700
    # map 'real' rows to types
 
701
    # at this point we still use the order as used in the topic
 
702
    # so the footer will be in the last rows
 
703
    # we are not yet using the updated state of $tableStats
 
704
 
 
705
    my @headerRowNums = ();
 
706
    my @footerRowNums = ();
 
707
    my @bodyRowNums   = ();
 
708
    my $rowNr         = 0;
 
709
 
 
710
    for ( @{ $inEditTableData->{'lines'} } ) {
 
711
 
 
712
        $rowNr++;
 
713
        $tableChanges->{$rowNr} ||= 0;
 
714
 
 
715
        if ( $rowNr <= $inEditTableData->{'headerRowCount'} ) {
 
716
 
 
717
            push @headerRowNums, $rowNr if ( $tableChanges->{$rowNr} != -1 );
 
718
        }
 
719
        elsif ( ( $rowNr > $inEditTableData->{'headerRowCount'} )
 
720
            && (
 
721
                $rowNr <= $inEditTableData->{'headerRowCount'} + $bodyRowCount )
 
722
          )
 
723
        {
 
724
            push @bodyRowNums, $rowNr if ( $tableChanges->{$rowNr} != -1 );
 
725
        }
 
726
        else {
 
727
            push @footerRowNums, $rowNr;
 
728
        }
 
729
    }
 
730
 
 
731
    # END GET THE ROW TYPE
 
732
    # ========================================
 
733
 
 
734
    # ========================================
 
735
    # START RENDER CURRENT ROWS
 
736
 
 
737
    foreach my $rowNr (@headerRowNums) {
 
738
        next
 
739
          if ( $inTableChanges
 
740
            && defined $inTableChanges->{$rowNr}
 
741
            && $inTableChanges->{$rowNr} == -1 );
 
742
        local $_ = $inEditTableData->{'lines'}->[ $rowNr - 1 ];
 
743
 
 
744
        my $rowId    = $rowNr;
 
745
        my $isNewRow = 0;
 
746
 
 
747
                # a header row will not be edited, so do not render table row with handleTableRow
 
748
 
 
749
                _handleSpreadsheetFormula($_);
 
750
        _debug("RENDER ROW: HEADER:rowNr=$rowNr; id=$rowId=$_");
 
751
        push( @headerRows, $_ );
 
752
    }
 
753
 
 
754
    foreach my $rowNr (@footerRowNums) {
 
755
        next
 
756
          if ( $inTableChanges
 
757
            && defined $inTableChanges->{$rowNr}
 
758
            && $inTableChanges->{$rowNr} == -1 );
 
759
        local $_ = $inEditTableData->{'lines'}->[ $rowNr - 1 ];
 
760
 
 
761
        my $rowId    = $rowNr;
 
762
        my $isNewRow = 0;
 
763
        
 
764
        # a footer row will not be edited, so do not render table row with handleTableRow
 
765
                
 
766
                _handleSpreadsheetFormula($_);
 
767
        _debug("RENDER ROW: FOOTER:rowNr=$rowNr; id=$rowId=$_");
 
768
        push( @footerRows, $_ );
 
769
    }
 
770
 
 
771
    foreach my $rowNr (@bodyRowNums) {
 
772
        next
 
773
          if ( $inTableChanges
 
774
            && defined $inTableChanges->{$rowNr}
 
775
            && $inTableChanges->{$rowNr} == -1 );
 
776
        local $_ = $inEditTableData->{'lines'}->[ $rowNr - 1 ];
 
777
 
 
778
        my $rowId    = @headerRows + scalar @bodyRows + 1;
 
779
        my $isNewRow = 0;
 
780
        if ( $tableChanges->{$rowId} && $tableChanges->{$rowId} == 2 ) {
 
781
            $isNewRow = 1;
 
782
            $tableChanges->{$rowId} = 0;
 
783
        }
 
784
 
 
785
s/$PATTERN_TABLE_ROW/handleTableRow( $1, $2, $inTableNr, $isNewRow, $rowId, $inDoEdit, $inDoSave, $inWeb, $inTopic )/eo;
 
786
 
 
787
        _debug("RENDER ROW: BODY:rowNr=$rowNr; id=$rowId=$_");
 
788
        push( @bodyRows, $_ );
 
789
    }
 
790
 
 
791
    # END RENDER CURRENT ROWS
 
792
    # ========================================
 
793
 
 
794
    # ========================================
 
795
    # START ADDING ROWS
 
796
 
 
797
    if ( !$query->param('etdelrow') ) {
 
798
 
 
799
        # START ADDING HEADER ROWS
 
800
 
 
801
        # add a header row if there is none,
 
802
        # but only if there are no body rows yet
 
803
        # (and not when a row has just been deleted)
 
804
 
 
805
        if ( !( scalar @bodyRows ) ) {
 
806
 
 
807
            my $headerRows = $query->param('etheaderrows') || 0;
 
808
            if ( $headerRows > scalar @headerRows ) {
 
809
                my $rowNr        = scalar @headerRows;
 
810
                my $newHeaderRow = $headerRows - @headerRows;
 
811
                while ( $newHeaderRow-- ) {
 
812
 
 
813
                    my $rowId =
 
814
                      scalar @headerRows +
 
815
                      scalar @footerRows +
 
816
                      scalar @bodyRows + 1;
 
817
                    next
 
818
                      if ( $tableChanges->{$rowId} == -1
 
819
                        || $tableChanges->{$rowId} == 2 );
 
820
 
 
821
                    $tableChanges->{$rowId} = 1;    # store the new row
 
822
                    my $isNewRow = 1;
 
823
                    my $newRow   = handleTableRow(
 
824
                        '',     '',        $inTableNr, $isNewRow,
 
825
                        $rowId, $inDoEdit, $inDoSave,  $inWeb,
 
826
                        $inTopic
 
827
                    );
 
828
                    push @headerRows, $newRow;
 
829
                }
 
830
            }
 
831
 
 
832
           # to start with table editing right away, add a minimum of 1 body row
 
833
            if ( !$tableStats->{bodyRowCount} ) {
 
834
 
 
835
                # put row at bottom
 
836
                my $rowId =
 
837
                  scalar @headerRows +
 
838
                  scalar @footerRows +
 
839
                  scalar @bodyRows + 1;
 
840
                $tableChanges->{$rowId} ||= 0;
 
841
                if (
 
842
                    !(
 
843
                           $tableChanges->{$rowId} == -1
 
844
                        || $tableChanges->{$rowId} == 2
 
845
                    )
 
846
                  )
 
847
                {
 
848
                    $tableChanges->{$rowId} = 1;    # store the new row
 
849
                    my $isNewRow = 0;
 
850
                    my $newRow   = handleTableRow(
 
851
                        '',     '',        $inTableNr, $isNewRow,
 
852
                        $rowId, $inDoEdit, $inDoSave,  $inWeb,
 
853
                        $inTopic
 
854
                    );
 
855
                    push @bodyRows, $newRow;
 
856
                }
 
857
            }
 
858
 
 
859
            # update table stats
 
860
            $tableStats = $inEditTableData->getTableStatistics($tableChanges);
 
861
        }
 
862
 
 
863
        # END ADDING HEADER ROWS
 
864
 
 
865
        # START ADDING BODY ROWS
 
866
 
 
867
        my $bodyRows = $tableStats->{bodyRowCount};
 
868
 
 
869
        if ( $bodyRows > scalar @bodyRows ) {
 
870
        
 
871
            my $rowNr =
 
872
              scalar @headerRows + scalar @footerRows + scalar @bodyRows;
 
873
            my $newBodyRow = $bodyRows - @bodyRows;
 
874
            
 
875
        _debug("ADD ROW: BODY: number=$newBodyRow");
 
876
        
 
877
            while ( $newBodyRow-- ) {
 
878
                $rowNr++;
 
879
 
 
880
                next
 
881
                  if (
 
882
                    (
 
883
                        defined $inTableChanges->{$rowNr}
 
884
                        && $inTableChanges->{$rowNr} == -1
 
885
                    )
 
886
                    || ( defined $inTableChanges->{$rowNr}
 
887
                        && $inTableChanges->{$rowNr} == 2 )
 
888
                  );
 
889
 
 
890
                my $rowId =
 
891
                  scalar @headerRows +
 
892
                  scalar @bodyRows + 1;
 
893
 
 
894
_debug("ADD ROW: BODY: rowId=$rowId");
 
895
 
 
896
                my $isNewRow = 0
 
897
                  ; # otherwise values entered in new rows are not preserved when adding yet more rows
 
898
 
 
899
                my $newRow = handleTableRow(
 
900
                    '',     '',        $inTableNr, $isNewRow,
 
901
                    $rowId, $inDoEdit, $inDoSave,  $inWeb,
 
902
                    $inTopic
 
903
                );
 
904
                
 
905
_debug("ADD ROW: BODY: newRow=$newRow");
 
906
 
 
907
 
 
908
                push @bodyRows, $newRow;
 
909
            }
 
910
 
 
911
            # update table stats
 
912
            $tableStats = $inEditTableData->getTableStatistics($tableChanges);
 
913
        }
 
914
 
 
915
        # END ADDING BODY ROWS
 
916
 
 
917
        # END ADDING ROWS
 
918
        # ========================================
 
919
    }
 
920
 
 
921
    _debug( "EditTablePlugin::processTableData - tableChanges at end="
 
922
          . Dumper($tableChanges) );
 
923
    _debug( "EditTablePlugin::processTableData - tableStats at end="
 
924
          . Dumper($tableStats) );
 
925
 
 
926
    _debug(
 
927
"EditTablePlugin::processTableData - headerRows=\n---------------------\n"
 
928
          . Dumper(@headerRows) );
 
929
    _debug(
 
930
        "EditTablePlugin::processTableData - bodyRows=\n---------------------\n"
 
931
          . Dumper(@bodyRows) );
 
932
    _debug(
 
933
"EditTablePlugin::processTableData - footerRows=\n---------------------\n"
 
934
          . Dumper(@footerRows) );
 
935
 
 
936
    my @combinedRows = ( @headerRows, @bodyRows, @footerRows );
 
937
 
 
938
    _debug(
 
939
"EditTablePlugin::processTableData - combinedRows=\n---------------------\n"
 
940
          . Dumper(@combinedRows) );
 
941
     
 
942
    push( @result, @combinedRows );
 
943
 
 
944
    @result = map { $_ .= "\n" } @result;
 
945
 
 
946
    return ( \@result, $tableChanges );
 
947
}
 
948
 
 
949
=begin TML
 
950
 
 
951
StaticMethod getPreferencesValues()
 
952
 
 
953
Read preferences from plugin topic of preferences.
 
954
 
 
955
=cut
 
956
 
 
957
sub getPreferencesValues {
 
958
 
 
959
    my $pluginName = $Foswiki::Plugins::EditTablePlugin::pluginName;
 
960
 
 
961
    $prefCHANGEROWS =
 
962
      Foswiki::Func::getPreferencesValue("\U$pluginName\E_CHANGEROWS") || 'on';
 
963
 
 
964
    $prefQUIETSAVE =
 
965
      Foswiki::Func::getPreferencesValue("\U$pluginName\E_QUIETSAVE") || 'on';
 
966
 
 
967
    $prefEDIT_BUTTON =
 
968
      Foswiki::Func::getPreferencesValue("\U$pluginName\E_EDIT_BUTTON")
 
969
      || '%MAKETEXT{"Edit this table"}%, %ATTACHURL%/edittable.gif';
 
970
 
 
971
    $prefSAVE_BUTTON =
 
972
      Foswiki::Func::getPreferencesValue("\U$pluginName\E_SAVE_BUTTON")
 
973
      || '%MAKETEXT{"Save table"}%';
 
974
 
 
975
    $prefQUIET_SAVE_BUTTON =
 
976
      Foswiki::Func::getPreferencesValue("\U$pluginName\E_QUIET_SAVE_BUTTON")
 
977
      || '%MAKETEXT{"Quiet save"}%';
 
978
 
 
979
    $prefADD_ROW_BUTTON =
 
980
      Foswiki::Func::getPreferencesValue("\U$pluginName\E_ADD_ROW_BUTTON")
 
981
      || '%MAKETEXT{"Add row"}%';
 
982
 
 
983
    $prefDELETE_LAST_ROW_BUTTON = Foswiki::Func::getPreferencesValue(
 
984
        "\U$pluginName\E_DELETE_LAST_ROW_BUTTON")
 
985
      || '%MAKETEXT{"Delete last row"}%';
 
986
 
 
987
    $prefCANCEL_BUTTON =
 
988
      Foswiki::Func::getPreferencesValue("\U$pluginName\E_CANCEL_BUTTON")
 
989
      || '%MAKETEXT{"Cancel"}%';
 
990
 
 
991
    $prefMESSAGE_INCLUDED_TOPIC_DOES_NOT_EXIST =
 
992
      Foswiki::Func::getPreferencesValue(
 
993
        "\U$pluginName\E_INCLUDED_TOPIC_DOES_NOT_EXIST")
 
994
      || '<span class="foswikiAlert">%MAKETEXT{"Warning: \'include\' topic does not exist!"}%</span>';
 
995
}
 
996
 
 
997
=begin TML
 
998
 
 
999
StaticMethod extractParams( $arguments, \%params ) 
 
1000
 
 
1001
=cut
 
1002
 
 
1003
sub extractParams {
 
1004
    my ( $inArguments, $inParams ) = @_;
 
1005
 
 
1006
    my $tmp;
 
1007
 
 
1008
    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'header' );
 
1009
    $$inParams{'header'} = $tmp if ($tmp);
 
1010
 
 
1011
    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'footer' );
 
1012
    $$inParams{'footer'} = $tmp if ($tmp);
 
1013
 
 
1014
    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'headerislabel' );
 
1015
    $$inParams{'headerislabel'} = $tmp if ($tmp);
 
1016
 
 
1017
    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'format' );
 
1018
    $tmp =~ s/^\s*\|*\s*//o;
 
1019
    $tmp =~ s/\s*\|*\s*$//o;
 
1020
    $$inParams{'format'} = $tmp if ($tmp);
 
1021
 
 
1022
    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'changerows' );
 
1023
    $$inParams{'changerows'} = $tmp if ($tmp);
 
1024
 
 
1025
    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'quietsave' );
 
1026
    $$inParams{'quietsave'} = $tmp if ($tmp);
 
1027
 
 
1028
    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'helptopic' );
 
1029
    $$inParams{'helptopic'} = $tmp if ($tmp);
 
1030
 
 
1031
    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'editbutton' );
 
1032
    $$inParams{'editbutton'} = $tmp if ($tmp);
 
1033
 
 
1034
    $tmp = Foswiki::Func::extractNameValuePair( $inArguments,
 
1035
        'javascriptinterface' );
 
1036
    $$inParams{'javascriptinterface'} = $tmp if ($tmp);
 
1037
 
 
1038
    $tmp = Foswiki::Func::extractNameValuePair( $inArguments, 'buttonrow' );
 
1039
    $$inParams{'buttonrow'} = $tmp if ($tmp);
 
1040
}
 
1041
 
 
1042
=begin TML
 
1043
 
 
1044
=cut
 
1045
 
 
1046
sub parseFormat {
 
1047
    my ( $theFormat, $inTopic, $inWeb, $doExpand ) = @_;
 
1048
 
 
1049
    $theFormat =~ s/\$nop(\(\))?//gos;         # remove filler
 
1050
    $theFormat =~ s/\$quot(\(\))?/\"/gos;      # expand double quote
 
1051
    $theFormat =~ s/\$percnt(\(\))?/\%/gos;    # expand percent
 
1052
    $theFormat =~ s/\$dollar(\(\))?/\$/gos;    # expand dollar
 
1053
 
 
1054
    if ($doExpand) {
 
1055
 
 
1056
        # expanded form to be able to use %-vars in format
 
1057
        $theFormat =~ s/<nop>//gos;
 
1058
        $theFormat =
 
1059
          Foswiki::Func::expandCommonVariables( $theFormat, $inTopic, $inWeb );
 
1060
    }
 
1061
 
 
1062
    my @aFormat = split( /\s*\|\s*/, $theFormat );
 
1063
    $aFormat[0] = "text,$DEFAULT_FIELD_SIZE" unless @aFormat;
 
1064
 
 
1065
    return @aFormat;
 
1066
}
 
1067
 
 
1068
=begin TML
 
1069
 
 
1070
=cut
 
1071
 
 
1072
sub handleEditTableTag {
 
1073
    my ( $inWeb, $inTopic, $inArguments ) = @_;
 
1074
 
 
1075
    %params = (
 
1076
        'header'              => '',
 
1077
        'footer'              => '',
 
1078
        'headerislabel'       => "1",
 
1079
        'format'              => '',
 
1080
        'changerows'          => $prefCHANGEROWS,
 
1081
        'quietsave'           => $prefQUIETSAVE,
 
1082
        'helptopic'           => '',
 
1083
        'editbutton'          => '',
 
1084
        'javascriptinterface' => '',
 
1085
        'buttonrow'           => '',
 
1086
    );
 
1087
    $warningMessage = '';
 
1088
 
 
1089
    # include topic to read definitions
 
1090
    my $iTopic = Foswiki::Func::extractNameValuePair( $inArguments, 'include' );
 
1091
    if ($iTopic) {
 
1092
        ( $inWeb, $iTopic ) =
 
1093
          Foswiki::Func::normalizeWebTopicName( $inWeb, $iTopic );
 
1094
 
 
1095
        unless ( Foswiki::Func::topicExists( $inWeb, $iTopic ) ) {
 
1096
            $warningMessage = $prefMESSAGE_INCLUDED_TOPIC_DOES_NOT_EXIST;
 
1097
        }
 
1098
        else {
 
1099
 
 
1100
            my $text = Foswiki::Func::readTopicText( $inWeb, $iTopic );
 
1101
            $text =~ /$PATTERN_EDITTABLEPLUGIN/os;
 
1102
            if ($2) {
 
1103
                my $args = $2;
 
1104
                if (   $inWeb ne $Foswiki::Plugins::EditTablePlugin::web
 
1105
                    || $iTopic ne $Foswiki::Plugins::EditTablePlugin::topic )
 
1106
                {
 
1107
 
 
1108
                    # expand common vars, unless oneself to prevent recursion
 
1109
                    $args =
 
1110
                      Foswiki::Func::expandCommonVariables( $args, $iTopic,
 
1111
                        $inWeb );
 
1112
                }
 
1113
                extractParams( $args, \%params );
 
1114
            }
 
1115
        }
 
1116
    }
 
1117
 
 
1118
    # We allow expansion of macros in the EDITTABLE arguments so one can
 
1119
    # set a macro that defines the arguments
 
1120
    my $arguments =
 
1121
      Foswiki::Func::expandCommonVariables( $inArguments, $inTopic, $inWeb );
 
1122
 
 
1123
    extractParams( $arguments, \%params );
 
1124
 
 
1125
    # FIXME: should use Foswiki::Func::extractParameters
 
1126
    $params{'header'} = '' if ( $params{header} =~ /^(off|no)$/oi );
 
1127
    $params{'header'} =~ s/^\s*\|//o;
 
1128
    $params{'header'} =~ s/\|\s*$//o;
 
1129
    $params{'headerislabel'} = ''
 
1130
      if ( $params{headerislabel} =~ /^(off|no)$/oi );
 
1131
    $params{'footer'} = '' if ( $params{footer} =~ /^(off|no)$/oi );
 
1132
    $params{'footer'} =~ s/^\s*\|//o;
 
1133
    $params{'footer'} =~ s/\|\s*$//o;
 
1134
 
 
1135
    $params{'changerows'} = '' if ( $params{changerows} =~ /^(off|no)$/oi );
 
1136
    $params{'quietsave'}  = '' if ( $params{quietsave}  =~ /^(off|no)$/oi );
 
1137
    $params{'javascriptinterface'} = 'off'
 
1138
      if ( $params{javascriptinterface} =~ /^(off|no)$/oi );
 
1139
 
 
1140
    @format         = parseFormat( $params{format}, $inTopic, $inWeb, 0 );
 
1141
    @formatExpanded = parseFormat( $params{format}, $inTopic, $inWeb, 1 );
 
1142
    $nrCols         = scalar @format;
 
1143
}
 
1144
 
 
1145
=begin TML
 
1146
 
 
1147
Creates the HTML for the start of the table.
 
1148
 
 
1149
=cut
 
1150
 
 
1151
sub handleTableStart {
 
1152
    my ( $inWeb, $inTopic, $inIncludingWeb, $inIncludingTopic, $theTableNr,
 
1153
        $doEdit )
 
1154
      = @_;
 
1155
 
 
1156
    if ($doEdit) {
 
1157
        require Foswiki::Contrib::JSCalendarContrib;
 
1158
        unless ($@) {
 
1159
            Foswiki::Contrib::JSCalendarContrib::addHEAD('foswiki');
 
1160
        }
 
1161
    }
 
1162
 
 
1163
    my $viewUrl = Foswiki::Func::getScriptUrl( $inWeb, $inTopic, 'viewauth' )
 
1164
      . "\#edittable$theTableNr";
 
1165
 
 
1166
    my $text = '';
 
1167
    $text .= "$preSp<noautolink>\n" if $doEdit;
 
1168
    $text .= "$preSp<a name=\"edittable$theTableNr\"></a>\n"
 
1169
      if ( "$inWeb.$inTopic" eq "$inIncludingWeb.$inIncludingTopic" );
 
1170
    my $cssClass = 'editTable';
 
1171
    if ($doEdit) {
 
1172
        $cssClass .= ' editTableEdit';
 
1173
    }
 
1174
    $text .= "<div class=\"" . $cssClass . "\">\n";
 
1175
    my $formName = "edittable$theTableNr";
 
1176
    $formName .= "\_$inIncludingWeb\_$inIncludingTopic"
 
1177
      if ( "$inWeb\_$inTopic" ne "$inIncludingWeb\_$inIncludingTopic" );
 
1178
    $text .=
 
1179
      "$preSp<form name=\"$formName\" action=\"$viewUrl\" method=\"post\">\n";
 
1180
 
 
1181
    $text .= $PLACEHOLDER_BUTTONROW_TOP;
 
1182
 
 
1183
    $text .= hiddenField( $preSp, 'ettablenr', $theTableNr, "\n" );
 
1184
    $text .= hiddenField( $preSp, 'etedit',    'on',        "\n" );
 
1185
 
 
1186
    return $text;
 
1187
}
 
1188
 
 
1189
=begin TML
 
1190
 
 
1191
=cut
 
1192
 
 
1193
sub handleTableEnd {
 
1194
    my ( $inDoEdit, $inTableChanges, $inHeaderRowCount, $inFooterRowCount ) =
 
1195
      @_;
 
1196
 
 
1197
    my $text = '';
 
1198
 
 
1199
    $text .= hiddenField(
 
1200
        $preSp,
 
1201
        'ettablechanges',
 
1202
        Foswiki::Plugins::EditTablePlugin::EditTableData::tableChangesMapToParamString(
 
1203
            $inTableChanges),
 
1204
        "\n"
 
1205
    );
 
1206
    $text .= hiddenField( $preSp, 'etheaderrows', $inHeaderRowCount, "\n" );
 
1207
    $text .= hiddenField( $preSp, 'etfooterrows', $inFooterRowCount, "\n" );
 
1208
 
 
1209
    $text .= $PLACEHOLDER_BUTTONROW_BOTTOM;
 
1210
 
 
1211
    $text .= "$preSp</form>\n";
 
1212
    $text .= "</div><!-- /editTable -->";
 
1213
    $text .= "</noautolink>" if $inDoEdit;
 
1214
 
 
1215
    return $text;
 
1216
}
 
1217
 
 
1218
=begin TML
 
1219
 
 
1220
=cut
 
1221
 
 
1222
sub hiddenField {
 
1223
    my ( $inPrefix, $inName, $inValue, $inSuffix ) = @_;
 
1224
 
 
1225
    my $prefix = defined $inPrefix ? $inPrefix : '';
 
1226
    my $suffix = defined $inSuffix ? $inSuffix : '';
 
1227
 
 
1228
    # Somehow this does not work at all:
 
1229
    # return $prefix
 
1230
    #   . CGI::hidden(
 
1231
    #     -name  => $name,
 
1232
    #     -value => $value
 
1233
    #   ) . $suffix;
 
1234
 
 
1235
    return
 
1236
"$prefix<input type=\"hidden\" name=\"$inName\" value=\"$inValue\" />$suffix";
 
1237
}
 
1238
 
 
1239
=begin TML
 
1240
 
 
1241
=cut
 
1242
 
 
1243
sub createButtonRow {
 
1244
    my ( $inWeb, $inTopic, $inIncludingWeb, $inIncludingTopic, $doEdit ) = @_;
 
1245
 
 
1246
    my $text = '';
 
1247
    if ( $doEdit
 
1248
        && ( "$inWeb.$inTopic" eq "$inIncludingWeb.$inIncludingTopic" ) )
 
1249
    {
 
1250
 
 
1251
        # Edit mode
 
1252
        $text .=
 
1253
"$preSp<input type=\"submit\" name=\"etsave\" id=\"etsave\" value=\"$prefSAVE_BUTTON\" class=\"foswikiSubmit\" />\n";
 
1254
        if ( $params{'quietsave'} ) {
 
1255
            $text .=
 
1256
"$preSp<input type=\"submit\" name=\"etqsave\" id=\"etqsave\" value=\"$prefQUIET_SAVE_BUTTON\" class=\"foswikiButton\" />\n";
 
1257
        }
 
1258
        if ( $params{'changerows'} ) {
 
1259
            $text .=
 
1260
"$preSp<input type=\"submit\" name=\"etaddrow\" id=\"etaddrow\" value=\"$prefADD_ROW_BUTTON\" class=\"foswikiButton\" />\n";
 
1261
            $text .=
 
1262
"$preSp<input type=\"submit\" name=\"etdelrow\" id=\"etdelrow\" value=\"$prefDELETE_LAST_ROW_BUTTON\" class=\"foswikiButton\" />\n"
 
1263
              unless ( $params{'changerows'} =~ /^add$/oi );
 
1264
        }
 
1265
        $text .=
 
1266
"$preSp<input type=\"submit\" name=\"etcancel\" id=\"etcancel\" value=\"$prefCANCEL_BUTTON\" class=\"foswikiButtonCancel\" />\n";
 
1267
 
 
1268
        if ( $params{'helptopic'} ) {
 
1269
 
 
1270
            # read help topic and show below the table
 
1271
            if ( $params{'helptopic'} =~ /^([^\.]+)\.(.*)$/o ) {
 
1272
                $inWeb = $1;
 
1273
                $params{'helptopic'} = $2;
 
1274
            }
 
1275
            my $helpText =
 
1276
              Foswiki::Func::readTopicText( $inWeb, $params{'helptopic'} );
 
1277
 
 
1278
            #Strip out the meta data so it won't be displayed.
 
1279
            $helpText =~ s/%META:[A-Za-z0-9]+{.*?}%//g;
 
1280
            if ($helpText) {
 
1281
                $helpText =~ s/.*?%STARTINCLUDE%//os;
 
1282
                $helpText =~ s/%STOPINCLUDE%.*//os;
 
1283
                $text .= $helpText;
 
1284
            }
 
1285
        }
 
1286
 
 
1287
        # table specific script
 
1288
        my $tableNr = $query->param('ettablenr');
 
1289
        &Foswiki::Plugins::EditTablePlugin::addEditModeHeadersToHead( $tableNr,
 
1290
            $params{'javascriptinterface'} );
 
1291
        &Foswiki::Plugins::EditTablePlugin::addJavaScriptInterfaceDisabledToHead
 
1292
          ($tableNr)
 
1293
          if ( $params{'javascriptinterface'} eq 'off' );
 
1294
        &Foswiki::Plugins::EditTablePlugin::addJavaScriptInterfaceDisabledToHead
 
1295
          ($tableNr)
 
1296
          if ( $params{'changerows'} eq '' );
 
1297
    }
 
1298
    else {
 
1299
        $params{editbutton} |= '';
 
1300
 
 
1301
        # View mode
 
1302
        if ( $params{editbutton} eq "hide" ) {
 
1303
 
 
1304
            # do nothing, button assumed to be in a cell
 
1305
        }
 
1306
        else {
 
1307
 
 
1308
            # Add edit button to end of table
 
1309
            $text .=
 
1310
              $preSp . viewEditCell("editbutton, 1, $params{'editbutton'}");
 
1311
        }
 
1312
    }
 
1313
    return $text;
 
1314
}
 
1315
 
 
1316
=begin TML
 
1317
 
 
1318
=cut
 
1319
 
 
1320
sub parseEditCellFormat {
 
1321
    $_[1] = Foswiki::Func::extractNameValuePair( $_[0] );
 
1322
    return '';
 
1323
}
 
1324
 
 
1325
=begin TML
 
1326
 
 
1327
=cut
 
1328
 
 
1329
sub viewEditCell {
 
1330
    my ($inAttr) = @_;
 
1331
 
 
1332
    my $attributes = Foswiki::Func::extractNameValuePair($inAttr);
 
1333
    return '' unless ( $attributes =~ /^editbutton/ );
 
1334
 
 
1335
    $params{editbutton} = 'hide'
 
1336
      unless ( $params{editbutton} );    # Hide below table edit button
 
1337
 
 
1338
    my @bits = split( /,\s*/, $attributes );
 
1339
    my $value = '';
 
1340
    $value = $bits[2] if ( @bits > 2 );
 
1341
    my $img = '';
 
1342
    $img = $bits[3] if ( @bits > 3 );
 
1343
 
 
1344
    unless ($value) {
 
1345
        $value = $prefEDIT_BUTTON || '';
 
1346
        $img = '';
 
1347
        if ( $value =~ s/(.+),\s*(.+)/$1/o ) {
 
1348
            $img = $2;
 
1349
            $img =~ s|%ATTACHURL%|%PUBURL%/%SYSTEMWEB%/EditTablePlugin|o;
 
1350
            $img =~ s|%WEB%|%SYSTEMWEB%|o;
 
1351
        }
 
1352
    }
 
1353
    if ($img) {
 
1354
        return
 
1355
"<input class=\"editTableEditImageButton\" type=\"image\" src=\"$img\" alt=\"$value\" /> $warningMessage";
 
1356
    }
 
1357
    else {
 
1358
        return
 
1359
"<input class=\"foswikiButton editTableEditButton\" type=\"submit\" value=\"$value\" /> $warningMessage";
 
1360
    }
 
1361
}
 
1362
 
 
1363
=begin TML
 
1364
 
 
1365
=cut
 
1366
 
 
1367
sub saveEditCellFormat {
 
1368
    my ( $theFormat, $theName ) = @_;
 
1369
 
 
1370
    return '' unless ($theFormat);
 
1371
    $theName =~ s/cell/format/;
 
1372
    return hiddenField( '', $theName, $theFormat, '' );
 
1373
}
 
1374
 
 
1375
=begin TML
 
1376
 
 
1377
digestedCellValue: properly handle labels whose result may have been moved around by javascript, and therefore no longer correspond to the raw saved table text.
 
1378
 
 
1379
=cut
 
1380
 
 
1381
sub inputElement {
 
1382
    my ( $inTableNr, $inRowNr, $inColumnNr, $inName, $inValue,
 
1383
        $inDigestedCellValue, $inWeb, $inTopic )
 
1384
      = @_;
 
1385
 
 
1386
    my $rawValue = $inValue;
 
1387
    my $text     = '';
 
1388
    my $i        = @format - 1;
 
1389
    $i = $inColumnNr if ( $inColumnNr < $i );
 
1390
 
 
1391
    my @bits         = split( /,\s*/, $format[$i] );
 
1392
    my @bitsExpanded = split( /,\s*/, $formatExpanded[$i] );
 
1393
 
 
1394
    my $cellFormat = '';
 
1395
    $inValue =~
 
1396
      s/\s*$PATTERN_EDITCELL/&parseEditCellFormat( $1, $cellFormat )/eo;
 
1397
 
 
1398
    # If cell is empty we remove the space to not annoy the user when
 
1399
    # he needs to add text to empty cell.
 
1400
    $inValue = '' if ( $inValue eq ' ' );
 
1401
 
 
1402
    if ($cellFormat) {
 
1403
        my @aFormat = parseFormat( $cellFormat, $inTopic, $inWeb, 0 );
 
1404
        @bits = split( /,\s*/, $aFormat[0] );
 
1405
        @aFormat = parseFormat( $cellFormat, $inTopic, $inWeb, 1 );
 
1406
        @bitsExpanded = split( /,\s*/, $aFormat[0] );
 
1407
    }
 
1408
 
 
1409
    my $type = 'text';
 
1410
    $type = $bits[0] if @bits > 0;
 
1411
 
 
1412
    # a table header is considered a label if read only header flag set
 
1413
    $type = 'label'
 
1414
      if ( ( $params{'headerislabel'} ) && ( $inValue =~ /^\s*\*.*\*\s*$/ ) );
 
1415
    $type = 'label' if ( $type eq 'editbutton' );    # Hide [Edit table] button
 
1416
    my $size = 0;
 
1417
    $size = $bits[1] if @bits > 1;
 
1418
 
 
1419
    my $val         = '';
 
1420
    my $valExpanded = '';
 
1421
    my $sel         = '';
 
1422
 
 
1423
    if ( $type eq 'select' ) {
 
1424
        my $expandedValue =
 
1425
          Foswiki::Func::expandCommonVariables( $inValue, $inTopic, $inWeb );
 
1426
        $size = 1 if $size < 1;
 
1427
        $text =
 
1428
          "<select class=\"foswikiSelect\" name=\"$inName\" size=\"$size\">";
 
1429
        $i = 2;
 
1430
        while ( $i < @bits ) {
 
1431
            $val         = $bits[$i]         || '';
 
1432
            $valExpanded = $bitsExpanded[$i] || '';
 
1433
            $expandedValue =~ s/^\s+//;
 
1434
            $expandedValue =~ s/\s+$//;
 
1435
            $valExpanded   =~ s/^\s+//;
 
1436
            $valExpanded   =~ s/\s+$//;
 
1437
 
 
1438
            if ( $valExpanded eq $expandedValue ) {
 
1439
                $text .= " <option selected=\"selected\">$val</option>";
 
1440
            }
 
1441
            else {
 
1442
                $text .= " <option>$val</option>";
 
1443
            }
 
1444
            $i++;
 
1445
        }
 
1446
        $text .= "</select>";
 
1447
        $text .= saveEditCellFormat( $cellFormat, $inName );
 
1448
 
 
1449
    }
 
1450
    elsif ( $type eq "radio" ) {
 
1451
        my $expandedValue =
 
1452
          &Foswiki::Func::expandCommonVariables( $inValue, $inTopic, $inWeb );
 
1453
        $size = 1 if $size < 1;
 
1454
        my $elements = ( @bits - 2 );
 
1455
        my $lines    = $elements / $size;
 
1456
        $lines = ( $lines == int($lines) ) ? $lines : int( $lines + 1 );
 
1457
        $text .= "<table class=\"editTableInnerTable\"><tr><td valign=\"top\">"
 
1458
          if ( $lines > 1 );
 
1459
        $i = 2;
 
1460
        while ( $i < @bits ) {
 
1461
            $val         = $bits[$i]         || "";
 
1462
            $valExpanded = $bitsExpanded[$i] || "";
 
1463
            $expandedValue =~ s/^\s+//;
 
1464
            $expandedValue =~ s/\s+$//;
 
1465
            $valExpanded   =~ s/^\s+//;
 
1466
            $valExpanded   =~ s/\s+$//;
 
1467
            $text .= "<input type=\"radio\" name=\"$inName\" value=\"$val\"";
 
1468
 
 
1469
            # make space to expand variables
 
1470
            $val = addSpaceToBothSides($val);
 
1471
            $text .= " checked=\"checked\""
 
1472
              if ( $valExpanded eq $expandedValue );
 
1473
            $text .= " />$val";
 
1474
            if ( $lines > 1 ) {
 
1475
 
 
1476
                if ( ( $i - 1 ) % $lines ) {
 
1477
                    $text .= "<br />";
 
1478
                }
 
1479
                elsif ( $i - 1 < $elements ) {
 
1480
                    $text .= "</td><td valign=\"top\">";
 
1481
                }
 
1482
            }
 
1483
            $i++;
 
1484
        }
 
1485
        $text .= "</td></tr></table>" if ( $lines > 1 );
 
1486
        $text .= saveEditCellFormat( $cellFormat, $inName );
 
1487
 
 
1488
    }
 
1489
    elsif ( $type eq "checkbox" ) {
 
1490
        my $expandedValue =
 
1491
          &Foswiki::Func::expandCommonVariables( $inValue, $inTopic, $inWeb );
 
1492
        $size = 1 if $size < 1;
 
1493
        my $elements = ( @bits - 2 );
 
1494
        my $lines    = $elements / $size;
 
1495
        my $names    = "Chkbx:";
 
1496
        $lines = ( $lines == int($lines) ) ? $lines : int( $lines + 1 );
 
1497
        $text .= "<table class=\"editTableInnerTable\"><tr><td valign=\"top\">"
 
1498
          if ( $lines > 1 );
 
1499
        $i = 2;
 
1500
 
 
1501
        while ( $i < @bits ) {
 
1502
            $val         = $bits[$i]         || "";
 
1503
            $valExpanded = $bitsExpanded[$i] || "";
 
1504
            $expandedValue =~ s/^\s+//;
 
1505
            $expandedValue =~ s/\s+$//;
 
1506
            $valExpanded   =~ s/^\s+//;
 
1507
            $valExpanded   =~ s/\s+$//;
 
1508
            $names .= " ${inName}x$i";
 
1509
            $text .=
 
1510
              " <input type=\"checkbox\" name=\"${inName}x$i\" value=\"$val\"";
 
1511
 
 
1512
            $val = addSpaceToBothSides($val);
 
1513
 
 
1514
            $text .= " checked=\"checked\""
 
1515
              if ( $expandedValue =~ /(^|\s*,\s*)\Q$valExpanded\E(\s*,\s*|$)/ );
 
1516
            $text .= " />$val";
 
1517
 
 
1518
            if ( $lines > 1 ) {
 
1519
                if ( ( $i - 1 ) % $lines ) {
 
1520
                    $text .= "<br />";
 
1521
                }
 
1522
                elsif ( $i - 1 < $elements ) {
 
1523
                    $text .= "</td><td valign=\"top\">";
 
1524
                }
 
1525
            }
 
1526
            $i++;
 
1527
        }
 
1528
        $text .= "</td></tr></table>" if ( $lines > 1 );
 
1529
        $text .= hiddenField( $preSp, $inName, $names );
 
1530
        $text .= saveEditCellFormat( $cellFormat, $inName, "\n" );
 
1531
 
 
1532
    }
 
1533
    elsif ( $type eq 'row' ) {
 
1534
        $size = $size + $inRowNr;
 
1535
        $text =
 
1536
            "<span class=\"et_rowlabel\">"
 
1537
          . hiddenField( $size, $inName, $size )
 
1538
          . "</span>";
 
1539
        $text .= saveEditCellFormat( $cellFormat, $inName );
 
1540
    }
 
1541
    elsif ( $type eq 'label' ) {
 
1542
 
 
1543
        # show label text as is, and add a hidden field with value
 
1544
        my $isHeader = 0;
 
1545
        $isHeader = 1 if ( $inValue =~ s/^\s*\*(.*)\*\s*$/$1/o );
 
1546
        $text = $inValue;
 
1547
 
 
1548
        # Replace CALC in labels with the fixed string CALC to avoid errors
 
1549
        # when editing.
 
1550
        _handleSpreadsheetFormula($text);
 
1551
 
 
1552
        # To optimize things, only in the case where a read-only column is
 
1553
        # being processed (inside of this unless() statement) do we actually
 
1554
        # go out and read the original topic.  Thus the reason for the
 
1555
        # following unless() so we only read the topic the first time through.
 
1556
 
 
1557
        unless ( defined $tableMatrix{$inWeb}{$inTopic}
 
1558
            and $inDigestedCellValue )
 
1559
        {
 
1560
 
 
1561
            # To deal with the situation where Foswiki variables, like
 
1562
            # %CALC%, have already been processed and end up getting saved
 
1563
            # in the table that way (processed), we need to read in the
 
1564
            # topic page in raw format
 
1565
            my $topicContents = Foswiki::Func::readTopicText(
 
1566
                $Foswiki::Plugins::EditTablePlugin::web,
 
1567
                $Foswiki::Plugins::EditTablePlugin::topic
 
1568
            );
 
1569
            parseTables( $topicContents, $inTopic, $inWeb );
 
1570
        }
 
1571
        my $table = $tableMatrix{$inWeb}{$inTopic};
 
1572
        my $cell =
 
1573
            $inDigestedCellValue
 
1574
          ? $table->getCell( $inTableNr, $inRowNr - 1, $inColumnNr )
 
1575
          : $rawValue;
 
1576
        $inValue = $cell if ( defined $cell );    # original value from file
 
1577
        Foswiki::Plugins::EditTablePlugin::encodeValue($inValue)
 
1578
          unless ( $inValue eq '' );
 
1579
 
 
1580
        #$inValue = "\*$inValue\*" if ( $isHeader and $inDigestedCellValue );
 
1581
        $text = "\*$text\*" if $isHeader;
 
1582
        $text .= ' ' . hiddenField( $preSp, $inName, $inValue );
 
1583
    }
 
1584
    elsif ( $type eq 'textarea' ) {
 
1585
        my ( $rows, $cols ) = split( /x/, $size );
 
1586
 
 
1587
        $rows |= 3  if !defined $rows;
 
1588
        $cols |= 30 if !defined $cols;
 
1589
        Foswiki::Plugins::EditTablePlugin::encodeValue($inValue)
 
1590
          unless ( $inValue eq '' );
 
1591
        $text .=
 
1592
"<textarea class=\"foswikiTextarea editTableTextarea\" rows=\"$rows\" cols=\"$cols\" name=\"$inName\">$inValue</textarea>";
 
1593
        $text .= saveEditCellFormat( $cellFormat, $inName );
 
1594
 
 
1595
    }
 
1596
    elsif ( $type eq 'date' ) {
 
1597
        my $ifFormat = '';
 
1598
        $ifFormat = $bits[3] if ( @bits > 3 );
 
1599
        $ifFormat ||= $Foswiki::cfg{JSCalendarContrib}{format} || '%e %B %Y';
 
1600
        $size = 10 if ( !$size || $size < 1 );
 
1601
        Foswiki::Plugins::EditTablePlugin::encodeValue($inValue)
 
1602
          unless ( $inValue eq '' );
 
1603
        $text .= CGI::textfield(
 
1604
            {
 
1605
                name     => $inName,
 
1606
                class    => 'foswikiInputField editTableInput',
 
1607
                id       => 'id' . $inName,
 
1608
                size     => $size,
 
1609
                value    => $inValue,
 
1610
                override => 1
 
1611
            }
 
1612
        );
 
1613
        $text .= saveEditCellFormat( $cellFormat, $inName );
 
1614
        eval 'use Foswiki::Contrib::JSCalendarContrib';
 
1615
 
 
1616
        unless ($@) {
 
1617
            $text .= '<span class="foswikiMakeVisible">';
 
1618
            $text .= CGI::image_button(
 
1619
                -class   => 'editTableCalendarButton',
 
1620
                -name    => 'calendar',
 
1621
                -onclick => "return showCalendar('id$inName','$ifFormat')",
 
1622
                -src     => Foswiki::Func::getPubUrlPath() . '/'
 
1623
                  . $Foswiki::cfg{SystemWebName}
 
1624
                  . '/JSCalendarContrib/img.gif',
 
1625
                -alt   => 'Calendar',
 
1626
                -align => 'middle'
 
1627
            );
 
1628
            $text .= '</span>';
 
1629
        }
 
1630
        $query->{'jscalendar'} = 1;
 
1631
 
 
1632
        # prevent wrapping of button below input field
 
1633
        $text = "<nobr>$text</nobr>";
 
1634
    }
 
1635
    else {    #  if( $type eq 'text')
 
1636
        $size = $DEFAULT_FIELD_SIZE if $size < 1;
 
1637
        Foswiki::Plugins::EditTablePlugin::encodeValue($inValue)
 
1638
          unless ( $inValue eq '' );
 
1639
        $text =
 
1640
"<input class=\"foswikiInputField editTableInput\" type=\"text\" name=\"$inName\" size=\"$size\" value=\"$inValue\" />";
 
1641
        $text .= saveEditCellFormat( $cellFormat, $inName );
 
1642
    }
 
1643
 
 
1644
    if ( $type ne 'textarea' ) {
 
1645
        $text =~
 
1646
          s/&#10;/<br \/>/go;    # change unicode linebreak character to <br />
 
1647
    }
 
1648
    return $text;
 
1649
}
 
1650
 
 
1651
=begin TML
 
1652
 
 
1653
=cut
 
1654
 
 
1655
sub handleTableRow {
 
1656
    my (
 
1657
        $thePre, $theRow, $theTableNr, $isNewRow, $theRowNr,
 
1658
        $doEdit, $doSave, $inWeb,      $inTopic
 
1659
    ) = @_;
 
1660
 
 
1661
    _debug( "EditTablePlugin::Core::handleTableRow; params="
 
1662
          . "\n\t thePre=$thePre."
 
1663
          . "\n\t theRow=$theRow."
 
1664
          . "\n\t theTableNr=$theTableNr"
 
1665
          . "\n\t isNewRow=$isNewRow"
 
1666
          . "\n\t theRowNr=$theRowNr"
 
1667
          . "\n\t doEdit=$doEdit"
 
1668
          . "\n\t doSave=$doSave"
 
1669
          . "\n\t inWeb=$inWeb"
 
1670
          . "\n\t inTopic=$inTopic" );
 
1671
 
 
1672
    $thePre |= '';
 
1673
    my $text = "$thePre\|";
 
1674
 
 
1675
    if ($doEdit) {
 
1676
        $theRow =~ s/\|\s*$//o;
 
1677
 
 
1678
        # retrieve any params sent by javascript interface (see edittable.js)
 
1679
        my $rowID = $query->param("etrow_id$theRowNr");
 
1680
        $rowID = $theRowNr if !defined $rowID;
 
1681
 
 
1682
        my @cells;
 
1683
        my $isNewRowFromHeader = ( $theRowNr <= 1 ) && ( $params{'header'} );
 
1684
        @cells =
 
1685
          $isNewRowFromHeader
 
1686
          ? split( /\|/, $params{'header'} )
 
1687
          : split( /\|/, $theRow );
 
1688
        my $tmp = @cells;
 
1689
        $nrCols = $tmp if ( $tmp > $nrCols );    # expand number of cols
 
1690
        my $val         = '';
 
1691
        my $cellFormat  = '';
 
1692
        my $cell        = '';
 
1693
        my $digested    = 0;
 
1694
        my $cellDefined = 0;
 
1695
        my $col         = 0;
 
1696
 
 
1697
        while ( $col < $nrCols ) {
 
1698
            $col += 1;
 
1699
            $cellDefined = 0;
 
1700
            my $cellValueParam = "etcell${rowID}x$col";
 
1701
            $val = $isNewRow ? undef : $query->param($cellValueParam);
 
1702
 
 
1703
           #my $tmpVal = defined $val ? $val : '';
 
1704
           #_debug( "\t col=$col, cellValueParam=$cellValueParam; val=$tmpVal");
 
1705
 
 
1706
            if ( defined $val && $val =~ /^Chkbx: (etcell.*)/ ) {
 
1707
 
 
1708
      # Multiple checkboxes, val has format "Chkbx: etcell4x2x2 etcell4x2x3 ..."
 
1709
                my $checkBoxNames  = $1;
 
1710
                my $checkBoxValues = "";
 
1711
                foreach ( split( /\s/, $checkBoxNames ) ) {
 
1712
                    $val = $query->param($_);
 
1713
 
 
1714
                    #$checkBoxValues .= "$val," if ( defined $val );
 
1715
                    if ( defined $val ) {
 
1716
 
 
1717
                        # make space to expand variables
 
1718
                        $val = addSpaceToBothSides($val);
 
1719
                        $checkBoxValues .= $val . ',';
 
1720
                    }
 
1721
                }
 
1722
                $checkBoxValues =~ s/,\s*$//;
 
1723
                $val = $checkBoxValues;
 
1724
            }
 
1725
 
 
1726
            # SMELL NOTE: etformat is not specified. What should it do?
 
1727
            $cellFormat = $query->param("etformat${rowID}x$col");
 
1728
            $val .= " %EDITCELL{$cellFormat}%" if ($cellFormat);
 
1729
 
 
1730
            if ( defined $val ) {
 
1731
 
 
1732
                # change any new line character sequences to <br />
 
1733
                $val =~ s/[\n\r]{2,}?/<br \/>/gos;
 
1734
 
 
1735
                # escape "|" to HTML entity
 
1736
                $val =~ s/\|/\&\#124;/gos;
 
1737
                $cellDefined = 1;
 
1738
 
 
1739
                # Expand %-vars
 
1740
                $cell = $val;
 
1741
            }
 
1742
            elsif ( $col <= @cells ) {
 
1743
 
 
1744
                $cell = $cells[ $col - 1 ];
 
1745
                $digested = 1;    # Flag that we are using non-raw cell text.
 
1746
                $cellDefined = 1 if ( length($cell) > 0 );
 
1747
                $cell =~ s/^\s*(.+?)\s*$/$1/o
 
1748
                  ; # remove spaces around content, but do not void a cell with just spaces
 
1749
            }
 
1750
            else {
 
1751
                $cell = '';
 
1752
            }
 
1753
            if ($isNewRowFromHeader) {
 
1754
 
 
1755
                unless ($cell) {
 
1756
                    if ( $params{'header'} =~ /^on$/i ) {
 
1757
                        if (   ( @format >= $col )
 
1758
                            && ( $format[ $col - 1 ] =~ /(.*?)\,/ ) )
 
1759
                        {
 
1760
                            $cell = $1;
 
1761
                        }
 
1762
                        $cell = 'text' unless $cell;
 
1763
                        $cell = "*$cell*";
 
1764
                    }
 
1765
                    else {
 
1766
                        my @hCells = split( /\|/, $params{'header'} );
 
1767
                        $cell = $hCells[ $col - 1 ] if ( @hCells >= $col );
 
1768
                        $cell = "*text*" unless $cell;
 
1769
                    }
 
1770
                }
 
1771
                $cell = addSpaceToBothSides($cell);
 
1772
                $text .= "$cell\|";
 
1773
            }
 
1774
            elsif ($doSave) {
 
1775
                $cell = addSpaceToBothSides($cell);
 
1776
 
 
1777
            # Item5217 Avoid that deleting content of cell creates unwanted span
 
1778
                $cell = ' ' if $cell eq '';
 
1779
 
 
1780
                $text .= "$cell\|";
 
1781
            }
 
1782
            else {
 
1783
                if (
 
1784
                       ( !$cellDefined )
 
1785
                    && ( @format >= $col )
 
1786
                    && ( $format[ $col - 1 ] =~
 
1787
                        /^\s*(.*?)\,\s*(.*?)\,\s*(.*?)\s*$/ )
 
1788
                  )
 
1789
                {
 
1790
 
 
1791
                    # default value of "| text, 20, a, b, c |" cell is "a, b, c"
 
1792
                    # default value of '| select, 1, a, b, c |' cell is "a"
 
1793
                    $val = $1;    # type
 
1794
 
 
1795
                    $cell = $3;
 
1796
                    $cell = ''
 
1797
                      unless ( defined $cell && $cell ne '' )
 
1798
                      ;           # Proper handling of '0'
 
1799
                    $cell =~ s/\,.*$//o
 
1800
                      if ( $val eq 'select' || $val eq 'date' );
 
1801
                }
 
1802
                my $element = '';
 
1803
                $cell = '' if $isNewRow;
 
1804
                $element =
 
1805
                  inputElement( $theTableNr, $theRowNr, $col - 1,
 
1806
                    "etcell${theRowNr}x$col", $cell, $digested, $inWeb,
 
1807
                    $inTopic );
 
1808
                $element = " $element \|";
 
1809
                $text .= $element;
 
1810
            }
 
1811
        }
 
1812
    }
 
1813
    else {
 
1814
 
 
1815
        # render EDITCELL in view mode
 
1816
        $theRow =~ s/$PATTERN_EDITCELL/viewEditCell($1)/geo if !$doSave;
 
1817
        $text .= $theRow;
 
1818
 
 
1819
    }    # /if ($doEdit)
 
1820
 
 
1821
    # render final value in view mode (not edit or save)
 
1822
    Foswiki::Plugins::EditTablePlugin::decodeFormatTokens($text)
 
1823
      if ( !$doSave && !$doEdit );
 
1824
 
 
1825
    return $text;
 
1826
}
 
1827
 
 
1828
=begin TML
 
1829
 
 
1830
Add one space to both sides of the text to allow TML expansion.
 
1831
Convert multiple (existing) spaces to one space.
 
1832
 
 
1833
=cut
 
1834
 
 
1835
sub addSpaceToBothSides {
 
1836
    my ($text) = @_;
 
1837
    return $text if $text eq '';
 
1838
 
 
1839
    $text = " $text ";
 
1840
    $text =~ s/^[[:space:]]+/ /;    # remove extra spaces
 
1841
    $text =~ s/[[:space:]]+$/ /;
 
1842
    return $text;
 
1843
}
 
1844
 
 
1845
=begin TML
 
1846
 
 
1847
=cut
 
1848
 
 
1849
sub doCancelEdit {
 
1850
    my ( $inWeb, $inTopic ) = @_;
 
1851
 
 
1852
    Foswiki::Func::setTopicEditLock( $inWeb, $inTopic, 0 );
 
1853
 
 
1854
    Foswiki::Func::redirectCgiQuery( $query,
 
1855
        Foswiki::Func::getViewUrl( $inWeb, $inTopic ) );
 
1856
}
 
1857
 
 
1858
=begin TML
 
1859
 
 
1860
=cut
 
1861
 
 
1862
sub doEnableEdit {
 
1863
    my ( $inWeb, $inTopic, $doCheckIfLocked ) = @_;
 
1864
 
 
1865
    my $wikiUserName = Foswiki::Func::getWikiName();
 
1866
    if (
 
1867
        !Foswiki::Func::checkAccessPermission(
 
1868
            'change', $wikiUserName, undef, $inTopic, $inWeb
 
1869
        )
 
1870
      )
 
1871
    {
 
1872
 
 
1873
        # user has no permission to change the topic
 
1874
        throw Foswiki::OopsException(
 
1875
            'accessdenied',
 
1876
            status => 403,
 
1877
            def    => 'topic_access',
 
1878
            web    => $inWeb,
 
1879
            topic  => $inTopic,
 
1880
            params => [ 'change', 'denied' ]
 
1881
        );
 
1882
    }
 
1883
 
 
1884
    my $breakLock = $query->param('breaklock') || '';
 
1885
    unless ($breakLock) {
 
1886
        my ( $oopsUrl, $lockUser ) =
 
1887
          Foswiki::Func::checkTopicEditLock( $inWeb, $inTopic, 'view' );
 
1888
        if ($oopsUrl) {
 
1889
            my $loginUser = Foswiki::Func::wikiToUserName($wikiUserName);
 
1890
            if ( $lockUser ne $loginUser ) {
 
1891
 
 
1892
                # change the default oopsleaseconflict url
 
1893
                # use viewauth instead of view
 
1894
                $oopsUrl =~ s/param4=view/param4=viewauth/;
 
1895
 
 
1896
                # add info of the edited table
 
1897
                my $params = '';
 
1898
                $query = Foswiki::Func::getCgiQuery();
 
1899
                $params .= ';ettablenr=' . $query->param('ettablenr');
 
1900
                $params .= ';etedit=on';
 
1901
                $oopsUrl =~ s/($|#\w*)/$params/;
 
1902
 
 
1903
                # warn user that other person is editing this topic
 
1904
                Foswiki::Func::redirectCgiQuery( $query, $oopsUrl );
 
1905
                return 0;
 
1906
            }
 
1907
        }
 
1908
    }
 
1909
 
 
1910
    # We are allowed to edit
 
1911
    Foswiki::Func::setTopicEditLock( $inWeb, $inTopic, 1 );
 
1912
 
 
1913
    return 1;
 
1914
}
 
1915
 
 
1916
=begin TML
 
1917
 
 
1918
stripCommentsFromRegex($pattern) -> $pattern
 
1919
 
 
1920
For debugging: removes all spaces and comments from a regular expression.
 
1921
 
 
1922
=cut
 
1923
 
 
1924
sub stripCommentsFromRegex {
 
1925
    my ($inRegex) = @_;
 
1926
 
 
1927
    ( my $cleanRegex = $inRegex ) =~ s/\s*(.*?)\s*(#.*?)*(\r|\n|$)/$1/go;
 
1928
    return $cleanRegex;
 
1929
}
 
1930
 
 
1931
=begin TML
 
1932
 
 
1933
StaticMethod _handleSpreadsheetFormula( $text ) -> $text
 
1934
 
 
1935
Replaces a SpreadSheetPlugin formula by a static text.
 
1936
 
 
1937
=cut
 
1938
 
 
1939
sub _handleSpreadsheetFormula {
 
1940
    
 
1941
    return if !$_[0];
 
1942
    $_[0] =~ s/$PATTERN_SPREADSHEETPLUGIN_CALC/$SPREADSHEETPLUGIN_CALC_SUBSTITUTION/go;
 
1943
    
 
1944
}
 
1945
 
 
1946
=begin TML
 
1947
 
 
1948
StaticMethod handleTmlInTables( \@lines )
 
1949
 
 
1950
Users using the plugin would be confused when they enter newlines,
 
1951
which get replaced with %BR%, and thus might not render their TML
 
1952
 
 
1953
So we hack it here so that all TML and HTML tags have spaces around them:
 
1954
- adds spaces around %BR% to render TML around linebreaks
 
1955
- add spaces around TML next to HTML tags, again to render TML
 
1956
- expands variables, for example %CALC% 
 
1957
Check Foswikibug:Item1017
 
1958
 
 
1959
=cut
 
1960
 
 
1961
sub handleTmlInTables {
 
1962
 
 
1963
    # my $lines = $_[0]
 
1964
 
 
1965
    map { $_ =~ s/(%BR%)/ $1 /gox; addSpacesToTmlNextToHtml($_) } @{ $_[0] };
 
1966
}
 
1967
 
 
1968
=begin TML
 
1969
 
 
1970
StaticMethod addSpacesToTmlNextToHtml( \$text )
 
1971
 
 
1972
So that:
 
1973
 
 
1974
| *bold*<br />_italic_ |
 
1975
 
 
1976
gets rendered as:
 
1977
 
 
1978
|*bold* <br /> _italic_|
 
1979
 
 
1980
=cut
 
1981
 
 
1982
sub addSpacesToTmlNextToHtml {
 
1983
 
 
1984
    # my $text = $_[0]
 
1985
 
 
1986
    # also remove spaces at both sides to prevent extra spaces are added to the
 
1987
    # cell, resulting in wrong alignment (when html tags are stripped in the
 
1988
    # core table renderer)
 
1989
 
 
1990
    my $TMLpattern = qr/[_*=]*/o;
 
1991
    my $pattern    = qr(
 
1992
        [[:space:]]*            # any space
 
1993
        ($TMLpattern)           # i1: optional TML syntax before html tag
 
1994
        (                                       # i2: html tag
 
1995
        </*                             # start of tag (optional closing tag)
 
1996
        (?:$HTML_TAGS)+         # any of the html tags
 
1997
        [[:space:]]*            # any space
 
1998
        .*?                                 # anything before the end of tag
 
1999
        /*>                             # end of tag (optional closing tag)
 
2000
        )                                       # /i2
 
2001
        ($TMLpattern)           # i3: optional TML syntax after html tag
 
2002
        [[:space:]]*            # any space
 
2003
        )ox;
 
2004
 
 
2005
    $_[0] =~ s/$pattern/$1 $2 $3/go;
 
2006
}
 
2007
 
 
2008
=begin TML
 
2009
 
 
2010
StaticMethod getHeaderAndFooterCount( $text ) -> ($headerRowCount, $footerRowCount)
 
2011
 
 
2012
Reads the headerrows and footerrows parameters from the TABLE macro (if any) and returns them as tuple.
 
2013
 
 
2014
If no TABLE tag is present, returns (0,0).
 
2015
 
 
2016
=cut
 
2017
 
 
2018
sub getHeaderAndFooterCount {
 
2019
    my ($inTag) = @_;
 
2020
 
 
2021
    my $tag = $inTag;
 
2022
 
 
2023
    # expand macros in tagline without creating infinite recursion,
 
2024
    # so delete EDITTABLE as we won't need it here
 
2025
    $tag =~ s/%EDITTABLE{/_DELETED_/o;
 
2026
    $tag = Foswiki::Func::expandCommonVariables($tag);
 
2027
 
 
2028
    my $headerRowCount = 0;
 
2029
    my $footerRowCount = 0;
 
2030
 
 
2031
    if ( $tag =~ m/$PATTERN_TABLEPLUGIN/ ) {
 
2032
 
 
2033
        # We want this info also when viewing, because the row count takes
 
2034
        # header and footer rows into account
 
2035
        # match with a TablePlugin line
 
2036
        # works when TABLE tag is just above OR just below the EDITTABLE tag
 
2037
        my %tablePluginParams = Foswiki::Func::extractParameters($1);
 
2038
        $headerRowCount = $tablePluginParams{'headerrows'} || 0;
 
2039
        $footerRowCount = $tablePluginParams{'footerrows'} || 0;
 
2040
    }
 
2041
    return ( $headerRowCount, $footerRowCount );
 
2042
}
 
2043
 
 
2044
sub _modeToString {
 
2045
    my ($mode) = @_;
 
2046
 
 
2047
    my $text = '';
 
2048
    $text .= "; mode is READ"           if ( $mode & $MODE->{READ} );
 
2049
    $text .= "; mode is EDIT"           if ( $mode & $MODE->{EDIT} );
 
2050
    $text .= "; mode is SAVE"           if ( $mode & $MODE->{SAVE} );
 
2051
    $text .= "; mode is SAVEQUIET"      if ( $mode & $MODE->{SAVEQUIET} );
 
2052
    $text .= "; mode is CANCEL"         if ( $mode & $MODE->{CANCEL} );
 
2053
    $text .= "; mode is EDITNOTALLOWED" if ( $mode & $MODE->{EDITNOTALLOWED} );
 
2054
 
 
2055
    return $text;
 
2056
}
 
2057
 
 
2058
sub _writeDebug {
 
2059
    my ($inText) = @_;
 
2060
 
 
2061
    Foswiki::Func::writeDebug($inText);
 
2062
}
 
2063
 
 
2064
sub _debug {
 
2065
    my ($inText) = @_;
 
2066
 
 
2067
    Foswiki::Func::writeDebug($inText)
 
2068
      if $Foswiki::Plugins::EditTablePlugin::debug;
 
2069
}
 
2070
 
 
2071
1;
 
2072
 
 
2073
__DATA__
 
2074
# Plugin for Foswiki - The Free and Open Source Wiki, http://foswiki.org/
 
2075
#
 
2076
# Copyright (C) 2008-2009 Arthur Clemens, arthur@visiblearea.com and Foswiki contributors
 
2077
# Copyright (C) 2002-2007 Peter Thoeny, peter@thoeny.org and
 
2078
# TWiki Contributors.
 
2079
#
 
2080
# This program is free software; you can redistribute it and/or
 
2081
# modify it under the terms of the GNU General Public License
 
2082
# as published by the Free Software Foundation; either version 2
 
2083
# of the License, or (at your option) any later version. For
 
2084
# more details read LICENSE in the root of this distribution.
 
2085
#
 
2086
# This program is distributed in the hope that it will be useful,
 
2087
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
2088
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
2089
#
 
2090
# As per the GPL, removal of this notice is prohibited.
 
2091
#
 
2092
# This is the EditTablePlugin used to edit tables in place.