File Coverage

blib/lib/CGI/Application/Muto.pm
Criterion Covered Total %
statement 40 48 83.3
branch n/a
condition n/a
subroutine 14 15 93.3
pod n/a
total 54 63 85.7


line stmt bran cond sub pod time code
1             package CGI::Application::Muto;
2              
3 1     1   31929 use base 'CGI::Application';
  1         2  
  1         1173  
4              
5 1     1   7700 use strict;
  1         4  
  1         25  
6 1     1   5 use warnings;
  1         6  
  1         22  
7              
8 1     1   4 use vars '$VERSION';
  1         2  
  1         44  
9             $VERSION = '0.02';
10              
11             # Load our recommended plugins
12 1     1   875 use CGI::Application::Plugin::DBH qw/dbh_config dbh/;
  1         1683  
  1         60  
13 1     1   883 use CGI::Application::Plugin::ConfigAuto qw/cfg_file cfg/;
  1         657  
  1         5  
14 1     1   1094 use CGI::Application::Plugin::Redirect;
  1         250  
  1         6  
15 1     1   863 use CGI::Application::Plugin::LogDispatch;
  1         26426  
  1         9  
16 1     1   890 use CGI::Application::Plugin::Session;
  1         8879  
  1         10  
17              
18              
19             # Load our magic maker plugins
20 1     1   1160 use Class::Inspector;
  1         4434  
  1         21  
21 1     1   661 use CGI::Application::Muto::MethodAttributes;
  1         2  
  1         11  
22 1     1   1192 use Data::Dumper;
  1         10100  
  1         75  
23 1     1   3573 use Module::Load;
  1         1103  
  1         8  
24              
25              
26             # We overload the CGI::App run method to provide some extra functionality
27             # of our own
28             sub run {
29              
30 0     0     my $self = shift;
31              
32 0           $self->_init_controllers();
33 0           $self->_init_controller_methods();
34              
35             #Some callbacks
36 0           $self->add_callback('prerun', \&_fetch_controller_method);
37 0           $self->add_callback('prerun', \&_check_protected_methods);
38              
39 0           return $self->SUPER::run(); #call CGI::App run method
40              
41             }
42              
43              
44             sub _init_controllers{
45              
46             my $self = shift;
47              
48             # @controller_paths define the search path where we want to search for
49             # plugins
50             our @controller_paths = ('Muto::App::Controller');
51             push @controller_paths, $self->param('controller_path') if $self->param('controller_path');
52              
53             # Using Module::Pluggable we fetch the list of all available plugins
54             # and have them available by the '_contr' method in our namespace
55 0           use Module::Pluggable search_path => \@controller_paths,
56 1     1   611 sub_name => '_contr';
  0            
57              
58             # We iterate through the plugin list and using Module::Load we
59             # attempt to load them
60             for my $Controller( $self->_contr ){
61             load $Controller;
62             }
63              
64             return;
65             }
66              
67              
68             # We are gonna provide a really cool way of making our methods work similar to
69             # the ones on Catalyst
70             # This code has been taken almost entirely from the module
71             # CGI::Application::Plugin::ActionDispatch by Jason Yates, Ejaywhy@gmail.comE
72             # so all credit goes to him on this aspect of the code
73             our %_attr_cache;
74             my %methods;
75             my $init_attr_handlers = 1;
76              
77             sub CGI::Application::Muto::Path :ATTR {
78             my ($class, $referent, $attr, $data) = @_;
79              
80             $data ||='';
81             $data =~ s/\/$//;
82             unless( $data =~ /^\// ) {
83             $data = "/" . $data;
84             }
85              
86             my $regex = qr/^$data\/?(\/.*)?$/;
87             push(@{ $_attr_cache{$class}{$attr} }, [ $referent, $regex ]);
88             }
89              
90             sub CGI::Application::Muto::Regex :ATTR {
91             my ($package, $referent, $attr, $data) = @_;
92             my $regex = qr/$data/;
93             push(@{ $_attr_cache{$package}{$attr} }, [$referent, $regex ]);
94             }
95              
96             sub CGI::Application::Muto::Runmode :ATTR {
97             my ($package, $referent, $attr, $data) = @_;
98              
99             $data = $methods{$referent};
100             my $regex = qr/^\/$data\/?$/;
101             push(@{ $_attr_cache{$package}{$attr} }, [ $referent, $regex ]);
102             }
103              
104             sub CGI::Application::Muto::Default :ATTR {
105             my ($package, $referent, $attr, $data) = @_;
106             $_attr_cache{$package}{$attr} = $referent;
107             }
108              
109             sub CGI::Application::Muto::ErrorRunmode :ATTR {
110             my ($package, $referent, $attr, $data) = @_;
111             $_attr_cache{$package}{$attr} = $referent;
112             }
113              
114             # We register the methods into CGI::App's run mode map
115             sub _init_controller_methods {
116              
117             my $self = shift;
118             my $class = ref $self || $self;
119              
120             # Setup a hash table of all the methods in the class.
121             $methods{$self->can($_)} = $_
122             foreach @{ Class::Inspector->methods($class) || [] }; #NOTE: This will search through ISA also.
123              
124             CGI::Application::Muto::MethodAttributes::init();
125              
126             if(defined $_attr_cache{$class}{'Default'}) {
127             my $runmode = $methods{$_attr_cache{$class}{'Default'}};
128             $self->start_mode($runmode);
129             $self->run_modes($runmode => $runmode);
130             }
131              
132             if(defined $_attr_cache{$class}{'ErrorRunmode'}) {
133             $self->error_mode($methods{$_attr_cache{$class}{'ErrorRunmode'}});
134             }
135              
136             }
137              
138             # Based on the 'path_env' variable we try to identify the correct
139             # method to execute
140             sub _fetch_controller_method {
141              
142             my $self = shift;
143             my $class = ref $self || $self;
144              
145             my $_TEST_PATH_ON = $self->param('path_env') || 'PATH_INFO';
146             my $_PATH_PREFIX = $self->param('path_prefix') || '';
147              
148             return unless defined $ENV{$_TEST_PATH_ON};
149              
150             my $_PATH_INFO = $ENV{$_TEST_PATH_ON};
151              
152             $_PATH_INFO =~ s/\?.*$//;
153              
154             if( $_PATH_PREFIX ){
155             $_PATH_INFO =~ s/^$_PATH_PREFIX//;
156             }
157              
158             my $start_mode = $self->start_mode();
159             ATTR: foreach my $type (qw( Runmode Regex Path )) {
160             my($code, @args) = _match_type($class, $type, $_PATH_INFO);
161             if($code) {
162             # Make sure the runmode isn't set already and prerun_mode isn't set.
163             if(! $self->prerun_mode()) {
164             # Sorta of a hack here to actually get the runmode to run.
165             my $runmode = $methods{$code};
166             $self->run_modes($runmode => $runmode);
167             $self->prerun_mode($runmode);
168              
169             # Set the action_args array.
170             $self->action_args(@args);
171             }
172              
173             last ATTR;
174             }
175             }
176              
177             }
178              
179             # This little function tries to match the method on the $path_info
180             # if several methods mathc, then it uses the one that matches
181             # the closest
182             sub _match_type {
183              
184             my($class, $type, $path_info) = @_;
185              
186             my $min;
187             my(@path_args, $code);
188             foreach my $attr (@{ $_attr_cache{$class}{$type} }) {
189             if(my @args = ($path_info =~ $attr->[1])) {
190             # We want to match the most accurate Path(). This is
191             # done by counting the args, and finding the Path with
192             # the fewest amount of args left over.
193             if($type eq 'Path') {
194             if(@args && $args[0]) {
195             $args[0] =~ s/^\///;
196             @path_args = split('/', $args[0]);
197             }
198              
199             # Set min if not defined.
200             $min = scalar(@path_args) if( not defined $min );
201              
202             # If complete match return.
203             if( scalar(@path_args) == 0 ) {
204             return ($attr->[0], undef);
205             } elsif(scalar(@path_args) <= $min) {
206             # Has fewest @path_args so far.
207             $min = scalar(@path_args);
208             $code = $attr->[0];
209             }
210             } else {
211             return ($attr->[0], @args);
212             }
213             }
214             }
215             return @path_args ? ($code, @path_args) : 0;
216              
217             }
218              
219              
220             sub action_args {
221             my($self, @args) = @_;
222              
223             # If args are passed set them.
224             if(@args) {
225             $self->{__CAP_ACTION_ARGS} = [ @args ];
226             return;
227             }
228              
229             return undef unless defined $self->{__CAP_ACTION_ARGS};
230             return wantarray ? @{$self->{__CAP_ACTION_ARGS}} : shift @{$self->{__CAP_ACTION_ARGS}};
231             }
232              
233              
234              
235              
236             # Add run mode protection
237             sub protect_rm{
238              
239             my $self = shift;
240             my %args = @_;
241              
242             if( !$args{'Regex'} && !$args{'Path'} ){
243             return;
244             }
245             elsif(!$args{'auth_check'}
246             || !ref($args{'auth_check'})
247             || ref($args{'auth_check'}) ne 'CODE' ){
248             return;
249             }
250             elsif( !$args{'login_page'} ){
251             return;
252             }
253              
254             #Register the new protected rm
255             push @{$self->{'_PROTECTED_RM'}}, \%args;
256              
257             return 1;
258              
259             }
260              
261              
262             # This function checks if the path that is about to be executed
263             # is protected
264             sub _check_protected_methods{
265              
266             my $self = shift;
267              
268             my $_TEST_PATH_ON = $self->param('path_env') || 'PATH_INFO';
269             my $_PATH_PREFIX = $self->param('path_prefix') || '';
270              
271             return unless defined $ENV{$_TEST_PATH_ON};
272              
273             my $_PATH_INFO = $ENV{$_TEST_PATH_ON};
274              
275             $_PATH_INFO =~ s/\?.*$//;
276              
277             if( $_PATH_PREFIX ){
278             $_PATH_INFO =~ s/^$_PATH_PREFIX//;
279             }
280              
281             $_PATH_INFO =~ s/^\///;
282              
283             my $_IS_PROTECTED = 0;
284             my $_PRM;
285              
286             for my $prm( @{$self->{'_PROTECTED_RM'}} ){
287              
288             if( $prm->{'Path'} && $prm->{'Path'} eq $_PATH_INFO ){
289             $_IS_PROTECTED = 1;
290             $_PRM = $prm;
291             last;
292             }
293             elsif( $prm->{'Regex'} && $_PATH_INFO =~ $prm->{'Regex'} ){
294             $_IS_PROTECTED = 1;
295             $_PRM = $prm;
296             last;
297             }
298              
299             }
300              
301              
302             #we are on a protected method
303             if( $_IS_PROTECTED && $_PRM ){
304             if( $_PRM->{'auth_check'}->($self) ){
305             return 1;
306             }
307             else{
308             return $self->redirect($_PRM->{'login_page'});
309             }
310             }
311             else{
312             return 1;
313             }
314              
315             }
316              
317             1;
318             __END__