]> bicyclesonthemoon.info Git - ott/bsta/blob - 2words.1.pl
hyperlinks to static previous/next page
[ott/bsta] / 2words.1.pl
1 ###RUN_PERL: #!/usr/bin/perl
2
3 # /bsta/2words
4 # 2words is generated from 2words.1.pl.
5 #
6 # The wordgame interface
7 #
8 # Copyright (C) 2016, 2017, 2023, 2024  Balthasar SzczepaƄski
9 #
10 # This program is free software: you can redistribute it and/or modify
11 # it under the terms of the GNU Affero General Public License as
12 # published by the Free Software Foundation, either version 3 of the
13 # License, or (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU Affero General Public License for more details.
19 #
20 # You should have received a copy of the GNU Affero General Public License
21 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
23 use strict;
24 use utf8;
25 # use Encode::Locale ('decode_argv');
26 use Encode ('encode', 'decode');
27
28 ###PERL_LIB: use lib /botm/lib/bsta
29 use botm_common (
30         'HTTP_STATUS',
31         'http_header_status', 'http_header_allow',
32         'read_data_file', 'write_data_file',
33         'merge_url',
34         'read_header_env',
35         'html_entity_encode_dec',
36         'url_query_decode',
37         'open_encoded'
38 );
39 use bsta_lib (
40         'STATE', 'INTF_STATE',
41         'get_id',
42         'fail_method', 'fail_content_type',
43         'print_html_start', 'print_html_end',
44         'print_html_head_start', 'print_html_head_end',
45         'print_html_body_start', 'print_html_body_end',
46         'write_index',
47         'get_remote_addr',
48         'merge_settings',
49         'ong'
50 );
51
52 ###PERL_CGI_PATH:           CGI_PATH           = /bsta/
53 ###PERL_CGI_2WORDS_PATH:    CGI_2WORDS_PATH    = /bsta/2words
54
55 ###PERL_DATA_SETTINGS_PATH: DATA_SETTINGS_PATH = /botm/data/bsta/settings
56 ###PERL_DATA_STATE_PATH:    DATA_STATE_PATH    = /botm/data/bsta/state
57 ###PERL_DATA_STORY_PATH:    DATA_STORY_PATH    = /botm/data/bsta/story
58
59 ###PERL_WEBSITE_NAME:       WEBSITE_NAME       = Bicycles on the Moon
60
61 ###PERL_STORY_LENGTH:       STORY_LENGTH       = 16
62 ###PERL_PAGE_LENGTH:        PAGE_LENGTH        = 16
63 ###PERL_FIRSTPAGE_LENGTH:   FIRSTPAGE_LENGTH   = 4
64
65 binmode STDIN,  ':encoding(UTF-8)';
66 binmode STDOUT, ':encoding(UTF-8)';
67 binmode STDERR, ':encoding(UTF-8)';
68 # decode_argv();
69
70 my %http;
71 my %cgi;
72 my %story;
73 my %new_story;
74 my %settings;
75 my %state;
76
77 my $time = time();
78 srand ($time-$$);
79
80 my $method;
81 my $IP;
82 my $words;
83 my $color2;
84 my $last_IP;
85 my $story_id;
86 my $turn;
87 my $status;
88 my $allow;
89 my $message;
90 my $first_letter;
91 my $second_letter;
92 my $last_letter;
93 my $intf_state;
94 my $intf_pass;
95 my $intf_pause;
96 my $intf_mode;
97 my $story_i_path;
98 my $fh;
99 my $story_lock;
100 my @story_lines;
101 my $ong_state;
102 my $page;
103 my $cmd_clear;
104 my $cmd_clear_all;
105
106
107 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
108 ###PERL_SET_PATH: $ENV{'PATH'} = /usr/local/bin:/usr/bin:/bin;
109
110 if ($ENV{'REQUEST_METHOD'} =~ /^(HEAD|GET|POST)$/) {
111         $method = $1;
112 }
113 else {
114         exit fail_method($ENV{'REQUEST_METHOD'}, ['GET','POST', 'HEAD']);
115 }
116
117 %http = read_header_env(\%ENV);
118 %cgi = url_query_decode($ENV{'QUERY_STRING'});
119
120 if ($method eq 'POST') {
121         if ($http{'content-type'} eq 'application/x-www-form-urlencoded') {
122                 my %cgi_post = url_query_decode( <STDIN> );
123                 %cgi = merge_settings(\%cgi, \%cgi_post);
124         }
125         # multipart not supported
126         else{
127                 exit fail_content_type($method, $http{'content-type'});
128         }
129 }
130
131 $IP = get_remote_addr();
132 $page = get_id(\%cgi);
133 if ($cgi{'words'} ne '') {
134         $words = $cgi{'words'};
135 }
136
137 %settings = read_data_file(DATA_SETTINGS_PATH());
138 %state    = read_data_file(DATA_STATE_PATH());
139 $ong_state = int($state{'state'});
140 $cmd_clear     = $settings{'password'}.' clear';
141 $cmd_clear_all = $settings{'password'}.' clearall';
142
143 $story_lock=0;
144 if (open_encoded($fh, "+<:encoding(UTF-8)", DATA_STORY_PATH())) {
145         $story_lock=1;
146         if (flock($fh,2)) {
147                 $story_lock=2;
148         }
149         %story = read_data_file($fh);
150         
151         if ($story{'lastip'} =~ /^.+$/) {
152                 $last_IP=$&;
153         }
154         else {
155                 $last_IP='0.0.0.0';
156         }
157         
158         $last_letter = lc($story{'letter'});
159         $story_id   = int($story{'id'});
160         $intf_pass  = int($story{'pass'});
161         $intf_state = int($story{'state'});
162         $intf_mode  = $intf_state & INTF_STATE->{'mode'};
163         $intf_pause = $intf_state & INTF_STATE->{'||'};
164         
165         if ($IP ne $last_IP) {
166                 $turn = 1;
167         }
168         else {
169                 $turn = 0;
170         }
171         
172         if (
173                 ($intf_state < 0) || (
174                         ($method eq 'POST') && (
175                                 ($words eq $cmd_clear) ||
176                                 ($words eq $cmd_clear_all)
177                         )
178                 )
179         ) {
180                 if (
181                         ($words eq $cmd_clear_all) ||
182                         ($intf_state < -1)
183                 ) {
184                         $story{'id'} = 0;
185                 }
186                 $story{'content'} = '';
187                 $story{'lastip' } = '0.0.0.0';
188                 $story{'letter' } = '';
189                 $story{'pass'   } = 0;
190                 $story{'state'  } = INTF_STATE->{'X'};
191                 $turn = 0;
192                 if ($ong_state == STATE->{'inactive'}) {
193                         write_index(
194                                 \%state,
195                                 \%settings,
196                                 $story{'pass'},
197                                 $story{'state'},
198                                 0 # pause
199                         );
200                 }
201                 write_data_file($fh, \%story);
202         }
203         
204         if (($words ne '') && ($method eq 'POST')) {
205                 if (!$turn) {
206                         $status = HTTP_STATUS->{'forbidden'};
207                         $message = "It's not your turn.";
208                 }
209                 # TODO: consider allowing non-ASCII letters in words.
210                 # (not very important in English language)
211                 elsif ($words =~ /^([!"\(\),\.:;\?][ \t]*)?([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\? \t][ \t]*)([A-Za-z][A-Za-z'\-]*[A-Za-z']?)([!"\(\),\.:;\?]?[ \t]*)$/) {
212                         # we have 2 words
213                         $first_letter  = lc(substr($2, 0, 1));
214                         $second_letter = lc(substr($4, 0, 1));
215                         if (
216                                 ($first_letter ne $last_letter) &&
217                                 ($last_letter ne '')
218                         ) {
219                                 $status = HTTP_STATUS->{'bad_request'};
220                                 $message = 'The first word must start with '.uc($last_letter).'.';
221                         }
222                         elsif ($first_letter eq $second_letter) {
223                                 $status = HTTP_STATUS->{'bad_request'};
224                                 $message = 'The second word can\'t also start with '.uc($first_letter).'.';
225                         }
226                         else {
227                                 # words are valid
228                                 # update state
229                                 $story{'content'} = $story{'content'} . $words."\n";
230                                 $turn = 0;
231                                 $story{'lastip'} = $IP;
232                                 $story{'letter'} = $second_letter;
233                                 
234                                 if ($cgi{'next'} ne '') {
235                                         # start next game
236                                         if (split(/\r?\n/,$story{'content'}) >= (STORY_LENGTH-1)) {
237                                                 # store finished game
238                                                 $story_i_path = DATA_STORY_PATH.$story_id;
239                                                 write_data_file($story_i_path, \%story);
240                                                 # init new game
241                                                 $new_story{'id'     } = $story_id + 1;
242                                                 $new_story{'letter' } = '';
243                                                 $new_story{'lastip' } = $IP;
244                                                 $new_story{'content'} = '';
245                                                 $new_story{'pass'   } = 0;
246                                                 $new_story{'state'  } = INTF_STATE->{'X'};
247                                                 # reset hidden interface
248                                                 $intf_state = INTF_STATE->{'X'};
249                                                 $intf_pass = 0;
250                                                 $intf_mode = INTF_STATE->{'X'};
251                                                 $intf_pause= 0;
252                                                 if($ong_state == STATE->{'inactive'}) {
253                                                         # ONG not activated yet; reset index
254                                                         write_index(
255                                                                 \%state,
256                                                                 \%settings,
257                                                                 $intf_pass,
258                                                                 $intf_mode,
259                                                                 $intf_pause
260                                                         );
261                                                 }
262                                                 # save new game
263                                                 write_data_file($fh, \%new_story);
264                                         }
265                                         else {
266                                                 $message = 'To early to finish this wordgame.';
267                                                 write_data_file($fh, \%story);
268                                         }
269                                 }
270                                 else {
271                                         # continue the game
272                                         if ($intf_pass == 1) {
273                                                 # hidden interface was already active; deactivate
274                                                 $intf_pass = 2;
275                                                 $story{'pass'} = 2;
276                                                 if($ong_state == STATE->{'inactive'}) {
277                                                         write_index(
278                                                                 \%state,
279                                                                 \%settings,
280                                                                 $intf_pass,
281                                                                 $intf_mode,
282                                                                 $intf_pause
283                                                         );
284                                                 }
285                                         }
286                                         elsif(lc($2).' '.lc($4) eq $settings{'unlock'}) {
287                                                 # correct password for the hidden interface!
288                                                 if ($intf_pass != 0) {
289                                                         $message = 'The password has already been used in this story.';
290                                                 }
291                                                 elsif ($ong_state != STATE->{'inactive'}) {
292                                                         # ONG already active, nothing to do here
293                                                         $message = "???";
294                                                 }
295                                                 else {
296                                                         # ready to activate?
297                                                         my $r;
298                                                         
299                                                         # ONG tape interface
300                                                         $r = ong(
301                                                                 'i',   # ID: tape interface
302                                                                 $time, # ONG time;    not relevant
303                                                                 0,     # timer;       not relevant
304                                                                 0,     # update;      not relevant
305                                                                 0,     # print
306                                                                 \%settings,         # not relevant
307                                                                 '',    # %default;    not relevant
308                                                                 '',    # %frame_data; not relevant
309                                                                 ''     # $goto_list;  not relevant
310                                                         );
311                                                         if ($r) {
312                                                                 # ONG CFRT
313                                                                 $r = ong(
314                                                                         'c',   # ID: CFRT
315                                                                         $time, # ONG time;   not relevant
316                                                                         0,     # timer;      not relevant
317                                                                         0,     # update;     not relevant
318                                                                         0,     # print
319                                                                         \%settings,
320                                                                         '',    # %default
321                                                                         '',    # %frame_data
322                                                                         ''     # $goto_list; not relevant
323                                                                 );
324                                                         }
325                                                         if ($r) {
326                                                                 # ONG frame 0!
327                                                                 $r = ong(
328                                                                         0,     # frame ID
329                                                                         $time, # ONG time; might get overwritten later
330                                                                         0,     # timer
331                                                                         0,     # update
332                                                                         0,     # print
333                                                                         \%settings,
334                                                                         '',    # %default
335                                                                         '',    # %frame_data
336                                                                         ''     # $goto_list
337                                                                 );
338                                                         }
339                                                         if($r) {
340                                                                 # new state of hidden interface
341                                                                 $intf_pass = 1;
342                                                                 $intf_state = INTF_STATE->{'X'};
343                                                                 $intf_mode  = INTF_STATE->{'X'};
344                                                                 $intf_pause = 0;
345                                                                 $story{'pass'} = 1;
346                                                                 $story{'state'} = INTF_STATE->{'X'};
347                                                                 write_index(
348                                                                         \%state,
349                                                                         \%settings,
350                                                                         $intf_pass,
351                                                                         $intf_mode,
352                                                                         $intf_pause
353                                                                 );
354                                                         }
355                                                 }
356                                         }
357                                         write_data_file($fh, \%story);
358                                 }
359                         }
360                 }
361                 else {
362                         $status = HTTP_STATUS->{'bad_request'};
363                         $message = 'Please, two words, not more, not less (some punctuation is allowed).';
364                 }
365         }
366         elsif (
367                 ($cgi{'s'} ne '') &&
368                 ($intf_pass == 1) &&
369                 ($ong_state == STATE->{'inactive'})
370         ) {
371                 $intf_state = int($cgi{'s'}) & INTF_STATE->{'mask'};
372                 $intf_mode  = $intf_state & INTF_STATE->{'mode'};
373                 $intf_pause = $intf_state & INTF_STATE->{'||'};
374                 $story{'state'} = $intf_state;
375                 write_index(
376                         \%state,
377                         \%settings,
378                         $intf_pass,
379                         $intf_mode,
380                         $intf_pause
381                 );
382                 write_data_file($fh, \%story);
383         }
384         @story_lines = split(/\r?\n/, $story{'content'});
385         if(@story_lines & 1) {
386                 $turn = !$turn;
387         }
388         
389         close($fh);
390 }
391
392 if ($status ne '') {
393         print http_header_status($status);
394 }
395 if ($allow ne '') {
396         print http_header_allow($allow);
397 }
398 print "Content-type: text/html; charset=UTF-8\n\n";
399
400 if($method eq 'HEAD') {
401         exit;
402 }
403
404 my $max_page = int(($story_id - FIRSTPAGE_LENGTH - 1) / PAGE_LENGTH) + 1;
405 my $newer_available = ($page > 0);
406 my $older_available = ($page < $max_page);
407 my $show_intf = ($intf_pass == 1) && ($ong_state == STATE->{'inactive'});
408 my $id_start = 
409         $story_id-1 -(
410                 ($page == 0)    ? 0 : (
411                         (($page-1) * PAGE_LENGTH ) + FIRSTPAGE_LENGTH
412                 )
413         );
414 my $id_stop = $story_id-1 - (($page*PAGE_LENGTH) + FIRSTPAGE_LENGTH);
415 if ($id_stop < 0) {
416         $id_stop = -1;
417 }
418
419 my $bsta_url = CGI_PATH;
420 my $twowords_url = CGI_2WORDS_PATH;
421 my $newer_url;
422 my $older_url;
423 my $oldest_url;
424 my $newest_url = merge_url(
425         {'path' => $twowords_url},
426         {'path' => 0}
427 );
428 if ($newer_available) {
429         $newer_url = merge_url(
430                 {'path' => $twowords_url},
431                 {'path' => $page-1}
432         );
433 }
434 if ($older_available) {
435         $older_url = merge_url(
436                 {'path' => $twowords_url},
437                 {'path' => $page+1}
438         );
439         $oldest_url = merge_url(
440                 {'path' => $twowords_url},
441                 {'path' => $max_page}
442         );
443 }
444 my $button_4_url = merge_url(
445         {'path' => $twowords_url},
446         {'query' => {'s' => (INTF_STATE->{'>'} | $intf_pause)}}
447 );
448 my $button_3_url = merge_url(
449         {'path' => $twowords_url},
450         {'query' => {'s' => (INTF_STATE->{'<<'} | $intf_pause)}}
451 );
452 my $button_2_url = merge_url(
453         {'path' => $twowords_url},
454         {'query' => {'s' => (INTF_STATE->{'>>'} | $intf_pause)}}
455 );
456 my $button_1_url = merge_url(
457         {'path' => $twowords_url},
458         {'query' => {'s' => INTF_STATE->{'X'}}}
459 );
460 my $button_0_url = merge_url(
461         {'path' => $twowords_url},
462         {'query' => {'s' => ($intf_pause ? $intf_mode : ($intf_mode | INTF_STATE->{'||'}))}}
463 );
464 my $button_5_img = merge_url(
465         {'path' => CGI_PATH()},
466         {'path' => 'intf-20.gif'}
467 );
468 my $button_4_img = merge_url(
469         {'path' => CGI_PATH()},
470         {'path' => 'intf-10'.(($intf_mode == INTF_STATE->{'>'}) ? '_' : '').'.gif'}
471 );
472 my $button_3_img = merge_url(
473         {'path' => CGI_PATH()},
474         {'path' => 'intf-08'.(($intf_mode == INTF_STATE->{'<<'}) ? '_' : '').'.gif'}
475 );
476 my $button_2_img = merge_url(
477         {'path' => CGI_PATH()},
478         {'path' => 'intf-04'.(($intf_mode == INTF_STATE->{'>>'}) ? '_' : '').'.gif'}
479 );
480 my $button_1_img = merge_url(
481         {'path' => CGI_PATH()},
482         {'path' => 'intf-02.gif'}
483 );
484 my $button_0_img = merge_url(
485         {'path' => CGI_PATH()},
486         {'path' => 'intf-01'.($intf_pause ? '_' : '').'.gif'}
487 );
488 my $intf_img_id = '';
489 if ($intf_state == INTF_STATE->{'>'}) {
490         $intf_img_id = '_10'
491 }
492 elsif ($intf_mode == INTF_STATE->{'<<'}) {
493         $intf_img_id = '_08'
494 }
495 elsif ($intf_mode == INTF_STATE->{'>>'}) {
496         $intf_img_id = '_04'
497 }
498 my $intf_img = merge_url(
499         {'path' => CGI_PATH()},
500         {'path' => 'intf-00'.$intf_img_id.'.gif'}
501 );
502
503 my $_bsta_url     = html_entity_encode_dec($bsta_url     , 1);
504 my $_twowords_url = html_entity_encode_dec($twowords_url , 1);
505 my $_newest_url   = html_entity_encode_dec($newest_url   , 1);
506 my $_newer_url    = html_entity_encode_dec($newer_url    , 1);
507 my $_older_url    = html_entity_encode_dec($older_url    , 1);
508 my $_oldest_url   = html_entity_encode_dec($oldest_url   , 1);
509 my $_button_4_url = html_entity_encode_dec($button_4_url , 1);
510 my $_button_3_url = html_entity_encode_dec($button_3_url , 1);
511 my $_button_2_url = html_entity_encode_dec($button_2_url , 1);
512 my $_button_1_url = html_entity_encode_dec($button_1_url , 1);
513 my $_button_0_url = html_entity_encode_dec($button_0_url , 1);
514 my $_button_5_img = html_entity_encode_dec($button_5_img , 1);
515 my $_button_4_img = html_entity_encode_dec($button_4_img , 1);
516 my $_button_3_img = html_entity_encode_dec($button_3_img , 1);
517 my $_button_2_img = html_entity_encode_dec($button_2_img , 1);
518 my $_button_1_img = html_entity_encode_dec($button_1_img , 1);
519 my $_button_0_img = html_entity_encode_dec($button_0_img , 1);
520 my $_intf_img     = html_entity_encode_dec($intf_img     , 1);
521 my $_message      = html_entity_encode_dec($message      , 1);
522 my $_website_name = html_entity_encode_dec(WEBSITE_NAME(), 1);
523
524 print_html_start(\*STDOUT);
525 print_html_head_start(\*STDOUT);
526
527
528 print '  <title>Two words &bull; '.$_website_name.'</title>'."\n";
529 print '  <link rel="start" href="'.$_oldest_url.'">'."\n";
530 if ($older_available) {
531         print '  <link rel="prev" href="'.$_older_url.'">'."\n";
532 }
533 if ($newer_available) {
534         print '  <link rel="next" href="'.$_newer_url.'">'."\n";
535 }
536
537 print_html_head_end(\*STDOUT);
538 print_html_body_start(\*STDOUT);
539
540 print '   <div id="inst" class="ins">'."\n";
541
542 print '    <div id="title">'."\n";
543 print '     <h1 id="titletext">Two words</h1>'."\n";
544 print '    </div>'."\n";
545
546 if ($page == 0) {
547         print '    <div id="storypuzzle">'."\n";
548         for (my $i = 0; $i < @story_lines; ++$i) {
549                 print '     <span class="'.($turn ? 'ni':'br').'">'.html_entity_encode_dec($story_lines[$i], 1).'</span>'."\n";
550                 $turn = !$turn;
551         }
552         print '    </div>'."\n";
553
554         print '    <div id="command">'."\n";
555         if ($message ne '') {
556                 print '     <span class="br">'.$_message.'</span>'."\n";
557         }
558         
559         if ($turn) {
560                 print '     <form method="post" action="'.$_twowords_url.'">'."\n";
561                 if ($message eq '') {
562                         if ($story{"content"} eq '') {
563                                 print '      Two words, please:<br>'."\n";
564                         }
565                         else {
566                                 print '      Please continue, two words:<br>'."\n";
567                         }
568                 }
569                 print '      <input class="intx" type="text" name="words">'."\n";
570                 print '      <input class="inbt" type="submit" value="enter">'."\n";
571                 if (@story_lines >= (STORY_LENGTH-1)) {
572                         print '      <input class="inbt" type="submit" name="next" value="enter and then start a new one">'."\n";
573                 }
574                 print '     </form>'."\n";
575         }
576         else {
577                 if ($message eq '') {
578                         print '     Wait for it.'."\n";
579                 }
580         }
581         print '    </div>'."\n";
582 }
583 elsif ($message ne '') {
584         print '    <div id="command">'."\n";
585         print '     <span class="br">'.$_message.'</span>'."\n";
586         print '    </div>'."\n";
587 }
588 print '   </div>'."\n";
589
590 if ($show_intf) {
591         print '   <div id="framespace">'."\n";
592         print '    <table id="intftable" cellspacing="0" cellpadding="0">'."\n";
593         print '     <tr class="intf">'."\n";
594         print '      <td colspan="6" class="intf"><img src="'.$_intf_img.'" alt="" class="intf"></td>'."\n";
595         print '     </tr>'."\n";
596         
597         print '     <tr class="intf">'."\n";
598         print '      <td class="intf"><img src="'.$_button_5_img.'" alt="o" class="intf"></td>'."\n";
599         print '      <td class="intf"><a href="'.$_button_4_url.'"><img src="'.$_button_4_img.'" class="intf" alt="&gt;"></a></td>'."\n";
600         print '      <td class="intf"><a href="'.$_button_3_url.'"><img src="'.$_button_3_img.'" class="intf" alt="&lt;&lt;"></a></td>'."\n";
601         print '      <td class="intf"><a href="'.$_button_2_url.'"><img src="'.$_button_2_img.'" class="intf" alt="&gt;&gt;"></a></td>'."\n";
602         print '      <td class="intf"><a href="'.$_button_1_url.'"><img src="'.$_button_1_img.'" class="intf" alt="^"></a></td>'."\n";
603         print '      <td class="intf"><a href="'.$_button_0_url.'"><img src="'.$_button_0_img.'" class="intf" alt="||"></a></td>'."\n";
604         print '     </tr>'."\n";
605         print '    </table>'."\n";
606         print '   </div>'."\n";
607 }
608
609 print '   <div id="insb" class="ins">'."\n";
610
611 print '    <div id="undertext">'."\n";
612 for (my $i = $id_start; $i > $id_stop; --$i) {
613         $story_i_path = DATA_STORY_PATH.$i;
614         %new_story = read_data_file($story_i_path);
615         print '     <p class="'.(($i&1)?'br':'ni').'" id="s'.$i.'">'.html_entity_encode_dec($new_story{'content'}).'</p>'."\n";
616 }
617 print '    </div>'."\n";
618
619 print '    <div id="underlinks">'."\n";
620 print '     <a href="'.$_bsta_url.'">BSTA</a> |'."\n";
621 print '     <a href="'.$_twowords_url.'">Once again</a>';
622 if ($older_available) {
623         print ' |'."\n";
624         print '     <a href="'.$_older_url.'">Before</a>';
625 }
626 if ($newer_available) {
627         print ' |'."\n";
628         print '     <a href="'.$newer_url.'">Unbefore</a>';
629 }
630 if ($older_available) {
631         print ' |'."\n";
632         print '<a href="'.$_oldest_url.'">Initially</a>';
633 }
634 if($turn) {
635         print ' |'."\n";
636         print '     (Entering words here is irreversible. Your actions might be remembered forever. So please be reasonable.)';
637 }
638 print "\n";
639 print '    </div>'."\n";
640
641 print '   </div>'."\n";
642
643 print_html_body_end(\*STDOUT, $ong_state == STATE->{'inactive'});
644 print_html_end(\*STDOUT);