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