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:
Plug-ins:
Authentication:
Database:
Web Forms:
Kernel Hacking:
Error Handling and Debugging:
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;
If you are developing plug-in web applications, you can develop in a variety of languages. See here for more info. A small amount of Perl may still have to be coded as a glue layer between ExSite and your application. Non-Perl applications will have limited inter-operability with ExSite's low-level database and security services, and may have to use their own toolkits to replace these functions.
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.
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.
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.
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");
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.
The Modules::BaseDCD::link method generates recursive links back to the DCD, optionally with modified parameters. If you are inheriting from Modules::BaseDCD (which you should be), then you can
my $url = $this->link(); # link back to same page my $url = $this->link(param1=>$value); # ditto, with a new parameter my $url = $this->link(param1=>undef); # ditto, clearing a parameter my $url = $this->link(param1=>$value, param2=>undef); # ditto, setting one and clearing another parameter
The parameters refer to the arguments in the URL query string.
The ExSite::Misc::relink method does much the same thing, except that it is insensitive to certain DCD behaviours such as inter-page services, and AJAX linking. See the Web Application Developer's Guide for more information.
The form action should be a page that invokes the plugin. If the plug-in generated the form, then it can 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 = $this->link(cmd=>"process_form"); return "<form method=\"post\" action=\"$form_action\"> ... </form>\n"; }
There are several sources of input data that a plug-in can draw from to make decisions:
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.
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};
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.
Make a 64 X 64 GIF or PNG graphic, and place it in .../html/_Modules/[myModule]/icon.(gif|png).
Create a file help.html and place it in .../html/_Modules/[myModule]/.
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 }
my $access_level = $share{DB}->authorize; # returns numeric access level my $admin = $share{DB}->is_admin; # returns true if system admin my $manager = $share{DB}->is_manager; # returns true if manager of this site my $member = $share{DB}->is_member; # returns true if member of this site
The numeric access level is an integer whose meaning in the default configuration is:
There are additional privelege-testing methods in the ExSite::Auth package to test access to plug-ins, websites, etc.
$name = $share{DB}->my_name; # returns the member's name $login = $share{DB}->my_login; # returns the member's login ID $user_rec = $share{DB}->my_user_record; # returns the full member record
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.)
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
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%" } );
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.
See previous question, especially the part on fetch_match.
my $new_record_id = $db->insert($table,$datahash);
$new_record_id should return as non-zero if the insert was successful.
If $datahash is a reference to a datahash of the existing record's updated 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. $datahash in this case should not reference a specific primary key.
my $stat = $db->update($table,$datahash,$matchhash);
In both cases, $stat will contain an error message if there were any problems.
To move the records to the trash bin, from which they can optionally be recovered, use one of the following calls. (The $auth option is a flag to authorize deletion of records regardless of whether the current user owns them.)
my $stat = $db->trash_key($table,$record_id); # trash a single record my $stat = $db->trash_r($table,$record_id,$auth); # trash record and its descendants
To permanently delete the records (no data recovery possible):
my $stat = $db->delete_key($table,$record_id); # delete a single record my $stat = $db->delete($table,$match_hash); # delete all matching records
In all cases, $stat will contain an error message if there were any problems.
my $n = $db->count($table,$match_hash);
The match hash is optional - leave it off to count all records.
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.
Note that WebDB is just a front-end to the database handling code in the ExSite Kernel. Similar functionality can be built in to any web application, by using the appropriate database management calls. Any web application that interacts with the database can serve as an example of how to write database management functions into your web application.
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.
Yes. The ExSite MySQL driver provides a simplified set of access methods to DBI. You can access the DBI object itself using $share{DB}->{dbh}.
$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.
$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.
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().
See the ExSite::Wizard module for more information.
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.
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).
See the ExSite Kernel Programming Guide for an introduction to kernel configuration and installing handlers.
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.
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.
See the Debugging & Profiling Guide for full details.
See the Debugging & Profiling Guide for full details.