Thread CGI-Gaestebuch: HTML::Template + Textdateien (7 answers)
Opened by SirLant at 2003-08-27 13:22

Strat
 2003-08-27 12:54
#28089 #28089
User since
2003-08-04
5246 Artikel
ModeratorIn
[Homepage] [default_avatar]
Hier das Guestbook, das bald mal auf meine Seite kommt.

Benoetigte Module:
x) HTML::Template
x) HTML::Entities
x) Mail::RFC822::Address
x) URI::Find::Schemeless

Template templates/guestbook_add.templ
[html]
<html>
 <head>
   <title>G&auml;stebucheintrag hinzuf&uuml;gen</title>
   <link href="<TMPL_VAR NAME=CSS_URL>" rel="stylesheet" type="text/css">
 </head>
 <body>

   <h1 align="center">G&auml;stebucheintrag hinzuf&uuml;gen</h1>
   <br>
   <div align="center">
   <TMPL_IF NAME=MESSAGE><p><b>Folgende Fehler sind aufgetreten:</b><TMPL_VAR NAME=MESSAGE></p></TMPL_IF>
   <form action="<TMPL_VAR NAME=SELF_URL>" METHOD="POST">
     <table border="0" cellspacing="1" cellpadding="4" >
<tr>
 <td class="text">Name:</td>
 <td class="text"><input type="text" name="nick" value="<TMPL_VAR NAME=NICK>" size="70"></td>
</tr>
<tr>
 <td class="text">Email:</td>
 <td class="text"><input type="text" name="email" value="<TMPL_VAR NAME=EMAIL>" size="70"></td>
</tr>
<tr>
 <td class="text">Homepage:</td>
 <td class="text"><input type="text" name="homepage" value="<TMPL_VAR NAME=HOMEPAGE>" size="70"></td>
</tr>
<tr><td colspan="2">&nbsp;</td>
       <tr>
         <td class="text">Betreff:</td>
         <td class="text"><input type="text" name="subject" value="<TMPL_VAR NAME=SUBJECT>" size="70"></td>
       </tr>
       <tr><td valign="top" class="text">Nachricht:</td><td class="text">
         <textarea name="text" cols="53" rows="6"><TMPL_VAR NAME=TEXT></textarea>
       </td></tr>
       <tr>
         <td></td>
         <td><input type="submit" value="Eintrag speichern">&nbsp;

             <input type="button" value="Zur&uuml;ck&nbsp;zum&nbsp;G&auml;stebuch" onClick="javascript:history.back()"></td>
       </tr>
     </table>
     <input type="hidden" name="action" value="guestbooksave">
   </form>
   </div>
 </body>
</html>
[/html]

Template templates/guestbook_show.templ
[html]
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
 <head>
   <title>Martin Fabiani: Software-Engineering und Perl-Programmierung</title>
   <META http-equiv=Content-Language content=de>
   <META http-equiv=Content-Type content="text/html; charset=windows-1252">
   <link href="<TMPL_VAR NAME=CSS_URL>" rel="stylesheet" type="text/css">
 </head>
 <body>

<div align="center">
<table width="97%" border="0" cellpadding="5" cellspacing="5"><tr>
     <td colspan="2" align="center" valign="middle" bgcolor="#FFFFFF">
       <h1><font color="#6666FF">Software-Engineering Martin Fabiani</font></h1></td></tr></table>

<table width="97%" border="0" cellpadding="5" cellspacing="5">
 <tr align="left" valign="top">

     <td width="124" bgcolor="#FFFFFF">
       <h3 align="center">Navigation:</h3>

<p><TMPL_INCLUDE NAME="../menu.html"></p>
</td>

     <td align="center" valign="middle" bgcolor="#FFFFFF">


   <h1 align="center">G&auml;stebuch</h1>

   <!-- gib die Anzahl der gefundenen Eintraege aus //-->
   <p>
   <TMPL_IF NAME="ENTRIES_COUNT"><TMPL_VAR NAME=ENTRIES_COUNT><TMPL_ELSE>Keine</TMPL_IF>
Eintr&auml;ge gefunden</p>

   <!-- schreibe Code fuer Links: neuere und aeltere eintraege //-->
   <center><table><tr>

   <!-- Neuere Eintraege //-->
   <TMPL_IF NAME="SHOW_LINK_NEWER">
     <td><a href="<TMPL_VAR NAME=SELF_URL>?action=guestbook&id=<TMPL_VAR NAME=NEWER_START_ID>">
Neuere Eintr&auml;ge</a></td>    
   </TMPL_IF>

   <!-- Aeltere Eintraege //-->
   <TMPL_IF NAME="SHOW_LINK_OLDER">
     <td><a href="<TMPL_VAR NAME=SELF_URL>?action=guestbook&id=<TMPL_VAR NAME=OLDER_START_ID>">
&Auml;ltere Eintr&auml;ge</a></td>    
   </TMPL_IF>
<td><a href="<TMPL_VAR NAME=SELF_URL>?action=guestbookadd">Neuen Eintrag hinzuf&uuml;gen</a></td>
   </tr></table></center>

   <br>
   <!-- Haupttabelle //-->
   <table border="0" cellpadding="4" >

   <!-- Einen Datensatz ausgeben //-->
   <TMPL_LOOP NAME=GBDATA>
     <tr>
       <td align="center" class="text"><TMPL_VAR NAME=ID></td>
       <td align="center" class="text">Von: <TMPL_VAR NAME=NICK></td>
       <td align="center" class="text">
 <TMPL_IF NAME="EMAIL">Email: <TMPL_VAR NAME=EMAIL><TMPL_ELSE>&nbsp;</TMPL_IF>
</td>
       <td align="center" class="text">
 <TMPL_IF NAME="HOMEPAGE">Homepage: <TMPL_VAR NAME=HOMEPAGE><TMPL_ELSE>&nbsp;</TMPL_IF>
</td>
       <td align="center" class="text">verfasst am: <TMPL_VAR NAME=DATE></td>
     </tr>
     <tr><td align="center" class="text">Betreff:</td>
       <td colspan="5" class="text"><TMPL_VAR NAME=SUBJECT></td></tr>
     <tr><td colspan="5" class="text"><TMPL_VAR NAME=TEXT></td></tr>
     </tr>
     <tr><td colspan="5">&nbsp;</td></tr>
   </TMPL_LOOP>

   <!-- Haupttabelle beenden //-->
   </table>

   <br>

   <!-- schreibe Code fuer Links: neuere und aeltere eintraege //-->
   <center><table border="1" cellpadding="5"><tr>

   <!-- Neuere Eintraege //-->
   <TMPL_IF NAME="SHOW_LINK_NEWER">
     <td><a href="<TMPL_VAR NAME=SELF_URL>?action=guestbook&id=<TMPL_VAR NAME=NEWER_START_ID>">
Neuere Eintr&auml;ge</a></td>    
   </TMPL_IF>

   <!-- Aeltere Eintraege //-->
   <TMPL_IF NAME="SHOW_LINK_OLDER">
     <td><a href="<TMPL_VAR NAME=SELF_URL>?action=guestbook&id=<TMPL_VAR NAME=OLDER_START_ID>">
&Auml;ltere Eintr&auml;ge</a></td>    
   </TMPL_IF>
<td><a href="<TMPL_VAR
   NAME=SELF_URL>?action=guestbookadd">Neuen Eintrag hinzuf&uuml;gen</a></td>
   </tr></table></center>


     </td>
 </tr>
</table>
</div>

 </body>
</html>
[/html]

Beispielinhalt von data/guestbook.txt (hier werden die nachrichten gespeichert)
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
nick: Admin
date: 22.07.2003 23:26
subject: Willkommen im Gästebuch
text: Hallo Leute,<br><br>endlich ist das Gästebuch fertig, und wir hoffen auf nette Einträge.<br><br>Gruesse,<br><br>Die Admins
homepage: http://www.test.de/
email: [EMAIL=admins@no.org]admins@no.org[/EMAIL]

nick: bjkasdfbjkl
subject: dasfblasdjkfbasf
text: asdbnjfasdbjklfb <br>asdfnjklasdfn
date: 24.07.2003 20:18

nick: bjkasdfbjkl
subject: dasfblasdjkfbasf
text: asdbnjfasdbjklfb <br>asdfnjklasdfn
date: 24.07.2003 20:18

nick: bjkasdfbjkl
subject: dasfblasdjkfbasf
text: asdbnjfasdbjklfb <br>asdfnjklasdfn
date: 24.07.2003 20:19

nick: Martin Fabiani
email: <a href="mailto:martin@fabiani.net">martin@fabiani.net</a>
homepage: <a href="http://www.fabiani.net/" target="_blank">www.fabiani.net</a>
subject: blablabla
text: willkommen
date: 24.07.2003 20:20


Und hier das Hauptscript: gb.cgi
Code (perl): (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
#! /usr/bin/perl
use warnings;
use strict;
use CGI ();
use CGI::Carp qw(fatalsToBrowser);
use Fcntl ':flock';

use HTML::Template ();
use FindBin        ();

# die folgenden Module werden direkt in den Modulen geladen
# require URI::Find::Schemeless;
# require HTML::Entities;
# require Mail::RFC822::Address;

# ------------------------------------------------------------
# Fuer die Konfiguration Konstanten verwenden
# ------------------------------------------------------------

# wo liegt das Cascading Style Sheet fuer das Layout
use constant CSS_URL => '/styles/Default.css';

# wie viele Eintraege sollen pro Seite angezeigt werden?
use constant MAX_SHOW_ENTRIES => 10;

# Die Variable &#36FindBin::Bin enthaelt den absoluten Pfad des ausgefuehrten
# Scriptes. Ich verwende sie, um so einem absoluten Pfad zu erhalten, weil
# manche Webserver mit relativen Pfadangaben Schwierigkeiten haben, und da
# vom htdocs-Verzeichnis ausgehen. Und auf diese Weise vermeide ich diese
# potentiellen Probleme am einfachsten.

# wo werden die Gaestebucheintraege abgespeichert?
use constant GB_DATA_FILE => "&#36FindBin::Bin/data/guestbook.txt";

# wo liegen die Templates?
use constant TEMPLATE_DIR => "&#36FindBin::Bin/templates";

# wie sind die Dateinamen der Templates?
use constant TEMPLATE_FILES => {
    showguestbook => TEMPLATE_DIR . "/guestbook_show.templ",
    addguestbook  => TEMPLATE_DIR . "/guestbook_add.templ",
};

use constant GUESTBOOK_FIELDS => [qw(nick email homepage subject text date)];

# ------------------------------------------------------------
# Hauptprogramm
# ------------------------------------------------------------

# neues CGI-Objekt erzeugen
my &#36cgi = CGI::->new();

# aktion abfragen; wenn keine Vorhanden, dann guestbook verwenden
my &#36action = &#36cgi->param('action') || 'guestbook';

if ( &#36action eq 'guestbookadd' ) {
    &AddGuestbookEntry(&#36cgi);
}    # if
elsif ( &#36action eq 'guestbooksave' ) {
    &SaveNewGuestbookEntry(&#36cgi);
}    # elsif
else {    # Standardvorgehen: zeige das Gaestebuch an
    &ShowGuestbook(&#36cgi);
}    # else

# ------------------------------------------------------------
sub SaveNewGuestbookEntry {
    my (&#36cgi) = @_;
    my %data = ();
    foreach ( @{&GUESTBOOK_FIELDS} ) {
        &#36data{&#36_} = &#36cgi->param(&#36_) || '';
        &#36data{&#36_} =~ s/^\s*//;
        &#36data{&#36_} =~ s/\s*&#36//;
    }    # foreach

    unless ( &#36data{nick} ) {
        &#36data{message} .= "<br> Der Name muß angegeben werden";
    }    # unless
    unless ( &#36data{subject} ) {
        &#36data{message} .= "<br> Der Betreff muß angegeben werden";
    }    # unless
    unless ( &#36data{text} ) {
        &#36data{message} .= "<br> Der Text muß angegeben werden";
    }    # unless

    if ( &#36data{email} ) {    # Ueberpruefe die Email-Adresse
        require Mail::RFC822::Address;
        unless ( &Mail::RFC822::Address::valid( &#36data{email} ) ) {
            &#36data{message} .=
              "<br> Diese Email-Adresse ist fehlerhaft (nicht RFC822-Konform)";
        }               
     # else
    }    # if

    if ( &#36data{message} ) {
        &AddGuestbookEntry( &#36cgi, \%data );
    }    # if
    else {
        require URI::Find::Schemeless;
        require HTML::Entities;

        # neues URI::Find::Schemeless-Objekt erstellen und ihm als Callback
        # die Aktion mitgeben, die fuer jede gefundene URI ausgefuehrt werden
        # soll (fuer genauere Infos siehe [URL=http://www.fabiani.net/]http://www.fabiani.net/[/URL] -> Tips&Tricks
        # -> Urls in HTML-Links umwandeln
        my &#36finder = URI::Find::Schemeless->new(
            sub {
               
 my ( &#36uri, &#36originalUri ) = @_;

               
 return ( '<a href="'
               
       . &HTML::Entities::encode_entities("&#36uri")
               
       . '" target="_blank">'
               
       . &HTML::Entities::encode_entities(&#36originalUri)
               
       . '</a>' );

              }    # sub
        );

        # allgemeine Umwandlungen
        foreach ( @{&GUESTBOOK_FIELDS} ) {

            #           &#36data{&#36_} =~ s/</</g;    &#36data{&#36_} =~ s/>/>/g;
            #           &#36data{&#36_} =~ s/\&/&/g;
            #           &#36data{&#36_} =~ s/\"/"/g; &#36data{&#36_} =~ s/\'/'/g;

            # ersetze Sonderzeichen wie < > & ' " durch deren Codes
            &#36data{&#36_} = &CGI::escapeHTML( &#36data{&#36_} );

            # ersetze Zeilenumbruecke durch <br>
            &#36data{&#36_} =~ s/\r?
/<br>/g;

            # ersetze URIs durch HTML-Links
            &#36finder->find( \&#36data{&#36_} );

        }    # foreach

        # erzeuge email-link
        if ( &#36data{email} ) {
            &#36data{email} = qq~<a href="mailto:&#36data{email}">&#36data{email}</a>~;
        }    # if

        # ermittle Datum und Uhrzeit
        my @time = localtime(time);
        &#36time[4]++;
        &#36time[5] += 1900;
        &#36data{date} =
          sprintf( "%02i.%02i.%04i %02i\:%02i", @time[ 3 .. 5, 2, 1 ] );

        &SaveNewEntryToFile( &#36cgi, \%data )
          and &ShowGuestbook(&#36cgi);
    }
}    # SaveNewGuestbookEntry

# ------------------------------------------------------------
sub SaveNewEntryToFile {
    my ( &#36cgi, &#36data ) = @_;
    my &#36string = join ( "",
        map    { "&#36_: &#36data->{&#36_}
" }
          grep { &#36data->{&#36_} } @{&GUESTBOOK_FIELDS} );

    unless ( open( GB, ">>" . GB_DATA_FILE ) ) {
        &PrintErrorPage( &#36cgi, "Konnte Datei nicht oeffnen: &#36!" );
        exit;
    }    # unless
    else {
        flock( GB, LOCK_EX );
        print( GB "&#36string
" );
        close(GB);
    }    # else
    return 1;
}    # SaveNewEntryToFile

# ------------------------------------------------------------
sub AddGuestbookEntry {
    my ( &#36cgi, &#36data ) = @_;

    # gib den HTML-Header aus
    print &#36cgi->header( -type => 'text/html', -expires => '+5s' );

    # lese das Template ein:
    my &#36template = HTML::Template->new(
        filename     &nbs
p;    => TEMPLATE_FILES->{addguestbook},
        die_on_bad_params => 0,
    );
    &#36template->param(

        # Url des Scriptes und CSS-Stylesheet
        SELF_URL => &#36ENV{SCRIPT_NAME} || '',
        CSS_URL  => CSS_URL,

        # eine eventuelle Fehlermeldung
        MESSAGE => &#36data->{message} || '',

        # Die Daten im Falle eines Fehlers
        NICK     => &#36data->{nick}     || '',
        EMAIL    => &#36data->{email}    || '',
        HOMEPAGE => &#36data->{homepage} || '',
        SUBJECT  => &#36data->{subject}  || '',
        TEXT     => &#36data->{text}     || '',

    );

    print &#36template->output;
}    # AddGuestbookEntry

# ------------------------------------------------------------
sub ShowGuestbook {
    my (&#36cgi) = @_;

    # &#36startId ist der offset zur letzten Nachricht
    # also 0 entspricht der letzten Nachricht, 1 der vorletzten usw.
    my &#36startId = &#36cgi->param('id') || 0;
    &#36startId > 0 or &#36startId = 0;

    # gib den HTML-Header aus
    print &#36cgi->header( -type => 'text/html', -expires => '+5s' );

    # lese die Gaestebucheintraege von der Datei ein und gebe sie als
    # Arrayreferenz zurueck
    my ( &#36entries, &#36entriesCount, &#36x, &#36y ) = &ReadEntriesFromFile(&#36startId);

    # lese das Template ein:
    my &#36template = HTML::Template->new(
        filename     &nbs
p;    => TEMPLATE_FILES->{showguestbook},
        die_on_bad_params => 0,
    );

    &#36template->param(

        # Url des Scriptes und CSS-Stylesheet
        SELF_URL => &#36ENV{SCRIPT_NAME} || '',
        CSS_URL  => CSS_URL,

        # Daten
        ENTRIES_COUNT => &#36entriesCount + 1,
        GBDATA      
  => &#36entries,

        # fuer die Navigation
        SHOW_LINK_NEWER => &#36y < &#36entriesCount,
        SHOW_LINK_OLDER => &#36x > 0,
        OLDER_START_ID  => &#36entriesCount - &#36x + 1,
        NEWER_START_ID  => &#36entriesCount - &#36y - MAX_SHOW_ENTRIES,
    );

    print &#36template->output;

    #    print "&#36entriesCount: &#36x/&#36y:&#36startId
";
}    # ShowGuestbook

# ------------------------------------------------------------
sub ReadEntriesFromFile {
    my (&#36startId) = @_;

    my @entries = ();

    my &#36entryId = 1;
    unless ( open( GB, GB_DATA_FILE ) ) {
        &PrintErrorPage( &#36cgi, "Konnte Datei nicht oeffnen: &#36!" );
    }    # unless
    else {

        # blockweises Einlesen: eine "Zeile" enthaelt nun einen Block,
        # der durch eine Leerzeile vom naechsten getrennt ist
        local &#36/ = "

";

        while (<GB>) {

            # splitte den Block an den Zeilenumbruechen auf
            my @lines = split ( /
/, &#36_ );

            # wenn da keine Daten herauskommen, weiter mit dem naechsten Block
            next unless scalar @lines;

            my %entry = ();
            foreach my &#36line (@lines) {

               
 # trenne Namen: Wert
               
 my ( &#36key, &#36value ) = split ( /\s*:\s+/, &#36line, 2 );

               
 #             print "&#36entryId: &#36key: &#36value
";

               
 # ueberpruefe, ob fuer diesen Namen schon ein Wert vorhanden
               
 # ist, wenn ja, gib einen Fehler aus, wenn nein, fuegen den
               
 # Namen und den WErt zum Hash %entry hinzu
               
 if ( exists &#36entry{&#36key} ) {
               
     &PrintErrorPage( &#36cgi, "Format der Datei ungueltig" );
               
     exit 0;
               
 }    # if
               
 else {
               
     &#36entry{&#36key} = &#36value;
               
 }    # else

            }    # foreach

            # id des Eintrages hinzufuegen
            &#36entry{id} = &#36entryId;

            # Eintrag an den Anfang von @entries hinzufuegen
            unshift ( @entries, \%entry );

            # erhoehe EntryId
            &#36entryId++;

        }    # while

        close(GB);
    }    # else

    # finde heraus, welche Eintraege angezeigt werden sollen (von &#36x bis &#36y)
    my &#36count = &#36#entries;
    my &#36y     = &#36count - &#36startId;
    my &#36x     = ( &#36y > MAX_SHOW_ENTRIES ) ? &#36y - MAX_SHOW_ENTRIES + 1 : 0;

    # und werfe die anderen weg
    @entries = @entries[ &#36count - &#36y .. &#36count - &#36x ];

    # gib eine Arrayreferenz der Eintraege zurueck sowie deren Anzahl und
    # deren Grenzen
    return ( \@entries, &#36count, &#36x, &#36y );

}    # ReadEntriesFromFile

# ------------------------------------------------------------
sub PrintErrorPage {
    my ( &#36cgi, &#36errorMessage ) = @_;

    print "Fehler: &#36errorMessage
";

}    # PrintErrorPage

# ------------------------------------------------------------

show:
  SELF_URL
  CSS_URL

  SHOW_LINK_NEWER
  SHOW_LINK_OLDER
  OLDER_START_ID
  NEWER_START_ID

  ENTRIES_COUNT
  GBDATA
    ID (Automatisch)
    NICK (Pflichtfeld)
    EMAIL (optional)
    HOMEPAGE (optional)
    DATE (Automatisch)
    SUBJECT (Pflichtfeld)
    TEXT (Pflichtfeld)


Und hier noch die /styles/Default.css'
Code: (dl )
1
2
3
4
5
6
7
8
9
10
11
12
13
body {
background-image : url();
background-color : C0C0C0;
color : Black;
}

h1 {
font-size: 20pt;
color: #6666FF;
font-style: bold;
}

td.text { background-color: #E0E0E0; }
\n\n

<!--EDIT|Strat|1061974776-->
perl -le "s::*erlco'unaty.'.dk':e,y;*kn:ai;penmic;;print"
http://www.fabiani.net/

View full thread CGI-Gaestebuch: HTML::Template + Textdateien