]> Dogcows Code - chaz/p5-CGI-Ex/blob - lib/CGI/Ex/App.pm
552045a7b8ae7a8091f463a528bb95180d595007
[chaz/p5-CGI-Ex] / lib / CGI / Ex / App.pm
1 package CGI::Ex::App;
2
3 ### CGI Extended Application
4
5 ###----------------------------------------------------------------###
6 # Copyright 2004 - Paul Seamons #
7 # Distributed under the Perl Artistic License without warranty #
8 ###----------------------------------------------------------------###
9
10 ### See perldoc at bottom
11
12
13 use strict;
14 use vars qw($VERSION
15 $EXT_PRINT $EXT_VAL $BASE_DIR_REL $BASE_DIR_ABS $BASE_NAME_MODULE
16 $RECURSE_LIMIT
17 %CLEANUP_EXCLUDE);
18
19 $VERSION = '1.14';
20 use CGI::Ex::Dump qw(debug);
21
22 BEGIN {
23 ### Default file locations
24 ### these are used for the provided stub functions - if you are not
25 ### using the stub functions - then you won't need to worry about these
26 $EXT_PRINT ||= 'html';
27 $EXT_VAL ||= 'val';
28 $BASE_DIR_REL ||= ''; # relative path - stub methods will look in $BASE_DIR_REL/dir/of/content.html
29 $BASE_DIR_ABS ||= ''; # content should be found at "$BASE_DIR_ABS/$BASE_DIR_REL/dir/of/content.html"
30 $BASE_NAME_MODULE ||= ''; # the cgi name
31
32 ### list of modules to exclude during cleanup
33 ### this takes care of situations such as
34 ### template toolkits rules area which contains
35 ### a nested structure of rules and sub references.
36 $CLEANUP_EXCLUDE{'Template::Parser'} = 1;
37 }
38
39
40 ###----------------------------------------------------------------###
41
42 sub new {
43 my $class = shift || __PACKAGE__;
44 my $self = ref($_[0]) ? shift : {@_};
45 bless $self, $class;
46 $self->init;
47 return $self;
48 }
49
50 sub init {}
51
52 ###----------------------------------------------------------------###
53
54 sub navigate {
55 my $self = shift;
56 my $args = ref($_[0]) ? shift : {@_};
57 $self = $self->new($args) if ! ref $self;
58
59 eval {
60
61 ### a chance to do things at the very beginning
62 return $self if $self->pre_navigate;
63
64 ### run the step loop
65 eval {
66 local $self->{'__morph_lineage_start_index'} = $#{$self->{'__morph_lineage'} || []};
67 $self->nav_loop;
68 };
69 if ($@) {
70 ### rethrow the error unless we long jumped out of recursive nav_loop calls
71 die $@ if $@ ne "Long Jump\n";
72 }
73
74 ### one chance to do things at the very end
75 $self->post_navigate;
76
77 };
78
79 ### catch errors - if any
80 if ($@) {
81 $self->handle_error($@);
82 }
83
84 return $self;
85 }
86
87 sub nav_loop {
88 my $self = shift;
89
90 ### keep from an infinate nesting
91 local $self->{recurse} = $self->{recurse} || 0;
92 if ($self->{recurse} ++ >= $self->recurse_limit) {
93 my $err = "recurse_limit reached (".$self->recurse_limit.")";
94 $err .= " number of jumps (".$self->{jumps}.")" if ($self->{jumps} || 0) > 1;
95 die $err;
96 }
97
98 ### get the path (simple arrayref based thing)
99 my $path = $self->path;
100
101 ### allow for an early return
102 return if $self->pre_loop($path); # a true value means to abort the navigate
103
104 ### get a hash of valid paths (if any)
105 my $valid_steps = $self->valid_steps;
106
107 ### iterate on each step of the path
108 foreach ($self->{path_i} ||= 0;
109 $self->{path_i} <= $#$path;
110 $self->{path_i} ++) {
111 my $step = $path->[$self->{path_i}];
112 next if $step !~ /^[a-zA-Z_]\w*$/; # don't process the step if it contains odd characters
113
114 ### check if this is an allowed step
115 if ($valid_steps) {
116 if (! $valid_steps->{$step}
117 && $step ne $self->default_step
118 && $step ne 'forbidden') {
119 $self->stash->{'forbidden_step'} = $step;
120 $self->replace_path('forbidden');
121 next;
122 }
123 }
124
125 ### allow for becoming another package (allows for some steps in external files)
126 $self->morph($step);
127
128 ### run the guts of the step
129 my $status = $self->run_hook('run_step', $step);
130
131 $self->unmorph($step);
132
133 ### Allow for the run_step to intercept.
134 ### A true status means the run_step took over navigation.
135 return if $status;
136 }
137
138 ### allow for one exit point after the loop
139 return if $self->post_loop($path); # a true value means to abort the navigate
140
141 ### run the default step as a last resort
142 $self->insert_path($self->default_step);
143 $self->nav_loop; # go recursive
144
145 return;
146 }
147
148 sub pre_navigate {}
149
150 sub post_navigate {}
151
152 sub recurse_limit { shift->{'recurse_limit'} || $RECURSE_LIMIT || 15 }
153
154 sub run_step {
155 my $self = shift;
156 my $step = shift;
157
158 ### if the pre_step exists and returns true, exit the nav_loop
159 return 1 if $self->run_hook('pre_step', $step);
160
161 ### allow for skipping this step (but stay in the nav_loop)
162 return 0 if $self->run_hook('skip', $step);
163
164 ### see if we have complete valid information for this step
165 ### if so, do the next step
166 ### if not, get necessary info and print it out
167 if ( ! $self->run_hook('prepare', $step, 1)
168 || ! $self->run_hook('info_complete', $step)
169 || ! $self->run_hook('finalize', $step, 1)) {
170
171 ### show the page requesting the information
172 $self->run_hook('prepared_print', $step);
173
174 ### a hook after the printing process
175 $self->run_hook('post_print', $step);
176
177 return 2;
178 }
179
180 ### a hook before end of loop
181 ### if the post_step exists and returns true, exit the nav_loop
182 return 1 if $self->run_hook('post_step', $step);
183
184 ### let the nav_loop continue searching the path
185 return 0;
186 }
187
188 ### standard functions for printing - gather information
189 sub prepared_print {
190 my $self = shift;
191 my $step = shift;
192
193 my $hash_base = $self->run_hook('hash_base', $step);
194 my $hash_comm = $self->run_hook('hash_common', $step);
195 my $hash_form = $self->run_hook('hash_form', $step);
196 my $hash_fill = $self->run_hook('hash_fill', $step);
197 my $hash_swap = $self->run_hook('hash_swap', $step);
198 my $hash_errs = $self->run_hook('hash_errors', $step);
199 $_ ||= {} foreach $hash_base, $hash_comm, $hash_form, $hash_fill, $hash_swap, $hash_errs;
200
201 ### fix up errors
202 $hash_errs->{$_} = $self->format_error($hash_errs->{$_})
203 foreach keys %$hash_errs;
204 $hash_errs->{has_errors} = 1 if scalar keys %$hash_errs;
205
206 ### layer hashes together
207 my $fill = {%$hash_form, %$hash_base, %$hash_comm, %$hash_fill};
208 my $swap = {%$hash_form, %$hash_base, %$hash_comm, %$hash_swap, %$hash_errs};
209 $fill = {} if $self->no_fill($step);
210
211 ### run the print hook - passing it the form and fill info
212 $self->run_hook('print', $step, undef,
213 $swap, $fill);
214 }
215
216 sub no_fill { shift->{'no_fill'} }
217
218 sub exit_nav_loop {
219 my $self = shift;
220
221 ### undo morphs
222 if (my $ref = $self->{'__morph_lineage'}) {
223 ### use the saved index - this allows for early "morphers" to only get rolled back so far
224 my $index = $self->{'__morph_lineage_start_index'};
225 $index = -1 if ! defined $index;
226 $self->unmorph while $#$ref != $index;
227 }
228
229 ### long jump back
230 die "Long Jump\n";
231 }
232
233 sub jump {
234 my $self = shift;
235 my $i = ($#_ == -1) ? 1 : shift;
236 my $path = $self->path;
237 my $path_i = $self->{path_i};
238 die "Can't jump if nav_loop not started" if ! defined $path_i;
239
240 ### validate where we are jumping to
241 if ($i =~ /^\w+$/) {
242 if ($i eq 'FIRST') {
243 $i = - $path_i - 1;
244 } elsif ($i eq 'LAST') {
245 $i = $#$path - $path_i;
246 } elsif ($i eq 'NEXT') {
247 $i = 1;
248 } elsif ($i eq 'CURRENT') {
249 $i = 0;
250 } elsif ($i eq 'PREVIOUS') {
251 $i = -1;
252 } else { # look for a step by that name
253 for (my $j = $#$path; $j >= 0; $j --) {
254 if ($path->[$j] eq $i) {
255 $i = $j - $path_i;
256 last;
257 }
258 }
259 }
260 }
261 if ($i !~ /^-?\d+$/) {
262 require Carp;
263 Carp::croak("Invalid jump index ($i)");
264 }
265
266 ### manipulate the path to contain the new jump location
267 my @replace;
268 my $cut_i = $path_i + $i;
269 if ($cut_i > $#$path) {
270 push @replace, $self->default_step;
271 } elsif ($cut_i < 0) {
272 push @replace, @$path;
273 } else {
274 push @replace, @$path[$cut_i .. $#$path];
275 }
276 $self->replace_path(@replace);
277
278 ### record the number of jumps
279 $self->{jumps} ||= 0;
280 $self->{jumps} ++;
281
282 ### run the newly fixed up path (recursively)
283 $self->{path_i} ++; # move along now that the path is updated
284 $self->nav_loop;
285 $self->exit_nav_loop;
286 }
287
288 sub default_step {
289 my $self = shift;
290 return $self->{'default_step'} || 'main';
291 }
292
293 ###----------------------------------------------------------------###
294
295 sub step_key {
296 my $self = shift;
297 return $self->{'step_key'} || 'step';
298 }
299
300 ### determine the path to follow
301 sub path {
302 my $self = shift;
303 return $self->{path} ||= do {
304 my @path = (); # default to empty path
305 my $step_key = $self->step_key;
306
307 if (my $step = $self->form->{$step_key}) {
308 push @path, $step;
309 } elsif ($ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} =~ m|^/(\w+)|) {
310 push @path, lc($1);
311 }
312
313 \@path; # return of the do
314 };
315 }
316
317 ### really should only be used during initialization
318 sub set_path {
319 my $self = shift;
320 my $path = $self->{path} ||= [];
321 die "Cannot call set_path after the navigation loop has begun" if $self->{path_i};
322 splice @$path, 0, $#$path + 1, @_; # change entries in the ref
323 }
324
325 ### legacy - same as append_path
326 sub add_to_path {
327 my $self = shift;
328 push @{ $self->path }, @_;
329 }
330
331 ### append entries onto the end
332 sub append_path {
333 my $self = shift;
334 push @{ $self->path }, @_;
335 }
336
337 ### replace all entries that are left
338 sub replace_path {
339 my $self = shift;
340 my $ref = $self->path;
341 my $i = $self->{path_i} || 0;
342 if ($i + 1 > $#$ref) {
343 push @$ref, @_;
344 } else {
345 splice(@$ref, $i + 1, $#$ref - $i, @_); # replace remaining entries
346 }
347 }
348
349 ### insert more steps into the current path
350 sub insert_path {
351 my $self = shift;
352 my $ref = $self->path;
353 my $i = $self->{path_i} || 0;
354 if ($i + 1 > $#$ref) {
355 push @$ref, @_;
356 } else {
357 splice(@$ref, $i + 1, 0, @_); # insert a path at the current location
358 }
359 }
360
361 ### a hash of paths that are allowed, default undef is all
362 sub valid_steps {}
363
364 ###----------------------------------------------------------------###
365 ### allow for checking where we are in the path
366
367 sub step_by_path_index {
368 my $self = shift;
369 my $i = shift || 0;
370 my $ref = $self->path;
371 return '' if $i < 0;
372 return $self->default_step if $i > $#$ref;
373 return $ref->[$i];
374 }
375
376 sub previous_step {
377 my $self = shift;
378 die "previous_step is readonly" if $#_ != -1;
379 return $self->step_by_path_index( ($self->{path_i} || 0) - 1 );
380 }
381
382 sub current_step {
383 my $self = shift;
384 die "current_step is readonly" if $#_ != -1;
385 return $self->step_by_path_index( ($self->{path_i} || 0) );
386 }
387
388 sub next_step {
389 my $self = shift;
390 die "next_step is readonly" if $#_ != -1;
391 return $self->step_by_path_index( ($self->{path_i} || 0) + 1 );
392 }
393
394 sub last_step {
395 my $self = shift;
396 die "last_step is readonly" if $#_ != -1;
397 return $self->step_by_path_index( $#{ $self->path } );
398 }
399
400 sub first_step {
401 my $self = shift;
402 die "first_step is readonly" if $#_ != -1;
403 return $self->step_by_path_index( 0 );
404 }
405
406 ###----------------------------------------------------------------###
407
408 sub pre_loop {}
409 sub post_loop {}
410
411 ### return the appropriate hook to call
412 sub hook {
413 my $self = shift;
414 my $hook = shift || do { require Carp; Carp::confess("Missing hook name") };
415 my $step = shift || '';
416 my $default = shift;
417 my $hist = $self->history;
418 my $code;
419 if ($step && ($code = $self->can("${step}_${hook}"))) {
420 push @$hist, "$step - $hook - ${step}_${hook}";
421 return $code;
422 } elsif ($code = $self->can($hook)) {
423 push @$hist, "$step - $hook - $hook";
424 return $code;
425 } elsif (UNIVERSAL::isa($default, 'CODE')) {
426 push @$hist, "$step - $hook - DEFAULT CODE";
427 return $default;
428 } elsif ($default) {
429 push @$hist, "$step - $hook - DEFAULT";
430 return sub { return $default };
431 } else {
432 return sub {};
433 }
434 }
435
436 ### get and call the appropriate hook
437 sub run_hook {
438 my $self = shift;
439 my $hook = shift;
440 my $step = shift;
441 my $default = shift;
442 my $code = $self->hook($hook, $step, $default);
443 return $self->$code($step, @_);
444 }
445
446 sub history {
447 return shift->{'history'} ||= [];
448 }
449
450 ### default die handler - show what happened and die (so its in the error logs)
451 sub handle_error {
452 my $self = shift;
453 my $err = shift;
454 debug $err, $self->path, $self->history;
455 die $err;
456 }
457
458 ###----------------------------------------------------------------###
459 ### utility modules for jeckyl/hyde on self
460
461 sub allow_morph {
462 my $self = shift;
463 return $self->{'allow_morph'} ? 1 : 0;
464 }
465
466 sub allow_nested_morph {
467 my $self = shift;
468 return $self->{'allow_nested_morph'} ? 1 : 0;
469 }
470
471 sub morph {
472 my $self = shift;
473 my $step = shift || return;
474 return if ! (my $allow = $self->allow_morph); # not true
475
476 ### place to store the lineage
477 my $lin = $self->{'__morph_lineage'} ||= [];
478 my $cur = ref $self; # what are we currently
479 push @$lin, $cur; # store so subsequent unmorph calls can do the right thing
480 my $hist = $self->history;
481 push @$hist, "$step - morph - morph";
482 my $sref = \$hist->[-1]; # get ref so we can add more info in a moment
483
484 if (ref($allow) && ! $allow->{$step}) { # hash - but no step - record for unbless
485 $$sref .= " - not allowed to morph to that step";
486 return;
487 }
488
489 ### make sure we haven't already been reblessed
490 if ($#$lin != 0 # is this the second morph call
491 && (! ($allow = $self->allow_nested_morph) # not true
492 || (ref($allow) && ! $allow->{$step}) # hash - but no step
493 )) {
494 $$sref .= $allow ? " - not allowed to nested_morph to that step" : " - nested_morph disabled";
495 return; # just return - don't die so that we can morph early
496 }
497
498 ### if we are not already that package - bless us there
499 my $new = $self->run_hook('morph_package', $step);
500 if ($cur ne $new) {
501 my $file = $new .'.pm';
502 $file =~ s|::|/|g;
503 if (UNIVERSAL::can($new, 'can') # check if the package space exists
504 || eval { require $file }) { # check for a file that holds this package
505 ### become that package
506 bless $self, $new;
507 $$sref .= " - changed $cur to $new";
508 if (my $method = $self->can('fixup_after_morph')) {
509 $self->$method($step);
510 }
511 } else {
512 if ($@) {
513 if ($@ =~ /^\s*(Can\'t locate \S+ in \@INC)/) { # let us know what happened
514 $$sref .= " - failed from $cur to $new: $1";
515 } else {
516 $$sref .= " - failed from $cur to $new: $@";
517 my $err = "Trouble while morphing to $file: $@";
518 debug $err;
519 warn $err;
520 }
521 }
522 }
523 }
524
525 }
526
527 sub unmorph {
528 my $self = shift;
529 my $step = shift || '__no_step';
530 my $lin = $self->{'__morph_lineage'} || return;
531 my $cur = ref $self;
532 my $prev = pop(@$lin) || die "unmorph called more times than morph - current ($cur)";
533
534 ### if we are not already that package - bless us there
535 my $hist = $self->history;
536 if ($cur ne $prev) {
537 if (my $method = $self->can('fixup_before_unmorph')) {
538 $self->$method($step);
539 }
540 bless $self, $prev;
541 push @$hist, "$step - unmorph - unmorph - changed from $cur to $prev";
542 } else {
543 push @$hist, "$step - unmorph - unmorph - already isa $cur";
544 }
545
546 return $self;
547 }
548
549 ###----------------------------------------------------------------###
550 ### allow for cleanup including deep nested objects
551
552 sub cleanup {
553 my $self = shift;
554 ref($self)->cleanup_cross_references($self);
555 }
556
557 sub cleanup_cross_references {
558 my $class = shift;
559 my $self = shift;
560 my $seen = shift || {};
561 return if $seen->{$self}; # prevent recursive checking
562 $seen->{$self} = 1;
563 return if $CLEANUP_EXCLUDE{ ref($self) };
564 if (UNIVERSAL::isa($self, 'HASH')) {
565 require Scalar::Util; # first self will always be hash
566 foreach my $key (keys %$self) {
567 next if ! $self->{$key};
568 $class->cleanup_cross_references($self->{$key}, $seen);
569 # weaken and remove blessed objects
570 # this will clober objects in global caches that are referenced in the structure
571 # so beware (that means weaken your cached references)
572 if (Scalar::Util::blessed($self->{$key})
573 && ! Scalar::Util::isweak($self->{$key})) {
574 Scalar::Util::weaken($self->{$key});
575 $self->{$key} = undef;
576 } elsif (UNIVERSAL::isa($self->{$key}, 'CODE')) {
577 $self->{$key} = undef;
578 }
579 }
580 } elsif (UNIVERSAL::isa($self, 'ARRAY')) {
581 for my $key (0 .. $#$self) {
582 next if ! $self->[$key];
583 $class->cleanup_cross_references($self->[$key], $seen);
584 if (Scalar::Util::blessed($self->[$key])
585 && ! Scalar::Util::isweak($self->[$key])) {
586 Scalar::Util::weaken($self->[$key]);
587 $self->[$key] = undef;
588 } elsif (UNIVERSAL::isa($self->[$key], 'CODE')) {
589 $self->[$key] = undef;
590 }
591 }
592 }
593 }
594
595 ###----------------------------------------------------------------###
596 ### a few standard base accessors
597
598 sub form {
599 my $self = shift;
600 if ($#_ != -1) {
601 $self->{form} = shift || die "Invalid form";
602 }
603 return $self->{form} ||= $self->cgix->get_form;
604 }
605
606 sub cookies {
607 my $self = shift;
608 if ($#_ != -1) {
609 $self->{cookies} = shift || die "Invalid cookies";
610 }
611 return $self->{cookies} ||= $self->cgix->get_cookies;
612 }
613
614 sub cgix {
615 my $self = shift;
616 return $self->{cgix} ||= do {
617 my $args = shift || {};
618 require CGI::Ex;
619 CGI::Ex->new($args); # return of the do
620 };
621 }
622
623 sub set_cgix {
624 my $self = shift;
625 $self->{cgix} = shift;
626 }
627
628 sub vob {
629 my $self = shift;
630 return $self->{vob} ||= do {
631 my $args = shift || {};
632 $args->{cgix} ||= $self->cgix;
633 require CGI::Ex::Validate;
634 CGI::Ex::Validate->new($args); # return of the do
635 };
636 }
637
638 sub set_vob {
639 my $self = shift;
640 $self->{vob} = shift;
641 }
642
643 sub auth {
644 my $self = shift;
645 return $self->{auth} ||= do {
646 my $args = shift || {};
647 $args->{cgix} ||= $self->cgix,
648 $args->{form} ||= $self->form,
649 $args->{cookies} ||= $self->cookies,
650 require CGI::Ex::Auth;
651 CGI::Ex::Auth->new($args); # return of the do
652 };
653 }
654
655 sub set_auth {
656 my $self = shift;
657 $self->{auth} = shift;
658 }
659
660 ### provide a place for placing variables
661 sub stash {
662 my $self = shift;
663 return $self->{'stash'} ||= {};
664 }
665
666 ### allow for adding arbitrary values to self
667 sub add_property {
668 my $self = shift;
669 my $prop = shift;
670 my $key = '__prop_'. $prop;
671 my $name = __PACKAGE__ ."::". $prop;
672 no strict 'refs';
673 *$name = sub : lvalue {
674 my $self = shift;
675 $self->{$key} = shift() if $#_ != -1;
676 $self->{$key};
677 } if ! defined &$name;
678 $self->$prop(shift()) if $#_ != -1;
679 }
680
681 ###----------------------------------------------------------------###
682 ### js_validation items
683
684 ### creates javascript suitable for validating the form
685 sub js_validation {
686 my $self = shift;
687 my $step = shift;
688 return '' if $self->ext_val eq 'htm'; # let htm validation do it itself
689
690 my $form_name = shift || $self->run_hook('form_name', $step);
691 my $hash_val = shift || $self->run_hook('hash_validation', $step, {});
692 my $js_uri = $self->js_uri_path;
693 return '' if UNIVERSAL::isa($hash_val, 'HASH') && ! scalar keys %$hash_val
694 || UNIVERSAL::isa($hash_val, 'ARRAY') && $#$hash_val == -1;
695
696 return $self->vob->generate_js($hash_val, $form_name, $js_uri);
697 }
698
699 ### where to find the javascript files
700 ### default to using this script as a handler
701 sub js_uri_path {
702 my $self = shift;
703 my $script = $ENV{'SCRIPT_NAME'} || die "Missing SCRIPT_NAME";
704 return ($self->can('path') == \&CGI::Ex::App::path)
705 ? $script . '/js' # try to use a cache friendly URI (if path is our own)
706 : $script . '?'.$self->step_key.'=js&js='; # use one that works with more paths
707 }
708
709 ### name to attach js validation to
710 sub form_name { 'theform' }
711
712 ### provide some rudimentary javascript support
713 ### if valid_steps is defined - it should include "js"
714 sub js_run_step {
715 my $self = shift;
716
717 ### make sure path info looks like /js/CGI/Ex/foo.js
718 my $file = $self->form->{'js'} || $ENV{'PATH_INFO'} || '';
719 $file = ($file =~ m!^(?:/js/|/)?(\w+(?:/\w+)*\.js)$!) ? $1 : '';
720
721 $self->cgix->print_js($file);
722 return 1; # intercepted
723 }
724
725 ###----------------------------------------------------------------###
726 ### implementation specific subs
727
728 sub template_args {
729 my $self = shift;
730 my $step = shift;
731 return {
732 INCLUDE_PATH => $self->base_dir_abs,
733 };
734 }
735
736 sub print {
737 my $self = shift;
738 my $step = shift;
739 my $swap = shift;
740 my $fill = shift;
741
742 ### get a filename relative to base_dir_abs
743 my $file = $self->run_hook('file_print', $step);
744
745 require Template;
746 my $t = Template->new($self->template_args($step));
747
748 ### process the document
749 my $out = '';
750 my $status = $t->process($file, $swap, \$out) || die $Template::ERROR;
751
752 ### fill in any forms
753 $self->cgix->fill(\$out, $fill) if $fill && ! $self->{no_fill};
754
755 ### now print
756 $self->cgix->print_content_type();
757 print $out;
758 }
759
760 sub base_dir_rel {
761 my $self = shift;
762 $self->{base_dir_rel} = shift if $#_ != -1;
763 return $self->{base_dir_rel} ||= $BASE_DIR_REL;
764 }
765
766 sub base_dir_abs {
767 my $self = shift;
768 $self->{base_dir_abs} = shift if $#_ != -1;
769 return $self->{base_dir_abs} || $BASE_DIR_ABS
770 || die "\$BASE_DIR_ABS not set for use in stub functions";
771 }
772
773 sub ext_val {
774 my $self = shift;
775 $self->{ext_val} = shift if $#_ != -1;
776 return $self->{ext_val} || $EXT_VAL || die "\$EXT_VAL not set for use in stub functions";
777 }
778
779 sub ext_print {
780 my $self = shift;
781 $self->{ext_print} = shift if $#_ != -1;
782 return $self->{ext_print} || $EXT_PRINT || die "\$EXT_PRINT not set for use in stub functions";
783 }
784
785 sub has_errors {
786 my $self = shift;
787 return 1 if scalar keys %{ $self->hash_errors };
788 }
789
790 sub format_error {
791 my $self = shift;
792 my $error = shift;
793 # return $error if $error =~ /<span/i;
794 # return "<span class=\"error\">$error</span>";
795 }
796
797 ###----------------------------------------------------------------###
798 ### default stub subs
799
800 ### used for looking up a module to morph into
801 sub morph_package {
802 my $self = shift;
803 my $step = shift || '';
804 my $cur = ref $self; # default to using self as the base for morphed modules
805 my $new = $cur .'::'. $step;
806 $new =~ s/(\b|_+)(\w)/\u$2/g; # turn Foo::my_step_name into Foo::MyStepName
807 return $new;
808 }
809
810 sub base_name_module {
811 my $self = shift;
812 $self->{base_name_module} = shift if $#_ != -1;
813 return $self->{base_name_module} ||= $BASE_NAME_MODULE;
814 }
815
816 ### used for looking up template content
817 sub name_module {
818 my $self = shift;
819 my $step = shift || '';
820 my $name;
821 if ($name = $self->base_name_module) {
822 return $name;
823 } else {
824 return ($0 =~ m/(\w+)(\.\w+)?$/) ? $1 # allow for cgi-bin/foo or cgi-bin/foo.pl
825 : die "Couldn't determine module name from \"name_module\" lookup ($step)";
826 }
827 }
828
829 ### which file is used for templating
830 sub file_print {
831 my $self = shift;
832 my $step = shift;
833
834 my $base_dir_rel = $self->base_dir_rel;
835 my $module = $self->run_hook('name_module', $step);
836 my $_step = $self->run_hook('name_step', $step, $step);
837 my $ext = $self->ext_print;
838
839 return "$base_dir_rel/$module/$_step.$ext";
840 }
841
842 ### which file is used for validation
843 sub file_val {
844 my $self = shift;
845 my $step = shift;
846
847 my $base_dir = $self->base_dir_rel;
848 my $module = $self->run_hook('name_module', $step);
849 my $_step = $self->run_hook('name_step', $step, $step);
850 my $ext = $self->ext_val;
851
852 ### get absolute if necessary
853 if ($base_dir !~ m|^/|) {
854 $base_dir = $self->base_dir_abs . "/$base_dir";
855 }
856
857 return "$base_dir/$module/$_step.$ext";
858 }
859
860
861 sub info_complete {
862 my $self = shift;
863 my $step = shift;
864
865 return 0 if ! $self->run_hook('ready_validate', $step);
866
867 return $self->run_hook('validate', $step);
868 }
869
870 sub ready_validate {
871 my $self = shift;
872 my $step = shift;
873
874 ### could do a slightly more complex test
875 return 0 if ! $ENV{REQUEST_METHOD} || $ENV{REQUEST_METHOD} ne 'POST';
876 return 1;
877 }
878
879 sub set_ready_validate {
880 my $self = shift;
881 my $ready = shift;
882 $ENV{REQUEST_METHOD} = ($ready) ? 'POST' : 'GET';
883 }
884
885 sub validate {
886 my $self = shift;
887 my $step = shift;
888 my $form = shift || $self->form;
889 my $hash = $self->run_hook('hash_validation', $step, {});
890 my $what_was_validated = [];
891
892 my $eob = eval { $self->vob->validate($form, $hash, $what_was_validated) };
893 if (! $eob && $@) {
894 die "Step $step: $@";
895 }
896
897 ### had an error - store the errors and return false
898 if ($eob) {
899 $self->add_errors($eob->as_hash({
900 as_hash_join => "<br>\n",
901 as_hash_suffix => '_error',
902 }));
903 return 0;
904 }
905
906 ### allow for the validation to give us some redirection
907 my $val;
908 OUTER: foreach my $ref (@$what_was_validated) {
909 foreach my $method (qw(append_path replace_path insert_path)) {
910 next if ! ($val = $ref->{$method});
911 $self->$method(ref $val ? @$val : $val);
912 last OUTER;
913 }
914 }
915
916 return 1;
917 }
918
919 ### allow for using ConfUtil instead of yaml
920 sub hash_validation {
921 my $self = shift;
922 my $step = shift;
923 return $self->{hash_validation}->{$step} ||= do {
924 my $hash;
925 my $file = $self->run_hook('file_val', $step);
926
927 ### allow for returning the validation hash in the filename
928 ### a scalar ref means it is a yaml document to be read by get_validation
929 if (ref($file) && ! UNIVERSAL::isa($file, 'SCALAR')) {
930 $hash = $file;
931
932 ### read the file - it it fails - errors should shown in the error logs
933 } elsif ($file) {
934 $hash = eval { $self->vob->get_validation($file) } || {};
935
936 } else {
937 $hash = {};
938 }
939
940 $hash; # return of the do
941 };
942 }
943
944 sub hash_base {
945 my ($self, $step) = @_;
946 return $self->{hash_base} ||= {
947 script_name => $ENV{'SCRIPT_NAME'} || $0,
948 path_info => $ENV{'PATH_INFO'} || '',
949 js_validation => sub { $self->run_hook('js_validation', $step, shift) },
950 form_name => sub { $self->run_hook('form_name', $step) },
951 };
952 }
953
954 sub hash_common { shift->{'hash_common'} ||= {} }
955 sub hash_form { shift->form }
956 sub hash_fill { shift->{'hash_fill'} ||= {} }
957 sub hash_swap { shift->{'hash_swap'} ||= {} }
958 sub hash_errors { shift->{'hash_errors'} ||= {} }
959
960 sub add_errors {
961 my $self = shift;
962 my $hash = $self->hash_errors;
963 my $args = ref($_[0]) ? shift : {@_};
964 foreach my $key (keys %$args) {
965 my $_key = ($key =~ /error$/) ? $key : "${key}_error";
966 if ($hash->{$_key}) {
967 $hash->{$_key} .= '<br>' . $args->{$key};
968 } else {
969 $hash->{$_key} = $args->{$key};
970 }
971 }
972 $hash->{'has_errors'} = 1;
973 }
974
975 sub add_to_errors { shift->add_errors(@_) }
976 sub add_to_swap { my $self = shift; $self->add_to_hash($self->hash_swap, @_) }
977 sub add_to_fill { my $self = shift; $self->add_to_hash($self->hash_fill, @_) }
978 sub add_to_form { my $self = shift; $self->add_to_hash($self->hash_form, @_) }
979 sub add_to_common { my $self = shift; $self->add_to_hash($self->hash_common, @_) }
980 sub add_to_base { my $self = shift; $self->add_to_hash($self->hash_base, @_) }
981
982 sub add_to_hash {
983 my $self = shift;
984 my $old = shift;
985 my $new = shift;
986 $new = {$new, @_} if ! ref $new; # non-hashref
987 $old->{$_} = $new->{$_} foreach keys %$new;
988 }
989
990 ###----------------------------------------------------------------###
991
992 sub forbidden_info_complete { 0 }
993
994 sub forbidden_file_print {
995 my $self = shift;
996 my $step = $self->stash->{'forbidden_step'};
997 my $str = "You do not have access to \"$step\"";
998 return \$str;
999 }
1000
1001 ###----------------------------------------------------------------###
1002
1003 1;
1004
1005 __END__
1006
1007 =head1 NAME
1008
1009 CGI::Ex::App - Full featured (within reason) application builder.
1010
1011 =head1 DESCRIPTION
1012
1013 Fill in the blanks and get a ready made CGI. This module is somewhat
1014 similar in spirit to CGI::Application, CGI::Path, and CGI::Builder and any
1015 other "CGI framework." As with the others, CGI::Ex::App tries to do as
1016 much as possible, in a simple manner, without getting in the
1017 developer's way. Your milage may vary.
1018
1019 =head1 SYNOPSIS
1020
1021 More examples will come with time. Here are the basics for now.
1022
1023 #!/usr/bin/perl -w
1024
1025 MyApp->navigate;
1026 # OR you could do the following which cleans
1027 # circular references - useful for a mod_perl situation
1028 # MyApp->navigate->cleanup;
1029 exit;
1030
1031 package MyApp;
1032 use strict;
1033 use base qw(CGI::Ex::App);
1034 use CGI::Ex::Dump qw(debug);
1035
1036 sub valid_steps { return {success => 1, js => 1} }
1037 # default_step (main) is a valid path
1038 # note the inclusion of js step to allow the
1039 # javascript scripts in js_validation to function properly.
1040
1041 # base_dir_abs is only needed if default print is used
1042 # template toolkit needs an INCLUDE_PATH
1043 sub base_dir_abs { '/tmp' }
1044
1045 sub main_file_print {
1046 # reference to string means ref to content
1047 # non-reference means filename
1048 return \ "<h1>Main Step</h1>
1049 <form method=post name=[% form_name %]>
1050 <input type=text name=foo>
1051 <span style='color:red' id=foo_error>[% foo_error %]</span><br>
1052 <input type=submit>
1053 </form>
1054 [% js_validation %]
1055 <a href='[% script_name %]?step=foo'>Link to forbidden step</a>
1056 ";
1057 }
1058
1059 sub post_print {
1060 debug shift->history;
1061 } # show what happened
1062
1063 sub main_file_val {
1064 # reference to string means ref to yaml document
1065 # non-reference means filename
1066 return \ "foo:
1067 required: 1
1068 min_len: 2
1069 max_len: 20
1070 match: 'm/^([a-z]\\d)+[a-z]?\$/'
1071 match_error: Characters must alternate letter digit letter.
1072 \n";
1073 }
1074
1075 sub main_finalize {
1076 my $self = shift;
1077
1078 debug $self->form, "Do something useful with form here";
1079
1080 ### add success step
1081 $self->add_to_swap({success_msg => "We did something"});
1082 $self->append_path('success');
1083 $self->set_ready_validate(0);
1084 return 1;
1085 }
1086
1087 sub success_file_print {
1088 \ "<h1>Success Step</h1> All done.<br>
1089 ([% success_msg %])<br>
1090 (foo = [% foo %])";
1091 }
1092
1093 ### not necessary - this is the default hash_base
1094 sub hash_base { # used to include js_validation
1095 my ($self, $step) = @_;
1096 return $self->{hash_base} ||= {
1097 script_name => $ENV{SCRIPT_NAME} || '',
1098 js_validation => sub { $self->run_hook('js_validation', $step) },
1099 form_name => sub { $self->run_hook('form_name', $step) },
1100 };
1101 }
1102
1103 __END__
1104
1105 Note: This example would be considerably shorter if the html file
1106 (file_print) and the validation file (file_val) had been placed in
1107 separate files. Though CGI::Ex::App will work "out of the box" as
1108 shown it is more probable that any platform using it will customize
1109 the various hooks to their own tastes (for example, switching print to
1110 use a system other than Template::Toolkit).
1111
1112 =head1 HOOKS / METHODS
1113
1114 CGI::Ex::App works on the principles of hooks which are essentially
1115 glorified method lookups. When a hook is called, CGI::Ex::App will
1116 look for a corresponding method call for that hook for the current
1117 step name. See the discussion under the method named "hook" for more
1118 details. The methods listed below are normal method calls.
1119 Hooks and methods are looked for in the following order:
1120
1121 =over 4
1122
1123 =item Method C<-E<gt>new>
1124
1125 Object creator. Takes a hash or hashref.
1126
1127 =item Method C<-E<gt>init>
1128
1129 Called by the default new method. Allows for any object
1130 initilizations.
1131
1132 =item Method C<-E<gt>form>
1133
1134 Returns a hashref of the items passed to the CGI. Returns
1135 $self->{form}. Defaults to CGI::Ex::get_form.
1136
1137 =item Method C<-E<gt>navigate>
1138
1139 Takes a class name or a CGI::Ex::App object as arguments. If a class
1140 name is given it will instantiate an object by that class. All returns
1141 from navigate will return the object.
1142
1143 The method navigate is essentially a safe wrapper around the ->nav_loop
1144 method. It will catch any dies and pass them to ->handle_error.
1145
1146 =item Method C<-E<gt>nav_loop>
1147
1148 This is the main loop runner. It figures out the current path
1149 and runs all of the appropriate hooks for each step of the path. If
1150 nav_loop runs out of steps to run (which happens if no path is set, or if
1151 all other steps run successfully), it will insert the ->default_step into
1152 the path and run nav_loop again (recursively). This way a step is always
1153 assured to run. There is a method ->recurse_limit (default 15) that
1154 will catch logic errors (such as inadvertently running the same
1155 step over and over and over).
1156
1157 The basic outline of navigation is as follows (the default actions for hooks
1158 are shown):
1159
1160 navigate {
1161 eval {
1162 ->pre_navigate
1163 ->nav_loop
1164 ->post_navigate
1165 }
1166 # dying errors will run the ->handle_error method
1167 }
1168
1169
1170 nav_loop {
1171 ->path (get the path steps)
1172 # DEFAULT ACTION
1173 # look in $ENV{'PATH_INFO'}
1174 # look in ->form for ->step_key
1175
1176 ->pre_loop
1177 # navigation stops if true
1178
1179 ->valid_steps (get list of valid paths)
1180
1181 foreach step of path {
1182
1183 # check that path is valid
1184
1185 ->morph
1186 # DEFAULT ACTION
1187 # check ->allow_morph
1188 # check ->allow_nested_morph
1189 # ->morph_package (hook - get the package to bless into)
1190 # ->fixup_after_morph if morph_package exists
1191
1192 ->run_step (hook)
1193
1194 ->unmorph
1195 # DEFAULT ACTION
1196 # ->fixup_before_unmorph if blessed to previous package
1197
1198 # exit loop if ->run_step returned true (intercepted)
1199
1200 } end of step foreach
1201
1202 ->post_loop
1203 # navigation stops if true
1204
1205 ->default_step (inserted into path at current location)
1206 ->nav_loop (called again recursively)
1207
1208 } end of nav_loop
1209
1210
1211 run_step {
1212 ->pre_step (hook)
1213 # exits nav_loop if true
1214
1215 ->skip (hook)
1216 # skips this step if true (stays in nav_loop)
1217
1218 ->prepare (hook - defaults to true)
1219
1220 ->info_complete (hook - ran if prepare was true)
1221 # DEFAULT ACTION
1222 # ->ready_validate (hook)
1223 # return false if ! ready_validate
1224 # ->validate (hook)
1225 # ->hash_validation (hook)
1226 # ->file_val (hook - uses base_dir_rel, name_module, name_step, ext_val)
1227 # uses CGI::Ex::Validate to validate the hash
1228 # returns true if validate is true
1229
1230 ->finalize (hook - defaults to true - ran if prepare and info_complete were true)
1231
1232 if ! ->prepare || ! ->info_complete || ! ->finalize {
1233 ->prepared_print
1234 # DEFAULT ACTION
1235 # ->hash_base (hook)
1236 # ->hash_common (hook)
1237 # ->hash_form (hook)
1238 # ->hash_fill (hook)
1239 # ->hash_swap (hook)
1240 # ->hash_errors (hook)
1241 # merge form, base, common, and fill into merged fill
1242 # merge form, base, common, swap, and errors into merged swap
1243 # ->print (hook - passed current step, merged swap hash, and merged fill)
1244 # DEFAULT ACTION
1245 # ->file_print (hook - uses base_dir_rel, name_module, name_step, ext_print)
1246 # ->template_args
1247 # Processes the file with Template Toolkit
1248 # Fills the any forms with CGI::Ex::Fill
1249 # Prints headers and the content
1250
1251 ->post_print (hook - used for anything after the print process)
1252
1253 # return true to exit from nav_loop
1254 }
1255
1256 ->post_step (hook)
1257 # exits nav_loop if true
1258
1259 } end of run_step
1260
1261
1262 =item Method C<-E<gt>pre_navigate>
1263
1264 Called from within navigate. Called before the nav_loop method is started.
1265 If a true value is returned then navigation is skipped (the nav_loop is never
1266 started).
1267
1268 =item Method C<-E<gt>post_navigate>
1269
1270 Called from within navigate. Called after the nav_loop has finished running.
1271 Will only run if there were no errors which died during the nav_loop
1272 process.
1273
1274 =item Method C<-E<gt>handle_error>
1275
1276 If anything dies during execution, handle_error will be called with
1277 the error that had happened. Default is to debug the error and path
1278 history.
1279
1280 =item Method C<-E<gt>history>
1281
1282 Returns an arrayref of which hooks of which steps of the path were ran.
1283 Useful for seeing what happened. In general - each line of the history
1284 will show the current step, the hook requested, and which hook was
1285 actually called. (hooks that don't find a method don't add to history)
1286
1287 =item Method C<-E<gt>path>
1288
1289 Return an arrayref (modifyable) of the steps in the path. For each
1290 step the remaining hooks can be run. Hook methods are looked up and
1291 ran using the method "run_hook" which uses the method "hook" to lookup
1292 the hook. A history of ran hooks is stored in the array ref returned
1293 by $self->history. Default will be a single step path looked up in
1294 $form->{path} or in $ENV{PATH_INFO}. By default, path will look for
1295 $ENV{'PATH_INFO'} or the value of the form by the key step_key. For
1296 the best functionality, the arrayref returned should be the same
1297 reference returned for every call to path - this ensures that other
1298 methods can add to the path (and will most likely break if the
1299 arrayref is not the same). If navigation runs out of steps to run,
1300 the default step found in default_step will be run.
1301
1302 =item Method C<-E<gt>default_step>
1303
1304 Step to show if the path runs out of steps. Default value is the
1305 'default_step' property or the value 'main'.
1306
1307 =item Method C<-E<gt>step_key>
1308
1309 Used by default to determine which step to put in the path. The
1310 default path will only have one step within it
1311
1312 =item Method C<-E<gt>set_path>
1313
1314 Arguments are the steps to set. Should be called before navigation
1315 begins. This will set the path arrayref to the passed steps.
1316
1317 =item Method C<-E<gt>append_path>
1318
1319 Arguments are the steps to append. Can be called any time. Adds more
1320 steps to the end of the current path.
1321
1322 =item Method C<-E<gt>replace_path>
1323
1324 Arguments are the steps used to replace. Can be called any time.
1325 Replaces the remaining steps (if any) of the current path.
1326
1327 =item Method C<-E<gt>insert_path>
1328
1329 Arguments are the steps to insert. Can be called any time. Inserts
1330 the new steps at the current path location.
1331
1332 =item Method C<-E<gt>jump>
1333
1334 This method should not normally be used. It provides for moving to the
1335 next step at any point during the nav_loop. It effectively short circuits
1336 the remaining hooks for the current step. It does increment the recursion
1337 counter (which has a limit of ->recurse_limit - default 15). It is normally
1338 better to allow the other hooks in the loop to carry on their normal functions
1339 and avoid jumping. (Essentially, this hook behaves like a goto method to
1340 bypass everything else and continue at a different location in the path - there
1341 are times when it is necessary or useful - but most of the time should be
1342 avoided)
1343
1344 Jump takes a single argument which is the location in the path to jump
1345 to. This argument may be either a step name, the special words
1346 "FIRST, LAST, CURRENT, PREVIOUS, OR NEXT" or the number of steps to
1347 jump forward (or backward) in the path. The default value, 1,
1348 indicates that CGI::Ex::App should jump to the next step (the default action for
1349 jump). A value of 0 would repeat the current step (watch out for
1350 recursion). A value of -1 would jump to the previous step. The
1351 special value of "LAST" will jump to the last step. The special value
1352 of "FIRST" will jump back to the first step. In each of these cases,
1353 the path array retured by ->path is modified to allow for the jumping.
1354
1355 ### goto previous step
1356 $self->jump($self->previous_step);
1357 $self->jump('PREVIOUS');
1358 $self->jump(-1);
1359
1360 ### goto next step
1361 $self->jump($self->next_step);
1362 $self->jump('NEXT');
1363 $self->jump(1);
1364 $self->jump;
1365
1366 ### goto current step (repeat)
1367 $self->jump($self->current_step);
1368 $self->jump('CURRENT');
1369 $self->jump(0);
1370
1371 ### goto last step
1372 $self->jump($self->last_step);
1373 $self->jump('LAST');
1374
1375 ### goto first step
1376 $self->jump($self->first_step);
1377 $self->jump('FIRST');
1378
1379 =item Method C<-E<gt>exit_nav_loop>
1380
1381 This method should not normally used. It allows for a long jump to the
1382 end of all nav_loops (even if they are recursively nested). This
1383 effectively short circuits all remaining hooks for the current and
1384 remaining steps. It is used to allow the ->jump functionality. If the
1385 application has morphed, it will be unmorphed before returning.
1386
1387 =item Method C<-E<gt>recurse_limit>
1388
1389 Default 15. Maximum number of times to allow nav_loop to call itself.
1390 If ->jump is used alot - the recurse_limit will be reached more quickly.
1391 It is safe to raise this as high as is necessary - so long as it is intentional.
1392
1393 =item Method C<-E<gt>valid_steps>
1394
1395 Returns a hashref of path steps that are allowed. If step found in
1396 default method path is not in the hash, the method path will return a
1397 single step "forbidden" and run its hooks. If no hash or undef is
1398 returned, all paths are allowed (default). A key "forbidden_step"
1399 containing the step that was not valid will be placed in the stash.
1400 Often the valid_steps method does not need to be defined as arbitrary
1401 method calls are not possible with CGI::Ex::App.
1402
1403 =item Method C<-E<gt>previous_step, -E<gt>current_step, -E<gt>next_step, -E<gt>last_step, -E<gt>first_step>
1404
1405 Return the previous, current, next, last, and first step name - useful for figuring
1406 out where you are in the path. Note that first_step may not be the same
1407 thing as default_step if the path was overridden.
1408
1409 =item Method C<-E<gt>pre_loop>
1410
1411 Called right before the navigation loop is started. At this point the
1412 path is set (but could be modified). The only argument is a reference
1413 to the path array. If it returns a true value - the navigation
1414 routine is aborted.
1415
1416 =item Method C<-E<gt>run_hook>
1417
1418 Calls "hook" to get a code ref which it then calls and returns the
1419 result. Arguments are the same as that for "hook".
1420
1421 =item Method C<-E<gt>hook>
1422
1423 Arguments are a hook name, a pathstep name, and an optional code sub
1424 or default value (default value will be turned to a sub) (code sub
1425 will be called as method of $self).
1426
1427 my $code = $self->hook('main', 'info_complete', sub {return 0});
1428 ### will look first for $self->main_info_complete;
1429 ### will then look for $self->info_complete;
1430 ### will then run $self->$default_passed_sub; # sub {return 0}
1431
1432 This system is used to allow for multiple steps to be in the same
1433 file and still allow for moving some steps out to external sub classed
1434 packages. If the application has successfully morphed then it is not
1435 necessary to add the step name to the beginning of the method name as
1436 the morphed packages method will override the base package (it is still
1437 OK to use the full method name "${step}_hookname").
1438
1439 If a hook is found (or a default value is found) then an entry is added
1440 to the arrayref contained in ->history.
1441
1442 =item Method C<-E<gt>morph>
1443
1444 Allows for temporarily "becoming" another object type for the
1445 execution of the current step. This allows for separating some steps
1446 out into their own packages. Morph will only run if the method
1447 allow_morph returns true. Additionally if the allow_morph returns a hash
1448 ref, morph will only run if the step being morphed to is in the hash.
1449 The morph call occurs at the beginning of the step loop. A
1450 corresponding unmorph call occurs before the loop is exited. An
1451 object can morph several levels deep if allow_nested_morph returns
1452 true. For example, an object running as Foo::Bar that is looping on
1453 the step "my_step" that has allow_morph = 1, will do the following:
1454 call the hook morph_package (which would default to returning
1455 Foo::Bar::MyStep in this case), translate this to a package filename
1456 (Foo/Bar/MyStep.pm) and try and require it, if the file can be
1457 required, the object is blessed into that package. If that package
1458 has a "fixup_after_morph" method, it is called. The navigate loop
1459 then continues for the current step. At any exit point of the loop,
1460 the unmorph call is made which reblesses the object into the original
1461 package.
1462
1463 It is possible to call morph earlier on in the program. An example of
1464 a useful early use of morph would be as in the following code:
1465
1466 sub allow_morph { 1 }
1467
1468 sub pre_navigate {
1469 my $self = shift;
1470 if ($ENV{'PATH_INFO'} && $ENV{'PATH_INFO'} =~ s|^/(\w+)||) {
1471 my $step = $1;
1472 $self->morph($step);
1473 $ENV{'PATH_INFO'} = "/$step";
1474 $self->stash->{'base_morphed'} = 1;
1475 }
1476 return 0;
1477 }
1478
1479 sub post_navigate {
1480 my $self = shift;
1481 $self->unmorph if $self->stash->{'base_morphed'};
1482 }
1483
1484 If this code was in a module Base.pm and the cgi running was cgi/base
1485 and called:
1486
1487 Base->navigate;
1488 # OR - for mod_perl resident programs
1489 Base->navigate->cleanup;
1490 # OR
1491 sub post_navigate { shift->cleanup }
1492
1493 and you created a sub module that inherited Base.pm called
1494 Base/Ball.pm -- you could then access it using cgi/base/ball. You
1495 would be able to pass it steps using either cgi/base/ball/step_name or
1496 cgi/base/ball?step=step_name - Or Base/Ball.pm could implement its
1497 own path. It should be noted that if you do an early morph, it is
1498 suggested to provide a call to unmorph. And if you want to let your
1499 early morphed object morph again - you will need to provide
1500
1501 sub allow_nested_morph { 1 }
1502
1503 With allow_nested_morph enabled you could create the file
1504 Base/Ball/StepName.pm which inherits Base/Ball.pm. The Base.pm, with
1505 the custom init and default path method, would automatically morph us
1506 first into a Base::Ball object (during init) and then into a
1507 Base::Ball::StepName object (during the navigation loop).
1508
1509 =item Method C<-E<gt>unmorph>
1510
1511 Allows for returning an object back to its previous blessed state.
1512 This only happens if the object was previously morphed into another
1513 object type. Before the object is reblessed the method
1514 "fixup_before_unmorph" is called if it exists.
1515
1516 =item Method C<-E<gt>allow_morph>
1517
1518 Boolean value. Specifies whether or not morphing is allowed.
1519 Defaults to the property "allow_morph" if found, otherwise false.
1520 For more granularity, if true value is a hash, the step being
1521 morphed to must be in the hash.
1522
1523 =item Method C<-E<gt>allow_nested_morph>
1524
1525 Boolean value. Specifies whether or not nested morphing is allowed.
1526 Defaults to the property "allow_nested_morph" if found, otherwise
1527 false. For more granularity, if true value is a hash, the step being
1528 morphed to must be in the hash.
1529
1530 =item Hook C<-E<gt>morph_package>
1531
1532 Used by morph. Return the package name to morph into during a morph
1533 call. Defaults to using the current object type as a base. For
1534 example, if the current object running is a Foo::Bar object and the
1535 step running is my_step, then morph_package will return
1536 Foo::Bar::MyStep.
1537
1538 =item Hook C<-E<gt>run_step>
1539
1540 Runs all of the hooks specific to each step, beginning with pre_step
1541 and ending with post_step. Called after ->morph($step) has been
1542 run. If this returns true, the nav_loop is exited (meaning the
1543 run_step hook displayed the information). If it returns false,
1544 the nav_loop continues on to run the next step. This is essentially
1545 the same thing as a method defined in CGI::Applications ->run_modes.
1546
1547 =item Hook C<-E<gt>pre_step>
1548
1549 Ran at the beginning of the loop before prepare, info_compelete, and
1550 finalize are called. If it returns true, execution of nav_loop is
1551 returned and no more steps are processed.
1552
1553 =item Hook C<-E<gt>skip>
1554
1555 Ran at the beginning of the loop before prepare, info_compelete, and
1556 finalize are called. If it returns true, nav_loop moves on to the
1557 next step (the current step is skipped).
1558
1559 =item Hook C<-E<gt>prepare>
1560
1561 Defaults to true. A hook before checking if the info_complete is true.
1562
1563 =item Hook C<-E<gt>info_complete>
1564
1565 Checks to see if all the necessary form elements have been passed in.
1566 Calls hooks ready_validate, and validate. Will not be run unless
1567 prepare returns true (default).
1568
1569 =item Hook C<-E<gt>finalize>
1570
1571 Defaults to true. Used to do whatever needs to be done with the data once
1572 prepare has returned true and info_complete has returned true. On failure
1573 the print operations are ran. On success navigation moves on to the next
1574 step.
1575
1576 =item Hook C<-E<gt>ready_validate>
1577
1578 Should return true if enough information is present to run validate.
1579 Default is to look if $ENV{'REQUEST_METHOD'} is 'POST'. A common
1580 usage is to pass a common flag in the form such as 'processing' => 1
1581 and check for its presence - such as the following:
1582
1583 sub ready_validate { shift->form->{'processing'} }
1584
1585 =item Method C<-E<gt>set_ready_validate>
1586
1587 Sets that the validation is ready to validate. Should set the value
1588 checked by the hook ready_validate. The following would complement the
1589 processing flag above:
1590
1591 sub set_ready_validate {
1592 my $self = shift;
1593 if (shift) {
1594 $self->form->{'processing'} = 1;
1595 } else {
1596 delete $self->form->{'processing'};
1597 }
1598 }
1599
1600 Note thate for this example the form key "processing" was deleted. This
1601 is so that the call to fill in any html forms won't swap in a value of
1602 zero for form elements named "processing."
1603
1604 =item Hook C<-E<gt>validate>
1605
1606 Runs validation on the information posted in $self->form. Uses
1607 CGI::Ex::Validate for the validation. Calls the hook hash_validation
1608 to load validation information. Should return true if enough
1609 information is present to run validate. Errors are stored as a hash
1610 in $self->{hash_errors} via method add_errors and can be checked for
1611 at a later time with method has_errors (if the default validate was
1612 used).
1613
1614 Upon success, it will look through all of the items which
1615 were validated, if any of them contain the keys append_path, insert_path,
1616 or replace_path, that method will be called with the value as arguments.
1617 This allows for the validation to apply redirection to the path. A
1618 validation item of:
1619
1620 {field => 'foo', required => 1, append_path => ['bar', 'baz']}
1621
1622 would append 'bar' and 'baz' to the path should all validation succeed.
1623
1624 =item Hook C<-E<gt>hash_validation>
1625
1626 Returns a hash of the validation information to check form against.
1627 By default, will look for a filename using the hook file_val and will
1628 pass it to CGI::Ex::Validate::get_validation. If no file_val is
1629 returned or if the get_validation fails, an empty hash will be returned.
1630 Validation is implemented by ->vob which loads a CGI::Ex::Validate object.
1631
1632 =item Hook C<-E<gt>file_val>
1633
1634 Returns a filename containing the validation. Adds method
1635 base_dir_rel to hook name_module, and name_step and adds on the
1636 default file extension found in $self->ext_val which defaults to the
1637 global $EXT_VAL (the property $self->{ext_val} may also be set). File
1638 should be readible by CGI::Ex::Validate::get_validation.
1639
1640 =item Hook C<-E<gt>js_validation>
1641
1642 Requires YAML.pm.
1643 Will return Javascript that is capable of validating the form. This
1644 is done using the capabilities of CGI::Ex::Validate. This will call
1645 the hook hash_validation which will then be encoded into yaml and
1646 placed in a javascript string. It will also call the hook form_name
1647 to determine which html form to attach the validation to. The method
1648 js_uri_path is called to determine the path to the appropriate
1649 yaml_load.js and validate.js files. If the method ext_val is htm,
1650 then js_validation will return an empty string as it assumes the htm
1651 file will take care of the validation itself. In order to make use
1652 of js_validation, it must be added to either the hash_base, hash_common, hash_swap or
1653 hash_form hook (see examples of hash_base used in this doc).
1654
1655 =item Hook C<-E<gt>form_name>
1656
1657 Return the name of the form to attach the js validation to. Used by
1658 js_validation.
1659
1660 =item Method C<-E<gt>js_uri_path>
1661
1662 Return the URI path where the CGI/Ex/yaml_load.js and
1663 CGI/Ex/validate.js files can be found. This will default to
1664 "$ENV{SCRIPT_NAME}/js" if the path method has not been overridden,
1665 otherwise it will default to "$ENV{SCRIPT_NAME}?step=js&js=" (the
1666 latter is more friendly with overridden paths). A default handler for
1667 the "js" step has been provided in "js_run_step" (this handler will
1668 nicely print out the javascript found in the js files which are
1669 included with this distribution - if valid_steps is defined, it must
1670 include the step "js" - js_run_step will work properly with the
1671 default "path" handler.
1672
1673 =item Hook C<-E<gt>hash_swap>
1674
1675 Called in preparation for print after failed prepare, info_complete,
1676 or finalize. Should contain a hash of any items needed to be swapped
1677 into the html during print. Will be merged with hash_base, hash_common, hash_form,
1678 and hash_errors. Can be populated by passing a hash to ->add_to_swap.
1679
1680 =item Hook C<-E<gt>hash_form>
1681
1682 Called in preparation for print after failed prepare, info_complete,
1683 or finalize. Defaults to ->form. Can be populated by passing a hash
1684 to ->add_to_form.
1685
1686 =item Hook C<-E<gt>hash_fill>
1687
1688 Called in preparation for print after failed prepare, info_complete,
1689 or finalize. Should contain a hash of any items needed to be filled
1690 into the html form during print. Items from hash_form, hash_base, and hash_common
1691 will be layered on top during a print cycle. Can be populated by passing
1692 a hash to ->add_to_fill.
1693
1694 By default - forms are sticky and data from previous requests will
1695 try and populate the form. There is a method called ->no_fill which
1696 will turn off sticky forms.
1697
1698 =item Method C<-E<gt>no_fill>
1699
1700 Passed the current step. Should return boolean value of whether or not
1701 to fill in the form on the printed page. (prevents sticky forms)
1702
1703 =item Hook C<-E<gt>hash_errors>
1704
1705 Called in preparation for print after failed prepare, info_complete,
1706 or finalize. Should contain a hash of any errors that occured. Will
1707 be merged into hash_form before the pass to print. Eash error that
1708 occured will be passed to method format_error before being added to
1709 the hash. If an error has occurred, the default validate will
1710 automatically add {has_errors =>1}. To the error hash at the time of
1711 validation. has_errors will also be added during the merge incase the
1712 default validate was not used. Can be populated by passing a hash to
1713 ->add_to_errors or ->add_errors.
1714
1715 =item Hook C<-E<gt>hash_common>
1716
1717 Almost identical in function and purpose to hash_base. It is
1718 intended that hash_base be used for common items used in various
1719 scripts inheriting from a common CGI::Ex::App type parent. Hash_common
1720 is more intended for step level populating of both swap and fill.
1721
1722 =item Hook C<-E<gt>hash_base>
1723
1724 A hash of base items to be merged with hash_form - such as pulldown
1725 menues. It will now also be merged with hash_fill, so it can contain
1726 default fillins. Can be populated by passing a hash to ->add_to_base.
1727 By default the following sub is what is used for hash_common (or something
1728 similiar). Note the use of values that are code refs - so that the
1729 js_validation and form_name hooks are only called if requested:
1730
1731 sub hash_base {
1732 my ($self, $step) = @_;
1733 return $self->{hash_base} ||= {
1734 script_name => $ENV{SCRIPT_NAME},
1735 js_validation => sub { $self->run_hook('js_validation', $step) },
1736 form_name => sub { $self->run_hook('form_name', $step) },
1737 };
1738 }
1739
1740 =item Hook C<-E<gt>name_module>
1741
1742 Return the name (relative path) that should be prepended to name_step
1743 during the default file_print and file_val lookups. Defaults to
1744 base_name_module.
1745
1746 =item Hook C<-E<gt>name_step>
1747
1748 Return the step (appended to name_module) that should used when
1749 looking up the file in file_print and file_val lookups. Defaults to
1750 the current step.
1751
1752 =item Hook C<-E<gt>file_print>
1753
1754 Returns a filename of the content to be used in the default print
1755 hook. Adds method base_dir_rel to hook name_module, and name_step and
1756 adds on the default file extension found in $self->ext_print which
1757 defaults to the global $EXT_PRINT (the property $self->{ext_print} may
1758 also be set). Should be a file that can be handled by hook print.
1759
1760 =item Hook C<-E<gt>print>
1761
1762 Take the information generated by prepared_print, format it, and print it out.
1763 Default incarnation uses Template::Toolkit. Arguments are: step name, form hashref,
1764 and fill hashref.
1765
1766 =item Hook C<-E<gt>prepared_print>
1767
1768 Called when any of prepare, info_complete, or finalize fail. Prepares
1769 a form hash and a fill hash to pass to print. The form hash is primarily
1770 intended for use by the templating system. The fill hash is intended
1771 to be used to fill in any html forms.
1772
1773 =item Hook C<-E<gt>post_print>
1774
1775 A hook which occurs after the printing has taken place. Is only run
1776 if the information was not complete. Useful for printing rows of a
1777 database query.
1778
1779 =item Hook C<-E<gt>post_step>
1780
1781 Ran at the end of the step's loop if prepare, info_complete, and
1782 finalize all returned true. Allows for cleanup. If a true value is
1783 returned, execution of navigate is returned and no more steps are
1784 processed.
1785
1786 =item Method C<-E<gt>post_loop>
1787
1788 Ran after all of the steps in the loop have been processed (if
1789 prepare, info_complete, and finalize were true for each of the steps).
1790 If it returns a true value the navigation loop will be aborted. If it
1791 does not return true, navigation continues by then inserting the step
1792 $self->default_step and running $self->nav_loop again (recurses) to
1793 fall back to the default step.
1794
1795 =item Method C<-E<gt>stash>
1796
1797 Returns a hashref that can store arbitrary user space data without
1798 clobering the internals of the application.
1799
1800 =item Method C<-E<gt>add_property>
1801
1802 Takes the property name as an argument. Creates an accessor that can
1803 be used to access a new property. If there were additional arguments
1804 they will call the new accessor. Calling the new accessor with an
1805 argument will set the property. Using the accessor in an assignment
1806 will also set the property (it is an lvalue). Calling the accessor in
1807 any other way will return the value.
1808
1809 =item Method C<-E<gt>cleanup>
1810
1811 Can be used at the end of execution to tear down the structure.
1812 Default method starts a cleanup_cross_references call.
1813
1814 =item Method C<-E<gt>cleanup_cross_references>
1815
1816 Used to destroy links in nested structures. Will spider through the
1817 data structure of the passed object and remove any blessed objects
1818 that are no weakly referenced. This means if you have a reference to
1819 an object in a global cache, that object should have its reference
1820 weakened in the global cache. Requires Scalar::Util to function. Use
1821 of this function is highly recommended in mod_perl environments to
1822 make sure that there are no dangling objects in memory. There are
1823 some global caches that can't be fixed (such as Template::Parser's
1824 reference to Template::Grammar in the Template::Toolkit). For these
1825 situations there is a %CLEANUP_EXCLUDE hash that contains the names of
1826 Object types to exclude from the cleanup process. Add any such global
1827 hashes (or objects with references to the global hashes) there.
1828
1829 =back
1830
1831 =head1 OTHER APPLICATION MODULES
1832
1833 The concepts used in CGI::Ex::App are not novel or unique. However, they
1834 are all commonly used and very useful. All application builders were
1835 built because somebody observed that there are common design patterns
1836 in CGI building. CGI::Ex::App differs in that it has found more common design
1837 patterns of CGI's.
1838
1839 CGI::Ex::App is intended to be sub classed, and sub sub classed, and each step
1840 can choose to be sub classed or not. CGI::Ex::App tries to remain simple
1841 while still providing "more than one way to do it." It also tries to avoid
1842 making any sub classes have to call ->SUPER::.
1843
1844 There are certainly other modules for building CGI applications. The
1845 following is a short list of other modules and how CGI::Ex::App is
1846 different.
1847
1848 =over 4
1849
1850 =item C<CGI::Application>
1851
1852 Seemingly the most well know of application builders.
1853 CGI::Ex::App is different in that it:
1854
1855 * Uses Template::Toolkit by default
1856 CGI::Ex::App can easily use another toolkit by simply
1857 overriding the ->print method.
1858 CGI::Application uses HTML::Template.
1859 * Offers integrated data validation.
1860 CGI::Application has had custom addons created that
1861 add some of this functionality. CGI::Ex::App has the benefit
1862 that once validation is created,
1863 * Allows the user to print at any time (so long as proper headers
1864 are sent. CGI::Application requires data to be pipelined.
1865 * Offers hooks into the various phases of each step ("mode" in
1866 CGI::Application lingo). CGI::Application essentially
1867 provides ->runmode
1868 * Support for easily jumping around in navigation steps.
1869 * Support for storing some steps in another package.
1870
1871 CGI::Ex::App and CGI::Application are similar in that they take care
1872 of handling headers and they allow for calling other "runmodes" from
1873 within any given runmode. CGI::Ex::App's ->run_step is essentially
1874 equivalent to a method call defined in CGI::Application's ->run_modes.
1875 The ->run method of CGI::Application starts the application in the same
1876 manner as CGI::Ex::App's ->navigate call. Many of the hooks around
1877 CGI::Ex::App's ->run_step call are similar in nature to those provided by
1878 CGI::Application.
1879
1880 =item C<CGI::Prototype>
1881
1882 There are actually many simularities. One of the nicest things about
1883 CGI::Prototype is that it is extremely short (very very short). The
1884 ->activate starts the application in the same manner as CGI::Ex::App's
1885 =>navigate call. Both use Template::Tookit as the default template system.
1886 CGI::Ex::App is differrent in that it:
1887
1888 * Offers integrated data validation.
1889 CGI::Application has had custom addons created that
1890 add some of this functionality. CGI::Ex::App has the benefit
1891 that once validation is created,
1892 * Offers more hooks into the various phases of each step.
1893 * Support for easily jumping around in navigation steps.
1894 * Support for storing some steps in another package.
1895
1896 =item C<CGI::Path>
1897
1898 CGI::Path and CGI::Ex::App are fairly similar in may ways as they
1899 were created under similar lines of thought. The primary difference
1900 in these two is that CGI::Ex::App:
1901
1902 * Does not provide "automated path following" based on
1903 validated key information. CGI::Path works well for
1904 wizard based applications. CGI::Ex::App assumes that
1905 the application will chose it's own path (it works very
1906 well in non-linear paths - it also works fine in
1907 linear paths but it doesn't provide some of magic that
1908 CGI::Path provides).
1909 * Does not provide integrated session support. CGI::Path
1910 requires it for path navigation. CGI::Ex::App assumes that
1911 if session support or authentication is needed by an
1912 application, a custom Application layer that inherits
1913 from CGI::Ex::App will be written to provide this support.
1914 * Offers more granularity in the navigation phase. CGI::Path
1915 has successfully been used as a sub class of CGI::Ex::App
1916 with limited modifications.
1917
1918 =back
1919
1920 =head1 BUGS
1921
1922 Uses CGI::Ex for header support by default - which means that support
1923 for mod_perl 2 is limited at this point.
1924
1925 There are a lot of hooks. Actually this is not a bug. Some may
1926 prefer not calling as many hooks - they just need to override
1927 methods high in the chain and subsequent hooks will not be called.
1928
1929 =head1 THANKS
1930
1931 Bizhosting.com - giving a problem that fit basic design patterns.
1932 Earl Cahill - pushing the idea of more generic frameworks.
1933 Adam Erickson - design feedback, bugfixing, feature suggestions.
1934 James Lance - design feedback, bugfixing, feature suggestions.
1935
1936 =head1 AUTHOR
1937
1938 Paul Seamons
1939
1940 =cut
This page took 0.162382 seconds and 3 git commands to generate.