Developers FAQ

This document is intended as a very brief introduction to some common ExSite programming tasks, and some typical methods that are used to address them. The solutions given are generally the simplest of those available, but by no means the only possible way to go about it.

Basics:

  1. What headers/preambles should be included in ExSite scripts and modules?

Plug-ins:

  1. What's the difference between a web application, a plug-in, and a DCD (dynamic content driver)?
  2. How do I create a plug-in?
  3. How do I install a plug-in?
  4. How does my plug-in know what page/website it is being invoked from?
  5. How can I access the main database from my plug-in?
  6. How do I link back to my plug-in, with different parameters?
  7. How do I make a form that will be processed by my plug-in?
  8. How do I control my plug-in's behaviour?
  9. How do I make a special icon for my plug-in?

Authentication:

  1. How do I know if the user is logged in?
  2. How do I know what level of priveleges the user has?
  3. How can I fetch the user's info?

Database:

  1. How do I connect to the main database?
  2. How do I fetch a database record?
  3. How do I fetch groups of records?
  4. How do I search for records?
  5. How do I add a new record?
  6. How do I edit an existing record?
  7. How do I delete records?
  8. How do I count records?
  9. How do I give the user the ability to do all of the above through a web interface?
  10. How do I connect to an alternate database?
  11. Can I use DBI to talk to the database?

Web Forms:

  1. How can I make and process a form to add a new record into a table?
  2. How can I make and process a form to edit an existing record?
  3. How can I make several simultaneous inserts/updates from a single form?
  4. How can I make and process a multi-screen "wizard" form?
  5. How can I make these auto-generated forms prettier?

Kernel Hacking:

  1. What if I don't want to use MySQL?
  2. What if I want to override some default system behaviour?

Error Handling and Debugging:

  1. How can I view system errors?
  2. How do I throw errors?
  3. How do I debug a script or plug-in?
  4. Are there any profiling tools?

What headers/preambles should be included in ExSite scripts and modules?

Everything should do this to pick up the basic system configuration:

use ExSite::Config;

ExSite kernel modules should either inherit from some existing kernel module, or from ExSite::Base.

use ExSite::Base;
use vars qw(@ISA);
@ISA = qw(ExSite::Base);

ExSite plug-in modules should inherit from ExSite::BaseDCD.

use Modules::BaseDCD;
use vars qw(@ISA);
@ISA = qw(Modules::BaseDCD);

CGI programs typically follow this format:

use ExSite::Config;
use ExSite::Form;

&exsite_init;
my $db = new ExSite::Form;    # create a database handle
&page_header("page title");   # optional, outputs admin screen head HTML

# ... your program code ...

&page_footer;                 # optional, outputs admin screen foot HTML
&exsite_close;

What's the difference between a web application, a plug-in, and a DCD (dynamic content driver)?

No difference. We tend to call them DCDs on the developer side, plug-ins on the IT side, and web applications on the end-user side.

How do I create a plug-in?

The plug-in is a Perl module that conforms to the ExSite DCD API. It must be an object-oriented class module that supports the following entry-point methods: read(), write(), and ioctl(). See the Web Application Developer's Guide for more details. Examples can be found in /cgi/Modules.

How do I install a plug-in?

In the simple case, place your Perl DCD module in the /cgi/Modules directory. ExSite will find it and automatically query it to determine its capabilities.

If your plug-in has any static images or other files that need to be served to clients, those ought to be placed in /html/_Modules/[YourModule].

If your plug-in has its own configuration file, it should go in /cgi/conf/[YourModule].conf.

If your plug-in modifies/extends the standard database, those modifications have to be made manually. Changes to the database map must be made directly to the files in /cgi/dbmap.

How does my plug-in know what page/website it is being invoked from?

The page that is currently being built is stored in $share{Page}. This is an ExSite::Page object. To fetch the current page record data:

$share{Page}->get();

To fetch the current page ID:

$share{Page}->id();

To fetch the current website [section] record:

$share{Page}->get("section");

To fetch the current website [section] ID:

$share{Page}->id("section");

How can I access the main database from my plug-in?

The page-building code opens up a database connection, which is stored in $share{DB} as an ExSite::Form object. This database handle can be shared by any plug-in that needs access to the main content database.

How do I link back to my plug-in, with different parameters?

The ExSite::Misc::relink method generates URLs to recursively link back to yourself, optionally with modified parameters:

my $url = &relink();                              # link back to same page
my $url = &relink(param1=>$value);                # ditto, with a new parameter
my $url = &relink(param1=>undef);                 # ditto, clearing a parameter
my $url = &relink(param1=>$value, param2=>undef); # ditto, setting one and clearing another parameter

The ExSite::BaseDCD::link method does much the same thing, but with extra logic for plug-ins to cross-link to special service pages. See the Web Application Developer's Guide for more information.

How do I make a form that will be processed by my plug-in?

The form action should be a page that invokes the plugin. If the plug-in generated the form, then it should submit the form back to itself, perhaps with some optional extra arguments to alert the plug-in of what it is expected to do:

if ($this->{input}{cmd} eq "process_form") {
    # we seem to have some form data
    ...
}
else {
    # prompt for the form data
    my $form_action = &relink(cmd=>"process_form");
    return "<form method=\"post\" action=\"$form_action\"> ... </form>\n";
}

How do I control my plug-in's behaviour?

There are several sources of input data that a plug-in can draw from to make decisions:

  1. GET, POST input

    So that all plug-ins and other systems can share POST input, ExSite normally pre-reads HTTP input (QUERY_STRING and POST data, but not cookies) into the {input} attribute of the plug-in object. The inputs are merged, with QUERY_STRING data overriding POST data in the event of a clash. (This behaviour is inherited from ExSite::BaseDCD if not overridden.)

    Alternate logic can be used by invoking the ExSite::Input class directly, eg.:

    my $in = new ExSite::Input;
    my $post = $in->post;    # hash ref to decoded post data
    my $query = $in->query;  # hash ref to decoded query string data
    my $input = $in->post_or_query;  # hash ref to post data, if it exists, query data otherwise
    my $input = $in->query_or_post;  # hash ref to query data, if it exists, post data otherwise
    my $input = $in->combine;  # hash ref to combined post and query data
    

    The read() method of the plug-in usually handles the internal logic of managing the plug-in's input, including cookies, and any other input sources that are relevant.

  2. plug-in's CMS tag arguments

    When a plug-in is invoked from a CMS tag using a syntax like <!--&Module(options)-->, the value of options is passed to the plug-in's write() method when the plug-in is called. This options string is often formatted as a URL-encoded query string, so that you can feed a default query through to the plug-in when no other query has been specified.

    Depending on how you code your plug-in, these parameters can be used as defaults that regular web inputs can override, or they can be used as fixed values that web inputs cannot override, eg:

    sub write {
        my ($this, $options) = @_;
    
        # URL-parse the options string
        my %param = &DecodeString($options);
    
        # the options string takes precedence over user input
        my $action = $param{action} || $this->{input}{action};
    
        # OR... user input can override the options string
        my $action = $this->{input}{action} || $param{action};
    
  3. %share hash

    The %share hash acts like a shared memory area for different plug-ins and ExSite subsystems to share data with each other. All ExSite systems and plug-ins have equal access to %share.

How do I make a special icon for my plug-in?

Make a 64 X 64 GIF or PNG graphic, and place it in /html/_Modules/[myModule]/icon.(gif|png).


How do I know if the user is logged in?

ExSite will not display a members-only page if the member is not logged in. In this case, your plug-in will never be called. In other cases, this logic works:

if ($share{DB}->authorize) {
    # the user is logged in

}
else {
    # the user is not logged in, or has been stripped of access permissions

}

How do I know what level of priveleges the user has?

my $access_level = $share{DB}->authorize;  # returns numeric access level
my $admin = $share{DB}->is_admin;          # returns true if system admin

The numeric access level is an integer whose meaning in the default configuration is:

  1. not logged in/general public
  2. regular site user, allowed in members-only sections
  3. limited-access site administrator
  4. unlimited-access system administrator

There are additional privelege-testing methods in the ExSite::Auth package to test access to plug-ins, websites, etc.

How can I fetch the user's info?

$user_ref = $share{DB}->my_user_record;

This returns a hash of user attributes (representing a record from the member table).


How do I connect to the main database?

If a connection has already been opened, the database handle will be stored in $share{DB}, and you can just use that.

my $db = $share{DB};           # typically an ExSite::Form object

If you are writing your own CGI program and need to make your own connection, use one of:

my $db = new ExSite::Form;     # inherits form handling tools

my $db = new ExSite::Report;   # inherits data reporting tools

my $db = new ExSite::DB;       # database-independent access methods only

my $db = new ExSite::SQL;      # SQL access methods

These are listed from highest level down to lowest. The higher level cases inherit the lower level ones, so typically we work with ExSite::Form objects to get all behaviour. (You will also need to use/require whichever module you choose.)

How do I fetch a database record?

Data is fetched as a datahash, ie. a hash of column names => column values.

my $data_ref = $db->fetch($table_name,$record_id);
my %data     = $db->fetch($table_name,$record_id);   # also works

How do I fetch groups of records?

Data is fetched as an array of datahashes.

To fetch all (!) records in a table:

my @recs = $db->fetch_all($table);

To fetch all child records that reference a given parent record:

my @children = $db->fetch_child($child_table,$parent_table,$parent_record_id);

To fetch all records that match a set of column values:

my @match = $db->fetch_match($table,$reference_to_match_hash);

The match hash contains the columns and their values that you want to select for. For SQL databases, you can include SQL wildcards, eg.:

my @match = $db->fetch_match($table, { column1 => "match this exactly",
                                       column2 => "%contains this%" } );

You can also use a scalar as the target variable, and an array reference will be returned instead of an array.

Optional sort and limit parameters may be added to the above calls. See the ExSite::DB docs for more info.

Other more complicated query methods are available (see ExSite::DB, in particular the fetch_m2m, and select methods, which allow for joins). If none of the standard queries are suitable, you can always issue a custom SQL query:

my @match = $db->custom_query("select * from my_table where ivalue < 25");

For custom queries, the programmer is responsible for sanity and taint-checking their data. ExSite only performs its own query security checks if the standard fetch methods are used.

You can use a scalar as the target variable in the above calls, and an array reference will be returned instead.

How do I search for records?

See previous question.

How do I add a new record?

my $new_record_id = $db->insert($table,$datahash);

$new_record_id should return as non-zero if the insert was successful.

How do I update an existing record?

If $datahash is a reference to a datahash of the existing record's data, including its primary key, then this is sufficient to update the one record:

my $stat = $db->update($table,$datahash);

Bulk updates are done by including a match hash to select the records to update:

my $stat = $db->update($table,$datahash,$matchhash);

In both cases, $stat will contain an error message if there were any problems.

How do I delete records?

my $stat = $db->delete_key($table,$record_id);  # delete a single record
my $stat = $db->delete($table,$match_hash);     # delete all matching records

In both cases, $stat will contain an error message if there were any problems.

How do I count records?

You could use $db->fetch_all and just check the size of the resulting array, but that would be slow (and potentially a memory pig). This is more efficient:

my $n = $db->count($table,$match_hash);

The match hash is optional.

How do I give the user the ability to do all of the above through a web interface?

A web-driven front-end to all these functions is provided by the WebDB plug-in. This code can be invoked directly, or used as a model. The webdb.cgi program also provides this functionality through the admin interface.

How do I connect to an alternate database?

However you like. If you choose to co-opt the ExSite database handling tools, you can create your own ExSite::Form, ExSite::Report, ExSite::DB, or ExSite::SQL object with your own connection parameters, and then do as you please with it. Be sure to include the parameter save=>0 in your connection parameters (the saved database handle is used for ExSite's core content management, so you don't want to override that).

If you have your own database handling procedures, then your plug-in is reponsible for its own database management code. ExSite uses the Perl DBI toolkit for talking to MySQL, so these tools are already loaded and available.

Can I use DBI to talk to the database?

Yes. The ExSite MySQL driver provides a simplified set of access methods to DBI. You can access the DBI object itself using $db->{dbh}.


How can I make and process a form to add a new record into a table?

$db->make(table=>$table_name);                   # blank form
$db->make(table=>$table_name,data=>$datahash);   # pre-filled with defaults

The form includes the necessary callbacks to process itself and then return to the original referring page. Additional features are documented in the ExSite::Form::make() routine.

How can I make and process a form to edit an existing record?

$db->make(table=>$table_name,record=>$record_id);

The form includes the necessary callbacks to process itself and then return to the original referring page. Additional features are documented in the ExSite::Form::make() routine.

How can I make several simultaneous inserts/updates from a single form?

There is a heap of logic in ExSite::Form for auto-processing forms for multiple inserts, multiple deletes, and multiple relational inserts (eg. insert a parent record, and then insert children of that record, with the foreign keys appropriately set). The possibilities are quite rich, so consult the code documentation for ExSite::Form::make().

How can I make and process a multi-screen "wizard" form?

See the ExSite::Wizard module for more information.

How can I make these auto-generated forms prettier?

There are %config settings that can be used to alter form layouts in small ways.

There are a number of CSS classes that are used in form building, which can be used to change appearances and styles of these forms.

More significant changes to the positioning of inputs requires that you copy the HTML and reformat it manually.


What if I don't want to use MySQL?

Replace the ExSite::SQL.pm driver with your alternate SQL driver. ExSite uses the DBI toolkit for interfacing with SQL database engines, so an alternate SQL driver will probably be quite similar to the MySQL driver.

Alternatively, write your own database driver (which does not have to have anything to do with SQL). It must support the following ExSite DB methods:

Follow the examples in ExSite::SQL (MySQL driver) and ExSite::Text (tab-delimited text driver).

What if I want to override some default system behaviour?

See the ExSite Kernel Programming Guide for an introduction to kernel configuration and installing handlers.


How can I view system errors?

Diagnostics (errors, warnings, and info messages) are logged into the data objects, and can be extracted as follows (using the $db data object as an example):

$db->errorcheck($type);            # returns true if there were any messages
$db->fetch_diagnostics($type);     # returns an array of messages
$db->show_diagnostics($type);      # returns a formatted report of messages

$type is "error", "warn", or "info".

If diagnostic logging is enabled, diagnostics may also be written to the log files (by default db.log, cms.log, and diag.log). Diagnostic logs can be viewed using the exsite_log.cgi program.

How do I throw errors?

Gracefully, we hope ;-) Log your errors in the affected ExSite object (use your database object if not sure) as follows:

$db->error("serious error message");
$db->warn("minor error that was worked around or ignored");
$db->info("informational/progress message");

To abort a DCD, simply return undef, empty string, or an error message.

CGI scripts can crash/abort/die/croak in any way they see fit. If there is any chance that the script may run in a mod_perl environment, more care ought to be taken, however.

How do I debug a script or plug-in?

You can use the regular Perl debugger. There is a wrapper script called cgi that roughly simulates the webserver environment (including logins and cookies), so that you can pass a URL and enter the debugger (or pass the results to any other program). Usage is something like:

cgi 'ctrl-panel.cgi/MyModule?query_data' [program_name|debug]
cgi 'page.cgi?_id=123&other_query_data' [program_name|debug]

The last argument is a program name to process the output, or "debug" to enter the Perl debugger.

Note that plug-ins are dynamically loaded, and will not be accessible from the start-up namespace in the debugger. To set a breakpoint in a plug-in, do something like this:

main::(page.cgi:50):	&exsite_init;
  DB<1> require Modules::MyModule

  DB<2> b Modules::MyModule::write

  (or DB<2> b Modules::MyModule::ctrl_panel)

  DB<3> c

Are there any profiling tools?

Only very crude ones. A good way to measure how hard your database is getting hit is to enable diagnostic logging in the exsite.conf file, and set your log levels to 3, which will log all errors, warnings, and informational messages. Then perform your site operation. The logs will record a large number of events, including every SQL query issued by the database driver, which is a good way to catch redundant and inefficient data lookups.

Leaving your log levels at 3 for an extended period is a good way to fill up your available disk space.