NAME

CAM::App - Web database application framework

SYNOPSIS

Directly instantiate this module:

use CAM::App;
require "./Config.pm";  # user-edited config hash

my $app = CAM::App->new(Config->new(), CGI->new());
if (!$app->authenticate()) {
  exit;
}
my $tmpl = $app->template("message.tmpl");
my $ans = $app->{cgi}->param('ans');
if (!$ans) {
   $tmpl->addParams(msg => "What is your favorite color?");
} elsif ($ans eq "blue") {
   $tmpl->addParams(msg => "Very good.");
} else {
   $tmpl->addParams(msg => "AIIEEEEE!");
}
$tmpl->print();

Subclass this module, create overridden methods (then use just like above):

package my::App;
use CAM::App;
@ISA = qw(CAM::App);

sub init {
   my $self = shift;
   $self->{config}->{cgidir} = ".";
   $self->{config}->{basedir} = "..";
   $self->{config}->{htmldir} = "../html";
   $self->{config}->{templatedir} = "../tmpls";
   $self->{config}->{libdir} = "../lib";
   $self->{config}->{sqldir} = "../lib/sql";
   $self->{config}->{error_template} = "error_tmpl.html";

   $self->addDB("App", "live", "dbi:mysql:database=app", "me", "mypass");
   $self->addDB("App", "dev", "dbi:mysql:database=appdev", "me", "mypass");

   return $self->SUPER::init();
}

sub authenticate {
   my $self = shift;
   return(($self->getCGI()->param('passwd') || "") eq "secret");
}

sub selectDB {
   my ($self, $params) = @_;
   my $key = $self->{config}->{myURL} =~ m,^http://dev\.foo\.com/, ? 
       "dev" : "live";
   return @{$params->{$key}};
}

DESCRIPTION

CAM::App is a framework for web-based, database-driven applications. This package abstracts away a lot of the tedious interaction with the application configuration state. It is quite generic, and is designed to be subclassed with more specific functions overriding its behavior.

CONFIGURATION

CAM::App relies on a few configuration variables set externally to achieve full functionality. All of the following are optional, and the descriptions below explain what will happen if they are not present. The following settings may be used:

cookiename (default 'session')
sessiontime (default unlimited)
sessiontable (default 'session')

These three are all used for session tracking via CAM::Session. New sessions are created with the getSession() method. The cookiename can be any alphanumeric string. The sessiontime is the duration of the cookie in seconds. The sessiontable is the name of a MySQL table which will store the session data. The structure of this latter table is described in CAM::Session. The session tracking requires a database connection (see the database config parameters)

dbistr
dbname
dbhost
dbusername
dbpassword

Parameters used to open a database connection. Either dbistr or dbname and dbhost are used, but not both. If dbistr is present, it is used verbatim. Otherwise the dbistr is constructed as either DBI:mysql:database=dbname or DBI:mysql:database=dbname;host=dbhost (the latter if a dbhost is present in the configuration). If dbpassword is missing, it is assumed to be the empty string ("").

An alternative database registration scheme is described in the addDB() method below.

mailhost

If this config variable is set, then all EmailTemplate messages will go out via SMTP through this host. If not set, EmailTemplate will use the sendmail program on the host computer to send the message.

templatedir

The directory where CAM::Template and its subclasses look for template files. If not specified and the template files are not in the current directory, all of the getTemplate() methods will trigger errors.

sqldir

The directory where CAM::SQLManager should look for SQL XML files. Without it, CAM::SQLManager will not find its XML files.

error_template

The name of a file in the templatedir directory. This template is used in the error() method (see below for more details).

FUNCTIONS

new [config => CONFIGURATION], [cgi => CGI], [dbi => DBI], [session => SESSION]

Create a new application instance. The configuration object must be a hash reference (blessed or unblessed, it doesn't matter). Included in this distibution is the example/SampleConfig.pm module that shows what sort of config data should be passed to this constructor. Otherwise, you can apply configuration parameters by subclassing and overriding the constructor.

Optional objects will be accepted as arguments; otherwise they will be created as needed.

init

After an object is constructed, this method is called. Subclasses may want to override this method to apply tweaks before calling the superclass initializer. An example:

sub init {
   my $self = shift;
   $self->{config}->{sqldir} = "../lib/sql";
   return $self->SUPER::init();
}

This init function does the following:

* Sets up some of the basic configuration parameters (myURL, cgidir, cgiurl)

* Creates a new CGI object if one does not exist

* Sets up the DBH object if one exists

* Tells CAM::SQLManager where the sqldir is located if possible

computeDir

Returns the directory in which this CGI script is located. This can be a class or instance method.

authenticate

Test the login information, if any. Currently no tests are performed -- this is a no-op. Subclasses may override this method to test login credentials. Even though it's currently trivial, subclass methods should alway include the line:

return undef if (!$self->SUPER::Authenticate());

In case the parent authenticate() method adds a test in the future.

Compose and return a CGI header, including the CAM::Session cookie, if applicable (i.e. if getSession() has been called first). Returns the empty string if the header has already been printed.

isAllowedHost

This function is called from authenticate(). Checks the incoming host and returns false if it should be blocked. Currently no tests are performed -- this is a no-op. Subclasses may override this behavior.

getConfig

Returns the configuration hash.

getCGI

Returns the CGI object.

getDBH
getDBH NAME

Return a DBI handle. This object is created, if one does not already exist, using the configuration parameters to initialize a DBI object.

There are two methods for specifying how to open the database connection: 1) use the dbistr, dbname, dbhost, dbusername, and dbpassword configuration variables, is set; 2) use the NAME argument to select from the parameters entered via the addDB() method.

The config variables dbusername and dbpassword are used, along with either dbistr (if present) or dbname and dbhost. If no dbistr is specified via config, MySQL is assumed. The DBI handle is cached in the package for future use. This means that under mod_perl, the database connection only needs to be opened once.

If NAME is specified, the database definitions entered from addDB() are searched for a matching name. If one is found, the connection is established. If the addDB() call specified multiple options, they are resolved via the selectDB() method, which mey be overridden by subclasses.

addDB NAME, LABEL, DBISTR, USERNAME, PASSWORD

Add a record to the list of available database connections. The NAME specified here is what you would pass to getDBH() later. The LABEL is used by selectDB(), if necessary, to choose between database options. If multiple entries with the same NAME and LABEL are entered, only the last one is remembered.

selectDB DB_PARAMETERS

Given a data structure of possible database connection parameters, select one to use for the database. Returns an array with dbistr, dbusername and dbpassword values, or an empty array on failure.

The incoming data structure is a hash reference where the keys are labels for the various database connection possibilities and the values are array references with three elements: dbistr, dbusername and dbpassword. For example:

{
   live     => ["dbi:mysql:database=game",     "gameuser", "gameon"],
   internal => ["dbi:mysql:database=game_int", "gameuser", "gameon"],
   dev      => ["dbi:mysql:database=game_dev", "chris", "pass"],
}

This default implementation simply picks the first key in alphabetical order. Subclasses will almost certainly want to override this method. For example:

sub selectDB {
   my ($self, $params) = @_;
   if ($self->getCGI()->url() =~ m,/dev/, && $params->{dev}) {
      return @{$params->{dev}};
   } elsif ($self->getCGI()->url() =~ /internal/ && $params->{internal}) {
      return @{$params->{internal}};
   } elsif ($params->{live}) {
      return @{$params->{live}};
   }
   return ();
}
applyDBH

Tell other packages to use this new DBH object. This method is called from init() and getDBH() as needed. This contacts the following modules, if they are already loaded: CAM::Session, CAM::SQLManager, and CAM::Template::Cache.

getSession

Return a CAM::Session object for this application. If one has not yet been created, make one now. Note! This must be called before the CGI header is printed, if at all.

getTemplate FILE, [KEY => VALUE, KEY => VALUE, ...]

Creates, prefills and returns a CAM::Template object. The FILE should be the template filename relative to the template directory specified in the Config file.

See the prefillTemplate() method to see which key-value pairs are preset.

getTemplateCache CACHEKEY, FILE, [KEY => VALUE, KEY => VALUE, ...]

Creates, prefills and returns a CAM::Template::Cache object. The CACHEKEY should be the unique string that identifies the filled template in the database cache.

getEmailTemplate FILE, [KEY => VALUE, KEY => VALUE, ...]

Creates, prefills and returns a CAM::EmailTemplate object. This is very similar to the template() method.

If the 'mailhost' config variable is set, this instead uses CAM::EmailTemplate::SMTP.

prefillTemplate TEMPLATE, [KEY => VALUE, KEY => VALUE, ...]

This fills the search-and-replace list of a template with typical values (like the base URL, the URL of the script, etc. Usually, it is just called from withing getTemplate() and related methods, but if you build your own templates you may want to use this explicitly.

The following value are set (and the order is significant, since later keys can override earlier ones):

- the configuration variables, including:
   - myURL => URL of the current script
   - cgiurl => URL of the directory containing the current script
   - cgidir => directory containing the current script
- mod_perl => boolean indicating whether the script is in mod_perl mode
- anything passed as arguments to this method

Subclasses may override this to add more fields to the template. We recommend implementing override methods like this:

sub prefillTemplate {
  my $self = shift;
  my $template = shift;
  
  $self->SUPER::prefillTemplate($template);
  $template->addParams(
                       myparam => myvalue,
                       # any other key-value pairs or hashes ...

                       @_,  # add this LAST to override any earlier params
                       );
  return $self;
}
error MSG

Prints an error message to the browser and exits.

If the 'error_template' configuration parameter is set, then that template is used to display the error. In that case, the error message will be substituted into the ::error:: template variable.

For the sake of your error template HTML layout, use these guidelines:

1) error messages do not end with puncuation
2) error messages might be multiline (with <br> tags, for example)
3) this function prepares the message for HTML display 
   (like escaping "<" and ">" for example).
loadModule MODULE

Load a perl module, returning a boolean indicating success or failure. Shortcuts are taken if the module is already loaded, or loading has previously failed.

AUTHOR

Chris Dolan, Clotho Advanced Media, chris@clotho.com