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    => [ '', '' ],

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

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, 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" ( 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: 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.



    plugins => [ 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 => {
        # mandatory
        dsn             => "DBI:mysql:database=test;host=localhost",
        page            => '/comments',

        #optional in some cases, no defaults
        email_to        => [ '', '' ],

        #optional, but default not specified
        user            => 'test', # user,
        pass            => 'test', # pass
        opts            => { RaiseError => 1, AutoCommit => 1 },
        uri             => '',
        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:


    dsn => "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_to => [ '', '' ],

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.


    page => '/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 Defaults to: /comments


    user => 'test_db_user',

Optional. Specifies the username to use when connecting to the SQL database used by the plugin. By default is not specified.


    pass => 'teh_password',

Optional. Specifies the password to use when connecting to the SQL database used by the plugin. By default is not specified.


    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 }


    uri => '',

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.


    mailer => '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' file is located.


    no_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.


    sort => 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.


    table => '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_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.


    moderate => 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_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


    subject => '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_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_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_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,


Fork this module on GitHub:


To report bugs or request features, please use

If you can't access GitHub, you can email your request to bug-App-ZofCMS at


Zoffix Znet <zoffix at> (,


You can use and distribute this module under the same terms as Perl itself. See the LICENSE file included in this distribution for complete details.