1#
2# WKC.pl -- Main package wikiCalc
3#
4# (c) Copyright 2005, 2006, 2007 Software Garden, Inc.
5# All Rights Reserved.
6# Subject to Software License at the end of this file
7#
8
9#
10
11#
12# Define Package
13#
14
15   package WKC;
16
17#
18# Do uses
19#
20
21   use strict;
22   use CGI qw(:standard);
23   use utf8;
24   use LWP::UserAgent;
25
26   use WKCStrings;
27   use WKCSheet;
28   use WKCSheetFunctions;
29   use WKCPageCommands;
30   use WKCDataFiles;
31   use WKCFormatCommands;
32   use WKCToolsCommands;
33
34#
35# Export symbols
36#
37
38   require Exporter;
39   our @ISA = qw(Exporter);
40   our @EXPORT = qw(process_request update_hiddenfields site_not_allowed find_in_sheet_cache
41                    %config_values $programname);
42   our $VERSION = '1.0.0';
43
44#
45# Define some variables
46#
47
48   #
49   # Public ones:
50   #
51
52   our %config_values = (socket => 6556);
53
54      #
55      # Here are the program name strings for display when executing. See the Note below.
56      #
57
58   our $programversion = "1.0"; # The version information string
59   our $programmark = "wikiCalc"; # This is the main trademark for the product. Change if required by trademark law.
60   our $programmarksymbol = qq!<span style="font-weight:normal;vertical-align:super;">&reg;</span>!; # The HTML for the trademark symbol for $programmark
61   our $programname = "$programmark $programversion"; # Change name if required by trademark usage. This is where the version is indicated.
62   our $trademark1 = qq!, a Software Garden<span style="font-weight:normal;vertical-align:super;">&reg;</span> product!; # Remove if required by trademark usage (also: \xC2\xAE as UTF-8)
63   our $SGIfootertext = <<"EOF";
64wikiCalc Program (c) Copyright 2007 Software Garden, Inc.
65<br>All Rights Reserved.
66<br>Garden, Software Garden, and wikiCalc are registered trademarks or trademarks
67<br>of Software Garden, Inc., in the United States and in other countries.
68<br>The original version of this program is from <a href="http://www.softwaregarden.com">Software Garden</a>.
69EOF
70
71      # *** Note: ***
72      #
73      # In order to carry a prominent notice stating that you changed the files when distributing
74      # works based on this program, you may use the $WKCStrings values "programextratop" and "footerextratext".
75      #
76      # An example of a "programextratop" value would be "Modified" which would result in
77      # a displayed banner of "Modified wikiCalc..." indicating that this is
78      # a modification to the original version to which the trademark applied.
79      # Another example would be "Foobar&nbsp;1.5&nbsp;modification&nbsp;of&nbsp;" which would
80      # result in a displayed banner of "Foobar 1.5 modification of wikiCalc...".
81      # (Use of "Foobar 1.5 " without the words "modification of" is a trademark violation.)
82      # If you make any modifications to the product you should set programextratop appropriately
83      # since it is no longer the Software Garden product alone.
84      #
85      # An example of a "footerextratext" value would be "<br><br>Modification to original:<br>Localisation to UK English (c) 2007 ABC Localizers Ltd."
86      #
87      # In both cases, you would also put comments in any source files that were modified indicating
88      # the author, date, and nature of the changes.
89
90   #
91   # Private ones:
92   #
93
94   my $securitycode = "not set!"; # Requests may need a parameter that matches this
95
96   my %initial_datavalues = (     # Used to initialize %datavalues
97         );
98
99   my $defaultcookieexpire = "+6M"; # how long do cookies (remembering page being edited and maybe login info) last by default?
100
101   my $template_headertop = <<"EOF";
102<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
103<html>
104<head>
105<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
106<title>$programname</title>
107EOF
108
109   my $template_styletop = <<"EOF";
110<style type="text/css">
111body, td, input, textarea {font-family:verdana,helvetica,sans-serif;font-size:small;}
112.smaller {font-size: smaller;}
113form {
114  margin:0px;
115  padding:0px;
116}
117dd {
118  padding: 1pt 0px 2pt 0px;
119}
120dt {
121  font-weight: bold;
122  padding: 1pt 0px 1pt 0px;
123}
124.programtop {
125  margin-bottom: 10px;
126  }
127.programtopdark {
128  color: #006600;
129  font-size: smaller;
130  font-weight: bold;
131}
132.programtoplight {
133  color: #999999;
134  font-size: smaller;
135}
136.programtoplogout, .programtoplogout a {
137  color: #999999;
138  font-weight: bold;
139  font-size: smaller;
140  text-decoration: none;
141}
142.programtoplogout a:hover {
143  text-decoration: underline;
144}
145.programtopname {
146  color: #006600;
147  font-weight: bold;
148  font-size: smaller;
149  border: 1px solid #006600;
150  padding: 4px;
151}
152.programtoprevert {
153  color: #CC0000;
154  font-size: smaller;
155  font-weight: bold;
156}
157.tab {
158  border-bottom: 1px solid black;
159  padding-bottom: 4px;
160  }
161.tab input {
162  background-color:#CCCC99;
163  color:black;
164}
165.tabselected {
166  border-left: 1px solid black;
167  border-right: 1px solid black;
168  border-top: 1px solid black;
169  background-color:#DDFFDD;
170  color:black;
171  padding: 1px 14px 0px 14px;
172  text-align: center;
173  font-weight: bold;
174  }
175.tab1 {
176  border-bottom: 1px solid black;
177  }
178.tab2 {
179  background-color:#DDFFDD;
180  }
181.tab2left {
182  border-left: 1px solid black;
183  background-color:#DDFFDD;
184  padding-left:20px;
185  }
186.tab2right {
187  border-right: 1px solid black;
188  background-color:#DDFFDD;
189  }
190.ttbody {
191  background-color:#DDFFDD;
192  border-right: 1px solid black;
193  border-left: 1px solid black;
194  border-bottom: 1px solid black;
195  padding: 0px 10px 4px 10px;
196}
197EOF
198
199   my $template_stylemiddle_verbose = <<"EOF";
200.sectionoutlined {
201  border: 1px solid #99CC99;
202  padding: 8px;
203}
204.sectionoutlineddark {
205  border-left: 20px solid #99CC99;
206  padding: 8px 8px 0px 8px;
207  margin-bottom: 8px; /* IE bug work-around with padding-bottom and border-left (?!!) */
208}
209.sectionplain {
210  padding: 10px;
211}
212.sectionerror {
213  padding: 10px;
214  color: red;
215  font-weight: bold;
216}
217.title {
218  color: #006600;
219  font-weight:bold;
220  margin: 0em 0px 2pt 0px;
221  padding: 0px 0px 0px 0px;
222}
223.title2 {
224  border-top: 1px solid #006600;
225  color: #006600;
226  font-weight:bold;
227  margin: .5em 0px 0px 0px;
228  padding: 0px 0px 0px 0px;
229}
230.pagetitle {
231  color: #006600;
232  font-weight:bold;
233  margin: 0em 0px 0px 0px;
234  padding: 0px 0px 0px 0px;
235}
236.pagebreadcrumbs {
237  color: #006600;
238  font-weight:bold;
239  font-size:smaller;
240  margin: 0em 0px 6pt 0px;
241  padding: 0px 0px 0px 0px;
242}
243.pagefilename {
244  color: #006600;
245  font-size: smaller;
246  font-weight: bold;
247  margin: 1pt 0px 2pt 0px;
248  padding: 0px 0px 0px 0px;
249}
250.pagetitledesc {
251  color: black;
252  font-size: smaller;
253  margin: 1pt 0px 2pt 0px;
254  padding: 0px 0px 0px 0px;
255}
256.desc {
257  font-size:smaller;
258  padding: 0px 0px .75em 0px;
259}
260.warning {
261  font-size:smaller;
262  font-weight:bold;
263  color:red;
264}
265.footer {
266  border: 1px dashed #006600;
267  padding: 6px;
268  color: #006600;
269  font-size: smaller;
270  margin: 1em 0px .5em 0px;
271}
272EOF
273
274   my $template_stylemiddle_concise = <<"EOF";
275.sectionoutlined {
276  padding: 0px 8px 6px 8px;
277}
278.sectionoutlineddark {
279  border-left: 20px solid #99CC99;
280  padding: 8px;
281  margin-bottom: 8px;
282  margin-left: 10px;
283}
284.sectionplain {
285  padding: 10px;
286}
287.sectionerror {
288  padding: 10px;
289  color: red;
290  font-weight: bold;
291}
292.title {
293  color: #006600;
294  font-weight:bold;
295  margin: 0em 0px 2pt 0px;
296  padding: 0px 0px 0px 0px;
297}
298.title2 {
299  border-top: 1px solid #006600;
300  color: #006600;
301  font-weight:bold;
302  margin: .5em 0px 0px 0px;
303  padding: 0px 0px 0px 0px;
304}
305.pagebreadcrumbs {
306  color: #006600;
307  font-weight:bold;
308  font-size:smaller;
309  margin: 0em 0px 2pt 0px;
310  padding: 0px 0px 0px 0px;
311}
312.pagetitle {
313  color: #006600;
314  font-weight:bold;
315  margin: 0em 0px 0px 0px;
316  padding: 0px 0px 0px 0px;
317}
318.pagefilename {
319  color: #006600;
320  font-size: smaller;
321  font-weight: bold;
322  margin: 1pt 0px 2pt 0px;
323  padding: 0px 0px 0px 0px;
324}
325.pagetitledesc {
326  visibility:hidden;
327  height:0px;
328}
329.desc {
330  visibility:hidden;
331  height:0px;
332}
333.warning {
334  font-size:smaller;
335  font-weight:bold;
336  color:red;
337}
338.footer {
339  visibility:hidden;
340  height:0px;
341}
342EOF
343
344   my $template_stylebottom = <<"EOF";
345.browsepageavailable {
346  font-size: smaller;
347  font-weight: bold;
348  background-color: white;
349  padding-left: 4pt;
350  padding-right: 4pt;
351  padding-bottom: 1pt;
352  vertical-align: top;
353}
354.browsepageavailable1 {
355  background-color: white;
356  padding-bottom: 1pt;
357  vertical-align: top;
358}
359.browsepageavailable2 {
360  font-size: smaller;
361  background-color: white;
362  padding-left: 4pt;
363  padding-right: 4pt;
364  padding-bottom: 1pt;
365  vertical-align: top;
366}
367.browsepageavailable3 {
368  font-size: smaller;
369  font-weight: bold;
370  color: #999999;
371  background-color: white;
372  padding-left: 4pt;
373  padding-right: 0px;
374  vertical-align: top;
375  text-align: right;
376}
377.browsepageediting {
378  font-size: smaller;
379  color: white;
380  font-weight: bold;
381  background-color: #339933;
382  padding-left: 4pt;
383  padding-right: 4pt;
384  padding-bottom: 1pt;
385  vertical-align: top;
386}
387.browsepageediting2 {
388  font-size: smaller;
389  color: white;
390  background-color: #339933;
391  padding-left: 4pt;
392  padding-right: 4pt;
393  padding-bottom: 1pt;
394  vertical-align: top;
395}
396.browsepageediting3 {
397  font-size: smaller;
398  font-weight: bold;
399  color: white;
400  background-color: #339933;
401  padding-left: 4pt;
402  padding-right: 4pt;
403  padding-bottom: 1pt;
404  vertical-align: top;
405}
406.browsepagemedium {
407  font-size: smaller;
408  font-weight: bold;
409  padding-left: 4pt;
410  vertical-align: top;
411  color: black;
412}
413.browsepagedim {
414  font-size: smaller;
415  padding-left: 4pt;
416  vertical-align: top;
417  color: #999999;
418}
419.browsepagename {
420  font-size: smaller;
421  padding-left: 4pt;
422  padding-right: 4pt;
423  vertical-align: top;
424  color: black;
425}
426.browseusername {
427  font-weight: bold;
428  padding-left: 4pt;
429  padding-right: 4pt;
430  vertical-align: top;
431  color: black;
432}
433.browseusersite {
434  text-align:center;
435}
436.browseuseradd {
437  font-size: smaller;
438  font-style: italic;
439  padding-left: 4pt;
440  text-align: center;
441  color: #999999;
442}
443.browsebuttons {
444  padding-left: 4pt;
445  vertical-align: top;
446}
447.browsenormal {
448  font-size: smaller;
449  padding-left: 4pt;
450  vertical-align: top;
451}
452.browsetablehead {
453  background-color: #339933;
454  color: white;
455  font-size: smaller;
456  font-weight: bold;
457  padding: 4pt 0 4pt 4pt;
458  text-align: center;
459}
460.browsegrouphead {
461  background-color: #99CC99;
462  color: black;
463  font-size: smaller;
464  font-weight: bold;
465  padding: 4pt 0 4pt 4pt;
466  text-align: center;
467}
468.browsecolumnhead {
469  background-color: #99CC99;
470  color: white;
471  font-size: smaller;
472  font-weight: bold;
473  padding: 4pt 0 4pt 4pt;
474}
475.browsebuttoncell {
476  vertical-align: top;
477  white-space: nowrap;
478}
479.browsebuttoncellediting {
480  text-align: left;
481  white-space: nowrap;
482  background-color: #339933;
483}
484.browseselectedtext {
485  font-size: smaller;
486  font-weight: bold;
487  color: black;
488  background-color: white;
489  margin: 0px 4px 4px 4px;
490  padding: 0px 3px 2px 0px;
491}
492.browsereconcile {
493  background-color: white;
494  padding: 6pt;
495  vertical-align: top;
496  text-align: center;
497}
498.colsample1 {
499  border-top:2px solid black;
500  border-left:1px solid black;
501  padding:10px 0px 0px 8px;
502  font-size:1pt;
503}
504.colsample2 {
505  border-left:1px solid black;
506  font-size:x-small;
507  padding-left:1pt;
508}
509.cellnormal {
510 }
511.cellcursor {
512 margin:1px;
513 padding:1px;
514 border:3px solid #666633;
515 }
516.cellcorner {
517 margin:1px;
518 padding:1px;
519 border:1px solid #99CC99;
520 }
521.colorbox {
522 border:1px solid black;
523 padding:5px;
524 height:1px;
525 width:1px;
526}
527.defaultbox {
528 border:1px solid black;
529 padding:0px;
530 background-color:white;
531 background-image:url('?getfile=hatchbg');
532 height:16px;
533 width:16px;
534}
535.previewlisttitle {
536 font-size:smaller;
537 font-weight:bold;
538 text-align:right;
539}
540</style>
541EOF
542
543   my $template_headerscripts = <<"EOF"; # start with do nothing for setf and remove end of URL for setf0
544<script>
545<!--
546var setf = function() {1;}
547function setactions() {
548if (document.f0) document.f0.action=location.protocol+"//"+location.host+location.pathname;
549if (document.ftabs) document.ftabs.action=location.protocol+"//"+location.host+location.pathname;
550}
551// -->
552</script>
553EOF
554
555#
556# * * * * *
557#
558# PRE-LOAD special strings (WKCStrings overrides)
559#
560# * * * * *
561#
562
563   load_special_strings(); # loaded at first parse... (in WKCSheet.pm)
564
565   my @tablist = ($WKCStrings{"Page"}, $WKCStrings{"Edit"},
566                  $WKCStrings{"Format"},  $WKCStrings{"Preview"},
567                  $WKCStrings{"Tools"}, $WKCStrings{"Quit"},);
568
569
570#
571# * * * * *
572#
573# process_request($querystring, $cookievalue, \%responsedata, $security, $noquittab)
574#
575# Process the HTTP request
576#
577# Responds to browser request and does all the work
578# Returns data in %responsedata:
579#    $responsedata{content} - a string with the HTML response
580#    $responsedata{contenttype} - the HTTP response header content MIME type or null for default text/html UTF-8
581#    $responsedata{contentexpires} - expire string or null for default (-1d)
582#    $responsedata{cookie} - a string with the cookie value to set, if any
583#    $responsedata{cookieexpires} - expire value for cookie (e.g., "+1yr" or "" for session only)
584#
585# The $querystring is the raw query from the browser.
586# $cookievalue is the value of any cookie.
587# If $security is present and true, a "security"
588# parameter is used in each request to guard against
589# URLs on web sites that guess we are running locally.
590# $noquittab suppresses the Quit tab if true (for CGI and other cases where you always have a running system)
591#
592
593sub process_request {
594
595   my ($querystring, $cookievalue, $responsedata, $security, $noquittab) = @_;
596   my ($response, $rhead, $rbody1, $rbodytabs, $rbodytabs2, $inlinescripts);
597
598   my %sheetdata;
599   my @sheetlines;
600   my %headerdata;
601   my @headerlines;
602
603   # Initialize timers
604
605   my $start_clock_time = scalar localtime;
606   my $start_cpu_time = times();
607
608   # Get CGI object to get parameters:
609
610   $querystring ||= ""; # make sure has something
611   my $q = new CGI($querystring);
612   my %params = $q->Vars;
613
614   # Parse cookie
615
616   my %cookievalues;
617
618   foreach my $cookie (split /;/, $cookievalue) {
619      if ($cookie) {
620         my ($n, $v) = split /:/, $cookie;
621         $cookievalues{$n} = $v;
622         }
623      }
624
625   # If Quit tab is chosen, then return nothing:
626
627   if ($params{newtab} eq $WKCStrings{"Quit"}) {
628      return unless $noquittab;
629      }
630
631   #
632   # Check security code
633   #
634   # If not the same as ours, set it and wipe out parameters.
635   # This is to prevent malicious web sites from linking
636   # to a "known" local address and affect this app.
637   #
638
639   $securitycode = "" if !$security; # Don't use if running through a web server
640
641   if ($params{securitycode} ne $securitycode) {
642      %params = ();
643      $querystring = "";
644      $securitycode = sprintf("%.14f", rand); # assign a random security code
645      }
646
647   my %datavalues = %initial_datavalues; # start with default values
648                                         # before reading in
649   my $dvchanged; # if true, then need to write out
650
651   #
652   # *** Declarations
653   #
654
655   my ($editmode, $editcoords);
656   my $published = 0;
657   my $ok;
658   my $responsecookie;
659
660   #
661   # *** Retrieve initial values from params
662   #
663
664   $params{localwkcpath} = $WKCdirectory;
665
666   $editmode = $params{editmode};
667   $editcoords = $params{editcoords};
668
669   my $hostinfo = get_hostinfo(\%params); # input hostinfo data
670
671   #
672   # *** Check if login is necessary
673   #
674
675   $params{loggedinusername} ||= $cookievalues{loggedinusername}; # get from cookie if not in params
676   $params{loggedinuserpassword} ||= $cookievalues{loggedinuserpassword};
677
678   if ($params{dologin}) {
679      $params{loggedinusername} = $params{editusername};
680      }
681   elsif ($params{dologout}) {
682      %params = (); # forget everything
683      %cookievalues = ();
684      }
685
686   my %userinfo; # information about current user to be filled in when checking password
687   $userinfo{HOSTrequirelogin} = $hostinfo->{requirelogin}; # remember whether this host requires login here, too
688
689   my $loggedinuser = $params{loggedinusername};
690   my $needlogin; # if true, need to login a user
691   my $loginerrtext;
692   $params{loggedinadmin} = "yes"; # if true, have wikiCalc admin privileges
693   if ($hostinfo->{requirelogin} eq "yes") { # global setting
694      my $password = $params{loggedinuserpassword};
695      $needlogin = 1; # assume need to login
696      $params{loggedinadmin} = ""; # assume not admin
697#print "logged in user: $loggedinuser/$password (cookies:$cookievalues{loggedinusername}/$cookievalues{loggedinuserpassword})\n";
698      if ($loggedinuser || $params{dologin}) { # is someone already logged in or logging in?
699         my $errtext = get_userinfofileinfo(\%params, \%userinfo, $loggedinuser);
700         if (!$errtext) {
701            if ($params{dologin}) {
702               $params{loggedinuserpassword} = substr(crypt($params{edituserpassword}, $userinfo{$params{editusername}}->{password}), 2);
703               $password = $params{loggedinuserpassword};
704               }
705            if ($password eq substr($userinfo{$loggedinuser}->{password}, 2)) { # password is OK (salt is not passed back to browser)
706               if ($userinfo{$loggedinuser}->{admin} eq "yes") {
707                  $params{loggedinadmin} = "yes"; # turn on
708                  }
709               $needlogin = 0; # don't need to login
710               if ($params{dologin}) {
711                  if ($params{editusercookies}) { # if the user says to save login info in cookie
712                     $responsecookie .= "loggedinusername:$params{loggedinusername};loggedinuserpassword:$params{loggedinuserpassword};";
713                     $cookievalues{expires} = $params{editusercookies};
714                     }
715                  }
716               elsif ($cookievalues{loggedinusername} && $params{loggedinusername}) { # if have been saving login info, continue to
717                  $responsecookie .= "loggedinusername:$params{loggedinusername};loggedinuserpassword:$params{loggedinuserpassword};";
718                  }
719               }
720            else {
721               sleep 4; # if bad password, wait a bit so can't power through many easily
722               $loginerrtext = $WKCStrings{"loginerror"};
723               }
724            }
725         else {
726            sleep 4; # if bad password, wait a bit so can't power through many easily
727            $loginerrtext = $WKCStrings{"loginerror"};
728            }
729         }
730      }
731
732   #
733   # *** Look for special calls
734   #
735
736   #
737   # ?getfile=type for downloading special files, such as 1x1.gif
738   #
739
740   if ($params{getfile}) {
741      if ($params{getfile} eq "1x1" || $params{getfile} eq "hatchbg" || $params{getfile} eq "colsizehandle" || $params{getfile} eq "colsizescale") {
742         open IMAGEFILE, "$WKCdirectory/$params{getfile}.gif";
743         binmode IMAGEFILE;
744         my @imagelines = <IMAGEFILE>;
745         close IMAGEFILE;
746         $response = join "", @imagelines;
747         $responsedata->{content} = $response;
748         $responsedata->{contenttype} = "image/gif";
749         $responsedata->{contentexpires} = "+1h";
750         return;
751         }
752      }
753
754   #
755   # ?ajaxsetcell=sitename:pagename:coord:newvalue for new cell value
756   #
757   # Returns XML with <root>new cell data in a CDATA</root>.
758   #
759   # The cell data is in the form of one or more text lines, each one of the following:
760   #    coord:type:displayvalue:editvalue:align:colspan:rowspan:skip:csss\n
761   #    error:Error message to display
762   #    needsrecalc:yes/no
763   #    footer:New runtime message for page footer
764   # Where:
765   #    coord - A1, B7, etc.
766   #    newvalue - what was typed in, including "=", etc., if present
767   #    type - t is explicit text, tw is wikitext, n is number,
768   #           f is formula without "=", and "e" is empty
769   #    displayvalue - rendered HTML
770   #    align - left/right/center
771   #    colspan/rowspan - 1 or more
772   #    skip - blank if normal cell, else coord of cell to move cursor to if it lands here
773   #    csss - optional explicit style
774   #
775
776   if ($params{ajaxsetcell}) {
777      my ($psite, $pname, $coord, $value) = split(/:/, $params{ajaxsetcell}, 4);
778      $value = decode_from_ajax($value);
779
780      ($params{sitename}, $params{datafilename}) = ("", ""); # not used, but do this just in case
781
782      if ($needlogin || site_not_allowed(\%userinfo, $loggedinuser, $psite)) { # Not logged in or not allowed
783         my $msg = $needlogin ? $WKCStrings{"ajxnotloggedin"} : $WKCStrings{"ajaxnotfound"};
784         my $xmlresponse = <<"EOF";
785<?xml version="1.0" ?>
786<root><![CDATA[
787error:$msg
788]]></root>
789EOF
790         $responsedata->{content} = $xmlresponse;
791         $responsedata->{contenttype} = "text/xml";
792         return;
793         }
794
795      my ($type, $v1, $aep, @aheaderlines, @asheetlines, %aheaderdata, %asheetdata, %asheetdata, $aerrtext);
796
797      $aep = get_page_edit_path(\%params, $hostinfo, $psite, $pname);
798      $ok = load_page($aep, \@aheaderlines, \@asheetlines);
799
800      $ok = parse_header_save(\@aheaderlines, \%aheaderdata);
801      $ok = parse_sheet_save(\@asheetlines, \%asheetdata);
802      init_sheet_cache(\%asheetdata, \%params, $hostinfo, $psite); # remember in case have references to other worksheets
803
804      my %celldatabefore;
805      my $linkstyle = "?view=$psite/[[pagename]]";
806      render_values_only(\%asheetdata, \%celldatabefore, $linkstyle);
807
808      # Determine value type and do appropriate command to set it
809
810      $type = "text tw";
811      my $fch = substr($value, 0, 1);
812      if ($fch eq "=" && $value !~ m/\n/) {
813         $type = "formula";
814         $value = substr($value, 1);
815         }
816      elsif ($fch eq "'") {
817         $type = "text tw";
818         $value = substr($value, 1);
819         }
820      elsif (length $value == 0) {
821         $type = "empty";
822         }
823      else {
824         $v1 = determine_value_type($value, \$type);
825         if ($type eq 'n' && $v1 == $value) { # check that we don't need "constant"
826            $type = "value n";
827            }
828         elsif ($type eq 't') {
829            $type = "text tw";
830            }
831         else { # handle all the special types
832            $type = "constant $type $v1";
833            }
834         }
835
836      my $cmdline = "set $coord $type $value"; # create and execute command to set the cell
837      $ok = execute_sheet_command_and_log(\%asheetdata, $cmdline, \%aheaderdata);
838      if ($ok) {
839         if ($asheetdata{sheetattribs}->{recalc} ne "off") {
840            $aerrtext = recalc_sheet(\%asheetdata);
841            }
842         else {
843            $asheetdata{sheetattribs}->{needsrecalc} = "yes";
844            }
845         }
846      else {
847         $aerrtext = "Failed: $ok"; # should not get here, so use English...
848         }
849      my %celldataafter;
850      render_values_only(\%asheetdata, \%celldataafter, $linkstyle);
851
852      my $asheetcontents = create_sheet_save(\%asheetdata);
853
854      $aheaderdata{lastmodified} = $start_clock_time; # remember it in header
855
856      my $sitedata = $hostinfo->{sites}->{$psite};
857      my $thisauthor = $sitedata->{authoronhost}; # Get author name
858      if ($sitedata->{authorfromlogin} eq "yes" && $params{loggedinusername}) {
859         $thisauthor = $params{loggedinusername};
860         $thisauthor =~ s/[^a-z0-9\-]//g;
861         }
862      $aheaderdata{lastauthor} = $thisauthor;
863
864      my $aheadercontents = create_header_save(\%aheaderdata);
865
866      $ok = save_page($aep, $aheadercontents, $asheetcontents);
867
868      foreach my $cr (sort keys %celldataafter) { # construct output
869         my $cdbefore = $celldatabefore{$cr};
870         my $cdafter = $celldataafter{$cr};
871         next
872          if $cdbefore->{type} eq $cdafter->{type}
873          && $cdbefore->{display} eq $cdafter->{display}
874          && $cdbefore->{align} eq $cdafter->{align}
875          && $cr ne $coord; # only stuff that has changed unknown to client (but send at least one -- the one with "loading")
876
877         my $displayvalue = encode_for_ajax($cdafter->{display});
878         $displayvalue = "" if $displayvalue eq "&nbsp;"; # this is the default
879         my $csssvalue = encode_for_save($asheetdata{cellattribs}->{$cr}->{csss}); # need this to scroll back
880         my $editvalue;
881         if ($asheetdata{datatypes}->{$cr} eq 'f' || $asheetdata{datatypes}->{$cr} eq 'c') { # formula or constant
882            $editvalue = encode_for_ajax($asheetdata{formulas}->{$cr});
883            }
884         else {
885            $editvalue = encode_for_ajax($asheetdata{datavalues}->{$cr});
886            }
887         $response .= "$cr:$cdafter->{type}:$displayvalue:$editvalue:$cdafter->{align}:$cdafter->{colspan}:$cdafter->{rowspan}:$cdafter->{skip}:$csssvalue\n";
888         }
889      if ($asheetdata{sheetattribs}->{circularreferencecell}) { # include some error information
890         my ($from, $to) = split(/\|/, $asheetdata{sheetattribs}->{circularreferencecell});
891         my $str = "$WKCStrings{editcircular1}$from$WKCStrings{editcircular2}$to";
892         $response .= "error:$str\n";
893         }
894      $response .= "needsrecalc:$asheetdata{sheetattribs}->{needsrecalc}\n";
895      $response .= sprintf ("footer:$WKCStrings{wkcfooterruntime} %.2f $WKCStrings{wkcfootersecondsat} $start_clock_time\n",
896                              times() - $start_cpu_time);
897
898      my $xmlresponse = <<"EOF";
899<?xml version="1.0" ?>
900<root><![CDATA[
901$response
902]]></root>
903EOF
904      $responsedata->{content} = $xmlresponse;
905      $responsedata->{contenttype} = "text/xml";
906      return;
907      }
908
909   #
910   # ?ajaxgetpagelist=sitename
911   #
912   # Returns XML with <root>page data in a CDATA</root>
913   #
914   # The page data is in one of the following forms:
915   #    pages:pagename1:longname1:type1:pagename2:longname2:type2...
916   # or:
917   #    error:Error message to display
918   # Where:
919   #    pagename - the name, without the .html
920   #    longname - longname, with \ and : converted to \b and \c
921   #    type - e (editing locally), r (editing remotely), or p (published)
922   #
923
924   if ($params{ajaxgetpagelist}) {
925      my $currentsite = $params{sitename}; # temporarily switch to other site
926      $params{sitename} = $params{ajaxgetpagelist};
927
928      if ($needlogin || site_not_allowed(\%userinfo, $loggedinuser, $params{sitename})) { # Not logged in or not allowed
929         my $msg = $needlogin ? $WKCStrings{"ajaxnotloggedin"} : $WKCStrings{"ajaxnotfound"};
930         my $xmlresponse = <<"EOF";
931<?xml version="1.0" ?>
932<root><![CDATA[error:$msg]]></root>
933EOF
934         $responsedata->{content} = $xmlresponse;
935         $responsedata->{contenttype} = "text/xml";
936         return;
937         }
938
939      my $siteinfo = get_siteinfo(\%params); # get site information
940      my $ok = update_siteinfo(\%params, $hostinfo, $siteinfo); # update it
941      if ($siteinfo->{updates}) {
942         $ok = save_siteinfo(\%params, $siteinfo);
943         }
944      my $currentauthor = $hostinfo->{sites}->{$params{sitename}}->{authoronhost};
945      if ($hostinfo->{sites}->{$params{sitename}}->{authorfromlogin} eq "yes" && $params{loggedinusername}) {
946         $currentauthor = $params{loggedinusername};
947         }
948      $params{sitename} = $currentsite;
949
950      $response = "pages";
951
952      foreach my $name (sort keys %{$siteinfo->{files}}) { # List each file, depending on edit/pub status
953         my $fileinfo = $siteinfo->{files}->{$name};
954         my $longname = encode_for_ajax($fileinfo->{fullnamepublished});
955         my $editstatus = $fileinfo->{authors}->{$currentauthor}->{editstatus}; # is current author editing?
956         my $pubstatus = $fileinfo->{pubstatus}; # has it been published?
957         if ($pubstatus) {
958            $response .= ":$name:$longname:p";
959            }
960         if ($editstatus) {
961            if ($editstatus eq "remote") {
962               $response .= ":$name:$longname:r";
963               }
964            else {
965               $response .= ":$name:$longname:e";
966               }
967            }
968         }
969
970      my $xmlresponse = <<"EOF";
971<?xml version="1.0" ?>
972<root><![CDATA[$response]]></root>
973EOF
974      $responsedata->{content} = $xmlresponse;
975      $responsedata->{contenttype} = "text/xml";
976      return;
977      }
978
979   #
980   # ?ajaxgetnamedtext=textname
981   #
982   # Returns XML with <root>text data in a CDATA</root>
983   # The text data is in WKCtextdata.txt. That file is in the following format:
984   #  *** WKCtexdataname: textname1 ***
985   #  Lines of text to be returned...
986   #  *** WKCtexdataname: textname2 ***
987   #  Lines of text to be returned...
988   # textname is alphanumerics plus -
989
990   if ($params{ajaxgetnamedtext}) {
991      my $textname = $params{ajaxgetnamedtext};
992      $textname =~ s/[^A-Za-z0-9\-]//g;
993      my ($returnstr, $line);
994
995      open(TEXTFILEIN, "$WKCdirectory/WKCtextdata.txt");
996
997      while ($line = <TEXTFILEIN>) { # find start of desired text
998         last if $line =~ m/^\*\*\* WKCtextdataname\: $textname \*\*\*/;
999         }
1000
1001      while ($line = <TEXTFILEIN>) { # copy lines
1002         last if $line =~ m/^\*\*\* WKCtextdataname\: [A-Za-z0-9\-]+ \*\*\*/;
1003         $returnstr .= $line;
1004         }
1005
1006      close TEXTFILEIN;
1007
1008      my $xmlresponse = <<"EOF";
1009<?xml version="1.0" ?>
1010<root><![CDATA[$returnstr]]></root>
1011EOF
1012      $responsedata->{content} = $xmlresponse;
1013      $responsedata->{contenttype} = "text/xml";
1014      return;
1015      }
1016
1017   #
1018   # *** Process CGI-access to view pages
1019   #
1020   # Use with:
1021   #    ?view=sitename/pagename (assumes type=page)
1022   #    ?view=sitename/pagename&type=outputtype (page, html, js, cdata, and source are the allowed types)
1023   #    optional: &recalc=no (anything else or no &recalc means "yes, do a recalc first"
1024   #    The saved page is not modified by viewing it, even if a recalc is done
1025   #    ?view=logout (logs user out)
1026   #    ?view=sitename/pagename&cell:coord:type=value (type is n, t, or e:na)
1027   #
1028
1029   if ($params{view}) { # "wikicalc.pl?view=sitename/pagename" invocation
1030      if ($params{view} eq "logout") { # logout user
1031         $responsedata->{content} = <<"EOF";
1032<html>
1033<head>
1034</head>
1035<body style="background-color:gray;">
1036<table cellspacing="0" cellpadding="0" border="0" align="center">
1037<tr><td style="border:1px solid black;background-color:white;padding:1em;">
1038$WKCStrings{"viewlogoutcompleted"}
1039</td></tr></table>
1040</body>
1041</html>
1042EOF
1043         $responsedata->{contenttype} = "text/html";
1044         $responsedata->{cookie} = "datafilename:;sitename:";
1045         $responsedata->{cookieexpires} = "";
1046         return;
1047         }
1048
1049      my ($vsitename, $vpagename) = split(/\//, lc $params{view}, 2);
1050      $vsitename =~ s/[^a-z0-9\-]//g;
1051      my $viewerrstr;
1052      $vpagename =~ s/[^a-z0-9\-]//g;
1053      $responsedata->{contenttype} = "text/plain"; # default
1054
1055      $params{sitename} = $vsitename;
1056      $params{datafilename} = $vpagename;
1057
1058      my (@vheaderlines, %vheaderdata, @vsheetlines, %vsheetdata);
1059
1060      my $vstr;
1061      my $linkstyle;
1062
1063      # Load page (even if this user doesn't have permission) and then parse header
1064
1065      my $vpath = get_page_published_datafile_path(\%params, $hostinfo, $vsitename, $vpagename);
1066
1067      my $loaderr = load_page($vpath, \@vheaderlines, \@vsheetlines);
1068
1069      my $pareseok = parse_header_save(\@vheaderlines, \%vheaderdata); # Get data from header
1070
1071      # Determine access
1072
1073      if ($vheaderdata{viewwithoutlogin} ne "yes" && $needlogin) {
1074         my $temp1 = $params{view};
1075         my $temp2 = $params{type};
1076         my $temp3 = $params{etpurl};
1077         %params = (); # need login -- wipe out all parameters (for now...)
1078         $params{view} = $temp1 if $temp1;
1079         $params{type} = $temp2 if $temp2;
1080         $params{etpurl} = $temp3 if $temp3;
1081         $loggedinuser = "";
1082         }
1083
1084      if ($vheaderdata{viewwithoutlogin} ne "yes" && readsite_not_allowed(\%userinfo, $loggedinuser, $vsitename)) {
1085         if (!$params{type} || lc($params{type}) eq "page") {
1086            if ($loginerrtext) { # error logging in
1087               $loginerrtext = qq!<span style="color:red;">$loginerrtext</span><br><br>!;
1088               }
1089            elsif ($params{viewlogin}) { # probably site not allowed
1090               $loginerrtext = <<"EOF";
1091$WKCStrings{"viewliveviewloggedinasuser"} "$loggedinuser" (<a href="$hostinfo->{sites}->{$vsitename}->{editurl}?view=logout">$WKCStrings{"viewliveviewsologout"}</a>)<br><br>
1092<span style="color:red;">$WKCStrings{"viewliveviewnoreadaccess"} "$vsitename" $WKCStrings{"viewliveviewforuser"} "$loggedinuser"</span><br><br>
1093EOF
1094               }
1095            $responsedata->{content} = <<"EOF"; # need POST to keep password out of URL
1096<html>
1097<head>
1098</head>
1099<body style="background-color:gray;" onload="setf();">
1100<form name="f0" method="POST">
1101<table cellspacing="0" cellpadding="0" border="0" align="center">
1102<tr><td style="border:1px solid black;background-color:white;padding:1em;">
1103$loginerrtext
1104$WKCStrings{"viewlogin1"} '$vsitename/$vpagename'
1105<br><br>
1106$WKCStrings{"loginname"}&nbsp;<input type="text" name="editusername" size="15" value="">
1107&nbsp;$WKCStrings{"loginpassword"}&nbsp;<input type="password" name="edituserpassword" size="10" value="">
1108&nbsp;<input type="submit" name="dologin" value="$WKCStrings{"loginlogin"}">
1109<br><br>
1110<input type="radio" name="editusercookies" value="session" CHECKED>$WKCStrings{"loginsessioncookie"}&nbsp;
1111<input type="radio" name="editusercookies" value="$defaultcookieexpire">$WKCStrings{"loginlongcookie"}
1112<input type="hidden" name="view" value="$params{view}">
1113<input type="hidden" name="type" value="$params{type}">
1114<input type="hidden" name="viewlogin" value="1">
1115</td></tr></table>
1116</form>
1117<script>
1118var setf = function() {document.f0.editusername.focus();}
1119</script>
1120</body>
1121</html>
1122EOF
1123            $responsedata->{contenttype} = "text/html";
1124            }
1125
1126         elsif (lc($params{type}) eq "js") {
1127            $responsedata->{content} = "document.write('$WKCStrings{viewnotloggedin} \\'$params{view}\\'');";
1128            $responsedata->{contenttype} = "text/javascript";
1129            }
1130         elsif (lc($params{type}) eq "cdata") {
1131            $responsedata->{content} = <<"EOF";
1132<?xml version="1.0" ?>
1133<root><![CDATA[$WKCStrings{"viewnotloggedin"} '$params{view}']]></root>
1134EOF
1135            $responsedata->{contenttype} = "text/xml";
1136            }
1137         else {
1138            $responsedata->{content} = "$WKCStrings{viewnotloggedin} '$params{view}'";
1139            }
1140         return;
1141         }
1142
1143      if ($params{viewlogin}) { # successful login
1144         # This makes sure the page is accessed with a GET and works with or without Javascript
1145         $responsedata->{content} = <<"EOF";
1146<html>
1147<head>
1148</head>
1149<body style="background-color:gray;" onload="setf();">
1150<form name="f0" method="GET">
1151<table cellspacing="0" cellpadding="0" border="0" align="center">
1152<tr><td style="border:1px solid black;background-color:white;padding:1em;">
1153$WKCStrings{"viewloggedin"} "$loggedinuser"<br>
1154<br>$WKCStrings{"viewclicktoview"} "$vsitename/$vpagename":<br><br>
1155<input type="submit" name="view" value="$params{view}" onclick="location.href=location.href;return false;">
1156</td></tr></table>
1157</form>
1158<script>
1159var setf = function() {document.f0.view.focus();}
1160</script>
1161</body>
1162</html>
1163EOF
1164            $responsedata->{contenttype} = "text/html";
1165            $responsecookie .= "datafilename:;sitename:;expires:$cookievalues{expires}";
1166            $responsedata->{cookie} = $responsecookie;
1167            $responsedata->{cookieexpires} = $cookievalues{expires} eq "session" ? "" : ($cookievalues{expires} || $defaultcookieexpire);
1168            return;
1169            }
1170
1171      if (lc($params{type}) eq "source") {
1172         if ($loaderr) {
1173            $responsedata->{content} = "$WKCStrings{viewunabletoload} '$params{view}'";
1174            return;
1175            }
1176         if ($vheaderdata{publishsource} ne "yes") { # not allowed
1177            $responsedata->{content} = "$WKCStrings{viewnosource} '$params{view}'.";
1178            return;
1179            }
1180         my $ok = open (DATAFILEIN, $vpath);
1181         if (!$ok) {
1182            $responsedata->{content} = "$WKCStrings{notfound1} '$vpath' $WKCStrings{notfound2}";
1183            return;
1184            }
1185         my $line;
1186         while ($line = <DATAFILEIN>) {
1187            $vstr .= $line;
1188            }
1189         close DATAFILEIN;
1190         $responsedata->{content} = $vstr;
1191         return;
1192         }
1193
1194      $pareseok = parse_sheet_save(\@vsheetlines, \%vsheetdata); # Get data from sheet
1195
1196      $loaderr = $WKCStrings{"viewunabletoload"} if $loaderr;
1197
1198      if (lc($params{recalc}) ne "no") { # do a recalc
1199         foreach my $param (keys %params) { # see if any cell value overrides
1200            next unless $param =~ /^cell\:([a-z]{1,2}\d+)\:(n|t|e\:na)$/i;
1201            if ($vsheetdata{cellattribs}->{uc $1}->{mod} ne "y") {
1202               $loaderr = "$WKCStrings{viewnomodify1} $1 $WKCStrings{viewnomodify2}";
1203               last;
1204               }
1205            if (lc($2) eq "n") { # numeric value
1206               $vsheetdata{datavalues}->{uc($1)} = $params{$param};
1207               $vsheetdata{datatypes}->{uc($1)} = "v";
1208               $vsheetdata{valuetypes}->{uc($1)} = "n";
1209               }
1210            elsif (lc($2) eq "t") { # text value
1211               $vsheetdata{datavalues}->{uc($1)} = $params{$param};
1212               $vsheetdata{datatypes}->{uc($1)} = "t";
1213               $vsheetdata{valuetypes}->{uc($1)} = "t";
1214               }
1215            if (lc($2) eq "e:na") { # error value
1216               $vsheetdata{datavalues}->{uc($1)} = $params{$param};
1217               $vsheetdata{datatypes}->{uc($1)} = "v";
1218               $vsheetdata{valuetypes}->{uc($1)} = "e#N/A";
1219               $vsheetdata{cellerrors}->{uc($1)} = "#N/A";
1220               }
1221            }
1222         init_sheet_cache(\%vsheetdata, \%params, $hostinfo, $vsitename); # remember in case have references to other worksheets
1223         my $aerrtext = recalc_sheet(\%vsheetdata); # do the recalc
1224         }
1225
1226      my $vstr;
1227
1228      if (!$params{type} || lc($params{type}) eq "page") {
1229         if ($loaderr) {
1230            $responsedata->{content} = "$loaderr '$params{view}'";
1231            return;
1232            }
1233         my $sitedata = $hostinfo->{sites}->{$vsitename};
1234         my $hostdata = $hostinfo->{hosts}->{$sitedata->{host}};
1235
1236         $linkstyle = "$sitedata->{editurl}?view=$vsitename/[[pagename]]";
1237         my ($stylestr, $sheetstr) = render_sheet(\%vsheetdata, 'class="wkcsheet"', "", "s", "a", "publish", "", "", $linkstyle);
1238
1239         my $author = $vheaderdata{lastauthor} || $sitedata->{authoronhost}; # Get author name
1240
1241         $vstr = $vheaderdata{templatetext}
1242                   || get_template(\%params, "htmltemplate", $vheaderdata{templatefile})
1243                   || get_template(\%params, "htmltemplate", "site:default")
1244                   || get_template(\%params, "htmltemplate", "shared:default")
1245                   || get_template(\%params, "htmltemplate", "system:default")
1246                   || $WKCStrings{"publishtemplate"};
1247
1248         $vstr = fill_in_HTML_template($vstr, $sitedata, \%vheaderdata, $vsitename, $vpagename, $start_clock_time, $author, $loggedinuser, $stylestr, $sheetstr, 1);
1249         $responsedata->{content} = $vstr;
1250         $responsedata->{contenttype} = "text/html; charset=UTF-8";
1251         }
1252
1253      elsif (lc($params{type}) eq "js") {
1254         if ($loaderr) {
1255            $responsedata->{content} = "document.write('$loaderr \\'$params{view}\\'');";
1256            $responsedata->{contenttype} = "text/javascript";
1257            return;
1258            }
1259         my ($stylestr, $sheetstr) = render_sheet(\%vsheetdata, 'class="wkcsheet"', "", "s", "a", "embed", "", "", $linkstyle);
1260         $vstr = create_embeddable_JS_sheet($stylestr, $sheetstr);
1261         $responsedata->{content} = $vstr;
1262         $responsedata->{contenttype} = "text/javascript; charset=UTF-8";
1263         }
1264
1265      elsif (lc($params{type}) eq "html") {
1266         if ($loaderr) {
1267            $responsedata->{content} = "$loaderr '$params{view}'";
1268            $responsedata->{contenttype} = "text/plain";
1269            return;
1270            }
1271         my ($stylestr, $sheetstr) = render_sheet(\%vsheetdata, 'class="wkcsheet"', "", "s", "a", "inline", "", "", $linkstyle);
1272         $responsedata->{content} = $sheetstr;
1273         $responsedata->{contenttype} = "text/plain; charset=UTF-8";
1274         }
1275
1276      elsif (lc($params{type}) eq "cdata") {
1277         if ($loaderr) {
1278            $responsedata->{content} = <<"EOF";
1279<?xml version="1.0" ?>
1280<root><![CDATA[Error: $loaderr '$params{view}']]></root>
1281EOF
1282            $responsedata->{contenttype} = "text/xml";
1283            return;
1284            }
1285         my ($stylestr, $sheetstr) = render_sheet(\%vsheetdata, 'class="wkcsheet"', "", "s", "a", "inline", "", "", $linkstyle);
1286         my $xmlresponse = <<"EOF";
1287<?xml version="1.0" ?>
1288<root><![CDATA[$sheetstr]]></root>
1289EOF
1290         $responsedata->{content} = $xmlresponse;
1291         $responsedata->{contenttype} = "text/xml";
1292         return;
1293         }
1294
1295      else {
1296         $responsedata->{content} = "$WKCStrings{viewunknowntype} '$params{type}'.";
1297         }
1298
1299      $responsedata->{contenttype} = "text/html; charset=UTF-8";
1300
1301      # (leave cookies as they were)
1302
1303      return;
1304      }
1305
1306   #
1307   # *** Protect from not being logged in
1308   #
1309
1310   if ($needlogin) {
1311      my $temp1 = $params{editthispage};
1312      my $temp2 = $params{etpurl};
1313      %params = (); # need login -- wipe out all parameters (for now...)
1314      $params{logineditthispage} = $temp1 if $temp1;
1315      $params{etpurl} = $temp2 if $temp2;
1316      }
1317
1318   #
1319   # *** Process Edit This Page call (do a POST with editthispage=sitename/pagename)
1320   #
1321
1322   my $checkmultiedit; # if non-blank, list of others editing already
1323
1324   if ($params{editthispage}) { # "Edit this page" invocation
1325      my ($etpsavesitename, $etpsavedatafilename) = ($params{sitename}, $params{datafilename});
1326      ($params{sitename}, $params{datafilename}) = split(/\//, $params{editthispage}, 2);
1327      $params{sitename} =~ s/[^a-z0-9\-]//g; # get new sitename and pagename
1328      my $etperrstr;
1329      if (site_not_allowed(\%userinfo, $loggedinuser, $params{sitename})) {
1330         $params{sitename} = "";
1331         $params{datafilename} = "";
1332         $params{etpurl} = ""; # if wipe out name, wipe out back to where we started when editing
1333         $etperrstr = $WKCStrings{"etpnotfound"};
1334         }
1335      if ($params{cancelmultieditthispage}) { # someone else editing and decided not to override
1336         $etperrstr = qq!$WKCStrings{"etpeditstartof"} "$params{datafilename}" $WKCStrings{"etpcancelled"}!;
1337         $params{datafilename} = "";
1338         $params{etpurl} = "";
1339         $params{newtab} = $WKCStrings{"Page"};
1340         }
1341      $params{datafilename} =~ s/[^a-z0-9\-]//g;
1342      $editcoords = "";
1343      delete $params{scrollrow};
1344
1345      # See if anybody else is editing this
1346
1347      if (!$params{editthispageokmulti} && !$etperrstr) {
1348         my $siteinfo = get_siteinfo(\%params);
1349
1350         my $currentauthor = $hostinfo->{sites}->{$params{sitename}}->{authoronhost};
1351           if ($hostinfo->{sites}->{$params{sitename}}->{authorfromlogin} eq "yes" && $params{loggedinusername}) {
1352            $currentauthor = $params{loggedinusername};
1353            }
1354
1355         my $ok = update_siteinfo(\%params, $hostinfo, $siteinfo);
1356
1357         if ($siteinfo->{updates}) {
1358            $ok = save_siteinfo(\%params, $siteinfo);
1359            }
1360         my $fileinfo = $siteinfo->{files}->{$params{datafilename}};
1361         my $editstatus = $fileinfo->{authors}->{$currentauthor}->{editstatus};
1362
1363         foreach my $author (sort keys %{$fileinfo->{authors}}) {
1364            if ($author ne $currentauthor) { # not us
1365               $checkmultiedit .= "$author, ";
1366               }
1367            }
1368         }
1369
1370      if ($checkmultiedit) { # others editing - check if that's OK first
1371         $checkmultiedit =~ s/, $//; # make readable
1372         ($params{sitename}, $params{datafilename}) = ($etpsavesitename, $etpsavedatafilename); # restore
1373         $params{checkmultiediteditthispage} = $params{editthispage};
1374         }
1375
1376      elsif (!$etperrstr) { # all ready for us
1377         delete $params{scrollrow};
1378         $etperrstr = edit_published_page(\%params, $hostinfo, $params{sitename}, $params{datafilename}) unless $etperrstr;
1379         if ($etperrstr && !$needlogin) {
1380            $params{newtab} = $WKCStrings{"Page"};
1381            $params{etpurl} = "";
1382            $params{debugmessage} = $etperrstr;
1383            }
1384         else {
1385            $params{newtab} =  $WKCStrings{"Edit"};
1386            }
1387         }
1388      }
1389
1390   #
1391   # *** If nothing set, see if anything remembered in cookie
1392   #
1393
1394   if (!$params{datafilename} && !$params{sitename}) {
1395      if ($cookievalues{datafilename}) {
1396         $params{datafilename} = $cookievalues{datafilename};
1397         delete $params{scrollrow};
1398         }
1399      if ($cookievalues{sitename}) {
1400         $params{sitename} = $cookievalues{sitename};
1401         }
1402      }
1403
1404   #
1405   # *** Check OK to edit this site
1406   #
1407
1408   if (site_not_allowed(\%userinfo, $loggedinuser, $params{sitename})) {
1409      $params{sitename} = "";
1410      $params{datafilename} = "";
1411      $params{etpurl} = "" unless $params{logineditthispage};
1412      }
1413
1414   #
1415   # *** Process buttons
1416   #
1417
1418   foreach my $p (keys %params) {  # go through all the parameters
1419
1420      #
1421      # Change which page we are editing
1422      #
1423
1424      if ($p =~ /^choosepage(local|pub|backup):(.*)/) {
1425         my $newname = $2;
1426         my $editerrstr;
1427         if ($1 eq "pub") { # if editing from published page, copy the contents to an edit file
1428            $editerrstr = edit_published_page(\%params, $hostinfo, $params{sitename}, $2);
1429            }
1430         elsif ($1 eq "backup") {
1431            my $basefile = $2;
1432            $basefile =~ m/([a-z0-9\-]+?)\.\w+?\.[0-9\-]+?\.txt$/;
1433            $newname = $1;
1434            $editerrstr = edit_backup_page(\%params, $hostinfo, $params{sitename}, $basefile);
1435            }
1436         elsif ($1 ne "local") {
1437            $editerrstr = qq!Got command "$p"!; # debugging, shouldn't get here
1438            $params{"oktools:backup"} = 1;
1439            }
1440         if ($editerrstr) {
1441            $params{pagemessage} = qq!$WKCStrings{"choosecondition1"} "$2" $WKCStrings{"choosecondition2"}<br>$editerrstr!;
1442            }
1443         else {
1444            $params{datafilename} = $newname;
1445            $params{etpurl} = ""; # don't go back to last one from Edit This Page invocation
1446            $editcoords = "";
1447            delete $params{scrollrow};
1448            $params{newtab} =  $WKCStrings{"Edit"};
1449            }
1450         }
1451
1452      #
1453      # Publish page
1454      #
1455
1456      elsif ($p =~ /^publish(preview):(.*)/) {
1457         $params{pagemessage} = qq!Old code: $p!; # debugging
1458         }
1459
1460      elsif ($p =~ /^publish(continue|page|frompage):(.*)/) {
1461         my $continuepage = $1; # which invocation
1462         my $name = $2;
1463
1464         my (@pubheaderlines, %pubheaderdata, @pubsheetlines, %pubsheetdata);
1465
1466         my $pubpath = get_page_edit_path(\%params, $hostinfo, $params{sitename}, $name);
1467         my $loaderr = load_page($pubpath, \@pubheaderlines, \@pubsheetlines);
1468
1469         my $pareseok = parse_header_save(\@pubheaderlines, \%pubheaderdata); # Get data from header
1470         $pareseok = parse_sheet_save(\@pubsheetlines, \%pubsheetdata); # Get data from sheet
1471
1472         my ($stylestr, $sheetstr) = render_sheet(\%pubsheetdata, 'class="wkcsheet"', "", "s", "a", "publish", "", "");
1473
1474         my $sitedata = $hostinfo->{sites}->{$params{sitename}};
1475         my $hostdata = $hostinfo->{hosts}->{$sitedata->{host}};
1476
1477         my $thisauthor = $sitedata->{authoronhost}; # Get author name
1478         if ($sitedata->{authorfromlogin} eq "yes" && $params{loggedinusername}) {
1479            $thisauthor = $params{loggedinusername};
1480            $thisauthor =~ s/[^a-z0-9\-]//g;
1481            }
1482
1483         my $pubstr;
1484         if ($continuepage ne "frompage") { # invoked from view tab -- check updates to what to publish
1485            $pubheaderdata{publishhtml} = $params{editpublishhtml} ? "yes" : "no";
1486            $pubheaderdata{publishsource} = $params{editpublishsource} ? "yes" : "";
1487            $pubheaderdata{publishjs} = $params{editpublishjs} ? "yes" : "";
1488            $pubheaderdata{viewwithoutlogin} = $params{editviewwithoutlogin} ? "yes" : "";
1489            }
1490         $pubstr = $pubheaderdata{templatetext}
1491                   || get_template(\%params, "htmltemplate", $pubheaderdata{templatefile})
1492                   || get_template(\%params, "htmltemplate", "site:default")
1493                   || get_template(\%params, "htmltemplate", "shared:default")
1494                   || get_template(\%params, "htmltemplate", "system:default")
1495                   || $WKCStrings{"publishtemplate"};
1496
1497         if ($params{editcomments}) { # New comment content to go with this publish - saved when published
1498            $pubheaderdata{editcomments} = $params{editcomments}; # do here so available to HTML
1499            }
1500
1501         add_to_editlog(\%pubheaderdata, "# $WKCStrings{logpublishedby} $thisauthor") if $thisauthor ne $pubheaderdata{lastauthor}; # if unmodified may have last author
1502
1503         $pubstr = fill_in_HTML_template($pubstr, $sitedata, \%pubheaderdata, $params{sitename}, $name, $start_clock_time, $thisauthor, $loggedinuser, $stylestr, $sheetstr, 0);
1504
1505         my $continueediting = $continuepage eq "continue" ? 1 : 0; # Continue editing if requested
1506
1507         my $jsstr;
1508         if ($pubheaderdata{publishjs} eq "yes") {
1509            ($stylestr, $sheetstr) = render_sheet(\%pubsheetdata, 'class="wkcsheet"', "", "s", "a", "embed", "", "");
1510            $jsstr = create_embeddable_JS_sheet($stylestr, $sheetstr);
1511            }
1512
1513         my $puberrstr = publish_page(\%params, $hostinfo, $params{sitename}, $name, $pubstr, $jsstr, \%pubheaderdata, \%pubsheetdata, $continueediting);
1514         if ($puberrstr) {
1515            $params{pagemessage} = qq!$WKCStrings{"publisherror"} "$name":<br>$puberrstr!;
1516            }
1517
1518         if (!$continueediting && $name eq $params{datafilename}) { # reset if were editing page just published
1519            $params{datafilename} = "";
1520            delete $params{scrollrow};
1521            $params{etpurl} = "" if $continuepage eq "frompage";
1522            }
1523
1524         $published = $name;
1525         }
1526
1527      #
1528      # Delete page
1529      #
1530
1531      elsif ($p =~ /^choosepagedel:(.*)/) {
1532         my $deletepage = $1;
1533         my $errstr = delete_page(\%params, $hostinfo, $params{sitename}, $deletepage);
1534         if ($errstr) {
1535            $params{pagemessage} = qq!$WKCStrings{"choosedelerror"} "$deletepage":<br>$errstr!;
1536            }
1537         else {
1538            $params{pagemessage} = qq!$WKCStrings{"choosedeldeleted"} "$deletepage"!;
1539            }
1540         if ($deletepage eq $params{datafilename}) {
1541            delete $params{datafilename}; # if deleted current page, make it not the current page
1542            delete $params{scrollrow};
1543            $params{etpurl} = "";
1544            }
1545         delete $params{pagebuttons}; # go back to normal editing state
1546         }
1547
1548      #
1549      # Abandon editing page
1550      #
1551
1552      elsif ($p =~ /^choosepageabandon:(.*)/) { # Abandon editing page
1553         my $abandonpage = $1;
1554         my $errstr = abandon_page_edit(\%params, $hostinfo, $params{sitename}, $abandonpage);
1555         if ($errstr) {
1556            $params{pagemessage} = qq!$WKCStrings{"chooseabandonerror"} "$abandonpage":<br>$errstr!;
1557            }
1558         else {
1559            $params{pagemessage} = qq!$WKCStrings{"chooseabandondeleted1"} "$abandonpage" $WKCStrings{"chooseabandondeleted2"}!;
1560            }
1561         if ($abandonpage eq $params{datafilename}) {
1562            delete $params{datafilename}; # if abandoned current page, make it not the current page
1563            delete $params{scrollrow};
1564            $params{etpurl} = "";
1565            }
1566         delete $params{pagebuttons}; # go back to normal editing state
1567         }
1568
1569      #
1570      # Set a new range
1571      #
1572
1573      elsif ($p =~ /^okeditrange:(.+)/) { # Range edit command
1574         $params{okeditrange} = $1; # Remember which particular command
1575         }
1576
1577      #
1578      # Backup subcommand
1579      #
1580
1581      elsif ($p =~ /^backup:(details|archive|delete|download|preferences|savepreferences|all|list):(.+)/) { # Backup sub-command
1582         $params{backupsubcommand} = $1; # Remember which particular command
1583         $params{backupfilename} = $2; # Remember which file and/or other information
1584         if ($1 eq "list" || $1 eq "all") { # List and All may have paging info
1585            ($params{backupfilename}, $params{startitem}) = split(/:/, $params{backupfilename});
1586            }
1587         $params{"oktools:backup"} = 1; # Invoke backup command to process
1588         }
1589
1590      }
1591
1592   if ($params{okeditpreview}) {
1593      $params{newtab} = $WKCStrings{"Preview"};
1594      }
1595
1596   if (!$params{datafilename}) { # If no page chosen, go to Page tab in most cases
1597      if ($params{newtab}) {
1598         if ($params{newtab} ne $WKCStrings{"showlicense"} && $params{newtab} ne $WKCStrings{"Tools"}) {
1599            $params{newtab} = $WKCStrings{"Page"};
1600            }
1601         }
1602      elsif ($params{currenttab} ne $WKCStrings{"Tools"}) {
1603         $params{newtab} = $WKCStrings{"Page"};
1604         }
1605      }
1606
1607   if ($params{newtab}) {
1608      if ($params{newtab} eq $WKCStrings{"Page"}) {
1609         $params{currenttab} = $params{newtab};
1610         $editmode = "";
1611         }
1612      elsif ($params{newtab} eq $WKCStrings{"Preview"}) {
1613         $params{currenttab} = $params{newtab};
1614         $editmode = "";
1615         }
1616      elsif ($params{newtab} eq $WKCStrings{"Edit"}) {
1617         $params{currenttab} = $params{newtab};
1618         $editmode = $WKCStrings{"editsub-general"};
1619         $editcoords =~ s/:.*$//; # start with only upper left
1620         $editcoords ||= "A1";
1621         $params{editcoords} = $editcoords;
1622         }
1623      elsif ($params{newtab} eq $WKCStrings{"Format"}) {
1624         $params{currenttab} = $params{newtab};
1625         $editmode = $WKCStrings{"formatsub-range"};
1626         $editcoords ||= "A1";
1627         $params{editcoords} = $editcoords;
1628         }
1629      elsif ($params{newtab} eq $WKCStrings{"Tools"}) {
1630         $params{currenttab} = $params{newtab};
1631         $editcoords ||= "A1";
1632         $params{editcoords} = $editcoords;
1633         }
1634      elsif ($params{newtab} eq $WKCStrings{"showlicense"}) {
1635         $params{currenttab} = $params{newtab};
1636         $editmode = "";
1637         }
1638      }
1639
1640   if ($params{editsub}) {
1641      $editmode = $params{editsub};
1642      }
1643
1644   #
1645   # Get current tab and switch if necessary
1646   #
1647
1648   my $currenttab = $params{currenttab} || $WKCStrings{"Page"};
1649   my $lineheight;
1650   if ($currenttab eq $WKCStrings{"Edit"} || $currenttab eq $WKCStrings{"Format"}) {
1651      $lineheight = qq! style="font-size:1pt;"!;
1652      }
1653
1654   #
1655   # Display tabs and other top stuff
1656   #
1657
1658   $rbodytabs = <<"EOF";
1659<form name="ftabs" action="" method="POST">
1660<table cellpadding="0" cellspacing="0">
1661<tr>
1662EOF
1663
1664   foreach my $tab (@tablist) {
1665      next if ($tab eq $WKCStrings{"Quit"} && $noquittab); # CGI version doesn't show Quit tab
1666      if ($currenttab eq $tab && !$needlogin) {
1667         $rbodytabs .= <<"EOF";
1668<td class="tab1">&nbsp;</td>
1669<td class="tabselected">$tab</td>
1670EOF
1671         }
1672      else {
1673         $rbodytabs .= <<"EOF";
1674<td class="tab1">&nbsp;</td>
1675<td class="tab"><input type="submit" name="newtab" value="$tab"></td>
1676EOF
1677         }
1678      }
1679
1680   $rbodytabs .= <<"EOF";
1681<td class="tab1" width="100%">&nbsp;</td>
1682</tr>
1683<tr>
1684<td class="tab2left" width="1"$lineheight>&nbsp;</td>
1685EOF
1686
1687   my $ncols = @tablist - ($noquittab ? 1 : 0);
1688   $ncols = $ncols * 2 + 1;
1689   for (my $i=0; $i<$ncols-2; $i++) {
1690      $rbodytabs .= <<"EOF";
1691<td class="tab2"$lineheight>&nbsp;</td>
1692EOF
1693      }
1694
1695   $rbodytabs .= <<"EOF";
1696<td class="tab2right"$lineheight>&nbsp;</td>
1697</tr>
1698</table>
1699EOF
1700
1701   # # # # # # # # # #
1702   #
1703   # *** Read in data and do things
1704   #
1705   # # # # # # # # # #
1706
1707
1708   my $sheetmodified;
1709
1710   my $editpath;
1711
1712   if ($params{datafilename} && $params{sitename}) {
1713      $editpath = get_page_edit_path(\%params, $hostinfo, $params{sitename}, $params{datafilename});
1714      my $loaderr = load_page($editpath, \@headerlines, \@sheetlines);
1715
1716      if ($loaderr) { # deselect if can't load -- don't let us edit an inappropriate blank page
1717         $params{datafilename} = "";
1718         }
1719
1720      $ok = parse_header_save(\@headerlines, \%headerdata);
1721      $ok = parse_sheet_save(\@sheetlines, \%sheetdata);
1722      $sheetmodified = 0;
1723      }
1724   else { # No file -- use null sheet
1725      $sheetlines[0] = "";
1726      $ok = parse_sheet_save(\@sheetlines, \%sheetdata);
1727      $sheetmodified = 0;
1728      }
1729   init_sheet_cache(\%sheetdata, \%params, $hostinfo, $params{sitename}); # remember in case have references to other worksheets
1730
1731   #
1732   # *** See if there are edits to do
1733   #
1734
1735   if ($params{okeditcoord}) {
1736      $editcoords = $params{editeditcoords};
1737      }
1738
1739   elsif ($params{editaddrow} || $params{editaddcol}) { # extend sheet by a row or column
1740      my $newsize;
1741      if ($params{editaddrow}) {
1742         $newsize = $sheetdata{sheetattribs}->{lastrow} + 1;
1743         $ok = execute_sheet_command_and_log(\%sheetdata, "set sheet lastrow $newsize", \%headerdata);
1744         }
1745      elsif ($params{editaddcol}) {
1746         $newsize = $sheetdata{sheetattribs}->{lastcol} + 1;
1747         $ok = execute_sheet_command_and_log(\%sheetdata, "set sheet lastcol $newsize", \%headerdata);
1748         }
1749      $sheetmodified += 1;
1750      }
1751
1752   elsif ($params{okcolumns}) {
1753      my $value = $params{colwidthvalue};
1754      $value = "" if $params{colwidthtype} eq "default";
1755      $value = "auto" if $params{colwidthtype} eq "auto";
1756      if ($params{colwidthpercent}) {
1757          $value = $value > 100 ? 100 : ($value < 1 ? 1 : $value);
1758          $value .= "%";
1759          }
1760      my $ok;
1761      if ($params{columnsedefault}) {
1762         my $cmdline = "set sheet defaultcolwidth $value";
1763         $ok = execute_sheet_command_and_log(\%sheetdata, $cmdline, \%headerdata);
1764         }
1765      else {
1766         my $colname = $editcoords;
1767         $colname =~ s/\d//g;
1768         my $cmdline = "set $colname width $value";
1769         $ok = execute_sheet_command_and_log(\%sheetdata, $cmdline, \%headerdata);
1770         $value = $params{colhidevalue} ? "yes" : ""; # set hide value to yes or blank
1771         $cmdline = "set $colname hide $value";
1772         $ok = execute_sheet_command_and_log(\%sheetdata, $cmdline, \%headerdata);
1773         }
1774      if ($ok) {
1775         $sheetmodified += 1;
1776         }
1777      else {
1778         $params{debugmessage} = "Failed: $ok\n";
1779         }
1780      }
1781
1782   elsif ($params{okrows}) {
1783      my $rowname = $editcoords;
1784      $rowname =~ s/[A-Za-z]//g;
1785      my $value = $params{rowhidevalue} ? "yes" : ""; # set hide value to yes or blank
1786      my $cmdline = "set $rowname hide $value";
1787      $ok = execute_sheet_command_and_log(\%sheetdata, $cmdline, \%headerdata);
1788      if ($ok) {
1789         $sheetmodified += 1;
1790         }
1791      else {
1792         $params{debugmessage} = "Failed: $ok\n";
1793         }
1794      }
1795
1796   elsif ($params{okborders}) {
1797      my ($crd, $ok);
1798      $ok = 1;
1799      if ($params{borderoutline}) {
1800         my ($coord1, $coord2) = split(/:/, $editcoords);
1801         my ($c1, $r1) = coord_to_cr($coord1);
1802         my $c2 = $c1;
1803         my $r2 = $r1;
1804         ($c2, $r2) = coord_to_cr($coord2) if $coord2;
1805         for (my $c = $c1; $c <= $c2; $c++) {
1806            $crd = cr_to_coord($c, $r1);
1807            $ok &&= execute_sheet_command_and_log(\%sheetdata, "set $crd bt $params{editborder1}", \%headerdata);
1808            $crd = cr_to_coord($c, $r2);
1809            $ok &&= execute_sheet_command_and_log(\%sheetdata, "set $crd bb $params{editborder3}", \%headerdata);
1810            }
1811         for (my $r = $r1; $r <= $r2; $r++) {
1812            $crd = cr_to_coord($c1, $r);
1813            $ok &&= execute_sheet_command_and_log(\%sheetdata, "set $crd bl $params{editborder4}", \%headerdata);
1814            $crd = cr_to_coord($c2, $r);
1815            $ok &&= execute_sheet_command_and_log(\%sheetdata, "set $crd br $params{editborder2}", \%headerdata);
1816            }
1817         }
1818      else {
1819         $ok &&= execute_sheet_command_and_log(\%sheetdata, "set $editcoords bt $params{editborder1}", \%headerdata);
1820         $ok &&= execute_sheet_command_and_log(\%sheetdata, "set $editcoords br $params{editborder2}", \%headerdata);
1821         $ok &&= execute_sheet_command_and_log(\%sheetdata, "set $editcoords bb $params{editborder3}", \%headerdata);
1822         $ok &&= execute_sheet_command_and_log(\%sheetdata, "set $editcoords bl $params{editborder4}", \%headerdata);
1823         }
1824      if ($ok) {
1825         $sheetmodified += 1;
1826         }
1827      else {
1828         $params{debugmessage} = "Failed: $ok\n";
1829         }
1830      }
1831
1832   elsif ($params{oklayout}) {
1833      my $ok;
1834      my $layoutstr; # default to blank, which is "use default"
1835      if ($params{explicitlayout} eq "yes") { # set explicitly
1836         #
1837         # layout format is: padding:top right bottom left;vertical-align:style
1838         #
1839         $layoutstr = "padding:$params{layoutpaddingtop} $params{layoutpaddingright} $params{layoutpaddingbottom} $params{layoutpaddingleft};";
1840         $layoutstr .= "vertical-align:$params{verticalalign};";
1841         }
1842      if ($params{layoutdefault}) {
1843         $ok = execute_sheet_command_and_log(\%sheetdata, "set sheet defaultlayout $layoutstr", \%headerdata);
1844         }
1845      else {
1846         $ok = execute_sheet_command_and_log(\%sheetdata, "set $editcoords layout $layoutstr", \%headerdata);
1847         }
1848      if ($ok) {
1849         $sheetmodified += 1;
1850         }
1851      else {
1852         $params{debugmessage} = "Failed: $ok\n";
1853         }
1854      }
1855
1856   elsif ($params{okcolors}) {
1857      my $ok;
1858      if ($params{colorsedefault}) {
1859         $ok = execute_sheet_command_and_log(\%sheetdata, "set sheet defaultcolor $params{edittextcolor}", \%headerdata);
1860         $ok &&= execute_sheet_command_and_log(\%sheetdata, "set sheet defaultbgcolor $params{editbackgroundcolor}", \%headerdata);
1861         }
1862      else {
1863         $ok = execute_sheet_command_and_log(\%sheetdata, "set $editcoords color $params{edittextcolor}", \%headerdata);
1864         $ok &&= execute_sheet_command_and_log(\%sheetdata, "set $editcoords bgcolor $params{editbackgroundcolor}", \%headerdata);
1865         }
1866      if ($ok) {
1867         $sheetmodified += 1;
1868         }
1869      else {
1870         $params{debugmessage} = "Failed: $ok\n";
1871         }
1872      }
1873
1874   elsif ($params{okfonts}) {
1875      my $ok;
1876      my $fontstyleweight;
1877      if ($params{fontdefault}) {
1878         $fontstyleweight = "*";
1879         }
1880      else {
1881         $fontstyleweight = $params{fontitalic} ? "italic " : "normal ";
1882         $fontstyleweight .= $params{fontbold} ? "bold" : "normal";
1883         }
1884      $params{fontsize} = "*" if $params{fontsize} eq "default";
1885      $params{fontfamily} = "*" if $params{fontfamily} eq "default";
1886      if ($params{fontedefault}) {
1887         $ok = execute_sheet_command_and_log(\%sheetdata, "set sheet defaultfont $fontstyleweight $params{fontsize} $params{fontfamily}", \%headerdata);
1888         }
1889      else {
1890         $ok = execute_sheet_command_and_log(\%sheetdata, "set $editcoords font $fontstyleweight $params{fontsize} $params{fontfamily}", \%headerdata);
1891         }
1892      if ($ok) {
1893         $sheetmodified += 1;
1894         }
1895      else {
1896         $params{debugmessage} = "Failed: $ok\n";
1897         }
1898      }
1899
1900   elsif ($params{oktext}) {
1901      my $ok;
1902      $params{textalign} = "" if $params{textalign} eq "default";
1903      $params{textvalueformat} = "" if $params{textvalueformat} eq "default";
1904      if ($params{textedefault}) {
1905         $ok = execute_sheet_command_and_log(\%sheetdata, "set sheet defaulttextformat $params{textalign}", \%headerdata);
1906         $ok = execute_sheet_command_and_log(\%sheetdata, "set sheet defaulttextvalueformat $params{textvalueformat}", \%headerdata);
1907         }
1908      else {
1909         $ok = execute_sheet_command_and_log(\%sheetdata, "set $editcoords cellformat $params{textalign}", \%headerdata);
1910         $ok = execute_sheet_command_and_log(\%sheetdata, "set $editcoords textvalueformat $params{textvalueformat}", \%headerdata) if $ok;
1911         }
1912      if ($ok) {
1913         $sheetmodified += 1;
1914         }
1915      else {
1916         $params{debugmessage} = "Failed: $ok\n";
1917         }
1918      }
1919
1920   elsif ($params{okmisc}) {
1921      my $ok;
1922      $ok = execute_sheet_command_and_log(\%sheetdata, "set $editcoords cssc $params{miscclassvalue}", \%headerdata);
1923      $ok = execute_sheet_command_and_log(\%sheetdata, "set $editcoords csss $params{miscstylevalue}", \%headerdata) if $ok;
1924      $ok = execute_sheet_command_and_log(\%sheetdata, "set $editcoords mod $params{miscmodvalue}", \%headerdata) if $ok;
1925      if ($ok) {
1926         $sheetmodified += 1;
1927         }
1928      else {
1929         $params{debugmessage} = "Failed: $ok\n";
1930         }
1931      }
1932
1933   elsif ($params{oknumeric}) {
1934      my $ok;
1935      $params{numbersalign} = "" if $params{numbersalign} eq "default";
1936      $params{numbersvalueformat} = "" if $params{numbersvalueformat} eq "default";
1937      if ($params{numbersedefault}) {
1938         $ok = execute_sheet_command_and_log(\%sheetdata, "set sheet defaultnontextformat $params{numbersalign}", \%headerdata);
1939         $ok = execute_sheet_command_and_log(\%sheetdata, "set sheet defaultnontextvalueformat $params{numbersvalueformat}", \%headerdata) if $ok;
1940         }
1941      else {
1942         $ok = execute_sheet_command_and_log(\%sheetdata, "set $editcoords cellformat $params{numbersalign}", \%headerdata);
1943         $ok = execute_sheet_command_and_log(\%sheetdata, "set $editcoords nontextvalueformat $params{numbersvalueformat}", \%headerdata) if $ok;
1944         }
1945      if ($ok) {
1946         $sheetmodified += 1;
1947         }
1948      else {
1949         $params{debugmessage} = "Failed: $ok\n";
1950         }
1951      }
1952
1953   elsif ($params{okeditrange}) {
1954      my $errtxt = execute_edit_command(\%params, \%sheetdata, $editcoords, \%headerdata);
1955      if ($errtxt) {
1956         $params{debugmessage} = "Failed: $errtxt\n";
1957         }
1958      else {
1959         if ($params{recalc} ne "off" && $params{okeditrange}!~/merge|copy/) {
1960            my $errtext = recalc_sheet(\%sheetdata);
1961            }
1962         else {
1963            if ($params{okeditrange}!~/merge|copy|recalc/) {
1964               $sheetdata{sheetattribs}->{needsrecalc} = "yes" if $params{okeditrange}!~/merge|copy/;
1965               }
1966            }
1967         $editcoords = $params{editcoords};
1968         $sheetmodified += 1;
1969         }
1970      }
1971
1972   elsif ($params{dorecalc}) {
1973      my $errtext = recalc_sheet(\%sheetdata);
1974      $sheetmodified += 1;
1975      }
1976
1977   elsif ($params{oktoolspageproperties}) {
1978      $headerdata{fullname} = $params{editlongname};
1979      if ($params{edittemplatetype} eq "default") {
1980         $headerdata{templatefile} = "";
1981         $headerdata{templatetext} = "";
1982         }
1983      if ($params{edittemplatetype} eq "shared") {
1984         $headerdata{templatefile} = $params{edittemplatelist};
1985         $headerdata{templatetext} = "";
1986         }
1987      if ($params{edittemplatetype} eq "explicit") {
1988         $headerdata{templatefile} = "";
1989         $headerdata{templatetext} = $params{edittemplatetext};
1990         }
1991      $headerdata{publishhtml} = $params{editpublishhtml} ? "yes" : "no";
1992      $headerdata{publishsource} = $params{editpublishsource};
1993      $headerdata{publishjs} = $params{editpublishjs};
1994      $headerdata{viewwithoutlogin} = $params{editviewwithoutlogin};
1995      add_to_editlog(\%headerdata, "# $WKCStrings{logeditedpageproperties}");
1996      $sheetmodified += 1;
1997      }
1998
1999   elsif ($params{okeditcomments}) {
2000      $headerdata{editcomments} = $params{editcomments}; # no need to log -- this is different for each backup
2001      $sheetmodified += 1;
2002      }
2003
2004   elsif ($params{oktoolsloadfromtext} || $params{oktoolsloadfromsheet}) {
2005      my $errtxt = execute_tools_command(\%params, \%sheetdata, $editcoords, \%headerdata);
2006      if ($errtxt) {
2007         $params{debugmessage} = "Failed: $errtxt\n";
2008         }
2009      else {
2010         if ($params{recalc} ne "off") {
2011            my $errtext = recalc_sheet(\%sheetdata);
2012            }
2013         else {
2014            $sheetdata{sheetattribs}->{needsrecalc} = "yes";
2015            }
2016         $editcoords = $params{editcoords};
2017         $sheetmodified += 1;
2018         }
2019      }
2020
2021   elsif ($params{oktoolsuseradmin}) {
2022      if ($params{loggedinadmin} eq "yes") { # must be admin
2023         my $errtxt = execute_tools_useradmincommand(\%params);
2024         }
2025      }
2026
2027   #
2028   # *** Save sheet if modifications
2029   #
2030
2031   if ($sheetmodified && $params{sitename}) {
2032      my $sheetcontents = create_sheet_save(\%sheetdata);
2033
2034      $headerdata{lastmodified} = $start_clock_time; # remember it in header
2035
2036      my $sitedata = $hostinfo->{sites}->{$params{sitename}};
2037      my $thisauthor = $sitedata->{authoronhost}; # Get author name
2038      if ($sitedata->{authorfromlogin} eq "yes" && $params{loggedinusername}) {
2039         $thisauthor = $params{loggedinusername};
2040         $thisauthor =~ s/[^a-z0-9\-]//g;
2041         }
2042      $headerdata{lastauthor} = $thisauthor;
2043      my $headercontents = create_header_save(\%headerdata);
2044
2045      $ok = save_page($editpath, $headercontents, $sheetcontents);
2046
2047      }
2048
2049   #
2050   # *** See if need to rename page
2051   #
2052
2053   if ($params{oktoolspageproperties} && $params{editpagename} ne $params{datafilename} && $params{sitename}) {
2054      my $newname = $params{editpagename};
2055      $newname =~ s/[^a-z0-9\-]//g;
2056      $params{toolsmessage} = rename_existing_page(\%params, $hostinfo, $params{sitename}, $params{datafilename}, $newname);
2057      $params{etpurl} = "";
2058      }
2059
2060   # # # # # # # # # #
2061   #
2062   # Output command section (if necessary) and data depending upon mode
2063   #
2064   # # # # # # # # # #
2065
2066   my $template_stylemiddle = $template_stylemiddle_verbose;
2067   $params{promptlevelspacing} = "<br>";
2068
2069
2070   my $fullname = special_chars($headerdata{fullname}); # used in some places
2071   my $fullsitename = special_chars($hostinfo->{sites}->{$params{sitename}}->{longname});
2072
2073   my $programnametop = $WKCStrings{programextratop} ? "$WKCStrings{programextratop}&nbsp;" : "";
2074   $programnametop .= "$programmark&nbsp;$programversion";
2075
2076   #
2077   # Top line of screen with page name and login/logout stuff
2078   #
2079
2080   $rbody1 = <<"EOF"; # Top line of screen
2081</head>
2082<body onload="setactions();setf()">
2083<div class="programtop">
2084<form name="flo" method="POST">
2085<table cellspacing="0" cellpadding="0"><tr><td width="100%" valign="top">
2086EOF
2087   $rbody1 .= <<"EOF" if $params{datafilename} && $currenttab ne $WKCStrings{"Page"};
2088<span class="programtopdark">$fullname</span>
2089<span class="programtoplight">($params{datafilename}.html $WKCStrings{"topon"} $fullsitename)</span>
2090EOF
2091   $rbody1 .= <<"EOF" if $params{loggedinusername}; # stay with POST
2092<span class="programtoplight">$WKCStrings{"topuser"}: $params{loggedinusername}</span>
2093<span class="programtoplogout">[<a href="" onClick="document.flo.submit();return false;">$WKCStrings{"toplogout"}</a>]</span>
2094<input name="dologout" type="hidden" value="1">
2095EOF
2096   if ($headerdata{reverted} && $currenttab ne $WKCStrings{"Page"}) {
2097      $headerdata{reverted} =~ m/^.+?\.\w+?\.([0-9\-]+)\.txt/;
2098      $rbody1 .= <<"EOF"
2099<br>
2100<span class="programtoprevert">$WKCStrings{"toprevert"} $1</span>
2101EOF
2102      }
2103   $rbody1 .= <<"EOF";
2104</td><td valign="top"><div class="programtopname">$programnametop</div></td></tr></table></form>
2105</div>
2106EOF
2107
2108   my $etpurlencoded = url_encode_plain($params{etpurl});
2109   my $hiddenfields = <<"EOF";
2110<input type="hidden" name="editcoords" value="$editcoords">
2111<input type="hidden" name="editmode" value="$editmode">
2112<input type="hidden" name="scrollrow" value="$params{scrollrow}">
2113<input type="hidden" name="formatmode" value="$params{formatmode}">
2114<input type="hidden" name="securitycode" value="$securitycode">
2115<input type="hidden" name="currenttab" value="$currenttab">
2116<input type="hidden" name="datafilename" value="$params{datafilename}">
2117<input type="hidden" name="sitename" value="$params{sitename}">
2118<input type="hidden" name="loggedinusername" value="$params{loggedinusername}">
2119<input type="hidden" name="loggedinuserpassword" value="$params{loggedinuserpassword}">
2120<input type="hidden" name="etpurl" value="$etpurlencoded">
2121EOF
2122
2123   $rbodytabs2 .= <<"EOF";
2124</form>
2125EOF
2126
2127   # # # # # # # # # #
2128   #
2129   # Do login
2130   #
2131   # # # # # # # # # #
2132
2133   if ($needlogin) {
2134      $response .= $template_headertop . $template_styletop . $template_stylemiddle . $template_stylebottom .
2135                   $template_headerscripts . $rbody1 . $rbodytabs . $hiddenfields . $rbodytabs2;
2136
2137      my $descstring = $loginerrtext;
2138
2139      my $etpstr;
2140      $etpstr = <<"EOF" if $params{logineditthispage};
2141<input name="editthispage" type="hidden" value="$params{logineditthispage}">
2142EOF
2143
2144      $response .= <<"EOF";
2145<table cellpadding="0" cellspacing="0" width="100%">
2146<tr>
2147<td class="ttbody" width="100%">
2148<div class="sectionoutlined">
2149<div class="pagetitle">$WKCStrings{"logintitle"}</div>
2150<div class="pagetitledesc">$descstring</div>
2151</div>
2152<form name="f0" method="POST">
2153<div class="sectionplain">
2154<table cellpadding="0" cellspacing="0">
2155<tr>
2156<td><div class="title">$WKCStrings{"loginname"}&nbsp;</div></td>
2157<td><div style="margin-bottom:2pt;"><input type="text" name="editusername" size="15" value=""></div></td>
2158<td><div class="title">&nbsp;$WKCStrings{"loginpassword"}&nbsp;</div></td>
2159<td><div style="margin-bottom:2pt;"><input type="password" name="edituserpassword" size="10" value=""></div></td>
2160<td>&nbsp;<input type="submit" name="dologin" value="$WKCStrings{"loginlogin"}"></td>
2161</tr>
2162</table>
2163<input type="radio" name="editusercookies" value="session" CHECKED><span class="smaller">$WKCStrings{"loginsessioncookie"}</span>&nbsp;
2164<input type="radio" name="editusercookies" value="$defaultcookieexpire"><span class="smaller">$WKCStrings{"loginlongcookie"}</span>
2165<br>
2166</div>
2167$etpstr
2168$hiddenfields
2169</form>
2170</td></tr>
2171</table>
2172<script>
2173var setf = function() {document.f0.editusername.focus();}
2174</script>
2175EOF
2176      }
2177
2178   # # # # # # # # # #
2179   #
2180   # Do you want to edit this page when others are already editing it?
2181   #
2182   # # # # # # # # # #
2183
2184   elsif ($checkmultiedit) {
2185      $response .= $template_headertop . $template_styletop . $template_stylemiddle . $template_stylebottom .
2186                   $template_headerscripts . $rbody1 . $rbodytabs . $hiddenfields . $rbodytabs2;
2187
2188      my $descstring = $loginerrtext;
2189
2190      $response .= <<"EOF";
2191<table cellpadding="0" cellspacing="0" width="100%">
2192<tr>
2193<td class="ttbody" width="100%">
2194<div class="sectionoutlined">
2195<div class="pagetitle">$WKCStrings{"multiedittitle"}</div>
2196<div class="pagetitledesc">$WKCStrings{"multieditdoyou"} "$params{datafilename}" $WKCStrings{""}
2197$WKCStrings{"multiediteventhough2"}
2198</div>
2199</div>
2200<form name="f0" method="POST">
2201<div class="sectionplain">
2202$WKCStrings{"multieditpage"} "$params{datafilename}" $WKCStrings{"multieditonsite"} "$params{sitename}" $WKCStrings{"multieditisopen"}: $checkmultiedit<br><br>
2203<input type="submit" name="okmultieditthispage" value="$WKCStrings{"multiedityes"}">
2204<input type="submit" name="cancelmultieditthispage" value="$WKCStrings{"multieditno"}">
2205</div>
2206<input name="editthispage" type="hidden" value="$params{checkmultiediteditthispage}">
2207<input name="editthispageokmulti" type="hidden" value="1">
2208$hiddenfields
2209</form>
2210</td></tr>
2211</table>
2212EOF
2213      }
2214
2215   # # # # # # # # # #
2216   #
2217   # Just published
2218   #
2219   # # # # # # # # # #
2220
2221   elsif ($published) {
2222      $response .= $template_headertop . $template_styletop . $template_stylemiddle . $template_stylebottom .
2223                   $template_headerscripts . $rbody1 . $rbodytabs . $hiddenfields . $rbodytabs2;
2224
2225      my $descstring = $params{datafilename} ? $WKCStrings{"publisheddescopen"} : $WKCStrings{"publisheddescclosed"};
2226
2227      my $pagemessage;
2228      if ($params{pagemessage}) { # Publish info
2229         $pagemessage = <<"EOF";
2230<div class="sectionerror">
2231$params{pagemessage}
2232</div>
2233EOF
2234         }
2235
2236      $response .= <<"EOF";
2237<table cellpadding="0" cellspacing="0" width="100%">
2238<tr>
2239<td class="ttbody" width="100%">
2240<div class="sectionoutlined">
2241<div class="pagetitle">$WKCStrings{"publishedtitle"}$published</div>
2242<div class="pagetitledesc">$descstring</div>
2243</div>
2244$pagemessage
2245<form name="f0" method="POST">
2246<div class="sectionplain">
2247EOF
2248      if ($params{datafilename}) { # continue
2249         $response .= <<"EOF";
2250<input type="submit" name="publishcontinue" value="$WKCStrings{"publishcontinueediting"}">
2251EOF
2252         }
2253      else {
2254         $response .= <<"EOF";
2255<input type="submit" name="publishcontinue" value="$WKCStrings{"publishviewpagelist"}">
2256EOF
2257         }
2258
2259      if ($params{editpublishhtml} eq "yes" && $hostinfo->{sites}->{$params{sitename}}->{htmlurl}) { # HTML accessible
2260         $response .= <<"EOF";
2261<input type="submit" value="$WKCStrings{"publishviewhtmlpage"}" onclick="location.href='$hostinfo->{sites}->{$params{sitename}}->{htmlurl}/$published.html';return false;">
2262EOF
2263         }
2264      my $editurl = $hostinfo->{sites}->{$params{sitename}}->{editurl};
2265      if ($editurl) {
2266         $response .= <<"EOF";
2267<input type="submit" value="$WKCStrings{"publishviewlivepage"}" onclick="location.href='$editurl?view=$params{sitename}/$published';return false;">
2268EOF
2269        }
2270      $response .= <<"EOF";
2271</div>
2272EOF
2273      if ($params{etpurl}) {
2274         my $etpurlencode = url_encode_plain($params{etpurl});
2275         $response .= <<"EOF";
2276<br>
2277<div class="sectionoutlined">
2278<div style="padding-bottom:4pt;"><input type="submit" value="$WKCStrings{"publishresume"}" onclick="location.href='$etpurlencode';return false;"></div>
2279<div class="pagetitledesc">$WKCStrings{"publishresumedesc"}<br>
2280$params{etpurl}
2281</div>
2282</div>
2283<br>
2284EOF
2285         }
2286      $response .= <<"EOF";
2287$hiddenfields
2288</form>
2289</td></tr>
2290</table>
2291EOF
2292      }
2293
2294   # # # # # # # # # #
2295   #
2296   # Page mode
2297   #
2298   # # # # # # # # # #
2299
2300   elsif ($currenttab eq $WKCStrings{"Page"}) {
2301      my $pcstr = do_page_command(\%params, $loggedinuser, \%userinfo, $hiddenfields);
2302
2303      $hiddenfields = update_hiddenfields(\%params, $hiddenfields);
2304
2305      $response .= $template_headertop . $template_styletop . $template_stylemiddle . $template_stylebottom .
2306                   $template_headerscripts . $rbody1 . $rbodytabs . $hiddenfields . $rbodytabs2 . $pcstr;
2307
2308      }
2309
2310   # # # # # # # # # #
2311   #
2312   # Preview mode (Publish tab)
2313   #
2314   # # # # # # # # # #
2315
2316   elsif ($currenttab eq $WKCStrings{"Preview"}) {
2317
2318      my $editcommentsnl = special_chars_nl($headerdata{editcomments});
2319
2320      # Load scripts from a file
2321
2322      $inlinescripts .= $WKCStrings{"jsdefinestrings"};
2323      open JSFILE, "$WKCdirectory/WKCjs.txt";
2324      while (my $line = <JSFILE>) {
2325         $inlinescripts .= $line;
2326         }
2327      close JSFILE;
2328
2329      open JSFILE, "$WKCdirectory/WKCpreviewjs.txt";
2330      while (my $line = <JSFILE>) {
2331         $inlinescripts .= $line;
2332         }
2333      close JSFILE;
2334
2335      my $onclickstr = q! onclick="rcc('$coord');"!;
2336      my $linkstyle = "?view=$params{sitename}/[[pagename]]";
2337      my ($stylestr, $outstr) = render_sheet(\%sheetdata, 'class="wkcsheet"', "", "s", "a", $editmode, $editcoords, $onclickstr, $linkstyle);
2338
2339      $response .= $template_headertop . $template_styletop . $template_stylemiddle . $stylestr . $template_stylebottom .
2340                   $template_headerscripts . $rbody1 . $rbodytabs . $hiddenfields . $rbodytabs2;
2341
2342      my ($publishhtmlchecked, $publishsourcechecked, $publishjschecked, $viewwithoutloginchecked);
2343      $publishhtmlchecked = " CHECKED" if $headerdata{publishhtml} ne "no";
2344      $publishsourcechecked = " CHECKED" if $headerdata{publishsource} eq "yes";
2345      $publishjschecked = " CHECKED" if $headerdata{publishjs} eq "yes";
2346      $viewwithoutloginchecked = " CHECKED" if $headerdata{viewwithoutlogin} eq "yes";
2347
2348      $response .= <<"EOF";
2349<table cellpadding="0" cellspacing="0" width="100%">
2350<tr><td class="ttbody" width="100%"><form name="f0" method="POST">
2351<div style="margin:4px 0px 4px 0px;">
2352 <table class="buttonbar" cellspacing="0" cellpadding="0"><tr>
2353  <td id="publishbutton"><input class="smaller" type="submit" name="bpublish" value="$WKCStrings{"previewpublish"}" onclick="this.blur();return switchto('publish');">
2354  </td><td id="viewbutton"><input class="smaller" type="submit" name="bview" value="$WKCStrings{"previewviewonweb"}" onclick="this.blur();return switchto('view');">
2355  </td><td id="helpbutton"><input class="smaller" type="submit" name="bhelp" value="$WKCStrings{"previewhelp"}" onclick="toggle_help('previewhelptext');this.blur();return false;">
2356  </td>
2357 </tr></table>
2358</div>
2359
2360<div id="c1publish" style="display:none;">
2361<span class="smaller">
2362<b>$WKCStrings{"previewpublishtoserver"}:</b> $hostinfo->{sites}->{$params{sitename}}->{htmlurl}/$params{datafilename}.html
2363</span>
2364<br>
2365<span class="smaller"><b>Options:</b></span>
2366<input type="checkbox" name="editpublishhtml" value="yes"$publishhtmlchecked><span class="smaller">$WKCStrings{"previewpublishhtml"}</span>
2367<input type="checkbox" name="editpublishsource" value="yes"$publishsourcechecked><span class="smaller">$WKCStrings{"previewpublishsource"}</span>
2368<input type="checkbox" name="editpublishjs" value="yes"$publishjschecked><span class="smaller">$WKCStrings{"previewpublishjs"}</span>
2369<input type="checkbox" name="editviewwithoutlogin" value="yes"$viewwithoutloginchecked><span class="smaller">$WKCStrings{"previewpublishnologin"}</span>
2370<br>
2371<span class="smaller">$WKCStrings{"previewcomments"}</span>
2372<br>
2373<textarea cols="60" rows="3" name="editcomments">
2374$editcommentsnl</textarea>
2375<br>
2376<div class="smaller" style="margin:6px 0px;">
2377$WKCStrings{"previewcomments2"}
2378</div>
2379<input type="submit" class="smaller" name="okeditcomments" value="$WKCStrings{"previewsavecomments"}">
2380<input type="submit" class="smaller" name="publishcontinue:$params{datafilename}" value="$WKCStrings{"previewpublishcontinue"}">
2381<input type="submit" class="smaller" name="publishpage:$params{datafilename}" value="$WKCStrings{"previewpublishclose"}">
2382</div>
2383
2384<div id="c1view" style="display:none;">
2385<div class="smaller" style="margin-bottom:6px;">
2386$WKCStrings{"previewview1"}
2387</div>
2388<table cellspacing="0" cellpadding="0">
2389EOF
2390
2391      my $editurl = $hostinfo->{sites}->{$params{sitename}}->{editurl};
2392      if ($editurl) {
2393         $response .= <<"EOF";
2394<tr>
2395<td align="center" style="padding:0px 2pt 2pt 0px;"><input type="submit" class="smaller" value="$WKCStrings{"previewlivethis"}" onclick="location.href='$editurl?view=$params{sitename}/$params{datafilename}';return false;"></td>
2396<td align="center" style="padding:0px 2pt 2pt 0px;"><input type="submit" class="smaller" value="$WKCStrings{"previewlivepopup"}" onclick="window.open('$editurl?view=$params{sitename}/$params{datafilename}');return false;"></td>
2397</tr>
2398EOF
2399         }
2400      else {
2401         $response .= <<"EOF";
2402<tr>
2403<td colspan="2"><span class="warning">
2404$WKCStrings{"previewnolive"}
2405</span>
2406</td>
2407</tr>
2408EOF
2409         }
2410
2411      if ($hostinfo->{sites}->{$params{sitename}}->{htmlurl}) {
2412         $response .= <<"EOF";
2413<tr>
2414<td align="center" style="padding:0px 2pt 2pt 0px;"><input type="submit" class="smaller" value="$WKCStrings{"previewplainthis"}" onclick="location.href='$hostinfo->{sites}->{$params{sitename}}->{htmlurl}/$params{datafilename}.html';return false;"></td>
2415<td align="center" style="padding:0px 2pt 2pt 0px;"><input type="submit" class="smaller" value="$WKCStrings{"previewplainpopup"}" onclick="window.open('$hostinfo->{sites}->{$params{sitename}}->{htmlurl}/$params{datafilename}.html');return false;"></td>
2416</tr>
2417EOF
2418         }
2419      else {
2420         $response .= <<"EOF";
2421<tr>
2422<td colspan="2"><span class="warning">
2423$WKCStrings{"previewnohtml"}
2424</span>
2425</td>
2426</tr>
2427EOF
2428         }
2429
2430      $response .= <<"EOF";
2431</table>
2432</div>
2433
2434<div id="helptext" style="width:500px;padding:10px 0px 10px 0px;display:none;">
2435 <div style="border-top:1px solid black;border-left:1px solid black;border-right:1px solid black;color:white;background-color:#66CC66;">
2436 <table cellspacing="0" cellpadding="0"><tr><td align="center" width="100%" class="smaller"><b>$WKCStrings{"helphelp"}</b></td>
2437 </td><td align="right"><input class="smaller" type="submit" name="hidehelp" value="$WKCStrings{"helphide"}" onClick="toggle_help('');this.blur();return false;"></td>
2438 </tr></table></div>
2439 <div id="helpbody" class="smaller" style="height:200px;overflow:auto;background-color:white;padding:4px;border:1px solid black;">
2440  $WKCStrings{"helpnotloaded"}
2441 </div>
2442</div>
2443
2444<br>
2445$hiddenfields
2446$inlinescripts
2447</form>
2448</td></tr></table>
2449<br>
2450<script>
2451<!--
2452function rcc(c) {document.ftabs.editcoords.value=c;document.ftabs.newtab[1].click();1;}
2453var setf = function() {switchto("publish");}
2454// -->
2455</script>
2456EOF
2457
2458      $response .= <<"EOF";
2459$outstr
2460<br>
2461EOF
2462
2463      }
2464
2465   # # # # # # # # # #
2466   #
2467   # Edit mode
2468   #
2469   # # # # # # # # # #
2470
2471   elsif ($currenttab eq $WKCStrings{"Edit"}) {
2472
2473      # Load scripts from a file
2474
2475      $inlinescripts .= $WKCStrings{"jsdefinestrings"};
2476      open JSFILE, "$WKCdirectory/WKCjs.txt";
2477      while (my $line = <JSFILE>) {
2478         $inlinescripts .= $line;
2479         }
2480      close JSFILE;
2481
2482      $inlinescripts .= $WKCStrings{"editjsdefinestrings"};
2483      open JSFILE, "$WKCdirectory/WKCeditjs.txt";
2484      while (my $line = <JSFILE>) {
2485         $inlinescripts .= $line;
2486         }
2487      close JSFILE;
2488
2489      my $coord = $editcoords;
2490      $coord =~ s/:.*$//; # only first cell
2491      my $cellcontents;
2492      if ($sheetdata{datatypes}->{$coord} eq "f" || $sheetdata{datatypes}->{$coord} eq "c") {
2493         $cellcontents = $sheetdata{formulas}->{$coord} if $sheetdata{datatypes}->{$coord} eq "f";
2494         }
2495      else {
2496         $cellcontents = $sheetdata{datavalues}->{$coord};
2497         }
2498      my $cellcontentsnl = special_chars_nl($cellcontents);
2499
2500      my $onclickstr = q! onclick="rc0('$coord');"!;
2501
2502      my $linkstyle = "?view=$params{sitename}/[[pagename]]";
2503      my ($stylestr, $outstr) = render_sheet(\%sheetdata, 'id="sheet0" class="wkcsheet"', "", "s", "a", "ajax", $coord, q! onclick="rc0('$coord');"!, , $linkstyle);
2504
2505      $rbody1 =~ s/<body /<body onKeydown="return ev1(event);" onKeypress="return ev2(event);" onLoad="save_initial_sheet_data();move_cursor(ecell,ecell,true);" /;
2506
2507      $response .= $template_headertop . $template_styletop . $template_stylemiddle . $stylestr . $template_stylebottom .
2508                   $template_headerscripts . $rbody1;
2509
2510      $response .= <<"EOF";
2511$rbodytabs
2512$hiddenfields
2513$rbodytabs2
2514<table cellspacing="0" cellpadding="0" width="100%">
2515<tr><td class="ttbody" width="100%">
2516<form name="f0" method="POST">
2517<table cellpadding="0" cellspacing="0">
2518EOF
2519
2520      my %afterentercheck;
2521      if ($params{afterenter}) {
2522         $afterentercheck{$params{afterenter}} = " CHECKED";
2523         }
2524      else {
2525         $afterentercheck{same} = " CHECKED";
2526         }
2527
2528      my %celldata;
2529      my ($lcol, $lrow) = render_values_only(\%sheetdata, \%celldata, $linkstyle);
2530      my $jsdata = qq!var isheet="";\nisheet="!;
2531
2532      foreach my $cr (sort keys %celldata) { # construct output
2533         my $cellspecifics = $celldata{$cr};
2534         my $displayvalue = encode_for_save($cellspecifics->{display});
2535         $displayvalue = "" if $displayvalue eq "&nbsp;"; # this is the default
2536         my $csssvalue = encode_for_save($sheetdata{cellattribs}->{$cr}->{csss}); # need this to scroll back
2537         my $editvalue;
2538         if ($sheetdata{datatypes}->{$cr} eq 'f' || $sheetdata{datatypes}->{$cr} eq 'c') { # formula or constant
2539            $editvalue = encode_for_save($sheetdata{formulas}->{$cr});
2540            }
2541         else {
2542            $editvalue = encode_for_save($sheetdata{datavalues}->{$cr});
2543            }
2544         my $str = "$cr:$cellspecifics->{type}:$displayvalue:$editvalue:$cellspecifics->{align}:$cellspecifics->{colspan}:$cellspecifics->{rowspan}:$cellspecifics->{skip}:$csssvalue";
2545         $str =~ s/\\/\\\\/g;
2546         $str =~ s/"/\\x22/g;
2547         $str =~ s/</\\x3C/g;
2548         $jsdata .= "$str\\n";
2549         }
2550      $jsdata .= qq!"\n!;
2551
2552      if ($sheetdata{sheetattribs}->{circularreferencecell}) {
2553         my ($from, $to) = split(/\|/, $sheetdata{sheetattribs}->{circularreferencecell});
2554         my $str = "$WKCStrings{editcircular1}$from$WKCStrings{editcircular2}$to";
2555         $jsdata .= qq!isheet=isheet+"error:$str\\n";\n!;
2556         }
2557
2558      $response .= <<"EOF";
2559<tr>
2560 <td class="smaller">&nbsp;</td>
2561 <td valign="bottom">
2562  <div id="mode1"><span class="smaller" id="valuetype">$WKCStrings{"editloading"}</span>&nbsp;<span id="warning" class="warning">&nbsp;</span>
2563   <span id="recalcmsg" class="smaller" style="font-style:italic;display:none">&nbsp;$WKCStrings{"editrecalcneeded"}</span></div>
2564  <div id="mode4" style="display:none;"><span class="smaller">$WKCStrings{"editrange"}</span>
2565   <span class="smaller" style="font-style:italic;color:gray;">$WKCStrings{"editextendrange"}</span></div>
2566  <div id="mode5" class="smaller" style="display:none;">$WKCStrings{"editmoreedit"}
2567   <span style="font-style:italic;color:gray;">$WKCStrings{"editesctoreturn"}</span></div>
2568  <div id="mode6" class="smaller" style="display:none;">$WKCStrings{"editdatatable"}
2569   <span style="font-style:italic;color:gray;">$WKCStrings{"editesctoreturn"}</span></div>
2570 </td>
2571</tr>
2572<tr>
2573 <td valign="top" width="1">
2574  <span id="coordtext">$editcoords</span>&nbsp;
2575 </td>
2576 <td valign="bottom" width="100%" nowrap><span id="config1a">
2577   <input class="smaller" type="text" size="80" name="valueedit" value="" autocomplete="off" onFocus="ve_focus()">
2578    <input class="smaller" type="submit" name="okedit" id="okeditve" value="$WKCStrings{"editok"}" onClick="this.blur();return process_OK();">
2579   </span><span id="config2a" style="display:none;">
2580   <input class="smaller" style="font-style:italic;" type="text" size="80" value="$WKCStrings{"editmultilinereq"}" disabled>
2581   </span><span id="config45a" style="display:none;"><span id="rangeend"></span><span id="kbdprompt" class="smaller"></span></span><span id="config3a" style="display:none;">
2582    <textarea cols="80" rows="10" name="valueedittext" onFocus="vet_focus()">
2583$cellcontentsnl</textarea>
2584   </span>
2585 </td>
2586</tr>
2587<tr>
2588 <td></td>
2589 <td valign="top"><div id="config1c">
2590  <input class="smaller" type="submit" name="switcheditvet" id="switcheditvet" value='$WKCStrings{"editmultilineedit"}' onClick="set_editconfig(3);document.f0.valueedittext.focus();return false;">
2591  <input class="smaller" type="submit" name="range" value="$WKCStrings{"editrange"}" onClick="this.blur();range_button();return false;">
2592  <input class="smaller" type="submit" name="range" value="$WKCStrings{"editmore"}" onClick="this.blur();more_button();return false;">
2593  <input class="smaller" type="submit" name="canceledit" value="$WKCStrings{"editcancel"}" onClick="this.blur();process_typed_char('[esc]');return false;">
2594  <input class="smaller" type="submit" name="help" value="$WKCStrings{"edithelp"}" onClick="toggle_help('edithelptext');this.blur();return false;">
2595  <input id="dorecalcbutton" class="smaller" type="submit" name="dorecalcbutton" value="$WKCStrings{"editrecalconce"}" style="display:none;" onClick="document.f0.dorecalc.value=1;">
2596  </div><div id="config3c" style="display:none;">
2597  <input class="smaller" type="submit" name="okeditvet" value="$WKCStrings{"editok"}" onClick="this.blur();return process_OK();">
2598  <input class="smaller" type="submit" name="blankvet" value="$WKCStrings{"editblank"}" onClick="document.f0.valueedittext.value='';val='';document.f0.valueedittext.focus();return false;">
2599  <input class="smaller" type="submit" name="pagelink" value="$WKCStrings{"editlinktopage"}" onClick="this.blur();update_page_list();return false;">
2600  <input class="smaller" type="submit" name="canceleditvet" value="$WKCStrings{"editcancel"}" onClick="this.blur();process_typed_char('[esc]');return false;">
2601  <input class="smaller" type="submit" name="helpvet" value="$WKCStrings{"edithelp"}" onClick="toggle_help('edithelptext');this.blur();return false;">
2602  </div>
2603 </td>
2604</tr>
2605<tr>
2606 <td></td>
2607 <td>
2608  <div id="config4d" style="display:none;">
2609   <input id="mergebutton" class="smaller" type="submit" name="okeditrange:merge" value="$WKCStrings{"editmergecells"}" onClick="set_range('all');document.f0.okeditrange.value='merge';">
2610   <input id="unmergebutton" class="smaller" type="submit" name="okeditrange:unmerge" value="$WKCStrings{"editunmerge"}" onClick="set_more('all');document.f0.okeditrange.value='unmerge';">
2611   <input class="smaller" type="submit" name="okeditrange:copy" value="$WKCStrings{"editcopy"}" onClick="set_range('all');">
2612   <input class="smaller" type="submit" name="okeditrange:cut" value="$WKCStrings{"editcut"}">
2613   <input class="smaller" type="submit" name="okeditrange:erase" value="$WKCStrings{"editerase"}">
2614   <input class="smaller" type="submit" name="okeditrange:fillright" value="$WKCStrings{"editfillright"}" onClick="set_range('all');">
2615   <input class="smaller" type="submit" name="okeditrange:filldown" value="$WKCStrings{"editfilldown"}" onClick="set_range('all');">
2616   <input class="smaller" type="submit" name="tables" value="$WKCStrings{"edittable"}" onClick="this.blur();table_button();return false;">
2617   <input class="smaller" type="submit" name="cancelrange" value="$WKCStrings{"editcancel"}" onClick="this.blur();cancel_range();return false;">
2618   <input class="smaller" type="submit" name="rangehelp" value="$WKCStrings{"edithelp"}" onClick="toggle_help('rangehelptext');this.blur();return false;">
2619   <span class="smaller">
2620    <input type="radio" name="editparts" value="all" CHECKED>$WKCStrings{"editall"}
2621    <input type="radio" name="editparts" value="formulas">$WKCStrings{"editcontents"}
2622    <input type="radio" name="editparts" value="formats">$WKCStrings{"editformats"}
2623   </span>
2624  </div><div id="config5d" style="display:none;">
2625   <div style="margin:6px 0px 6px 0px;">
2626    <input class="smaller" type="submit" name="range" value="$WKCStrings{"editrange"}" onClick="this.blur();range_button();return false;">
2627    <input class="smaller" type="submit" name="okeditrange:paste" value="$WKCStrings{"editpasteall"}" onClick="set_more('all');">
2628    <input class="smaller" type="submit" name="okeditrange:paste" value="$WKCStrings{"editpastecontents"}" onClick="set_more('formulas');">
2629    <input class="smaller" type="submit" name="okeditrange:paste" value="$WKCStrings{"editpasteformats"}" onClick="set_more('formats');">
2630    <input id="recalcmanualbutton" class="smaller" type="submit" name="okeditrange:recalcmanual" value="$WKCStrings{"editrecalcmanual"}">
2631    <input id="recalcautobutton" class="smaller" type="submit" name="okeditrange:recalcauto" value="$WKCStrings{"editrecalcauto"}" style="display:none;">
2632   </div>
2633   <input class="smaller" type="submit" name="okeditrange:insertrow" value="$WKCStrings{"editinsertrow"}" onClick="set_more('all');">
2634   <input class="smaller" type="submit" name="okeditrange:insertcol" value="$WKCStrings{"editinsertcol"}" onClick="set_more('all');">
2635   <input class="smaller" type="submit" name="okeditrange:deleterow" value="$WKCStrings{"editdeleterow"}" onClick="set_more('all');">
2636   <input class="smaller" type="submit" name="okeditrange:deletecol" value="$WKCStrings{"editdeletecol"}" onClick="set_more('all');">
2637   <input class="smaller" type="submit" name="cancelrange2" value="$WKCStrings{"editcancel"}" onClick="this.blur();cancel_more();return false;">
2638   <input class="smaller" type="submit" name="rangehelp2" value="$WKCStrings{"edithelp"}" onClick="toggle_help('editmorehelptext');this.blur();return false;">
2639  </div><div id="config6d" style="display:none;"><div id="config6d1">
2640    <input class="smaller" type="submit" name="tablesort" value="$WKCStrings{"editsort"}" onClick="this.blur();set_kbdprompt('/RTS');return false;">
2641    <input class="smaller" type="submit" name="canceltable" value="$WKCStrings{"editcancel"}" onClick="this.blur();cancel_table();return false;">
2642    <input class="smaller" type="submit" name="canceltable" value="$WKCStrings{"edithelp"}" onClick="toggle_help('edittablehelptext');this.blur();return false;">
2643   </div><div id="config6d2">
2644    <table cellspacing="0" cellpadding="0" style="border:1px solid black;background-color:white;margin:6px 0px 4px 0px;">
2645     <tr><td colspan="13" style="background-color:gray;color:white;font-weight:bold;text-align:center;">$WKCStrings{"editsorttitle"}</td></tr><tr>
2646      <td>&nbsp;</td>
2647      <td style="font-size:smaller;font-weight:bold;color:black">$WKCStrings{"editmajorsort"}</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;&nbsp;</td>
2648      <td style="font-size:smaller;font-weight:bold;color:black">$WKCStrings{"editminorsort"}</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;&nbsp;</td>
2649      <td style="font-size:smaller;font-weight:bold;color:black">$WKCStrings{"editlastsort"}</td><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;&nbsp;</td>
2650     </tr><tr>
2651      <td>&nbsp;</td>
2652      <td valign="top"><select name="sort1" size="1"><option value="" selected><option value="B">Column B<option value="C">Column C</select></td><td>&nbsp;</td>
2653      <td>
2654       <input name="sortorder1" type="radio" value="up" checked><span class="smaller">$WKCStrings{"editascending"}</span><br>
2655       <input name="sortorder1" type="radio" value="down"><span class="smaller">$WKCStrings{"editdescending"}</span><br>
2656      </td><td>&nbsp;</td>
2657      <td valign="top"><select name="sort2" size="1"><option value="" selected><option value="B">Column B<option value="C">Column C</select></td><td>&nbsp;</td>
2658      <td>
2659       <input name="sortorder2" type="radio" value="up" checked><span class="smaller">$WKCStrings{"editascending"}</span><br>
2660       <input name="sortorder2" type="radio" value="down"><span class="smaller">$WKCStrings{"editdescending"}</span><br>
2661      </td><td>&nbsp;</td>
2662      <td valign="top"><select name="sort3" size="1"><option value="" selected><option value="B">Column B<option value="C">Column C</select></td><td>&nbsp;</td>
2663      <td>
2664       <input name="sortorder3" type="radio" value="up" checked><span class="smaller">$WKCStrings{"editascending"}</span><br>
2665       <input name="sortorder3" type="radio" value="down"><span class="smaller">$WKCStrings{"editdescending"}</span><br></td><td>&nbsp;</td>
2666     </tr><tr><td>&nbsp;</td><td colspan="12" style="padding:6px 0px 6px 0px;">
2667      <input class="smaller" type="submit" name="okeditrange:sort" value="$WKCStrings{"editok"}">
2668      <input class="smaller" type="submit" name="cancelsort" value="$WKCStrings{"editcancel"}" onClick="this.blur();set_kbdprompt('/RT');return false;">
2669      <input class="smaller" type="submit" name="sorthelp" value="$WKCStrings{"edithelp"}" onClick="toggle_help('edittablesorthelptext');this.blur();return false;">
2670      </td>
2671     </tr>
2672    </table>
2673   </div>
2674  </div><div id="linkpagelist" style="padding-top:10px;display:none;">
2675   <span class="smaller">$WKCStrings{"editpagetolinkto"}</span><br>
2676   <select name="pagelist" size="10">
2677   <option value="">$WKCStrings{"editempty"}
2678   </select><br>
2679   <input class="smaller" type="submit" value="$WKCStrings{"editchoose"}" onClick="choose_pagelink(true);return false;">
2680   <input class="smaller" type="submit" value="$WKCStrings{"editcancel"}" onClick="choose_pagelink(false);return false;">
2681  </div><div id="helptext" style="width:500px;padding:10px 0px 10px 0px;display:none;">
2682   <div style="border-top:1px solid black;border-left:1px solid black;border-right:1px solid black;color:white;background-color:#66CC66;">
2683   <table cellspacing="0" cellpadding="0"><tr><td align="center" width="100%" class="smaller"><b>$WKCStrings{"helphelp"}</b></td>
2684   </td><td align="right"><input class="smaller" type="submit" name="hidehelp" value="$WKCStrings{"helphide"}" onClick="toggle_help('');this.blur();return false;"></td>
2685   </tr></table></div>
2686   <div id="helpbody" class="smaller" style="height:200px;overflow:auto;background-color:white;padding:4px;border:1px solid black;">
2687    $WKCStrings{"helpnotloaded"}
2688   </div>
2689  </div>
2690 </td>
2691</tr>
2692EOF
2693      $inlinescripts .= <<"EOF";
2694<script>
2695
2696$jsdata
2697
2698sheetlastcol=$lcol;
2699sheetlastrow=$lrow;
2700parse_sheet(isheet);
2701ecell="$coord";
2702cliprange="$sheetdata{clipboard}->{range}";
2703needsrecalc="$sheetdata{sheetattribs}->{needsrecalc}";
2704check_error();
2705</script>
2706EOF
2707
2708      $response .= <<"EOF";
2709</table>
2710$inlinescripts
2711$hiddenfields
2712<input type="hidden" name="okeditrange" value="">
2713<input type="hidden" name="newtab" value="">
2714<input type="hidden" name="recalc" value="$sheetdata{sheetattribs}->{recalc}">
2715<input type="hidden" name="dorecalc" value="">
2716<input type="hidden" name="mergetype" value="">
2717</form>
2718</td></tr></table>
2719<br>
2720EOF
2721
2722      $response .= <<"EOF";
2723<table cellspacing="0" cellpadding="0"><tr>
2724<td valign="top">
2725$outstr
2726</td>
2727<td valign="top">
2728<div style="border:4px solid #CCCC99;background-color: #CCCC99;" unselectable="on">
2729<div class="smaller" style="text-align:center;color:#666633;" id="statusthing" unselectable="on" onmousedown="scroll_to_home();">&nbsp;</div>
2730<div id="draghandle" style="margin:0 auto 0 auto;width:14px;height:6px;border:1px solid #666633;background-color:#DDFFDD;position:relative;top:17px;left:0;z-index:1" onmousedown="begindrag(event);" unselectable="on"><img src="?getfile=1x1" width="1" height="1" unselectable="on"></div>
2731<div id="scrollup" style="margin:0 auto 0 auto;width:12px;height:8px;border:1px solid #666633;background-color:white;" onmousedown="begin_scroll(-1,this);" unselectable="on"><img  src="?getfile=1x1" width="1" height="1" unselectable="on"></div>
2732<div id="slider" style="margin:0 auto 0 auto;width:20px;position:relative;top:0px;left:0;" onmousedown="slider_page(event);" unselectable="on"><div style="margin:0 auto 0 auto;width:4px;height:150px;background-color:white;border-left:1px solid #666633;border-right:1px solid #666633;"></div></div>
2733<div id="scrolldown" style="margin:0 auto 0 auto;width:12px;height:8px;border:1px solid #666633;background-color:white;" onmousedown="begin_scroll(1,this);" unselectable="on"><img src="?getfile=1x1" width="1" height="1" unselectable="on"></div>
2734<img src="?getfile=1x1" width="24" height="10" unselectable="on">
2735</div>
2736</td>
2737</tr>
2738</table>
2739<div style="margin:6px 0px 6px 0px;">
2740<form name="f1" method="POST">
2741<input name="editaddrow" type="submit" value="$WKCStrings{"editaddrow"}" class="smaller" onclick="document.f1.editcoords.value=ecell;">
2742<input name="editaddcol" type="submit" value="$WKCStrings{"editaddcolumn"}" class="smaller" onclick="document.f1.editcoords.value=ecell;">
2743<span class="smaller" style="color:#999999;">$WKCStrings{"editcurrentsheetextent"} $sheetdata{sheetattribs}->{lastcol} $WKCStrings{"editcolsby"} $sheetdata{sheetattribs}->{lastrow} $WKCStrings{"editrows"}</span>
2744$hiddenfields
2745</form>
2746</div>
2747EOF
2748      }
2749
2750   # # # # # # # # # #
2751   #
2752   # Format mode
2753   #
2754   # # # # # # # # # #
2755
2756   elsif ($currenttab eq $WKCStrings{"Format"}) {
2757
2758      my ($fcstylestr, $fcbodystr) = do_format_command(\%params, \%sheetdata, \%headerdata, $hiddenfields, $editcoords, $editmode);
2759
2760      $hiddenfields = update_hiddenfields(\%params, $hiddenfields);
2761
2762      $rbody1 =~ s/<body /<body onKeydown="return ev1(event);" onKeypress="return ev2(event);" /;
2763
2764      $response .= $template_headertop . $template_styletop . $template_stylemiddle . $fcstylestr . $template_stylebottom .
2765                   $template_headerscripts . $rbody1 . $rbodytabs . $hiddenfields . $rbodytabs2 . $fcbodystr;
2766
2767      }
2768
2769   # # # # # # # # # #
2770   #
2771   # Tools mode
2772   #
2773   # # # # # # # # # #
2774
2775   elsif ($currenttab eq $WKCStrings{"Tools"}) {
2776
2777      my ($tcstylestr, $tcbodystr) = do_tools_command(\%params, \%sheetdata, \%headerdata, $hiddenfields, $editcoords, $hostinfo, $loggedinuser, \%userinfo);
2778
2779      $hiddenfields = update_hiddenfields(\%params, $hiddenfields);
2780
2781      $response .= $template_headertop . $template_styletop . $template_stylemiddle . $tcstylestr . $template_stylebottom .
2782                   $template_headerscripts . $rbody1 . $rbodytabs . $hiddenfields . $rbodytabs2 . $tcbodystr;
2783
2784      }
2785
2786   # # # # # # # # # #
2787   #
2788   # Show License mode
2789   #
2790   # # # # # # # # # #
2791
2792   elsif ($currenttab eq $WKCStrings{"showlicense"}) {
2793
2794      use WKCLicense;
2795
2796      $response .= $template_headertop . $template_styletop . $template_stylemiddle . $template_stylebottom .
2797                   $template_headerscripts . $rbody1;
2798
2799      $response .= <<"EOF";
2800$rbodytabs
2801$hiddenfields
2802$rbodytabs2
2803<table cellpadding="0" cellspacing="0" width="100%">
2804<tr><td class="ttbody" width="100%">
2805<div class="sectiondark">
2806$sgilicensetext
2807</div>
2808<br>
2809<div class="sectiondark">
2810$gpllicensetext
2811</div>
2812</td></tr>
2813</table>
2814EOF
2815      }
2816
2817   # # # # # # # # # #
2818   #
2819   # ? mode
2820   #
2821   # # # # # # # # # #
2822
2823   else {
2824      $response .= <<"EOF";
2825UNKNOWN MODE<br>
2826EOF
2827      }
2828
2829   # # # # # # # # # #
2830   #
2831   # Output footer
2832   #
2833   # # # # # # # # # #
2834
2835   my $end_time = times();
2836   my $time_string = sprintf ("$WKCStrings{wkcfooterruntime} %.2f $WKCStrings{wkcfootersecondsat} $start_clock_time",
2837                              $end_time - $start_cpu_time);
2838
2839   if ($params{debugmessage}) { # Message to display -- would print, but that would mess up CGI-based version
2840      $response .= <<"EOF";
2841<div class="sectionerror">
2842$WKCStrings{"editdebuggingmsg"}: $params{debugmessage}<br><br>
2843</div>
2844EOF
2845      }
2846
2847   my $programnamebottom;
2848   if ($WKCStrings{programextratop}) { # if extra program name material, don't add SGI stuff
2849      $programnamebottom = "$WKCStrings{programextratop}&nbsp;$programmark$programmarksymbol&nbsp;$programversion";
2850      }
2851   else {
2852      $programnamebottom .= "$programmark$programmarksymbol&nbsp;$programversion$trademark1";
2853      }
2854
2855   $response .= <<"EOF";
2856<div class="footer">
2857$programnamebottom<br><br>
2858<div id="footertimemsg">$time_string</div>
2859<br>
2860$SGIfootertext
2861$WKCStrings{"footerextratext"}
2862EOF
2863
2864   $response .= <<"EOF";
2865<br><br>
2866<form name="fsl" action="" method="POST">
2867$hiddenfields
2868<input style="font-size:xx-small;" type="submit" name="newtab" value="$WKCStrings{"showlicense"}">
2869</form>
2870</div>
2871</body>
2872</html>
2873EOF
2874
2875   $responsecookie .= "datafilename:$params{datafilename};sitename:$params{sitename};expires:$cookievalues{expires}";
2876
2877   $responsedata->{content} = $response;
2878   $responsedata->{contenttype} = "text/html; charset=UTF-8";
2879   $responsedata->{cookie} = $responsecookie;
2880   $responsedata->{cookieexpires} = $cookievalues{expires} eq "session" ? "" : ($cookievalues{expires} || $defaultcookieexpire);
2881   return;
2882}
2883
2884
2885   # # # # # # # # # #
2886   #
2887   # $hiddenfields = update_hiddenfields(\$params, $hiddenfields)
2888   #
2889   # Updates the values for editmode, datafilename, sitename, etc., from params
2890   #
2891
2892sub update_hiddenfields {
2893
2894   my ($params, $hiddenfields) = @_;
2895
2896   $hiddenfields =~ s/(name="editmode" value=").*?"/$1$params->{editmode}"/;
2897   $hiddenfields =~ s/(name="datafilename" value=").*?"/$1$params->{datafilename}"/;
2898   $hiddenfields =~ s/(name="sitename" value=").*?"/$1$params->{sitename}"/;
2899   $hiddenfields =~ s/(name="editcoords" value=").*?"/$1$params->{editcoords}"/;
2900   $hiddenfields =~ s/(name="etpurl" value=").*?"/$1$params->{etpurl}"/;
2901   $hiddenfields =~ s/(name="scrollrow" value=").*?"/$1$params->{scrollrow}"/;
2902
2903   return $hiddenfields;
2904
2905}
2906
2907
2908# # # # # # # # # #
2909#
2910# $notok = site_not_allowed(\%userinfo, $user, $sitename)
2911#
2912# Checks to see if $user is allowed to edit $sitename, returning 0 (yes) or 1 (no)
2913#
2914# # # # # # # # # #
2915
2916sub site_not_allowed {
2917
2918   my ($userinfo, $user, $sitename) = @_;
2919
2920   return 0 if $userinfo->{HOSTrequirelogin} ne "yes";
2921
2922   return 0 if $userinfo->{$user}->{allsites} eq "yes";
2923
2924   my $sitestr = ",$userinfo->{$user}->{sites},"; # comma separated list of allowed sites
2925
2926   return 0 if ($sitestr =~ m/,$sitename,/ && $userinfo->{$user}->{sites});
2927
2928   return 1;
2929
2930}
2931
2932
2933# # # # # # # # # #
2934#
2935# $notok = readsite_not_allowed(\%userinfo, $user, $sitename)
2936#
2937# Checks to see if $user is allowed to read $sitename, returning 0 (yes) or 1 (no)
2938#
2939# # # # # # # # # #
2940
2941sub readsite_not_allowed {
2942
2943   my ($userinfo, $user, $sitename) = @_;
2944
2945   return 0 if $userinfo->{HOSTrequirelogin} ne "yes";
2946
2947   return 0 if ($userinfo->{$user}->{allsites} eq "yes" || $userinfo->{$user}->{allreadsites} eq "yes");
2948
2949   my $sitestr = ",$userinfo->{$user}->{sites},"; # comma separated list of allowed read/write sites
2950   return 0 if ($sitestr =~ m/,$sitename,/ && $userinfo->{$user}->{sites});
2951
2952   my $readsitestr = ",$userinfo->{$user}->{readsites},"; # comma separated list of allowed read sites
2953   return 0 if ($readsitestr =~ m/,$sitename,/ && $userinfo->{$user}->{readsites});
2954
2955   return 1;
2956
2957}
2958
2959
2960# # # # # # # #
2961#
2962# $tstr = fill_in_HTML_template($inputstr, $sitedata, \%headerdata, $sitename, $pagename, $clock_time, $author, $loggedinusername, $stylestr, $sheetstr, $renderingliveview)
2963#
2964# Fill in the HTML template variables
2965#
2966# # # # # # # #
2967
2968sub fill_in_HTML_template {
2969
2970   my ($inputstr, $sitedata, $headerdata, $sitename, $pagename, $clock_time, $author, $loggedinusername, $stylestr, $sheetstr, $renderingliveview) = @_;
2971
2972   my $tstr = $inputstr;
2973
2974   # Remove description line if it's there
2975
2976   $tstr =~ s/^{{templatedescriptionline}}.*?(\n|\r\n)//;
2977
2978   # Process directives
2979
2980   while ($tstr =~ m/{{line-if-editurl}}/) {
2981      if ($sitedata->{editurl}) { # if a URL for editing is provided leave rest of line
2982         $tstr =~ s/{{line-if-editurl}}(.*?)(\n|\r\n)/$1$2/;
2983         }
2984      else { # no URL so remove line
2985         $tstr =~ s/{{line-if-editurl}}.*?(\n|\r\n)/$1/;
2986         }
2987      }
2988
2989   while ($tstr =~ m/{{line-if-htmlurl}}/) {
2990      if ($sitedata->{htmlurl}) { # if a URL for the directory with HTML is provided leave rest of line
2991         $tstr =~ s/{{line-if-htmlurl}}(.*?)(\n|\r\n)/$1$2/;
2992         }
2993      else { # no URL so remove line
2994         $tstr =~ s/{{line-if-htmlurl}}.*?(\n|\r\n)/$1/;
2995         }
2996      }
2997
2998   while ($tstr =~ m/{{line-if-loggedin}}/) {
2999      if ($loggedinusername) { # have the name of logged in user leave rest of line
3000         $tstr =~ s/{{line-if-loggedin}}(.*?)(\n|\r\n)/$1$2/;
3001         }
3002      else { # no URL so remove line
3003         $tstr =~ s/{{line-if-loggedin}}.*?(\n|\r\n)/$1/;
3004         }
3005      }
3006
3007   while ($tstr =~ m/{{line-if-liveview}}/) {
3008      if ($renderingliveview) { # if doing live view rendering leave rest of line
3009         $tstr =~ s/{{line-if-liveview}}(.*?)(\n|\r\n)/$1$2/;
3010         }
3011      else { # no URL so remove line
3012         $tstr =~ s/{{line-if-liveview}}.*?(\n|\r\n)/$1/;
3013         }
3014      }
3015
3016   # Do all the substitutions
3017
3018   $tstr =~ s/{{editthispagehtml}}/$sitedata->{editurl}?$WKCStrings{"editthispagehtml"}:""/ge; # must preceed others
3019   $tstr =~ s/{{pagetitle}}/$headerdata->{fullname}/ge;
3020   $tstr =~ s/{{pagename}}/$pagename/ge;
3021   $tstr =~ s/{{sitename}}/$sitename/ge;
3022   $tstr =~ s/{{pubdatetime}}/$clock_time/ge;
3023   $tstr =~ s/{{author}}/$author/ge;
3024   $tstr =~ s/{{loggedinuser}}/$loggedinusername/ge;
3025   $tstr =~ s/{{editurl}}/$sitedata->{editurl}/ge;
3026   $tstr =~ s/{{htmlurl}}/$sitedata->{htmlurl}/ge;
3027
3028   $tstr =~ s/{{sheetstyles}}/$stylestr/e;
3029   $tstr =~ s/{{sheet0}}/$sheetstr/e;
3030
3031   return $tstr;
3032
3033   }
3034
3035
3036# # # # # # # #
3037#
3038# $jsstr = create_embeddable_JS_sheet($stylestr, $sheetstr)
3039#
3040# Turn the output of rendering into embeddable Javascript
3041# Uses WKCembeddablejs.txt, replacing {{stylestr}} and {{sheetstr}}
3042#
3043# # # # # # # #
3044
3045sub create_embeddable_JS_sheet {
3046
3047   my ($stylestr, $sheetstr) = @_;
3048
3049   my $jsstr;
3050   $sheetstr =~ s/^(.*?)$/ str+=pl('$1',styles);/gm;
3051   $stylestr =~ s/^/ /gm;
3052
3053   open JSFILE, "$WKCdirectory/WKCembeddablejs.txt";
3054   while (my $line = <JSFILE>) {
3055      $jsstr .= $line;
3056      }
3057   close JSFILE;
3058
3059   $jsstr =~ s/{{stylestr}}/$stylestr/e;
3060   $jsstr =~ s/{{sheetstr}}/$sheetstr/e;
3061
3062   return $jsstr;
3063
3064   }
3065
3066
3067# # # # # # # #
3068#
3069# $errortext = execute_edit_command(\%params, \%sheetdata, $editcoords, \%headerdata)
3070#
3071# Modify the sheet in response to an edit command
3072#
3073# # # # # # # #
3074
3075sub execute_edit_command {
3076
3077   my ($params, $sheetdata, $editcoords, $headerdata) = @_;
3078
3079   my ($ok, $errortext);
3080
3081   if ($params->{okeditrange} eq "erase") {
3082      $ok = execute_sheet_command_and_log($sheetdata, "erase $editcoords $params->{editparts}", $headerdata);
3083      }
3084
3085   elsif ($params->{okeditrange} eq "copy" || $params->{okeditrange} eq "cut") {
3086      $ok = execute_sheet_command_and_log($sheetdata, "$params->{okeditrange} $editcoords $params->{editparts}", $headerdata);
3087      }
3088
3089   elsif ($params->{okeditrange} eq "clearclipboard") {
3090      $ok = execute_sheet_command_and_log($sheetdata, "clearclipboard", $headerdata);
3091      }
3092
3093   elsif ($params->{okeditrange} eq "paste") {
3094      $ok = execute_sheet_command_and_log($sheetdata, "paste $editcoords $params->{editparts}", $headerdata);
3095      delete $params->{doingrangesetting};
3096      }
3097
3098   elsif ($params->{okeditrange} eq "fillright" || $params->{okeditrange} eq "filldown") {
3099      $ok = execute_sheet_command_and_log($sheetdata, "$params->{okeditrange} $editcoords $params->{editparts}", $headerdata);
3100      }
3101
3102   elsif ($params->{okeditrange} eq "insertrow" || $params->{okeditrange} eq "insertcol") {
3103      $ok = execute_sheet_command_and_log($sheetdata, "$params->{okeditrange} $editcoords", $headerdata);
3104      delete $params->{doingrangesetting};
3105      }
3106
3107   elsif ($params->{okeditrange} eq "deleterow" || $params->{okeditrange} eq "deletecol") {
3108      $ok = execute_sheet_command_and_log($sheetdata, "$params->{okeditrange} $editcoords", $headerdata);
3109      $editcoords =~ s/:(.+)//; # switch to just one cell
3110      my ($c, $r) = coord_to_cr($editcoords);
3111      if ($c > $sheetdata->{sheetattribs}->{lastcol}) {
3112         $c = $sheetdata->{sheetattribs}->{lastcol};
3113         }
3114      if ($r > $sheetdata->{sheetattribs}->{lastrow}) {
3115         $r = $sheetdata->{sheetattribs}->{lastrow};
3116         }
3117      $params->{editcoords} = cr_to_coord($c, $r);
3118      }
3119
3120   elsif ($params->{okeditrange} eq "merge" || $params->{okeditrange} eq "unmerge") {
3121      $ok = execute_sheet_command_and_log($sheetdata, "$params->{okeditrange} $editcoords", $headerdata);
3122      $editcoords =~ s/:(.+)//; # switch to just one cell
3123      $params->{editcoords} = $editcoords;
3124      }
3125
3126   elsif ($params->{okeditrange} eq "recalcauto" || $params->{okeditrange} eq "recalcmanual") {
3127      $sheetdata->{sheetattribs}->{recalc} = $params->{okeditrange} eq "recalcauto" ? "on" : "off";
3128      $params->{recalc} = $sheetdata->{sheetattribs}->{recalc};
3129      add_to_editlog($headerdata, "# $WKCStrings{logautorecalset}: $params->{recalc}");
3130      }
3131
3132   elsif ($params->{okeditrange} eq "sort") {
3133      my $cstr = "sort $editcoords";
3134      $cstr .= " $params->{sort1} $params->{sortorder1}" if $params->{sort1} ne "none";
3135      $cstr .= " $params->{sort2} $params->{sortorder2}" if $params->{sort2} ne "none";
3136      $cstr .= " $params->{sort3} $params->{sortorder3}" if $params->{sort3} ne "none";
3137      $ok = execute_sheet_command_and_log($sheetdata, $cstr, $headerdata);
3138      }
3139
3140   return $errortext;
3141}
3142
3143
3144# # # # # # # # # #
3145#
3146# decode_from_ajax($string)
3147#
3148# Returns a string with \n, \b, \c, and \e escaped to \n, \, :, and ]]>
3149#
3150
3151sub decode_from_ajax {
3152   my $string = shift @_;
3153
3154   $string =~ s/\\n/\n/g;
3155   $string =~ s/\\c/:/g;
3156   $string =~ s/\\b/\\/g;
3157
3158   return $string;
3159}
3160
3161
3162# # # # # # # # # #
3163#
3164# encode_for_ajax($string)
3165#
3166# Returns a string with \n, \, :, and ]]> escaped to \n, \b, \c, and \e
3167#
3168
3169sub encode_for_ajax {
3170   my $string = shift @_;
3171
3172   $string =~ s/\\/\\b/g;
3173   $string =~ s/\n/\\n/g;
3174   $string =~ s/\r//g;
3175   $string =~ s/:/\\c/g;
3176   $string =~ s/]]>/\\e/g;
3177
3178   return $string;
3179}
3180
3181
3182# # # # # # # # # #
3183#
3184# $ok = execute_sheet_command_and_log($sheetdata, $command, \%headerdata)
3185#
3186# Passes the first two arguments to execute_sheet_command and then logs the command if ok
3187#
3188
3189sub execute_sheet_command_and_log {
3190
3191   my ($sheetdata, $command, $headerdata) = @_;
3192
3193   my $ok = execute_sheet_command($sheetdata, $command);
3194
3195   if ($ok) {
3196      add_to_editlog($headerdata, $command)
3197      }
3198
3199   return $ok;
3200
3201}
3202
3203
3204# # # # # # # # # #
3205#
3206# init_sheet_cache(\%sheetdata, \%params, \%hostinfo, $sitename)
3207#
3208# Stores enough information in the sheetdata to load additional sheets' information for worksheet references
3209#
3210
3211sub init_sheet_cache {
3212
3213   my ($sheetdata, $params, $hostinfo, $sitename) = @_;
3214
3215   $sheetdata->{sheetcache} = {};
3216   $sheetdata->{sheetcache}->{params} = $params;
3217   $sheetdata->{sheetcache}->{hostinfo} = $hostinfo;
3218   $sheetdata->{sheetcache}->{sitename} = $sitename;
3219   $sheetdata->{sheetcache}->{sheets} = {}; # see find_in_sheet_cache
3220
3221   return;
3222
3223}
3224
3225
3226# # # # # # # # # #
3227#
3228# $othersheet_sheetdata = find_in_sheet_cache(\%sheetdata, $datafilename)
3229#
3230# Load additional sheet's information for worksheet references as a sheetdata structure
3231# stored in $sheetdata->{sheetcache}->{sheets}->{$datafilename} if necessary.
3232# Return that structure as \%othersheet_sheetdata
3233#
3234# If $datafilename starts with "http:" it is assumed to be a URL and an HTTP GET is done to retrieve
3235# the file. Otherwise it is assumed to be a pagename and the file is loaded from the current site.
3236#
3237
3238sub find_in_sheet_cache {
3239
3240   my ($sheetdata, $datafilename) = @_;
3241
3242   my $sdsc = $sheetdata->{sheetcache};
3243
3244   if ($datafilename !~ m/^http:/i) { # not URL
3245      $datafilename = lc $datafilename; # lower case for consistency
3246      }
3247
3248   if ($sdsc->{sheets}->{$datafilename}) { # already in cache
3249      return $sdsc->{sheets}->{$datafilename};
3250      }
3251
3252   my (@headerlines, @sheetlines, $loaderror);
3253
3254   if ($datafilename =~ m/^http:/i) { # URL - use HTTP GET
3255      my $ua = LWP::UserAgent->new;
3256      $ua->agent($programname);
3257      $ua->timeout(30);
3258      my $req = HTTP::Request->new("GET", $datafilename);
3259      $req->header('Accept' => '*/*');
3260      my $res = $ua->request($req);
3261      if ($res->is_success) {
3262         $loaderror = load_page_from_array($res->content, \@headerlines, \@sheetlines);
3263         }
3264      else {
3265         $loaderror = "$WKCStrings{findsheetincacheunabletoload} '$datafilename'";
3266         }
3267      }
3268   else { # assume local pagename
3269      my $editpath = get_page_published_datafile_path($sdsc->{params}, $sdsc->{hostinfo}, $sdsc->{sitename}, $datafilename);
3270      $loaderror = load_page($editpath, \@headerlines, \@sheetlines);
3271      }
3272
3273   $sdsc->{sheets}->{$datafilename} = {}; # start fresh
3274   my $ok = parse_sheet_save(\@sheetlines, $sdsc->{sheets}->{$datafilename});
3275
3276   $sdsc->{sheets}->{$datafilename}->{loaderror} = $loaderror if $loaderror;
3277
3278   return $sdsc->{sheets}->{$datafilename};
3279
3280}
3281
3282
32831;
3284
3285=begin license
3286
3287SOFTWARE LICENSE
3288
3289This software and documentation is
3290Copyright (c) 2007 Software Garden, Inc.
3291All rights reserved.
3292
32931. The source code of this program is made available as free software;
3294you can redistribute it and/or modify it under the terms of the GNU
3295General Public License, version 2, as published by the Free Software
3296Foundation.
3297
32982. This program is distributed in the hope that it will be useful, but
3299WITHOUT ANY WARRANTY; without even the implied warranty of
3300MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
3301General Public License for more details. You should have received a
3302copy of the GNU General Public License along with this program; if not,
3303write to the Free Software Foundation, Inc., 51 Franklin Street,
3304Fifth Floor, Boston, MA  02110-1301, USA.
3305
33063. If the GNU General Public License is restrictive in a way that does
3307not meet your needs, contact the copyright holder (Software Garden,
3308Inc.) to inquire about the availability of other licenses, such as
3309traditional commercial licenses.
3310
33114. The right to distribute this software or to use it for any purpose
3312does not give you the right to use Servicemarks or Trademarks of
3313Software Garden, Inc., including Garden, Software Garden, ListGarden,
3314and wikiCalc.
3315
33165. An appropriate copyright notice will include the Software Garden,
3317Inc., copyright, and a prominent change notice will include a
3318reference to Software Garden, Inc., as the originator of the code
3319to which the changes were made.
3320
3321Exception for Executable Bundle
3322
3323In some cases this program is distributed together with programs and
3324libraries of ActiveState Corporation as a single executable file (an
3325"Executable Bundle") produced using ActiveState Corporation's "Perl Dev
3326Kit" PerlTray program ("PDK PerlTray"). This free software license does
3327not apply to those programs and libraries of ActiveState Corporation
3328that are part of the Executable Bundle. You only have a license to use
3329those programs and libraries of ActiveState Corporation for runtime
3330purposes in order to execute this software of Software Garden, Inc. In
3331order to create and distribute similar executable files from modified
3332source files, you will need to license your own copy of PDK PerlTray.
3333
3334As a specific exception for this product to the terms and conditions of
3335the GNU General Public License version 2, you are free to distribute
3336this software (modified or unmodified) in an Executable Bundle created
3337with PDK PerlTray as long as you adhere to the GNU General Public
3338License in all respects for all software components except for those of
3339PDK PerlTray added by that program when used to create the Executable
3340Bundle.
3341
3342Disclaimer
3343
3344THIS SOFTWARE IS PROVIDED BY SOFTWARE GARDEN, INC., "AS IS" AND ANY
3345EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
3346WARRANTIES OF INFRINGEMENT AND THE IMPLIED WARRANTIES OF
3347MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
3348IN NO EVENT SHALL SOFTWARE GARDEN, INC. NOR ITS EMPLOYEES AND OFFICERS
3349BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
3350CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
3351SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
3352BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
3353WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
3354OTHERWISE) ARISING IN ANY WAY OUT OF THE DISTRIBUTION OR USE OF THIS
3355SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3356
3357Software Garden, Inc.
3358PO Box 610369
3359Newton Highlands, MA 02461 USA
3360www.softwaregarden.com
3361
3362License version: 1.4/2007-01-02
3363
3364=end
3365
3366=cut
3367