| App-ZofCMS-Plugin-Comments documentation | Contained in the App-ZofCMS-Plugin-Comments distribution. |
App::ZofCMS::Plugin::Comments - drop-in visitor comments support.
In your "main config" file:
comments_plugin => {
dsn => "DBI:mysql:database=test;host=localhost",
user => 'test',
pass => 'test',
email_to => [ 'admin@example.com', 'admin2@example.com' ],
},
In your ZofCMS template:
plugins => [ qw/Comments/ ],
In your "comments" page HTML::Template template, which we set to be /comments by default:
<tmpl_var name="zofcms_comments_form">
In any page on which you wish to have comments:
<tmpl_var name="zofcms_comments_form">
<tmpl_var name="zofcms_comments">
The module is a plugin for App::ZofCMS. It provides means to easily add "visitor comments" to your pages. The plugin offers configurable flood protection ( $x comments per $y seconds ) as well as ability to notify you of new comments via e-mail. The "moderation" function is also implemented, what that means is that you (the admin) would get two links (via e-mail) following one of them will approve the comment; following the other will simply delete the comment from the database.
I am an utterly lazy person, thus you may find that not everything you
may want to configure in the plugin is configurable. The plugin is
yet to undergo (at the time of this writing) deployment testing, as in
how flexible it is. If you'd like to see some features added, don't be shy
to drop me a line to zoffix@cpan.org
This documentation assumes you've read App::ZofCMS, App::ZofCMS::Config and App::ZofCMS::Template
So here is how it works, you have some page where you added the plugin's
functionality. Visitor enters his/hers comment and pressed "Post" button.
The request will be POSTed to a "comments" page and depending on what
the visitor entered he or she will either get an error with ability to
fix it or a "success" message with an ability to go back to the page
on which the comment was created. The reason for this "comments" page is
that I couldn't figure out a simple way to have the comments markup inserted
with simple <tmpl_var> and keep any page on which the plugin
was used small enough for the user to see the error message easily.
The "comments" must have <tmpl_var name="zofcms_comments_form"> on it somewhere for the plugin to work.
If you have a sharp eye, you've noticed that plugin's configuration was
placed into the 'main config file' in the SYNOPSIS. You actually don't
have to
do that and can keep plugin's configuration in your ZofCMS template,
but personally I find it much easier to just drop it into the main config
and enable it on per-page basis by sticking only Comments in the list
of the plugins on ZofCMS templates.
Under the hood the plugin uses DBI to stick data into SQL tables. Generally speaking you shouldn't have trouble using the plugin with $database_of_your_choice; however, the plugin was tested only with MySQL database. Before you can use the plugin you need to create one or two tables in your database. The columns have to be named those names and be in that order:
# comments table
CREATE TABLE comments (name VARCHAR(100), email VARCHAR(200), comment TEXT, page VARCHAR(100), remote_host TEXT, time VARCHAR(11));
#moderation table
CREATE TABLE mod_comments (name VARCHAR(100), email VARCHAR(200), comment TEXT, page VARCHAR(100), remote_host TEXT, time VARCHAR(11), id TEXT);
Now, the note on value types. The name, email and comment is the
data that the comment poster posts. Since the maximum lengths of those
fields are configurable, pick the value types you think fit. The page
column will contain the "page" on which the comment was posted. In other
words, if the comment was posted on
http://example.com/?page=/foo/bar/baz, the page cell will contain
/foo/bar/baz. The remote_host is obtained from
CGI's remote_host() method. The time cell is obtained from
the call to time() and the id in moderation table is generated with
rand() . time() . rand() (keep those flames away plz).
When moderation of comments is turned on in the plugin you will get
two links e-mailed when a new comment was submitted. One is "approve"
and another one is "deny". Functions of each are self explanatory. What
happens is that the comment is first placed in the "moderation table". If
you click "approve", the comment is moved into the "comments table". If
the comment is denied by you, it is simply deleted from the
"moderation table". There is a feature that allows all comments that
are older than $x seconds (see mod_out_time argument) to be deleted
from the "moderation table" automatically.
You will notice that there is no "captcha" (http://en.wikipedia.org/wiki/Captcha) thing done with comments form generated by the plugin. The reason for that is that I hate them... pure hate. I think the worst captcha I ever came across was this: http://www.zoffix.com/new/captcha-wtf.png. But most of all, I think they are plain annoying.
In this plugin I implemented a non-annoying "captcha" mechanizm suggested
by one of the people I know who claimed it works very well. At the time
of this writing I am not yet aware of how "well" it really is. Basically,
the plugin sticks <input type="hidden" name="zofcms_comments_username" value="your user name"> in the form. When checking the parameters,
the plugin checks that this hidden input's value matches. If it doesn't,
boot the request. Apparently the technique works much better when the
<input> is not of type="hidden" but I am very against "hiding"
something with CSS.
So, time will show, if this technique proves to be a failure, expect the plugin to have an option to provide a better "captcha" mechanizm. As for now, this is all you get, although, I am open for good ideas.
pluginsplugins => [ qw/Comments/ ],
This goes without saying that you'd need to stick 'Comments' into the list
of plugins used in ZofCMS template. As opposed to many other plugins
this plugin will not bail out of the execution right away if
comments_plugin first level key (described below) is not specified in
the template (however it will if you didn't specify comments_plugin in
neither the ZofCMS template nor the main config file).
comments_plugin comments_plugin => {
# mandatory
dsn => "DBI:mysql:database=test;host=localhost",
page => '/comments',
#optional in some cases, no defaults
email_to => [ 'admin@test.com', 'admin2@test.com' ],
#optional, but default not specified
user => 'test', # user,
pass => 'test', # pass
opts => { RaiseError => 1, AutoCommit => 1 },
uri => 'http://yoursite.com',
mailer => 'testfile',
no_pages => [ qw(/foo /bar/beer /baz/beer/meer) ],
# optional, defaults presented here
sort => 0
table => 'comments',
mod_table => 'mod_comments',
must_name => 0,
must_email => 0,
must_comment => 1,
name_max => 100,
email_max => 200,
comment_max => 10000,
moderate => 1,
send_entered => 1,
subject => 'ZofCMS Comments',
flood_num => 2,
flood_time => 180,
mod_out_time => 1209600,
}
Whoosh, now that's a list of options! Luckly, most of them have defaults.
I'll go over them in a second. Just want to point out that all these
arguments can be set in the "main config file" same way you'd set them
in ZofCMS template (the first-level comments_plugin key). In fact,
I recommend you set them all in ZofCMS main config file instead of ZofCMS
templates, primarily because you'd
want to have it duplicated at least twice: once on the "comments page"
and once on the page on which you actually want to have visitors' comments
functionality. So here are the possible arguments:
dsndsn => "DBI:mysql:database=test;host=localhost",
Mandatory. Takes a scalar as a value which must contain a valid
"$data_source" as explained in DBI's connect_cached() method (which
plugin currently uses).
email_toemail_to => [ 'admin@test.com', 'admin2@test.com' ],
Mandatory unless moderate and send entered are set to a
false values. Takes either a scalar or an arrayref as a value.
Specifying a scalar is equivalent to specifying an arrayref with just
that scalar in it. When moderate or send_entered are set
to true values, the e-mail will be sent to each of the addresses
specified in the email_to arrayref.
pagepage => '/comments',
Optional. This is the "comments page" that I explained in the
HOW IT ALL COMES TOGETHER OR "WHAT'S THAT 'comments' PAGE ANYWAY?"
section above. Argument takes a string as a value. That value is what
you'd set the page query parameter in order to get to the "comments
page". Make sure you also prepend the dir. In the example above
the comments page is accessed via
http://example.com/index.pl?page=comments&dir=/. Defaults to:
/comments
useruser => 'test_db_user',
Optional. Specifies the username to use when connecting to the SQL database used by the plugin. By default is not specified.
passpass => 'teh_password',
Optional. Specifies the password to use when connecting to the SQL database used by the plugin. By default is not specified.
opts opts => { RaiseError => 1, AutoCommit => 1 },
Optional. Takes a hashref as a value.
Specifies additional options to DBI's connect_cached()
method, see DBI's documentation for possible keys/values of this
hashref. Defaults to: { RaiseError => 1, AutoCommit => 1 }
uriuri => 'http://yoursite.com/index.pl?page=/comments',
Optional. The only place in which this argument is used is for
generating the "Approve" and "Deny" URIs in the e-mail sent to you when
moderate is set to a true value. Basically, here you would give
the plugin a URI to your "comments page" (see page argument above).
If you don't specify this argument, nothing will explode (hopefully) but
you won't be able to "click" the "Approve"/"Deny" URIs.
mailermailer => 'testfile',
Optional. When either moderate or send_entered arguments are
set to true values, the mailer argument specifies which "mailer" to
use to send e-mails. See documentation for Mail::Mailer for possible
mailers. By default mailer argument is not specified, thus the
"mailers" will be tried until one of them works. When mailer is set
to testfile, the mail file will be located at the same place ZofCMS'
index.pl file is located.
no_pagesno_pages => [ qw(/foo /bar/beer /baz/beer/meer) ],
Optional. Takes an arrayref as a value. Each element of that arrayref
must be a page with dir appended to it, even if dir is /
(see the "Note on page and dir query parameters" in App::ZofCMS::Config
documentation). Basically, any pages listed here will not be processed
by the plugin even if the plugin is listed in plugins first-level
ZofCMS template key. By default is not set.
sortsort => 0,
Optional. Currently accepts only true or false values.
When set to a true value
the comments on the page will be listed in the "oldest-first" fashion.
When set to a false value the comments will be reversed - "newest-first"
sorting. Defaults to: 0.
tabletable => 'comments',
Optional. Takes a string as a value which must contain the name of
SQL table used for storage of comments. See THE SQL TABLES! section
above for details. Defaults to: comments
mod_tablemod_table => 'mod_comments',
Optional. Same as table argument (see above) except this one
specifies the name of "moderation table", i.e. the comments awaiting
moderation will be stored in this SQL table. Defaults to:
mod_comments
must_name, must_email and must_comment must_name => 0,
must_email => 0,
must_comment => 1,
Optional. The "post comment" form generated by the plugin contains
the Name, E-mail and Comment fields. The
must_name, must_email and must_comment arguments take either
true or false values. When set to a true value, the visitor must fill
the corresponding field in order to post the comment. If field is
spefied as "optional" (by setting a false value) and the visitor doesn't
fill it, it will default to N/A. By default must_name and
must_email are set to false values and must_comment is set to
a true value.
name_max, email_max and comment_max name_max => 100,
email_max => 200,
comment_max => 10000,
Optional. Same principle as with must_* arguments explained above,
except *_max arguments specify the maximum length of the fields. If
visitor enters more than specified by the corresponding *_max argument,
he or she (hopefully no *it*s) will get an error. By default
name_max is set to 100, email_max is set to 200 and
comment_max is set to 10000.
moderatemoderate => 1,
Optional. Takes either true or false values. When set to a true value
will enable "moderation" functionality. See COMMENT MODERATION
section above
for details. When set to a false value, comments will appear on the
page right away. Note: when set to a true value e-mail will be
automatically sent to email_to addresses. Defaults to: 1
send_enteredsend_entered => 1,
Optional. Takes either true or false values, regarded only when
moderate argument is set to a false value. When set to a true value
will dispatch an e-mail about a new comment to the addresses set
in email_to argument. Defaults to: 1
subjectsubject => 'ZofCMS Comments',
Optional. Takes a string as a value. Nothing fancy, this will be
the "Subject" of the e-mails sent by the plugin (see moderate and
send_entered arguments). Defaults to: 'ZofCMS Comments'
flood_numflood_num => 2,
Optional. Takes a positive integer or zero as a value. Indicates how many
comments a visitor may post in flood_time (see below) amount of time.
Setting this value to 0 effectively disables flood protection.
Defaults to: 2
flood_timeflood_time => 180,
Optional. Takes a positive integer as a value. Specifies the time
in seconds during which the visitor may post only flood_num (see
above) comments. Defaults to: 180
mod_out_timemod_out_time => 1209600,
Optional. Takes a positive integer or false value as a value. When
set to a positive integer indicates how old (in seconds) the comment
in mod_table must get before it will be automatically removed from
the mod_table (i.e. "denied"). Comments older than mod_out_time
seconds will not actually be deleted until moderation takes place, i.e.
until you approve or deny some comment. Setting this value to 0
effectively disables this "auto-delete" feature. Defaults to:
1209600 (two weeks)
The examples/ directory of this distribution contains main config file
and HTML/ZofCMS templates which were used during testing of this plugin.
This plugin requires more goodies than any other ZofCMS plugin to the date. Plugin needs the following modules for happy operation. Plugin was tested with module versions indicated:
'DBI' => 1.602,
'URI' => 1.35,
'HTML::Template' => 2.9,
'HTML::Entities' => 1.35,
'Storable' => 2.18,
'Mail::Send' => 2.04,
Zoffix Znet, <zoffix at cpan.org>
(http://zoffix.com, http://haslayout.net)
Please report any bugs or feature requests to bug-app-zofcms-plugin-comments at rt.cpan.org, or through
the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=App-ZofCMS-Plugin-Comments. I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.
You can find documentation for this module with the perldoc command.
perldoc App::ZofCMS::Plugin::Comments
You can also look for information at:
http://rt.cpan.org/NoAuth/Bugs.html?Dist=App-ZofCMS-Plugin-Comments
Copyright 2008 Zoffix Znet, all rights reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| App-ZofCMS-Plugin-Comments documentation | Contained in the App-ZofCMS-Plugin-Comments distribution. |
package App::ZofCMS::Plugin::Comments; use warnings; use strict; our $VERSION = '0.0102'; use DBI; use URI; use HTML::Template; use HTML::Entities; use Storable qw/lock_store lock_retrieve/; sub new { return bless {}, shift } sub process { my ( $self, $template, $query, $config ) = @_; my $plug_conf = { %{ $config->conf->{comments_plugin} || {} }, %{ delete $template->{comments_plugin} || {} }, }; return unless keys %$plug_conf; return unless $plug_conf->{dsn}; my %no_pages = map { $_ => 1 } @{ $plug_conf->{no_pages} || [] }; return if exists $no_pages{ $query->{dir} . $query->{page} }; return if $plug_conf->{page} eq $query->{dir} . $query->{page} and not exists $query->{zofcms_comments_page}; $plug_conf->{comments_page} = exists $query->{zofcms_comments_page} ? $query->{zofcms_comments_page} : $query->{dir} . $query->{page}; $plug_conf = $self->_fill_defaults( $plug_conf ); $plug_conf->{remote_host} = $config->cgi->remote_host(); my $dbh = DBI->connect_cached( @$plug_conf{ qw/dsn user pass opts/ } ) or die "Failed to connect"; if ( $query->{zofcms_comments_approve} or $query->{zofcms_comments_deny} ) { $self->_moderate_comment( $dbh, $plug_conf, $query ); } my $html_template = $self->_prepare_html_template( $plug_conf->{comments_page}, $plug_conf->{page}, $query, ); if ( defined $query->{zofcms_comments_username} and $query->{zofcms_comments_username} eq 'your user name' ) { $self->_enter_comment( $dbh, $plug_conf, $html_template, $query ); } @{ $template->{t} }{ qw/zofcms_comments zofcms_comments_form/ } = ( $self->_fetch_comments( $dbh, $plug_conf, $query ), $html_template->output, ); eval { $dbh->commit; $dbh->disconnect; }; return 1; } sub _moderate_comment { my ( $self, $dbh, $plug_conf, $query ) = @_; return unless exists $query->{zofcms_comment_id}; my $comments = $dbh->selectall_arrayref( "SELECT * FROM $plug_conf->{mod_table} WHERE id = ?;", undef, $query->{zofcms_comment_id} ); unless ( @$comments ) { $self->_print_moderation("No such comment found"); } if ( exists $query->{zofcms_comments_approve} ) { $dbh->do( "INSERT INTO $plug_conf->{table} VALUES(?, ?, ?, ?, ?, ?);", undef, @{ shift @$comments }, ); } if ( $plug_conf->{mod_out_time} ) { $dbh->do( "DELETE FROM $plug_conf->{mod_table} WHERE time < ?;", undef, time() - $plug_conf->{mod_out_time}, ); } $dbh->do( "DELETE FROM $plug_conf->{mod_table} WHERE id = ?;", undef, $query->{zofcms_comment_id}, ); $self->_print_moderation("Comment was successfuly denied") if exists $query->{zofcms_comments_deny}; $self->_print_moderation("Comment was successfuly approved"); } sub _print_moderation { my ( $self, $message ) = @_; print "Content-type: text/plain\n\n"; print "$message\n"; exit; } sub _fetch_comments { my ( $self, $dbh, $plug_conf, $query ) = @_; my $data = $dbh->selectall_arrayref( "SELECT * FROM $plug_conf->{table} WHERE page = ?;", undef, $query->{dir} . $query->{page}, ); my $comment_number = 0; @$data = sort { $b->[5] <=> $a->[5] } map { push @$_, $comment_number++; $_ } @$data; if ( $plug_conf->{sort} ) { @$data = reverse @$data; } my @comments_loop = map +{ name => $_->[0], date => scalar(localtime($_->[5])), comment => do { my $x = encode_entities($_->[2]); $x =~ s/\n/<br>/g; $x }, comment_number => $_->[-1], }, @$data; my $html_template = HTML::Template->new_scalar_ref( \ $self->_comments_template ); $html_template->param( comments => \@comments_loop ); return $html_template->output; } sub _enter_comment { my ( $self, $dbh, $plug_conf, $html_template, $query ) = @_; return unless $self->_is_valid_form( $plug_conf, $html_template, $query ); #'CREATE TABLE comments (name VARCHAR(100), email VARCHAR(200), comment TEXT, page VARCHAR(100), remote_host TEXT, time VARCHAR(11));'; if ( $plug_conf->{flood_num} ) { my $user_comments = $dbh->selectall_arrayref( "SELECT * FROM $plug_conf->{table} WHERE remote_host = ? AND time > ?;", undef, $plug_conf->{remote_host}, time() - $plug_conf->{flood_time}, ); if ( @$user_comments >= $plug_conf->{flood_num} ) { $html_template->param( success => 0, error => 'Sorry, but due to abuse the number of posts' . ' is limited. Please try again shortly' ); return; } } my @dbi_insert_args = ( @$query{ qw/ zofcms_comments_name zofcms_comments_email zofcms_comments_comment / }, $plug_conf->{comments_page}, $plug_conf->{remote_host}, time(), ); if ( $plug_conf->{moderate} ) { my $mod_id = rand() . time() . rand(); $dbh->do( "INSERT INTO $plug_conf->{mod_table} VALUES(?, ?, ?, ?, ?, ?, ?);", undef, @dbi_insert_args, $mod_id, ); $self->_send_mod_email( $plug_conf, @dbi_insert_args, $mod_id ); } else { my $r = $dbh->do( "INSERT INTO $plug_conf->{table} VALUES(?, ?, ?, ?, ?, ?);", undef, @dbi_insert_args, ); $self->_send_comment_entered_email( $plug_conf, @dbi_insert_args ) if $plug_conf->{send_entered}; } } sub _send_mod_email { my ( $self, $plug_conf, @data ) = @_; my %data; @data{ qw/name email comment page host time id/ } = @data; my $approve_uri = URI->new( $plug_conf->{uri} ); my @query = $approve_uri->query_form; push @query, zofcms_comment_id => $data{id}; my $deny_uri = $approve_uri->clone; $approve_uri->query_form( @query, zofcms_comments_approve => 1 ); $deny_uri->query_form( @query, zofcms_comments_deny => 1 ); my $body_template = HTML::Template->new_scalar_ref( \ $self->_get_mail_template ); $body_template->param( mod => 1, approve => $approve_uri->as_string, deny => $deny_uri->as_string, time => scalar(localtime $data{time}), map +( $_ => $data{ $_ } ), qw/page host email name comment/, ); $self->_send_mail( $plug_conf, $body_template->output ); } sub _send_comment_entered_email { my ( $self, $plug_conf, @data ) = @_; my %data; @data{ qw/name email comment page host time/ } = @data; my $body_template = HTML::Template->new_scalar_ref( \ $self->_get_mail_template ); $data{time} = localtime $data{time}; $body_template->param( %data ); $self->_send_mail( $plug_conf, $body_template->output ); return; } sub _send_mail { my ( $self, $plug_conf, $body ) = @_; require Mail::Send; my $email = Mail::Send->new; $email->subject( $plug_conf->{subject} ); my $to = ref $plug_conf->{email_to} ? $plug_conf->{email_to} : [ $plug_conf->{email_to} ]; $email->to( @$to ); my $fh; if ( $plug_conf->{mailer} ) { $Mail::Mailer::testfile::config{outfile} = 'mailer.testfile'; $fh = $email->open( $plug_conf->{mailer} ); } else { $fh = $email->open; } print $fh $body; $fh->close; return; } sub _is_valid_form { my ( $self, $plug_conf, $html_template, $query ) = @_; my @form_fields = qw/ zofcms_comments_name zofcms_comments_email zofcms_comments_comment /; for ( @form_fields ) { $query->{$_} = '' unless defined $query->{$_}; } my ( $name, $email, $comment ) = @$query{ @form_fields }; if ( $plug_conf->{must_name} and not length $name ) { $html_template->param( error => 'Missing <em>Name</em> field' ); return; } if ( $plug_conf->{must_email} and not length $email ) { $html_template->param( error => 'Missing <em>E-mail</em> field' ); return; } if ( $plug_conf->{must_comment} and not length $comment ) { $html_template->param( error => 'Missing <em>Comment</em> field' ); return; } if ( length( $name ) > $plug_conf->{name_max} ) { $html_template->param( error => 'Parameter <em>Name</em> cannot exceed ' . $plug_conf->{name_max} . ' characters in length' ); return; } if ( length( $email ) > $plug_conf->{email_max} ) { $html_template->param( error => 'Parameter <em>E-mail</em> cannot exceed ' . $plug_conf->{email_max} . ' characters in length' ); return; } if ( length( $comment ) > $plug_conf->{comment_max} ) { $html_template->param( error => 'Parameter <em>Comment</em> cannot exceed ' . $plug_conf->{comment_max} . ' characters in length' ); return; } for ( $name, $email, $comment ) { $_ = 'N/A' unless length; } $html_template->param( success => 1 ); @$query{ @form_fields } = ( $name, $email, $comment ); return 1; } sub _prepare_html_template { my ( $self, $comments_page, $page, $query ) = @_; my $html_template = HTML::Template->new_scalar_ref( \ $self->_form_template ); $html_template->param( name => $query->{zofcms_comments_name}, email => $query->{zofcms_comments_email}, comment => $query->{zofcms_comments_comment}, page => $page, comments_page => $comments_page, back_to_comments_page => $query->{zofcms_comments_page}, ); return $html_template; } sub _fill_defaults { my ( $self, $plug_conf ) = @_; $plug_conf = { 'sort' => 0, table => 'comments', mod_table => 'mod_comments', page => '/comments', must_name => 0, must_email => 0, must_comment => 1, name_max => 100, email_max => 200, comment_max => 10000, moderate => 1, send_entered => 1, subject => 'ZofCMS Comments', flood_num => 2, flood_time => 180, mod_out_time => 1209600, opts => { RaiseError => 1, AutoCommit => 1 }, %$plug_conf, }; } sub _comments_template { return <<'END_TEMPLATE'; <ul class="zofcms_comments"> <tmpl_loop name="comments"> <li id="zofcms_comment_<tmpl_var name="comment_number">"> <p class="zofcms_comments_name"><a href="#zofcms_comment_<tmpl_var name="comment_number">"><tmpl_var escape="html" name="name"></a></p> <p class="zofcms_comments_date"><tmpl_var escape="html" name="date"></p> <p class="zofcms_comments_comment"><tmpl_var name="comment"></p> </li> </tmpl_loop> </ul> END_TEMPLATE } sub _form_template { return <<'END_TEMPLATE'; <tmpl_if name="success"><p class="success">Your comment was successfuly added.</p> <p>Feel free to go back to the <a href="/index.pl?page=<tmpl_var name="back_to_comments_page" escape="html">">original page</a>.</p> <tmpl_else> <tmpl_if name="error"><p class="error"><tmpl_var name="error"></p></tmpl_if> <form class="zofcms_comments" action="" method="POST"> <fieldset> <legend>Create new comment</legend> <input type="hidden" name="zofcms_comments_page" value="<tmpl_var name="comments_page" escape="html">"> <input type="hidden" name="zofcms_comments_username" value="your user name"> <input type="hidden" name="page" value="<tmpl_var escape="html"name="page">"> <ul> <li> <label for="zofcms_comments_name">Name: </label ><input type="text" name="zofcms_comments_name" id="zofcms_comments_name" value="<tmpl_var escape="html" name="name">"> </li> <li> <label for="zofcms_comments_email">E-mail: </label ><input type="text" name="zofcms_comments_email" id="zofcms_comments_email" value="<tmpl_var escape="html" name="email">"> </li> <li> <label for="zofcms_comments_comment">Comment: </label ><textarea name="zofcms_comments_comment" id="zofcms_comments_comment" cols="40" rows="10"><tmpl_var name="comment" escape="html"></textarea> </li> </ul> <input id="zofcms_comments_submit" type="submit" value="Post"> </fieldset> </form> </tmpl_if> END_TEMPLATE } sub _get_mail_template { return <<'END_MAIL_TEMPLATE'; <tmpl_if name="mod"> Approve: <tmpl_var name="approve"> Deny: <tmpl_var name="deny"> </tmpl_if> Comment on page: <tmpl_var name="page"> From: <tmpl_var name="name"> [ <tmpl_var name="host"> ] Time: <tmpl_var name="time"> E-mail: <tmpl_var name="email"> Comment: <tmpl_var name="comment"> END_MAIL_TEMPLATE } 1; __END__