#! /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 $FindBin::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 => "$FindBin::Bin/data/guestbook.txt";
# wo liegen die Templates?
use constant TEMPLATE_DIR => "$FindBin::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 $cgi = CGI::->new();
# aktion abfragen; wenn keine Vorhanden, dann guestbook verwenden
my $action = $cgi->param('action') || 'guestbook';
if ( $action eq 'guestbookadd' ) {
&AddGuestbookEntry($cgi);
} # if
elsif ( $action eq 'guestbooksave' ) {
&SaveNewGuestbookEntry($cgi);
} # elsif
else { # Standardvorgehen: zeige das Gaestebuch an
&ShowGuestbook($cgi);
} # else
# ------------------------------------------------------------
sub SaveNewGuestbookEntry {
my ($cgi) = @_;
my %data = ();
foreach ( @{&GUESTBOOK_FIELDS} ) {
$data{$_} = $cgi->param($_) || '';
$data{$_} =~ s/^\s*//;
$data{$_} =~ s/\s*$//;
} # foreach
unless ( $data{nick} ) {
$data{message} .= "
Der Name muß angegeben werden";
} # unless
unless ( $data{subject} ) {
$data{message} .= "
Der Betreff muß angegeben werden";
} # unless
unless ( $data{text} ) {
$data{message} .= "
Der Text muß angegeben werden";
} # unless
if ( $data{email} ) { # Ueberpruefe die Email-Adresse
require Mail::RFC822::Address;
unless ( &Mail::RFC822::Address::valid( $data{email} ) ) {
$data{message} .=
"
Diese Email-Adresse ist fehlerhaft (nicht RFC822-Konform)";
}
# else
} # if
if ( $data{message} ) {
&AddGuestbookEntry( $cgi, \%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 $finder = URI::Find::Schemeless->new(
sub {
my ( $uri, $originalUri ) = @_;
return ( ''
. &HTML::Entities::encode_entities($originalUri)
. '' );
} # sub
);
# allgemeine Umwandlungen
foreach ( @{&GUESTBOOK_FIELDS} ) {
# $data{$_} =~ s//>/g;
# $data{$_} =~ s/\&/&/g;
# $data{$_} =~ s/\"/"/g; $data{$_} =~ s/\'/'/g;
# ersetze Sonderzeichen wie < > & ' " durch deren Codes
$data{$_} = &CGI::escapeHTML( $data{$_} );
# ersetze Zeilenumbruecke durch
$data{$_} =~ s/\r?
/
/g;
# ersetze URIs durch HTML-Links
$finder->find( \$data{$_} );
} # foreach
# erzeuge email-link
if ( $data{email} ) {
$data{email} = qq~$data{email}~;
} # if
# ermittle Datum und Uhrzeit
my @time = localtime(time);
$time[4]++;
$time[5] += 1900;
$data{date} =
sprintf( "%02i.%02i.%04i %02i\:%02i", @time[ 3 .. 5, 2, 1 ] );
&SaveNewEntryToFile( $cgi, \%data )
and &ShowGuestbook($cgi);
}
} # SaveNewGuestbookEntry
# ------------------------------------------------------------
sub SaveNewEntryToFile {
my ( $cgi, $data ) = @_;
my $string = join ( "",
map { "$_: $data->{$_}
" }
grep { $data->{$_} } @{&GUESTBOOK_FIELDS} );
unless ( open( GB, ">>" . GB_DATA_FILE ) ) {
&PrintErrorPage( $cgi, "Konnte Datei nicht oeffnen: $!" );
exit;
} # unless
else {
flock( GB, LOCK_EX );
print( GB "$string
" );
close(GB);
} # else
return 1;
} # SaveNewEntryToFile
# ------------------------------------------------------------
sub AddGuestbookEntry {
my ( $cgi, $data ) = @_;
# gib den HTML-Header aus
print $cgi->header( -type => 'text/html', -expires => '+5s' );
# lese das Template ein:
my $template = HTML::Template->new(
filename &nbs
p; => TEMPLATE_FILES->{addguestbook},
die_on_bad_params => 0,
);
$template->param(
# Url des Scriptes und CSS-Stylesheet
SELF_URL => $ENV{SCRIPT_NAME} || '',
CSS_URL => CSS_URL,
# eine eventuelle Fehlermeldung
MESSAGE => $data->{message} || '',
# Die Daten im Falle eines Fehlers
NICK => $data->{nick} || '',
EMAIL => $data->{email} || '',
HOMEPAGE => $data->{homepage} || '',
SUBJECT => $data->{subject} || '',
TEXT => $data->{text} || '',
);
print $template->output;
} # AddGuestbookEntry
# ------------------------------------------------------------
sub ShowGuestbook {
my ($cgi) = @_;
# $startId ist der offset zur letzten Nachricht
# also 0 entspricht der letzten Nachricht, 1 der vorletzten usw.
my $startId = $cgi->param('id') || 0;
$startId > 0 or $startId = 0;
# gib den HTML-Header aus
print $cgi->header( -type => 'text/html', -expires => '+5s' );
# lese die Gaestebucheintraege von der Datei ein und gebe sie als
# Arrayreferenz zurueck
my ( $entries, $entriesCount, $x, $y ) = &ReadEntriesFromFile($startId);
# lese das Template ein:
my $template = HTML::Template->new(
filename &nbs
p; => TEMPLATE_FILES->{showguestbook},
die_on_bad_params => 0,
);
$template->param(
# Url des Scriptes und CSS-Stylesheet
SELF_URL => $ENV{SCRIPT_NAME} || '',
CSS_URL => CSS_URL,
# Daten
ENTRIES_COUNT => $entriesCount + 1,
GBDATA
=> $entries,
# fuer die Navigation
SHOW_LINK_NEWER => $y < $entriesCount,
SHOW_LINK_OLDER => $x > 0,
OLDER_START_ID => $entriesCount - $x + 1,
NEWER_START_ID => $entriesCount - $y - MAX_SHOW_ENTRIES,
);
print $template->output;
# print "$entriesCount: $x/$y:$startId
";
} # ShowGuestbook
# ------------------------------------------------------------
sub ReadEntriesFromFile {
my ($startId) = @_;
my @entries = ();
my $entryId = 1;
unless ( open( GB, GB_DATA_FILE ) ) {
&PrintErrorPage( $cgi, "Konnte Datei nicht oeffnen: $!" );
} # unless
else {
# blockweises Einlesen: eine "Zeile" enthaelt nun einen Block,
# der durch eine Leerzeile vom naechsten getrennt ist
local $/ = "
";
while () {
# splitte den Block an den Zeilenumbruechen auf
my @lines = split ( /
/, $_ );
# wenn da keine Daten herauskommen, weiter mit dem naechsten Block
next unless scalar @lines;
my %entry = ();
foreach my $line (@lines) {
# trenne Namen: Wert
my ( $key, $value ) = split ( /\s*:\s+/, $line, 2 );
# print "$entryId: $key: $value
";
# 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 $entry{$key} ) {
&PrintErrorPage( $cgi, "Format der Datei ungueltig" );
exit 0;
} # if
else {
$entry{$key} = $value;
} # else
} # foreach
# id des Eintrages hinzufuegen
$entry{id} = $entryId;
# Eintrag an den Anfang von @entries hinzufuegen
unshift ( @entries, \%entry );
# erhoehe EntryId
$entryId++;
} # while
close(GB);
} # else
# finde heraus, welche Eintraege angezeigt werden sollen (von $x bis $y)
my $count = $#entries;
my $y = $count - $startId;
my $x = ( $y > MAX_SHOW_ENTRIES ) ? $y - MAX_SHOW_ENTRIES + 1 : 0;
# und werfe die anderen weg
@entries = @entries[ $count - $y .. $count - $x ];
# gib eine Arrayreferenz der Eintraege zurueck sowie deren Anzahl und
# deren Grenzen
return ( \@entries, $count, $x, $y );
} # ReadEntriesFromFile
# ------------------------------------------------------------
sub PrintErrorPage {
my ( $cgi, $errorMessage ) = @_;
print "Fehler: $errorMessage
";
} # 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)