| 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 |
||||||
| 133 | be any alphanumeric string. The C |
||||||
| 134 | cookie in seconds. The C |
||||||
| 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 |
||||||
| 152 | C |
||||||
| 153 | C |
||||||
| 154 | constructed as either | ||||||
| 155 | C |
||||||
| 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 |
||||||
| 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 |
||||||
| 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 |
||||||
| 218 | created, C |
||||||
| 219 | C |
||||||
| 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 |
||||||
| 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 |
||||||
| 530 | C |
||||||
| 531 | use the NAME argument to select from the parameters entered via the | ||||||
| 532 | addDB() method. | ||||||
| 533 | |||||||
| 534 | The config variables C |
||||||
| 535 | with either C |
||||||
| 536 | If no C |
||||||
| 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 |
||||||
| 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 |
||||||
| 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__ |