| 1 | |
|---|
| 2 | package Apache::Request::Dispatcher; |
|---|
| 3 | |
|---|
| 4 | use strict; |
|---|
| 5 | use DBI; |
|---|
| 6 | use Template; |
|---|
| 7 | use AppConfig; |
|---|
| 8 | #use Exception qw(:all); |
|---|
| 9 | |
|---|
| 10 | use Apache::Const qw(:common :methods :http); |
|---|
| 11 | use POSIX qw(strftime); |
|---|
| 12 | use Carp qw(cluck confess); |
|---|
| 13 | use Apache::Session::Postgres; |
|---|
| 14 | |
|---|
| 15 | use Data::Dumper; |
|---|
| 16 | |
|---|
| 17 | # TODO: Need to change these to Apache::Request (libapreq) |
|---|
| 18 | use CGI; |
|---|
| 19 | use CGI::Cookie; |
|---|
| 20 | |
|---|
| 21 | our @ISA; |
|---|
| 22 | |
|---|
| 23 | =pod |
|---|
| 24 | =head1 NAME |
|---|
| 25 | |
|---|
| 26 | Apache::Request::Dispatcher - dispatches requests to a sub-class of Apache::Request::Controller |
|---|
| 27 | |
|---|
| 28 | =head1 SYNOPSIS |
|---|
| 29 | |
|---|
| 30 | <Location /myApplication> |
|---|
| 31 | SetHandler perl-script |
|---|
| 32 | Perlhander Apache::Request::Dispatcher |
|---|
| 33 | SetEnv DispatcherConf /path/to/file.cfg |
|---|
| 34 | setEnv APP_NAME myApplication |
|---|
| 35 | </Location> |
|---|
| 36 | |
|---|
| 37 | =head1 DESCRIPTION |
|---|
| 38 | |
|---|
| 39 | Apache::Request::Dispatcher is a mod_perl handler which handles |
|---|
| 40 | Apache HTTP requests under mod_perl, and dispatches them to a |
|---|
| 41 | sub-class of Apache::Request::Controller (after some initial |
|---|
| 42 | request setup has been performed). |
|---|
| 43 | |
|---|
| 44 | If required, it will establish a connection to a database |
|---|
| 45 | using the DBI, retrieve (or create) session data for this |
|---|
| 46 | request (using Apache::Session), initialise a template |
|---|
| 47 | processor (using the Template-Toolkit). |
|---|
| 48 | |
|---|
| 49 | The Dispatcher parses the URI of the request to determine |
|---|
| 50 | which subclass of Apache::Request::Controller to then pass |
|---|
| 51 | control to. |
|---|
| 52 | |
|---|
| 53 | Parsing of the URI occurs as follows: |
|---|
| 54 | |
|---|
| 55 | APP_NAME (environment set in Apache Config) is removed |
|---|
| 56 | from the begining of the URI, so that: |
|---|
| 57 | |
|---|
| 58 | '/myApplication/SubClass/action' |
|---|
| 59 | |
|---|
| 60 | becomes: 'SubClass/action' |
|---|
| 61 | |
|---|
| 62 | or |
|---|
| 63 | |
|---|
| 64 | '/myApplication/Sub/Class/action' |
|---|
| 65 | |
|---|
| 66 | becomes: 'Sub/Class/action' |
|---|
| 67 | |
|---|
| 68 | This is then converted to a module name, and a method name, |
|---|
| 69 | such as: |
|---|
| 70 | |
|---|
| 71 | Apache::Request::Controller::SubClass or |
|---|
| 72 | Apache::Request::Controller::Sub::Class |
|---|
| 73 | |
|---|
| 74 | with action() being the method name. |
|---|
| 75 | |
|---|
| 76 | It should be noted that if the SubClass or Action name |
|---|
| 77 | contain any thing other than [A-Za-z0-9_] then the request |
|---|
| 78 | is declined. |
|---|
| 79 | |
|---|
| 80 | The dispatcher then dynamically inherits from the module name, |
|---|
| 81 | and then calls $self->action(). |
|---|
| 82 | |
|---|
| 83 | The action() method of the controller is then called in an object-oriented |
|---|
| 84 | fashion, with a dispatcher object passed in as its first parameter. |
|---|
| 85 | |
|---|
| 86 | This object contains the following hash elements: |
|---|
| 87 | |
|---|
| 88 | request => $r, # The Apache Request Object |
|---|
| 89 | dbh => $dbh, # The Database Connection Object |
|---|
| 90 | cfg => $cfg, # The AppConfig object |
|---|
| 91 | template => $template, # The Template Processor |
|---|
| 92 | apr => $q, # The CGI/libapreq object |
|---|
| 93 | session => \%session # Any session data for this user |
|---|
| 94 | |
|---|
| 95 | Depending on the configuration file, 'dbh' or 'session' may be |
|---|
| 96 | undefined if they've been turned off. |
|---|
| 97 | |
|---|
| 98 | an example controller method might be written as follows: |
|---|
| 99 | |
|---|
| 100 | package Apache::Request::Controller::SubClass; |
|---|
| 101 | our @ISA = qw(Apache::Request::Controller); |
|---|
| 102 | use strict; |
|---|
| 103 | use Apache::Const qw(:common :methods :http); |
|---|
| 104 | |
|---|
| 105 | sub action |
|---|
| 106 | { |
|---|
| 107 | my $self = shift; |
|---|
| 108 | |
|---|
| 109 | my $thing = getThingByID($self->{'dbh'}, $self->{'apr'}->param('thingId')); |
|---|
| 110 | |
|---|
| 111 | $self->{'request'}->status(HTTP_OK); |
|---|
| 112 | $self->{'request'}->content_type('text/html'); |
|---|
| 113 | |
|---|
| 114 | $self->{'template'}->process('myTemplate', {thing => $thing}); |
|---|
| 115 | return OK; |
|---|
| 116 | } |
|---|
| 117 | 1; |
|---|
| 118 | |
|---|
| 119 | =head2 Special Actions |
|---|
| 120 | |
|---|
| 121 | =over 4 |
|---|
| 122 | |
|---|
| 123 | =item __cache() |
|---|
| 124 | |
|---|
| 125 | Generally, the controllers that are dispatched to will generate dynamic |
|---|
| 126 | content, and as such the dispatcher automatically sets the browsers |
|---|
| 127 | caching policy to not cache any content. However, if your Controller |
|---|
| 128 | sub-class has a method called __cache() then will be used to define |
|---|
| 129 | the caching policy. The action name is given as a parameter to the |
|---|
| 130 | __cache() method, and based on this, the __cache() method should |
|---|
| 131 | return 1 to allow caching, or zero to prevent it. |
|---|
| 132 | |
|---|
| 133 | For exmaple, if your Controller sub-class provides 2 actions, staticContent() |
|---|
| 134 | and dynamicContent(), then your __cache() method can control the caching |
|---|
| 135 | policy as follows: |
|---|
| 136 | |
|---|
| 137 | sub __cache |
|---|
| 138 | { |
|---|
| 139 | my $action = shift; |
|---|
| 140 | |
|---|
| 141 | my $policy = { staticContent => 1, |
|---|
| 142 | dynamicContent => 0}; |
|---|
| 143 | return $policy->{$action} || 0; # Default to no caching. |
|---|
| 144 | } |
|---|
| 145 | |
|---|
| 146 | If your Controller sub-class wants to turn off caching globally, |
|---|
| 147 | then you can just return zero regardless of what the action name is. |
|---|
| 148 | |
|---|
| 149 | =item __index() |
|---|
| 150 | |
|---|
| 151 | If the dispatcher cannot work out which action the request is for |
|---|
| 152 | (this happens on a URI such as '/myApplication/SubClass') then the |
|---|
| 153 | dispatcher checkes to see if the Controller SubClass has a 'default' |
|---|
| 154 | action by calling __index(). If this method does not exist, then |
|---|
| 155 | the request is declined. The __index() method should return the name |
|---|
| 156 | of the default action, such as: |
|---|
| 157 | |
|---|
| 158 | sub __index |
|---|
| 159 | { |
|---|
| 160 | return 'listAllItems'; |
|---|
| 161 | } |
|---|
| 162 | |
|---|
| 163 | =back |
|---|
| 164 | |
|---|
| 165 | =head2 Template Defaults |
|---|
| 166 | |
|---|
| 167 | The template processor has the following defaults defined, and may |
|---|
| 168 | be used by all templates: |
|---|
| 169 | |
|---|
| 170 | =over 4 |
|---|
| 171 | |
|---|
| 172 | =item DisplayNumber() |
|---|
| 173 | |
|---|
| 174 | This template variable is a reference to a subroutine which will add |
|---|
| 175 | comma's in the right place in numbers, for exmaple: |
|---|
| 176 | |
|---|
| 177 | [% DisplayNumber(1000) %] becomes 1,000 |
|---|
| 178 | |
|---|
| 179 | =item DisplayDate() |
|---|
| 180 | |
|---|
| 181 | This template variable is a reference to a subroutine which will take |
|---|
| 182 | a time value (in the form of seconds since the epoch) and display |
|---|
| 183 | the actual date. You may optionally specify an strftime() format: |
|---|
| 184 | |
|---|
| 185 | [% DisplayDate( CURRENT_TIME, '%a %d %B %Y' ) %] |
|---|
| 186 | |
|---|
| 187 | =item DisplayTime() |
|---|
| 188 | |
|---|
| 189 | This template variable is a reference to a subroutine which will |
|---|
| 190 | take a time value (in the form of seconds since the epoch) and |
|---|
| 191 | displays the time-of-day. You may optionally specify an strftime() format: |
|---|
| 192 | |
|---|
| 193 | [% DisplayTime( CURRENT_TIME, '%a %d %B %Y' ) %] |
|---|
| 194 | |
|---|
| 195 | =item DisplayDateTime() |
|---|
| 196 | |
|---|
| 197 | This template variable is a reference to a subroutine which will |
|---|
| 198 | take a time value (in the form of seconds since the epoch) and |
|---|
| 199 | displays the date and time of day |
|---|
| 200 | |
|---|
| 201 | [% DisplayDateTime( CURRENT_TIME ) %] |
|---|
| 202 | |
|---|
| 203 | =item DisplayDuration() |
|---|
| 204 | |
|---|
| 205 | This template variable is a reference to a subroutine which |
|---|
| 206 | will take a number of seconds as input, and output a string |
|---|
| 207 | of the form 'H hours, M minutes and S seconds', eg: |
|---|
| 208 | |
|---|
| 209 | Last Modified: [% DisplayDuration( CURRENT_TIME - LAST_MODIFIED %] |
|---|
| 210 | |
|---|
| 211 | =item APP_NAME |
|---|
| 212 | |
|---|
| 213 | This template variable is a string which represents the application |
|---|
| 214 | name, as defined by the environment variable APP_NAME. |
|---|
| 215 | |
|---|
| 216 | =item REQUEST |
|---|
| 217 | |
|---|
| 218 | This template variable is an Apache2::RequestRec object, so that the |
|---|
| 219 | template can have access to the current URI etc. Its not really meant |
|---|
| 220 | to be used to set any outgoing headers or any thing tho, as setting |
|---|
| 221 | up the response should really be done in the Controller. |
|---|
| 222 | |
|---|
| 223 | =back |
|---|
| 224 | |
|---|
| 225 | =head2 The Configuration File |
|---|
| 226 | |
|---|
| 227 | The dispatcher can be used to dispatch to multiple controllers |
|---|
| 228 | that dont even need to belong to the same application, and each |
|---|
| 229 | can application can have its own database connection and set of |
|---|
| 230 | templates. This is achieved by having Apache specify which |
|---|
| 231 | configuration file to use based on the Location of the request |
|---|
| 232 | URI. For example: |
|---|
| 233 | |
|---|
| 234 | <Location /myApplication> |
|---|
| 235 | SetHandler perl-script |
|---|
| 236 | Perlhander Apache::Request::Dispatcher |
|---|
| 237 | SetEnv DispatcherConf /path/to/file.cfg |
|---|
| 238 | setEnv APP_NAME myApplication |
|---|
| 239 | </Location> |
|---|
| 240 | |
|---|
| 241 | will specify that all requests with a root-URI of /myApplication |
|---|
| 242 | will use the configuration file as specified in the DispatcherConf |
|---|
| 243 | environment variable, which is /path/to/file.cfg in the above |
|---|
| 244 | example. |
|---|
| 245 | |
|---|
| 246 | The contents of this configuration file are as follows: |
|---|
| 247 | |
|---|
| 248 | db_dsn="DBI:Pg:database=myApplication;host=127.0.0.1" |
|---|
| 249 | db_username=apache |
|---|
| 250 | db_password=apache |
|---|
| 251 | |
|---|
| 252 | templatePath="/data/myApplication/templates" |
|---|
| 253 | |
|---|
| 254 | defaultController=WelcomePage |
|---|
| 255 | |
|---|
| 256 | # If you wish to use sessions, uncomment this and make sure you have |
|---|
| 257 | # created the sessions table within the database db_dsn. |
|---|
| 258 | useSessions="Apache::Session::Postgres" |
|---|
| 259 | sessionTable="sessions" |
|---|
| 260 | |
|---|
| 261 | where db_dsn, db_username, and db_password specifies the database connection |
|---|
| 262 | options, templatePath specifies where the templates are all stored, |
|---|
| 263 | and defaultController specifies which controller is the default, so |
|---|
| 264 | that if a request for '/myApplication' is recieved, it will be converted |
|---|
| 265 | to '/myApplication/WelcomePage', which will in turn get converted to |
|---|
| 266 | Apache::Request::Controller::WelcomePage. |
|---|
| 267 | |
|---|
| 268 | At a minimum, you have to have templatePath defined. If you dont specify a |
|---|
| 269 | default Controller, then the top-level URI will not work. |
|---|
| 270 | |
|---|
| 271 | =head1 AUTHOR |
|---|
| 272 | |
|---|
| 273 | Bradley Kite <bradley-cpan@kitefamily.co.uk> |
|---|
| 274 | |
|---|
| 275 | If you wish to email me, then please remove the '-cpan' part |
|---|
| 276 | of my email address as anything addressed to 'bradley-cpan' |
|---|
| 277 | is assumed to be spam and is not read. |
|---|
| 278 | |
|---|
| 279 | =head1 SEE ALSO |
|---|
| 280 | |
|---|
| 281 | L<Apache::Request::Controller>, L<DB::Table>, L<DB::Table::Row>, L<DBI>, L<perl> |
|---|
| 282 | |
|---|
| 283 | =cut |
|---|
| 284 | |
|---|
| 285 | # /* handler */ {{{ |
|---|
| 286 | sub handler |
|---|
| 287 | { |
|---|
| 288 | my $r = shift; |
|---|
| 289 | |
|---|
| 290 | # Validate the URI |
|---|
| 291 | my $uri = $r->uri; |
|---|
| 292 | |
|---|
| 293 | # The app-name is allowed to contain funny characters, so we strip it off |
|---|
| 294 | # before we check its validity. |
|---|
| 295 | my $relativePath = substr($uri, length($r->location) + 1); # +1 for the training slash |
|---|
| 296 | if (($relativePath ne '') && ($relativePath !~ /^[A-Za-z0-9_\/]+$/)) |
|---|
| 297 | { |
|---|
| 298 | return DECLINED; |
|---|
| 299 | } |
|---|
| 300 | |
|---|
| 301 | my $self = init($r); |
|---|
| 302 | |
|---|
| 303 | # Auth Levels: none (default), mixed (anon users allowed), login (must login) |
|---|
| 304 | if ($self->{'cfg'}->authLevel eq 'login') |
|---|
| 305 | { |
|---|
| 306 | # Force the user to login |
|---|
| 307 | unless ($self->{'session'}->{'auth'}->{'username'}) |
|---|
| 308 | { |
|---|
| 309 | my $authController = join('/', $self->{'request'}->location, $self->{'cfg'}->authController); |
|---|
| 310 | $self->{'request'}->headers_out->set(Location => $authController); |
|---|
| 311 | return HTTP_MOVED_TEMPORARILY; |
|---|
| 312 | } |
|---|
| 313 | } |
|---|
| 314 | |
|---|
| 315 | # Start parsing the URI |
|---|
| 316 | my @bits = split(/\/+/, $relativePath); |
|---|
| 317 | |
|---|
| 318 | my $webPrefix = 'Apache::Request::Controller'; |
|---|
| 319 | my $method = pop @bits; |
|---|
| 320 | my $pkgName = join('::', $webPrefix, @bits); |
|---|
| 321 | |
|---|
| 322 | # Prevent access to the session table. |
|---|
| 323 | if ($self->{'cfg'}->useSessions && $pkgName eq "Apache::Request::Controller::" . ucfirst($self->{'cfg'}->sessionTable)) |
|---|
| 324 | { |
|---|
| 325 | return DECLINED; |
|---|
| 326 | } |
|---|
| 327 | |
|---|
| 328 | # Show the "Index" page of the given component. |
|---|
| 329 | if ($pkgName eq $webPrefix) |
|---|
| 330 | { |
|---|
| 331 | $pkgName .= '::' . $method; |
|---|
| 332 | $method = ''; |
|---|
| 333 | } |
|---|
| 334 | |
|---|
| 335 | if ($pkgName eq 'Apache::Request::Controller::' || $pkgName eq 'Apache::Request::Controller') |
|---|
| 336 | { |
|---|
| 337 | if ($self->{'cfg'}->defaultController) |
|---|
| 338 | { |
|---|
| 339 | unless ($uri =~ /\/$/) |
|---|
| 340 | { |
|---|
| 341 | $uri .= '/'; |
|---|
| 342 | } |
|---|
| 343 | $uri .= $self->{'cfg'}->defaultController; |
|---|
| 344 | #$r->uri($uri); |
|---|
| 345 | $r->headers_out->set(Location => $uri); |
|---|
| 346 | return HTTP_MOVED_TEMPORARILY; |
|---|
| 347 | } |
|---|
| 348 | } |
|---|
| 349 | |
|---|
| 350 | if ($method =~ /^\_/) |
|---|
| 351 | { |
|---|
| 352 | warn("Cannot dispatch to $pkgName\-\>$method (actions cannot start with a leading underscore)"); |
|---|
| 353 | return DECLINED; |
|---|
| 354 | } |
|---|
| 355 | |
|---|
| 356 | # my $ret = try { |
|---|
| 357 | unshift @ISA, ($pkgName); |
|---|
| 358 | my $return; |
|---|
| 359 | eval { |
|---|
| 360 | if ($method eq '') |
|---|
| 361 | { |
|---|
| 362 | unless ($uri =~ /\/$/) |
|---|
| 363 | { |
|---|
| 364 | $uri .= '/'; |
|---|
| 365 | } |
|---|
| 366 | if ($self->can('__index')) |
|---|
| 367 | { |
|---|
| 368 | $uri .= $self->__index(); |
|---|
| 369 | $r->uri($uri); |
|---|
| 370 | $r->headers_out->set(Location => $uri); |
|---|
| 371 | $return = HTTP_MOVED_TEMPORARILY; |
|---|
| 372 | } |
|---|
| 373 | else |
|---|
| 374 | { |
|---|
| 375 | warn("Cannot figure out the default method of $pkgName. Please write an __index() method which returns it"); |
|---|
| 376 | $return = DECLINED; |
|---|
| 377 | } |
|---|
| 378 | } |
|---|
| 379 | elsif ($self->can($method)) |
|---|
| 380 | { |
|---|
| 381 | if ($self->can('__cache')) |
|---|
| 382 | { |
|---|
| 383 | eval "\$r->no_cache((${pkgName}::__cache}(\$method) ? 0 : 1));"; |
|---|
| 384 | confess($@) if ($@); |
|---|
| 385 | } |
|---|
| 386 | else |
|---|
| 387 | { |
|---|
| 388 | $r->no_cache(1); |
|---|
| 389 | } |
|---|
| 390 | $return = $self->$method(); |
|---|
| 391 | } |
|---|
| 392 | else |
|---|
| 393 | { |
|---|
| 394 | warn("Cannot find $pkgName->$method, declining request"); |
|---|
| 395 | $return = DECLINED; |
|---|
| 396 | } |
|---|
| 397 | }; |
|---|
| 398 | if ($@) |
|---|
| 399 | { |
|---|
| 400 | my $err = sprintf('Cannot dispatch to %s->%s (%s)', |
|---|
| 401 | $pkgName, $method, $@); |
|---|
| 402 | warn($err); |
|---|
| 403 | # Exception->new('other')->raise($err); |
|---|
| 404 | $self->{'dbh'}->rollback; |
|---|
| 405 | return HTTP_INTERNAL_SERVER_ERROR; |
|---|
| 406 | } |
|---|
| 407 | if (($return == OK || $return == HTTP_MOVED_TEMPORARILY) && $self->{'request'}->is_initial_req()) |
|---|
| 408 | { |
|---|
| 409 | # We only commit if we're the "main" (initial) request (and if every thing is OK, of course) |
|---|
| 410 | $self->{'session'}->{'_mtime'} = time(); |
|---|
| 411 | $self->{'dbh'}->commit(); |
|---|
| 412 | } |
|---|
| 413 | elsif ($return != OK && $return != HTTP_MOVED_TEMPORARILY) |
|---|
| 414 | { |
|---|
| 415 | # We roll-back any time. This is so that if the main request works but then |
|---|
| 416 | # makes a sub-request which fails, then the subrequest will roll-back the main |
|---|
| 417 | # requests actions too. This is because the sub-request will present some sort |
|---|
| 418 | # of error to the user, leading them to think that what they just did failed. |
|---|
| 419 | $self->{'dbh'}->rollback; |
|---|
| 420 | } |
|---|
| 421 | return $return; |
|---|
| 422 | # } |
|---|
| 423 | # when 'template', |
|---|
| 424 | # except |
|---|
| 425 | # { |
|---|
| 426 | # # If a template error occurs, then the http header etc. would |
|---|
| 427 | # # have already have been sent. |
|---|
| 428 | # my $err = shift; |
|---|
| 429 | # $err->confess; |
|---|
| 430 | # $self->{'template'}->process('error.tt2'. {ERROR => $err->stringify}) |
|---|
| 431 | # or do { print $err->stringify }; |
|---|
| 432 | # return OK; |
|---|
| 433 | # } |
|---|
| 434 | # when 'not_found', |
|---|
| 435 | # except |
|---|
| 436 | # { |
|---|
| 437 | # my $err = shift; |
|---|
| 438 | # $err->confess; |
|---|
| 439 | # return DECLINED; |
|---|
| 440 | # } |
|---|
| 441 | # when 'other', |
|---|
| 442 | # except |
|---|
| 443 | # { |
|---|
| 444 | # my $err = shift; |
|---|
| 445 | # $err->confess; |
|---|
| 446 | # return HTTP_INTERNAL_SERVER_ERROR; |
|---|
| 447 | # } |
|---|
| 448 | # finally |
|---|
| 449 | # { |
|---|
| 450 | # my $err = shift; |
|---|
| 451 | # my $retCode = shift; |
|---|
| 452 | |
|---|
| 453 | # if ($err) |
|---|
| 454 | # { |
|---|
| 455 | # $self->{'dbh'}->rollback; |
|---|
| 456 | # } |
|---|
| 457 | # else |
|---|
| 458 | # { |
|---|
| 459 | # $self->{'dbh'}->commit; |
|---|
| 460 | # } |
|---|
| 461 | # return $retCode; |
|---|
| 462 | # }; |
|---|
| 463 | # |
|---|
| 464 | # return $ret; |
|---|
| 465 | } |
|---|
| 466 | # /* handler */ }}} |
|---|
| 467 | |
|---|
| 468 | # /* init */ {{{ |
|---|
| 469 | sub init |
|---|
| 470 | { |
|---|
| 471 | my $r = shift; |
|---|
| 472 | |
|---|
| 473 | # Do some initial setup config. |
|---|
| 474 | |
|---|
| 475 | @ISA = (); |
|---|
| 476 | |
|---|
| 477 | my $q = new CGI; |
|---|
| 478 | |
|---|
| 479 | # TODO: Perhaps cache the config files using the key ($ENV{'DispatcherConf'}) into a |
|---|
| 480 | # global hash |
|---|
| 481 | my ($cfg, $dbh, $template); |
|---|
| 482 | unless ($cfg) |
|---|
| 483 | { |
|---|
| 484 | $cfg = AppConfig->new(qw(db_dsn=s db_username=s db_password=s |
|---|
| 485 | templatePath=s defaultController=s |
|---|
| 486 | useSessions=s sessionTable=s |
|---|
| 487 | session_dsn=s session_username=s session_password=s |
|---|
| 488 | authLevel=s authController=s)); |
|---|
| 489 | $cfg->file($ENV{'DispatcherConf'}) || die "Could not open config: $ENV{DispatcherConf}: $!"; |
|---|
| 490 | } |
|---|
| 491 | |
|---|
| 492 | if ($cfg->db_dsn) |
|---|
| 493 | { |
|---|
| 494 | $dbh = DBI->connect($cfg->db_dsn, $cfg->db_username, $cfg->db_password, |
|---|
| 495 | {AutoCommit => 0, |
|---|
| 496 | RaiseError => 0}); |
|---|
| 497 | unless ($dbh) |
|---|
| 498 | { |
|---|
| 499 | confess(sprintf("Could not connect to database %s: %s", |
|---|
| 500 | $cfg->db_dsn, |
|---|
| 501 | $DBI::errstr)); |
|---|
| 502 | } |
|---|
| 503 | } |
|---|
| 504 | |
|---|
| 505 | my %session; |
|---|
| 506 | if ($cfg->useSessions) |
|---|
| 507 | { |
|---|
| 508 | my $cookies = CGI::Cookie->fetch($r) || {}; |
|---|
| 509 | my $cookie = $cookies->{'session_id'}; |
|---|
| 510 | |
|---|
| 511 | $cfg->sessionTable('sessions') unless ($cfg->sessionTable); |
|---|
| 512 | my $opts = {Handle => $dbh, Commit => 1, TableName => $cfg->sessionTable}; |
|---|
| 513 | |
|---|
| 514 | if ($cookie) |
|---|
| 515 | { |
|---|
| 516 | my $cookieValue = $cookie->value; |
|---|
| 517 | tie %session, $cfg->useSessions, $cookieValue, $opts; |
|---|
| 518 | } |
|---|
| 519 | else |
|---|
| 520 | { |
|---|
| 521 | tie %session, $cfg->useSessions, undef, $opts; |
|---|
| 522 | } |
|---|
| 523 | # TODO: Add some extra stuff to the config file to determine cookie |
|---|
| 524 | # options, such as expire time. |
|---|
| 525 | $cookie = $q->cookie('-name' => 'session_id', |
|---|
| 526 | '-value' => $session{'_session_id'}, |
|---|
| 527 | #'-expires' => '+1d', Per-Session is all thats needed. |
|---|
| 528 | '-path' => $r->location . '/', |
|---|
| 529 | '-domain' => $r->hostname, |
|---|
| 530 | '-secure' => 0); |
|---|
| 531 | $r->headers_out->set('Set-Cookie' => $cookie->as_string); |
|---|
| 532 | } |
|---|
| 533 | |
|---|
| 534 | my $templatePath = $cfg->templatePath || '/data/templates'; |
|---|
| 535 | $template ||= new Template(INCLUDE_PATH => $templatePath, |
|---|
| 536 | RECURSION => 1, |
|---|
| 537 | PRE_DEFINE => { DisplayNumber => \&T_DisplayNumber, |
|---|
| 538 | DisplayDate => \&T_DisplayDate, |
|---|
| 539 | DisplayTime => \&T_DisplayTime, |
|---|
| 540 | DisplayDateTime => \&T_DisplayDateTime, |
|---|
| 541 | DisplayDuration => \&T_DisplayDuration, |
|---|
| 542 | REQUEST => $r, |
|---|
| 543 | SESSION => \%session, |
|---|
| 544 | CFG => $cfg}); |
|---|
| 545 | my $self = {request => $r, |
|---|
| 546 | dbh => $dbh, |
|---|
| 547 | cfg => $cfg, |
|---|
| 548 | template => $template, |
|---|
| 549 | apr => $q, |
|---|
| 550 | session => \%session}; |
|---|
| 551 | bless ($self, __PACKAGE__); |
|---|
| 552 | |
|---|
| 553 | return $self; |
|---|
| 554 | } |
|---|
| 555 | # /* init */ }}} |
|---|
| 556 | |
|---|
| 557 | # /* Template Display Functions */ {{{ |
|---|
| 558 | sub T_DisplayNumber |
|---|
| 559 | { |
|---|
| 560 | my $number = reverse(shift); |
|---|
| 561 | $number =~ s/(\d\d\d)(?!$)/$1\,/g; |
|---|
| 562 | return scalar(reverse($number)); |
|---|
| 563 | } |
|---|
| 564 | |
|---|
| 565 | sub T_DisplayDateTime |
|---|
| 566 | { |
|---|
| 567 | my $time = shift; |
|---|
| 568 | |
|---|
| 569 | return strftime('%a %d %B %T %Y', gmtime($time)); |
|---|
| 570 | } |
|---|
| 571 | |
|---|
| 572 | sub T_DisplayTime |
|---|
| 573 | { |
|---|
| 574 | my $time = shift; |
|---|
| 575 | my $format = shift || '%a %d %B %Y'; |
|---|
| 576 | |
|---|
| 577 | return strftime($format, gmtime($time)); |
|---|
| 578 | } |
|---|
| 579 | |
|---|
| 580 | sub T_DisplayDate |
|---|
| 581 | { |
|---|
| 582 | my $time = shift; |
|---|
| 583 | my $format = shift || '%a %d %B %Y'; |
|---|
| 584 | |
|---|
| 585 | return strftime($format, gmtime($time)); |
|---|
| 586 | } |
|---|
| 587 | |
|---|
| 588 | sub T_DisplayDuration |
|---|
| 589 | { |
|---|
| 590 | my $duration = shift; # number of seconds |
|---|
| 591 | |
|---|
| 592 | my @bits = ({ NAME => 'Seconds', |
|---|
| 593 | NEXT => 60}, |
|---|
| 594 | { NAME => 'Minutes', |
|---|
| 595 | NEXT => 60 }, |
|---|
| 596 | { NAME => 'Hours', |
|---|
| 597 | NEXT => '24' }, |
|---|
| 598 | { NAME => 'Days', |
|---|
| 599 | NEXT => 7 }, |
|---|
| 600 | { NAME => 'Weeks' }); |
|---|
| 601 | my @results; |
|---|
| 602 | do |
|---|
| 603 | { |
|---|
| 604 | my $remainder = ($duration % $bits[0]->{'NEXT'}); |
|---|
| 605 | unshift @results, sprintf("%d %s", $remainder, |
|---|
| 606 | $bits[0]->{'NAME'}) if ($remainder > 0); |
|---|
| 607 | $duration -= $remainder; |
|---|
| 608 | $duration /= $bits[0]->{'NEXT'}; |
|---|
| 609 | shift @bits; |
|---|
| 610 | } |
|---|
| 611 | while (defined ($bits[0]) && defined ($bits[0]->{'NEXT'})); |
|---|
| 612 | |
|---|
| 613 | return join(' ', @results); |
|---|
| 614 | } |
|---|
| 615 | # /* Template Display Functions */ }}} |
|---|
| 616 | |
|---|
| 617 | 1; |
|---|
| 618 | |
|---|