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__ |