]> bicyclesonthemoon.info Git - ott/bsta/blob - bsta_lib.1.pm
262d852947ef44e85ce0190d762a056e309abcdd
[ott/bsta] / bsta_lib.1.pm
1 # bsta_lib.pm is generated from bsta_lib.1.pm
2
3 # Library of functions
4 #
5 # Copyright (C) 2016, 2017, 2019, 2020, 2022, 2023, 2024  Balthasar Szczepański
6 #
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU Affero General Public License for more details.
16 #
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 # TODO: FQ NBSP ?
21 # TODO: DEBUG
22 # TODO: timer JS
23 # TODO: BB & INFO indent
24
25 package bsta_lib;
26
27 use strict;
28 #use warnings
29
30 use utf8;
31 use Encode ('encode', 'decode');
32 use Exporter;
33 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
34
35 ###PERL_EXPORT_VERSION: our $VERSION = 'x.x.x';
36 our @ISA         = qw(Exporter);
37 our @EXPORT      = ();
38 our @EXPORT_OK   = (
39         'STATE', 'TEXT_MODE', 'INTF_STATE', 'CHAT_STATE', 'CHAT_ACTION',
40         'failpage',
41         'fail_method', 'fail_content_type', 'fail_open_file', 'fail_attachment', 'fail_500',
42         'redirect',
43         'get_remote_addr', 'get_id', 'get_frame', 'get_password',
44         'merge_settings',
45         'print_html_start', 'print_html_end',
46         'print_html_head_start', 'print_html_head_end',
47         'print_html_body_start', 'print_html_body_end',
48         'print_viewer_page',
49         'write_index', 'write_static_viewer_page', 'write_static_goto',
50         'ong',
51         'eval_bb', 'bb_to_bbcode', 'bb_to_html'
52 );
53
54 ###PERL_LIB: use lib /botm/lib/bsta
55 use botm_common (
56         'HTTP_STATUS',
57         'url_query_decode', 'url_query_encode',
58         'url_decode', 'url_encode',
59         'html_entity_encode_dec',
60         'merge_url',
61         'read_header_env',
62         'read_data_file', 'write_data_file',
63         'join_path',
64         'copy_encoded', 'open_encoded', '_x_encoded',
65         'http_header_line', 'http_status',
66         'http_header_status', 'http_header_allow', 'http_header_location'
67 );
68
69 ###PERL_PATH_SEPARATOR:     PATH_SEPARATOR     = /
70
71 ###PERL_CGI_PATH:           CGI_PATH           = /bsta/
72 ###PERL_CGI_ATTACH_PATH:    CGI_ATTACH_PATH    = /bsta/a
73 ###PERL_CGI_2WORDS_PATH:    CGI_2WORDS_PATH    = /bsta/2words
74 ###PERL_CGI_BBCODE_PATH:    CGI_BBCODE_PATH    = /bsta/b
75 ###PERL_CGI_COIN_PATH:      CGI_COIN_PATH      = /bsta/coin
76 ###PERL_CGI_CSS_PATH:       CGI_CSS_PATH       = /bsta/bsta.css
77 ###PERL_CGI_FRAME_PATH:     CGI_FRAME_PATH     = /bsta/f
78 ###PERL_CGI_GOTO_PATH:      CGI_GOTO_PATH      = /bsta/g
79 ###PERL_CGI_INFO_PATH:      CGI_INFO_PATH      = /bsta/i
80 ###PERL_CGI_LOGO_PATH:      CGI_LOGO_PATH      = /bsta/botmlogo.png
81 ###PERL_CGI_TIMER_PATH:     CGI_TIMER_PATH     = /bsta/timer.js
82 ###PERL_CGI_VIEWER_PATH:    CGI_VIEWER_PATH    = /bsta/v
83 ###PERL_CGI_WORDS_PATH:     CGI_WORDS_PATH     = /bsta/w
84
85 ###PERL_DATA_PATH:          DATA_PATH          = /botm/data/bsta/
86 ###PERL_DATA_ATTACH_PATH:   DATA_ATTACH_PATH   = /botm/data/bsta/a
87 ###PERL_DATA_COIN_PATH:     DATA_COIN_PATH     = /botm/data/bsta/coincidence
88 ###PERL_DATA_DEFAULT_PATH:  DATA_DEFAULT_PATH  = /botm/data/bsta/default
89 ###PERL_DATA_LIST_PATH:     DATA_LIST_PATH     = /botm/data/bsta/list
90 ###PERL_DATA_NOACCESS_PATH: DATA_NOACCESS_PATH = /botm/data/bsta/noaccess
91 ###PERL_DATA_STATE_PATH:    DATA_STATE_PATH    = /botm/data/bsta/state
92 ###PERL_DATA_WORDS_PATH:    DATA_WORDS_PATH    = /botm/data/bsta/words/
93
94 ###PERL_WWW_PATH:           WWW_PATH           = /botm/www/
95 ###PERL_WWW_INDEX_PATH:     WWW_INDEX_PATH     = /botm/www/1190/bsta/index.htm
96
97 ###PERL_SCHEME:             SCHEME             = http
98 ###PERL_WEBSITE:            WEBSITE            = 1190.bicyclesonthemoon.info
99 ###PERL_WEBSITE_NAME:       WEBSITE_NAME       = Bicycles on the Moon
100 ###PERL_FAVICON_PATH:       FAVICON_PATH       = /img/favicon.png
101
102 ###PERL_COIN_DATE:          COIN_DATE          = 13-Nov-2016 22:15
103 ###PERL_INTF_DATE:          INTF_DATE          = 28-Sep-2016 20:34
104
105 ###PERL_STORY_CREDITS:      STORY_CREDITS      = "BSTA" by Balthasar Szczepański
106 ###PERL_INTF_CREDITS:       INTF_CREDITS       = Online interface © Balthasar Szczepański; AGPL 3 license
107 ###PERL_SOURCE_URL:         SOURCE_URL         = http://bicyclesonthemoon.info/git-projects/?p=ott/bsta
108
109 ###PERL_COMMENT_PAGE_LENGTH:COMMENT_PAGE_LENGTH= 16
110
111 use constant STATE => {
112         'inactive' => 0,
113         'waiting'  => 1,
114         'ready'    => 2,
115         'end'      => 3,
116 };
117 use constant INTF_STATE => {
118         'X'  => 0b000000,
119         'x'  => 0b000000,
120         '||' => 0b000001,
121         '>>' => 0b000100,
122         '>>|'=> 0b000101,
123         '<<' => 0b001000,
124         '|<<'=> 0b001001,
125         '>'  => 0b010000,
126         '>|' => 0b010001,
127         'mask'=>0b111111,
128         'mode'=>0b111110,
129 };
130 use constant TEXT_MODE => {
131         'normal' => 0,
132         'bb'     => 1,
133         'info'   => 2,
134         'words'  => 3
135 };
136 use constant CHAT_STATE => {
137         'disconnected' => 0,
138         'ready'        => 1,
139         'active'       => 2,
140 };
141 use constant CHAT_ACTION => {
142         'none'   => 0,
143         'join'   => 1,
144         'leave'  => 2,
145         'nopost' => 3,
146         'file'   => 4,
147 };
148
149 use constant tags_bbcode => {
150         'ht'     => '',
151         '/ht'    => '',
152         'fq'     => '[quote]',
153         '/fq'    => '[/quote]',
154         'tq'     => '[quote]',
155         '/tq'    => '[/quote]',
156         'quote'  => '[quote]',
157         'quote=' => '[quote="',
158         'quote/='=> '"]',
159         '/quote' => '[/quote]',
160         'ni'     => '[color=#0057AF]',
161         '/ni'    => '[/color]',
162         'br'     => '[color=#BB6622]',
163         '/br'    => '[/color]',
164         'po'     => '[color=#FF8800]',
165         '/po'    => '[/color]',
166         'url'    => '[url]',
167         'url='   => '[url=',
168         'url/='  => ']',
169         '/url'   => '[/url]',
170         'i'      => '[i]',
171         '/i'     => '[/i]',
172         'list'   => '[list]',
173         'list='  => '[list=',
174         'list/=' => ']',
175         '/list'  => '[/list]',
176         '*'      => '[*]',
177         '/*'     => '[/*]',
178         '?'      => '[unknown!]',
179         '/?'     => '[/unknown!]',
180 };
181 use constant tags_html => {
182         'ht'     => '',
183         '/ht'    => '',
184         'fq'     => '<div class="fq">',
185         '/fq'    => '</div>',
186         'tq'     => '<div class="tq">',
187         '/tq'    => '</div>',
188         'quote'  => '<div class="opomba"><div class="opomba_text">',
189         'quote=' => '<div class="opomba"><div class="opomba_info"><b>',
190         'quote/='=> '</b> wrote:</div><div class="opomba_text">',
191         '/quote' => '</div></div>',
192         'ni'     => '<span class="ni">',
193         '/ni'    => '</span>',
194         'br'     => '<span class="br">',
195         '/br'    => '</span>',
196         'po'     => '<span class="po">',
197         '/po'    => '</span>',
198         'url'    => '<a href="#">',#think: how to add selfincluding?
199         'url='   => '<a href="',
200         'url/='  => '">',
201         '/url'   => '</a>',
202         'i'      => '<i>',
203         '/i'     => '</i>',
204         'list'   => '<ul>',
205         'list='  => '<ol style="list-style-type: ',
206         'list=1' => 'decimal',
207         'list=A' => 'upper-alpha',
208         'list=a' => 'lower-alpha',
209         'list=I' => 'upper-roman',
210         'list=i' => 'lower-roman',
211         'list/=' => '">',
212         '/list'  => '</ul>',
213         '/list=' => '</ol>',
214         '*'      => '<li>',
215         '/*'     => '</li>',
216         '?'      => '[unknown!]',
217         '/?'     => '[/unknown!]',
218 };
219
220
221 # Function to return an error page
222 # arguments: 1 - header fields, 2 - page title, 3 - error message, 4 method
223 sub failpage {
224         (my $header, my $title, my $message, my $method, my $hyperlink) = @_;
225         
226         if (ref($header)) {
227                 foreach my $header_name (keys %$header) {
228                         print http_header_line($header_name, $header->{$header_name});
229                 }
230         }
231         elsif($header ne '') {
232                 print $header;
233         }
234         if($method eq 'HEAD') {
235                 print "\n";
236                 return;
237         }
238         my $_title     = html_entity_encode_dec($title    , 1);
239         my $_message   = html_entity_encode_dec($message  , 1);
240         my $_hyperlink = html_entity_encode_dec($hyperlink, 1);
241         
242         print "Content-type: text/html; charset=UTF-8\n\n";
243         
244         print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
245         print ' <html lang="en">'."\n";
246         print '  <head>'."\n";
247         print '   <meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
248         if ($title ne '') {
249                 print '   <title>'.$_title.'</title>'."\n";
250         }
251         print '  </head>'."\n";
252         print ' <body>'."\n";
253         if ($title ne '') {
254                 print '  <h1>'.$_title.'</h1>'."\n";
255         }
256         if (($message ne '') || ($hyperlink ne '')) {
257                 print "  <p>\n";
258                 if ($message ne '') {
259                         print '   '.$_message.($hyperlink ne '' ? '<br>' : '')."\n";
260                 }
261                 if ($hyperlink ne '') {
262                         print '   <a href="'.$_hyperlink.'">'.$_hyperlink."</a>\n";
263                 }
264                 print "  </p>\n";
265         }
266         print ' </body>'."\n";
267         print '</html>'."\n";
268 }
269
270 sub fail_method {
271         (my $method, my $allowed) = @_;
272         
273         my $status = http_status(HTTP_STATUS->{'method_not_allowed'});
274         my $header =
275                 http_header_line('status', $status) .
276                 http_header_allow($allowed);
277         
278         return failpage(
279                 $header,
280                 $status,
281                 "The interface does not support the $method method.",
282                 $method
283         );
284 }
285
286 sub fail_content_type
287 {
288         (my $method, my $content_type) = @_;
289         
290         my $status = http_status(HTTP_STATUS->{'unsupported_media_type'});
291         my $header = http_header_line('status', $status);
292         
293         return failpage(
294                 $header,
295                 $status,
296                 "Unsupported Content-type: $content_type.",
297                 $method
298         );
299 }
300
301 sub fail_open_file
302 {
303         (my $method, my $type, my $path) = @_;
304         
305         my $status = http_status(HTTP_STATUS->{'not_found'});
306         my $header = http_header_line('status', $status);
307         
308         return failpage(
309                 $header,
310                 $status,
311                         "Can't open ".
312                         ($type ne '' ? $type : 'file').
313                         ($path ne '' ? ': "'.$path.'"' : '').
314                         '.',
315                 $method
316         );
317 }
318
319 sub fail_attachment
320 {
321         (my $method, my $ID) = @_;
322         
323         my $status = http_status(HTTP_STATUS->{'not_found'});
324         my $header = http_header_line('status', $status);
325
326         return failpage(
327                 $header,
328                 $status,
329                 "Attachment $ID not found.",
330                 $method
331         );
332 }
333
334 sub fail_500
335 {
336         (my $method, my $text) = @_;
337         
338         my $status = http_status(HTTP_STATUS->{'internal_server_error'});
339         my $header = http_header_line('status', $status);
340         
341         return failpage(
342                 $header,
343                 $status,
344                 $text,
345                 $method
346         );
347 }
348
349 sub redirect
350 {
351         (my $method, my $uri, my $code) = @_;
352         my $header;
353         my $status;
354         if ($code eq '') {
355                 $code = HTTP_STATUS->{'found'};
356         }
357         # https://insanecoding.blogspot.com/2014/02/http-308-incompetence-expected.html
358         # 301 Moved Permanently
359         # 302 Found
360         # 303 See Other
361         # 307 Temporary Redirect
362         # 308 Permanent Redirect
363         $status = http_status($code);
364         $header = http_header_line('status', $status);
365         $header .= http_header_location($uri);
366         
367         return failpage(
368                 $header,
369                 $status,
370                 '',
371                 $method,
372                 $uri
373         );
374 }
375
376
377 # function to obtain address of remote agent
378 sub get_remote_addr {
379         if ($ENV{'HTTP_X_FORWARDED_FOR'} =~ /^.+$/) {
380                 return $&;
381         }
382         elsif ($ENV{'REMOTE_ADDR'} =~ /^.+$/) {
383                 return $&;
384         }
385         else {
386                 return '0.0.0.0';
387         }
388 }
389
390 # functions to get ID/number etc.
391 sub get_id {
392         (my $cgi, my $default, my $cgi_name) = @_;
393                 if ($default eq '') {
394                 $default = 0;
395         }
396         if ($cgi_name eq '') {
397                 $cgi_name = 'i';
398         }
399         
400         if ($cgi->{$cgi_name} =~ /^.+$/) {
401                 return int($&);
402         }
403         elsif ($ENV{'PATH_INFO'} =~ /^\/(.+)$/) {
404                 return int($1);
405         }
406         else {
407                 return int($default);
408         }
409 }
410
411 # function to obtain frame number
412 sub get_frame {
413         (my $cgi, my $default) = @_;
414         return get_id($cgi, $default, 'f');
415 }
416
417 # function to obtain password
418 sub get_password {
419         (my $cgi) = @_;
420         
421         if ($cgi->{'p'} =~ /^.+$/) {
422                 return $&;
423         }
424         else {
425                 return '';
426         }
427 }
428
429
430 sub merge_settings {
431                 my %final_settings;
432         
433         foreach my $settings (@_) {
434                 foreach my $ind (keys %$settings) {
435                         $final_settings{$ind} = $settings->{$ind};
436                 }
437         }
438         return %final_settings;
439 }
440
441
442 # BB code stuff
443 # different & simpler implementation than in post library
444 # to consider:
445 # a BBcode library?
446
447 #analyse bbcode text to build tag tree
448 #TODO make [/*] optional!
449 sub bbtree {
450         (my $bb, my $printdebug) = @_;
451         my %bbtree;
452         my $ind;
453         my $tag;
454         my $tag_name;
455         my $tag_value;
456         my $tag_end;
457         my $level=0;
458         my $pre_text;
459         my $debug;
460         
461         $ind="_";
462         $level=0;
463         $bbtree{"_.name" }  = "ht";
464         $bbtree{"_.value" } = '';
465         $bbtree{"_.type"  } = "tag";
466         $bbtree{"_.count" } = 0;
467         $bbtree{"_.closed"} = 0;
468         $debug .= debug($printdebug,
469                 "\n".
470                 "<!--GENERATING BBCODE TREE:\n".
471                 '[_]automatic tag: [ht]'."\n"
472         );
473         
474         while ($bb ne '') {
475                 my $new_ind = $ind.'.'.$bbtree{$ind.'.count'};
476                 
477                 if($bb =~ m/\[(\/?)([A-Za-z]+|\*)(=([^\[\]]*))?\]/g) {
478                         $pre_text = $`;
479                         $tag = $&;
480                         $tag_end = $1;
481                         $tag_name = lc($2);
482                         $tag_value = $4;
483                         $bb = $';
484                         if ($tag_value =~ /^"(.*)"$/) {
485                                 $tag_value = $1;
486                         }
487                         
488                         if ($pre_text ne '') {
489                                 $debug .= debug($printdebug, "[$new_ind]text: $pre_text\n");
490                                 $bbtree{$new_ind.'.type' } = 'text';
491                                 $bbtree{$new_ind.'.value'} = $pre_text;
492                                 $bbtree{    $ind.'.count'}+= 1;
493                                 $new_ind = $ind.'.'.$bbtree{$ind.'.count'};
494                         }
495                         
496                         if($tag_name =~ /^(fq|tq|quote|br|ni|po|url|i|list|\*)$/) {
497                                 if ($tag_end ne '') {
498                                         if (
499                                                 ($tag_name ne $bbtree{$ind.'.name'}) ||
500                                                 ($level <= 0)
501                                         ) {
502                                                 $debug .= debug($printdebug, "[$new_ind]text: $tag\n");
503                                                 $bbtree{$new_ind.'.type' } = 'text';
504                                                 $bbtree{$new_ind.'.value'} = $tag;
505                                                 $bbtree{    $ind.'.count'}+= 1;
506                                                 # $new_ind = $ind.'.'.$bbtree{$ind.'.count'};
507                                         }
508                                         else {
509                                                 $debug .= debug($printdebug, "[$new_ind]tag: $tag\n");
510                                                 $bbtree{$new_ind.'.type'  } = 'tag';
511                                                 $bbtree{$new_ind.'.name'  } = '/'.$tag_name;
512                                                 $bbtree{$new_ind.'.value' } = $tag_value;
513                                                 $bbtree{    $ind.'.count' }+= 1;
514                                                 $bbtree{    $ind.'.closed'} = 1;
515                                                 $level -= 1;
516                                                 $ind =~ s/\.[0-9]+$//;
517                                                 # $new_ind = $ind.'.'.$bbtree{$ind.'.count'};
518                                         }
519                                 }
520                                 else
521                                 {
522                                         $debug .= debug($printdebug, "[$new_ind]tag: $tag\n");
523                                         $bbtree{$new_ind.'.type'  } = 'tag';
524                                         $bbtree{$new_ind.'.name'  } = $tag_name;
525                                         $bbtree{$new_ind.'.value' } = $tag_value;
526                                         $bbtree{$new_ind.'.count' } = 0;
527                                         $bbtree{$new_ind.'.closed'} = 0;
528                                         $bbtree{    $ind.'.count' }+= 1;
529                                         $level += 1;
530                                         $ind = $new_ind;
531                                         # $new_ind = $ind.'.'.$bbtree{$ind.'.count'};
532                                 }
533                         }
534                         else {
535                                 $debug .= debug($printdebug, "[$new_ind]text: $tag\n");
536                                 $bbtree{$new_ind.'.type' } = 'text';
537                                 $bbtree{$new_ind.'.value'} = $tag;
538                                 $bbtree{    $ind.'.count'}+= 1;
539                                 # $new_ind = $ind.'.'.$bbtree{$ind.'.count'};
540                         }
541                 }
542                 else {
543                         $debug .= debug($printdebug, "[$new_ind]text: $bb\n");
544                         $bbtree{$new_ind.'.type' } = 'text';
545                         $bbtree{$new_ind.'.value'} = $bb;
546                         $bbtree{    $ind.'.count'}+= 1;
547                         # $new_ind = $ind.'.'.$bbtree{$ind.'.count'};
548                         $bb = '';
549                 }
550         }
551         my $final_ind = '_.'.$bbtree{"_.count"};
552         $debug .= debug($printdebug, "[$final_ind]automatic tag: [/ht]\n -->\n");
553         $bbtree{$final_ind.'.type' } = "tag";
554         $bbtree{$final_ind.'.name' } = '/ht';
555         $bbtree{         '_.count' }+= 1;
556         $bbtree{         '_.closed'} = 1;
557         
558         return ($debug, %bbtree);
559 }
560
561 #convert tag tree to final text
562 sub convtree {
563         (my $printdebug, my $debug, my $lang, my $bbtree) = @_;
564         my $out;
565         my $ind;
566         my $indd;
567         my $level = 0;
568         my $tags = ($lang eq 'html') ? tags_html : tags_bbcode;
569         my $escape = ($lang eq 'html');
570         
571         # $debug .= debug($printdebug, "\n****\n");
572         # foreach my $iiii (keys %tags) {
573                 # $debug .= debug($printdebug, $iiii.'='.$tags->{$iiii}."\n");
574         # }
575         # $debug .= debug($printdebug, "****\n");
576         
577         $level = 0;
578         $ind = '_';
579         $out = '';
580         $debug .= debug($printdebug, "\n<!--PROCESSING BBCODE TREE:\n");
581         
582         while ($level >= 0) {
583                 my $goto_next = '';
584                 $debug .= debug($printdebug, "[$level:$ind:".int($bbtree->{$ind.'.count'})."]");
585                 #normal text
586                 if ($bbtree->{$ind.'.type'} eq 'text') {
587                         my $text = $bbtree->{$ind.'.value'};
588                         $debug .= debug($printdebug, "text: ".$text);
589                         $out .= $escape ? html_encode_line($text) : $text;
590                         
591                         $goto_next = 'tx';
592                 }
593                 #tag
594                 elsif ($bbtree->{$ind.'.type'} eq 'tag') {
595                         my $name = $bbtree->{$ind.'.name'};
596                         #endtag
597                         if ($name =~ /^\//) {
598                                 $debug .= debug($printdebug, "tag: [$name]");
599                                 $indd = $ind;
600                                 $indd =~ s/\.([0-9]+)$//;
601                                 if (exists($tags->{$name.'='}) && ($bbtree->{$indd.'.value'} ne '')) {
602                                         $out .= $tags->{$name.'='};
603                                 }
604                                 elsif (exists($tags->{$name})) {
605                                         $out .= $tags->{$name};
606                                 }
607                                 else {
608                                         $out .= $tags->{'/?'};
609                                         $debug .= debug($printdebug, "[unknown!]");
610                                 }
611                                 
612                                 $ind =~ s/\.([0-9]+)$//;
613                                 $level -= 1;
614                                 $debug .= debug($printdebug, "[<]");
615                                 if ($level > 0) {
616                                         $goto_next = 'nd';
617                                 }
618                                 else {
619                                         # time to end this
620                                         $level = -1;
621                                 }
622                         }
623                         #starttag
624                         else {
625                                 my $value = $bbtree->{$ind.'.value'};
626                                 if($bbtree->{$ind.'.closed'} ne '') {
627                                         $debug .= debug($printdebug, "tag: [$name]");
628                                         
629                                         if (exists($tags->{$name.'='}) && ($value ne '')) {
630                                                 if (exists($tags->{$name.'='.$value})) {
631                                                         $out .=
632                                                                 $tags->{$name.'='} .
633                                                                 $tags->{$name.'='.$value} .
634                                                                 $tags->{$name.'/='};
635                                                 }
636                                                 else {
637                                                         $out .=
638                                                                 $tags->{$name.'='} .
639                                                                 ($escape ? html_entity_encode_dec($value, 1) : $value) .
640                                                                 $tags->{$name.'/='};
641                                                 }
642                                         }
643                                         elsif (exists($tags->{$name})) {
644                                                 $out .= $tags->{$name};
645                                         }
646                                         else {
647                                                 $out .= $out.$tags->{'?'};
648                                                 $debug .= debug($printdebug, "[unknown!]");
649                                         }
650                                 }
651                                 else {
652                                         $debug .= debug($printdebug, "unclosed tag: [$name]");
653                                         my $text = $name . (($value ne '') ? ('='.$value) : '');
654                                         $out .= '['.($escape ? html_encode_line($text) : $text).']';
655                                 }
656                                 if ($bbtree->{$ind.'.count'} > 0) {
657                                         $ind = $ind.'.0';
658                                         $level += 1;
659                                         $debug .= debug($printdebug, "[>]");
660                                 }
661                                 else {
662                                         $goto_next = 'st';
663                                 }
664                         }
665                 }
666                 # what is this
667                 else {
668                         $debug .= debug($printdebug, "unknown thing: ".$bbtree->{$ind.'.type'});
669                         #should not occur with a correct bbtree
670                         #unless unimplemented
671                         $ind =~ s/\.([0-9]+)$//;
672                         $level -= 1;
673                         $debug .= debug($printdebug, "[<ui]");
674                         if ($level > 0) {
675                                 $goto_next = 'un';
676                         }
677                         else {
678                                 # time to end this
679                                 $level = -1;
680                         }
681                 }
682                 if ($goto_next ne '') {
683                         {do{
684                                 $ind =~ s/\.([0-9]+)$//;
685                                 my $i = int($1) + 1;
686                                 if (($i < $bbtree->{$ind.'.count'}) and ($1 ne '')){
687                                         # goto next
688                                         $ind = $ind.'.'.$i;
689                                         last;
690                                 }
691                                 else {
692                                         # step out
693                                         # should not occur with a correct bbtree
694                                         $debug .= debug($printdebug, "[<$goto_next]");
695                                         $level -= 1;
696                                 }
697                         } while ($level >= 0);}
698                 }
699                 
700                 $debug .= debug($printdebug, "[>$level:$ind]\n");
701         }
702         
703         $debug .= debug($printdebug, "-->\n");
704         return ($debug, $out);
705 }
706
707 #bbcode to html, TBD
708 sub bb_to_html {
709         (my $bb, my $printdebug) = @_;
710         my $ht;
711         my %bbtree;
712         my $debug;
713         
714         ($debug, %bbtree) = bbtree($bb, $printdebug);
715         ($debug, $ht) = convtree ($printdebug, $debug, 'html', \%bbtree);
716         
717         return $ht;
718 }
719
720 #bbcode to bb, TBD
721 sub bb_to_bbcode {
722         (my $bb, my $printdebug) = @_;
723         my $ht;
724         my %bbtree;
725         my $debug;
726         
727         ($debug, %bbtree) = bbtree($bb, $printdebug);
728         ($debug, $ht) = convtree ($printdebug, $debug, 'bb', \%bbtree);
729         
730         return $ht;
731 }
732
733 sub eval_bb {
734         (my $bb, my $full_url, my $password) = @_;
735         my $value;
736         my $before;
737         my $after;
738         
739         my $base_url = $full_url ?
740                 {'scheme' => SCHEME(), 'host' => WEBSITE()} :
741                 {'path' => ''};
742         
743         while ($bb =~ m/###([^#;]*);/g) {
744                 $value = $1;
745                 $before = $`;
746                 $after = $';
747                 
748                 if ($value =~ /^att&([0-9]+)$/) {
749                         $value = merge_url(
750                                 $base_url,
751                                 {'path' => CGI_ATTACH_PATH()},
752                                 {'path' => int($1)}
753                         )
754                 }
755                 elsif ($value =~ /^vw&([0-9]+)$/) {
756                         $value = merge_url(
757                                 $base_url,
758                                 {'path' => CGI_VIEWER_PATH()},
759                                 {'path' => int($1)}
760                         )
761                 }
762                 elsif ($value =~ /^fr&([0-9]+)$/) {
763                         $value = merge_url(
764                                 $base_url,
765                                 {'path' => CGI_FRAME_PATH()},
766                                 {'path' => int($1)}
767                         )
768                 }
769                 else {
770                         $value = '';
771                 }
772                 if (($value ne '') && ($password ne '')) {
773                         $value = merge_url(
774                                 $value,
775                                 {'query' => {'p' => $password}}
776                         );
777                 }
778                 $bb = $before . $value . $after;
779         }
780         return $bb;
781 }
782
783
784 sub html_encode_line {
785         (my $text, my $non_ascii, my $all) = @_;
786         my $html;
787         my $ind;
788         
789         $text =~ s/\r\n/\n/gs;
790         $text =~ s/\r/\n/gs;
791         
792         while ($text ne '') {
793                 $ind = index($text, "\n");
794                 if ($ind >= 0) {
795                         $html .= html_entity_encode_dec(substr($text, 0, $ind), $non_ascii, $all)."<br>\n";
796                         $text = substr($text, $ind+1);
797                 }
798                 else
799                 {
800                         $html .= html_entity_encode_dec($text, 1);
801                         $text = '';
802                 }
803         }
804         return $html;
805 }
806
807 sub debug {
808         (my $print, my $text) = @_;
809         
810         if ($print) {
811                 print $text;
812         }
813         
814         return $text;
815 }
816
817
818 sub print_html_start {
819         (my $fh) = @_;
820         print $fh '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "">'."\n";
821         print $fh '<html lang="en">'."\n";
822 }
823
824 sub print_html_end {
825         (my $fh) = @_;
826         print $fh '</html>'."\n";
827 }
828
829 sub print_html_head_start {
830         (my $fh) = @_;
831         print $fh ' <head>'."\n";
832         print $fh '  <meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
833         print $fh '  <link rel="icon" type="image/png" href="'.html_entity_encode_dec(FAVICON_PATH(),1).'">'."\n";
834         print $fh '  <link rel="stylesheet" href="'.html_entity_encode_dec(CGI_CSS_PATH(),1).'">'."\n";
835 }
836
837 sub print_html_head_end {
838         (my $fh) = @_;
839         print $fh ' </head>'."\n";
840 }
841         
842 sub print_html_body_start {
843         (my $fh) = @_;
844         print $fh ' <body>'."\n";
845         print $fh '  <a href="/"><img id="botmlogo" src="'.html_entity_encode_dec(CGI_LOGO_PATH(),1).'" alt="'.html_entity_encode_dec(WEBSITE(),1).'"></a>'."\n";
846         print $fh '  <div id="all">'."\n";
847 }
848
849 sub print_html_body_end {
850         (my $fh, my $hide_credits) = @_;
851         print $fh '  </div>'."\n";
852         unless ($hide_credits) {
853                 print $fh '  <p>'."\n";
854                 print $fh '   '.html_entity_encode_dec(STORY_CREDITS(),1).'<br>'."\n";
855                 print $fh '   '.html_entity_encode_dec(INTF_CREDITS(),1).'<br>'."\n";
856                 print $fh '   <a href="'.html_entity_encode_dec(SOURCE_URL(),1).'" class="cz">source code</a>'."\n";
857                 print $fh '  </p>'."\n";
858         }
859         print $fh '  <a href="/" class="cz">'.html_entity_encode_dec(WEBSITE(),1).'</a>'."\n";
860         print $fh ' </body>'."\n";
861 }
862
863 sub print_html_data {
864         (my $fh, my $data) = @_;
865         
866         foreach my $key (keys %$data) {
867                 unless ($key eq 'content') {
868                         my $val = $data->{$key};
869                         $val =~ s/(\r)?\n/\n /gs; # does the space make sense in HTML anyway?
870                         print $fh html_encode_line("$key: $val\n", 1);
871                 }
872         }
873         print $fh html_encode_line("\n".$data->{'content'});
874 }
875
876 sub print_viewer_page {
877         (
878                 my $file,
879                 my $context,
880                 my $state,
881                 my $settings,
882                 my $frame_data,
883                 my $prev_frame_data,
884                 my $next_frame_data,
885                 my $words_data,
886         ) = @_;
887         my $fh;
888         
889         my $launch      = $context->{'launch'};
890         my $access      = $context->{'access'};
891         my $password_ok = $context->{'password_ok'};
892         my $static      = $context->{'static'};
893         
894         my $frame          = int($context->{'frame'});
895         my $text_mode      = int($context->{'text_mode'});
896         my $timer_unlocked = int($context->{'timer_unlocked'});
897         my $timer          = int($context->{'timer'});
898         # my $words_page     = int($context->{'words_page'});
899         
900         my $prev_frame = $frame - 1;
901         my $next_frame = $frame + 1;
902         
903         my $story = $settings->{'story'};
904         my $title = $frame_data->{'title'};
905         my $command = ($frame_data->{'command'} ne '') ?
906                 $frame_data->{'command'} :
907                 $next_frame_data->{'title'};
908         
909         my $last_frame = int($state->{'last'});
910         my $ong_state  = int($state->{'state'});
911         
912         my $width  = int($frame_data->{'width'});
913         my $height = int($frame_data->{'height'});
914         my $frame_type = $frame_data->{'frametype'};
915         
916         my $timer_color_h = (($timer_unlocked >= 1) || ($ong_state >= STATE->{'ready'})) ? 'br' : 'ni';
917         my $timer_color_m = (($timer_unlocked >= 2) || ($ong_state >= STATE->{'ready'})) ? 'br' : 'ni';
918         my $timer_color_s = (($timer_unlocked >= 3) || ($ong_state >= STATE->{'ready'})) ? 'br' : 'ni';
919         
920         my $timer_h;
921         my $timer_m;
922         my $timer_s;
923         if (
924                 ($timer > 0) ||
925                 (($timer >= 0) && ($frame == 0))
926         ) {
927                 $timer_s = sprintf('%02d', $timer % 60);
928                 $timer_h = int($timer / 60);
929                 $timer_m = sprintf('%02d', $timer_h % 60);
930                 $timer_h = sprintf('%02d', $timer_h / 60);
931         }
932         elsif (($timer >= -15) && ($ong_state >= STATE->{'ready'})) {
933                 $timer_h = '00';
934                 $timer_m = '00';
935                 $timer_s = 'NG';
936         }
937         else {
938                 $timer_h = 'EE';
939                 $timer_m = 'EE';
940                 $timer_s = 'EE';
941         }
942         
943         my $words_posts = int($words_data->{'posts'});
944         my $words_link_text = 'Words'.(($words_posts > 0) ? "[$words_posts]" : '');
945         
946         my $prev_available = (($frame > 0) && $access);
947         my $next_available = ($launch || $password_ok || ($next_frame <= $last_frame));
948         my $prefetch_prev = (
949                 $password_ok ||
950                 ($prev_frame < $last_frame) || (  # avoid unseen trigger!
951                         ($prev_frame <= $last_frame) &&
952                         ($ong_state >= STATE->{'ready'})
953                 )
954         );
955         my $prefetch_next  = (
956                 $password_ok ||
957                 ($next_frame < $last_frame) || (  # avoid unseen trigger!
958                         ($next_frame <= $last_frame) &&
959                         ($ong_state >= STATE->{'ready'})
960                 )
961         );
962         my $show_timer = (
963                 (
964                         $access && $launch
965                 ) || (
966                         ($frame == $last_frame) && (
967                                 ($ong_state == STATE->{'waiting'}) ||
968                                 ($ong_state == STATE->{'ready'})
969                         )
970                 )
971         );
972         my $show_command = (
973                 $launch ||
974                 $password_ok ||
975                 (!$access) ||
976                 ($frame < $last_frame) || (
977                         ($ong_state >= STATE->{'ready'}) &&
978                         $context->{'show_command'}
979                 )
980         );
981         my $show_command_link = ($next_available || (!$access));
982         my $show_command_cursor = ((!$next_available) || ($command eq ''));
983         my $show_words = ($password_ok || ($access && !$launch));
984         
985         my $frame_indirect = !(
986                 (!$access) || (
987                         ($frame <= $last_frame) &&
988                         ($ong_state > STATE->{'inactive'})
989                 )
990         );
991         my $prevframe_indirect = !($prev_frame <= $last_frame);
992         my $nextframe_indirect = !($next_frame <= $last_frame);
993         
994         my $password_query;
995         
996         my $base_url   = CGI_PATH();
997         my $goto_url   = CGI_GOTO_PATH();
998         my $timer_url  = CGI_TIMER_PATH();
999         my $viewer_full_url = merge_url(
1000                 {'scheme' => SCHEME(), 'host' => WEBSITE()},
1001                 {'path' => CGI_VIEWER_PATH()},
1002                 {'path' => $frame}
1003         );
1004         my $viewer_url = merge_url(
1005                 {'path' => CGI_VIEWER_PATH()},
1006                 {'path' => $frame}
1007         );
1008         my $viewer_0_url = merge_url(
1009                 {'path' => CGI_VIEWER_PATH()},
1010                 {'path' => 0}
1011         );
1012         my $viewer_prev_url = merge_url(
1013                 {'path' => CGI_VIEWER_PATH()},
1014                 {'path' => $prev_frame}
1015         );
1016         my $viewer_next_url = merge_url(
1017                 {'path' => CGI_VIEWER_PATH()},
1018                 {'path' => $next_frame}
1019         );
1020         my $viewer_last_url = merge_url(
1021                 {'path' => CGI_VIEWER_PATH()},
1022                 {'path' => ($static ? -1 : $last_frame)}
1023         );
1024         unless ($password_ok) {
1025                 my $page_file;
1026                 $viewer_0_url = $base_url;
1027                 if ($prev_frame == 0) {
1028                         $viewer_prev_url = $viewer_0_url;
1029                 }
1030                 else {
1031                         if ($prev_frame_data->{'page'} ne '') {
1032                                 $page_file = $prev_frame_data->{'page'};
1033                         }
1034                         else {
1035                                 $page_file = sprintf(
1036                                         $settings->{'frame'},
1037                                         $prev_frame, 'htm'
1038                                 );
1039                         }
1040                         if (_x_encoded('-f',
1041                                 join_path(PATH_SEPARATOR(), WWW_PATH() , $page_file)
1042                         )) {
1043                                 $viewer_prev_url = merge_url(
1044                                         {'path' => $base_url},
1045                                         {'path' => $page_file}
1046                                 );
1047                         }
1048                 }
1049                 if ($next_frame < $last_frame) {
1050                         if ($next_frame_data->{'page'} ne '') {
1051                                 $page_file = $next_frame_data->{'page'};
1052                         }
1053                         else {
1054                                 $page_file = sprintf(
1055                                         $settings->{'frame'},
1056                                         $next_frame, 'htm'
1057                                 );
1058                         }
1059                         if (_x_encoded('-f',
1060                                 join_path(PATH_SEPARATOR(), WWW_PATH() , $page_file)
1061                         )) {
1062                                 $viewer_next_url = merge_url(
1063                                         {'path' => $base_url},
1064                                         {'path' => $page_file}
1065                                 );
1066                         }
1067                 }
1068         }
1069         my $bbcode_url = ($text_mode == TEXT_MODE->{'bb'}) ?
1070                 merge_url(
1071                         {'path' => CGI_BBCODE_PATH()},
1072                         {'path' => $frame}
1073                 ) :
1074                 merge_url (
1075                         $viewer_url,
1076                         {
1077                                 'query'=>{
1078                                 'b' => TEXT_MODE->{'bb'}
1079                                 },
1080                                 'fragment'=>'insb'
1081                         }
1082                 );
1083         my $info_url = ($text_mode == TEXT_MODE->{'info'}) ?
1084                 merge_url(
1085                         {'path' => CGI_INFO_PATH()},
1086                         {'path' => $frame}
1087                 ) :
1088                 merge_url (
1089                         $viewer_url,
1090                         {
1091                                 'query'=>{
1092                                 'b' => TEXT_MODE->{'info'}
1093                                 },
1094                                 'fragment'=>'insb'
1095                         }
1096                 );
1097         my $words_url = merge_url (
1098                 $viewer_url,
1099                 {
1100                         'query'=>{
1101                         'b' => TEXT_MODE->{'words'}
1102                         },
1103                         'fragment'=>'insw'
1104                 }
1105         );
1106         my $frame_file;
1107         my $frame_url;
1108         my $frame_prev_url;
1109         my $frame_next_url;
1110         my $frame_normal_url;
1111         my $frame_full_url;
1112         if ($frame_data->{'frame'} ne '') {
1113                 $frame_file = $frame_data->{'frame'};
1114         }
1115         else {
1116                 $frame_file = sprintf(
1117                         $settings->{'frame'},
1118                         $frame, $frame_data->{'ext'}
1119                 );
1120         }
1121         $frame_normal_url = merge_url(
1122                         {'path' => CGI_PATH()},
1123                         {'path' => $frame_file}
1124                 );
1125         $frame_url = $frame_indirect ?
1126                 merge_url(
1127                         {'path' => CGI_FRAME_PATH()},
1128                         {'path' => $frame}
1129                 ) :
1130                 $frame_normal_url;
1131         $frame_full_url = merge_url(
1132                 {'scheme' => SCHEME(), 'host' => WEBSITE()},
1133                 {'path' => $frame_normal_url}
1134         );
1135         if ($prevframe_indirect) {
1136                 $frame_prev_url = merge_url(
1137                         {'path' => CGI_FRAME_PATH()},
1138                         {'path' => $prev_frame}
1139                 );
1140         }
1141         elsif ($prev_frame_data->{'frame'} ne '') {
1142                 $frame_prev_url = merge_url(
1143                         {'path' => CGI_PATH()},
1144                         {'path' => $prev_frame_data->{'frame'}}
1145                 );
1146         }
1147         else {
1148                 $frame_prev_url = merge_url(CGI_PATH(), sprintf(
1149                         $settings->{'frame'}, $prev_frame, $prev_frame_data->{'ext'}
1150                 ));
1151         }
1152         if ($nextframe_indirect) {
1153                 $frame_next_url = merge_url(
1154                         {'path' => CGI_FRAME_PATH()},
1155                         {'path' => $next_frame}
1156                 );
1157         }
1158         elsif ($next_frame_data->{'frame'} ne '') {
1159                 $frame_next_url = merge_url(
1160                         {'path' => CGI_PATH()},
1161                         {'path' => $next_frame_data->{'frame'}}
1162                 );
1163         }
1164         else {
1165                 $frame_next_url = merge_url(CGI_PATH(), sprintf(
1166                         $settings->{'frame'}, $next_frame, $next_frame_data->{'ext'}
1167                 ));
1168         }
1169         
1170         if ($password_ok) {
1171                 $password_query = url_query_encode({'p', $settings->{'password'}});
1172                 $goto_url        = merge_url($goto_url       , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1173                 $info_url        = merge_url($info_url       , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1174                 $words_url       = merge_url($words_url      , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1175                 $bbcode_url      = merge_url($bbcode_url     , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1176                 $viewer_url      = merge_url($viewer_url     , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1177                 $viewer_0_url    = merge_url($viewer_0_url   , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1178                 $viewer_prev_url = merge_url($viewer_prev_url, {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1179                 $viewer_next_url = merge_url($viewer_next_url, {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1180                 $viewer_last_url = merge_url($viewer_last_url, {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1181                 if ($frame_indirect) {
1182                         $frame_url     = merge_url($frame_url      , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1183                 }
1184                 if ($prevframe_indirect) {
1185                         $frame_prev_url= merge_url($frame_prev_url , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1186                 }
1187                 if ($nextframe_indirect) {
1188                         $frame_next_url= merge_url($frame_next_url , {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1189                 }
1190         }
1191         my $_base_url        = html_entity_encode_dec($base_url       , 1);
1192         my $_goto_url        = html_entity_encode_dec($goto_url       , 1);
1193         my $_info_url        = html_entity_encode_dec($info_url       , 1);
1194         my $_words_url       = html_entity_encode_dec($words_url      , 1);
1195         my $_bbcode_url      = html_entity_encode_dec($bbcode_url     , 1);
1196         my $_timer_url       = html_entity_encode_dec($timer_url      , 1);
1197         my $_viewer_full_url = html_entity_encode_dec($viewer_full_url, 1);
1198         my $_viewer_url      = html_entity_encode_dec($viewer_url     , 1);
1199         my $_viewer_0_url    = html_entity_encode_dec($viewer_0_url   , 1);
1200         my $_viewer_prev_url = html_entity_encode_dec($viewer_prev_url, 1);
1201         my $_viewer_next_url = html_entity_encode_dec($viewer_next_url, 1);
1202         my $_viewer_last_url = html_entity_encode_dec($viewer_last_url, 1);
1203         my $_frame_url       = html_entity_encode_dec($frame_url      , 1);
1204         my $_frame_prev_url  = html_entity_encode_dec($frame_prev_url , 1);
1205         my $_frame_next_url  = html_entity_encode_dec($frame_next_url , 1);
1206         my $_frame_full_url  = html_entity_encode_dec($frame_full_url , 1);
1207         
1208         my $_story      = html_entity_encode_dec($story     , 1);
1209         my $_title      = html_entity_encode_dec($title     , 1);
1210         my $_command    = html_entity_encode_dec($command   , 1);
1211         my $_frame_type = html_entity_encode_dec($frame_type, 1);
1212         
1213         my $_website_name = html_entity_encode_dec(WEBSITE_NAME(), 1);
1214         
1215         if ($text_mode == TEXT_MODE->{'info'}) {
1216                 if ($show_command) {
1217                         $frame_data->{'command'} = $command;
1218                 }
1219                 if ($access) {
1220                         $frame_data->{'frame'} = $frame_file;
1221                 }
1222                 if ($frame_data->{'page'} eq '') {
1223                         unless (($access) && ($frame < $last_frame)) {
1224                                 $frame_data->{'page'} = '';
1225                         }
1226                         elsif ($frame == 0) {
1227                                 $frame_data->{'page'} = 'index.htm';
1228                         }
1229                         else {
1230                                 $frame_data->{'page'} = sprintf(
1231                                         $settings->{'frame'},
1232                                         $frame, 'htm'
1233                                 );
1234                         }
1235                 }
1236         }
1237         
1238         # everything determined, now start generating
1239         
1240         if (ref($file)) {
1241                 $fh=$file;
1242                 unless (seek($fh, 0, 0)) {
1243                         #don't actually fail here
1244                 }
1245         }
1246         else {
1247                 unless (open_encoded($fh, ">:encoding(UTF-8)", $file)) {
1248                         return 0;
1249                 }
1250         }
1251         
1252         print_html_start($fh);
1253         print_html_head_start($fh);
1254         
1255         print $fh '  <title>'.$_title;
1256         if ($story ne $title) {
1257                 print $fh ' &bull; '.$_story;
1258         }
1259         print $fh ' &bull; '.$_website_name.'</title>'."\n";
1260         print $fh '  <link rel="index" href="'.$_goto_url.'">'."\n";
1261         print $fh '  <link rel="start" href="'.$_viewer_0_url.'">'."\n";
1262         if ($prev_available) {
1263                 print $fh '  <link rel="prev" href="'.$_viewer_prev_url.'">'."\n";
1264                 if ($prefetch_prev) {
1265                         print $fh '  <link rel="prefetch" href="'.$_viewer_prev_url.'">'."\n";
1266                         print $fh '  <link rel="prefetch" href="'.$_frame_prev_url.'">'."\n";
1267                 }
1268         }
1269         if ($next_available) {
1270                 print $fh '  <link rel="next" href="'.$_viewer_next_url.'">'."\n";
1271                 if ($prefetch_next) {
1272                         print $fh '  <link rel="prefetch" href="'.$_viewer_next_url.'">'."\n";
1273                         print $fh '  <link rel="prefetch" href="'.$_frame_next_url.'">'."\n";
1274                 }
1275         }
1276         if ($show_timer) {
1277                 print $fh '  <script src="'.$_timer_url.'"></script>'."\n";
1278         }
1279         
1280         print_html_head_end($fh);
1281         print_html_body_start($fh);
1282         
1283         print $fh '   <div id="inst" class="ins">'."\n";
1284         
1285         print $fh '    <div id="title">'."\n";
1286         print $fh '     <h1 id="titletext">'.$_title.'</h1>'."\n";
1287         print $fh '    </div>'."\n";
1288         
1289         print $fh '   </div>'."\n";
1290         print $fh '   <div id="framespace">'."\n";
1291         
1292         print $fh '    <img src="'.$_frame_url.'" id="frame" class="'.$_frame_type.'" alt="'.$frame.'" title="'.$_title.'" width="'.$width.'" height="'.$height.'">'."\n";
1293         
1294         print $fh '   </div>'."\n";
1295         print $fh '   <div id="insb" class="ins">'."\n";
1296         
1297         if ($text_mode == TEXT_MODE->{'info'}) {
1298                 print $fh '    <div id="chat">'."\n";
1299                 
1300                 print_html_data($fh, $frame_data); 
1301                 
1302                 print $fh '    </div>'."\n";
1303         }
1304         elsif ($text_mode == TEXT_MODE->{'bb'}) {
1305                 print $fh '    <div id="chat">'."\n";
1306                 
1307                 print $fh '[quote][center][size=200]'.$_title.'[/size]<br>'."\n";
1308                 print $fh '[url='.$_viewer_full_url.'][img]'.$_frame_full_url.'[/img][/url][/center]<br>'."\n";
1309                 print $fh html_encode_line(
1310                         bb_to_bbcode(
1311                                 eval_bb(
1312                                         $frame_data->{'content'},
1313                                         1
1314                                 )
1315                         )
1316                 );
1317                 print $fh '[/quote]'."\n";
1318                 
1319                 print $fh '    </div>'."\n";
1320         }
1321         elsif ($frame_data->{'content'} ne '') {
1322                 print $fh '    <div id="undertext">'."\n";
1323                 print $fh bb_to_html(
1324                         eval_bb(
1325                                 $frame_data->{'content'},
1326                                 0,
1327                                 $password_ok ? $settings->{'password'} : ''
1328                         )
1329                 )."\n";
1330                 print $fh '    </div>'."\n";
1331         }
1332         
1333         print $fh '    <div id="command">'."\n";
1334         
1335         if ($show_timer) {
1336                 print $fh '     <span id="timer">';
1337                 print $fh '[<span id="ongh" class="hv '.$timer_color_h.'">'.$timer_h.'</span>';
1338                 print $fh ':<span id="ongm" class="hv '.$timer_color_m.'">'.$timer_m.'</span>';
1339                 print $fh ':<span id="ongs" class="hv '.$timer_color_s.'">'.$timer_s.'</span>]';
1340                 print $fh '</span><br>'."\n";
1341         }
1342         print $fh '     &gt;';
1343         if ($show_command_link) {
1344                 print $fh '<a href="'.($access ? $_viewer_next_url : $_viewer_last_url).'">';
1345         }
1346         if ($show_command) {
1347                 print $fh $_command;
1348         }
1349         if ($show_command_cursor) {
1350                 print $fh '<span class="inp">_</span>';
1351         }
1352         if ($show_command_link) {
1353                 print $fh '</a>';
1354         }
1355         print $fh "<br>\n";
1356         print $fh "    </div>\n";
1357         
1358         print $fh '    <div id="underlinks">'."\n     ";
1359         
1360         unless (($frame == 0) && $static) {
1361                 print $fh '<a href="'.$_base_url.'">Once again</a> | ';
1362         }
1363         if ($prev_available) {
1364                 print $fh '<a href="'.$_viewer_prev_url.'">Before</a> | ';
1365         }
1366         unless ($frame == $last_frame) {
1367                 print $fh '<a href="'.$_viewer_last_url.'">Now</a> | ';
1368         }
1369         print $fh '<a href="'.$_goto_url.'">GOTO</a>'."\n";
1370         print $fh '     <span style="float: right;">'."\n      ";
1371         if ($text_mode == TEXT_MODE->{'normal'}) {
1372                 if ($show_words) {
1373                         print $fh '<a href="'.$_words_url.'">'.$words_link_text.'</a> | ';
1374                 }
1375         }
1376         else {
1377                 print $fh '<a href="'.$_viewer_url.'">Without</a> | ';
1378         }
1379         print $fh '<a href="'.$_info_url.'">Info</a> | ';
1380         print $fh '<a href="'.$_bbcode_url.'">BB</a>';
1381         print $fh "\n     </span>\n";
1382         
1383         print $fh "    </div>\n";
1384         print $fh "   </div>\n";
1385         
1386         if (($text_mode == TEXT_MODE->{'words'}) && $show_words) {
1387                 print_comments($fh, $context, $settings, $words_data);
1388         }
1389         
1390         print_html_body_end($fh, $ong_state == STATE->{'inactive'});
1391         print_html_end($fh);
1392         
1393         
1394         unless (ref($file)) {
1395                 close ($fh);
1396         }
1397         else {
1398                 truncate ($fh , tell($fh));
1399         }
1400         
1401         return 1;
1402 }
1403
1404 sub print_comments {
1405         (my $fh, my $context, my $settings, my $words_data) = @_;
1406         
1407         my $password_ok = $context->{'password_ok'};
1408         my $frame = int($context->{'frame'});
1409         my $page = int($context->{'words_page'});
1410         my $post_count = int($words_data->{'posts'});
1411         my $id_start = $page * COMMENT_PAGE_LENGTH();
1412         my $id_stop = $id_start + COMMENT_PAGE_LENGTH();
1413         my $older = ($page > 0) ? ($page-1) : '';
1414         my $newer;
1415         my $password_query;
1416         if ($id_stop >= $post_count) {
1417                         $id_stop = $post_count;
1418         }
1419         else {
1420                 $newer = $page+1;
1421         }
1422         my $links;
1423         
1424         my $words_url = merge_url(
1425                 {'path' => CGI_VIEWER_PATH()},
1426                 {
1427                         'path' => $frame,
1428                         'query' => {'b' => TEXT_MODE->{'words'}},
1429                 }
1430         );
1431         my $older_url = merge_url(
1432                 $words_url,
1433                 {
1434                         'query' => {'i' => $page-1},
1435                         'fragment' => 'insw',
1436                         'append_query' => 1
1437                 }
1438         );
1439         my $newer_url = merge_url(
1440                 $words_url,
1441                 {
1442                         'query' => {'i' => $page+1},
1443                         'fragment' => 'insw',
1444                         'append_query' => 1
1445                 }
1446         );
1447         
1448         if ($password_ok) {
1449                 $password_query = url_query_encode({'p', $settings->{'password'}});
1450                 $older_url = merge_url($older_url, {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1451                 $newer_url = merge_url($older_url, {'query' => $password_query, 'append_query' => 1, 'preserve_fragment' => 1});
1452         }
1453         
1454         my $_post_url = html_entity_encode_dec(CGI_WORDS_PATH(), 1);
1455         my $_password = html_entity_encode_dec($settings->{'password'}, 1);
1456         my $_older_url = html_entity_encode_dec($older_url, 1);
1457         my $_newer_url = html_entity_encode_dec($newer_url, 1);
1458         
1459         if (($older ne '') || ($newer ne '')) {
1460                 $links .= '    <div class="underlinks">'."\n";
1461                 $links .= '     ';
1462                 if ($older ne '') {
1463                         $links .= '<a href="'.$_older_url.'">Older</a>'
1464                 }
1465                 if (($older ne '') && ($newer ne '')) {
1466                         $links .= ' | ';
1467                 }
1468                 if ($newer ne '') {
1469                         $links .= '<a href="'.$_newer_url.'">Newer</a>';
1470                 }
1471                 $links .= "\n";
1472                 $links .= '    </div>'."\n";
1473         }
1474         
1475         print $fh '   <div class="space"></div>'."\n";
1476         print $fh '   <div id="insw" class="ins">'."\n";
1477         
1478         print $fh '    <div class="title" id="wordstitle">'."\n";
1479         print $fh '     <h1 class="titletext" id="wordstitletext">Words</h1>'."\n";
1480         print $fh '    </div>'."\n";
1481         
1482         if ($links ne '') {
1483                 print $fh $links;
1484         }
1485         
1486         print $fh '    <div class="undertext" id="words">'."\n";
1487         
1488         if ($post_count > 0) {
1489                 for (my $i=$id_start; $i<$id_stop; ++$i) {
1490                         my $ID = $words_data->{'content'}->[$i];
1491                         my $post_path = join_path(PATH_SEPARATOR(), DATA_WORDS_PATH(), $ID);
1492                         my %post_data = read_data_file($post_path);
1493                         
1494                         my $post_time = int($post_data{'posttime'});
1495                         my $edit_time = int($post_data{'edittime'});
1496                         
1497                         my $post_time_text;
1498                         my $edit_time_text;
1499                         
1500                         if ($post_time != 0) {
1501                                 my @time_tab = gmtime($post_time);
1502                                 $post_time_text = sprintf(
1503                                         '%04d.%02d.%02d %02d:%02d:%02d UTC',
1504                                         $time_tab[5]+1900,
1505                                         $time_tab[4]+1,
1506                                         $time_tab[3],
1507                                         $time_tab[2],
1508                                         $time_tab[1],
1509                                         $time_tab[0]
1510                                 );
1511                         }
1512                         if (($edit_time !=0) && ($edit_time != $post_time)) {
1513                                 my @time_tab = gmtime($edit_time);
1514                                 $edit_time_text = sprintf(
1515                                         '%04d.%02d.%02d %02d:%02d UTC',
1516                                         $time_tab[5]+1900,
1517                                         $time_tab[4]+1,
1518                                         $time_tab[3],
1519                                         $time_tab[2],
1520                                         $time_tab[1]
1521                                 );
1522                         }
1523                         my $quote_url = merge_url(
1524                                 {'path' => CGI_WORDS_PATH()},
1525                                 {
1526                                         'query' => {
1527                                                 'f' => $frame,
1528                                                 'quote' => $ID,
1529                                                 'p' => ($password_ok ? $settings->{'password'} : '')
1530                                         }
1531                                 }
1532                         );
1533                         my $edit_url = merge_url(
1534                                 {'path' => CGI_WORDS_PATH()},
1535                                 {
1536                                         'query' => {
1537                                                 'f' => $frame,
1538                                                 'edit' => $ID,
1539                                                 'key' => $post_data{'key'},
1540                                                 'username' => $post_data{'name'},
1541                                                 'p' => ($password_ok ? $settings->{'password'} : '')
1542                                         }
1543                                 }
1544                         );
1545                         my $remove_url = merge_url(
1546                                 {'path' => CGI_WORDS_PATH()},
1547                                 {
1548                                         'query' => {
1549                                                 'f' => $frame,
1550                                                 'remove' => $ID,
1551                                                 'key' => $post_data{'key'},
1552                                                 'username' => $post_data{'name'},
1553                                                 'p' => ($password_ok ? $settings->{'password'} : '')
1554                                         }
1555                                 }
1556                         );
1557                         
1558                         my $_ID         = html_entity_encode_dec($ID, 1);
1559                         my $_name       = html_entity_encode_dec($post_data{'name'}, 1);
1560                         my $_quote_url  = html_entity_encode_dec($quote_url, 1);
1561                         my $_edit_url   = html_entity_encode_dec($edit_url, 1);
1562                         my $_remove_url = html_entity_encode_dec($remove_url, 1);
1563                         
1564                         print $fh '     <div id="'.$_ID.'"class="opomba">'."\n";
1565                         print $fh '      <div class="opomba_info">'."\n";
1566                         print $fh '       <a href="#'.$_ID.'" class="bi hu">'.$i.': '.$_name;
1567                         if ($post_time_text ne '') {
1568                                 print $fh ' &bull; '.$post_time_text;
1569                         }
1570                         if ($edit_time_text ne '') {
1571                                 print $fh ' &bull; '.$edit_time_text;
1572                         }
1573                         print $fh '</a>'."\n";
1574                         print $fh '       <div class="pr">'."\n";
1575                         print $fh '        <a href="'.$_quote_url.'" class="bi hu">quote</a> | <a href="'.$_edit_url.'" class="bi hu">edit</a> | <a href="'.$_remove_url.'" class="bi hu">remove</a>'."\n";
1576                         print $fh '       </div>'."\n";
1577                         print $fh '      </div>'."\n";
1578                         print $fh '      <div class="opomba_text">'."\n";
1579                   print $fh bb_to_html(
1580                                 eval_bb(
1581                                         $post_data{'content'},
1582                                         0,
1583                                         $password_ok ? $settings->{'password'} : ''
1584                                 )
1585                         )."\n";
1586                         print $fh '      </div>'."\n";
1587                         print $fh '     </div>'."\n";
1588                         print $fh '     <br>'."\n";
1589                 }
1590         }
1591         
1592         print $fh '     <form method="post" action="'.$_post_url.'">'."\n";
1593         print $fh '      <b>Your words:</b>'."\n";
1594         print $fh '      <textarea class="inta" name="words" rows="4"></textarea>'."\n";
1595         print $fh '      <table cellpadding="0" cellspacing="0" border="0"><tr>'."\n";
1596         print $fh '       <td><b>Your name: </b></td>'."\n";
1597         print $fh '       <td><input class="intx" type="text" name="username" value=""></td>'."\n";
1598         print $fh '       <td></td>'."\n";
1599         print $fh '      </tr><tr>'."\n";
1600         print $fh '       <td><b>Optional password: </b></td>'."\n";
1601         print $fh '       <td><input class="intx" type="password" name="password" value=""></td>'."\n";
1602         print $fh '       <td>(if you want to edit later)</td>'."\n";
1603         print $fh '      </tr><tr>'."\n";
1604         print $fh '       <td><b>Leave this empty: </b></td>'."\n";
1605         print $fh '       <td><input class="intx" type="text" name="password2" value=""></td>'."\n";
1606         print $fh '       <td>'."\n";
1607         print $fh '        <input class="inbt" type="submit" name="post" value="Send">'."\n";
1608         print $fh '        <input class="inbt" type="submit" name="preview" value="Preview">'."\n";
1609         print $fh '       </td>'."\n";
1610         print $fh '      </tr></table>'."\n";
1611         print $fh '      <input type="hidden" name="f" value="'.$frame.'">'."\n";
1612         if ($password_ok) {
1613                 print $fh '      <input type="hidden" name="p" value="'.$_password.'">'."\n";
1614         }
1615         print $fh '     </form>'."\n";
1616         print $fh '    </div>'."\n";
1617         
1618         if ($links ne '') {
1619                 print $fh $links;
1620         }
1621         
1622         print $fh '   </div>'."\n";
1623 }
1624
1625 sub write_index {
1626         (
1627                 my $state,
1628                 my $settings,
1629                 my $pass,
1630                 my $mode,
1631                 my $pause
1632         ) = @_;
1633         my $fh;
1634         my $r = 1;
1635         my $ong_state = int($state->{'state'});
1636         
1637         unless (open_encoded($fh, ">:encoding(UTF-8)", WWW_INDEX_PATH())) {
1638                 return 0;
1639         }
1640         
1641         # normal running story
1642         if ($ong_state > STATE->{'inactive'}) {
1643                 my %frame_data     = read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), 0));
1644                 my %next_frame_data= read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), 1));
1645                 my %default        = read_data_file(DATA_DEFAULT_PATH());
1646                 my %words_data     = read_data_file(
1647                         join_path(PATH_SEPARATOR(), DATA_WORDS_PATH(), 0),
1648                         '', # encoding
1649                         0,  # no header
1650                         1,  # header only
1651                         1,  # as list
1652                 );
1653                 
1654                 %frame_data     = merge_settings(\%default,      \%frame_data);
1655                 %next_frame_data= merge_settings(\%default, \%next_frame_data);
1656                 
1657                 $r = print_viewer_page(
1658                         $fh,
1659                         {
1660                                 'launch'         => 0,
1661                                 'frame'          => 0,
1662                                 'access'         => 1,
1663                                 'password_ok'    => 0,
1664                                 'timer_unlocked' => 3, # not relevant
1665                                 'timer'          => 0, # not relevant
1666                                 'static'         => 1,
1667                                 'show_command'   => 1,
1668                                 'text_mode'      => TEXT_MODE->{'normal'},
1669                                 'words_page'     => 0 # not relevant
1670                         },
1671                         $state,
1672                         $settings,
1673                         \%frame_data,
1674                         \%default, # prev
1675                         \%next_frame_data,
1676                         \%words_data
1677                 );
1678         }
1679         # no conditions met, pretend a normal Apache2 index
1680         elsif ($pass != 1) { 
1681                 my $index_of = CGI_PATH;
1682                 $index_of =~ s/\/$//g;
1683                 
1684                 my $_index_of     = html_entity_encode_dec($index_of  , 1);
1685                 my $_2words_date  = html_entity_encode_dec(INTF_DATE(), 1);
1686                 my $_coin_date    = html_entity_encode_dec(COIN_DATE(), 1);
1687                 my $_website      = html_entity_encode_dec(WEBSITE()  , 1);
1688                 
1689                 print_html_start ($fh);
1690                 print $fh ' <head>'."\n";
1691                 print $fh '  <meta http-equiv="Content-type" content="text/html; charset=UTF-8">'."\n";
1692                 print $fh '  <title>Index of '.$_index_of.'</title>'."\n";
1693                 print $fh ' </head>'."\n";
1694                 print $fh ' <body>'."\n";
1695                 print $fh '  <h1>Index of '.$_index_of.'</h1>'."\n";
1696                 print $fh '  <table>'."\n";
1697                 print $fh '   <tr>'."\n";
1698                 print $fh '    <th><img src="/icons/blank.gif" alt="[ICO]"></th>'."\n";
1699                 print $fh '    <th><a href="?C=N;O=D">Name</a></th>'."\n";
1700                 print $fh '    <th><a href="?C=M;O=A">Last modified</a></th>'."\n";
1701                 print $fh '    <th><a href="?C=S;O=A">Size</a></th>'."\n";
1702                 print $fh '    <th><a href="?C=D;O=A">Description</a></th>'."\n";
1703                 print $fh '   </tr><tr>'."\n";
1704                 print $fh '    <th colspan="5"><hr></th>'."\n";
1705                 print $fh '   </tr><tr>'."\n";
1706                 print $fh '    <td valign="top"><img src="/icons/back.gif" alt="[DIR]"></td>'."\n";
1707                 print $fh '    <td><a href="/">Parent Directory</a></td>'."\n";
1708                 print $fh '    <td>&nbsp;</td>'."\n";
1709                 print $fh '    <td align="right">  - </td>'."\n";
1710                 print $fh '    <td>&nbsp;</td>'."\n";
1711                 print $fh '   </tr><tr>'."\n";
1712                 print $fh '    <td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td>'."\n";
1713                 print $fh '    <td><a href="2words/">2words/</a></td>'."\n";
1714                 print $fh '    <td align="right">'.$_2words_date.'  </td>'."\n";
1715                 print $fh '    <td align="right">  - </td><td>&nbsp;</td>'."\n";
1716                 print $fh '   </tr><tr>'."\n";
1717                 print $fh '    <td valign="top"><img src="/icons/folder.gif" alt="[DIR]"></td>'."\n";
1718                 print $fh '    <td><a href="coin/">coin/</a></td>'."\n";
1719                 print $fh '    <td align="right">'.$_coin_date.'  </td>'."\n";
1720                 print $fh '    <td align="right">  - </td><td> Coincidence </td>'."\n";
1721                 print $fh '   </tr><tr>'."\n";
1722                 print $fh '    <th colspan="5"><hr></th>'."\n";
1723                 print $fh '   </tr>'."\n";
1724                 print $fh '  </table>'."\n";
1725                 print $fh '  <address>Apache/2.2.22 (Debian) Server at '.$_website.' Port 80</address>'."\n";
1726                 print $fh '  </body>'."\n";
1727                 print_html_end ($fh);
1728         }
1729         # the launch index
1730         else {
1731                 my %frame_data     = read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), 0));
1732                 my %next_frame_data= read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), 1));
1733                 my %default        = read_data_file(DATA_DEFAULT_PATH());
1734                 my %coin_data      = read_data_file(DATA_COIN_PATH());
1735                 
1736                 %frame_data     = merge_settings(\%default,      \%frame_data);
1737                 %next_frame_data= merge_settings(\%default, \%next_frame_data);
1738                 
1739                 if (($mode == INTF_STATE->{'>'}) && $pause) {
1740                         $r = print_viewer_page(
1741                                 $fh,
1742                                 {
1743                                         'launch'         => 1,
1744                                         'frame'          => 0,
1745                                         'access'         => 1,
1746                                         'password_ok'    => 0,
1747                                         'timer_unlocked' => 3,
1748                                         'timer'          => 0,
1749                                         'static'         => 1,
1750                                         'show_command'   => 1,
1751                                         'text_mode'      => TEXT_MODE->{'normal'},
1752                                         'words_page'     => 0 # not relevant
1753                                 },
1754                                 $state,
1755                                 $settings,
1756                                 \%frame_data,
1757                                 \%default, # prev
1758                                 \%next_frame_data,
1759                                 {'posts' => 0} # words_data
1760                         );
1761                         return $r;
1762                 }
1763                 
1764                 my $index_of = CGI_PATH;
1765                 $index_of =~ s/\/$//g;
1766                 my $title;
1767                 my $frame_file;
1768                 my $undertext = '';
1769                 my $show_parent_dir = 0;
1770                 my $show_yb = 0;
1771                 my $show_folders = 0;
1772                 my $timer = '';
1773                 my $timer_color = 'ni';
1774                 if ($mode == INTF_STATE->{'>'}) {
1775                         $title = $settings->{'story'}; # $frame_data{'title'} ?
1776                         $frame_file = 'intf-tr.gif';
1777                         $undertext = '...';
1778                         $timer = '--';
1779                 }
1780                 elsif ($mode == INTF_STATE->{'<<'}) {
1781                         $title = 'Index of';
1782                         $frame_file = 'intf-ll.gif';
1783                         $show_parent_dir = 1;
1784                         $show_yb = 1;
1785                         $timer = 'EE';
1786                         $timer_color = 'br';
1787                 }
1788                 elsif ($mode == INTF_STATE->{'>>'}) {
1789                         $title = 'Index of';
1790                         $frame_file = 'intf-pp.gif';
1791                         $show_parent_dir = 1;
1792                         $show_yb = 1;
1793                         $timer = 'EE';
1794                 }
1795                 else
1796                 {
1797                         $title = 'Index of '.$index_of;
1798                         $frame_file = 'intf-kw.gif';
1799                         $show_parent_dir = 1;
1800                         $show_folders = 1;
1801                 }
1802                 my $frame_url = merge_url(
1803                         {'path' => CGI_PATH()},
1804                         {'path' => $frame_file}
1805                 );
1806                 my $coin_server = $coin_data{'server'};
1807                 
1808                 my $_title        = html_entity_encode_dec($title           , 1);
1809                 my $_website_name = html_entity_encode_dec(WEBSITE_NAME()   , 1);
1810                 my $_frame_url    = html_entity_encode_dec($frame_url       , 1);
1811                 my $_undertext    = html_entity_encode_dec($undertext       , 1);
1812                 my $_2words_date  = html_entity_encode_dec(INTF_DATE()      , 1);
1813                 my $_coin_date    = html_entity_encode_dec(COIN_DATE()      , 1);
1814                 my $_coin_server  = html_entity_encode_dec($coin_server     , 1);
1815                 my $_2words_url   = html_entity_encode_dec(CGI_2WORDS_PATH(), 1);
1816                 my $_coin_url     = html_entity_encode_dec(CGI_COIN_PATH()  , 1);
1817                 
1818                 print_html_start($fh);
1819                 print_html_head_start($fh);
1820                 
1821                 print $fh '  <title>'.$_title.' &bull; '.$_website_name.'</title>'."\n";
1822                 
1823                 print_html_head_end($fh);
1824                 print_html_body_start($fh);
1825                 
1826                 print $fh '   <div id="inst" class="ins">'."\n";
1827                 
1828                 print $fh '    <div id="title">'."\n";
1829                 print $fh '     <h1 id="titletext">'.$_title.'</h1>'."\n";
1830                 print $fh '    </div>'."\n";
1831                 
1832                 print $fh '   </div>'."\n";
1833                 print $fh '   <div id="framespace">'."\n";
1834                 
1835                 print $fh '    <img src="'.$_frame_url.'" id="frame" alt="0">'."\n"; # title="'.$_title.'"
1836                 
1837                 print $fh '   </div>'."\n";
1838                 print $fh '   <div id="insb" class="ins">'."\n";
1839                 
1840                 print $fh '    <div id="undertext">'."\n";
1841                 
1842                 if ($show_parent_dir) {
1843                         print $fh '     <img src="/icons/back.gif" alt="[DIR]"> <a href="..">Parent Directory</a><br>'."\n";
1844                 }
1845                 if ($show_folders) {
1846                         print $fh '     <img src="/icons/folder.gif" alt="[DIR]"> <a href="'.$_2words_url.'">2words/</a> '.$_2words_date.' - <br>'."\n";
1847                         print $fh '     <img src="/icons/folder.gif" alt="[DIR]"> <a href="'.$_coin_url.'">coin/</a> '.$_coin_date.' - '.$_coin_server."\n";
1848                 }
1849                 elsif ($show_yb) {
1850                         print $fh '     <img src="/icons/folder.gif" alt="[DIR]"> <a href="'.$_2words_url.'">yyyyb/</a>'."\n";
1851                 }
1852                 if ($undertext ne '') {
1853                         print $fh '     '.$_undertext."\n";
1854                 }
1855                 
1856                 print $fh '    </div>'."\n";
1857                 
1858                 if ($timer ne '') {
1859                         print $fh '    <div id="command">'."\n";
1860                         
1861                         print $fh '     [<span id="ongh" class="'.$timer_color.'">'.$timer.'</span>';
1862                         print $fh      ':<span id="ongm" class="'.$timer_color.'">'.$timer.'</span>';
1863                         print $fh      ':<span id="ongs" class="'.$timer_color.'">'.$timer.'</span>]<br>'."\n";
1864                         
1865                         if ($undertext ne '') {
1866                                 print $fh '&gt;<a href="'.$_2words_url.'">'.$_undertext.'</a><span class="inp">_</span>'."\n";
1867                         }
1868                         print $fh "    </div>\n";
1869                 }
1870                 
1871                 print $fh "   </div>\n";
1872                 
1873                 print_html_body_end($fh, $ong_state == STATE->{'inactive'});
1874                 print_html_end($fh);
1875         }
1876         close ($fh);
1877         return $r
1878 }
1879
1880 sub write_static_viewer_page {
1881         (
1882                 my $frame,
1883                 my $state_ref,
1884                 my $settings_ref,
1885                 my $default_ref,
1886                 my $frame_data_ref,
1887                 my $prev_frame_data_ref,
1888                 my $next_frame_data_ref,
1889                 my $words_data_ref
1890         ) = @_;
1891         
1892         my %state;
1893         my %settings;
1894         my %default;
1895         my %frame_data;
1896         my %prev_frame_data;
1897         my %next_frame_data;
1898         my %words_data;
1899         
1900         my $file;
1901         
1902         $frame = int($frame);
1903         my $prev_frame = $frame -1;
1904         my $next_frame = $frame +1;
1905         
1906         %state = (ref ($state_ref)) ?
1907                 %$state_ref :
1908                 read_data_file(DATA_STATE_PATH());
1909         my $ong_state = int($state{'state'});
1910         my $last_frame = int($state{'last'});
1911         
1912         unless ($ong_state > STATE->{'inactive'}) {
1913                 return 0;
1914         }
1915         unless (
1916                 ($frame >= 0) && (
1917                         ($frame < $last_frame) || (
1918                                 ($frame <= $last_frame) &&
1919                                 ($ong_state >= STATE->{'end'})
1920                         )
1921                 )
1922         ) {
1923                 return 0;
1924         }
1925         
1926         %settings = (ref ($settings_ref)) ?
1927                 %$settings_ref :
1928                 read_data_file(DATA_SETTINGS_PATH());
1929         %default = (ref ($default_ref)) ?
1930                 %$default_ref :
1931                 read_data_file(DATA_DEFAULT_PATH());
1932         
1933         %frame_data = (ref ($frame_data_ref)) ?
1934                 %$frame_data_ref :
1935                 read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), $frame));
1936         
1937         %prev_frame_data = (ref ($prev_frame_data_ref)) ?
1938                 %$prev_frame_data_ref : (
1939                         ($prev_frame >= 0) ?
1940                         read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), $prev_frame)) :
1941                         %default
1942                 );
1943                 
1944         %next_frame_data = (ref ($next_frame_data_ref)) ?
1945                 %$next_frame_data_ref : (
1946                         (($next_frame < $last_frame) || (
1947                                 ($next_frame <= $last_frame) &&
1948                                 ($next_frame >= STATE->{'end'})
1949                         )) ?
1950                         read_data_file(join_path(PATH_SEPARATOR(), DATA_PATH(), $next_frame)) :
1951                         %default
1952                 );
1953         
1954         %frame_data      = merge_settings(\%default, \%frame_data);
1955         %prev_frame_data = merge_settings(\%default, \%prev_frame_data);
1956         %next_frame_data = merge_settings(\%default, \%next_frame_data);
1957         
1958         %words_data = (ref ($words_data_ref)) ?
1959                 %$words_data_ref :
1960                 read_data_file(
1961                         join_path(PATH_SEPARATOR(), DATA_WORDS_PATH(), $frame), # file
1962                         '', # encoding
1963                         0,  # no header
1964                         1,  # header only
1965                         1,  # as list; not relevant
1966                 );
1967         
1968         if ($frame_data{'page'} ne '') {
1969                 $file = $frame_data{'page'}
1970         }
1971         else {
1972                 $file = sprintf(
1973                         $settings{'frame'},
1974                         $frame, 'htm'
1975                 );
1976         }
1977         $file = join_path(PATH_SEPARATOR(), WWW_PATH(), $file);
1978         
1979         return print_viewer_page(
1980                 $file,
1981                 {
1982                         'launch'        => 0,
1983                         'frame'         => $frame,
1984                         'access'        => 1,
1985                         'password_ok'   => 0,
1986                         'timer_unlocked'=> 3, # not relevant
1987                         'timer'         => 0, # not relevant
1988                         'static'        => 1,
1989                         'show_command'  => 1,
1990                         'text_mode'     => TEXT_MODE->{'normal'},
1991                         'words_page'    => 0, # not relevant
1992                 },
1993                 \%state,
1994                 \%settings,
1995                 \%frame_data,
1996                 \%prev_frame_data,
1997                 \%next_frame_data,
1998                 \%words_data
1999         );
2000 }
2001
2002 sub write_static_goto {
2003         
2004 }
2005
2006 # ONG the frame + attachment & stuff. NOT update state file.
2007 sub ong {
2008         (
2009                 my $ID, my $ongtime, my $timer, my $update, my $print,
2010                 my $settings_ref, my $default_ref, my $data_ref, my $goto_ref
2011         ) = @_;
2012         my @files;
2013         my $cfrt;
2014         my $intf;
2015         my $frame;
2016         my $frame_data_path;
2017         my $write_data;
2018         my $in_path;
2019         my $out_path;
2020         my $r;
2021         my %settings;
2022         my %default;
2023         my %frame_data;
2024         my %frame_full_data;
2025         my %goto_list;
2026         
2027         if ($ongtime eq '') {
2028                 $ongtime = time();
2029         }
2030         
2031         if ($ID eq 'i') {
2032                 $intf = 1;
2033         }
2034         elsif ($ID eq 'c') {
2035                 $cfrt = 1;
2036         }
2037         else {
2038                 $frame = int($ID);
2039         }
2040         
2041         if ($intf) {
2042                 @files = (
2043                         'intf-00.gif',
2044                         'intf-00_04.gif',
2045                         'intf-00_08.gif',
2046                         'intf-00_10.gif',
2047                         'intf-01.gif',
2048                         'intf-01_.gif',
2049                         'intf-02.gif',
2050                         'intf-02_.gif',
2051                         'intf-04.gif',
2052                         'intf-04_.gif',
2053                         'intf-08.gif',
2054                         'intf-08_.gif',
2055                         'intf-10.gif',
2056                         'intf-10_.gif',
2057                         'intf-20.gif',
2058                         'intf-20_.gif',
2059                         'intf-kw.gif',
2060                         'intf-ll.gif',
2061                         'intf-pp.gif',
2062                         'intf-tr.gif',
2063                 );
2064         }
2065         else {
2066                 %settings = (ref ($settings_ref)) ?
2067                         %$settings_ref :
2068                         read_data_file(DATA_SETTINGS_PATH());
2069                 %default = (ref ($default_ref)) ?
2070                         %$default_ref :
2071                         read_data_file(DATA_DEFAULT_PATH());
2072                 $frame_data_path = $cfrt ?
2073                         DATA_NOACCESS_PATH() :
2074                         join_path(PATH_SEPARATOR(), DATA_PATH(), $frame);
2075                 %frame_data = (ref ($data_ref)) ?
2076                         %$data_ref :
2077                         read_data_file($frame_data_path);
2078                 %frame_full_data = merge_settings(\%default, \%frame_data);
2079                 @files = (
2080                         ($frame_full_data{'frame'} ne '') ?
2081                                 $frame_full_data{'frame'} :
2082                                 sprintf(
2083                                         $settings{'frame'},
2084                                         $frame, $frame_full_data{'ext'}
2085                                 )
2086                         ,
2087                 );
2088                 unless ($cfrt) {
2089                         %goto_list = (ref ($goto_ref)) ?
2090                                 %$goto_ref :
2091                                 read_data_file(DATA_LIST_PATH());
2092                         for (my $i=0; ;$i+=1) {
2093                                 my %file_data = read_data_file(DATA_ATTACH_PATH().$i);
2094                                 if ($file_data{'frame'} eq '') {
2095                                         last;
2096                                 }
2097                                 if (int($file_data{'frame'}) != $frame) {
2098                                         next;
2099                                 }
2100                                 if ($file_data{'content'} ne '') {
2101                                         next;
2102                                 }
2103                                 unshift @files, $file_data{'filename'};
2104                         }
2105                         if (
2106                                 (!$update) ||
2107                                 ($frame_full_data{'ongtime'} eq '')
2108                         ) {
2109                                 $frame_data     {'ongtime'} = $ongtime;
2110                                 $frame_full_data{'ongtime'} = $ongtime;
2111                                 $write_data = 1;
2112                         }
2113                         if (
2114                                 ($timer ne '') && (
2115                                         (!$update) ||
2116                                         ($frame_full_data{'timer'} eq '')
2117                                 )
2118                         ) {
2119                                 $frame_data{'timer'} = int($timer);
2120                                 $write_data = 1;
2121                         }
2122                         if ($write_data) {
2123                                 $r = write_data_file($frame_data_path, \%frame_data);
2124                                 unless ($r) {
2125                                         print STDERR "fail writing $frame_data_path\n";
2126                                         if ($print) {
2127                                                 print "write frame data fail\n";
2128                                         }
2129                                         return $r;
2130                                 }
2131                         }
2132                         $goto_list{'title-'  .$frame} = $frame_full_data{'title'};
2133                         $goto_list{'ongtime-'.$frame} = $frame_full_data{'ongtime'};
2134                         $r = write_data_file(DATA_LIST_PATH(), \%goto_list);
2135                         unless ($r) {
2136                                 print STDERR "fail writing ".DATA_LIST_PATH()."\n";
2137                                 if ($print) {
2138                                         print "write GOTO list fail\n";
2139                                 }
2140                                 return $r;
2141                         }
2142                 }
2143         }
2144         foreach my $file (@files) {
2145                 $in_path  = join_path(PATH_SEPARATOR(), DATA_PATH(), $file);
2146                 $out_path = join_path(PATH_SEPARATOR(), WWW_PATH() , $file);
2147                 if ($print) {
2148                         print $in_path.' -> '.$out_path;
2149                 }
2150                 $r = copy_encoded($in_path, $out_path);
2151                 if ($print) {
2152                         print (($r) ? " OK\n" : " FAIL\n");
2153                 }
2154                 unless ($r) {
2155                         print STDERR "fail copy $in_path $out_path\n";
2156                         return $r
2157                 }
2158         }
2159         
2160         return 1;
2161 }
2162
2163 1