lib/TWiki/Plugins/EditTablePlugin/Core.pm
changeset 0 414e01d06fd5
child 1 e2915a7cbdfa
equal deleted inserted replaced
-1:000000000000 0:414e01d06fd5
       
     1 # Plugin for TWiki Enterprise Collaboration Platform, http://TWiki.org/
       
     2 #
       
     3 # Copyright (C) 2002-2007 Peter Thoeny, peter@thoeny.org and
       
     4 # TWiki Contributors.
       
     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. For
       
    10 # more details read LICENSE in the root of this distribution.
       
    11 #
       
    12 # This program is distributed in the hope that it will be useful,
       
    13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
       
    15 #
       
    16 # As per the GPL, removal of this notice is prohibited.
       
    17 #
       
    18 # This is the EditTablePlugin used to edit tables in place.
       
    19 
       
    20 package TWiki::Plugins::EditTablePlugin::Core;
       
    21 
       
    22 use strict;
       
    23 use Assert;
       
    24 
       
    25 use vars qw(
       
    26   $preSp %params @format @formatExpanded
       
    27   $prefsInitialized $prefCHANGEROWS $prefEDIT_BUTTON $prefSAVE_BUTTON $prefQUIET_SAVE_BUTTON $prefADD_ROW_BUTTON $prefDELETE_LAST_ROW_BUTTON $prefCANCEL_BUTTON $prefMESSAGE_INCLUDED_TOPIC_DOES_NOT_EXIST
       
    28   $prefQUIETSAVE
       
    29   $nrCols $encodeStart $encodeEnd $table $query %regex
       
    30   $warningMessage
       
    31 );
       
    32 
       
    33 my $RENDER_HACK        = "\n<nop>\n";
       
    34 my $DEFAULT_FIELD_SIZE = 16;
       
    35 
       
    36 BEGIN {
       
    37     %regex                    = ();
       
    38     $regex{edit_table_plugin} = '%EDITTABLE{(.*?)}%';
       
    39     $regex{table_plugin}      = '%TABLE(?:{(.*?)})?%';
       
    40     $regex{table_row_full}    = '^(\s*)\|.*\|\s*$';
       
    41     $regex{table_row}         = '^(\s*)\|(.*)';
       
    42 }
       
    43 
       
    44 sub init {
       
    45     $preSp                      = '';
       
    46     %params                     = ();
       
    47     @format                     = ();
       
    48     @formatExpanded             = ();
       
    49     $prefsInitialized           = undef;
       
    50     $prefCHANGEROWS             = undef;
       
    51     $prefEDIT_BUTTON            = undef;
       
    52     $prefSAVE_BUTTON            = undef;
       
    53     $prefQUIET_SAVE_BUTTON      = undef;
       
    54     $prefADD_ROW_BUTTON         = undef;
       
    55     $prefDELETE_LAST_ROW_BUTTON = undef;
       
    56     $prefDELETE_LAST_ROW_BUTTON = undef;
       
    57     $prefQUIETSAVE              = undef;
       
    58     $nrCols                     = undef;
       
    59     $encodeStart                = undef;
       
    60     $encodeEnd                  = undef;
       
    61     $table                      = undef;
       
    62     $query                      = undef;
       
    63     $warningMessage             = '';
       
    64 }
       
    65 
       
    66 =pod
       
    67 
       
    68 ---+++ process( $doSave, $saveTableNr, $doSaveQuiet, $text, $topic, $web )
       
    69 
       
    70 Called from commonTagsHandler. Pass over to processText in 'no Save' mode.
       
    71 
       
    72 =cut
       
    73 
       
    74 sub process {
       
    75     init();
       
    76     my $saveMode      = $TWiki::Plugins::EditTablePlugin::saveMode{'NONE'};
       
    77     my $saveTableNr   = 0;
       
    78     my $saveQuietMode = $TWiki::Plugins::EditTablePlugin::saveMode{'SAVEQUIET'};
       
    79     processText( $saveMode, $saveTableNr, $saveQuietMode, @_ );
       
    80 }
       
    81 
       
    82 =pod
       
    83 
       
    84 ---+++ processText( $doSave, $saveTableNr, $doSaveQuiet, $text, $topic, $web )
       
    85 
       
    86 Process the text line by line.
       
    87 When a EditTablePlugin table is encountered, its contents is rendered according to the view: 
       
    88    * View mode - default
       
    89    * Edit mode - when an Edit button is clicked, renders the rest of the table in edit mode
       
    90    * Save mode - when called from a Save button: calls processText again, only renders the selected table number, then saves the topic text
       
    91 
       
    92 =cut
       
    93 
       
    94 sub processText {
       
    95 
       
    96     my $doSave = ( shift == $TWiki::Plugins::EditTablePlugin::saveMode{'SAVE'} )
       
    97       || 0;
       
    98     my $saveTableNr = shift;
       
    99     my $doSaveQuiet =
       
   100       ( shift == $TWiki::Plugins::EditTablePlugin::saveMode{'SAVEQUIET'} ) || 0;
       
   101 
       
   102     $query = TWiki::Func::getCgiQuery();
       
   103 
       
   104     TWiki::Func::writeDebug(
       
   105         "- EditTablePlugin::commonTagsHandler( $_[2].$_[1] )")
       
   106       if $TWiki::Plugins::EditTablePlugin::debug;
       
   107 
       
   108     unless ($prefsInitialized) {
       
   109         $prefCHANGEROWS = TWiki::Func::getPreferencesValue('CHANGEROWS')
       
   110           || TWiki::Func::getPreferencesValue('EDITTABLEPLUGIN_CHANGEROWS')
       
   111           || 'on';
       
   112         $prefQUIETSAVE = TWiki::Func::getPreferencesValue('QUIETSAVE')
       
   113           || TWiki::Func::getPreferencesValue('EDITTABLEPLUGIN_QUIETSAVE')
       
   114           || 'on';
       
   115         $prefEDIT_BUTTON = TWiki::Func::getPreferencesValue('EDIT_BUTTON')
       
   116           || TWiki::Func::getPreferencesValue('EDITTABLEPLUGIN_EDIT_BUTTON')
       
   117           || 'Edit table';
       
   118         $prefSAVE_BUTTON = TWiki::Func::getPreferencesValue('SAVE_BUTTON')
       
   119           || TWiki::Func::getPreferencesValue('EDITTABLEPLUGIN_SAVE_BUTTON')
       
   120           || 'Save table';
       
   121         $prefQUIET_SAVE_BUTTON =
       
   122           TWiki::Func::getPreferencesValue('QUIET_SAVE_BUTTON')
       
   123           || TWiki::Func::getPreferencesValue(
       
   124             'EDITTABLEPLUGIN_QUIET_SAVE_BUTTON')
       
   125           || 'Quiet save';
       
   126         $prefADD_ROW_BUTTON = TWiki::Func::getPreferencesValue('ADD_ROW_BUTTON')
       
   127           || TWiki::Func::getPreferencesValue('EDITTABLEPLUGIN_ADD_ROW_BUTTON')
       
   128           || 'Add row';
       
   129         $prefDELETE_LAST_ROW_BUTTON =
       
   130           TWiki::Func::getPreferencesValue('DELETE_LAST_ROW_BUTTON')
       
   131           || TWiki::Func::getPreferencesValue(
       
   132             'EDITTABLEPLUGIN_DELETE_LAST_ROW_BUTTON')
       
   133           || 'Delete last row';
       
   134         $prefCANCEL_BUTTON = TWiki::Func::getPreferencesValue('CANCEL_BUTTON')
       
   135           || TWiki::Func::getPreferencesValue('EDITTABLEPLUGIN_CANCEL_BUTTON')
       
   136           || 'Cancel';
       
   137         $prefMESSAGE_INCLUDED_TOPIC_DOES_NOT_EXIST =
       
   138           TWiki::Func::getPreferencesValue('INCLUDED_TOPIC_DOES_NOT_EXIST')
       
   139           || TWiki::Func::getPreferencesValue(
       
   140             'EDITTABLEPLUGIN_INCLUDED_TOPIC_DOES_NOT_EXIST')
       
   141           || 'Warning: \'include\' topic does not exist!';
       
   142 
       
   143         $prefsInitialized = 1;
       
   144     }
       
   145 
       
   146     my $theTopic = $query->param('ettabletopic') || $_[1];
       
   147     my $theWeb   = $query->param('ettableweb')   || $_[2];
       
   148     my $invokedFromTopic = $_[3];    # not used yet
       
   149     my $invokedFromWeb   = $_[4];    # not used yet
       
   150 
       
   151     my $result = '';
       
   152 
       
   153     my $insidePRE    = 0;
       
   154     my $cgiTableNr   = 0;
       
   155     my $tableNr      = 0;      # current EditTable table
       
   156     my $isAtTheTable = 0;
       
   157     my $rowNr        = 0;      # current row number; starting at 1
       
   158     my $enableForm   = 0;
       
   159     my $insideTable  = 0;
       
   160     my $doEdit       = $doSave;
       
   161     my $hasTableRow  = 0;      # the current line has a row with '| some text |'
       
   162     my $hasTableTag  = 0;      # the current line has a %TABLE{}% variable tag
       
   163     my $createdNewTable = 0;
       
   164     my @rows            = ();
       
   165     my $etrows          = -1
       
   166       ; # the number of content rows as passed as form parameter: only available on edit or save; -1 if not rendered
       
   167     my $etrowsParam;
       
   168     my $addedRowCount      = 0;
       
   169     my $addedRowCountParam = 0;
       
   170     my $headerRowCount     = 0;    #$query->param('etheaderrows') || 0;
       
   171     my $footerRowCount     = 0;    #$query->param('etfooterrows') || 0;
       
   172     my $endOfTable         = 0;
       
   173 
       
   174     my $theText;
       
   175     if ($doSave) {
       
   176         $theText = TWiki::Func::readTopicText( $theWeb, $theTopic );
       
   177     }
       
   178     else {
       
   179         $theText = $_[0];
       
   180     }
       
   181 
       
   182     $theText =~
       
   183       s/\r//go;    # strip out all \r chars (may be pasted into a table cell)
       
   184     $theText =~ s/\\\n//go;    # Join lines ending in "\"
       
   185     $theText .= $RENDER_HACK
       
   186       ;    # appended stuff is a hack to handle EDITTABLE correctly if at end
       
   187 
       
   188     my @lines = split( /\n/, $theText );
       
   189     for (@lines) {
       
   190 
       
   191         # Check if we are inside <pre> or <verbatim> tags
       
   192         # if so, do not process
       
   193         m|<pre>|i       && ( $insidePRE = 1 );
       
   194         m|<verbatim>|i  && ( $insidePRE = 1 );
       
   195         m|</pre>|i      && ( $insidePRE = 0 );
       
   196         m|</verbatim>|i && ( $insidePRE = 0 );
       
   197 
       
   198         if ($insidePRE) {
       
   199 
       
   200             # no need to process, just copy the line
       
   201             $result .= "$_\n";
       
   202             next;
       
   203         }
       
   204 
       
   205         my $isLineWithEditTableTag = m/(\s*)$regex{edit_table_plugin}/go;
       
   206         if ($isLineWithEditTableTag) {
       
   207 
       
   208             # this is a line with an EDITTABLE tag
       
   209             if ($doSave) {
       
   210 
       
   211                 # no need to process, just copy the line
       
   212                 $result .= "$_\n";
       
   213             }
       
   214             else {
       
   215                 my $line = $_;
       
   216 
       
   217                 # process the tag contents
       
   218                 my $restRegex = '([^$]*)';
       
   219                 my $editTablePluginRE =
       
   220                   "(.*?)$regex{edit_table_plugin}$restRegex";
       
   221                 $line =~
       
   222 s/$editTablePluginRE/&handleEditTableTag( $theWeb, $theTopic, $1, $2 )/geo;
       
   223 
       
   224                 # TODO: something strange has happened to the prefix
       
   225                 # it is no longer used by handleEditTableTag
       
   226                 # we add it here:
       
   227                 $result .= $1 if $1;
       
   228                 $result .= $3 if $3;
       
   229             }
       
   230             $tableNr++;
       
   231 
       
   232             next if ( $doSave && ( $tableNr != $saveTableNr ) );
       
   233             $enableForm = 1;
       
   234 
       
   235             $cgiTableNr = $query->param('ettablenr')
       
   236               || 0;    # only on save and edit
       
   237             $etrowsParam = $query->param('etrows');
       
   238             $etrows =
       
   239               ( defined $etrowsParam )
       
   240               ? $etrowsParam
       
   241               : -1;
       
   242             $addedRowCountParam = $query->param('etaddedrows') || 0;
       
   243             $addedRowCount = $addedRowCountParam;
       
   244 
       
   245             $isAtTheTable = 0;
       
   246             if (
       
   247                 ( $cgiTableNr == $tableNr )
       
   248                 && (  $theWeb . '.'
       
   249                     . $theTopic eq
       
   250 "$TWiki::Plugins::EditTablePlugin::web.$TWiki::Plugins::EditTablePlugin::topic"
       
   251                 )
       
   252               )
       
   253             {
       
   254                 $isAtTheTable = 1;
       
   255                 if ( !$doSave && $query->param('etsave') ) {
       
   256 
       
   257                     # [Save table] button pressed
       
   258                     my $theSaveMode =
       
   259                       $TWiki::Plugins::EditTablePlugin::saveMode{'SAVE'};
       
   260                     my $theSaveQuietMode =
       
   261                       $TWiki::Plugins::EditTablePlugin::saveMode{'NONE'};
       
   262 
       
   263                     return processText( $theSaveMode, $tableNr,
       
   264                         $theSaveQuietMode, @_ );
       
   265                 }
       
   266                 elsif ( !$doSave && $query->param('etqsave') ) {
       
   267 
       
   268                     # [Quiet save] button pressed
       
   269                     my $theSaveMode =
       
   270                       $TWiki::Plugins::EditTablePlugin::saveMode{'SAVE'};
       
   271                     my $theSaveQuietMode =
       
   272                       $TWiki::Plugins::EditTablePlugin::saveMode{'SAVEQUIET'};
       
   273                     return processText( $theSaveMode, $tableNr,
       
   274                         $theSaveQuietMode, @_ );
       
   275                 }
       
   276                 elsif ( $query->param('etcancel') ) {
       
   277 
       
   278                     # [Cancel] button pressed
       
   279                     doCancelEdit( $theWeb, $theTopic );
       
   280                     ASSERT(0) if DEBUG;
       
   281                     return;    # in case browser does not redirect
       
   282                 }
       
   283                 elsif ( $query->param('etaddrow') ) {
       
   284 
       
   285                     # [Add row] button pressed
       
   286                     $etrows = ( $etrows == -1 ) ? 1 : $etrows + 1;
       
   287                     $addedRowCount++;
       
   288                     $doEdit = doEnableEdit( $theWeb, $theTopic, 0 );
       
   289                     return unless ($doEdit);
       
   290                 }
       
   291                 elsif ( $query->param('etdelrow') ) {
       
   292 
       
   293                     # [Delete row] button pressed
       
   294                     if ( $etrows > 0 ) {
       
   295                         $etrows--;
       
   296                     }
       
   297                     $addedRowCount--;
       
   298                     $doEdit = doEnableEdit( $theWeb, $theTopic, 0 );
       
   299                     return unless ($doEdit);
       
   300                 }
       
   301                 elsif ( $query->param('etedit') ) {
       
   302 
       
   303                     # [Edit table] button pressed
       
   304                     $doEdit = doEnableEdit( $theWeb, $theTopic, 1 );
       
   305 
       
   306                     # never return if locked or no permission
       
   307                     return unless ($doEdit);
       
   308                 }
       
   309             }
       
   310         }    # if $isLineWithEditTableTag
       
   311 
       
   312         $hasTableTag = 0;
       
   313         if (/$regex{table_plugin}/) {
       
   314 
       
   315             # match with a TablePlugin line
       
   316             # works when TABLE tag is just above OR just below the EDITTABLE tag
       
   317             my %tablePluginParams = TWiki::Func::extractParameters($1);
       
   318             $headerRowCount = $tablePluginParams{'headerrows'} || 0;
       
   319             $footerRowCount = $tablePluginParams{'footerrows'} || 0;
       
   320 
       
   321             # When editing we append a disableallsort="on" to the TABLE tag
       
   322             # to prevent TablePlugin from sorting the table. (Item5135)
       
   323             $_ =~ s/(}%)/ disableallsort="on"$1/ if ( $doEdit && !$doSave );
       
   324 
       
   325             $hasTableTag = 1;
       
   326         }
       
   327 
       
   328         $hasTableRow = 0;    # assume no row
       
   329         if (m/$regex{table_row_full}/) {
       
   330             $hasTableRow = 1;
       
   331         }
       
   332 
       
   333         if ($enableForm) {
       
   334 
       
   335             if ( !$doEdit && !$doSave ) {
       
   336 
       
   337                 if ( !$hasTableRow && !$insideTable ) {
       
   338                     my $tableStart =
       
   339                       handleTableStart( $theWeb, $theTopic, $tableNr, $doEdit );
       
   340                     $result .= $tableStart;
       
   341                     $insideTable = 1;
       
   342                     $hasTableRow = 1;
       
   343                     next;
       
   344                 }
       
   345                 if ($hasTableRow) {
       
   346                     $insideTable = 1;
       
   347                     $rowNr++;
       
   348                     my $isNewRow = 0;
       
   349 s/^(\s*)\|(.*)/handleTableRow( $1, $2, $tableNr, $isNewRow, $rowNr, $doEdit, $doSave, $theWeb, $theTopic )/eo;
       
   350                 }
       
   351                 elsif ( $insideTable && !$hasTableTag ) {
       
   352 
       
   353                     # end of table
       
   354                     $endOfTable = 1;
       
   355                     my $rowCount = $rowNr - $headerRowCount - $footerRowCount;
       
   356                     my $tableEnd = handleTableEnd( $theWeb, $rowCount, $doEdit,
       
   357                         $headerRowCount, $footerRowCount );
       
   358                     $result .= $tableEnd;
       
   359                 }
       
   360             }    # if !$doEdit && !$doSave
       
   361 
       
   362             if ( $doEdit || $doSave ) {
       
   363                 if ( !$hasTableRow && !$insideTable && !$createdNewTable ) {
       
   364 
       
   365                     # start new table
       
   366                     $createdNewTable = 1;
       
   367                     if ( !$doSave ) {
       
   368                         my $tableStart =
       
   369                           handleTableStart( $theWeb, $theTopic, $tableNr,
       
   370                             $doEdit );
       
   371                         $result .= $tableStart;
       
   372                     }
       
   373                     $insideTable = 1;
       
   374                     $hasTableRow = 1;
       
   375                     next;
       
   376                 }
       
   377                 if ($hasTableRow) {
       
   378                     $insideTable = 1;
       
   379                     $rowNr++;
       
   380 
       
   381 # when adding new rows, previously entered values will be mapped onto the new table rows
       
   382 # when the last row is not the newly added, as may happen with footer rows, we need to adjust the mapping
       
   383 # we introduce a 'rowNr shift' for values
       
   384 # we assume that new rows are added just before the footer
       
   385                     my $shift = 0;
       
   386                     if ( $footerRowCount > 0 ) {
       
   387                         my $bodyRowNr = $rowNr - $headerRowCount;
       
   388                         if ( $bodyRowNr > ( $etrows - $addedRowCount ) ) {
       
   389                             $shift = $addedRowCountParam;
       
   390                         }
       
   391                     }
       
   392                     my $theRowNr = $rowNr + $shift;
       
   393                     my $isNewRow = 0;
       
   394 s/$regex{table_row}/handleTableRow( $1, $2, $tableNr, $isNewRow, $theRowNr, $doEdit, $doSave, $theWeb, $theTopic )/eo;
       
   395                     push @rows, $_;
       
   396                     next;
       
   397                 }
       
   398                 elsif ( $insideTable && !$hasTableTag ) {
       
   399 
       
   400                     # end of table
       
   401                     $endOfTable = 1;
       
   402                     my @headerRows = ();
       
   403                     my @footerRows = ();
       
   404                     my @bodyRows   = @rows;    #clone
       
   405 
       
   406                     if ( $headerRowCount > 0 ) {
       
   407                         @headerRows = @rows;    # clone
       
   408                         splice @headerRows, $headerRowCount;
       
   409 
       
   410                         # remove the header rows from the body rows
       
   411                         splice @bodyRows, 0, $headerRowCount;
       
   412                     }
       
   413                     if ( $footerRowCount > 0 ) {
       
   414                         @footerRows = @rows;    # clone
       
   415                         splice @footerRows, 0,
       
   416                           ( scalar @footerRows - $footerRowCount );
       
   417 
       
   418                         # remove the footer rows from the body rows
       
   419                         splice @bodyRows,
       
   420                           ( scalar @bodyRows - $footerRowCount ),
       
   421                           $footerRowCount;
       
   422                     }
       
   423 
       
   424                     # delete rows?
       
   425                     if ( $doEdit || $doSave ) {
       
   426                         if ( scalar @bodyRows > $etrows && $etrows != -1 ) {
       
   427                             splice( @bodyRows, $etrows );
       
   428                         }
       
   429                     }
       
   430 
       
   431                     # no table at all?
       
   432                     if ( $doEdit && !$doSave ) {
       
   433 
       
   434                         # if we are starting with an empty table, we force
       
   435                         # create a row, with an optional header row
       
   436                         my $addHeader =
       
   437                           ( $params{'header'} && $headerRowCount == 0 )
       
   438                           ? 1
       
   439                           : 0;
       
   440                         my $firstRowsCount = 1 + $addHeader;
       
   441 
       
   442                         if ( scalar @bodyRows < $firstRowsCount
       
   443                             && !$query->param('etdelrow') )
       
   444                         {
       
   445                             if ( $etrows < $firstRowsCount ) {
       
   446                                 $etrows = $firstRowsCount;
       
   447                             }
       
   448                         }
       
   449                     }
       
   450 
       
   451                     # add rows?
       
   452                     if ( $doEdit || $doSave ) {
       
   453                         while ( scalar @bodyRows < $etrows ) {
       
   454                             $rowNr++;
       
   455                             my $newBodyRowNr = scalar @bodyRows + 1;
       
   456                             my $theRowNr     = $newBodyRowNr + $headerRowCount;
       
   457 
       
   458                             my $isNewRow = ( defined $etrowsParam
       
   459                                   && $newBodyRowNr > $etrowsParam ) ? 1 : 0;
       
   460                             my $newRow = handleTableRow(
       
   461                                 '',        '',      $tableNr, $isNewRow,
       
   462                                 $theRowNr, $doEdit, $doSave,  $theWeb,
       
   463                                 $theTopic
       
   464                             );
       
   465                             push @bodyRows, $newRow;
       
   466                         }
       
   467                     }
       
   468 
       
   469                     my @combinedRows = ( @headerRows, @bodyRows, @footerRows );
       
   470 
       
   471                     # after re-ordering, renumber the cells
       
   472                     my $rowCounter = 0;
       
   473                     for my $cellRow (@combinedRows) {
       
   474                         $rowCounter++;
       
   475                         $cellRow =~
       
   476                           s/(etcell)([0-9]+)(x)([0-9]+)/$1$rowCounter$3$4/go;
       
   477                     }
       
   478                     $result .= join( "\n", @combinedRows ) . "\n";
       
   479 
       
   480                     if ( !$doSave ) {
       
   481                         my $rowCount = scalar @bodyRows;
       
   482                         my $tableEnd =
       
   483                           handleTableEnd( $theWeb, $rowCount, $doEdit,
       
   484                             $headerRowCount, $footerRowCount, $addedRowCount );
       
   485                         $result .= $tableEnd;
       
   486                     }
       
   487 
       
   488                 }    # $hasTableRow
       
   489             }    #/ if $doEdit
       
   490         }    # if $enableForm
       
   491 
       
   492         if ($endOfTable) {
       
   493             $endOfTable = 0;
       
   494 
       
   495             # re-init values
       
   496             $insideTable     = 0;
       
   497             $enableForm      = 0;
       
   498             $doEdit          = 0;
       
   499             $rowNr           = 0;
       
   500             $createdNewTable = 0;
       
   501             $headerRowCount  = 0;
       
   502             $footerRowCount  = 0;
       
   503             $etrows          = -1;
       
   504             @rows            = ();
       
   505             $isAtTheTable    = 0;
       
   506             $cgiTableNr      = 0;
       
   507         }
       
   508 
       
   509         $result .= "$_\n";
       
   510     }
       
   511 
       
   512     # clean up hack that handles EDITTABLE correctly if at end
       
   513     $result =~ s/($RENDER_HACK)+$//go;
       
   514 
       
   515     if ($doSave) {
       
   516         my $error = TWiki::Func::saveTopicText( $theWeb, $theTopic, $result, '',
       
   517             $doSaveQuiet );
       
   518         TWiki::Func::setTopicEditLock( $theWeb, $theTopic, 0 );   # unlock Topic
       
   519         my $url = TWiki::Func::getViewUrl( $theWeb, $theTopic );
       
   520         if ($error) {
       
   521             $url = TWiki::Func::getOopsUrl( $theWeb, $theTopic, 'oopssaveerr',
       
   522                 $error );
       
   523         }
       
   524         TWiki::Func::redirectCgiQuery( $query, $url );
       
   525         return;
       
   526     }
       
   527     $_[0] = $result;
       
   528 }
       
   529 
       
   530 =pod
       
   531 
       
   532 =cut
       
   533 
       
   534 sub extractParams {
       
   535     my ( $theArgs, $theHashRef ) = @_;
       
   536 
       
   537     my $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'header' );
       
   538     $$theHashRef{'header'} = $tmp if ($tmp);
       
   539 
       
   540     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'footer' );
       
   541     $$theHashRef{'footer'} = $tmp if ($tmp);
       
   542 
       
   543     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'headerislabel' );
       
   544     $$theHashRef{'headerislabel'} = $tmp if ($tmp);
       
   545 
       
   546     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'format' );
       
   547     $tmp =~ s/^\s*\|*\s*//o;
       
   548     $tmp =~ s/\s*\|*\s*$//o;
       
   549     $$theHashRef{'format'} = $tmp if ($tmp);
       
   550 
       
   551     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'changerows' );
       
   552     $$theHashRef{'changerows'} = $tmp if ($tmp);
       
   553 
       
   554     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'quietsave' );
       
   555     $$theHashRef{'quietsave'} = $tmp if ($tmp);
       
   556 
       
   557     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'helptopic' );
       
   558     $$theHashRef{'helptopic'} = $tmp if ($tmp);
       
   559 
       
   560     $tmp = TWiki::Func::extractNameValuePair( $theArgs, 'editbutton' );
       
   561     $$theHashRef{'editbutton'} = $tmp if ($tmp);
       
   562 
       
   563     return;
       
   564 }
       
   565 
       
   566 =pod
       
   567 
       
   568 =cut
       
   569 
       
   570 sub parseFormat {
       
   571     my ( $theFormat, $theTopic, $theWeb, $doExpand ) = @_;
       
   572 
       
   573     #$theFormat =~ s/\$nop(\(\))?//gos;         # remove filler
       
   574     #$theFormat =~ s/\$quot(\(\))?/\"/gos;      # expand double quote
       
   575     #$theFormat =~ s/\$percnt(\(\))?/\%/gos;    # expand percent
       
   576     #$theFormat =~ s/\$dollar(\(\))?/\$/gos;    # expand dollar
       
   577 
       
   578     if ($doExpand) {
       
   579 
       
   580         # expanded form to be able to use %-vars in format
       
   581         $theFormat =~ s/<nop>//gos;
       
   582         $theFormat =
       
   583           TWiki::Func::expandCommonVariables( $theFormat, $theTopic, $theWeb );
       
   584     }
       
   585     my @aFormat = split( /\s*\|\s*/, $theFormat );
       
   586     $aFormat[0] = "text,$DEFAULT_FIELD_SIZE" unless @aFormat;
       
   587 
       
   588     return @aFormat;
       
   589 }
       
   590 
       
   591 =pod
       
   592 
       
   593 =cut
       
   594 
       
   595 sub handleEditTableTag {
       
   596     my ( $theWeb, $theTopic, $thePreSpace, $theArgs ) = @_;
       
   597 
       
   598     #$preSp = $thePreSpace || '';
       
   599 
       
   600     %params = (
       
   601         'header'        => '',
       
   602         'footer'        => '',
       
   603         'headerislabel' => "1",
       
   604         'format'        => '',
       
   605         'changerows'    => $prefCHANGEROWS,
       
   606         'quietsave'     => $prefQUIETSAVE,
       
   607         'helptopic'     => '',
       
   608         'editbutton'    => '',
       
   609     );
       
   610     $warningMessage = '';
       
   611 
       
   612     # include topic to read definitions
       
   613     my $iTopic = TWiki::Func::extractNameValuePair( $theArgs, 'include' );
       
   614     my $iTopicExists = 0;
       
   615     if ($iTopic) {
       
   616         if ( $iTopic =~ /^([^\.]+)\.(.*)$/o ) {
       
   617             $theWeb = $1;
       
   618             $iTopic = $2;
       
   619         }
       
   620 
       
   621         $iTopicExists = TWiki::Func::topicExists( $theWeb, $iTopic )
       
   622           if $iTopic ne '';
       
   623         if ( $iTopic && !$iTopicExists ) {
       
   624             $warningMessage = $prefMESSAGE_INCLUDED_TOPIC_DOES_NOT_EXIST;
       
   625         }
       
   626         if ($iTopicExists) {
       
   627 
       
   628             my $text = TWiki::Func::readTopicText( $theWeb, $iTopic );
       
   629             $text =~ /$regex{edit_table_plugin}/os;
       
   630             if ($1) {
       
   631                 my $args = $1;
       
   632                 if (   $theWeb ne $TWiki::Plugins::EditTablePlugin::web
       
   633                     || $iTopic ne $TWiki::Plugins::EditTablePlugin::topic )
       
   634                 {
       
   635 
       
   636                     # expand common vars, unless oneself to prevent recursion
       
   637                     $args = TWiki::Func::expandCommonVariables( $1, $iTopic,
       
   638                         $theWeb );
       
   639                 }
       
   640                 extractParams( $args, \%params );
       
   641             }
       
   642         }
       
   643     }
       
   644 
       
   645     extractParams( $theArgs, \%params );
       
   646 
       
   647     # FIXME: should use TWiki::Func::extractParameters
       
   648     $params{'header'} = '' if ( $params{header} =~ /^(off|no)$/oi );
       
   649     $params{'header'} =~ s/^\s*\|//o;
       
   650     $params{'header'} =~ s/\|\s*$//o;
       
   651     $params{'headerislabel'} = ''
       
   652       if ( $params{headerislabel} =~ /^(off|no)$/oi );
       
   653     $params{'footer'} = '' if ( $params{footer} =~ /^(off|no)$/oi );
       
   654     $params{'footer'} =~ s/^\s*\|//o;
       
   655     $params{'footer'} =~ s/\|\s*$//o;
       
   656     $params{'changerows'} = '' if ( $params{changerows} =~ /^(off|no)$/oi );
       
   657     $params{'quietsave'}  = '' if ( $params{quietsave}  =~ /^(off|no)$/oi );
       
   658 
       
   659     @format         = parseFormat( $params{format}, $theTopic, $theWeb, 0 );
       
   660     @formatExpanded = parseFormat( $params{format}, $theTopic, $theWeb, 1 );
       
   661     $nrCols         = @format;
       
   662 
       
   663     return "$preSp";
       
   664 }
       
   665 
       
   666 =pod
       
   667 
       
   668 =cut
       
   669 
       
   670 sub handleTableStart {
       
   671     my ( $theWeb, $theTopic, $theTableNr, $doEdit ) = @_;
       
   672     my $viewUrl = TWiki::Func::getScriptUrl( $theWeb, $theTopic, 'viewauth' )
       
   673       . "\#edittable$theTableNr";
       
   674     my $text = '';
       
   675     if ($doEdit) {
       
   676         require TWiki::Contrib::JSCalendarContrib;
       
   677         unless ($@) {
       
   678             TWiki::Contrib::JSCalendarContrib::addHEAD('twiki');
       
   679         }
       
   680     }
       
   681     $text .= "$preSp<noautolink>\n" if $doEdit;
       
   682     $text .= "$preSp<a name=\"edittable$theTableNr\"></a>\n";
       
   683     my $cssClass = 'editTable';
       
   684     if ($doEdit) {
       
   685         $cssClass .= ' editTableEdit';
       
   686     }
       
   687     $text .= "<div class=\"" . $cssClass . "\">\n";
       
   688     $text .=
       
   689 "$preSp<form name=\"edittable$theTableNr\" action=\"$viewUrl\" method=\"post\">\n";
       
   690     $text .= hiddenField( $preSp, 'ettablenr', $theTableNr, "\n" );
       
   691     $text .= hiddenField( $preSp, 'etedit', 'on', "\n" )
       
   692       unless $doEdit;
       
   693     return $text;
       
   694 }
       
   695 
       
   696 sub hiddenField {
       
   697     my ( $prefix, $name, $value, $suffix ) = @_;
       
   698     $prefix = defined $prefix ? $prefix : '';
       
   699     $suffix = defined $suffix ? $suffix : '';
       
   700     return
       
   701       "$prefix<input type=\"hidden\" name=\"$name\" value=\"$value\" />$suffix";
       
   702 }
       
   703 
       
   704 =pod
       
   705 
       
   706 =cut
       
   707 
       
   708 sub handleTableEnd {
       
   709     my ( $theWeb, $rowCount, $doEdit, $headerRowCount, $footerRowCount,
       
   710         $addedRowCount )
       
   711       = @_;
       
   712     my $text = '';
       
   713     $text .= hiddenField( $preSp, 'etrows',       $rowCount,       "\n" );
       
   714     $text .= hiddenField( $preSp, 'etheaderrows', $headerRowCount, "\n" )
       
   715       if $headerRowCount;
       
   716     $text .= hiddenField( $preSp, 'etfooterrows', $footerRowCount, "\n" )
       
   717       if $footerRowCount;
       
   718     $text .= hiddenField( $preSp, 'etaddedrows', $addedRowCount, "\n" )
       
   719       if $addedRowCount;
       
   720 
       
   721     $text .= hiddenField( $preSp, 'sort', 'off', "\n" );
       
   722 
       
   723     if ($doEdit) {
       
   724 
       
   725         # Edit mode
       
   726         $text .=
       
   727 "$preSp<input type=\"submit\" name=\"etsave\" id=\"etsave\" value=\"$prefSAVE_BUTTON\" class=\"twikiSubmit\" />\n";
       
   728         if ( $params{'quietsave'} ) {
       
   729             $text .=
       
   730 "$preSp<input type=\"submit\" name=\"etqsave\" id=\"etqsave\" value=\"$prefQUIET_SAVE_BUTTON\" class=\"twikiButton\" />\n";
       
   731         }
       
   732         if ( $params{'changerows'} ) {
       
   733             $text .=
       
   734 "$preSp<input type=\"submit\" name=\"etaddrow\" id=\"etaddrow\" value=\"$prefADD_ROW_BUTTON\" class=\"twikiButton\" />\n";
       
   735             $text .=
       
   736 "$preSp<input type=\"submit\" name=\"etdelrow\" id=\"etdelrow\" value=\"$prefDELETE_LAST_ROW_BUTTON\" class=\"twikiButton\" />\n"
       
   737               unless ( $params{'changerows'} =~ /^add$/oi );
       
   738         }
       
   739         $text .=
       
   740 "$preSp<input type=\"submit\" name=\"etcancel\" id=\"etcancel\" value=\"$prefCANCEL_BUTTON\" class=\"twikiButton twikiButtonCancel\" />\n";
       
   741 
       
   742         if ( $params{'helptopic'} ) {
       
   743 
       
   744             # read help topic and show below the table
       
   745             if ( $params{'helptopic'} =~ /^([^\.]+)\.(.*)$/o ) {
       
   746                 $theWeb = $1;
       
   747                 $params{'helptopic'} = $2;
       
   748             }
       
   749             my $helpText =
       
   750               TWiki::Func::readTopicText( $theWeb, $params{'helptopic'} );
       
   751 
       
   752             #Strip out the meta data so it won't be displayed.
       
   753             $helpText =~ s/%META:[A-Za-z0-9]+{.*?}%//g;
       
   754             if ($helpText) {
       
   755                 $helpText =~ s/.*?%STARTINCLUDE%//os;
       
   756                 $helpText =~ s/%STOPINCLUDE%.*//os;
       
   757                 $text .= $helpText;
       
   758             }
       
   759         }
       
   760         my $assetUrl = '%PUBURL%/%TWIKIWEB%/EditTablePlugin';
       
   761 
       
   762         # table specific script
       
   763         my $tableNr = $query->param('ettablenr');
       
   764         &TWiki::Plugins::EditTablePlugin::addEditModeHeadersToHead( $tableNr,
       
   765             $assetUrl );
       
   766     }
       
   767     else {
       
   768         $params{editbutton} |= '';
       
   769 
       
   770         # View mode
       
   771         if ( $params{editbutton} eq "hide" ) {
       
   772 
       
   773             # do nothing, button assumed to be in a cell
       
   774         }
       
   775         else {
       
   776 
       
   777             # Add edit button to end of table
       
   778             $text .=
       
   779               $preSp . viewEditCell("editbutton, 1, $params{'editbutton'}");
       
   780         }
       
   781     }
       
   782     $text .= "$preSp</form>\n";
       
   783     $text .= "</div><!-- /editTable -->";
       
   784     $text .= "$preSp</noautolink>\n" if $doEdit;
       
   785     $text .= "\n";
       
   786     return $text;
       
   787 }
       
   788 
       
   789 =pod
       
   790 
       
   791 =cut
       
   792 
       
   793 sub parseEditCellFormat {
       
   794     $_[1] = TWiki::Func::extractNameValuePair( $_[0] );
       
   795     return '';
       
   796 }
       
   797 
       
   798 =pod
       
   799 
       
   800 =cut
       
   801 
       
   802 sub viewEditCell {
       
   803     my ($theAttr) = @_;
       
   804     $theAttr = TWiki::Func::extractNameValuePair($theAttr);
       
   805     return '' unless ( $theAttr =~ /^editbutton/ );
       
   806 
       
   807     $params{editbutton} = 'hide'
       
   808       unless ( $params{editbutton} );    # Hide below table edit button
       
   809 
       
   810     my @bits = split( /,\s*/, $theAttr );
       
   811     my $value = '';
       
   812     $value = $bits[2] if ( @bits > 2 );
       
   813     my $img = '';
       
   814     $img = $bits[3] if ( @bits > 3 );
       
   815 
       
   816     unless ($value) {
       
   817         $value = $prefEDIT_BUTTON || '';
       
   818         $img = '';
       
   819         if ( $value =~ s/(.+),\s*(.+)/$1/o ) {
       
   820             $img = $2;
       
   821             $img =~ s|%ATTACHURL%|%PUBURL%/%TWIKIWEB%/EditTablePlugin|o;
       
   822             $img =~ s|%WEB%|%TWIKIWEB%|o;
       
   823         }
       
   824     }
       
   825     if ($img) {
       
   826         return
       
   827 "<input class=\"editTableEditImageButton\" type=\"image\" src=\"$img\" alt=\"$value\" /> $warningMessage";
       
   828     }
       
   829     else {
       
   830         return
       
   831 "<input class=\"twikiButton editTableEditButton\" type=\"submit\" value=\"$value\" /> $warningMessage";
       
   832     }
       
   833 }
       
   834 
       
   835 =pod
       
   836 
       
   837 =cut
       
   838 
       
   839 sub saveEditCellFormat {
       
   840     my ( $theFormat, $theName ) = @_;
       
   841     return '' unless ($theFormat);
       
   842     $theName =~ s/cell/format/;
       
   843     return hiddenField( '', $theName, $theFormat, '' );
       
   844 }
       
   845 
       
   846 =pod
       
   847 
       
   848 digestedCellValue: properly handle labels whose rows may have been moved around by javascript, and therefore no longer correspond to the raw saved table text.
       
   849 
       
   850 =cut
       
   851 
       
   852 sub inputElement {
       
   853     my ( $theTableNr, $theRowNr, $theCol, $theName, $theValue,
       
   854         $digestedCellValue, $theWeb, $theTopic )
       
   855       = @_;
       
   856 
       
   857     my $rawValue = $theValue;
       
   858     my $text     = '';
       
   859     my $i        = @format - 1;
       
   860     $i = $theCol if ( $theCol < $i );
       
   861 
       
   862     my @bits         = split( /,\s*/, $format[$i] );
       
   863     my @bitsExpanded = split( /,\s*/, $formatExpanded[$i] );
       
   864 
       
   865     my $cellFormat = '';
       
   866     $theValue =~
       
   867       s/\s*%EDITCELL{(.*?)}%/&parseEditCellFormat( $1, $cellFormat )/eo;
       
   868 
       
   869     # If cell is empty we remove the space to not annoy the user when
       
   870     # he needs to add text to empty cell.
       
   871     $theValue = '' if ( $theValue eq ' ' );
       
   872     
       
   873     if ($cellFormat) {
       
   874         my @aFormat = parseFormat( $cellFormat, $theTopic, $theWeb, 0 );
       
   875         @bits = split( /,\s*/, $aFormat[0] );
       
   876         @aFormat = parseFormat( $cellFormat, $theTopic, $theWeb, 1 );
       
   877         @bitsExpanded = split( /,\s*/, $aFormat[0] );
       
   878     }
       
   879 
       
   880     my $type = 'text';
       
   881     $type = $bits[0] if @bits > 0;
       
   882 
       
   883     # a table header is considered a label if read only header flag set
       
   884     $type = 'label'
       
   885       if ( ( $params{'headerislabel'} ) && ( $theValue =~ /^\s*\*.*\*\s*$/ ) );
       
   886     $type = 'label' if ( $type eq 'editbutton' );    # Hide [Edit table] button
       
   887     my $size = 0;
       
   888     $size = $bits[1] if @bits > 1;
       
   889     my $val         = '';
       
   890     my $valExpanded = '';
       
   891     my $sel         = '';
       
   892     if ( $type eq 'select' ) {
       
   893         my $expandedValue =
       
   894           TWiki::Func::expandCommonVariables( $theValue, $theTopic, $theWeb );
       
   895         $size = 1 if $size < 1;
       
   896         $text =
       
   897           "<select class=\"twikiSelect\" name=\"$theName\" size=\"$size\">";
       
   898         $i = 2;
       
   899         while ( $i < @bits ) {
       
   900             $val         = $bits[$i]         || '';
       
   901             $valExpanded = $bitsExpanded[$i] || '';
       
   902             $expandedValue =~ s/^\s+//;
       
   903             $expandedValue =~ s/\s+$//;
       
   904             $valExpanded   =~ s/^\s+//;
       
   905             $valExpanded   =~ s/\s+$//;
       
   906 
       
   907             if ( $valExpanded eq $expandedValue ) {
       
   908                 $text .= " <option selected=\"selected\">$val</option>";
       
   909             }
       
   910             else {
       
   911                 $text .= " <option>$val</option>";
       
   912             }
       
   913             $i++;
       
   914         }
       
   915         $text .= "</select>";
       
   916         $text .= saveEditCellFormat( $cellFormat, $theName );
       
   917 
       
   918     }
       
   919     elsif ( $type eq "radio" ) {
       
   920         my $expandedValue =
       
   921           &TWiki::Func::expandCommonVariables( $theValue, $theTopic, $theWeb );
       
   922         $size = 1 if $size < 1;
       
   923         my $elements = ( @bits - 2 );
       
   924         my $lines    = $elements / $size;
       
   925         $lines = ( $lines == int($lines) ) ? $lines : int( $lines + 1 );
       
   926         $text .= "<table class=\"editTableInnerTable\"><tr><td valign=\"top\">"
       
   927           if ( $lines > 1 );
       
   928         $i = 2;
       
   929         while ( $i < @bits ) {
       
   930             $val         = $bits[$i]         || "";
       
   931             $valExpanded = $bitsExpanded[$i] || "";
       
   932             $expandedValue =~ s/^\s+//;
       
   933             $expandedValue =~ s/\s+$//;
       
   934             $valExpanded   =~ s/^\s+//;
       
   935             $valExpanded   =~ s/\s+$//;
       
   936             $text .= "<input type=\"radio\" name=\"$theName\" value=\"$val\"";
       
   937 
       
   938             # make space to expand variables
       
   939             $val = addSpaceToBothSides($val);
       
   940             $text .= " checked=\"checked\""
       
   941               if ( $valExpanded eq $expandedValue );
       
   942             $text .= " />$val";
       
   943             if ( $lines > 1 ) {
       
   944 
       
   945                 if ( ( $i - 1 ) % $lines ) {
       
   946                     $text .= "<br />";
       
   947                 }
       
   948                 elsif ( $i - 1 < $elements ) {
       
   949                     $text .= "</td><td valign=\"top\">";
       
   950                 }
       
   951             }
       
   952             $i++;
       
   953         }
       
   954         $text .= "</td></tr></table>" if ( $lines > 1 );
       
   955         $text .= saveEditCellFormat( $cellFormat, $theName );
       
   956 
       
   957     }
       
   958     elsif ( $type eq "checkbox" ) {
       
   959         my $expandedValue =
       
   960           &TWiki::Func::expandCommonVariables( $theValue, $theTopic, $theWeb );
       
   961         $size = 1 if $size < 1;
       
   962         my $elements = ( @bits - 2 );
       
   963         my $lines    = $elements / $size;
       
   964         my $names    = "Chkbx:";
       
   965         $lines = ( $lines == int($lines) ) ? $lines : int( $lines + 1 );
       
   966         $text .= "<table class=\"editTableInnerTable\"><tr><td valign=\"top\">"
       
   967           if ( $lines > 1 );
       
   968         $i = 2;
       
   969 
       
   970         while ( $i < @bits ) {
       
   971             $val         = $bits[$i]         || "";
       
   972             $valExpanded = $bitsExpanded[$i] || "";
       
   973             $expandedValue =~ s/^\s+//;
       
   974             $expandedValue =~ s/\s+$//;
       
   975             $valExpanded   =~ s/^\s+//;
       
   976             $valExpanded   =~ s/\s+$//;
       
   977             $names .= " ${theName}x$i";
       
   978             $text .=
       
   979               " <input type=\"checkbox\" name=\"${theName}x$i\" value=\"$val\"";
       
   980 
       
   981             $val = addSpaceToBothSides($val);
       
   982 
       
   983             $text .= " checked=\"checked\""
       
   984               if ( $expandedValue =~ /(^|\s*,\s*)\Q$valExpanded\E(\s*,\s*|$)/ );
       
   985             $text .= " />$val";
       
   986 
       
   987             if ( $lines > 1 ) {
       
   988                 if ( ( $i - 1 ) % $lines ) {
       
   989                     $text .= "<br />";
       
   990                 }
       
   991                 elsif ( $i - 1 < $elements ) {
       
   992                     $text .= "</td><td valign=\"top\">";
       
   993                 }
       
   994             }
       
   995             $i++;
       
   996         }
       
   997         $text .= "</td></tr></table>" if ( $lines > 1 );
       
   998         $text .= hiddenField( $preSp, $theName, $names );
       
   999         $text .= saveEditCellFormat( $cellFormat, $theName, "\n" );
       
  1000 
       
  1001     }
       
  1002     elsif ( $type eq 'row' ) {
       
  1003         $size = $size + $theRowNr;
       
  1004         $text =
       
  1005             "<span class=\"et_rowlabel\">"
       
  1006           . hiddenField( $size, $theName, $size )
       
  1007           . "</span>";
       
  1008         $text .= saveEditCellFormat( $cellFormat, $theName );
       
  1009 
       
  1010     }
       
  1011     elsif ( $type eq 'label' ) {
       
  1012 
       
  1013         # show label text as is, and add a hidden field with value
       
  1014         my $isHeader = 0;
       
  1015         $isHeader = 1 if ( $theValue =~ s/^\s*\*(.*)\*\s*$/$1/o );
       
  1016         $text = $theValue;
       
  1017 
       
  1018         # To optimize things, only in the case where a read-only column is
       
  1019         # being processed (inside of this unless() statement) do we actually
       
  1020         # go out and read the original topic.  Thus the reason for the
       
  1021         # following unless() so we only read the topic the first time through.
       
  1022         unless ( defined $table and $digestedCellValue ) {
       
  1023 
       
  1024             # To deal with the situation where TWiki variables, like
       
  1025             # %CALC%, have already been processed and end up getting saved
       
  1026             # in the table that way (processed), we need to read in the
       
  1027             # topic page in raw format
       
  1028             my $topicContents = TWiki::Func::readTopicText(
       
  1029                 $TWiki::Plugins::EditTablePlugin::web,
       
  1030                 $TWiki::Plugins::EditTablePlugin::topic
       
  1031             );
       
  1032             $table = TWiki::Plugins::Table->new($topicContents);
       
  1033         }
       
  1034         my $cell =
       
  1035             $digestedCellValue
       
  1036           ? $table->getCell( $theTableNr, $theRowNr - 1, $theCol )
       
  1037           : $rawValue;
       
  1038         $theValue = $cell if ( defined $cell );    # original value from file
       
  1039         $theValue = TWiki::Plugins::EditTablePlugin::encodeValue($theValue)
       
  1040           unless ( $theValue eq '' );
       
  1041         $theValue = "\*$theValue\*" if ( $isHeader and $digestedCellValue );
       
  1042         $text .= hiddenField( $preSp, $theName, $theValue );
       
  1043         $text = "\*$text\*" if ($isHeader);
       
  1044 
       
  1045     }
       
  1046     elsif ( $type eq 'textarea' ) {
       
  1047         my ( $rows, $cols ) = split( /x/, $size );
       
  1048 
       
  1049         $rows |= 3  if !defined $rows;
       
  1050         $cols |= 30 if !defined $cols;
       
  1051         $theValue = TWiki::Plugins::EditTablePlugin::encodeValue($theValue)
       
  1052           unless ( $theValue eq '' );
       
  1053         $text .=
       
  1054 "<textarea class=\"twikiTextarea editTableTextarea\" rows=\"$rows\" cols=\"$cols\" name=\"$theName\">$theValue</textarea>";
       
  1055         $text .= saveEditCellFormat( $cellFormat, $theName );
       
  1056 
       
  1057     }
       
  1058     elsif ( $type eq 'date' ) {
       
  1059         my $ifFormat = '';
       
  1060         $ifFormat = $bits[3] if ( @bits > 3 );
       
  1061         $ifFormat ||= $TWiki::cfg{JSCalendarContrib}{format} || '%e %B %Y';
       
  1062         $size = 10 if ( !$size || $size < 1 );
       
  1063         $theValue = TWiki::Plugins::EditTablePlugin::encodeValue($theValue)
       
  1064           unless ( $theValue eq '' );
       
  1065         $text .= CGI::textfield(
       
  1066             {
       
  1067                 name     => $theName,
       
  1068                 class    => 'twikiInputField editTableInput',
       
  1069                 id       => 'id' . $theName,
       
  1070                 size     => $size,
       
  1071                 value    => $theValue,
       
  1072                 override => 1
       
  1073             }
       
  1074         );
       
  1075         $text .= saveEditCellFormat( $cellFormat, $theName );
       
  1076         eval 'use TWiki::Contrib::JSCalendarContrib';
       
  1077 
       
  1078         unless ($@) {
       
  1079             $text .= '<span class="twikiMakeVisible">';
       
  1080             $text .= CGI::image_button(
       
  1081                 -class   => 'editTableCalendarButton',
       
  1082                 -name    => 'calendar',
       
  1083                 -onclick => "return showCalendar('id$theName','$ifFormat')",
       
  1084                 -src     => TWiki::Func::getPubUrlPath() . '/'
       
  1085                   . TWiki::Func::getTwikiWebname()
       
  1086                   . '/JSCalendarContrib/img.gif',
       
  1087                 -alt   => 'Calendar',
       
  1088                 -align => 'middle'
       
  1089             );
       
  1090             $text .= '</span>';
       
  1091         }
       
  1092         $query->{'jscalendar'} = 1;
       
  1093     }
       
  1094     else {    #  if( $type eq 'text')
       
  1095         $size = $DEFAULT_FIELD_SIZE if $size < 1;
       
  1096         $theValue = TWiki::Plugins::EditTablePlugin::encodeValue($theValue)
       
  1097           unless ( $theValue eq '' );
       
  1098         $text =
       
  1099 "<input class=\"twikiInputField editTableInput\" type=\"text\" name=\"$theName\" size=\"$size\" value=\"$theValue\" />";
       
  1100         $text .= saveEditCellFormat( $cellFormat, $theName );
       
  1101     }
       
  1102 
       
  1103     if ( $type ne 'textarea' ) {
       
  1104         $text =~
       
  1105           s/&#10;/<br \/>/go;    # change unicode linebreak character to <br />
       
  1106     }
       
  1107     return $text;
       
  1108 }
       
  1109 
       
  1110 =pod
       
  1111 
       
  1112 =cut
       
  1113 
       
  1114 sub handleTableRow {
       
  1115     my (
       
  1116         $thePre, $theRow, $theTableNr, $isNewRow, $theRowNr,
       
  1117         $doEdit, $doSave, $theWeb,     $theTopic
       
  1118     ) = @_;
       
  1119     $thePre |= '';
       
  1120     my $text = "$thePre\|";
       
  1121     if ($doEdit) {
       
  1122         $theRow =~ s/\|\s*$//o;
       
  1123         my $rowID = $query->param("etrow_id$theRowNr");
       
  1124         $rowID = $theRowNr if !defined $rowID;
       
  1125         my @cells = split( /\|/, $theRow );
       
  1126         my $tmp = @cells;
       
  1127         $nrCols = $tmp if ( $tmp > $nrCols );    # expand number of cols
       
  1128         my $val         = '';
       
  1129         my $cellFormat  = '';
       
  1130         my $cell        = '';
       
  1131         my $digested    = 0;
       
  1132         my $cellDefined = 0;
       
  1133         my $col         = 0;
       
  1134 
       
  1135         while ( $col < $nrCols ) {
       
  1136             $col += 1;
       
  1137             $cellDefined = 0;
       
  1138             $val = $isNewRow ? undef : $query->param("etcell${rowID}x$col");
       
  1139             if ( $val && $val =~ /^Chkbx: (etcell.*)/ ) {
       
  1140 
       
  1141       # Multiple checkboxes, val has format "Chkbx: etcell4x2x2 etcell4x2x3 ..."
       
  1142                 my $chkBoxeNames = $1;
       
  1143                 my $chkBoxVals   = "";
       
  1144                 foreach ( split( /\s/, $chkBoxeNames ) ) {
       
  1145                     $val = $query->param($_);
       
  1146 
       
  1147                     #$chkBoxVals .= "$val," if ( defined $val );
       
  1148                     if ( defined $val ) {
       
  1149 
       
  1150                         # make space to expand variables
       
  1151                         $val = addSpaceToBothSides($val);
       
  1152                         $chkBoxVals .= $val . ',';
       
  1153                     }
       
  1154                 }
       
  1155                 $chkBoxVals =~ s/,\s*$//;
       
  1156                 $val = $chkBoxVals;
       
  1157             }
       
  1158             $cellFormat = $query->param("etformat${rowID}x$col");
       
  1159             $val .= " %EDITCELL{$cellFormat}%" if ($cellFormat);
       
  1160             if ( defined $val ) {
       
  1161 
       
  1162                 # change any new line character sequences to <br />
       
  1163                 $val =~ s/[\n\r]{2,}?/%BR%/gos;
       
  1164 
       
  1165                 # escape "|" to HTML entity
       
  1166                 $val =~ s/\|/\&\#124;/gos;
       
  1167                 $cellDefined = 1;
       
  1168 
       
  1169                 # Expand %-vars
       
  1170                 $cell = $val;
       
  1171             }
       
  1172             elsif ( $col <= @cells ) {
       
  1173                 $cell = $cells[ $col - 1 ];
       
  1174                 $digested = 1;    # Flag that we are using non-raw cell text.
       
  1175                 $cellDefined = 1 if ( length($cell) > 0 );
       
  1176                 $cell =~ s/^\s*(.+?)\s*$/$1/o
       
  1177                   ; # remove spaces around content, but do not void a cell with just spaces
       
  1178             }
       
  1179             else {
       
  1180                 $cell = '';
       
  1181             }
       
  1182 
       
  1183             if ( ( $theRowNr <= 1 ) && ( $params{'header'} ) ) {
       
  1184                 unless ($cell) {
       
  1185                     if ( $params{'header'} =~ /^on$/i ) {
       
  1186                         if (   ( @format >= $col )
       
  1187                             && ( $format[ $col - 1 ] =~ /(.*?)\,/ ) )
       
  1188                         {
       
  1189                             $cell = $1;
       
  1190                         }
       
  1191                         $cell = 'text' unless $cell;
       
  1192                         $cell = "*$cell*";
       
  1193                     }
       
  1194                     else {
       
  1195                         my @hCells = split( /\|/, $params{'header'} );
       
  1196                         $cell = $hCells[ $col - 1 ] if ( @hCells >= $col );
       
  1197                         $cell = "*text*" unless $cell;
       
  1198                     }
       
  1199                 }
       
  1200                 $cell = addSpaceToBothSides($cell);
       
  1201                 $text .= "$cell\|";
       
  1202             }
       
  1203             elsif ($doSave) {
       
  1204                 $cell = addSpaceToBothSides($cell);
       
  1205                 
       
  1206                 # Item5217 Avoid that deleting content of cell creates unwanted span
       
  1207                 $cell = ' ' if $cell eq '';
       
  1208                 
       
  1209                 $text .= "$cell\|";
       
  1210             }
       
  1211             else {
       
  1212                 if (
       
  1213                        ( !$cellDefined )
       
  1214                     && ( @format >= $col )
       
  1215                     && ( $format[ $col - 1 ] =~
       
  1216                         /^\s*(.*?)\,\s*(.*?)\,\s*(.*?)\s*$/ )
       
  1217                   )
       
  1218                 {
       
  1219 
       
  1220                     # default value of "| text, 20, a, b, c |" cell is "a, b, c"
       
  1221                     # default value of '| select, 1, a, b, c |' cell is "a"
       
  1222                     $val  = $1;    # type
       
  1223                     $cell = $3;
       
  1224                     $cell = ''
       
  1225                       unless ( defined $cell && $cell ne '' )
       
  1226                       ;            # Proper handling of '0'
       
  1227                     $cell =~ s/\,.*$//o
       
  1228                       if ( $val eq 'select' || $val eq 'date' );
       
  1229                 }
       
  1230                 my $element = '';
       
  1231                 $element =
       
  1232                   inputElement( $theTableNr, $theRowNr, $col - 1,
       
  1233                     "etcell${theRowNr}x$col", $cell, $digested, $theWeb,
       
  1234                     $theTopic );
       
  1235                 $element = " $element \|";
       
  1236                 $text .= $element;
       
  1237             }
       
  1238         }
       
  1239     }
       
  1240     else {
       
  1241         $theRow =~ s/%EDITCELL{(.*?)}%/viewEditCell($1)/geo;
       
  1242         $text .= $theRow;
       
  1243     }
       
  1244 
       
  1245     # render final value in view mode (not edit or save)
       
  1246     TWiki::Plugins::EditTablePlugin::decodeFormatTokens($text)
       
  1247       if ( !$doSave && !$doEdit );
       
  1248 
       
  1249     # put one space before linebreak (but not more than one)
       
  1250     # so TML can get expanded
       
  1251     $text =~ s/\s*<br \/>/ <br \/>/go;
       
  1252     return $text;
       
  1253 }
       
  1254 
       
  1255 =pod
       
  1256 
       
  1257 Add one space to both sides of the text to allow TML expansion.
       
  1258 Convert multiple (existing) spaces to one space.
       
  1259 
       
  1260 =cut
       
  1261 
       
  1262 sub addSpaceToBothSides {
       
  1263     my ($text) = @_;
       
  1264     return $text if $text eq '';
       
  1265 
       
  1266     $text = " $text ";
       
  1267     $text =~ s/^\s+/ /;    # remove extra spaces
       
  1268     $text =~ s/\s+$/ /;
       
  1269     return $text;
       
  1270 }
       
  1271 
       
  1272 =pod
       
  1273 
       
  1274 =cut
       
  1275 
       
  1276 sub doCancelEdit {
       
  1277     my ( $theWeb, $theTopic ) = @_;
       
  1278 
       
  1279     TWiki::Func::writeDebug(
       
  1280         "- EditTablePlugin::doCancelEdit( $theWeb, $theTopic )")
       
  1281       if $TWiki::Plugins::EditTablePlugin::debug;
       
  1282 
       
  1283     TWiki::Func::setTopicEditLock( $theWeb, $theTopic, 0 );
       
  1284 
       
  1285     TWiki::Func::redirectCgiQuery( $query,
       
  1286         TWiki::Func::getViewUrl( $theWeb, $theTopic ) );
       
  1287 }
       
  1288 
       
  1289 =pod
       
  1290 
       
  1291 =cut
       
  1292 
       
  1293 sub doEnableEdit {
       
  1294     my ( $theWeb, $theTopic, $doCheckIfLocked ) = @_;
       
  1295 
       
  1296     TWiki::Func::writeDebug(
       
  1297         "- EditTablePlugin::doEnableEdit( $theWeb, $theTopic )")
       
  1298       if $TWiki::Plugins::EditTablePlugin::debug;
       
  1299 
       
  1300     my $wikiUserName = TWiki::Func::getWikiName();
       
  1301     if (
       
  1302         !TWiki::Func::checkAccessPermission(
       
  1303             'change', $wikiUserName, undef, $theTopic, $theWeb
       
  1304         )
       
  1305       )
       
  1306     {
       
  1307 
       
  1308         # user has no permission to change the topic
       
  1309         throw TWiki::OopsException(
       
  1310             'accessdenied',
       
  1311             def    => 'topic_access',
       
  1312             web    => $theWeb,
       
  1313             topic  => $theTopic,
       
  1314             params => [ 'change', 'denied' ]
       
  1315         );
       
  1316     }
       
  1317 
       
  1318     my $breakLock = $query->param('breaklock') || '';
       
  1319     unless ($breakLock) {
       
  1320         my ( $oopsUrl, $lockUser ) =
       
  1321           TWiki::Func::checkTopicEditLock( $theWeb, $theTopic, 'view' );
       
  1322         if ($oopsUrl) {
       
  1323             my $loginUser = TWiki::Func::wikiToUserName($wikiUserName);
       
  1324             if ( $lockUser ne $loginUser ) {
       
  1325 
       
  1326                 # change the default oopsleaseconflict url
       
  1327                 # use viewauth instead of view
       
  1328                 $oopsUrl =~ s/param4=view/param4=viewauth/;
       
  1329 
       
  1330                 # add info of the edited table
       
  1331                 my $params = '';
       
  1332                 $query = TWiki::Func::getCgiQuery();
       
  1333                 $params .= ';ettablenr=' . $query->param('ettablenr');
       
  1334                 $params .= ';etedit=on';
       
  1335                 $oopsUrl =~ s/($|#\w*)/$params/;
       
  1336 
       
  1337                 # warn user that other person is editing this topic
       
  1338                 TWiki::Func::redirectCgiQuery( $query, $oopsUrl );
       
  1339                 return 0;
       
  1340             }
       
  1341         }
       
  1342     }
       
  1343 
       
  1344     # We are allowed to edit
       
  1345     TWiki::Func::setTopicEditLock( $theWeb, $theTopic, 1 );
       
  1346 
       
  1347     return 1;
       
  1348 }
       
  1349 
       
  1350 package TWiki::Plugins::Table;
       
  1351 
       
  1352 use vars qw(
       
  1353   %regex
       
  1354 );
       
  1355 $regex{edit_table_plugin} = '%EDITTABLE{(.*?)}%';
       
  1356 
       
  1357 =pod
       
  1358 
       
  1359 =cut
       
  1360 
       
  1361 sub new {
       
  1362     my ( $class, $topicContents ) = @_;
       
  1363     my $this = {};
       
  1364     bless $this, $class;
       
  1365     $this->_parseOutTables($topicContents);
       
  1366     return $this;
       
  1367 }
       
  1368 
       
  1369 =pod
       
  1370 
       
  1371 TODO: this is currently only used for label tags, so this seams a lot of overhead for such a small thing
       
  1372 
       
  1373 The guts of this routine was initially copied from SpreadSheetPlugin.pm
       
  1374 and were used in the ChartPlugin Table object which this was copied from,
       
  1375 but this has been modified to support the functionality needed by the
       
  1376 EditTablePlugin.  One major change is to only count and save tables
       
  1377 following an %EDITTABLE{.*}% tag.
       
  1378 
       
  1379 This routine basically returns an array of hashes where each hash
       
  1380 contains the information for a single table.  Thus the first hash in the
       
  1381 array represents the first table found on the topic page, the second hash
       
  1382 in the array represents the second table found on the topic page, etc.
       
  1383 
       
  1384 =cut
       
  1385 
       
  1386 sub _parseOutTables {
       
  1387     my ( $this, $topic ) = @_;
       
  1388     my $tableNum = 1;    # Table number (only count tables with EDITTABLE tag)
       
  1389     my @tableMatrix;     # Currently parsed table.
       
  1390 
       
  1391     my $inEditTable = 0; # Flag to keep track if in an EDITTABLE table
       
  1392     my $insidePRE   = 0;
       
  1393     my $insideTABLE = 0;
       
  1394     my $line        = '';
       
  1395     my @row         = ();
       
  1396 
       
  1397     foreach ( split( /\n/, $topic ) ) {
       
  1398 
       
  1399         # change state:
       
  1400         m|<pre\b|i      && ( $insidePRE = 1 );
       
  1401         m|<verbatim\b|i && ( $insidePRE = 1 );
       
  1402         m|</pre>|i      && ( $insidePRE = 0 );
       
  1403         m|</verbatim>|i && ( $insidePRE = 0 );
       
  1404 
       
  1405         if ( !$insidePRE ) {
       
  1406             $inEditTable = 1 if (/$regex{edit_table_plugin}/);
       
  1407             if ($inEditTable) {
       
  1408                 if (/^\s*\|.*\|\s*$/) {
       
  1409 
       
  1410                     # inside | table |
       
  1411                     $insideTABLE = 1;
       
  1412                     $line        = $_;
       
  1413                     $line =~ s/^(\s*\|)(.*)\|\s*$/$2/o;    # Remove starting '|'
       
  1414                     @row = split( /\|/o, $line, -1 );
       
  1415                     _trim( \@row );
       
  1416                     push( @tableMatrix, [@row] );
       
  1417 
       
  1418                 }
       
  1419                 else {
       
  1420 
       
  1421                     # outside | table |
       
  1422                     if ($insideTABLE) {
       
  1423 
       
  1424                         # We were inside a table and are now outside of it so
       
  1425                         # save the table info into the Table object.
       
  1426                         $insideTABLE = 0;
       
  1427                         $inEditTable = 0;
       
  1428                         if ( @tableMatrix != 0 ) {
       
  1429 
       
  1430                             # Save the table via its table number
       
  1431                             $$this{"TABLE_$tableNum"} = [@tableMatrix];
       
  1432                             $tableNum++;
       
  1433                         }
       
  1434                         undef @tableMatrix;    # reset table matrix
       
  1435                     }
       
  1436                 }
       
  1437             }
       
  1438         }
       
  1439     }
       
  1440     $$this{NUM_TABLES} = $tableNum;
       
  1441 }
       
  1442 
       
  1443 =pod
       
  1444 
       
  1445 Trim any leading and trailing white space and/or '*'.
       
  1446 
       
  1447 =cut
       
  1448 
       
  1449 sub _trim {
       
  1450     my ($totrim) = @_;
       
  1451     for my $element (@$totrim) {
       
  1452         $element =~ s/^[\s\*]+//;    # Strip off leading white
       
  1453         $element =~ s/[\s\*]+$//;    # Strip off trailing white
       
  1454     }
       
  1455 }
       
  1456 
       
  1457 =pod
       
  1458 
       
  1459 Return the contents of the specified cell
       
  1460 
       
  1461 =cut
       
  1462 
       
  1463 sub getCell {
       
  1464     my ( $this, $tableNum, $row, $column ) = @_;
       
  1465 
       
  1466     my @selectedTable = $this->getTable($tableNum);
       
  1467     my $value         = $selectedTable[$row][$column];
       
  1468     return $value;
       
  1469 }
       
  1470 
       
  1471 =pod
       
  1472 
       
  1473 =cut
       
  1474 
       
  1475 sub getTable {
       
  1476     my ( $this, $tableNumber ) = @_;
       
  1477     my $table = $$this{"TABLE_$tableNumber"};
       
  1478     return @$table if defined($table);
       
  1479     return ();
       
  1480 }
       
  1481 
       
  1482 1;