blib/lib/CGI/Ex/App.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 577 | 592 | 97.4 |
branch | 288 | 344 | 83.7 |
condition | 241 | 325 | 74.1 |
subroutine | 163 | 165 | 98.7 |
pod | 103 | 135 | 76.3 |
total | 1372 | 1561 | 87.8 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package CGI::Ex::App; | ||||||
2 | |||||||
3 | ###---------------------### | ||||||
4 | # Copyright - Paul Seamons | ||||||
5 | # Distributed under the Perl Artistic License without warranty | ||||||
6 | |||||||
7 | 1 | 1 | 707 | use 5.006; #our | |||
1 | 3 | ||||||
8 | 1 | 1 | 4 | use strict; | |||
1 | 1 | ||||||
1 | 23 | ||||||
9 | BEGIN { | ||||||
10 | 1 | 1 | 412 | eval { use Time::HiRes qw(time) }; | |||
1 | 1 | 1102 | |||||
1 | 3 | ||||||
1 | 2 | ||||||
0 | 0 | ||||||
11 | 1 | 1 | 170 | eval { use Scalar::Util }; | |||
1 | 2 | ||||||
1 | 26 | ||||||
1 | 7765 | ||||||
0 | 0 | ||||||
12 | } | ||||||
13 | our $VERSION = '2.53'; # VERSION | ||||||
14 | |||||||
15 | 21 | 21 | 0 | 248 | sub croak { die sprintf "%s at %3\$s line %4\$s\n", $_[0], caller 1 } | ||
16 | |||||||
17 | sub new { | ||||||
18 | 196 | 66 | 196 | 1 | 32225 | my $class = shift || croak "Missing class name"; | |
19 | 194 | 100 | 640 | my $self = bless ref($_[0]) ? shift() : (@_ % 2) ? {} : {@_}, $class; | |||
100 | |||||||
20 | 194 | 506 | $self->init; | ||||
21 | 194 | 535 | $self->init_from_conf; | ||||
22 | 192 | 775 | return $self; | ||||
23 | } | ||||||
24 | |||||||
25 | 69 | 1 | sub init {} | ||||
26 | sub init_from_conf { | ||||||
27 | 194 | 194 | 1 | 229 | my $self = shift; | ||
28 | 194 | 100 | 100 | 359 | @$self{keys %$_} = values %$_ if $self->load_conf and $_ = $self->conf; | ||
29 | } | ||||||
30 | |||||||
31 | sub import { # only ever called with explicit use CGI::Ex::App qw() - not with use base | ||||||
32 | 3 | 3 | 1328 | my $class = shift; | |||
33 | 3 | 50 | 6 | return if not @_ = grep { /^:?App($|__)/ } @_; | |||
3 | 23 | ||||||
34 | 3 | 424 | require CGI::Ex::App::Constants; | ||||
35 | 3 | 7 | unshift @_, 'CGI::Ex::App::Constants'; | ||||
36 | 3 | 7949 | goto &CGI::Ex::App::Constants::import; | ||||
37 | } | ||||||
38 | |||||||
39 | ###---------------------### | ||||||
40 | |||||||
41 | sub navigate { | ||||||
42 | 78 | 78 | 1 | 2674 | my ($self, $args) = @_; | ||
43 | 78 | 100 | 172 | $self = $self->new($args) if ! ref $self; | |||
44 | |||||||
45 | 78 | 190 | $self->{'_time'} = time; | ||||
46 | 78 | 105 | eval { | ||||
47 | 78 | 100 | 100 | 285 | return $self if ! $self->{'_no_pre_navigate'} && $self->pre_navigate; | ||
48 | 77 | 100 | 101 | local $self->{'_morph_lineage_start_index'} = $#{$self->{'_morph_lineage'} || []}; | |||
77 | 275 | ||||||
49 | 77 | 211 | $self->nav_loop; | ||||
50 | }; | ||||||
51 | 78 | 178 | my $err = $@; | ||||
52 | 78 | 100 | 66 | 194 | if ($err && (ref($err) || $err ne "Long Jump\n")) { # catch any errors | ||
100 | |||||||
53 | 8 | 50 | 30 | die $err if ! $self->can('handle_error'); | |||
54 | 8 | 100 | 14 | if (! eval { $self->handle_error($err); 1 }) { | |||
8 | 18 | ||||||
6 | 15 | ||||||
55 | 2 | 17 | die "$err\nAdditionally, the following happened while calling handle_error: $@"; | ||||
56 | } | ||||||
57 | } | ||||||
58 | 76 | 0 | 66 | 158 | $self->handle_error($@) if ! $self->{'_no_post_navigate'} && ! eval { $self->post_navigate; 1 } && $@ && $@ ne "Long Jump\n"; | ||
33 | |||||||
0 | |||||||
59 | 76 | 188 | $self->destroy; | ||||
60 | 76 | 249 | return $self; | ||||
61 | } | ||||||
62 | |||||||
63 | sub nav_loop { | ||||||
64 | 153 | 153 | 1 | 184 | my $self = shift; | ||
65 | 153 | 100 | 361 | local $self->{'_recurse'} = $self->{'_recurse'} || 0; | |||
66 | 153 | 100 | 326 | if ($self->{'_recurse'}++ >= $self->recurse_limit) { | |||
67 | 2 | 4 | my $err = "recurse_limit (".$self->recurse_limit.") reached"; | ||||
68 | 2 | 50 | 50 | 10 | croak(($self->{'jumps'} || 0) <= 1 ? $err : "$err number of jumps (".$self->{'jumps'}.")"); | ||
69 | } | ||||||
70 | |||||||
71 | 151 | 304 | my $path = $self->path; | ||||
72 | 149 | 100 | 313 | return if $self->pre_loop($path); | |||
73 | |||||||
74 | 148 | 100 | 461 | foreach ($self->{'path_i'} ||= 0; $self->{'path_i'} <= $#$path; $self->{'path_i'}++) { | |||
75 | 127 | 215 | my $step = $path->[$self->{'path_i'}]; | ||||
76 | 127 | 100 | 577 | if ($step !~ /^([^\W0-9]\w*)$/) { | |||
77 | 1 | 4 | $self->stash->{'forbidden_step'} = $step; | ||||
78 | 1 | 4 | $self->goto_step($self->forbidden_step); | ||||
79 | } | ||||||
80 | 126 | 291 | $step = $1; # untaint | ||||
81 | |||||||
82 | 126 | 100 | 276 | if (! $self->is_authed) { | |||
83 | 125 | 243 | my $req = $self->run_hook('require_auth', $step, 1); | ||||
84 | 125 | 100 | 66 | 310 | return if (ref($req) ? $req->{$step} : $req) && ! $self->run_hook('get_valid_auth', $step); | ||
50 | |||||||
85 | } | ||||||
86 | |||||||
87 | 119 | 250 | $self->run_hook('morph', $step); # let steps be in external modules | ||||
88 | 117 | 221 | $self->parse_path_info('path_info_map', $self->run_hook('path_info_map', $step)); | ||||
89 | 115 | 100 | 218 | if ($self->run_hook('run_step', $step)) { | |||
90 | 68 | 146 | $self->run_hook('unmorph', $step); | ||||
91 | 68 | 171 | return; | ||||
92 | } | ||||||
93 | |||||||
94 | 36 | 115 | $self->run_hook('refine_path', $step, $self->{'path_i'} >= $#$path); | ||||
95 | 36 | 62 | $self->run_hook('unmorph', $step); | ||||
96 | } | ||||||
97 | |||||||
98 | 57 | 100 | 147 | return if $self->post_loop($path); | |||
99 | 56 | 124 | $self->insert_path($self->default_step); # run the default step as a last resort | ||||
100 | 56 | 232 | $self->nav_loop; # go recursive | ||||
101 | 22 | 59 | return; | ||||
102 | } | ||||||
103 | |||||||
104 | sub path { | ||||||
105 | 329 | 329 | 1 | 478 | my $self = shift; | ||
106 | 329 | 66 | 653 | return $self->{'path'} ||= do { | |||
107 | 80 | 90 | my @path; | ||||
108 | 80 | 193 | $self->parse_path_info('path_info_map_base', $self->path_info_map_base); # add initial items to the form hash from path_info | ||||
109 | 76 | 314 | my $step = $self->form->{$self->step_key}; # make sure the step is valid | ||||
110 | 76 | 100 | 172 | if (defined $step) { | |||
111 | 41 | 77 | $step =~ s|^/+||; $step =~ s|/|__|g; | ||||
41 | 69 | ||||||
112 | 41 | 100 | 100 | 485 | if ($step =~ /^_/) { # can't begin with _ | ||
100 | 100 | ||||||
100 | |||||||
113 | 1 | 5 | $self->stash->{'forbidden_step'} = $step; | ||||
114 | 1 | 4 | push @path, $self->forbidden_step; | ||||
115 | } elsif ($self->valid_steps # must be in valid_steps if defined | ||||||
116 | && ! $self->valid_steps->{$step} | ||||||
117 | && $step ne $self->default_step | ||||||
118 | && $step ne $self->js_step) { | ||||||
119 | 1 | 4 | $self->stash->{'forbidden_step'} = $step; | ||||
120 | 1 | 4 | push @path, $self->forbidden_step; | ||||
121 | } else { | ||||||
122 | 39 | 69 | push @path, $step; | ||||
123 | } | ||||||
124 | } | ||||||
125 | 76 | 227 | \@path; | ||||
126 | }; | ||||||
127 | } | ||||||
128 | |||||||
129 | sub parse_path_info { | ||||||
130 | 197 | 197 | 0 | 484 | my ($self, $type, $maps, $info, $form) = @_; | ||
131 | 197 | 100 | 365 | return if !$maps; | |||
132 | 88 | 100 | 245 | $info ||= $self->path_info || return; | |||
66 | |||||||
133 | 18 | 100 | 66 | croak "Usage: sub $type { [] }" if ! UNIVERSAL::isa($maps, 'ARRAY'); | |||
134 | 15 | 26 | foreach my $map (@$maps) { | ||||
135 | 12 | 100 | 27 | croak "Usage: sub $type { [[qr{/path_info/(\\w+)}, 'keyname']] }" if ! UNIVERSAL::isa($map, 'ARRAY'); | |||
136 | 9 | 100 | 55 | my @match = $info =~ $map->[0] or next; | |||
137 | 6 | 33 | 22 | $form ||= $self->form; | |||
138 | 6 | 100 | 38 | if (UNIVERSAL::isa($map->[1], 'CODE')) { | |||
139 | 1 | 5 | $map->[1]->($form, @match); | ||||
140 | } else { | ||||||
141 | 5 | 10 | $form->{$map->[$_]} = $match[$_ - 1] foreach grep {! defined $form->{$map->[$_]}} 1 .. $#$map; | ||||
5 | 24 | ||||||
142 | } | ||||||
143 | 6 | 18 | last; | ||||
144 | } | ||||||
145 | } | ||||||
146 | |||||||
147 | sub run_hook { | ||||||
148 | 2545 | 2545 | 1 | 4573 | my ($self, $hook, $step, @args) = @_; | ||
149 | 2545 | 50 | 4417 | my ($code, $found) = (ref $hook eq 'CODE') ? ($_[1], $hook = 'coderef') : ($self->find_hook($hook, $step)); | |||
150 | 2545 | 100 | 4147 | croak "Could not find a method named ${step}_${hook} or ${hook}" if ! $code; | |||
151 | |||||||
152 | 2544 | 100 | 5295 | return scalar $self->$code($step, @args) if !$self->{'no_history'}; | |||
153 | |||||||
154 | 31 | 28 | push @{ $self->history }, my $hist = {step => $step, meth => $hook, found => $found, time => time, level => $self->{'_level'}, elapsed => 0}; | ||||
31 | 47 | ||||||
155 | 31 | 100 | 83 | local $self->{'_level'} = 1 + ($self->{'_level'} || 0); | |||
156 | 31 | 62 | $hist->{'elapsed'} = time - $hist->{'time'}; | ||||
157 | 31 | 57 | return $hist->{'response'} = $self->$code($step, @args); | ||||
158 | } | ||||||
159 | |||||||
160 | sub find_hook { | ||||||
161 | 2545 | 2545 | 1 | 3087 | my ($self, $hook, $step) = @_; | ||
162 | 2545 | 100 | 3287 | croak "Missing hook name" if ! $hook; | |||
163 | 2544 | 100 | 100 | 14699 | if ($step and my $code = $self->can("${step}_${hook}")) { | ||
100 | |||||||
164 | 311 | 793 | return ($code, "${step}_${hook}"); | ||||
165 | } elsif ($code = $self->can($hook)) { | ||||||
166 | 2232 | 5152 | return ($code, $hook); | ||||
167 | } | ||||||
168 | 1 | 3 | return; | ||||
169 | } | ||||||
170 | |||||||
171 | sub run_hook_as { | ||||||
172 | 3 | 3 | 1 | 2785 | my ($self, $hook, $step, $pkg, @args) = @_; | ||
173 | 3 | 50 | 6 | croak "Missing hook" if ! $hook; | |||
174 | 3 | 50 | 7 | croak "Missing step" if ! $step; | |||
175 | 3 | 50 | 6 | croak "Missing package" if ! $pkg; | |||
176 | 3 | 9 | $self->morph($step, 2, $pkg); | ||||
177 | 3 | 7 | my $resp = $self->run_hook($hook, $step, @args); | ||||
178 | 3 | 7 | $self->unmorph; | ||||
179 | 3 | 5 | return $resp; | ||||
180 | } | ||||||
181 | |||||||
182 | sub run_step { | ||||||
183 | 111 | 111 | 1 | 157 | my ($self, $step) = @_; | ||
184 | 111 | 100 | 169 | return 1 if $self->run_hook('pre_step', $step); # if true exit the nav_loop | |||
185 | 109 | 100 | 177 | return 0 if $self->run_hook('skip', $step); # if true skip this step | |||
186 | |||||||
187 | # check for complete valid information for this step | ||||||
188 | 101 | 100 | 100 | 170 | if ( ! $self->run_hook('prepare', $step) | ||
100 | |||||||
189 | || ! $self->run_hook('info_complete', $step) | ||||||
190 | || ! $self->run_hook('finalize', $step)) { | ||||||
191 | |||||||
192 | 73 | 274 | $self->run_hook('prepared_print', $step); # show the page requesting the information | ||||
193 | 72 | 831 | $self->run_hook('post_print', $step); # a hook after the printing process | ||||
194 | |||||||
195 | 72 | 201 | return 1; | ||||
196 | } | ||||||
197 | |||||||
198 | 28 | 100 | 46 | return 1 if $self->run_hook('post_step', $step); # if true exit the nav_loop | |||
199 | 27 | 57 | return 0; # let the nav_loop continue searching the path | ||||
200 | } | ||||||
201 | |||||||
202 | sub prepared_print { | ||||||
203 | 73 | 73 | 1 | 83 | my $self = shift; | ||
204 | 73 | 92 | my $step = shift; | ||||
205 | 73 | 100 | 110 | my $hash_form = $self->run_hook('hash_form', $step) || {}; | |||
206 | 73 | 100 | 316 | my $hash_base = $self->run_hook('hash_base', $step) || {}; | |||
207 | 73 | 100 | 117 | my $hash_comm = $self->run_hook('hash_common', $step) || {}; | |||
208 | 73 | 100 | 125 | my $hash_swap = $self->run_hook('hash_swap', $step) || {}; | |||
209 | 72 | 100 | 139 | my $hash_fill = $self->run_hook('hash_fill', $step) || {}; | |||
210 | 72 | 100 | 140 | my $hash_errs = $self->run_hook('hash_errors', $step) || {}; | |||
211 | 72 | 213 | $hash_errs->{$_} = $self->format_error($hash_errs->{$_}) foreach keys %$hash_errs; | ||||
212 | 72 | 100 | 130 | $hash_errs->{'has_errors'} = 1 if scalar keys %$hash_errs; | |||
213 | |||||||
214 | 72 | 593 | my $swap = {%$hash_form, %$hash_base, %$hash_comm, %$hash_swap, %$hash_errs}; | ||||
215 | 72 | 364 | my $fill = {%$hash_form, %$hash_base, %$hash_comm, %$hash_fill}; | ||||
216 | 72 | 165 | $self->run_hook('print', $step, $swap, $fill); | ||||
217 | } | ||||||
218 | |||||||
219 | sub print { | ||||||
220 | 72 | 72 | 1 | 104 | my ($self, $step, $swap, $fill) = @_; | ||
221 | 72 | 104 | my $file = $self->run_hook('file_print', $step); # get a filename relative to template_path | ||||
222 | 72 | 223 | my $out = $self->run_hook('swap_template', $step, $file, $swap); | ||||
223 | 72 | 204 | $self->run_hook('fill_template', $step, \$out, $fill); | ||||
224 | 72 | 189 | $self->run_hook('print_out', $step, \$out); | ||||
225 | } | ||||||
226 | |||||||
227 | sub handle_error { | ||||||
228 | 8 | 8 | 1 | 14 | my ($self, $err) = @_; | ||
229 | 8 | 50 | 14 | die $err if $self->{'_handling_error'}; | |||
230 | 8 | 22 | local @$self{'_handling_error', '_recurse' } = (1, 0); # allow for this next step - even if we hit a recurse error | ||||
231 | 8 | 20 | $self->stash->{'error_step'} = $self->current_step; | ||||
232 | 6 | 10 | $self->stash->{'error'} = $err; | ||||
233 | 6 | 7 | eval { | ||||
234 | 6 | 17 | my $step = $self->error_step; | ||||
235 | 6 | 22 | $self->morph($step); # let steps be in external modules | ||||
236 | 6 | 50 | 22 | $self->run_hook('run_step', $step) && $self->unmorph($step); | |||
237 | }; | ||||||
238 | 6 | 50 | 33 | 23 | die $@ if $@ && $@ ne "Long Jump\n"; | ||
239 | } | ||||||
240 | |||||||
241 | ###---------------------### | ||||||
242 | # read only accessors | ||||||
243 | |||||||
244 | 117 | 117 | 1 | 369 | sub allow_morph { $_[0]->{'allow_morph'} } | ||
245 | 2 | 2 | 1 | 9 | sub auth_args { $_[0]->{'auth_args'} } | ||
246 | 11 | 50 | 11 | 1 | 26 | sub auth_obj { shift->{'auth_obj'} || do { require CGI::Ex::Auth; CGI::Ex::Auth->new(@_) } } | |
11 | 960 | ||||||
11 | 36 | ||||||
247 | 5 | 100 | 5 | 0 | 25 | sub charset { $_[0]->{'charset'} || '' } | |
248 | 5 | 5 | 1 | 16 | sub conf_args { $_[0]->{'conf_args'} } | ||
249 | 2 | 100 | 2 | 0 | 11 | sub conf_die_on_fail { $_[0]->{'conf_die_on_fail'} || ! defined $_[0]->{'conf_die_on_fail'} } | |
250 | 3 | 100 | 3 | 1 | 13 | sub conf_path { $_[0]->{'conf_path'} || $_[0]->base_dir_abs } | |
251 | 4 | 4 | 1 | 9 | sub conf_validation { $_[0]->{'conf_validation'} } | ||
252 | 60 | 100 | 60 | 1 | 274 | sub default_step { $_[0]->{'default_step'} || 'main' } | |
253 | 8 | 100 | 8 | 1 | 33 | sub error_step { $_[0]->{'error_step'} || '__error' } | |
254 | 71 | 71 | 1 | 216 | sub fill_args { $_[0]->{'fill_args'} } | ||
255 | 5 | 100 | 5 | 1 | 33 | sub forbidden_step { $_[0]->{'forbidden_step'} || '__forbidden' } | |
256 | 79 | 50 | 79 | 1 | 248 | sub form_name { $_[0]->{'form_name'} || 'theform' } | |
257 | 519 | 100 | 519 | 1 | 1887 | sub history { $_[0]->{'history'} ||= [] } | |
258 | 18 | 100 | 18 | 0 | 76 | sub js_step { $_[0]->{'js_step'} || 'js' } | |
259 | 9 | 100 | 9 | 0 | 46 | sub login_step { $_[0]->{'login_step'} || '__login' } | |
260 | 5 | 100 | 5 | 0 | 33 | sub mimetype { $_[0]->{'mimetype'} || 'text/html' } | |
261 | 173 | 100 | 100 | 173 | 0 | 823 | sub path_info { $_[0]->{'path_info'} || $ENV{'PATH_INFO'} || '' } |
262 | 80 | 100 | 80 | 1 | 382 | sub path_info_map_base { $_[0]->{'path_info_map_base'} ||[[qr{/(\w+)}, $_[0]->step_key]] } | |
263 | 155 | 100 | 155 | 1 | 447 | sub recurse_limit { $_[0]->{'recurse_limit'} || 15 } | |
264 | 116 | 100 | 100 | 116 | 0 | 613 | sub script_name { $_[0]->{'script_name'} || $ENV{'SCRIPT_NAME'} || $0 } |
265 | 24 | 100 | 24 | 1 | 70 | sub stash { $_[0]->{'stash'} ||= {} } | |
266 | 224 | 100 | 224 | 1 | 1077 | sub step_key { $_[0]->{'step_key'} || 'step' } | |
267 | 74 | 74 | 1 | 218 | sub template_args { $_[0]->{'template_args'} } | ||
268 | 73 | 100 | 73 | 1 | 173 | sub template_obj { shift->{'template_obj'} || do { require Template::Alloy; Template::Alloy->new(@_) } } | |
72 | 862 | ||||||
72 | 21518 | ||||||
269 | 78 | 100 | 78 | 1 | 295 | sub template_path { $_[0]->{'template_path'} || $_[0]->base_dir_abs } | |
270 | 16 | 16 | 0 | 47 | sub val_args { $_[0]->{'val_args'} } | ||
271 | 10 | 100 | 10 | 0 | 64 | sub val_path { $_[0]->{'val_path'} || $_[0]->template_path } | |
272 | |||||||
273 | sub conf_obj { | ||||||
274 | 4 | 4 | 1 | 7 | my $self = shift; | ||
275 | 4 | 66 | 12 | return $self->{'conf_obj'} || do { | |||
276 | my $args = $self->conf_args || {}; | ||||||
277 | $args->{'paths'} ||= $self->conf_path; | ||||||
278 | $args->{'directive'} ||= 'MERGE'; | ||||||
279 | require CGI::Ex::Conf; | ||||||
280 | CGI::Ex::Conf->new($args); | ||||||
281 | }; | ||||||
282 | } | ||||||
283 | |||||||
284 | sub val_obj { | ||||||
285 | 15 | 15 | 0 | 19 | my $self = shift; | ||
286 | 15 | 66 | 41 | return $self->{'val_obj'} || do { | |||
287 | my $args = $self->val_args || {}; | ||||||
288 | $args->{'cgix'} ||= $self->cgix; | ||||||
289 | require CGI::Ex::Validate; | ||||||
290 | CGI::Ex::Validate->new($args); | ||||||
291 | }; | ||||||
292 | } | ||||||
293 | |||||||
294 | ###---------------------### | ||||||
295 | # read/write accessors | ||||||
296 | |||||||
297 | 143 | 100 | 143 | 1 | 281 | sub auth_data { (@_ == 2) ? $_[0]->{'auth_data'} = pop : $_[0]->{'auth_data'} } | |
298 | 82 | 100 | 100 | 82 | 1 | 856 | sub base_dir_abs { (@_ == 2) ? $_[0]->{'base_dir_abs'} = pop : $_[0]->{'base_dir_abs'} || ['.'] } |
299 | 19 | 100 | 100 | 19 | 1 | 78 | sub base_dir_rel { (@_ == 2) ? $_[0]->{'base_dir_rel'} = pop : $_[0]->{'base_dir_rel'} || '' } |
300 | 14 | 100 | 66 | 14 | 0 | 78 | sub cgix { (@_ == 2) ? $_[0]->{'cgix'} = pop : $_[0]->{'cgix'} ||= do { require CGI::Ex; CGI::Ex->new } } |
1 | 706 | ||||||
1 | 6 | ||||||
301 | 3 | 100 | 33 | 3 | 1 | 27 | sub cookies { (@_ == 2) ? $_[0]->{'cookies'} = pop : $_[0]->{'cookies'} ||= $_[0]->cgix->get_cookies } |
302 | 6 | 100 | 100 | 6 | 1 | 56 | sub ext_conf { (@_ == 2) ? $_[0]->{'ext_conf'} = pop : $_[0]->{'ext_conf'} || 'pl' } |
303 | 7 | 100 | 100 | 7 | 1 | 45 | sub ext_print { (@_ == 2) ? $_[0]->{'ext_print'} = pop : $_[0]->{'ext_print'} || 'html' } |
304 | 11 | 100 | 100 | 11 | 1 | 54 | sub ext_val { (@_ == 2) ? $_[0]->{'ext_val'} = pop : $_[0]->{'ext_val'} || 'val' } |
305 | 11 | 100 | 66 | 11 | 1 | 53 | sub form { (@_ == 2) ? $_[0]->{'form'} = pop : $_[0]->{'form'} ||= $_[0]->cgix->get_form } |
306 | 195 | 100 | 195 | 1 | 697 | sub load_conf { (@_ == 2) ? $_[0]->{'load_conf'} = pop : $_[0]->{'load_conf'} } | |
307 | |||||||
308 | sub conf { | ||||||
309 | 8 | 8 | 1 | 13 | my $self = shift; | ||
310 | 8 | 100 | 19 | $self->{'conf'} = pop if @_ == 1; | |||
311 | 8 | 66 | 39 | return $self->{'conf'} ||= do { | |||
312 | 4 | 8 | my $conf = $self->conf_file; | ||||
313 | 4 | 100 | 33 | 31 | $conf = $self->conf_obj->read($conf, {no_warn_on_fail => 1}) || ($self->conf_die_on_fail ? croak $@ : {}) | ||
314 | if ! ref $conf; | ||||||
315 | 3 | 11 | my $hash = $self->conf_validation; | ||||
316 | 3 | 50 | 100 | 21 | if ($hash && scalar keys %$hash) { | ||
317 | 2 | 8 | my $err_obj = $self->val_obj->validate($conf, $hash); | ||||
318 | 2 | 100 | 18 | croak "$err_obj" if $err_obj; | |||
319 | } | ||||||
320 | 2 | 22 | $conf; | ||||
321 | } | ||||||
322 | } | ||||||
323 | |||||||
324 | sub conf_file { | ||||||
325 | 10 | 10 | 1 | 16 | my $self = shift; | ||
326 | 10 | 100 | 22 | $self->{'conf_file'} = pop if @_ == 1; | |||
327 | 10 | 66 | 24 | return $self->{'conf_file'} ||= do { | |||
328 | 4 | 66 | 9 | my $module = $self->name_module || croak 'Missing name_module during conf_file call'; | |||
329 | 3 | 22 | $module .'.'. $self->ext_conf; | ||||
330 | }; | ||||||
331 | } | ||||||
332 | |||||||
333 | ###---------------------### | ||||||
334 | # general methods | ||||||
335 | |||||||
336 | 2 | 2 | 0 | 15 | sub add_to_base { my $self = shift; $self->add_to_hash($self->hash_base, @_) } | ||
2 | 4 | ||||||
337 | 2 | 2 | 0 | 9 | sub add_to_common { my $self = shift; $self->add_to_hash($self->hash_common, @_) } | ||
2 | 5 | ||||||
338 | 3 | 3 | 0 | 16 | sub add_to_errors { shift->add_errors(@_) } | ||
339 | 2 | 2 | 0 | 11 | sub add_to_fill { my $self = shift; $self->add_to_hash($self->hash_fill, @_) } | ||
2 | 6 | ||||||
340 | 2 | 2 | 0 | 15 | sub add_to_form { my $self = shift; $self->add_to_hash($self->hash_form, @_) } | ||
2 | 5 | ||||||
341 | 1 | 1 | 0 | 21 | sub add_to_path { shift->append_path(@_) } # legacy | ||
342 | 2 | 2 | 0 | 14 | sub add_to_swap { my $self = shift; $self->add_to_hash($self->hash_swap, @_) } | ||
2 | 5 | ||||||
343 | 7 | 7 | 1 | 31 | sub append_path { my $self = shift; push @{ $self->path }, @_ } | ||
7 | 64 | ||||||
7 | 15 | ||||||
344 | 3 | 3 | 1 | 5 | sub cleanup_user { my ($self, $user) = @_; $user } | ||
3 | 28 | ||||||
345 | 10 | 100 | 10 | 1 | 42 | sub current_step { $_[0]->step_by_path_index($_[0]->{'path_i'} || 0) } | |
346 | 76 | 1 | sub destroy {} | ||||
347 | 2 | 2 | 1 | 12 | sub first_step { $_[0]->step_by_path_index(0) } | ||
348 | 12 | 0 | sub fixup_after_morph {} | ||||
349 | 10 | 0 | sub fixup_before_unmorph {} | ||||
350 | 8 | 8 | 0 | 12 | sub format_error { my ($self, $error) = @_; $error } | ||
8 | 17 | ||||||
351 | 1 | 1 | 1 | 3 | sub get_pass_by_user { croak "get_pass_by_user is a virtual method and needs to be overridden for authentication to work" } | ||
352 | 1 | 1 | 0 | 3 | sub has_errors { scalar keys %{ $_[0]->hash_errors } } | ||
1 | 3 | ||||||
353 | 2 | 2 | 1 | 4 | sub last_step { $_[0]->step_by_path_index($#{ $_[0]->path }) } | ||
2 | 5 | ||||||
354 | 62 | 1 | sub path_info_map {} | ||||
355 | 56 | 56 | 1 | 94 | sub post_loop { 0 } # true value means to abort the nav_loop - don't recurse | ||
356 | 74 | 1 | sub post_navigate {} | ||||
357 | 148 | 148 | 1 | 256 | sub pre_loop { 0 } # true value means to abort the nav_loop routine | ||
358 | 73 | 73 | 1 | 196 | sub pre_navigate { 0 } # true means to not enter nav_loop | ||
359 | 3 | 100 | 3 | 1 | 29 | sub previous_step { $_[0]->step_by_path_index(($_[0]->{'path_i'} || 0) - 1) } | |
360 | 36 | 1 | sub valid_steps {} | ||||
361 | 3 | 3 | 1 | 8 | sub verify_user { 1 } | ||
362 | |||||||
363 | sub add_errors { | ||||||
364 | 7 | 7 | 0 | 13 | my $self = shift; | ||
365 | 7 | 10 | my $hash = $self->hash_errors; | ||||
366 | 7 | 100 | 16 | my $args = ref($_[0]) ? shift : {@_}; | |||
367 | 7 | 15 | foreach my $key (keys %$args) { | ||||
368 | 7 | 100 | 19 | my $_key = ($key =~ /error$/) ? $key : "${key}_error"; | |||
369 | 7 | 100 | 12 | if ($hash->{$_key}) { | |||
370 | 1 | 3 | $hash->{$_key} .= ' ' . $args->{$key}; |
||||
371 | } else { | ||||||
372 | 6 | 16 | $hash->{$_key} = $args->{$key}; | ||||
373 | } | ||||||
374 | } | ||||||
375 | 7 | 13 | $hash->{'has_errors'} = 1; | ||||
376 | } | ||||||
377 | |||||||
378 | sub add_to_hash { | ||||||
379 | 10 | 10 | 0 | 14 | my $self = shift; | ||
380 | 10 | 10 | my $old = shift; | ||||
381 | 10 | 100 | 20 | my $new = ref($_[0]) ? shift : {@_}; | |||
382 | 10 | 33 | @$old{keys %$new} = values %$new; | ||||
383 | } | ||||||
384 | |||||||
385 | sub clear_app { | ||||||
386 | 1 | 1 | 1 | 2 | my $self = shift; | ||
387 | 1 | 7 | delete @$self{qw(cgix cookies form hash_common hash_errors hash_fill hash_swap history | ||||
388 | _morph_lineage _morph_lineage_start_index path path_i stash val_obj)}; | ||||||
389 | 1 | 5 | return $self; | ||||
390 | } | ||||||
391 | |||||||
392 | sub dump_history { | ||||||
393 | 3 | 3 | 1 | 13 | my ($self, $all) = @_; | ||
394 | 3 | 7 | my $hist = $self->history; | ||||
395 | 3 | 32 | my $dump = [sprintf "Elapsed: %.5f", time - $self->{'_time'}]; | ||||
396 | |||||||
397 | 3 | 6 | foreach my $row (@$hist) { | ||||
398 | 33 | 100 | 100 | 134 | if (! ref($row) || ref($row) ne 'HASH' || ! exists $row->{'elapsed'}) { | ||
100 | |||||||
399 | 9 | 10 | push @$dump, $row; | ||||
400 | 9 | 12 | next; | ||||
401 | } | ||||||
402 | my $note = (' ' x ($row->{'level'} || 0)) | ||||||
403 | 24 | 50 | 117 | . join(' - ', $row->{'step'}, $row->{'meth'}, $row->{'found'}, sprintf '%.5f', $row->{'elapsed'}); | |||
404 | 24 | 30 | my $resp = $row->{'response'}; | ||||
405 | 24 | 100 | 30 | if ($all) { | |||
406 | 16 | 40 | $note = [$note, $resp]; | ||||
407 | } else { | ||||||
408 | 8 | 100 | 100 | 61 | $note .= ' - ' | ||
100 | 100 | ||||||
100 | |||||||
100 | |||||||
409 | .(! defined $resp ? 'undef' | ||||||
410 | : ref($resp) eq 'ARRAY' && !@$resp ? '[]' | ||||||
411 | : ref($resp) eq 'HASH' && !scalar keys %$resp ? '{}' | ||||||
412 | : $resp =~ /^(.{30}|.{0,30}(?=\n))(?s:.)/ ? "$1..." : $resp); | ||||||
413 | 8 | 50 | 16 | $note .= ' - '.$row->{'info'} if defined $row->{'info'}; | |||
414 | } | ||||||
415 | 24 | 45 | push @$dump, $note; | ||||
416 | } | ||||||
417 | |||||||
418 | 3 | 19 | return $dump; | ||||
419 | } | ||||||
420 | |||||||
421 | sub exit_nav_loop { | ||||||
422 | 14 | 14 | 1 | 31 | my $self = shift; | ||
423 | 14 | 100 | 28 | if (my $ref = $self->{'_morph_lineage'}) { # undo morphs | |||
424 | 2 | 3 | my $index = $self->{'_morph_lineage_start_index'}; # allow for early "morphers" to only get rolled back so far | ||||
425 | 2 | 100 | 5 | $index = -1 if ! defined $index; | |||
426 | 2 | 5 | $self->unmorph while $#$ref != $index; | ||||
427 | } | ||||||
428 | 14 | 185 | die "Long Jump\n"; | ||||
429 | } | ||||||
430 | |||||||
431 | sub insert_path { | ||||||
432 | 57 | 57 | 1 | 79 | my $self = shift; | ||
433 | 57 | 82 | my $ref = $self->path; | ||||
434 | 57 | 100 | 122 | my $i = $self->{'path_i'} || 0; | |||
435 | 57 | 100 | 113 | if ($i + 1 > $#$ref) { push @$ref, @_ } | |||
56 | 110 | ||||||
436 | 1 | 4 | else { splice(@$ref, $i + 1, 0, @_) } # insert a path at the current location | ||||
437 | } | ||||||
438 | |||||||
439 | 9 | 9 | 1 | 101 | sub jump { shift->goto_step(@_) } | ||
440 | |||||||
441 | sub goto_step { | ||||||
442 | 20 | 20 | 1 | 46 | my $self = shift; | ||
443 | 20 | 50 | 34 | my $i = @_ == 1 ? shift : 1; | |||
444 | 20 | 34 | my $path = $self->path; | ||||
445 | 20 | 100 | 47 | my $path_i = $self->{'path_i'} || 0; | |||
446 | |||||||
447 | 20 | 100 | 103 | if ( $i eq 'FIRST' ) { $i = - $path_i - 1 } | |||
2 | 100 | 10 | |||||
100 | |||||||
100 | |||||||
100 | |||||||
100 | |||||||
448 | 1 | 3 | elsif ($i eq 'LAST' ) { $i = $#$path - $path_i } | ||||
449 | 1 | 3 | elsif ($i eq 'NEXT' ) { $i = 1 } | ||||
450 | 1 | 5 | elsif ($i eq 'CURRENT' ) { $i = 0 } | ||||
451 | 1 | 2 | elsif ($i eq 'PREVIOUS') { $i = -1 } | ||||
452 | elsif ($i !~ /^-?\d+/) { # look for a step by that name in the current remaining path | ||||||
453 | 11 | 12 | my $found; | ||||
454 | 11 | 22 | for (my $j = $path_i; $j < @$path; $j++) { | ||||
455 | 16 | 100 | 35 | if ($path->[$j] eq $i) { | |||
456 | 1 | 2 | $i = $j - $path_i; | ||||
457 | 1 | 3 | $found = 1; | ||||
458 | 1 | 2 | last; | ||||
459 | } | ||||||
460 | } | ||||||
461 | 11 | 100 | 30 | if (! $found) { | |||
462 | 10 | 61 | $self->replace_path($i); | ||||
463 | 10 | 18 | $i = $#$path; | ||||
464 | } | ||||||
465 | } | ||||||
466 | 20 | 50 | 82 | croak "Invalid jump index ($i)" if $i !~ /^-?\d+$/; | |||
467 | |||||||
468 | 20 | 23 | my $cut_i = $path_i + $i; # manipulate the path to contain the new jump location | ||||
469 | 20 | 100 | 110 | my @replace = ($cut_i > $#$path) ? $self->default_step | |||
100 | |||||||
470 | : ($cut_i < 0) ? @$path | ||||||
471 | : @$path[$cut_i .. $#$path]; | ||||||
472 | 20 | 61 | $self->replace_path(@replace); | ||||
473 | |||||||
474 | 20 | 100 | 54 | $self->{'jumps'} = ($self->{'jumps'} || 0) + 1; | |||
475 | 20 | 25 | $self->{'path_i'}++; # move along now that the path is updated | ||||
476 | |||||||
477 | 20 | 100 | 56 | my $lin = $self->{'_morph_lineage'} || []; | |||
478 | 20 | 100 | 39 | $self->unmorph if @$lin; | |||
479 | 20 | 70 | $self->nav_loop; # recurse on the path | ||||
480 | 12 | 64 | $self->exit_nav_loop; | ||||
481 | } | ||||||
482 | |||||||
483 | sub js_uri_path { | ||||||
484 | 13 | 13 | 1 | 26 | my $self = shift; | ||
485 | 13 | 17 | my $script = $self->script_name; | ||||
486 | 13 | 35 | my $js_step = $self->js_step; | ||||
487 | 13 | 50 | 33 | 105 | return ($self->can('path') == \&CGI::Ex::App::path | ||
488 | && $self->can('path_info_map_base') == \&CGI::Ex::App::path_info_map_base) | ||||||
489 | ? $script .'/'. $js_step # try to use a cache friendly URI (if path is our own) | ||||||
490 | : $script .'?'. $self->step_key .'='. $js_step .'&js='; # use one that works with more paths | ||||||
491 | } | ||||||
492 | |||||||
493 | |||||||
494 | sub morph { | ||||||
495 | 133 | 133 | 1 | 517 | my $self = shift; | ||
496 | 133 | 313 | my $ref = $self->history->[-1]; | ||||
497 | 133 | 100 | 66 | 428 | if (! $ref || ! $ref->{'meth'} || $ref->{'meth'} ne 'morph') { | ||
100 | |||||||
498 | 115 | 124 | push @{ $self->history }, ($ref = {meth => 'morph', found => 'morph', elapsed => 0, step => 'unknown', level => $self->{'_level'}}); | ||||
115 | 152 | ||||||
499 | } | ||||||
500 | 133 | 100 | 253 | my $step = shift || return; | |||
501 | 132 | 100 | 315 | my $allow = shift || $self->run_hook('allow_morph', $step) || return; | |||
502 | 21 | 52 | my $new = shift; # optionally allow passing in the package to morph to | ||||
503 | 21 | 100 | 55 | my $lin = $self->{'_morph_lineage'} ||= []; | |||
504 | 21 | 33 | my $ok = 0; | ||||
505 | 21 | 28 | my $cur = ref $self; | ||||
506 | |||||||
507 | 21 | 28 | push @$lin, $cur; # store so subsequent unmorph calls can do the right thing | ||||
508 | |||||||
509 | # hash - but no step - record for unbless | ||||||
510 | 21 | 100 | 100 | 68 | if (ref($allow) && ! ($allow = $allow->{$step})) { | ||
50 | 66 | ||||||
100 | |||||||
511 | 1 | 2 | $ref->{'info'} = "not allowed to morph to that step"; | ||||
512 | |||||||
513 | } elsif (! ($new ||= $self->run_hook('morph_package', $step))) { | ||||||
514 | 0 | 0 | $ref->{'info'} = "Missing morph_package for step $step"; | ||||
515 | |||||||
516 | } elsif ($cur eq $new) { | ||||||
517 | 2 | 10 | $ref->{'info'} = "already isa $new"; | ||||
518 | 2 | 2 | $ok = 1; | ||||
519 | |||||||
520 | ### if we are not already that package - bless us there | ||||||
521 | } else { | ||||||
522 | 18 | 66 | (my $file = "$new.pm") =~ s|::|/|g; | ||||
523 | 18 | 100 | 66 | 122 | if (UNIVERSAL::can($new, 'fixup_after_morph') # check if the package space exists | ||
100 | 66 | ||||||
50 | |||||||
524 | 6 | 742 | || (eval { require $file } # check for a file that holds this package | ||||
525 | && UNIVERSAL::can($new, 'fixup_after_morph'))) { | ||||||
526 | 12 | 20 | bless $self, $new; # become that package | ||||
527 | 12 | 22 | $self->fixup_after_morph($step); | ||||
528 | 12 | 27 | $ref->{'info'} = "changed $cur to $new"; | ||||
529 | } elsif ($@) { | ||||||
530 | 5 | 100 | 66 | 37 | if ($allow eq '1' && $@ =~ /^\s*(Can\'t locate \S+ in \@INC)/) { # let us know what happened | ||
531 | 4 | 18 | $ref->{'info'} = "failed from $cur to $new: $1"; | ||||
532 | } else { | ||||||
533 | 1 | 6 | $ref->{'info'} = "failed from $cur to $new: $@"; | ||||
534 | 1 | 8 | die "Trouble while morphing from $cur to $new: $@"; | ||||
535 | } | ||||||
536 | } elsif ($allow ne '1') { | ||||||
537 | 1 | 4 | $ref->{'info'} = "package $new doesn't support CGI::Ex::App API"; | ||||
538 | 1 | 9 | die "Found package $new, but $new does not support CGI::Ex::App API"; | ||||
539 | } | ||||||
540 | 16 | 38 | $ok = 1; | ||||
541 | } | ||||||
542 | |||||||
543 | 19 | 40 | return $ok; | ||||
544 | } | ||||||
545 | |||||||
546 | sub replace_path { | ||||||
547 | 31 | 31 | 1 | 42 | my $self = shift; | ||
548 | 31 | 39 | my $ref = $self->path; | ||||
549 | 31 | 100 | 83 | my $i = $self->{'path_i'} || 0; | |||
550 | 31 | 100 | 61 | if ($i + 1 > $#$ref) { push @$ref, @_; } | |||
13 | 21 | ||||||
551 | 18 | 60 | else { splice(@$ref, $i + 1, $#$ref - $i, @_); } # replace remaining entries | ||||
552 | } | ||||||
553 | |||||||
554 | sub set_path { | ||||||
555 | 3 | 3 | 1 | 601 | my $self = shift; | ||
556 | 3 | 100 | 13 | my $path = $self->{'path'} ||= []; | |||
557 | 3 | 100 | 9 | croak "Cannot call set_path after the navigation loop has begun" if $self->{'path_i'}; | |||
558 | 2 | 7 | splice @$path, 0, $#$path + 1, @_; # change entries in the ref (which updates other copies of the ref) | ||||
559 | } | ||||||
560 | |||||||
561 | sub step_by_path_index { | ||||||
562 | 45 | 45 | 0 | 58 | my $self = shift; | ||
563 | 45 | 100 | 77 | my $i = shift || 0; | |||
564 | 45 | 66 | my $ref = $self->path; | ||||
565 | 43 | 100 | 101 | return '' if $i < 0; | |||
566 | 42 | 157 | return $ref->[$i]; | ||||
567 | } | ||||||
568 | |||||||
569 | sub unmorph { | ||||||
570 | 116 | 116 | 1 | 136 | my $self = shift; | ||
571 | 116 | 100 | 198 | my $step = shift || '_no_step'; | |||
572 | 116 | 50 | 227 | my $ref = $self->history->[-1] || {}; | |||
573 | 116 | 100 | 33 | 435 | if (! $ref || ! $ref->{'meth'} || $ref->{'meth'} ne 'unmorph') { | ||
66 | |||||||
574 | 114 | 130 | push @{ $self->history }, ($ref = {meth => 'unmorph', found => 'unmorph', elapsed => 0, step => $step, level => $self->{'_level'}}); | ||||
114 | 153 | ||||||
575 | } | ||||||
576 | 116 | 100 | 333 | my $lin = $self->{'_morph_lineage'} || return; | |||
577 | 19 | 26 | my $cur = ref $self; | ||||
578 | 19 | 33 | 33 | my $prev = pop(@$lin) || croak "unmorph called more times than morph (current: $cur)"; | |||
579 | 19 | 100 | 41 | delete $self->{'_morph_lineage'} if ! @$lin; | |||
580 | |||||||
581 | 19 | 100 | 34 | if ($cur ne $prev) { | |||
582 | 10 | 41 | $self->fixup_before_unmorph($step); | ||||
583 | 10 | 12 | bless $self, $prev; | ||||
584 | 10 | 23 | $ref->{'info'} = "changed from $cur to $prev"; | ||||
585 | } else { | ||||||
586 | 9 | 17 | $ref->{'info'} = "already isa $cur"; | ||||
587 | } | ||||||
588 | |||||||
589 | 19 | 37 | return 1; | ||||
590 | } | ||||||
591 | |||||||
592 | ###---------------------### | ||||||
593 | # hooks | ||||||
594 | |||||||
595 | sub file_print { | ||||||
596 | 7 | 7 | 1 | 13 | my ($self, $step) = @_; | ||
597 | 7 | 17 | my $base_dir = $self->base_dir_rel; | ||||
598 | 7 | 16 | my $module = $self->run_hook('name_module', $step); | ||||
599 | 7 | 66 | 17 | my $_step = $self->run_hook('name_step', $step) || croak "Missing name_step"; | |||
600 | 6 | 35 | $_step =~ s|\B__+|/|g; | ||||
601 | 6 | 100 | 20 | $_step .= '.'. $self->ext_print if $_step !~ /\.\w+$/; | |||
602 | 6 | 100 | 66 | 13 | foreach ($base_dir, $module) { $_ .= '/' if length($_) && ! m|/$| } | ||
12 | 47 | ||||||
603 | 6 | 43 | return $base_dir . $module . $_step; | ||||
604 | } | ||||||
605 | |||||||
606 | sub file_val { | ||||||
607 | 10 | 10 | 1 | 20 | my ($self, $step) = @_; | ||
608 | |||||||
609 | 10 | 100 | 17 | my $abs = $self->val_path || []; | |||
610 | 10 | 100 | 42 | $abs = $abs->() if UNIVERSAL::isa($abs, 'CODE'); | |||
611 | 10 | 100 | 31 | $abs = [$abs] if ! UNIVERSAL::isa($abs, 'ARRAY'); | |||
612 | 10 | 100 | 21 | return {} if @$abs == 0; | |||
613 | |||||||
614 | 9 | 16 | my $base_dir = $self->base_dir_rel; | ||||
615 | 9 | 17 | my $module = $self->run_hook('name_module', $step); | ||||
616 | 9 | 66 | 23 | my $_step = $self->run_hook('name_step', $step) || croak "Missing name_step"; | |||
617 | 8 | 37 | $_step =~ s|\B__+|/|g; | ||||
618 | 8 | 18 | $_step =~ s/\.\w+$//; | ||||
619 | 8 | 18 | $_step .= '.'. $self->ext_val; | ||||
620 | |||||||
621 | 8 | 100 | 100 | 17 | foreach (@$abs, $base_dir, $module) { $_ .= '/' if length($_) && ! m|/$| } | ||
25 | 71 | ||||||
622 | |||||||
623 | 8 | 100 | 16 | if (@$abs > 1) { | |||
624 | 1 | 3 | foreach my $_abs (@$abs) { | ||||
625 | 2 | 8 | my $path = "$_abs/$base_dir/$module/$_step"; | ||||
626 | 2 | 50 | 38 | return $path if -e $path; | |||
627 | } | ||||||
628 | } | ||||||
629 | 8 | 59 | return $abs->[0] . $base_dir . $module . $_step; | ||||
630 | } | ||||||
631 | |||||||
632 | sub fill_template { | ||||||
633 | 72 | 72 | 1 | 127 | my ($self, $step, $outref, $fill) = @_; | ||
634 | 72 | 100 | 66 | 269 | return if ! $fill || ! scalar keys %$fill; | ||
635 | 71 | 50 | 127 | my $args = $self->run_hook('fill_args', $step) || {}; | |||
636 | 71 | 192 | local @$args{'text', 'form'} = ($outref, $fill); | ||||
637 | 71 | 1113 | require CGI::Ex::Fill; | ||||
638 | 71 | 185 | CGI::Ex::Fill::fill($args); | ||||
639 | } | ||||||
640 | |||||||
641 | 25 | 25 | 1 | 56 | sub finalize { 1 } # false means show step | ||
642 | |||||||
643 | sub hash_base { | ||||||
644 | 78 | 78 | 1 | 132 | my ($self, $step) = @_; | ||
645 | 78 | 100 | 250 | my $hash = $self->{'hash_base'} ||= { | |||
646 | script_name => $self->script_name, | ||||||
647 | path_info => $self->path_info, | ||||||
648 | }; | ||||||
649 | |||||||
650 | 78 | 146 | my $copy = $self; eval { require Scalar::Util; Scalar::Util::weaken($copy) }; | ||||
78 | 103 | ||||||
78 | 334 | ||||||
78 | 226 | ||||||
651 | 78 | 1 | 295 | $hash->{'js_validation'} = sub { $copy->run_hook('js_validation', $step, shift) }; | |||
1 | 729 | ||||||
652 | 78 | 0 | 0 | 206 | $hash->{'generate_form'} = sub { $copy->run_hook('generate_form', $step, (ref($_[0]) ? (undef, shift) : shift)) }; | ||
0 | 0 | ||||||
653 | 78 | 166 | $hash->{'form_name'} = $self->run_hook('form_name', $step); | ||||
654 | 78 | 163 | $hash->{$self->step_key} = $step; | ||||
655 | 78 | 250 | return $hash; | ||||
656 | } | ||||||
657 | |||||||
658 | 61 | 100 | 61 | 1 | 276 | sub hash_common { $_[0]->{'hash_common'} ||= {} } | |
659 | 82 | 100 | 82 | 1 | 277 | sub hash_errors { $_[0]->{'hash_errors'} ||= {} } | |
660 | 72 | 100 | 72 | 1 | 262 | sub hash_fill { $_[0]->{'hash_fill'} ||= {} } | |
661 | 76 | 76 | 1 | 170 | sub hash_form { $_[0]->form } | ||
662 | 72 | 100 | 72 | 1 | 249 | sub hash_swap { $_[0]->{'hash_swap'} ||= {} } | |
663 | |||||||
664 | sub hash_validation { | ||||||
665 | 2 | 2 | 1 | 6 | my ($self, $step) = @_; | ||
666 | 2 | 66 | 8 | return $self->{'hash_validation'}->{$step} ||= do { | |||
667 | 1 | 3 | my $file = $self->run_hook('file_val', $step); | ||||
668 | 1 | 50 | 9 | $file ? $self->val_obj->get_validation($file) : {}; # if the file is not found, errors will be in the webserver logs (all else dies) | |||
669 | }; | ||||||
670 | } | ||||||
671 | |||||||
672 | sub info_complete { | ||||||
673 | 9 | 9 | 1 | 15 | my ($self, $step) = @_; | ||
674 | 9 | 100 | 14 | return 0 if ! $self->run_hook('ready_validate', $step); | |||
675 | 8 | 100 | 23 | return $self->run_hook('validate', $step, $self->form) ? 1 : 0; | |||
676 | } | ||||||
677 | |||||||
678 | sub js_validation { | ||||||
679 | 6 | 6 | 1 | 12 | my ($self, $step) = @_; | ||
680 | 6 | 100 | 28 | my $form_name = $_[2] || $self->run_hook('form_name', $step); | |||
681 | 6 | 100 | 23 | my $hash_val = $_[3] || $self->run_hook('hash_validation', $step); | |||
682 | 6 | 100 | 100 | 41 | return '' if ! $form_name || ! ref($hash_val) || ! scalar keys %$hash_val; | ||
100 | |||||||
683 | 2 | 11 | return $self->val_obj->generate_js($hash_val, $form_name, $self->js_uri_path); | ||||
684 | } | ||||||
685 | |||||||
686 | sub generate_form { | ||||||
687 | 0 | 0 | 0 | 0 | my ($self, $step) = @_; | ||
688 | 0 | 0 | 0 | my $form_name = $_[2] || $self->run_hook('form_name', $step); | |||
689 | 0 | 0 | 0 | my $args = ref($_[3]) eq 'HASH' ? $_[3] : {}; | |||
690 | 0 | 0 | my $hash_val = $self->run_hook('hash_validation', $step); | ||||
691 | 0 | 0 | 0 | 0 | return '' if ! $form_name || ! ref($hash_val) || ! scalar keys %$hash_val; | ||
0 | |||||||
692 | 0 | 0 | local $args->{'js_uri_path'} = $self->js_uri_path; | ||||
693 | 0 | 0 | return $self->val_obj->generate_form($hash_val, $form_name, $args); | ||||
694 | } | ||||||
695 | |||||||
696 | 20 | 20 | 0 | 30 | sub morph_base { my $self = shift; ref($self) } | ||
20 | 31 | ||||||
697 | sub morph_package { | ||||||
698 | 20 | 20 | 1 | 674 | my ($self, $step) = @_; | ||
699 | 20 | 40 | my $cur = $self->morph_base; # default to using self as the base for morphed modules | ||||
700 | 20 | 50 | 66 | 63 | my $new = ($cur ? $cur .'::' : '') . ($step || croak "Missing step"); | ||
701 | 19 | 43 | $new =~ s/\B__+/::/g; # turn Foo::my_nested__step info Foo::my_nested::step | ||||
702 | 19 | 187 | $new =~ s/(?:_+|\b)(\w)/\u$1/g; # turn Foo::my_step_name into Foo::MyStepName | ||||
703 | 19 | 93 | return $new; | ||||
704 | } | ||||||
705 | |||||||
706 | sub name_module { | ||||||
707 | 21 | 21 | 1 | 47 | my ($self, $step) = @_; | ||
708 | 21 | 100 | 100 | 61 | return $self->{'name_module'} ||= ($self->script_name =~ m/ (\w+) (?:\.\w+)? $/x) | ||
66 | |||||||
709 | ? $1 : die "Could not determine module name from \"name_module\" lookup (".($step||'').")\n"; | ||||||
710 | } | ||||||
711 | |||||||
712 | 10 | 10 | 1 | 21 | sub name_step { my ($self, $step) = @_; $step } | ||
10 | 22 | ||||||
713 | 28 | 100 | 28 | 1 | 77 | sub next_step { $_[0]->step_by_path_index(($_[0]->{'path_i'} || 0) + 1) } | |
714 | 72 | 72 | 1 | 92 | sub post_print { 0 } | ||
715 | 27 | 27 | 1 | 46 | sub post_step { 0 } # true indicates we handled step (exit loop) | ||
716 | 109 | 109 | 1 | 218 | sub pre_step { 0 } # true indicates we handled step (exit loop) | ||
717 | 100 | 100 | 1 | 239 | sub prepare { 1 } # false means show step | ||
718 | |||||||
719 | sub print_out { | ||||||
720 | 4 | 4 | 1 | 8 | my ($self, $step, $out) = @_; | ||
721 | 4 | 10 | $self->cgix->print_content_type($self->run_hook('mimetype', $step), $self->run_hook('charset', $step)); | ||||
722 | 4 | 100 | 420 | print ref($out) eq 'SCALAR' ? $$out : $out; | |||
723 | } | ||||||
724 | |||||||
725 | sub ready_validate { | ||||||
726 | 12 | 12 | 1 | 24 | my ($self, $step) = @_; | ||
727 | 12 | 50 | 33 | 22 | if ($self->run_hook('validate_when_data', $step) | ||
728 | 0 | 0 | 0 | and my @keys = keys %{ $self->run_hook('hash_validation', $step) || {} }) { | |||
729 | 0 | 0 | my $form = $self->form; | ||||
730 | 0 | 0 | 0 | return (grep { exists $form->{$_} } @keys) ? 1 : 0; | |||
0 | 0 | ||||||
731 | } | ||||||
732 | 12 | 100 | 66 | 72 | return ($ENV{'REQUEST_METHOD'} && $ENV{'REQUEST_METHOD'} eq 'POST') ? 1 : 0; | ||
733 | } | ||||||
734 | |||||||
735 | sub refine_path { | ||||||
736 | 36 | 36 | 1 | 56 | my ($self, $step, $is_at_end) = @_; | ||
737 | 36 | 100 | 82 | return 0 if ! $is_at_end; # if we are not at the end of the path, do not do anything | |||
738 | 27 | 100 | 41 | my $next_step = $self->run_hook('next_step', $step) || return 0; | |||
739 | 1 | 9 | $self->run_hook('set_ready_validate', $step, 0); | ||||
740 | 1 | 4 | $self->append_path($next_step); | ||||
741 | 1 | 2 | return 1; | ||||
742 | } | ||||||
743 | |||||||
744 | sub set_ready_validate { | ||||||
745 | 5 | 5 | 1 | 33 | my $self = shift; | ||
746 | 5 | 100 | 13 | my ($step, $is_ready) = (@_ == 2) ? @_ : (undef, shift); # hook and method | |||
747 | 5 | 100 | 21 | $ENV{'REQUEST_METHOD'} = ($is_ready) ? 'POST' : 'GET'; | |||
748 | 5 | 9 | return $is_ready; | ||||
749 | } | ||||||
750 | |||||||
751 | 101 | 101 | 1 | 176 | sub skip { 0 } # success indicates to skip the step (and continue loop) | ||
752 | |||||||
753 | sub swap_template { | ||||||
754 | 72 | 72 | 1 | 409 | my ($self, $step, $file, $swap) = @_; | ||
755 | 72 | 171 | my $t = $self->__template_obj($step); | ||||
756 | 72 | 1125 | my $out = ''; | ||||
757 | 72 | 50 | 203 | $t->process($file, $swap, \$out) || die $t->error; | |||
758 | 72 | 60059 | return $out; | ||||
759 | } | ||||||
760 | |||||||
761 | sub __template_obj { | ||||||
762 | 72 | 72 | 94 | my ($self, $step) = @_; | |||
763 | 72 | 50 | 106 | my $args = $self->run_hook('template_args', $step) || {}; | |||
764 | 72 | 33 | 337 | $args->{'INCLUDE_PATH'} ||= $args->{'include_path'} || $self->template_path; | |||
33 | |||||||
765 | 72 | 179 | return $self->template_obj($args); | ||||
766 | } | ||||||
767 | |||||||
768 | sub validate { | ||||||
769 | 8 | 8 | 1 | 11 | my ($self, $step, $form) = @_; | ||
770 | 8 | 16 | my $hash = $self->__hash_validation($step); | ||||
771 | 8 | 100 | 66 | 68 | return 1 if ! ref($hash) || ! scalar keys %$hash; | ||
772 | |||||||
773 | 7 | 8 | my @validated_fields; | ||||
774 | 7 | 100 | 7 | if (my $err_obj = eval { $self->val_obj->validate($form, $hash, \@validated_fields) }) { | |||
7 | 22 | ||||||
775 | 4 | 19 | $self->add_errors($err_obj->as_hash({as_hash_join => " \n", as_hash_suffix => '_error'})); |
||||
776 | 4 | 44 | return 0; | ||||
777 | } | ||||||
778 | 3 | 50 | 10 | die "Step $step: $@" if $@; | |||
779 | |||||||
780 | 3 | 6 | foreach my $ref (@validated_fields) { # allow for the validation to give us some redirection | ||||
781 | 3 | 0 | 4 | $self->append_path( ref $_ ? @$_ : $_) if $_ = $ref->{'append_path'}; | |||
50 | |||||||
782 | 3 | 0 | 7 | $self->replace_path(ref $_ ? @$_ : $_) if $_ = $ref->{'replace_path'}; | |||
50 | |||||||
783 | 3 | 0 | 4 | $self->insert_path( ref $_ ? @$_ : $_) if $_ = $ref->{'insert_path'}; | |||
50 | |||||||
784 | } | ||||||
785 | |||||||
786 | 3 | 22 | return 1; | ||||
787 | } | ||||||
788 | |||||||
789 | 8 | 8 | 27 | sub __hash_validation { shift->run_hook('hash_validation', @_) } | |||
790 | |||||||
791 | 12 | 12 | 1 | 26 | sub validate_when_data { $_[0]->{'validate_when_data'} } | ||
792 | |||||||
793 | ###---------------------### | ||||||
794 | # authentication | ||||||
795 | |||||||
796 | sub navigate_authenticated { | ||||||
797 | 3 | 3 | 1 | 7 | my ($self, $args) = @_; | ||
798 | 3 | 50 | 7 | $self = $self->new($args) if ! ref $self; | |||
799 | 3 | 100 | 20 | croak "Cannot call navigate_authenticated method if default require_auth method is overwritten" | |||
800 | if $self->can('require_auth') != \&CGI::Ex::App::require_auth; | ||||||
801 | 2 | 7 | $self->require_auth(1); | ||||
802 | 2 | 6 | return $self->navigate; | ||||
803 | } | ||||||
804 | |||||||
805 | sub require_auth { | ||||||
806 | 116 | 116 | 1 | 145 | my $self = shift; | ||
807 | 116 | 50 | 66 | 217 | $self->{'require_auth'} = shift if @_ == 1 && (! defined($_[0]) || ref($_[0]) || $_[0] =~ /^[01]$/); | ||
100 | |||||||
808 | 116 | 100 | 323 | return $self->{'require_auth'} || 0; | |||
809 | } | ||||||
810 | |||||||
811 | 138 | 100 | 138 | 1 | 280 | sub is_authed { my $data = shift->auth_data; $data && ! $data->{'error'} } | |
138 | 349 | ||||||
812 | |||||||
813 | 4 | 1 | 0 | 24 | sub check_valid_auth { shift->_do_auth({login_print => sub {}, location_bounce => sub {}}) } | ||
4 | |||||||
814 | |||||||
815 | sub get_valid_auth { | ||||||
816 | 8 | 8 | 1 | 9 | my $self = shift; | ||
817 | return $self->_do_auth({ | ||||||
818 | login_print => sub { # use CGI::Ex::Auth - but use our formatting and printing | ||||||
819 | 7 | 7 | 13 | my ($auth, $template, $hash) = @_; | |||
820 | 7 | 13 | local $self->{'__login_file_print'} = $template; | ||||
821 | 7 | 11 | local $self->{'__login_hash_common'} = $hash; | ||||
822 | 7 | 37 | return $self->goto_step($self->login_step); | ||||
823 | } | ||||||
824 | 8 | 46 | }); | ||||
825 | } | ||||||
826 | |||||||
827 | sub _do_auth { | ||||||
828 | 12 | 12 | 19 | my ($self, $extra) = @_; | |||
829 | 12 | 100 | 18 | return $self->auth_data if $self->is_authed; | |||
830 | 11 | 50 | 16 | my $args = { %{ $self->auth_args || {} }, %{ $extra || {} } }; | |||
11 | 50 | 27 | |||||
11 | 123 | ||||||
831 | 11 | 33 | 67 | $args->{'script_name'} ||= $self->script_name; | |||
832 | 11 | 33 | 38 | $args->{'path_info'} ||= $self->path_info; | |||
833 | 11 | 33 | 49 | $args->{'cgix'} ||= $self->cgix; | |||
834 | 11 | 33 | 87 | $args->{'form'} ||= $self->form; | |||
835 | 11 | 33 | 63 | $args->{'cookies'} ||= $self->cookies; | |||
836 | 11 | 33 | 67 | $args->{'js_uri_path'} ||= $self->js_uri_path; | |||
837 | 11 | 50 | 3 | 59 | $args->{'get_pass_by_user'} ||= sub { my ($auth, $user) = @_; $self->get_pass_by_user($user, $auth) }; | ||
3 | 5 | ||||||
3 | 7 | ||||||
838 | 11 | 50 | 3 | 60 | $args->{'verify_user'} ||= sub { my ($auth, $user) = @_; $self->verify_user( $user, $auth) }; | ||
3 | 4 | ||||||
3 | 14 | ||||||
839 | 11 | 50 | 3 | 67 | $args->{'cleanup_user'} ||= sub { my ($auth, $user) = @_; $self->cleanup_user( $user, $auth) }; | ||
3 | 6 | ||||||
3 | 11 | ||||||
840 | |||||||
841 | 11 | 34 | my $obj = $self->auth_obj($args); | ||||
842 | 11 | 27 | my $resp = $obj->get_valid_auth; | ||||
843 | 4 | 10 | my $data = $obj->last_auth_data; | ||||
844 | 4 | 100 | 11 | delete $data->{'real_pass'} if defined $data; # data may be defined but false | |||
845 | 4 | 11 | $self->auth_data($data); # failed authentication may still have auth_data | ||||
846 | 4 | 100 | 66 | 20 | return ($resp && $data) ? $data : undef; | ||
847 | } | ||||||
848 | |||||||
849 | ###---------------------### | ||||||
850 | # default steps | ||||||
851 | |||||||
852 | 1 | 1 | 0 | 3 | sub js_require_auth { 0 } | ||
853 | sub js_run_step { # step that allows for printing javascript libraries that are stored in perls @INC. | ||||||
854 | 3 | 3 | 0 | 5 | my $self = shift; | ||
855 | 3 | 100 | 6 | my $path = $self->form->{'js'} || $self->path_info; | |||
856 | 3 | 100 | 16 | $self->cgix->print_js($path =~ m!^(?:/js/|/)?(\w+(?:/\w+)*\.js)$! ? $1 : ''); | |||
857 | 3 | 20 | $self->{'_no_post_navigate'} = 1; | ||||
858 | 3 | 4 | return 1; | ||||
859 | } | ||||||
860 | |||||||
861 | 3 | 3 | 6 | sub __forbidden_require_auth { 0 } | |||
862 | 3 | 50 | 3 | 7 | sub __forbidden_allow_morph { shift->allow_morph(@_) && 1 } | ||
863 | 3 | 3 | 8 | sub __forbidden_info_complete { 0 } # step that will be used the path method determines it is forbidden | |||
864 | 3 | 3 | 7 | sub __forbidden_hash_common { shift->stash } | |||
865 | 3 | 3 | 5 | sub __forbidden_file_print { \ "DeniedYou do not have access to the step \"[% forbidden_step.html %]\"" } |
|||
866 | |||||||
867 | 4 | 50 | 4 | 7 | sub __error_allow_morph { shift->allow_morph(@_) && 1 } | ||
868 | 6 | 6 | 48 | sub __error_info_complete { 0 } # step that is used by the default handle_error | |||
869 | 6 | 6 | 12 | sub __error_hash_common { shift->stash } | |||
870 | 4 | 4 | 6 | sub __error_file_print { \ "A fatal error occurredStep: \"[% error_step.html %]\"[% TRY; CONFIG DUMP => {header => 0}; DUMP error; END %]" } |
|||
871 | |||||||
872 | 7 | 7 | 14 | sub __login_require_auth { 0 } | |||
873 | 7 | 50 | 7 | 19 | sub __login_allow_morph { shift->allow_morph(@_) && 1 } | ||
874 | 7 | 7 | 19 | sub __login_info_complete { 0 } # step used by default authentication | |||
875 | 7 | 50 | 7 | 23 | sub __login_hash_common { shift->{'__login_hash_common'} || {error => "hash_common not set during default __login"} } | ||
876 | 7 | 50 | 7 | 18 | sub __login_file_print { shift->{'__login_file_print'} || \ "file_print not set during default __login [% login_error %]" } |
||
877 | |||||||
878 | 1; # Full documentation resides in CGI/Ex/App.pod |