The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

CGI::WebToolkit - Website Toolkit

SYNOPSIS

        use CGI::WebToolkit;
        my $wtk = CGI::WebToolkit->new( %options );
        print $wtk->handle();

DESCRIPTION

CGI::WebToolkit tries to simplify the common tasks when creating dynamic websites. The use of CGI::WebToolkit should lead to the development of easy to understand, relieable and fast dynamic web applications that are easy to adjust and maintain.

CGI::WebToolkit itself is designed to be as simple and straight forward as possible. The basic philosophy behind the module is best described as "Do not repeat yourself." (DNRY). The experience gained while developing a number of websites has led to certain common recipes that are packaged as a single module for reusability.

CGI::WebToolkit was writted to provide abstractions and functionality for the following common tasks in web application development:

1 Configuration
2 Workflow abstraction (aka runlevel, modes, actions, ...)
3 Sessions
4 Templates (incl. form creation etc.)
5 Datenbase abstraction
6 User and rights management
7 Internationalization
8 Caching

There is a tutorial: CGI::WebToolkit::Tutorial.

Directory structure

CGI::WebToolkit relies on a common directory structure. This structure makes it possible to simplify the configuration of the application to a level where only the minimal information needed must be given. This makes development more relieble, faster and other tools may work on many websites.

The directory structure of the public directory as required by CGI::WebToolkit, usually this would go somewhere in the htdocs directory on the server:

        core/
        themes/
          <themename>/
        uploads/

The directory structure of the private directory as required by CGI::WebToolkit, usually this would go outside of the web-accessable area on the server:

        accesschecks/
        accessconfigs/
        cacheconfigs/
        configs/
        generators/
        javascripts/
        logs/
        modules/
        schemas/
        styles/ 
        templates/
          <themename>/
        workflows/

The directory structure of the cgi directory can have any form. Usually this resembles the cgi directory on the server. The actual application scripts go there.

Constructor new()

The constructor takes only named parameters, of which some are optional. These options are given in a shell-like syntax, e.g -optname. The case of the parameter name does not matter, so you can either write -OptName, -optName, optname or any kind of other case variation.

        use CGI::WebToolkit;
        my $wtk = CGI::WebToolkit->new(
          # required
          -publicpath  => '...',
          -publicurl   => '...',
          -privatepath => '...',
          -cgipath     => '...',
          -cgiurl      => '...',        
          # optional
          # ...
        );

Required settings

-publicpath => path

The path to the public directory of the web application.

-publicurl => url

The url to the public directory of the web application.

-privatepath => path

The path to the private directory of the web application.

-cgipath => path

The path to the cgi directory of the web application.

-cgiurl => url

The url to the cgi directory of the web application.

Optional settings

-config => filename

This is the name of a configuration file to load. The file should exist in the config directory. Values from configuration files have low priority, any parameter that is set directly within the new() call overwrites the config value.

-engine => database-type

The name of the database engine, default is mysql.

-user => name

The name of the database user, default is guest.

-name => database-name

The name of the database to use, default is empty.

-password => password

The password of the database user, default is empty.

-host => host

The host name for the database server, default is localhost.

-port => port

The port number for the database server, default is empty.

-templatefallbacks => [ name, ... ]

The fallback directories that are searched for the template files. The first one is searched first, then the second etc. until the template file is found. If no template file could be found, the template core.error is used.

-idparam => name

The name of the POST/GET parameter that holds the session id, default is sid.

-sessiontable => name

The name of the database table that holds the session data. The table must contain these columns: id, session_id, content and last_update. The default table name is session.

An example SQL statement (for MySQL) that will create an appropriate database session table:

        create table `session` (
          `id` int(11) not null auto_increment primary key,
          `session_id` varchar(32) not null,
          `content` text not null,
          `last_update` int(16) not null
        );

To deactivate sessions, leave this option empty, which is also the default.

-sessiontimeout => seconds

The number of seconds after which a session is regarded as trash and not available anymore. Default timeout is 1800 seconds (30 minutes).

-usertable => name

Name of the database table to store user info in. An example SQL statement (for MySQL) that will create an appropriate database user table:

        create table `user` (
          `id` int(11) not null auto_increment primary key,
          `loginname` varchar(255) not null,
          `password` varchar(32) not null,
          `language` varchar(5) not null
        );

If you do not need this feature, just leave the tablename empty, which is also the default.

-accessconfig => name

This option defined what access configuration file is used. The default is empty, which means that no access check will be performed.

-cachetable => name

The name of the database table that is used to store cached results from workflow functions. In order to make the cache work properly you have to define cache parameters (see Caching).

An example SQL statement (for MySQL) that will create an appropriate database cache table:

        create table `cache` (
          `id` int(11) not null auto_increment primary key,
          `hash` varchar(32) not null,
          `content` text not null,
          `last_update` int(16) not null
        );

To deactivate caching, leave the tablename empty, which is also the default.

-allowclearcache => 1/0

Caching is activated, as soon as you define a cachetable inside the configuration variables, s.a. But how do you delete the cache, e.g. for testing purposes? If this option is set to 1 (the default), you only have to attach an additional parameter to the url named "clearcache" and the whole cache will be removed from the database.

This method clears all cache entries in the configured cache table. It is currently impossible to selectively remove only certain cache entries, because the cache entry's name (a hash value) cannot be used to determine any details, about where this entry is from, e.g. the name of the workflow function etc.

-clearcacheparam => name

The name of the POST/GET parameter that triggers the removal of the cache, if activated, see -allowclearcache.

-workflowparam => name

The name of the POST/GET parameter that holds the name of the workflow function, default is do.

-entryaction => name

The name of the workflow function that is called if no workflow function name could be determined, default is core.default.

-modules => [ name, ... ]

The names of modules to load. These modules will be available from inside the workflow functions. Modules such as these usually contain general functions that return arbitrary data in order to calculate, deliver or store other data. The modules must exist somewhere in @INC or inside the modules directory.

-cssfiles => [ name, ... ]

The names of css files that are all combined into one single css. The final css can be retrieved via the special url ...?to=core.combine.css The css files must exist inside the styles directory.

-jsfiles => [ name, ... ]

The names of javascript files that are all combined into one single javascript. The final javascript can be retrieved via the special url ...?to=core.combine.js The javascript files must exist inside the javascript directory.

-phrasetable => <name>

The name of the database table to use for the translation dictionary.

An example SQL statement (for MySQL) that will create an appropriate database dictionary table:

        create table `phrase` (
          `id` int(11) not null auto_increment primary key,
          `language` varchar(5) not null,
          `name` varchar(32) not null,
          `translations` text not null
        );

To deactivate the translation feature, just leave the tablename empty, which is also the default.

-defaultlanguage => language

The language used for guests that come to the website for the first time and are not currenlty logged in.

-onsessionstart => functionname

The name of the workflow function that is called when the session is started. This may be useful to initialize some session variables. The result of that workflow function is then ignored.

-allowmacros => 1/0

If this option is set to 1 (default), the templates that are loaded, are filtered through a macro processor. It is basicly a processor that allows the loading of templates from within (xml-based) templates. Disabling this feature makes processing of template a bit faster.

See separate section macros below for details on macros.

-uploadmaxsize => bytes

Maximum allowed upload size. This is the internal limit, there is usually a server limit for uploads, which probably has to be adjusted as well. The default size is 6MB.

Objectoriented and functional style

The methods of CGI::WebToolkit can be invoced in two different styles: in the object oriented manner using standard syntax:

        $wtk->methodname();

and in a functional manner:

        methodname();

When called as usual functions, the CGI::WebToolkit instance the call works on is a singleton and refers to the last instance of CGI::WebToolkit that was created via the new() constructor.

The functional way of invocation has the advantage of beeing visually more obvious. Usually there is only one instance of CGI::WebToolkit anyway, so conflicts hardly happen.

Methods for workflow abstraction

In CGI::WebToolkit, each time a webpage is requested, the workflow engine tries to determine, which workflow function should be executed. Then this function is executed. Workflow functions are ordinary Perl functions that get certain parameters and return an array of values. Each workflow function has a unique name and can be sorted into categories and/or subcategories etc.

handle()

This method is used to handle the current request. It will determine the workflow function to be called, call it, analyse its result and call other workflow functions if nessessary. Finally, it will return a string - the webpage requested, a part of a webpage or a valid replacement, such as an error page.

handle() will analyse the workflow parameters that were given to the request from the client and based on that call a certain workflow function. If this function does return a followup, it will be called directly afterwards, if not, the message returned by the workflow function is returned. A <followup> is a special return value that tells CGI::WebToolkit to forward control flow directly to another workflow function.

Inside a workflow function the CGI::WebToolkit instance is magically available as the variable $wtk and the additional arguments are stored in @args.

Examples for workflow functions:

        # ...
        return $wtk->output(1, 'ok', "<h1>Hello, World!</h1>");

        # ...
        return $wtk->followup("core.error");

        # ...
        return $wtk->output(1, 'ok', "...", 'image/png');

The bodies of workflow functions are stored in Perl files, one for each function inside a directory (or subdirectory etc.) in the workflows directory.

call()

This method is used to call a workflow function directly and return its result unmodified and unanalysed.

        my $result = $wtk->call( $name, @args );

output()

This method returns a valid CGI::WebToolkit result that can be returned from a workflow function. This result is used, when the workflow function wants to return a complete page or a part of a page. It gets the following options:

        # ...
        return $wtk->output(1, 'info...', $html, 'text/html');

If the mimetype is not explicitly given, text/html is used.

followup()

This method returns a valid CGI::WebToolkit result that can be returned from a workflow function. Its used when a workflow function wants to silently hand over control flow to another workflow function. It gets the following options:

        # ...
        return $wtk->followup('group.function', @args);

getparam()

The getparam() method is used to return a parameter, match it against a regular expression or return a default value if no parameter was set in neither POST nor GET vars.

Parameters:

1 $name = The name of the parameter.
2 $default = The default value.
3 $regex = The regular expression used to check the parameter value.

fail()

fail() dies hard with a short message. All control flow comes to an end immedietly. This method can be used when some error occurs and there is no hope of recovery. Since only the raw message will be shown to the user, you should consider creating a complete error page, if that is possible.

Example:

        fail("cannot open file 'xyz'");

Shortcut Syntax for calling module functions

Businesss logic usually goes into modules that are loaded using the -modules switch, s.a. But when you call a subroutine from such a module, e.g. inside a workflow function, its usually a unhandy, for example:

        CGI::WebToolkit::Modules::MyProjectModule::my_tiny_function($wtk, @args);

Plus, in most cases, you have to pass the CGI::WebToolkit instance to the subroutine, because you somehow need it in there.

To make life easier, there is an alternative, shorter way of calling a module subroutine:

        _my_tiny_function(@args);

That means exactly the same as the call above. To be honest, it means almost the same. Whereas with the long syntax, you exactly know which module is used, whereas with the short syntax, CGI::WebToolkit will try one module after another until it has found the subroutine named my_tiny_function and then calls it.

Methods for session management

Session data is data that is stored on the server side and can be accessed throughout several webpage requests. In the CGI::WebToolkit the session is a flat hash of arbitrary data that is stored in the database or a flat file.

In order to identify the session, a session id is used. This session id has to be submitted by the client for each request, either through a cookie, a POST variable or a GET variable. The name of this variable is usually sid, but can be configured. CGI::WebToolkit takes automaticly care of that the session id is submitted via links and form submits to the application script.

To group certain session data entries, usually a dot-based notation is used, e.g. my_workflow.my_name. This method is stronlgy recommended as well as limiting the amount of session entries to the absolute minimum.

When the CGI::WebToolkit instance is created, the session is loaded automaticly and can be accessed through the following methods:

get()

        my $value = $wtk->get( "my_name" );

set()

        $wtk->set( "my_name", "value" );

unset()

This method removes the entry from the session. When you try to retrieve the entry's value afterwards, you will get an undefined value.

        $wtk->set( "my_name" );

Methods for template management

By definition a template is something that contains placeholders that are replaced by actual values when the template is filled. In CGI::WebToolkit a template is a function that returns any kind of string, usually that would be XHTML or XML.

fill()

The fill() method fills a hash of data into a template by replacing the placeholders inside the template with actual values. Here are some examples for fill() calls:

        my $name1 = 'group.subgroup.name';
        my $name2 = 'othername';
        
        my $hash = { 'title' => "...", -info => "..." };
        
        my $string1 = $wtk->fill( $name1,   $hash );
        my $string2 = $wtk->fill( $name2, [ $hash, $hash ] );

The first parameter to the fill() method is the name of the template. See below for details on template names.

The second parameter to the fill() method is the hash with the actual information. The values inside the hash are (usually) all strings. If an array (reference) of multiple hashs is supplied, the template is loaded for each hash and the final result is the concatenation of the filled templates.

When the information is filled into the template, each placeholder is replaced with the value of the key of the same name, e.g. the hash key info provides the value for the placeholder info. Inside the template the placeholders are usually noted inside curly brackets. Here is an example of a template:

        <h1>{title}</h1>
        <p>{info}</p>

The hash keys can be noted in any case with an optional dash at the beginning in order to allow easy notation, e.g. the hash keys Info, -info, -InFO etc. refer to the same placeholder info.

Template names and themes

In CGI::WebToolkit Themes are sets of templates. Each theme has its own subdirectory inside the templates directory. These theme directories can contain more subdirectories to group templates semantically.

Here some examples fof valid names and the corresponding template files, assuming the template fallback is set to myproject as the first theme and core as the second theme:

        my $name1 = 'page';      # "<private-path>/templates/myproject/page.html"
        my $name2 = 'form.text'; # "<private-path>/templates/core/form/text.html"

Each dot in the name is converted to a slash to form the final template filename. Then the fallback theme directories are sequentially checked for a file with that name and the first match is used as the template file.

If you want to load the template from a specific theme, you can use the following syntax:

        # This will load from the "core" theme
        my $string1 = $wtk->fill( 'core:form.date', @data );
        
        # This will load from the theme that is first
        # defined inside the fallback hierarchy of themes
        my $string2 = $wtk->fill( 'form.date', @data );

Generator functions

If no template could be found using the theme fallbacks declared in the configuration (s.a.), then a generator function of that name is called.

[...]

Common template placeholders

The following placeholders are always available and can therefor be used in any template loaded with the fill() method:

{script_url}

The URL of the script executable, including the script name.

{public_url}

The URL of the public directory.

{clear}

The special XHTML snippet <div class="clear"></div>

{do_nothing_url}

This URL can be used in Links that should do nothing. It containts the Javascript snippet javascript:void(1);

{javascript_url}

This URL points to the special core workflow function core.combine.javascript and therefor points to a combined javascript.

{css_url}

This URL points to the special core workflow function core.combine.css and therefor points to a combined stylesheet.

Default placeholder values

Any placeholders in a template that have not been filled, are by default replaced with an empty string. Sometimes you want to have a special default value instead of the empty string. Example:

        <b>{title:This is the default title}</b>

Simplified calling of fill()

To make it visually clearer what template is filled, there is an alternative way of calling the fill() method. The following statements mean the same:

        my $string = $wtk->fill('form.date', {-month => '...', -year => '...'});
        
        my $string = $wtk->FormDate(-month => '...', -year => '...');

Using the general method of using the functional style of invocation, this even gets shorter:

        my $string = FormDate(-month => '...', -year => '...');

In case you want the template from a specific theme, use this syntax for the functional invocation:

        my $string = Core_FormDate(-month => '...', -year => '...');

Template Macros

Template macros are a way of loading templates from within templates.

For example, if you want to create a general piece of markup that is considered "a box", you wish you were able to keep this markup in one place and use it from anywhere else in other templates. That is what these macros are for.

Example of a box markup:

     <div class="box">
        <h1>{title}</h1>
        <p>{content}</p>
     </div>

This markup goes in a file called templates/my_project/box.html (in our example).

And here is how to use the box markup inside another template:

        <h1>Hallo!</h2>
        <box title="My Box">In the box.</box>

The macro processor allows recursive notation of macros, so that the following works as expected:

        <box>In the <box>other box</box> box.</box>

Methods for database access

To access the data that is stored inside the attached (relational) database, CGI::WebToolkit offers some handy functions. When the CGI::WebToolkit instance is created, all information regarding the database connection is given and CGI::WebToolkit will try to establish a connection.

Internally, a DBI instance is created, so any kind of database can be used for which a DBI driver is provided.

Any fieldname noted below can consist of the field's name only or addiontally the tablename, e.g. myfield and mytable.myfield are both valid field names.

find()

To retrieve records from the database, use the select() method:

        my $query = $wtk->find(
          -tables       => [qw(mytable1 mytable2 ...)],
          -where                => { name => "...", ... },
          -wherelike    => {...},
          -group                => [qw(id name ...)],
          -order                => [qw(id name ...)],
          -limit                => 10,
          -distinct     => 1,
          -columns              => [qw(id name ...)],
          -joins                => { name => name, ... },
          -sortdir              => 'asc', # or 'desc'
        );

To access the records of the result set, use the normal DBI methods:

        my $array = $query->fetchrow_arrayref();
        my $hash = $query->fetchrow_hashref();
        while (my $record = $query->fetchrow_arrayref()) {
                # ...
        }
        # ...

create()

To insert a record, use the create() method:

        my $id = $wtk->create(
          -table => "...",
          -row => { name => "...", ... },
        );

update()

To update fields in a record, use the update() method:

        my $success = $wtk->update(
          -table => "...",
          -set => { name => "...", ... },
          -where => { ... },
          -wherelike => { ... },
        );

remove()

To delete records, use the remove() method:

        my $query = $wtk->remove(
          -table => "...",
          -where => { ... },
          -wherelike => { ... },                        
        );

query()

Any kind of other query can be executed using the query() method:

        my $query = $wtk->query( $sql );

load()

This method is used to import a text file that contains a number of records into a certain table in the database. This is nice, if you set up many databases for an application and want to insert some default data all at once.

Example:

        load( 'my_project', 'default_data', 'my_table' );

This example will load the file data/my_project/default_data.txt from the configured private directory into the database table named "my_table" (in the configured database).

The data file must be in a certain format. Here is an example:

        [1]
        name:Mr.X
        age:23

        [2]
        name:Mr.Y
        age:56
        bio.
                Born in 1980, Mr.Y
                was the first to invent
                the toaster.

        [3]
        name:Mrs.Y
        age:25

Each data file can contain zero or more records, each of which starts with a line containing the id of the record in brackets. Each line after that contains a field value, which starts with the field name followed by a colon (":"), followed by the field value up to the end of line (without the newline).

If a field value contains newlines, the fieldname must be followed by a dot (instead of a colon) and the following lines are considered the value of the field. The field value lines must contain a space character (space or horizontal tab) at the line start, which identifies them as field value lines but is ignored.

Due to the format, certain restrictions apply to data that is stored in data files:

1 The table must have a column named id.
2 Field names are not allowed to contain colons, dots or newline characters.

When inserted in the database, CGI::WebToolkit checks first, if a certain row with that id already exists. If so, nothing happens. In almost all cases you do not want to have your data in the database be overwritten by data from data files.

Empty lines in data files and space characters before the colon are completely ignored.

Methods for user and rights management

login()

The login() method associates the current session with a user, aka it logs the user in. The method takes two parameters, the username and the password:

        $wtk->login( $username, $password );

To work, the -usertable option has to be configured (s.a.).

After logging in, the user's data is available in the session under the name user.

The method returns 1 on success and 0 on failure.

logout()

This method removes any associated user from the current session, aka logt the user out.

allowed()

This method checks for a given workflow function name if the given user (or the current session user) is allowed to execute the workflow function.

It gets two parameter, first the workflow function name and second optionally the user name, which defaults to the currently logged in user, if any. If no user is logged in and no username is given as second parameter, then the special username guest is used. allowed() returns 1 if the user is allowed to execute the workflow function, 0 therwise.

In order to determine, if the user has access, the access configuration file is loaded from accessconfigs/ in the private directory. This file maps workflow function names to access function names. The appropriate access function is then executed and tells CGI::WebToolkit if the user should be given access.

The access functions are stored in accesschecks/ in the private directory and are raw subroutine bodies. When called, the special variable $wtk is magically available, which is the CGI::WebToolkit instance. The subroutine body should return either 1 or 0. Additionally the workflow function name and the username that are beeing checked are passed to the subroutine body as first and second parameter in the array @args.

Here is an example of such an access check subroutine body that would go inside a file in accesschecks in the private directory:

        my ($wf, $username) = @args;
        if ($username eq 'root') {
          return 1;
        }
        elsif ($wf eq 'my_project.public.home') {
          return 1;
        }
        else {
          return 0;
        }

The format of an access configuration file is the same as of a normal config file. Here is an example:

        .*: my_project.general_rights
        admin\..*: my_project.admin_rights

This file says in the first line that for all workflow functions matching ".*" the access check function named my_project.general_rights will be consulted. For all workflow functions matching "admin\..*" (means literally: in the group of "admin") the access check function named my_project.admin_rights should be consulted.

If a workflow function name is matching more than one entry in an access configuration file, all of the matching entries are processed and all of them have to grant the user access, to finally grant access.

Methods for Internationalization

_()

The _() (underscore-method) is used to translate a phrase into the current or any other language. It will consult the dictionary and return the translated string. If no translation could be found, the phrase itself is returned.

The _() takes two parameters: the phrase (string) and optionally a language name, e.g. en or en_GB etc. The language names should conform to the international naming conventions though this is not required.

Example:

        my $text_de = _('I like to walk around the block', 'de');
        my $text_in_current_language =
                _('Hello, World!');

If you need to call the _() method from within a template, just use the core generator t, as in this example:

        <t>Hello</t>, dude!
        <t lang="de_DE">Hello</t>, dude!

This is equivalent to the following calls within perl:

        _('Hello');
        _('Hello', 'de_DE');

lang()

The lang() method is used to set and retrieve the language of the current session. If passed a parameter, this will be the new current session language. The current session language is always returned.

translate()

translate() is used to translate a phrase to another language. The most common syntax is that of a sequence of pairs, with the language identifier as the key and the phrase as value. The first pair is used as the key phrase to be stored in the database phrase table.

        translate( <language> => <phrase>, ... );

Example:

        translate(
          'en_GB' => 'Hello!',
          'de_DE' => 'Hallo!',
          # you can add even more pairs
        );

Methods for form file upload

upload()

This method will save a file that was transferred via a normal form submit inside a normal file form field:

        upload('attachment','my_project_uploads');

To make this example work, the form beeing submitted must contain a file field named attachment and there must be a writeable directory uploads/my_project_uploads/ in the public directory.

The file is loaded from the parameter, saved inside the directory uploads/my_project_uploads/ in the public directory and if everything goes well, a new entry like the following is added to the session variable array named uploads, s.a.:

        {
                'success'  => 1/0,
                'info'     => '...',
                
                # optional fields if upload save was successful
                'path'     => '...',
                'filename' => '...',
                'created'  => '...',
                'mimetype' => '',
                
                'original_filename' => '',
        }

The field info contains all the information from the method CGI::uploadInfo(), including the actual filename that was selected in the form field and the content type.

To retrieve this information about the latest upload, you can simply do the following:

        my $info = $wtk->get()->[-1];

Other methods

logmsg()

logmsg() writes a text message to a logfile. It takes the message as first parameter and optionally a priority, which can be one of DEBUG (default), INFO, WARNING, ERROR or FATAL.

The logfiles are kept in the private path under logs. For each priority and workflow function there is a separate logfile that can then be inspected for debugging or other reasons.

Predefined template functions

There are a lot of predefined generator functions, which are located in the core subdirectory of the generators directory.

Fileformats

Configuration file format

The configuration files have a format that allows for grouping configuration values into separate namespaces. An example configuration file that can be parsed is:

        # comment
        name: value
        name2 : value

Template file format

An example template file:

        <h1>{title}</h1>
        <p>{content}</p>
        <i>{author:Default Value}</i>

EXPORT

None by default.

SEE ALSO

CGI::WebToolkit::Tutorial

Other modules are worth a look, including CGI, CGI::App and many more. Use the search on cpan.org to find alternatives to the CGI::WebToolkit.

There is no website for this module.

If you have any questions, hints or something else to say, please mail to tokirc@gmx.net or post in the comp.lang.perl.modules mailing list- thank you for helping make CGI::WebToolkit better!

AUTHOR

Tom Kirchner, tokirc@gmx.net

COPYRIGHT AND LICENSE

Copyright (C) 2009 by Tom Kirchner

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.6 or, at your option, any later version of Perl 5 you may have available.