File Coverage

blib/lib/MasonX/WebApp.pm
Criterion Covered Total %
statement 22 33 66.6
branch 4 10 40.0
condition 3 8 37.5
subroutine 7 7 100.0
pod n/a
total 36 58 62.0


line stmt bran cond sub pod time code
1             package MasonX::WebApp;
2              
3 3     3   250531 use strict;
  3         6  
  3         89  
4              
5 3     3   95 use 5.006;
  3         11  
  3         116  
6              
7 3     3   16 use vars qw($VERSION);
  3         5  
  3         296  
8              
9             $VERSION = 0.12;
10              
11             use Exception::Class
12 3         43 ( 'MasonX::WebApp::Exception' =>
13             { alias => 'error',
14             description => 'Generic super-class for MasonX::WebApp exceptions' },
15              
16             'MasonX::WebApp::Exception::Abort' =>
17             { isa => 'MasonX::WebApp::Exception',
18             alias => 'abort_exception',
19             description => 'The abort method was called' },
20              
21             'MasonX::WebApp::Exception::Declaration' =>
22             { isa => 'MasonX::WebApp::Exception',
23             alias => 'declaration_error',
24             description => 'Attempted to use a feature without declaring something needed for it' },
25              
26             'MasonX::WebApp::Exception::Params' =>
27             { isa => 'MasonX::WebApp::Exception',
28             alias => 'param_error',
29             description => 'Bad parameters given to a method/function' },
30 3     3   2912 );
  3         39465  
31              
32             MasonX::WebApp::Exception->Trace(1);
33              
34 3     3   9924 use base 'Class::Data::Inheritable';
  3         7  
  3         938  
35              
36             my $ModPerlVersion;
37             my $ApReqClass;
38             BEGIN
39             {
40 3 50 33 3   60 if ( $ENV{MOD_PERL} && $ENV{MOD_PERL} =~ /1\.99|2\.0/ )
    50          
41             {
42 0         0 require mod_perl2;
43             }
44             elsif ( $ENV{MOD_PERL} )
45             {
46 0         0 require mod_perl;
47             }
48              
49 3   50     118 $ModPerlVersion = (mod_perl2->VERSION || mod_perl->VERSION || 0);
50              
51 3 50 33     148 if ( $ModPerlVersion >= 1.99 && $ModPerlVersion < 1.999022 )
52             {
53 0         0 die "mod_perl-1.99 is not supported; upgrade to 2.00";
54             }
55              
56 3 50       14 if ( $ModPerlVersion < 1.99 )
57             {
58 3     3   635 eval "use Apache::Constants qw( OK REDIRECT )";
  3         2708  
  0            
  0            
59 3         6509 require Apache::Request;
60 0         0 $ApReqClass = 'Apache::Request';
61             }
62             else
63             {
64 0         0 eval "use Apache2::Const qw( OK REDIRECT )";
65 0         0 require Apache2::RequestUtil;
66 0         0 require Apache2::Request;
67 0         0 $ApReqClass = 'Apache2::Request';
68             }
69 0 0       0 die $@ if $@;
70             }
71              
72             use Class::Factory::Util;
73             use HTML::Mason::Interp;
74             use URI;
75              
76             use Params::Validate
77             qw( validate validate_pos validate_with UNDEF SCALAR BOOLEAN ARRAYREF HASHREF OBJECT );
78             Params::Validate::validation_options
79             ( on_fail => sub { param_error( join '', @_ ) } );
80              
81              
82             BEGIN
83             {
84             __PACKAGE__->mk_classdata( 'ActionURIPrefix' );
85             __PACKAGE__->mk_classdata( 'ActionURIPrefixRegex' );
86              
87             __PACKAGE__->mk_classdata( 'ApacheHandlerParams' );
88              
89             __PACKAGE__->mk_classdata( 'MasonGlobalName' );
90              
91             __PACKAGE__->mk_classdata( 'RequireAbortAfterAction' );
92              
93             __PACKAGE__->mk_classdata( 'SessionWrapperParams' );
94             __PACKAGE__->mk_classdata( 'UseSession' );
95              
96             __PACKAGE__->ActionURIPrefix('/submit/');
97             __PACKAGE__->MasonGlobalName('$WebApp');
98             __PACKAGE__->UseSession(0);
99             __PACKAGE__->RequireAbortAfterAction(1);
100             }
101              
102             { no warnings 'redefine';
103              
104             sub UseSession
105             {
106             my $class = shift;
107              
108             if (@_)
109             {
110             my ($bool) = validate_pos( @_, { type => BOOLEAN } );
111              
112             $class->_UseSession_accessor($bool);
113              
114             if ( $class->_UseSession_accessor )
115             {
116             require Apache::Session::Wrapper;
117             }
118             }
119              
120             return $class->_UseSession_accessor;
121             }
122              
123             sub SessionWrapperParams
124             {
125             my $class = shift;
126              
127             if (@_)
128             {
129             my ($p) = validate_pos( @_, { type => HASHREF } );
130              
131             $class->_SessionWrapperParams_accessor($p);
132              
133             $class->UseSession(1);
134             }
135              
136             return $class->_SessionWrapperParams_accessor;
137             }
138              
139             sub ApacheHandlerParams
140             {
141             my $class = shift;
142              
143             if (@_)
144             {
145             my ($p) = validate_pos( @_, { type => HASHREF } );
146              
147             $class->_ApacheHandlerParams_accessor($p);
148             }
149              
150             return $class->_ApacheHandlerParams_accessor;
151             }
152              
153             sub ActionURIPrefix
154             {
155             my $class = shift;
156              
157             if (@_)
158             {
159             my ($prefix) = validate_pos( @_, { regex => qr{^(?:/|/.+/)$} } );
160              
161             $class->_ActionURIPrefix_accessor($prefix);
162              
163             $class->ActionURIPrefixRegex( qr/^\Q$prefix\E/ );
164             }
165              
166             return $class->_ActionURIPrefix_accessor;
167             }
168              
169             sub _LoadActions
170             {
171             my $class = shift;
172              
173             foreach my $sub ( $class->subclasses )
174             {
175             eval "use ${class}::$sub";
176             die $@ if $@;
177             }
178             }
179              
180             } # no warnings 'redefine'
181              
182             sub new
183             {
184             my $class = shift;
185              
186             my %p =
187             validate_with
188             ( params => \@_,
189             spec =>
190             { apache_req =>
191             { can => [ qw( method uri err_headers_out
192             headers_in status pnotes ) ],
193             },
194             args => { type => HASHREF },
195             },
196             allow_extra => 1,
197             );
198              
199             my $self = bless { __apache_req__ => delete $p{apache_req},
200             __args__ => delete $p{args},
201             }, $class;
202              
203             eval
204             {
205             $self->__set_wrapper if $self->UseSession;
206              
207             $self->_init(%p);
208              
209             $self->_handle_action;
210             };
211              
212             if ($@)
213             {
214             if ( UNIVERSAL::isa( $@, 'MasonX::WebApp::Exception::Abort' ) )
215             {
216             # This shouldn't propogate out to the caller
217             undef $@;
218             }
219             else
220             {
221             UNIVERSAL::can( $@, 'rethrow' ) ? $@->rethrow : die $@;
222             }
223             }
224              
225             return $self;
226             }
227              
228             sub _init { }
229              
230             sub apache_req { $_[0]->{__apache_req__} }
231             sub args { $_[0]->{__args__} }
232              
233             sub __set_wrapper
234             {
235             my $self = shift;
236              
237             $self->{__wrapper__} = $self->_make_session_wrapper;
238             }
239              
240             sub _make_session_wrapper
241             {
242             return Apache::Session::Wrapper->new( %{ $_[0]->SessionWrapperParams || {} } );
243             }
244              
245             sub session_wrapper
246             {
247             error "Cannot call session_wrapper() method unless UseSession is true"
248             unless $_[0]->UseSession;
249              
250             return $_[0]->{__wrapper__};
251             }
252              
253             sub session { $_[0]->session_wrapper->session }
254              
255             sub _handle_action
256             {
257             my $self = shift;
258              
259             return if $self->aborted;
260              
261             my $prefix_re = $self->ActionURIPrefixRegex;
262              
263             return unless defined $prefix_re;
264              
265             my ($action) = $self->apache_req->uri =~ m{$prefix_re(\w+)};
266              
267             return unless defined $action && length $action;
268              
269             param_error "Invalid action: $action"
270             unless $self->_is_valid_action($action);
271              
272             $self->$action();
273              
274             # This code is unlikely to be executed, as issuing an abort causes
275             # an exception
276             error "No abort was issued after the $action action."
277             unless $self->aborted || ! $self->RequireAbortAfterAction;
278             }
279              
280             sub _is_valid_action { $_[0]->can( $_[1] ) }
281              
282             sub redirect
283             {
284             my $self = shift;
285             my %p = @_;
286              
287             my $uri = exists $p{uri} ? $p{uri} : $self->uri( %p, xhtml => 0 );
288              
289             if ( my $m = HTML::Mason::Request->instance )
290             {
291             $m->redirect($uri);
292             }
293             else
294             {
295             $self->{__redirected__} = 1;
296              
297             my $r = $self->apache_req;
298              
299             $r->method('GET');
300             $r->headers_in->unset('Content-length');
301             $r->err_headers_out->add( Location => $uri );
302             $r->status( REDIRECT );
303              
304             $r->send_http_header
305             if $ModPerlVersion < 1.99;
306              
307             $self->abort( REDIRECT );
308             }
309             }
310              
311             # kept for backwards compat - use aborted instead
312             sub redirected { $_[0]->{__redirected__} }
313              
314             sub abort
315             {
316             my $self = shift;
317             my $status = shift;
318              
319             $self->{__aborted__} = 1;
320             $self->{__abort_status__} = defined $status ? $status : OK;
321              
322             abort_exception;
323             }
324              
325             sub aborted { $_[0]->{__aborted__} }
326             sub abort_status { $_[0]->{__abort_status__} }
327              
328             sub uri
329             {
330             shift;
331             my %p = validate( @_,
332             { scheme => { type => SCALAR, default => 'http' },
333             username => { type => SCALAR, optional => 1 },
334             password => { type => SCALAR, default => '' },
335             host => { type => SCALAR, optional => 1 },
336             port => { type => SCALAR, optional => 1 },
337             path => { type => SCALAR },
338             query => { type => HASHREF, default => {} },
339             fragment => { type => SCALAR, optional => 1 },
340             xhtml => { type => BOOLEAN, default => 1 },
341             },
342             );
343              
344             my $uri = URI->new;
345              
346             if ( defined $p{host} )
347             {
348             $uri->scheme( $p{scheme} );
349              
350             if ( defined $p{username} )
351             {
352             $uri->authority( "$p{username}:$p{password}" );
353             }
354              
355             $uri->host( $p{host} );
356             $uri->port( $p{port} ) if $p{port};
357             }
358              
359             $uri->path( $p{path} );
360              
361             # $uri->query_form doesn't handle hash ref values properly
362             while ( my ( $k, $v ) = each %{ $p{query} } )
363             {
364             $p{query}{$k} = UNIVERSAL::isa( $v, 'HASH' ) ? [ %$v ] : $v;
365             }
366              
367             $uri->query_form( %{ $p{query} } ) if keys %{ $p{query} };
368              
369             $uri->fragment( $p{fragment} ) if $p{fragment} ;
370              
371             my $canonical = $uri->canonical;
372              
373             # make URI XHTML-compliant
374             $canonical =~ s/&(?!amp;)/&/g if $p{xhtml};
375              
376             # force stringification
377             return $canonical . '';
378             }
379              
380             sub _handle_error
381             {
382             my $self = shift;
383              
384             my %p = validate_with( params => \@_,
385             spec => { error => { type => SCALAR | ARRAYREF | OBJECT },
386             save_args => { type => HASHREF, default => {} },
387             },
388             allow_extra => 1,
389             );
390              
391             if ( UNIVERSAL::can( $p{error}, 'messages' ) && $p{error}->messages )
392             {
393             $self->_add_error_message($_) for $p{error}->messages;
394             }
395             elsif ( UNIVERSAL::can( $p{error}, 'message' ) )
396             {
397             $self->_add_error_message( $p{error}->message );
398             }
399             elsif ( ref $p{error} eq 'ARRAY' )
400             {
401             $self->_add_error_message($_) for @{ $p{error} };
402             }
403             else
404             {
405             # force stringification
406             $self->_add_error_message( "$p{error}" );
407             }
408              
409             while ( my ( $k, $v ) = each %{ $p{save_args} } )
410             {
411             $self->_save_arg( $k => $v );
412             }
413              
414             delete @p{ 'error', 'save_args' };
415              
416             $self->redirect(%p);
417             }
418              
419             sub _save_arg { $_[0]->session->{__saved_args__}{ $_[1] } = $_[2] }
420              
421             sub saved_args { $_[0]->session->{__saved_args__} || {} }
422              
423             sub _add_message { push @{ $_[0]->session->{__messages__} }, $_[1] }
424              
425             sub _add_error_message { push @{ $_[0]->session->{__errors__} }, $_[1] }
426              
427             sub messages { my $s = $_[0]->session;
428             $s->{__messages__} ? @{ delete $s->{__messages__} } : () }
429              
430             sub errors { my $s = $_[0]->session;
431             $s->{__errors__} ? @{ delete $s->{__errors__} } : () }
432              
433             sub clean_session { delete @{ $_[0]->session }{ qw( __messages__ __errors__ __saved_args__ ) } }
434              
435             sub handler ($$) : method
436             {
437             my $class = shift;
438             my $r = shift;
439             my $apr = $ApReqClass->new($r);
440              
441             my $ah = $class->_apache_handler_object;
442              
443             my $args = $ah->request_args($apr);
444              
445             my $app = $class->new( apache_req => $apr, args => $args );
446              
447             return $app->abort_status if $app->aborted;
448              
449             if ( $ah->interp->compiler->can('add_allowed_globals')
450             && defined $class->MasonGlobalName )
451             {
452             $ah->interp->compiler->add_allowed_globals( $class->MasonGlobalName );
453             $ah->interp->set_global( $class->MasonGlobalName => $app );
454             }
455              
456             my $return = eval { $ah->handle_request($r) };
457              
458             my $err = $@;
459              
460             # We want to wipe out the variable before the request ends,
461             # because if the $ah variable persists, then so does the interp,
462             # which means the $app object won't be destroyed until the next
463             # request in this process, which can hose up sessions big time.
464             $ah->interp->set_global( $class->MasonGlobalName => undef );
465              
466             $app->clean_session if $class->UseSession;
467              
468             die $err if $err;
469              
470             return $return;
471             }
472              
473             sub _apache_handler_object
474             {
475             my $class = shift;
476              
477             return MasonX::WebApp::ApacheHandler->new( %{ $class->ApacheHandlerParams || {} } );
478             }
479              
480              
481             package MasonX::WebApp::ApacheHandler;
482              
483             use base 'HTML::Mason::ApacheHandler';
484              
485             sub request_args
486             {
487             my $self = shift;
488             my $r = shift;
489              
490             return $r->pnotes('__request_args__') if $r->pnotes('__request_args__');
491              
492             MasonX::WebApp::Exception->throw
493             ( "request_args() requires an Apache::Request object or you must set "
494             . "the ApacheHandler object's args_method parameter to 'CGI'" )
495             if $self->args_method eq 'mod_perl' && ! $r->can('param');
496              
497             my $args = ($self->SUPER::request_args($r))[0] || {};
498              
499             $r->pnotes( __request_args__ => $args );
500              
501             return $args;
502             }
503              
504              
505             1;
506              
507             __END__