File Coverage

blib/lib/CAM/App.pm
Criterion Covered Total %
statement 138 272 50.7
branch 40 134 29.8
condition 20 60 33.3
subroutine 23 37 62.1
pod 23 25 92.0
total 244 528 46.2


line stmt bran cond sub pod time code
1             package CAM::App;
2              
3             =head1 NAME
4              
5             CAM::App - Web database application framework
6              
7             =head1 LICENSE
8              
9             Copyright 2005 Clotho Advanced Media, Inc.,
10              
11             This library is free software; you can redistribute it and/or modify it
12             under the same terms as Perl itself.
13              
14             =head1 SYNOPSIS
15              
16             You can either directly instantiate this module, or create a subclass,
17             creating overridden methods as needed.
18              
19             Direct use:
20              
21             use CAM::App;
22             require "Config.pm"; # user-edited config hash
23            
24             my $app = CAM::App->new(Config->new(), CGI->new());
25             $app->authenticate() or $app->error("Login failed");
26            
27             my $tmpl = $app->template("message.tmpl");
28             my $ans = $app->getCGI()->param('ans');
29             if (!$ans) {
30             $tmpl->addParams(msg => "What is your favorite color?");
31             } elsif ($ans eq "blue") {
32             $tmpl->addParams(msg => "Very good.");
33             } else {
34             $tmpl->addParams(msg => "AIIEEEEE!");
35             }
36             $tmpl->print();
37              
38             Subclass: (then use just like above, replacing CAM::App with my::App)
39              
40             package my::App;
41             use CAM::App;
42             @ISA = qw(CAM::App);
43            
44             sub init {
45             my $self = shift;
46            
47             my $basedir = "..";
48             $self->{config}->{cgidir} = ".";
49             $self->{config}->{basedir} = $basedir;
50             $self->{config}->{htmldir} = "$basedir/html";
51             $self->{config}->{templatedir} = "$basedir/tmpls";
52             $self->{config}->{libdir} = "$basedir/lib";
53             $self->{config}->{sqldir} = "$basedir/lib/sql";
54             $self->{config}->{error_template} = "error_tmpl.html";
55            
56             $self->addDB("App", "live", "dbi:mysql:database=app", "me", "mypass");
57             $self->addDB("App", "dev", "dbi:mysql:database=appdev", "me", "mypass");
58            
59             return $self->SUPER::init();
60             }
61            
62             sub authenticate {
63             my $self = shift;
64             return(($self->getCGI()->param('passwd') || "") eq "secret");
65             }
66            
67             sub selectDB {
68             my ($self, $params) = @_;
69             my $key = $self->{config}->{myURL} =~ m,^http://dev\.foo\.com/, ?
70             "dev" : "live";
71             return @{$params->{$key}};
72             }
73              
74             =head1 DESCRIPTION
75              
76             CAM::App is a framework for web-based, database-driven applications.
77             This package abstracts away a lot of the tedious interaction with the
78             application configuration state. It is quite generic, and is designed
79             to be subclassed with more specific functions overriding its behavior.
80              
81             =cut
82              
83             #--------------------------------#
84              
85             require 5.005_62;
86 1     1   71563 use strict;
  1         3  
  1         36  
87 1     1   5 use warnings;
  1         3  
  1         31  
88 1     1   16 use File::Spec;
  1         1  
  1         22  
89 1     1   4 use Carp;
  1         3  
  1         102  
90 1     1   1998 use CGI;
  1         27415  
  1         8  
91              
92             ## These are loaded on-demand below, if they are not already loaded.
93             ## Please keep this list up to date!
94             #use DBI;
95             #use CAM::Template;
96             #use CAM::EmailTemplate;
97             #use CAM::EmailTemplate::SMTP;
98             #use CAM::Template::Cache;
99             #use CAM::Session;
100              
101             # The following modules may loaded externally, if at all. This module
102             # detects their presence by looking for their $VERSION variables.
103             # CGI::Compress::Gzip
104             # CAM::Session
105             # CAM::SQLManager
106             # CAM::Template::Cache
107              
108             our @ISA = qw();
109             our $VERSION = '1.07';
110              
111             ### Package globals
112             our %global_dbh_cache = (); # used to hold DBH objects created by this package
113              
114             #--------------------------------#
115              
116             =head1 CONFIGURATION
117              
118             CAM::App relies on a few configuration variables set externally to
119             achieve full functionality. All of the following are optional, and
120             the descriptions below explain what will happen if they are not
121             present. The following settings may be used:
122              
123             =over 2
124              
125             =item cookiename (default 'session')
126              
127             =item sessiontime (default unlimited)
128              
129             =item sessiontable (default 'session')
130              
131             These three are all used for session tracking via CAM::Session. New
132             sessions are created with the getSession() method. The C can
133             be any alphanumeric string. The C is the duration of the
134             cookie in seconds. The C is the name of a MySQL table
135             which will store the session data. The structure of this latter table
136             is described in CAM::Session. The session tracking requires a
137             database connection (see the database config parameters)
138              
139             =item dbistr
140              
141             =item dbhost
142              
143             =item dbport
144              
145             =item dbname
146              
147             =item dbusername
148              
149             =item dbpassword
150              
151             Parameters used to open a database connection. Either C or
152             C, C and C are used, but not both. If
153             C is present, it is used verbatim. Otherwise the C is
154             constructed as either
155             C (the host and
156             port clauses are omitted if the corresponding variables are not
157             present in the configuration). If dbpassword is missing, it is
158             assumed to be the empty string ("").
159              
160             An alternative database registration scheme is described in the
161             addDB() method below.
162              
163             =item mailhost
164              
165             If this config variable is set, then all EmailTemplate messages will
166             go out via SMTP through this host. If not set, EmailTemplate will use
167             the C program on the host computer to send the message.
168              
169             =item templatedir
170              
171             The directory where CAM::Template and its subclasses look for template
172             files. If not specified and the template files are not in the current
173             directory, all of the getTemplate() methods will trigger errors.
174              
175             =item sqldir
176              
177             The directory where CAM::SQLManager should look for SQL XML files.
178             Without it, CAM::SQLManager will not find its XML files.
179              
180             =item error_template
181              
182             The name of a file in the C directory. This template is
183             used in the error() method (see below for more details).
184              
185             =item sessionclass
186              
187             The Perl package to use for session instantiation. The default is
188             CAM::Session. CAM::App is closely tied to CAM::Session, so only a
189             CAM::Session subclass will likely function here.
190              
191             =back
192              
193             =cut
194              
195             #--------------------------------#
196              
197             =head1 FUNCTIONS
198              
199             =over 4
200              
201             =cut
202              
203             #--------------------------------#
204              
205             =item new [config => CONFIGURATION], [cgi => CGI], [dbi => DBI], [session => SESSION]
206              
207             Create a new application instance. The configuration object must be a
208             hash reference (blessed or unblessed, it doesn't matter). Included in
209             this distibution is the example/SampleConfig.pm module that shows what
210             sort of config data should be passed to this constructor. Otherwise,
211             you can apply configuration parameters by subclassing and overriding
212             the constructor.
213              
214             Optional objects will be accepted as arguments; otherwise they will be
215             created as needed. If you pass an argument with value undef, that
216             will be interpreted as meaning that you don't want the object
217             auto-created. For example, C will cause a CGI object to be
218             created, C $cgi)> will use the passed CGI object, and
219             C undef)> will not create use CGI object at all. The
220             latter is useful where the creation of a CGI object may be
221             destructive, for example in a SOAP::Lite environment.
222              
223             =cut
224              
225             sub new
226             {
227 1     1 1 33 my $pkg = shift;
228 1         4 my %params = (@_);
229              
230 1         6 my $self = bless({
231             dbparams => {},
232             status => [],
233             }, $pkg);
234 1         5 $self->applyDBH(); # clear any cached values
235              
236 1         3 foreach my $key (qw(cgi dbh session config))
237             {
238 4 100       11 $self->{$key} = $params{$key} if (exists $params{$key});
239             }
240 1 50       5 if (!$self->{config})
241             {
242 1         3 $self->{config} = {};
243             }
244 1         7 $self->init();
245 1         5 return $self;
246             }
247              
248             #--------------------------------#
249              
250             =item init
251              
252             After an object is constructed, this method is called. Subclasses may
253             want to override this method to apply tweaks before calling the
254             superclass initializer. An example:
255              
256             sub init {
257             my $self = shift;
258             $self->{config}->{sqldir} = "../lib/sql";
259             return $self->SUPER::init();
260             }
261              
262             This init function does the following:
263              
264             * Sets up some of the basic configuration parameters
265             (myURL, fullURL, cgidir, cgiurl)
266              
267             * Creates a new CGI object if one does not exist (as per getCGI)
268              
269             * Sets up the DBH object if one exists
270              
271             * Tells CAM::SQLManager where the sqldir is located if possible
272              
273             =cut
274              
275             sub init
276             {
277 1     1 1 3 my $self = shift;
278              
279 1         2 my $cfg = $self->{config}; # shorthand
280              
281             #$SIG{__DIE__} = sub {$self->{dying}=1;$self->error(@_)};
282              
283             ## Initialize session package
284 1   50     9 $cfg->{sessionclass} ||= "CAM::Session";
285              
286             ## Initialize CGI
287 1         5 $self->getCGI(); # initialize CGI if possible/appropriate
288              
289             ## Initialize myURL
290 1 50       22 if (!exists $cfg->{myURL})
291             {
292 1         16 $cfg->{myURL} = CGI->url();
293             }
294 1 50 33     7839 if (!exists $cfg->{fullURL} && $self->getCGI())
295             {
296             # For file uploads, the self_url call generates a
297             # "Use of uninitialized value at (eval 29) line 8."
298             # error because of a bug in CGI v2.46.
299             # Block this by turning off warnings for this line.
300 1     1   368 no warnings;
  1         2  
  1         66  
301 0         0 $cfg->{fullURL} = $self->getCGI()->self_url();
302 1     1   5 use warnings;
  1         3  
  1         4854  
303             }
304              
305             ## Initialize cgiurl
306 1 50 33     9 if ($cfg->{myURL} && (!exists $cfg->{cgiurl}))
307             {
308             # Truncate the filename from the URL
309 1         8 ($cfg->{cgiurl} = $cfg->{myURL}) =~ s,/[^/\\]*$,,;
310             }
311              
312             ## Initialize cgidir
313 1 50       4 if (!exists $cfg->{cgidir})
314             {
315 1         4 $cfg->{cgidir} = $self->computeDir();
316             }
317              
318             ## Initialize DBH
319 1 50       5 if ($self->{dbh})
320             {
321             # Note that unlike getDBH(), the DBH is NOT cached in this case.
322             # This is the correct behavior. Since the calling script handed
323             # us the DBH, it's assumed that the caller will handle any
324             # caching
325              
326 0         0 $self->applyDBH();
327             }
328              
329             ## Initialize sqldir
330 1 50 33     4 if ($CAM::SQLManager::VERSION && $self->{config}->{sqldir})
331             {
332 0         0 CAM::SQLManager->setDirectory($self->{config}->{sqldir});
333             }
334              
335 1         2 return $self;
336             }
337             #--------------------------------#
338              
339             =item computeDir
340              
341             Returns the directory in which this CGI script is located. This can
342             be a class or instance method.
343              
344             =cut
345              
346             sub computeDir
347             {
348 1     1 1 3 my $pkg_or_self = shift;
349              
350 1         2 my $cgidir;
351 1 50       9 if ($ENV{SCRIPT_FILENAME})
    50          
    50          
352             {
353 0         0 ($cgidir = $ENV{SCRIPT_FILENAME}) =~ s,/[^/\\]*$,,;
354             }
355             elsif ($ENV{PATH_TRANSLATED})
356             {
357 0         0 $cgidir = $ENV{PATH_TRANSLATED};
358             }
359             elsif ($ENV{PWD})
360             {
361             # Append the calling path (if any) to the PWD
362 1 50       5 if ($0 =~ /(.*)[\/\\]/)
363             {
364 0         0 my $execpath = $1;
365 0 0       0 if ($execpath =~ m,^[/\\],)
366             {
367 0         0 $cgidir = $execpath;
368             }
369             else
370             {
371 0         0 $cgidir = File::Spec->catdir($ENV{PWD}, $execpath);
372             }
373             }
374             else
375             {
376 1         3 $cgidir = $ENV{PWD};
377             }
378             }
379             # Fix odd cases, like a script called from "./myscript" or "../myscript
380 1 50       4 if ($cgidir)
381             {
382 1         3 $cgidir =~ s,/[^/]+/\.\.,,g; # remove "/dir/.."
383 1         3 $cgidir =~ s,\\[^\\]+\\\.\.,,g; # remove "\dir\.."
384 1         3 $cgidir =~ s,/\./,/,g; # change "path/./path" to "path/path"
385 1         2 $cgidir =~ s,\\\.\\,\\,g; # change "path\.\path" to "path\path"
386 1         4 $cgidir =~ s,/\.$,,g; # change "path/." to "path"
387 1         3 $cgidir =~ s,\\\.$,,g; # change "path\." to "path"
388 1         2 $cgidir =~ s,//+$,/,g; # change "path///path" to "path/path"
389 1         3 $cgidir =~ s,\\\\+$,\\,g; # change "path\\\path" to "path\path"
390             }
391 1         4 return $cgidir;
392             }
393             #--------------------------------#
394              
395             =item authenticate
396              
397             Test the login information, if any. Currently no tests are performed
398             -- this is a no-op. Subclasses may override this method to test login
399             credentials. Even though it's currently trivial, subclass methods
400             should alway include the line:
401              
402             return undef if (!$self->SUPER::authenticate());
403              
404             In case the parent authenticate() method adds a test in the future.
405              
406             =cut
407              
408             sub authenticate {
409 0     0 1 0 my $self = shift;
410              
411             # No checks
412              
413 0         0 return $self;
414             }
415              
416             #--------------------------------#
417              
418             =item header
419              
420             Compose and return a CGI header, including the CAM::Session cookie, if
421             applicable (i.e. if getSession() has been called first). Returns the
422             empty string if the header has already been printed.
423              
424             =cut
425              
426             sub header {
427 2     2 1 5 my $self = shift;
428              
429 2         5 my $cgi = $self->getCGI();
430 2 50       7 if (!$cgi)
    0          
431             {
432 2 100       6 if (!$self->{header_printed})
433             {
434 1         3 $self->{header_printed} = 1;
435 1         7 return "Content-Type: text/html\n\n";
436             }
437             else
438             {
439 1         5 return "";
440             }
441             }
442             elsif (!$cgi->{'.header_printed'})
443             {
444 0 0       0 if ($self->{session})
445             {
446 0         0 return $cgi->header(-cookie => $self->{session}->getCookie(), @_);
447             }
448             else
449             {
450 0         0 return $cgi->header(@_);
451             }
452             }
453             else
454             {
455 0         0 return "";
456             }
457             }
458             #--------------------------------#
459              
460             =item isAllowedHost
461              
462             This function is called from authenticate(). Checks the incoming host
463             and returns false if it should be blocked. Currently no tests are
464             performed -- this is a no-op. Subclasses may override this behavior.
465              
466             =cut
467              
468             sub isAllowedHost {
469 0     0 1 0 my $self = shift;
470              
471             # For now, let any host view the site
472             # Return undef to block access to a host
473 0         0 return $self;
474             }
475             #--------------------------------#
476              
477             =item getConfig
478              
479             Returns the configuration hash.
480              
481             =cut
482              
483             sub getConfig
484             {
485 0     0 1 0 my $self = shift;
486 0         0 return $self->{config};
487             }
488             #--------------------------------#
489              
490             =item getCGI
491              
492             Returns the CGI object. If a CGI object does not exist, one is
493             created. If this application is initialized explicitly like
494             C undef)>, then no new CGI object is created. This
495             behavior is useful for non-CGI applications, like SOAP handlers.
496              
497             CGI::Compress::Gzip is preferred over CGI. The former will be used if
498             it is installed and the client browser supports gzip encoding.
499              
500             =cut
501              
502             sub getCGI
503             {
504 5     5 1 10 my $self = shift;
505 5 50       18 if (!exists $self->{cgi})
506             {
507 0 0 0     0 if ($ENV{HTTP_ACCEPT_ENCODING} && # don't bother unless it's possible
508             $self->loadModule("CGI::Compress::Gzip"))
509             {
510 0         0 $self->{cgi} = CGI::Compress::Gzip->new();
511             }
512             else
513             {
514 0         0 $self->{cgi} = CGI->new();
515             }
516             }
517 5         18 return $self->{cgi};
518             }
519             #--------------------------------#
520              
521             =item getDBH
522              
523             =item getDBH NAME
524              
525             Return a DBI handle. This object is created, if one does not already
526             exist, using the configuration parameters to initialize a DBI object.
527              
528             There are two methods for specifying how to open the database
529             connection: 1) use the C, C, C, C,
530             C, and C configuration variables, is set; 2)
531             use the NAME argument to select from the parameters entered via the
532             addDB() method.
533              
534             The config variables C and C are used, along
535             with either C (if present) or C and C.
536             If no C is specified via config, MySQL is assumed. The DBI
537             handle is cached in the package for future use. This means that under
538             mod_perl, the database connection only needs to be opened once.
539              
540             If NAME is specified, the database definitions entered from addDB()
541             are searched for a matching name. If one is found, the connection is
542             established. If the addDB() call specified multiple options, they are
543             resolved via the selectDB() method, which mey be overridden by
544             subclasses.
545              
546             =cut
547              
548             sub getDBInfo
549             {
550 1     1 0 3 my $self = shift;
551 1         2 my $name = shift; # optional
552              
553 1         2 my $dbistr;
554             my $dbuser;
555 0         0 my $dbpass;
556              
557 1         3 my $cfg = $self->{config}; # shorthand
558            
559 1 50 33     13 if ($name)
    50 33        
560             {
561             # Retrieve parameters for a named handle
562 0         0 my $dbparams = $self->{dbparams}->{$name};
563 0 0       0 if ($dbparams)
564             {
565 0         0 ($dbistr, $dbuser, $dbpass) = $self->selectDB($dbparams);
566             }
567             }
568             elsif (($cfg->{dbistr} || $cfg->{dbname}) && $cfg->{dbusername})
569             {
570 0         0 ($dbistr, $dbuser, $dbpass) = $self->getDBInfoFromCfg($cfg);
571             }
572              
573 1         4 return ($dbistr, $dbuser, $dbpass);
574             }
575              
576             sub getDBInfoFromCfg
577             {
578 0     0 0 0 my $self = shift;
579 0         0 my $cfg = shift;
580              
581             # Get the config parameters for the handle
582             # Use "dbistr" if possible, otherwise use "dbname", "dbhost"
583             # and "dbport"
584            
585 0         0 my $dbistr;
586             my $dbuser;
587 0         0 my $dbpass;
588            
589 0 0       0 if ($cfg)
590             {
591 0         0 $dbistr = $cfg->{dbistr};
592 0 0 0     0 if (!$dbistr && $cfg->{dbname})
593             {
594 0         0 $dbistr = "DBI:mysql:database=".$cfg->{dbname};
595 0 0       0 $dbistr .= ";host=".$cfg->{dbhost} if ($cfg->{dbhost});
596 0 0       0 $dbistr .= ";port=".$cfg->{dbport} if ($cfg->{dbport});
597             }
598 0         0 $dbuser = $cfg->{dbusername};
599 0         0 $dbpass = $cfg->{dbpassword};
600             }
601 0         0 return ($dbistr, $dbuser, $dbpass);
602             }
603              
604             sub getDBH
605             {
606 1     1 1 3 my $self = shift;
607 1         2 my $name = shift; # optional
608              
609 1         3 my $cfg = $self->{config};
610              
611             # Build the DBH if there is no DBH yet, or if the requested one has
612             # a different name from the previous one.
613 1 50 0     8 if ((!exists $self->{dbh}) ||
      0        
      33        
614             ($name && ((!$self->{dbhname}) ||
615             $self->{dbhname} ne $name)))
616             {
617 1         5 my ($dbistr, $dbuser, $dbpass) = $self->getDBInfo($name);
618              
619 1 50       5 if (!$dbistr)
620             {
621             # return undef below
622             }
623             else
624             {
625 0 0       0 if (!$self->loadModule("DBI"))
626             {
627 0         0 $self->error("Internal error: Failed to load the DBI library");
628             }
629            
630             # First try to retrieve a global dbh object, shared between
631             # CAM::App objects, or left over from a previous mod_perl run.
632             # Construct a unique key from the connection parameters
633            
634 0 0       0 $dbpass = "" if (!defined $dbpass); # fix possible undef
635              
636 0   0     0 my $cache_key = ($dbistr .
637             ";username=".($dbuser || "") .
638             ";password=".($dbpass));
639            
640 0 0       0 if ($global_dbh_cache{$cache_key})
641             {
642             #print STDERR "reuse cached dbh for key $cache_key\n";
643 0         0 $self->{dbh} = $global_dbh_cache{$cache_key};
644             }
645             else
646             {
647             #print STDERR "open new dbh as key $cache_key\n";
648 0         0 $self->{dbh} = DBI->connect($dbistr, $dbuser, $dbpass,
649             {autocommit => 0,
650             RaiseError => !$self->{config}->{dbnonfatal}});
651 0 0       0 if (!$self->{dbh})
652             {
653 0 0       0 if (!$self->{config}->{dbnonfatal})
654             {
655 0   0     0 $self->error("Failed to connect to the database: " .
656             ($DBI::errstr || $! || "(unknown error)"));
657             }
658             }
659 0         0 $global_dbh_cache{$cache_key} = $self->{dbh};
660             }
661             }
662 1         4 $self->{dbhname} = $name;
663 1         4 $self->applyDBH();
664             }
665             else
666             {
667             #print STDERR "reuse existing dbh\n";
668             }
669 1         6 return $self->{dbh};
670             }
671              
672             #--------------------------------#
673              
674             =item addDB NAME, LABEL, DBISTR, USERNAME, PASSWORD
675              
676             Add a record to the list of available database connections. The NAME
677             specified here is what you would pass to getDBH() later. The LABEL is
678             used by selectDB(), if necessary, to choose between database options.
679             If multiple entries with the same NAME and LABEL are entered, only the
680             last one is remembered.
681              
682             =cut
683              
684             sub addDB
685             {
686 0     0 1 0 my $self = shift;
687 0         0 my $name = shift;
688 0         0 my $label = shift;
689 0         0 my $dbistr = shift;
690 0         0 my $user = shift;
691 0         0 my $pass = shift;
692              
693 0   0     0 $self->{dbparams}->{$name} ||= {}; # create if missing
694 0         0 $self->{dbparams}->{$name}->{$label} = [$dbistr, $user, $pass];
695 0         0 return $self;
696             }
697             #--------------------------------#
698              
699             =item selectDB DB_PARAMETERS
700              
701             Given a data structure of possible database connection parameters,
702             select one to use for the database. Returns an array with C,
703             C and C values, or an empty array on failure.
704              
705             The incoming data structure is a hash reference where the keys are
706             labels for the various database connection possibilities and the
707             values are array references with three elements: dbistr, dbusername
708             and dbpassword. For example:
709              
710             {
711             live => ["dbi:mysql:database=game", "gameuser", "gameon"],
712             internal => ["dbi:mysql:database=game_int", "gameuser", "gameon"],
713             dev => ["dbi:mysql:database=game_dev", "chris", "pass"],
714             }
715              
716             This default implementation simply picks the first key in alphabetical
717             order. Subclasses will almost certainly want to override this method.
718             For example:
719              
720             sub selectDB {
721             my ($self, $params) = @_;
722             if ($self->getCGI()->url() =~ m,/dev/, && $params->{dev}) {
723             return @{$params->{dev}};
724             } elsif ($self->getCGI()->url() =~ /internal/ && $params->{internal}) {
725             return @{$params->{internal}};
726             } elsif ($params->{live}) {
727             return @{$params->{live}};
728             }
729             return ();
730             }
731              
732             =cut
733              
734             sub selectDB
735             {
736 0     0 1 0 my $self = shift;
737 0         0 my $params = shift;
738              
739             # Find the first key alphabetically, if any
740 0         0 my $key = (sort keys %$params)[0];
741 0 0       0 if ($key)
742             {
743 0         0 return @{$params->{$key}};
  0         0  
744             }
745 0         0 return ();
746             }
747              
748             #--------------------------------#
749              
750             =item applyDBH
751              
752             Tell other packages to use this new DBH object. This method is called
753             from init() and getDBH() as needed. This contacts the following
754             modules, if they are already loaded:
755             CAM::Session, CAM::SQLManager, and CAM::Template::Cache.
756              
757             =cut
758              
759             sub applyDBH
760             {
761 2     2 1 5 my $self = shift;
762              
763 2         9 my $dbh = $self->{dbh};
764 2 50       5 CAM::Session->setDBH($dbh) if ($CAM::Session::VERSION);
765 2 50       6 CAM::SQLManager->setDBH($dbh) if ($CAM::SQLManager::VERSION);
766 2 50       9 CAM::Template::Cache->setDBH($dbh) if ($CAM::Template::Cache::VERSION);
767             }
768             #--------------------------------#
769              
770             =item getSession
771              
772             Return a CAM::Session object for this application. If one has not yet
773             been created, make one now. Note! This must be called before the CGI
774             header is printed, if at all.
775              
776             To use a class other than CAM::Session, set the C config
777             variable.
778              
779             =cut
780              
781             sub getSession
782             {
783 0     0 1 0 my $self = shift;
784 0         0 my $dbname = shift;
785              
786 0 0       0 if (!exists $self->{session})
787             {
788 0         0 my $class = $self->{config}->{sessionclass};
789 0 0       0 if (!$self->loadModule($class))
790             {
791 0         0 $self->error("Internal error: Failed to load the $class library");
792             }
793              
794 0 0       0 if ($self->{config}->{cookiename})
795             {
796 0         0 $class->setCookieName($self->{config}->{cookiename});
797             }
798 0 0       0 if ($self->{config}->{sessiontable})
799             {
800 0         0 $class->setTableName($self->{config}->{sessiontable});
801             }
802 0 0       0 if ($self->{config}->{sessiontime})
803             {
804 0         0 $class->setExpiration($self->{config}->{sessiontime});
805             }
806 0 0       0 if (!$class->getDBH())
807             {
808 0 0       0 if (!$self->getDBH($dbname))
809             {
810 0         0 $self->error("No database connection, so a session could not be recorded");
811             }
812 0         0 $class->setDBH($self->getDBH($dbname));
813             }
814 0         0 $self->{session} = $class->new();
815             }
816 0         0 return $self->{session};
817             }
818             #--------------------------------#
819              
820             =item getTemplate FILE, [KEY => VALUE, KEY => VALUE, ...]
821              
822             Creates, prefills and returns a CAM::Template object. The FILE should
823             be the template filename relative to the template directory specified
824             in the Config file.
825              
826             See the prefillTemplate() method to see which key-value pairs are
827             preset.
828              
829             =cut
830              
831             sub getTemplate {
832 2     2 1 4 my $self = shift;
833 2         4 my $file = shift;
834              
835 2         9 return $self->_template("CAM::Template", $file, undef, @_);
836             }
837             #--------------------------------#
838              
839             =item getTemplateCache CACHEKEY, FILE, [KEY => VALUE, KEY => VALUE, ...]
840              
841             Creates, prefills and returns a CAM::Template::Cache object. The
842             CACHEKEY should be the unique string that identifies the filled
843             template in the database cache.
844              
845             =cut
846              
847             sub getTemplateCache {
848 0     0 1 0 my $self = shift;
849 0         0 my $key = shift;
850 0         0 my $file = shift;
851              
852 0         0 return $self->_template("CAM::Template::Cache", $file, $key, @_);
853             }
854             #--------------------------------#
855              
856             =item getEmailTemplate FILE, [KEY => VALUE, KEY => VALUE, ...]
857              
858             Creates, prefills and returns a CAM::EmailTemplate object. This is
859             very similar to the getTemplate() method.
860              
861             If the 'mailhost' config variable is set, this instead uses
862             CAM::EmailTemplate::SMTP.
863              
864             =cut
865              
866             sub getEmailTemplate {
867 1     1 1 7 my $self = shift;
868 1         3 my $file = shift;
869              
870 1         2 my $module = "CAM::EmailTemplate";
871 1 50       11 if ($self->{config}->{mailhost})
872             {
873 0         0 $module = "CAM::EmailTemplate::SMTP";
874 0 0       0 if (!$self->loadModule($module))
875             {
876 0 0       0 $self->error("Internal error: Failed to load the $module library" .
877             ( $self->{load_error} ? "($$self{load_error})" : "" ));
878             }
879 0         0 CAM::EmailTemplate::SMTP->setHost($self->{config}->{mailhost});
880             }
881 1         5 return $self->_template($module, $file, undef, @_);
882             }
883             #--------------------------------#
884              
885             =item getPkgTemplate PKG, FILE, [KEY => VALUE, KEY => VALUE, ...]
886              
887             Creates, prefills and returns a template instance of the specified
888             class. That class should have a similar API to CAM::Template. For
889             example:
890              
891             my $tmpl = $app->getPkgTemplate("CAM::PDFTemplate", "tmpl.pdf");
892             ...
893             $tmpl->print();
894              
895             =cut
896              
897             sub getPkgTemplate
898             {
899 0     0 1 0 my $self = shift;
900 0         0 my $templatePkg = shift;
901 0         0 my $file = shift;
902              
903 0         0 return $self->_template($templatePkg, $file, undef, @_);
904             }
905             #--------------------------------#
906             # Internal function:
907             # builds, fills and returns a template object
908              
909             sub _template {
910 3     3   6 my $self = shift;
911 3   50     8 my $module = shift || "CAM::Template";
912 3         6 my $file = shift;
913 3         4 my $key = shift;
914              
915 3 50       10 if (!$self->loadModule($module))
916             {
917 0 0       0 $self->error("Internal error: Failed to load the $module library")
918             unless ($self->{in_error});
919             }
920              
921 3         6 my $template;
922 3 50       11 if ($key)
923             {
924             # This is a ::Cache template
925 0         0 $template = $module->new($key, $self->getDBH());
926             }
927             else
928             {
929             # This is a normal template
930 3         17 $template = $module->new();
931             }
932              
933 3 100       108 if (defined $file)
934             {
935 1   50     10 my $dir = $self->{config}->{templatedir} || "";
936 1 50       5 if (defined $dir)
937             {
938 1         2 $dir =~ s,[/\\]$,,; # trim trailing sep char
939             }
940 1 50 33     14 if (!$template->setFilename(defined $dir && $dir ne "" ? File::Spec->catfile($dir, $file) : $file))
    50          
941             {
942 0 0       0 $self->error("Internal error: problem locating the web page template")
943             unless ($self->{in_error});
944             }
945             }
946 3         216 $self->prefillTemplate($template, @_);
947              
948 3         17 return $template;
949             }
950             #--------------------------------#
951              
952             =item prefillTemplate TEMPLATE, [KEY => VALUE, KEY => VALUE, ...]
953              
954             This fills the search-and-replace list of a template with typical
955             values (like the base URL, the URL of the script, etc. Usually, it is
956             just called from withing getTemplate() and related methods, but if you
957             build your own templates you may want to use this explicitly.
958              
959             The following value are set (and the order is significant, since later
960             keys can override earlier ones):
961              
962             - the configuration variables, including:
963             - myURL => URL of the current script
964             - fullURL => URL of the current page, including CGI parameters and target
965             - cgiurl => URL of the directory containing the current script
966             - cgidir => directory containing the current script
967             - many others...
968             - mod_perl => boolean indicating whether the script is in mod_perl mode
969             - anything passed as arguments to this method
970              
971             Subclasses may override this to add more fields to the template. We
972             recommend implementing override methods like this:
973              
974             sub prefillTemplate {
975             my $self = shift;
976             my $template = shift;
977            
978             $self->SUPER::prefillTemplate($template);
979             $template->addParams(
980             myparam => myvalue,
981             # any other key-value pairs or hashes ...
982             @_, # add this LAST to override any earlier params
983             );
984             return $self;
985             }
986              
987             =cut
988              
989             sub prefillTemplate
990             {
991 3     3 1 5 my $self = shift;
992 3         4 my $template = shift;
993              
994 3 50       6 if (!$template->setParams(
  3         28  
995              
996             # you MUST update the documentation above
997             # if you change anything in this list!!!
998              
999             %{$self->{config}},
1000             mod_perl => (exists $ENV{MOD_PERL}),
1001             @_,
1002             ))
1003             {
1004 0 0       0 $self->error("Internal error: problem setting template parameters")
1005             unless ($self->{in_error});
1006             }
1007 3         145 return $self;
1008             }
1009             #--------------------------------#
1010              
1011             =item addStatusMessage MESSAGE
1012              
1013             This is a handy repository for non-fatal status messages accumulated
1014             by the application. [Fatal messages can be handled by the error()
1015             method] Applications who use this mechanism frequently may wish to
1016             override prefillTemplate to set something like:
1017              
1018             status => join("
", $app->getStatusMessages())
1019              
1020             so in template HTML you could, for example, display this via
1021              
1022            
1023             ...
1024             ??status??
::status::
??status??
1025              
1026             =cut
1027              
1028             sub addStatusMessage
1029             {
1030 0     0 1 0 my $self = shift;
1031 0         0 push @{$self->{status}}, join("", @_);
  0         0  
1032 0         0 return $self;
1033             }
1034             #--------------------------------#
1035              
1036             =item getStatusMessages
1037              
1038             Returns the array of messages that had been accumulated by the
1039             application via the addStatusMessage() method.
1040              
1041             =cut
1042              
1043             sub getStatusMessages
1044             {
1045 0     0 1 0 my $self = shift;
1046 0         0 return @{$self->{status}};
  0         0  
1047             }
1048             #--------------------------------#
1049              
1050             =item clearStatusMessages
1051              
1052             Clears the array of messages that had been accumulated by the
1053             application via the addStatusMessage() method.
1054              
1055             =cut
1056              
1057             sub clearStatusMessages
1058             {
1059 0     0 1 0 my $self = shift;
1060 0         0 $self->{status} = [];
1061 0         0 return $self;
1062             }
1063             #--------------------------------#
1064              
1065             =item error MSG
1066              
1067             Prints an error message to the browser and exits.
1068              
1069             If the 'error_template' configuration parameter is set, then that
1070             template is used to display the error. In that case, the error
1071             message will be substituted into the ::error:: template variable.
1072              
1073             For the sake of your error template HTML layout, use these guidelines:
1074              
1075             1) error messages do not end with puncuation
1076             2) error messages might be multiline (with
tags, for example)
1077             3) this function prepares the message for HTML display
1078             (like escaping "<" and ">" for example).
1079              
1080             =cut
1081              
1082             sub error {
1083 0     0 1 0 my $self = shift;
1084 0         0 my $msg = shift;
1085              
1086 0 0       0 if ($self->{cgi})
1087             {
1088 0         0 $msg = $self->{cgi}->escapeHTML($msg);
1089 0         0 $msg =~ s/\n/
\n/gs;
1090             }
1091             else
1092             {
1093 0         0 $msg = "
$msg
";
1094             }
1095              
1096 0 0       0 if ($self->{in_error})
1097             {
1098 0         0 die "Error function called too many times";
1099             }
1100 0         0 $self->{in_error} = 1; # Flag so we don't call error() recursively
1101              
1102 0         0 print $self->header();
1103 0         0 my $tmplFilename = $self->{config}->{error_template};
1104 0         0 my $errTmpl;
1105              
1106 0 0       0 if ($tmplFilename)
1107             {
1108 0         0 $errTmpl = $self->getTemplate($tmplFilename, error => $msg);
1109             }
1110              
1111 0 0       0 if (!$errTmpl)
1112             {
1113 0         0 print $msg,"\n";
1114             }
1115             else
1116             {
1117 0         0 $errTmpl->print();
1118             }
1119              
1120 0 0       0 confess if ($self->{dying});
1121 0         0 delete $self->{in_error};
1122 0         0 exit;
1123             }
1124             #--------------------------------#
1125              
1126             =item loadModule MODULE
1127              
1128             Load a perl module, returning a boolean indicating success or failure.
1129             Shortcuts are taken if the module is already loaded, or loading has
1130             previously failed. This can be called as either a class or an
1131             instance method. If called on an instance, any error messages are
1132             stored in $self->{load_error}.
1133              
1134             =cut
1135              
1136             sub loadModule {
1137 7     7 1 1731 my $pkg_or_self = shift;
1138 7         10 my $module = shift;
1139              
1140             # Get a reference to the module VERSION and ISA variables
1141 7         376 my $ver_ref = eval "\\\$${module}::VERSION";
1142 7         363 my $isa_ref = eval "\\\@${module}::ISA";
1143 7 50       43 delete $pkg_or_self->{load_error} if (ref $pkg_or_self); # clear if it was previously set
1144 7 100 100     36 unless (defined($$ver_ref) || @$isa_ref > 0)
1145             {
1146 3         15 local $SIG{__WARN__} = 'DEFAULT';
1147 3         11 local $SIG{__DIE__} = 'DEFAULT';
1148 1     1   414 eval "use $module;";
  0     1   0  
  0     1   0  
  1         880  
  1         3101  
  1         21  
  1         899  
  1         974  
  1         17  
  3         157  
1149 3 100 33     32 if ($@ || (!defined $$ver_ref && @$isa_ref == 0))
      66        
1150             {
1151 1 50 33     15 $pkg_or_self->{load_error} = "$@" if (ref($pkg_or_self) && $@);
1152             # Set the version to a false-but-defined value to prevent re-eval
1153 1         5 $$ver_ref = 0;
1154             }
1155             }
1156             # Note: this is deliberately not "defined $$ver_ref" unlike above
1157 7   100     40 return $$ver_ref || @$isa_ref;
1158             }
1159             #--------------------------------#
1160              
1161             =item DESTROY
1162              
1163             Override this method to perform any final cleanup when the application
1164             run ends. You can use this, perhaps, to do an logging or
1165             benchmarking. For example:
1166              
1167             package MyApp;
1168             use CAM::App;
1169             our @ISA = qw(CAM::App);
1170            
1171             sub new {
1172             my $pkg = shift;
1173             my $start = time();
1174             my $self = $pkg->SUPER::new(@_);
1175             $self->{start_time} = $start;
1176             return $self;
1177             }
1178             sub DESTROY {
1179             my $self = shift;
1180             my $elapsed = time() - $self->{start_time};
1181             print STDERR "elapsed time: $elapsed seconds\n";
1182             $self->SUPER::DESTROY();
1183             }
1184              
1185             =cut
1186              
1187             sub DESTROY
1188 0     0     {
1189             # do nothing special, just here to silence warnings, and to let
1190             # subclasses override
1191             }
1192              
1193             1;
1194             __END__