Advertisement
Guest User

httpcpansearchperlo

a guest
Sep 16th, 2009
181
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 78.67 KB | None | 0 0
  1. # -*- Mode: perl -*-
  2. #
  3. # $Id: MxScreen.pm,v 0.1.1.1 2001/05/30 21:13:07 ram Exp $
  4. #
  5. #  Copyright (c) 1998-2001, Raphael Manfredi
  6. #  Copyright (c) 2000-2001, Christophe Dehaudt
  7. #  
  8. #  You may redistribute only under the terms of the Artistic License,
  9. #  as specified in the README file that comes with the distribution.
  10. #
  11. # HISTORY
  12. # $Log: MxScreen.pm,v $
  13. # Revision 0.1.1.1  2001/05/30 21:13:07  ram
  14. # patch1: fixed HISTORY section
  15. # patch1: random cleanup in named argument docs
  16. # patch1: updated version number
  17. #
  18. # Revision 0.1  2001/04/22 17:57:03  ram
  19. # Baseline for first Alpha release.
  20. #
  21. # $EndLog$
  22. #
  23.  
  24.  
  25. use strict;
  26.  
  27. package CGI::MxScreen;
  28.  
  29. use vars qw($VERSION $BIN_VERSION);
  30. $VERSION = '0.103';
  31. $BIN_VERSION = '0.1';
  32.  
  33. use CGI::MxScreen::Constant;
  34. use CGI::MxScreen::Error;
  35. use Carp::Datum;
  36. use Log::Agent;
  37. use Getargs::Long;
  38. use Time::HiRes qw(time);
  39.  
  40. require CGI;
  41. require CGI::MxScreen::Form::Field;
  42. require CGI::MxScreen::Form::Button;
  43. require CGI::MxScreen::Layout;
  44. require CGI::MxScreen::Session;
  45.  
  46. my @managers;       # For END {}
  47.  
  48. #
  49. # ->make
  50. #
  51. # Creation routine.
  52. #
  53. sub make {
  54.     DFEATURE my $f_;
  55.     my $self = bless {}, shift;
  56.     my ($tm_start, $tm_user, $tm_sys) = (time, times);
  57.  
  58.     #
  59.     # Prevent anything to be written to STDOUT by tieing it to a package
  60.     # that will log anything written there, without letting it go through.
  61.     #
  62.  
  63.     require CGI::MxScreen::Tie::Stdout;
  64.     tie *main::STDOUT, "CGI::MxScreen::Tie::Stdout";
  65.  
  66.     #
  67.     # Argument parsing.
  68.     #
  69.  
  70.     (
  71.         $self->{_screen_list},
  72.         $self->{_initial_state},
  73.         $self->{_cgi_version},
  74.         $self->{_valid_time},
  75.         $self->{_bgcolor},
  76.         $self->{_layout},
  77.     ) =
  78.         cxgetargs(@_,
  79.             -screens    => 'HASH',
  80.             -initial    => undef,
  81.             -version    => [undef, '1.0'],
  82.             -timeout    => ['i'],
  83.             -bgcolor    => [undef, '#bfbfbf'],
  84.             -layout     => ['CGI::MxScreen::Layout'],
  85.         );
  86.    
  87.     $self->{_start_times} = [$tm_start, $tm_user, $tm_sys];
  88.     $self->{_last_times} = [$tm_start, $tm_user, $tm_sys];
  89.     $self->{_layout} = CGI::MxScreen::Layout->make()
  90.         unless defined $self->{_layout};
  91.  
  92.     #
  93.     # Perform default initialization if not already done via a call
  94.     # to "use CGI::MxScreen::Config;".
  95.     #
  96.  
  97.     require CGI::MxScreen::Config;
  98.     CGI::MxScreen::Config::configure();     # Will return if already done
  99.  
  100.     if (defined $CGI::MxScreen::Config::LOG) {
  101.         $self->{_log} = $CGI::MxScreen::Config::LOG;
  102.     } else {
  103.         use File::Spec;
  104.         require Log::Agent::Logger;
  105.         require Log::Agent::Channel::File;
  106.  
  107.         my $devnull = Log::Agent::Channel::File->make(
  108.             -filename       => File::Spec->devnull,
  109.             -no_prefixing   => 1,
  110.             -no_ucfirst     => 1,
  111.             -no_newline     => 1,
  112.         );
  113.         $self->{_log} = Log::Agent::Logger->make(-channel => $devnull);
  114.     }
  115.  
  116.     #
  117.     # Now that logging is up, validate the creation routine parameters.
  118.     #
  119.  
  120.     logcroak "-initial must be either a plain scalar or an ARRAY ref"
  121.         if ref $self->initial_state && ref $self->initial_state ne "ARRAY";
  122.  
  123.     my $state_name = ref $self->initial_state ?
  124.         $self->initial_state->[0] : $self->initial_state;
  125.  
  126.     logcroak "initial state '$state_name' is not a known state"
  127.         unless $self->is_valid_state($state_name);
  128.  
  129.     #
  130.     # Initialize whole script context.
  131.     #
  132.  
  133.     $self->trace_incoming;  # XXX if logtrace_at("info") or logdebug_at("warn")
  134.  
  135.     my $session = $self->{_session} = CGI::MxScreen::Session->make(
  136.         -serializer     => $CGI::MxScreen::Config::SERIALIZER,
  137.         -medium         => $CGI::MxScreen::Config::MEDIUM,
  138.     );
  139.  
  140.     $self->{_context} = $session->restore;
  141.     $self->check_validity();
  142.  
  143.     #
  144.     # Relink all serialized screens to the new manager.
  145.     #
  146.  
  147.     my $ctx = $self->ctx;
  148.  
  149.     if (exists $ctx->{'screens'}) {
  150.         foreach my $screen (values %{$ctx->{'screens'}}) {
  151.             $screen->relink_to_manager($self);
  152.         }
  153.     }
  154.  
  155.     #
  156.     # For session logging, maintain the following parameters in
  157.     # the private CGI::MxScreen context.
  158.     #
  159.     #    log_session    unique session ID for logging (IP number-PID)
  160.     #    log_starttime  time when session started
  161.     #    log_cnt        counter incremented each time we're invoked
  162.     #
  163.     #
  164.  
  165.     unless (exists $ctx->{'log_session'}) {
  166.         $ctx->{'log_session'} = CGI::remote_host() . "-" . $$;
  167.         $ctx->{'log_starttime'} = int(time);
  168.         $ctx->{'log_cnt'} = 0;
  169.     } else {
  170.         $ctx->{'log_cnt'}++;
  171.     }
  172.  
  173.     #
  174.     # From now on, all Log::Agent messages will bear the session ID.
  175.     #
  176.  
  177.     require Log::Agent::Tag::String;
  178.     use Log::Agent qw(logtags);
  179.  
  180.     my $tag = Log::Agent::Tag::String->make(
  181.         -name       => "session id",
  182.         -value      => "(" . $ctx->{'log_session'} . ")",
  183.     );
  184.  
  185.     my $log = $self->log;
  186.     $log->tags->append($tag);
  187.     logtags()->append($tag);
  188.  
  189.     $log->warning("");
  190.     $log->warning(\&log_session, $self);
  191.     $log->info(\&log_agent, $self);
  192.     $log->debug(\&log_inc_times, $self, "context restore + log init");
  193.  
  194.     #
  195.     # Process incoming parameters, trap all errors.
  196.     #
  197.     # Since we might be using CGI::Carp, we must cancel any trap hook by
  198.     # localizing the __DIE__ and __WARN__ special handlers.
  199.     #
  200.  
  201.     eval {
  202.         local $SIG{__DIE__};
  203.         local $SIG{__WARN__};
  204.  
  205.         $self->process_params;
  206.     };
  207.     $log->debug(\&log_inc_times, $self, "parameter init");
  208.     $self->internal_error($@) if chomp $@;
  209.  
  210.     push(@managers, $self);
  211.  
  212.     return DVAL $self;
  213. }
  214.  
  215. #
  216. # ->process_params
  217. #
  218. # Get CGI parameters, fill internal data structures.
  219. #
  220. sub process_params {
  221.     DFEATURE my $f_;
  222.     my $self = shift;
  223.  
  224.     #
  225.     # Save params provided by CGI
  226.     #
  227.     # It is a quite big story because there are different possiblities
  228.     # for the store location according to the way the fields have been
  229.     # recorded, and also according to the storage indication settings.
  230.     #
  231.     # When the field has been recorded at display time (use of
  232.     # record_field method), it might contain some storage indications
  233.     # (see Form::Field). It may also contain an indication to not save
  234.     # the value (useful for password). Anyway, when the incoming param
  235.     # matches a recorded field from the last display, the store_value
  236.     # method of the field is invoked to perform the task according to
  237.     # the indication. Returning true indicates there is no need to
  238.     # save the param value in the MxScreen repository (see
  239.     # below). Actually, either the value has been save somewhere else
  240.     # or there were some indication to not keep the value persistent.
  241.     #
  242.     # When there is no specific indication for the storage (either the
  243.     # field has not been recorded but has been merely displayed, or
  244.     # the store_value method returned false), the value is memorized
  245.     # into the MxScreen repository for the orphan params. It is a
  246.     # dedicated section of the context. Each orphan params is stored
  247.     # in that section under the index of the screen name. All the orphan
  248.     # params are replayed --put into the CGI param list-- to benefit
  249.     # from their values when the field is once again displayed.
  250.     #
  251.     # NOTES: You have to know that a button press returns also a value
  252.     # into the incoming CGI param list. The following code needs to
  253.     # take care of that by filtering them before being considered as
  254.     # orphan fields. For simple button, it is quite easy since the
  255.     # param name must have been recorded into the Mxscreen Button
  256.     # list, but for image button the returned param does not match
  257.     # exactly the one recorded. For this latter button, the returned
  258.     # param is in fact 2 params which indicates the click
  259.     # location. Their name is composed by the param name (recorded in
  260.     # the Mxscreen Button list) plus '.x' or '.y'.
  261.     #
  262.     # NOTES: CGI param list does not alway returned a value for all
  263.     # displayed field of the screen. For some specific elements (for
  264.     # instance checkbox group), no value is returned when the field is
  265.     # cleared (no box checked in the previous example). This clear
  266.     # value must however be saved into the storage location. To cope
  267.     # this problem, all the known displayed fields (those in the
  268.     # recorded list of fields, and those in the orphan repository of
  269.     # the screen) are checked to validate the existence of a value
  270.     # into the CGI param list. When no value is found, a clear value
  271.     # ('') is enforced.
  272.     #
  273.  
  274.     # load the package of the last screen where all needed classes
  275.     # should have been defined.
  276.     my $current_state = $self->initial_state;
  277.     $current_state = $self->ctx->{'current_state'} if
  278.       (defined $self->ctx->{'current_state'});
  279.     my ($screen_name) = $self->scatter($current_state);
  280.     $self->load_screen_package($screen_name);
  281.  
  282.     # build a easy access way to recorded field and button: make a
  283.     # hash from the array
  284.     my $var_ctx = $self->context(PERSISTENT);
  285.     my $field_hash = {};
  286.     for my $field (@{$self->context(SCREEN_FIELD)}) {
  287.         DASSERT $field->isa('CGI::MxScreen::Form::Field');
  288.         $field_hash->{$field->name} = $field;
  289.     }
  290.  
  291.     my $button_hash = {};
  292.     for my $button (@{$self->context(SCREEN_BUTTON)}) {
  293.         DASSERT $button->isa('CGI::MxScreen::Form::Button');
  294.         $button_hash->{$button->name} = $button;
  295.     }
  296.  
  297.     #
  298.     # Patch the CGI param list for fields which are known to be
  299.     # displayed but no value appears in the CGI list.
  300.     #
  301.  
  302.     my $cgi_param = $self->context(CGI_PARAM);
  303.  
  304.     while (my ($k, $v) = each %{$cgi_param->{$screen_name}}) {
  305.         CGI::param(-name => $k, -values => $v) unless
  306.           defined CGI::param($k);
  307.     }
  308.     while (my ($k, $v) = each %$field_hash) {
  309.         CGI::param(-name => $k, -values => $v->value) unless
  310.           defined CGI::param($k);
  311.     }
  312.    
  313.     # walkthrough the CGI param list to store values
  314.     for my $param (CGI::param()) {
  315.         DTRACE "storing incoming param $param";
  316.         my $field = $field_hash->{$param};
  317.  
  318.         # return form CGI param might be either a single element or a
  319.         # list of elements. To get all of them, an array context must
  320.         # be used. Then, the value that will be stored is either the
  321.         # array reference or the first and single element of the
  322.         # array.
  323.         my @value = CGI::param($param);
  324.         my $value = $#value == 0 ? $value[0]: \@value;
  325.  
  326.         if (defined $field) {
  327.             #
  328.             # Patch the value (if needed)
  329.             # Then store value according to the storage indication given in
  330.             # the field (if any)
  331.             #
  332.             my ($patched, $nvalue) = $field->patch_value($value);
  333.             if ($patched) {
  334.                 CGI::param(-name => $param, -values => $nvalue) if $patched;
  335.                 next if $field->store_value($var_ctx, $nvalue);
  336.             } else {
  337.                 next if $field->store_value($var_ctx, $value);
  338.             }
  339.         }
  340.         # no storage indication is present
  341.  
  342.         #
  343.         # perhaps it was a button rather than a field
  344.         #
  345.  
  346.         # image button press is embarrassing. In such a case, the
  347.         # returned param is not 1 but 2 params which represents the
  348.         # location of the click within the image
  349.         if ($param =~ /(.*)\.([xy])$/) {
  350.             if (defined (my $x = CGI::param("$1.x")) &&
  351.                 defined (my $y = CGI::param("$1.y"))) {
  352.                 next if $2 eq "y"; # do the job only for x
  353.                 $param = $1;
  354.             }
  355.         }
  356.  
  357.         if (defined $button_hash->{$param}) {
  358.             $self->internal_error(
  359.                 "invalid input form: buttons '" . $self->button_pressed->name .
  360.                  "' and '$param' were simultaneously pressed!")
  361.               if defined $self->button_pressed;
  362.  
  363.             # Remember it as the button that was pressed
  364.             $self->{_button_pressed} = $button_hash->{$param};
  365.             next;
  366.         }
  367.  
  368.         # It is an orphan field that has not been saved.  Keep it in
  369.         # mind into the param repository.  The param context is stored
  370.         # under the name of the screen to build a kind of
  371.         # hierachy. That allows the clean up functionality when
  372.         # leaving a screen (on explicit request).
  373.         $cgi_param->{$screen_name}->{$param} = $value;
  374.     }
  375.  
  376.     # all orphan params will populate the CGI's param list if they are
  377.     # not already present. That will allow to prefill fields when
  378.     # redisplay and to give an access to their values with regular
  379.     # CGI::param().
  380.     #
  381.     # Information is organized in a hash table where the key is the
  382.     # screen id (state name) and the value is another hash. The latter
  383.     # contains the pair of data: symbol, value that must be restored.
  384.     while (my ($screen , $hash) = each %$cgi_param) {
  385.         while (my ($k, $v) = each %$hash) {
  386.             CGI::param(-name => $k, -values => $v);
  387.         }
  388.     }
  389.  
  390.     return DVOID;
  391. }
  392.  
  393.  
  394. #########################################################################
  395. # Internal Attribute Access: these methods are not intended to be used  #
  396. # from the external world.                                              #
  397. #########################################################################
  398.  
  399. sub screen_list    { $_[0]->{'_screen_list'} }
  400. sub context_root   { $_[0]->{'_context'} }
  401. sub screen         { $_[0]->{'_screen'} }
  402. sub session        { $_[0]->{'_session'} }
  403. sub cgi_version    { $_[0]->{'_cgi_version'} }
  404. sub valid_time     { $_[0]->{'_valid_time'} }
  405. sub initial_state  { $_[0]->{'_initial_state'} }
  406. sub bgcolor        { $_[0]->{'_bgcolor'} }
  407. sub layout         { $_[0]->{'_layout'} }
  408. sub log            { $_[0]->{'_log'} }
  409. sub start_times    { $_[0]->{'_start_times'} }
  410. sub last_times     { $_[0]->{'_last_times'} }
  411.  
  412. sub button_pressed { $_[0]->{_button_pressed} }
  413. sub ctx            {
  414.     defined $_[0]->{'_context'} ? $_[0]->{'_context'}->[MXSCREEN] : {}
  415. }
  416.  
  417. #
  418. # ->is_valid_state
  419. #
  420. # Check whether state is known
  421. #
  422. sub is_valid_state {
  423.     DFEATURE my $f_;
  424.     my $self = shift;
  425.     my ($state) = @_;
  426.  
  427.     return DVAL exists $self->screen_list->{$state};
  428. }
  429.  
  430. #
  431. # ->load_screen_package
  432. #
  433. # Load source file for the class implementing the screen $name, unless
  434. # it is already present.
  435. #
  436. sub load_screen_package {
  437.     DFEATURE my $f_;
  438.     my $self = shift;
  439.     my ($name) = @_;
  440.  
  441.     DREQUIRE $self->is_valid_state($name), "valid state '$name'";
  442.  
  443.     my ($class_name) = cgetargs(@{$self->screen_list->{$name}},
  444.                                 {-strict => 0},
  445.                                 qw(class));
  446.  
  447.     #
  448.     # The following eval "" attempts to load the screen class by using
  449.     # a require, assuming there is one class by file.  However, we
  450.     # check for the presence of an @ISA variable in the target package
  451.     # before performing the require, since the application could have
  452.     # already loaded all the screen classes.  Given that all screens must
  453.     # inherit from CGI::MxScreen::Screen, we know @ISA is defined if the
  454.     # package is present.
  455.     #
  456.  
  457.     eval "require $class_name unless defined \@${class_name}::ISA;";
  458.     if (chomp $@) {
  459.         logerr "loading of $class_name failed: $@";
  460.         logdie "can't locate class \"$class_name\" for screen state \"$name\"";
  461.     }
  462.  
  463.     return DVOID;
  464. }
  465.  
  466. #
  467. # ->make_screen
  468. #
  469. # Create the screen for given state.
  470. #
  471. sub make_screen {
  472.     DFEATURE my $f_;
  473.     my $self = shift;
  474.     my ($name) = @_;
  475.  
  476.     DREQUIRE $self->is_valid_state($name), "valid state '$name'";
  477.  
  478.     $self->load_screen_package($name);
  479.     my ($class_name, @remaining) = cgetargs(@{$self->screen_list->{$name}},
  480.                                             {-strict => 0, -extra => 1},
  481.                                             qw(class));
  482.  
  483.     #
  484.     # If the state has already been seen already, it has been serialized
  485.     # in the context, but it needs to be relinked to the new manager instance.
  486.     #
  487.     # Otherwise, a new object is created and remembered in the context.
  488.     #
  489.  
  490.     my $cxt = $self->ctx;       # CGI::MxScreen own private context
  491.     my $screen;
  492.  
  493.     if (exists $cxt->{'screens'}->{$name}) {
  494.         $screen = $cxt->{'screens'}->{$name};
  495.         $screen->remake($self);
  496.     } else {
  497.         $screen = $class_name->make(
  498.             -manager => $self,
  499.             -name    => $name,
  500.             @remaining
  501.         );
  502.         $cxt->{'screens'}->{$name} = $screen;
  503.     }
  504.  
  505.     return DVAL $screen;
  506. }
  507.  
  508. #
  509. # ->scatter
  510. #
  511. # Return:
  512. #   either a list with a single element when incoming param is a
  513. #   scalar value or a list with all element of the incoming list.
  514. #
  515. sub scatter {
  516.     DFEATURE my $f_;
  517.     my $self = shift;
  518.     my ($id) = @_;
  519.  
  520.     return DARY @$id if ref $id eq 'ARRAY';
  521.     return DARY ($id);
  522. }
  523.  
  524. #
  525. # ->obj_scatter
  526. #
  527. # Same as scatter(), but handles ($obj, $routine, @args) as well.
  528. # Supplies the screen if no blessed object is identified in the first
  529. # position of the list.
  530. #
  531. sub obj_scatter {
  532.     DFEATURE my $f_;
  533.     my $self = shift;
  534.     my ($screen, $id) = @_;
  535.  
  536.     return DARY ($screen, $id) unless ref $id eq 'ARRAY';
  537.  
  538.     if (ref $id->[0] && UNIVERSAL::isa($id->[0], "UNIVERSAL")) {
  539.         $screen = $id->[0];
  540.         return DARY ($screen, @$id[1..$#$id]);
  541.     }
  542.  
  543.     return DARY ($screen, @$id);
  544. }
  545.  
  546. #########################################################################
  547. # Class Feature: usable from the external world                         #
  548. #########################################################################
  549.  
  550. #
  551. # ->context
  552. #
  553. # return a reference of a given section withtin the overal context
  554. # area
  555. #
  556. # Arguments:
  557. #   $index: index of the context section to returned
  558. #
  559. # Return:
  560. #   a reference to the requested context section
  561. #
  562. sub context {
  563.     DFEATURE my $f_;
  564.  
  565.     DREQUIRE $_[1] =~ /^\d+$/;
  566.     DREQUIRE $_[1] >= 0 && $_[1] < CONTEXT_COUNT;
  567.  
  568.     return DVAL $_[0]->context_root->[$_[1]];
  569. }
  570.  
  571. #
  572. # ->spring_screen
  573. # ->previous_screen
  574. # ->current_screen
  575. #
  576. # Returns [state, display_args]
  577. #
  578. sub spring_screen {
  579.     DFEATURE my $f_;
  580.     return DVAL $_[0]->ctx->{'spring_state'};       # Last stable state(args)
  581. }
  582. sub previous_screen {
  583.     DFEATURE my $f_;
  584.     return DVAL $_[0]->ctx->{'previous_state'};     # Previous state(args)
  585. }
  586. sub current_screen {
  587.     DFEATURE my $f_;
  588.     return DVAL $_[0]->ctx->{'current_state'};      # Current state(args)
  589. }
  590.  
  591.  
  592. #
  593. # ->play
  594. #
  595. # Play the sequence of action necessary to display the new screen.
  596. #
  597. sub play {
  598.     DFEATURE my $f_;
  599.     my $self = shift;
  600.  
  601. # coderef is a temporary arg until storable is able to select things to
  602. # store (storable::Hook)
  603.     my ($coderef) = @_;
  604.  
  605.     my $log = $self->log;
  606.     $log->debug(\&log_inc_times, $self, "outside CGI::MxScreen");
  607.  
  608.     #
  609.     # Compute target screen, trap all errors.
  610.     #
  611.     # Since we might be using CGI::Carp, we must cancel any trap hook by
  612.     # localizing the __DIE__ and __WARN__ special handlers.
  613.     #
  614.  
  615.     my ($screen, $args);
  616.     eval {
  617.         local $SIG{__DIE__};
  618.         local $SIG{__WARN__};
  619.  
  620.         ($screen, $args) = $self->compute_screen;
  621.     };
  622.     $log->debug(\&log_inc_times, $self, "screen computation");
  623.     $self->internal_error($@) if chomp $@;
  624.  
  625.     #
  626.     # Emit CGI headers
  627.     # From now on, output is safe and will not get us a server error.
  628.     #
  629.  
  630.     untie *main::STDOUT;            # Restore original STDOUT stream
  631.  
  632.     #
  633.     # If they configured us to buffer all STDOUT until context is ready
  634.     # to be emitted, then create object, print headers and mark the
  635.     # output of headers as done: further output to STDOUT will be buffered
  636.     # and printed only after the context.
  637.     #
  638.     # The reason for this is to have the context emitted before any other
  639.     # form widget.  That way, pressing a submit button before the whole form
  640.     # is loaded in the browser won't matter as much, since we'll have at
  641.     # least the context to propagate in the POST parameters.
  642.     #
  643.  
  644.     my $stdout;
  645.     if ($CGI::MxScreen::cf::mx_buffer_stdout) {
  646.         require CGI::MxScreen::Tie::Buffered_Output;
  647.         $stdout = tie *main::STDOUT, "CGI::MxScreen::Tie::Buffered_Output";
  648.     }
  649.  
  650.     #
  651.     # Display screen, with proper "bounce" exception support.
  652.     # Returns screen that was finally displayed.
  653.     #
  654.  
  655.     $screen = $self->display($screen, $args, $stdout);
  656.     $log->debug(\&log_inc_times, $self, "\"%s\" display", $screen->name);
  657.  
  658.     #
  659.     # Snapshot current time and last modification date of the
  660.     # scriptright before saving context.  That fields can be used to
  661.     # check for session validity.
  662.     #
  663.  
  664.     $self->ctx->{'time'} = time;
  665.     $self->ctx->{'script_date'} = (stat($0))[9];
  666.  
  667.     #
  668.     # Cleanup context to avoid saving transient data
  669.     #
  670.  
  671.     &{$coderef}() if defined $coderef; # TBR
  672.  
  673.     for my $f (@{$self->context_root->[SCREEN_FIELD]}) {
  674.         DASSERT $f->isa('CGI::MxScreen::Form::Field');
  675.         $f->cleanup();
  676.     }
  677.     for my $b (@{$self->context_root->[SCREEN_BUTTON]}) {
  678.         DASSERT $b->isa('CGI::MxScreen::Form::Button');
  679.         $b->cleanup();
  680.     }
  681.  
  682.     #
  683.     # If STDOUT was bufferd, the context must be emitted explicitely
  684.     # between the header of the form and the remaining data.
  685.     #
  686.  
  687.     if (defined $stdout) {
  688.         my $context = $self->session->save;
  689.         $stdout->print_all($context);
  690.         untie_stdout();
  691.     } else {
  692.         print $self->session->save;
  693.     }
  694.  
  695.     $log->debug(\&log_inc_times, $self, "context save");
  696.  
  697.     #
  698.     # Emit CGI trailers.
  699.     #
  700.  
  701.     print CGI::endform;
  702.  
  703.     my $layout = $self->layout;
  704.     $layout->postamble;
  705.     $layout->end_HTML;
  706.  
  707.     return DVOID;
  708. }
  709.  
  710. #
  711. # ->compute_screen
  712. #
  713. # Compute target screen, and run and enter/leave hooks if we change screens.
  714. # This routine does not display anything, but runs all the action callbacks.
  715. #
  716. # Returns new screen object, and a ref to the argument list.
  717. #
  718. sub compute_screen {
  719.     DFEATURE my $f_;
  720.     my $self = shift;
  721.     my ($current_state, $previous_state, $new_state);
  722.     my ($origin_name, $target_name, @arg_list);
  723.     my $screen;
  724.     my $errors = 0;
  725.     my $ctx = $self->ctx;
  726.  
  727.    
  728.     # get the current state from the context its format can be either
  729.     # 'screen_name' or ['screen_name', @arg_list]. 'screen_name' is the
  730.     # symbol key given to a screen name into the given screen list (at
  731.     # make time) and @arg_list is a list of arg to pass to the display
  732.     # routine of the screen.
  733.  
  734.     $current_state = $self->initial_state;
  735.     $current_state = $ctx->{'current_state'} if
  736.       (defined $ctx->{'current_state'});
  737.  
  738.     $previous_state = $current_state;
  739.     $new_state = $current_state;
  740.  
  741.     #
  742.     # Compute the destination and process the associated actions when
  743.     # a button has been detected as pressed (during the make method).
  744.     #
  745.     # If we could not identify a button that was pressed, we'll simply
  746.     # remain in the current state and re-display the form unless there was
  747.     # a default button recorded in the previous screen.
  748.     #
  749.  
  750.     my $button_pressed = $self->button_pressed;
  751.  
  752.     if ($ctx->{'log_cnt'} && !defined $button_pressed) {
  753.  
  754.         #
  755.         # Create the previous screen to lookup for a default button
  756.         #
  757.  
  758.         ($origin_name) = $self->scatter($previous_state);
  759.         $screen = $self->make_screen($origin_name);
  760.         my $default = $screen->default_button;
  761.  
  762.         if (defined $default) {
  763.             $button_pressed = $self->{_button_pressed} = $default;
  764.             $self->log->warning("no button pressed, using default \"%s\"",
  765.                 $default->value);
  766.         } else {
  767.             $self->log->error(
  768.                 "no button pressed, no default, will stay in same state");
  769.         }
  770.     }
  771.  
  772.     if (defined $button_pressed) {
  773.      
  774.         #
  775.         # Create the previous screen to perform the actions
  776.         # Screen could have been created above, during the default
  777.         # button computation, hence the check.
  778.         #
  779.  
  780.         unless (defined $screen) {
  781.             ($origin_name) = $self->scatter($previous_state);
  782.             $screen = $self->make_screen($origin_name);
  783.         }
  784.  
  785.         # Those are not serialized
  786.         DASSERT !defined $screen->error_env, "no callback error condition";
  787.         DASSERT !defined $screen->error, "no user error condition";
  788.  
  789.         my $act_env;                    # Action environment
  790.  
  791.         if (defined $button_pressed->action) {
  792.             DASSERT ref $button_pressed->action eq 'ARRAY';
  793.  
  794.             use CGI::MxScreen::Error qw(is_mx_errcode);
  795.             require CGI::MxScreen::Action_Env;
  796.  
  797.             $act_env = CGI::MxScreen::Action_Env->make();
  798.  
  799.             for my $action (@{$button_pressed->action}) {
  800.                 my ($obj, $routine, @routine_arg) =
  801.                     $self->obj_scatter($screen, $action);
  802.  
  803.                 my $errcode = $obj->$routine(@routine_arg, $act_env);
  804.  
  805.                 #
  806.                 # Temporary safety net whilst migration of all callback
  807.                 # returned values is ongoing.
  808.                 #
  809.  
  810.                 if ($errcode == 0 || $errcode == 1) {
  811.                     logwarn "callback %s->%s returned OLD boolean status",
  812.                         ref $obj, $routine;
  813.                     $errcode = $errcode ? CGI_MX_OK : CGI_MX_ABORT;
  814.                 }
  815.  
  816.                 VERIFY is_mx_errcode($errcode),
  817.                     "callback ", ref($obj), "->$routine returns valid code",
  818.                     " -- returned $errcode";
  819.  
  820.                 next if $errcode == CGI_MX_OK;
  821.  
  822.                 #
  823.                 # an error occurred, don't process the remaining
  824.                 # of actions if it is CGI_MX_ABORT.
  825.                 #
  826.                 # The screen is tagged with an error flag and the state
  827.                 # destination is resumed to the origin screen.
  828.                 #
  829.  
  830.                 my $called = sprintf "%s->%s", ref($obj), $routine;
  831.                 my $binfo = sprintf "for button \"%s\"",
  832.                     $button_pressed->value;
  833.                 $binfo .= sprintf " (%s)", $button_pressed->name
  834.                     if $button_pressed->name ne $button_pressed->value;
  835.  
  836.                 DTRACE "error in action callback: $called $binfo";
  837.                 $self->log->error("action callback $called failed $binfo%s",
  838.                     $errcode == CGI_MX_ABORT ? ", aborting" : "");
  839.  
  840.                 $errors++;
  841.                 $screen->set_error_env($act_env);
  842.                 $act_env->add_error($obj, $routine, \@routine_arg);
  843.                 last if $errcode == CGI_MX_ABORT;
  844.             }
  845.  
  846.             $new_state = $current_state if $errors;
  847.         }
  848.        
  849.         #
  850.         # Get the destination
  851.         #
  852.         #  * when an error was found, we look at -on_error or -dyn_on_error,
  853.         #    and if one is found, we clear the error condition.
  854.         #  * when no error is raised, we look at -dyn_target or -target.
  855.         #
  856.  
  857.         if ($errors) {
  858.             #
  859.             # Look for possible error trapping, which will force a move to
  860.             # an alternate screen.  The error condition is reset, therefore
  861.             # the internal context of the screen will be cleared.
  862.             #
  863.             # For -dyn_on_error, we append the action environment.
  864.             #
  865.  
  866.             DASSERT defined $act_env, "at least one action ran";
  867.  
  868.             if ($button_pressed->has_error_trap) {
  869.                 my $dyn = $button_pressed->dyn_on_error;
  870.                 if (defined $dyn) {
  871.                     my ($routine, @args) = $self->scatter($dyn);
  872.                     DASSERT $screen->can($routine);
  873.                     $new_state = $screen->$routine(@args, $act_env);
  874.                 } else {
  875.                     $new_state = $button_pressed->on_error;
  876.                 }
  877.                 DASSERT defined $new_state;
  878.                 $errors = 0;            # Moving to alternate screen
  879.             }
  880.         } else {
  881.             #
  882.             # No error found.
  883.             #
  884.  
  885.             if ($button_pressed->is_computed_target) {
  886.                 my ($routine, @args) =
  887.                   $self->scatter($button_pressed->dyn_target);
  888.                 DASSERT $screen->can($routine);
  889.  
  890.                 $new_state = $screen->$routine(@args);
  891.             }
  892.             else {
  893.                 $new_state = $button_pressed->target;
  894.             }
  895.         }
  896.     }
  897.  
  898.     # clear context area dedicated to save field handles
  899.     $self->context_root->[SCREEN_FIELD] = [];
  900.     $self->context_root->[SCREEN_BUTTON] = [];
  901.     # context might have been saved by the screen -> also clear the copy
  902.     $screen->_clear_internal_context() if defined $screen && !$errors;
  903.  
  904.     #
  905.     # update the MXSCREEN context
  906.     #
  907.     $ctx->{'current_state'} = $new_state;
  908.     $ctx->{'previous_state'} = $previous_state unless $errors;
  909.     $ctx->{'cgi_version'} = $self->cgi_version;
  910.     $ctx->{'bin_version'} = $BIN_VERSION;
  911.  
  912.     $self->log->notice(\&log_state, $self, $previous_state, $new_state);
  913.  
  914.     #
  915.     # Create the destination state (if needed)
  916.     # Then call ->leave and ->enter hooks.
  917.     #
  918.  
  919.     ($target_name, @arg_list) = $self->scatter($new_state);
  920.     unless (defined $screen && $target_name eq $origin_name) {
  921.         my $prev_screen = $screen;
  922.         $screen = $self->make_screen($target_name);
  923.         if (defined $prev_screen) {
  924.             $prev_screen->leave($screen);
  925.             $ctx->{'spring_state'} = $previous_state;   # Where we came from
  926.         }
  927.         $screen->enter($prev_screen);
  928.     }
  929.  
  930.     return DARY ($screen, \@arg_list);
  931. }
  932.  
  933. #
  934. # ->display
  935. #
  936. # Display $screen, with args @$args, with proper support for screen "bounce".
  937. #
  938. # If $stdout is not undef, then it is a ref to a tied object, meaning STDOUT
  939. # is buffered.  When bouncing with untied STDOUT, the layout and the headers
  940. # can only be emitted once, i.e. for the original screen.  A warning is issued
  941. # if bouncing.
  942. #
  943. # Returns screen that was finally displayed.
  944. #
  945. sub display {
  946.     DFEATURE my $f_;
  947.     my $self = shift;
  948.     my ($screen, $args, $stdout) = @_;
  949.  
  950.     for (my $i = 0; $i < 20; $i++) {        # Max 20 bounces
  951.  
  952.         #
  953.         # Can only emit the layout and the header each time when $stdout
  954.         # is tied.  We always emit the first time, naturally, since we
  955.         # don't know whether we'll bounce at all.
  956.         #
  957.  
  958.         if ($i == 0 || defined $stdout) {
  959.  
  960.             #
  961.             # The layout object controls the following aspects:
  962.             #
  963.             #  html headers
  964.             #    preamble
  965.             #      <form goes here>
  966.             #    postabmle
  967.             #  html trailers
  968.             #
  969.  
  970.             my $layout = $self->layout;
  971.  
  972.             $layout->init($screen);
  973.             $layout->start_HTML(
  974.                 -title      => $screen->screen_title,
  975.                 -bgcolor    => $screen->bgcolor,
  976.             );
  977.             $layout->preamble;
  978.  
  979.             #
  980.             # Start the form
  981.             #
  982.  
  983.             my @args = (-method => 'POST', -action => CGI::url());
  984.             print $CGI::DISABLE_UPLOADS ?
  985.                 CGI::startform(@args) : CGI::start_multipart_form(@args);
  986.  
  987.         }
  988.  
  989.         $stdout->header_ok if defined $stdout;      # Buffer remaining as BODY
  990.  
  991.         #
  992.         # Display target screen, trap all errors.
  993.         #
  994.  
  995.         eval {
  996.             local $SIG{__DIE__};
  997.             local $SIG{__WARN__};
  998.  
  999.             $screen->display(@$args);
  1000.         };
  1001.  
  1002.         #
  1003.         # Deal with "bounce" exceptions.
  1004.         #
  1005.  
  1006.         if (ref $@ && $@->isa("CGI::MxScreen::Exception::Bounce")) {
  1007.             my $old_state = $self->current_screen;
  1008.             my $new_state = $@->target;
  1009.             my $log = $self->log;
  1010.             my $old_name = $screen->name;
  1011.             my $old_screen = $screen;
  1012.  
  1013.             $log->notice(\&log_bounce, $self, $old_state, $new_state, $@);
  1014.             $log->debug(\&log_inc_times, $self, "bounce on \"%s\"", $old_name);
  1015.  
  1016.             my ($target_name, @arg_list) = $self->scatter($new_state);
  1017.  
  1018.             #
  1019.             # Clear buffered data in tied STDOUT, so we may start afresh
  1020.             # with new screen.  If the old screen had started emitting data
  1021.             # before bouncing, warn them: the screen should not have done so
  1022.             # anyway, so we may discard data bluntly.
  1023.             #
  1024.  
  1025.             if (defined $stdout) {
  1026.                 my $discarded = $stdout->reset;
  1027.                 logwarn "discarded %d byte%s emitted by \"%s\" " .
  1028.                     "(before bouncing to \"%s\")",
  1029.                     $discarded, $discarded == 1 ? "" : "s",
  1030.                     $old_name, $target_name if $discarded;
  1031.             }
  1032.  
  1033.             #
  1034.             # Set args for next loop.
  1035.             #
  1036.  
  1037.             $screen = $self->make_screen($target_name);
  1038.             $args   = \@arg_list;
  1039.             $self->ctx->{'current_state'} = $new_state;
  1040.  
  1041.             #
  1042.             # Need to call ->leave() and ->enter() when states are different.
  1043.             # We pass undef to leave() to indicate that we left as the result
  1044.             # of a bounce.
  1045.             #
  1046.             # We don't alter `spring_state' though.
  1047.             #
  1048.  
  1049.             if ($target_name ne $screen->name) {
  1050.                 $old_screen->leave(undef);              # Signals: bounced
  1051.                 $screen->enter($old_screen);
  1052.             }
  1053.  
  1054.             next;           # Restart display loop
  1055.         }
  1056.  
  1057.         #
  1058.         # Regular display error.
  1059.         #
  1060.  
  1061.         if (ref $@ || chomp $@) {
  1062.             my $msg = $@;
  1063.             $msg =~ s/^\(.*?\)\s+//;    # Remove already added session tag
  1064.             $self->log->critical("display error for screen \"%s\": %s",
  1065.                     $screen->name, $msg);
  1066.  
  1067.             #
  1068.             # If they buffered STDOUT, it's nice, because the screen will not
  1069.             # mix regular output and the error message.  And since we discard
  1070.             # even the form header, the Content-Type printed by CGI::Carp will
  1071.             # not even show!
  1072.             #
  1073.  
  1074.             untie_stdout(1) if defined $stdout;
  1075.             logdie $msg;
  1076.         }
  1077.  
  1078.         return DVAL $screen;        # Successfully displayed the screen
  1079.     }
  1080.  
  1081.     $self->log->critical("too many screen bounces");
  1082.     logdie "possible infinite loop detected, aborting";
  1083. }
  1084.  
  1085. #
  1086. # ->check_validity
  1087. #
  1088. # Check context validity: proper version, no timeout.
  1089. #
  1090. sub check_validity {
  1091.     DFEATURE my $f_;
  1092.     my $self = shift;
  1093.  
  1094.     unless (defined $self->context_root) {
  1095.         logerr "mangled context from %s", CGI::remote_host();
  1096.         $self->internal_error("cannot retrieve application context");
  1097.     }
  1098.  
  1099.     my $ctx = $self->ctx;
  1100.     return DVOID unless exists $ctx->{'cgi_version'};   # Empty context
  1101.  
  1102.     #
  1103.     # Ensure binary version (which traces variations in the way session
  1104.     # context are represented) is compatible.
  1105.     #
  1106.  
  1107.     my $bin = $ctx->{'bin_version'};
  1108.     if ($bin > $BIN_VERSION) {
  1109.         $self->internal_error(<<EOS);
  1110. Script session used a format (v$bin) more recent than I am (v$BIN_VERSION).
  1111. Please restart a new session.
  1112. EOS
  1113.     }
  1114.  
  1115.     #
  1116.     # check that the script file has not been modified (compare the
  1117.     # last modification time on the file system)
  1118.     #
  1119.     if ($ctx->{'script_date'} != (stat($0))[9]) {
  1120.         $self->internal_error(<<EOS);
  1121. Script file has been modified since the last display,
  1122. please restart a new session.
  1123. EOS
  1124.     }
  1125.  
  1126.     #
  1127.     # check whether the cgi version is still the same
  1128.     #
  1129.     if (defined $ctx->{'cgi_version'}) {
  1130.         my $version = $ctx->{'cgi_version'};
  1131.  
  1132.         if ($version ne $self->cgi_version) {
  1133.             $self->internal_error(<<EOS);
  1134. Script version has evolved since the last display, please restart a new session.
  1135. EOS
  1136.         }
  1137.     }
  1138.  
  1139.     #
  1140.     # check whether the timeout is not exhausted
  1141.     #
  1142.     if (defined $self->valid_time && defined $ctx->{'time'}) {
  1143.         my $last_time = $ctx->{'time'};
  1144.  
  1145.         if ((time - $last_time) > $self->valid_time) {
  1146.             $self->internal_error(<<EOS);
  1147. Session timeout since the last display, please restart a new session.
  1148. EOS
  1149.         }
  1150.     }
  1151.  
  1152.     return DVOID;
  1153. }
  1154.  
  1155. #
  1156. # ->internal_error
  1157. #
  1158. #
  1159. sub internal_error {
  1160.     DFEATURE my $f_;
  1161.     my $self = shift;
  1162.     my ($message) = @_;
  1163.  
  1164.     my $logmsg = $message;
  1165.     $logmsg =~ s/\s+/ /sg;
  1166.     logerr "internal error: $logmsg";
  1167.  
  1168.     untie_stdout(1);        # Restore original STDOUT stream, discard all
  1169.  
  1170.     my $layout = $self->layout;
  1171.  
  1172.     $layout->init(undef);
  1173.     $layout->start_HTML("Internal Script Error");
  1174.     $layout->preamble;
  1175.  
  1176.     print CGI::h1("Internal Script Error");
  1177.     print CGI::p(CGI::tt(ucfirst($message)));
  1178.     print CGI::p(CGI::a({-href => CGI::url()}, "Restart a new session"));
  1179.  
  1180.     $layout->postamble;
  1181.     $layout->end_HTML;
  1182.  
  1183.     my $log = $self->log;
  1184.     $log->alert("internal error: $logmsg") if defined $log;
  1185.  
  1186.     exit 0;
  1187. }
  1188.  
  1189. #
  1190. # ->trace_incoming
  1191. #
  1192. # Trace incoming parameters
  1193. #
  1194. sub trace_incoming {
  1195.     DFEATURE my $f_;
  1196.     foreach my $p (CGI::param()) {
  1197.         my $value = CGI::param($p);
  1198.         DTRACE(TRC_INFO, "incoming param: '$p' => '$value'");
  1199.     }
  1200.     return DVOID;
  1201. }
  1202.  
  1203. #
  1204. # (log_session)         -- logging callback
  1205. #
  1206. # Log session state
  1207. #
  1208. sub log_session {
  1209.     DFEATURE my $f_;
  1210.     my $self = shift;
  1211.     my $current = $self->current_screen || $self->initial_state;
  1212.     my $cxt = $self->ctx;
  1213.     my $cnt = $cxt->{log_cnt};
  1214.     my ($state) = $self->scatter($current);
  1215.     my $user = CGI::remote_user();
  1216.     my @url_param = CGI::url_param();
  1217.     my $query = join(';', map { "$_=" . CGI::url_param($_) } @url_param);
  1218.  
  1219.     my $msg = sprintf "[%s/%d]", $state, $cnt;
  1220.     $msg .= sprintf " t=%s", relative_age(int(time) - $cxt->{log_starttime});
  1221.     $msg .= sprintf " d=%s", relative_age($^T - $cxt->{time}) if $cnt;
  1222.     $msg .= " u=\"$user\"" if $user;
  1223.  
  1224.     #
  1225.     # If there were no parameters on the URL, CGI still returns one entry
  1226.     # for a "keywords" parameter, so we need to guard against this as well.
  1227.     #
  1228.  
  1229.     $msg .= " q=\"$query\"" if $query ne '' && $query ne 'keywords=';
  1230.  
  1231.     return DVAL $msg;
  1232. }
  1233.  
  1234. #
  1235. # (log_state)           -- logging callback
  1236. #
  1237. # Log state change and button pressed
  1238. #
  1239. sub log_state {
  1240.     DFEATURE my $f_;
  1241.     my $self = shift;
  1242.     my ($old, $new) = @_;
  1243.     my $cxt = $self->ctx;
  1244.     my $cnt = $cxt->{log_cnt};
  1245.  
  1246.     my ($old_state, @old_args) = $self->scatter($old);
  1247.  
  1248.     my $msg = sprintf "%s%s",
  1249.         $old_state, @old_args ? ("(" . join(', ', @old_args) . ")") : "";
  1250.  
  1251.     unless ($cnt) {                         # First time
  1252.         return DVAL '' unless @old_args;    # Don't log state if no args
  1253.         return DVAL $msg
  1254.     }
  1255.  
  1256.     my ($new_state, @new_args) = $self->scatter($new);
  1257.  
  1258.     $msg .= sprintf " -> %s%s",
  1259.         $new_state, @new_args ? ("(" . join(', ', @new_args) . ")") : "";
  1260.  
  1261.     #
  1262.     # Log button pressed, or bounce indication.
  1263.     #
  1264.  
  1265.     my $button = $self->button_pressed;
  1266.     if (defined $button) {
  1267.         my $name = $button->name;
  1268.         my $value = $button->value;
  1269.         $msg .= sprintf " on \"%s\" pressed", $value;
  1270.         $msg .= sprintf " (%s)", $name if $value ne $name;
  1271.     }
  1272.  
  1273.     return DVAL $msg;
  1274. }
  1275.  
  1276. #
  1277. # (log_bounce)          -- logging callback
  1278. #
  1279. # Log screen bounces
  1280. #
  1281. sub log_bounce {
  1282.     DFEATURE my $f_;
  1283.     my $self = shift;
  1284.     my ($old, $new, $bounce) = @_;
  1285.     my $cxt = $self->ctx;
  1286.  
  1287.     my ($old_state, @old_args) = $self->scatter($old);
  1288.  
  1289.     my $msg = sprintf "%s%s",
  1290.         $old_state, @old_args ? ("(" . join(', ', @old_args) . ")") : "";
  1291.  
  1292.     my ($new_state, @new_args) = $self->scatter($new);
  1293.  
  1294.     $msg .= sprintf " -> %s%s",
  1295.         $new_state, @new_args ? ("(" . join(', ', @new_args) . ")") : "";
  1296.  
  1297.     $msg .= " (via $bounce)";
  1298.  
  1299.     return DVAL $msg;
  1300. }
  1301.  
  1302. #
  1303. # (log_agent)           -- logging callback
  1304. #
  1305. # Log user agent
  1306. #
  1307. sub log_agent {
  1308.     DFEATURE my $f_;
  1309.     my $self = shift;
  1310.     my $cnt = $self->ctx->{log_cnt};
  1311.     return if $cnt;                 # Nothing after first time
  1312.     return DVAL sprintf "using \"%s\"", CGI::user_agent();
  1313. }
  1314.  
  1315. #
  1316. # (log_inc_times)       -- logging callback
  1317. #
  1318. # Log incremental time between values recorded in last_times and now.
  1319. # Update last_times as a side effect for next incremental logging.
  1320. #
  1321. sub log_inc_times {
  1322.     DFEATURE my $f_;
  1323.     my $self = shift;
  1324.     my ($fmt, @args) = @_;          # Can be single string or (fmt, args)
  1325.     $fmt = sprintf $fmt, @args if @args;
  1326.     my $times = $self->last_times;
  1327.     my $new_times = [time, (times)[0,1]];
  1328.     $self->{_last_times} = $new_times;
  1329.     my @delta;
  1330.     for (my $i = 0; $i < @$times; $i++) {
  1331.         $delta[$i] = $new_times->[$i] - $times->[$i];
  1332.     }
  1333.     return DVAL sprintf "t=%.2fs usr=%.2fs sys=%.2fs [%s]", @delta, $fmt;
  1334. }
  1335.  
  1336. #
  1337. # (log_total_time)      -- logging callback
  1338. #
  1339. # Log total time spent since start_times.
  1340. #
  1341. sub log_total_time {
  1342.     DFEATURE my $f_;
  1343.     my $self = shift;
  1344.     my $times = $self->start_times;
  1345.     my $new_times = [time, (times)[0,1]];
  1346.     my @delta;
  1347.     for (my $i = 0; $i < @$times; $i++) {
  1348.         $delta[$i] = $new_times->[$i] - $times->[$i];
  1349.     }
  1350.     my $runtime = time - $^T;
  1351.     return DVAL sprintf "t=%.2fs usr=%.2fs sys=%.2fs [total time] T=%.2fs",
  1352.         @delta, $runtime;
  1353. }
  1354.  
  1355. #
  1356. # relative_age
  1357. #
  1358. # Given seconds, convert to 4d9h23m15s format.
  1359. #
  1360. sub relative_age {
  1361.     DFEATURE my $f_;
  1362.     my ($secs) = @_;
  1363.     my ($days, $hours, $mins);
  1364.  
  1365.     $days  = int($secs / (24 * 60 * 60));
  1366.     $secs -= $days     * (24 * 60 * 60);
  1367.  
  1368.     $hours = int($secs / (60 * 60));
  1369.     $secs -= $hours    * (60 * 60);
  1370.  
  1371.     $mins  = int($secs / 60);
  1372.     $secs -= $mins     * 60;
  1373.  
  1374.     my $retstr  = '';
  1375.     $retstr .= $days  . "d" if $days;
  1376.     $retstr .= $hours . "h" if $hours;
  1377.     $retstr .= $mins  . "m" if $mins;
  1378.     $retstr .= int($secs + 0.5) . "s";  # can be fractional with Time::HiRes
  1379.  
  1380.     return DVAL $retstr;
  1381. }
  1382.  
  1383. #
  1384. # ::add_utils_path              -- static
  1385. #
  1386. # Screen designers can identify new Form::Utils packages for their own
  1387. # specific uses with this routine. It must be invoked in the user
  1388. # script as a static routine => CGI::MxScreen::add_utils_path , and
  1389. # before the creation of the MxScreen object.
  1390. #
  1391. # NB: This routine name is misleading: it does not involve file paths, but
  1392. # module names.  The purpose is to allow some kind of routine lookup to
  1393. # be able to locate a validation routine named "is_time" for instance.
  1394. # I'm keeping it for now, because it's been used in production, but this
  1395. # mechanism will have to be revisited.
  1396. #   -- RAM, 13/04/2001
  1397. #
  1398. sub add_utils_path {
  1399.     DFEATURE my $f_;
  1400.  
  1401.     VERIFY defined($_[0]) && !UNIVERSAL::isa($_[0], __PACKAGE__);
  1402.  
  1403.     require CGI::MxScreen::Form::Utils;
  1404.  
  1405.     CGI::MxScreen::Form::Utils::add_path(@_);
  1406.     return DVOID;
  1407. }
  1408.  
  1409. #
  1410. # ::untie_stdout
  1411. #
  1412. # Safely untie STDOUT by forcing a DESTROY, in case someone holds a reference
  1413. # on the tied object.
  1414. #
  1415. sub untie_stdout {
  1416.     DFEATURE my $f_;
  1417.     my ($discard) = @_;
  1418.     my $stdout = tied *main::STDOUT;
  1419.  
  1420.     #
  1421.     # Within CGI::MxScreen, all the packages that can be tied to STDOUT are
  1422.     # heirs of CGI::MxScreen::Tie::Sinkable, which provides a discard_all()
  1423.     # method.
  1424.     #
  1425.  
  1426.     DASSERT !defined($stdout) || $stdout->isa("CGI::MxScreen::Tie::Sinkable");
  1427.  
  1428.     if (defined $stdout) {
  1429.         logtrc 'info', "un-tieing STDOUT (%s) with%s discarding",
  1430.             ref $stdout, $discard ? "" : "out";
  1431.         $stdout->discard_all if defined $discard && $discard;
  1432.         $stdout->DESTROY;
  1433.         untie *main::STDOUT;
  1434.     }
  1435.     return DVOID;
  1436. }
  1437.  
  1438. #
  1439. # END
  1440. #
  1441. # Whatever happens, log total running time, provided they created a manager.
  1442. #
  1443. sub END {
  1444.     untie_stdout();     # They might have not got a chance to do so yet
  1445.  
  1446.     #
  1447.     # Log running time, once per manager.
  1448.     #
  1449.  
  1450.     foreach my $self (@managers) {
  1451.         $self->log->info(\&log_total_time, $self);
  1452.     }
  1453. }
  1454.  
  1455. 1;
  1456. __END__
  1457.  
  1458. =head1 NAME
  1459.  
  1460. CGI::MxScreen - a multi-screen stateful CGI framework
  1461.  
  1462. =head1 SYNOPSIS
  1463.  
  1464.  require CGI::MxScreen;
  1465.  
  1466.  my $manager = CGI::MxScreen->make(
  1467.      -bgcolor    => "#dedeef",
  1468.      -screens    =>
  1469.          {
  1470.              "state_1"   =>
  1471.                  [-class => "STATE_1", -title => "Hello"],
  1472.              "state_2"   =>
  1473.                  [-class => "STATE_2", -title => "Hello #2"],
  1474.          },
  1475.      -initial    => "state_1",
  1476.      -version    => "1.0",
  1477.  );
  1478.  
  1479.  $manager->play();
  1480.  
  1481. =head1 DESCRIPTION
  1482.  
  1483. C<CGI::MxScreen> is a framework for building multi-screen stateful CGI
  1484. programs.  It is rather object-oriented, with some peculiarities brought
  1485. by persistency constraints: all objects must be handled by C<Storable>.
  1486.  
  1487. C<CGI::MxScreen> is based on the C<CGI> module, and co-operates with it,
  1488. meaning you are able to use most C<CGI> calls normally.  The few places
  1489. where you should not is where C<CGI::MxScreen> supersedes the C<CGI>
  1490. functionalities: for instance, there's no need to propagate hidden values
  1491. when you use C<CGI::MxScreen>.
  1492.  
  1493. C<CGI::MxScreen> is architected around the concept of B<screens>.
  1494. Among the set of defined screens within the same script, only one is visible
  1495. at a time.  One moves around the various screens by pressing buttons,
  1496. which submit data to the server and possibly move you to a different screen.
  1497. The state machine is handled by C<CGI::MxScreen>, the user only defines
  1498. which state (I<screen>) a button shall move the application to
  1499.  
  1500. C<CGI::MxScreen> is stateful in the sense that many of the runtime objects
  1501. created to operate (and screens are among those) are made persistent.
  1502. This is a very interesting property, because you do not have to worry
  1503. too much about the underlying stateless nature of the CGI protocol.  The
  1504. C<CGI> module brought the statefulness to the level of form controls, but
  1505. C<CGI::MxScreen> raises it to the level of the application itself.
  1506.  
  1507. C<CGI::MxScreen> is not meant to be used for so-called I<quick and dirty>
  1508. scripts, or for scripts which do not require some fair amount of round trips
  1509. between the browser and the server.  You'll be better off with using
  1510. the good old C<CGI> module.  However, for more complex web applications,
  1511. where there is a fair amount of processing required on the server side, and
  1512. where each script involves several states, C<CGI::MxScreen> is for you.
  1513.  
  1514. OK, enough talking.
  1515.  
  1516. =head1 FRAMEWORK
  1517.  
  1518. This section describes the C<CGI::MxScreen> framework.  If you wish to
  1519. read about the interface of the C<CGI::MxScreen> managing object, please
  1520. skip down to L<"INTERFACE">.
  1521.  
  1522. =head2 Features
  1523.  
  1524. Here are the main features of C<CGI::MxScreen>:
  1525.  
  1526. =over 4
  1527.  
  1528. =item *
  1529.  
  1530. The module is a superset of the C<CGI> module.  You can continue to use C<CGI>
  1531. routines wherever you like.
  1532.  
  1533. =item *
  1534.  
  1535. It handles B<sessions> for you, saving much of the application state, and
  1536. making CGI hidden parameters useless.  You may save sessions within the
  1537. browser, or to files, or you may even build your own medium backend.
  1538. You may also define your own serializing options, although C<Storable> is
  1539. natively supported.
  1540. See L<CGI::MxScreen::Session::Medium> for the medium interface and
  1541. L<CGI::MxScreen::Serializer> for the serialization interface.
  1542.  
  1543. =item *
  1544.  
  1545. It handles the state machine for you.  You define the various B<screen
  1546. objects>, and then specify, for each submit button, which state the
  1547. application should go.  The target state can be specified statically,
  1548. or computed dynamically by the application.  Action routines can be
  1549. attached to the button, to run some processing during the state change.
  1550. See L<CGI::MxScreen::Form::Button> for more information.
  1551.  
  1552. =item *
  1553.  
  1554. It has an object-oriented design.  Each screen is an object inheriting from
  1555. C<CGI::MxScreen::Screen> and redefining the C<display> routine, at least.
  1556. There are also C<enter> and C<leave> hooks for each screen.
  1557. Each created screen object is made persistent accross the whole session.
  1558. See L<CGI::MxScreen::Screen> for the full interface.
  1559.  
  1560. =item *
  1561.  
  1562. Any script output done before the screen's C<display> routine is called
  1563. will be trapped and discarded (with logging showing the place where such a
  1564. violation occurs).  This architecturally enforces proper application behaviour.
  1565. Furthermore, by default, the whole output is buffered until it is
  1566. time to save the context, thereby protecting against further submits
  1567. with a partially received form on the browser side, and also strengthening
  1568. the protection when the application uses bounce exceptions to jump into
  1569. another state.
  1570.  
  1571. =item *
  1572.  
  1573. Each CGI parameter (form control) can be given an explicit storage indication
  1574. (i.e. how the application should dispose of the value), a validation routine,
  1575. and an on-the-fly patching routines (to normalize values, for instance).
  1576. Each parameter may also be given a mandatory status, causing an error when
  1577. it is not filled.
  1578. See L<CGI::MxScreen::Form::Field> for more information.
  1579.  
  1580. =item *
  1581.  
  1582. There is a global hash that is made available to all screens and which is
  1583. made persistent accross the whole session.  By default, every key access
  1584. to that hash is checked to prevent typos, and reading an unknown key is
  1585. a fatal error (at run-time, unfortunately).
  1586.  
  1587. =item *
  1588.  
  1589. There are layout hooks allowing the generation of a common preamble and
  1590. postamble section, common to a group of scripts.  See L<CGI::MxScreen::Layout>
  1591. for details.
  1592.  
  1593. =item *
  1594.  
  1595. The framework can be configured by loading a configuration Perl script,
  1596. allowing easy sharing of the settings among a set of scripts, with
  1597. possible local superseding on a script basis.  See L<CGI::MxScreen::Config>
  1598. for details.
  1599.  
  1600. =item *
  1601.  
  1602. All error logging is done via C<Log::Agent>, and application logging is
  1603. done via C<Log::Agent::Logger>, which ensures the maximum flexibility.
  1604. Logfile rotation is also supported via C<Log::Agent::Rotate>.
  1605. Configuration of the various logging parameters is done via the
  1606. C<CGI::MxScreen::Config> interface.
  1607.  
  1608. =item *
  1609.  
  1610. C<CGI::MxScreen> uses C<Carp::Datum> internally.  If you have chosen to
  1611. install a non-stripped version, you may trace parts of the module to better
  1612. understand what is going on with the various callbacks you register.
  1613.  
  1614. =back
  1615.  
  1616. =head2 Flow
  1617.  
  1618. Here is a high-level description of the processing flow when issuing requests
  1619. to a C<CGI::MxScreen> script:
  1620.  
  1621. =over 4
  1622.  
  1623. =item *
  1624.  
  1625. An initial log tracing the user (if HTTP authentication is used), the
  1626. time since the session started, the elapsed time since the previous display,
  1627. and the CGI query string is emitted.
  1628.  
  1629. =item *
  1630.  
  1631. The session context is retrieved if any, otherwise a new one is created.
  1632. The context holds the various screen objects, the submit buttons and other
  1633. form fields descriptions, plus all the other data stored within the
  1634. persistent global hash.
  1635.  
  1636. =item *
  1637.  
  1638. Input parameters are processed, following the directives held within the
  1639. session to validate and optionally store them in some place.
  1640. If an error is detected, the application remains in the same state and
  1641. the previous screen is redisplayed.
  1642.  
  1643. =item *
  1644.  
  1645. If no error occurred during parameter processing, the target state is computed
  1646. based on the descriptions attached to the button that was pressed.  The
  1647. state can be given statically, or computed by a routine.
  1648. The determined target state is composed of a screen object, plus some optional
  1649. arguments that are to be given to its C<display> routine.
  1650. Any processing action attached to the button is also run at that point.
  1651.  
  1652. =item *
  1653.  
  1654. The transition is logged, tracing the pressed button, the previous state
  1655. and the new one.
  1656.  
  1657. =item *
  1658.  
  1659. If a screen change occurs (i.e. the new screen to display is not the same
  1660. as the previously displayed one), the C<leave> routine is called on the
  1661. old screen and C<enter> is called on the new one.
  1662.  
  1663. =item *
  1664.  
  1665. The enclosing form setting is emitted, and the screen's C<display> routine
  1666. is called to actually generate the form's content.  Before they output
  1667. anything, screens are allowed to request the bouncing to some other state,
  1668. based on some local information (but if output buffering is configured, any
  1669. spurious output from the old screen will be cleanly discarded).
  1670. Any other exception that can occur during C<display> is trapped and cleanly
  1671. logged, before displaying an internal error message.
  1672.  
  1673. =item *
  1674.  
  1675. The application context is saved, the form is closed, and buffered output
  1676. is emitted.  A final log tracing the total time spent is emitted.
  1677.  
  1678. =back
  1679.  
  1680. =head2 Example
  1681.  
  1682. The following example demonstrates the various common operations that need
  1683. to be performed with C<CGI::MxScreen>.
  1684.  
  1685. An important comment first: if we forget about the fact that you need
  1686. an object per screen (which has some code overhead compared to using
  1687. plain C<CGI>), you will need to write more I<declarative> code with
  1688. C<CGI::MxScreen> than you would with C<CGI>, but this buys you more
  1689. persistent state for fields, and lets you define the state transitions and
  1690. associated processing for buttons.
  1691.  
  1692. Moreover, please note that this example could be written in less code
  1693. by using the C<CGI> module only.  But C<CGI::MxScreen> is not aimed at
  1694. simple scripts.
  1695.  
  1696. Our example defines a two-state script, where one choose a color in the
  1697. first screen, and then a week day in the second screen.  The script reminds
  1698. you about the choice made in the other screen, if any.  It is possible to
  1699. "redraw" the first screen to prove that the selection made is sticky.
  1700. First, the whole script:
  1701.  
  1702.   1 #!/usr/local/bin/perl -T
  1703.   2
  1704.   3 package Color; use base qw(CGI::MxScreen::Screen);
  1705.   4
  1706.   5 use CGI qw/:standard/;
  1707.   6
  1708.   7 sub init {
  1709.   8     my $self = shift;
  1710.   9     $self->vars->{color} = "";
  1711.  10 }
  1712.  11
  1713.  12 sub display {
  1714.  13     my $self = shift;
  1715.  14     print h1($self->screen_title);
  1716.  15
  1717.  16     my $color = $self->record_field(
  1718.  17         -name       => "color",
  1719.  18         -storage    => "color",
  1720.  19         -default    => $self->vars->{color} || "Green",
  1721.  20         -override   => 1,
  1722.  21         -values     => [qw(Red Green Blue White Black Yellow Orange Cyan)],
  1723.  22     );
  1724.  23
  1725.  24     print p("You told me your favorite weekday was", $self->vars->{weekday})
  1726.  25         if exists $self->vars->{weekday};
  1727.  26
  1728.  27     print p("Your favorite color is", popup_menu($color->properties));
  1729.  28
  1730.  29     my $ok = $self->record_button(
  1731.  30         -name   => "Next",
  1732.  31         -target => "Weekday");
  1733.  32
  1734.  33     my $redraw = $self->record_button(
  1735.  34         -name   => "Redraw",
  1736.  35         -target => $self->current_screen);
  1737.  36
  1738.  37     print submit($ok->properties), submit($redraw->properties);
  1739.  38 }
  1740.  39
  1741.  40 package Weekday; use base qw(CGI::MxScreen::Screen);
  1742.  41
  1743.  42 use CGI qw/:standard/;
  1744.  43
  1745.  44 sub init {
  1746.  45     my $self = shift;
  1747.  46     $self->vars->{weekday} = "";
  1748.  47 }
  1749.  48
  1750.  49 sub display {
  1751.  50     my $self = shift;
  1752.  51     print h1($self->screen_title);
  1753.  52
  1754.  53     print p("You told me your favorite color was", $self->vars->{color});
  1755.  54
  1756.  55     my $weekday = $self->record_field(
  1757.  56         -name       => "day",
  1758.  57         -storage    => "weekday",
  1759.  58         -default    => $self->vars->{weekday} || "Mon",
  1760.  59         -override   => 1,
  1761.  60         -values     => [qw(Mon Tue Wed Thu Fri Sat Sun)],
  1762.  61     );
  1763.  62
  1764.  63     print p("Your favorite weekday is", popup_menu($weekday->properties));
  1765.  64
  1766.  65     my $back = $self->record_button(
  1767.  66         -name       => "Back",
  1768.  67         -target     => $self->spring_screen,
  1769.  68     );
  1770.  69
  1771.  70     print submit($back->properties);
  1772.  71 }
  1773.  72
  1774.  73 package main;
  1775.  74
  1776.  75 require CGI::MxScreen;
  1777.  76
  1778.  77 my $manager = CGI::MxScreen->make(
  1779.  78     -screens    =>
  1780.  79         {
  1781.  80             'Color'     => [-class => 'Color',   -title => "Choose Color" ],
  1782.  81             'Weekday'   => [-class => 'Weekday', -title => "Choose Day" ],
  1783.  82         },
  1784.  83     -initial    => ['Color'],
  1785.  84 );
  1786.  85
  1787.  86 $manager->play();
  1788.  87
  1789.  
  1790. Let's study this a piece at a time:
  1791.  
  1792.   1 #!/usr/local/bin/perl -T
  1793.   2
  1794.  
  1795. The classical declaration for a CGI script, in taint mode.
  1796.  
  1797.   3 package Color; use base qw(CGI::MxScreen::Screen);
  1798.   4
  1799.  
  1800. This defines the first state, C<Color>.  It inherits from
  1801. C<CGI::MxScreen::Screen>, as it should.
  1802.  
  1803.   5 use CGI qw/:standard/;
  1804.   6
  1805.  
  1806. We're going to use CGI routines.  We could do with less than what is
  1807. exported by the C<:standard> tag, but I did not bothered.
  1808.  
  1809.   7 sub init {
  1810.   8     my $self = shift;
  1811.   9     $self->vars->{color} = "";
  1812.  10 }
  1813.  11
  1814.  
  1815. The C<init()> routine is called on the screen the first time it is created.
  1816. Upon further invocations, the same screen object will be used and re-used
  1817. each time we need to access the C<Color> state.
  1818.  
  1819. To differentiate from a plain C<CGI> script which would use hidden parameters
  1820. to propagate the information, we store the application variable in the
  1821. persistent hash table, which every screen can access through C<$self-E<gt>vars>.
  1822. Here, we initialize the C<"color"> key, because any access to an unknown
  1823. key is an error at runtime (to avoid malicious typos).
  1824.  
  1825.  12 sub display {
  1826.  13     my $self = shift;
  1827.  
  1828. The C<display()> routine is invoked by the state manager on the screen
  1829. selected for displaying.
  1830.  
  1831.  14     print h1($self->screen_title);
  1832.  15
  1833.  
  1834. Prints screen title.  This refers to the defined title in the manager,
  1835. which are declared for each known screen further down on lines 78-82.
  1836.  
  1837.  16     my $color = $self->record_field(
  1838.  17         -name       => "color",
  1839.  18         -storage    => "color",
  1840.  19         -default    => $self->vars->{color} || "Green",
  1841.  20         -override   => 1,
  1842.  21         -values     => [qw(Red Green Blue White Black Yellow Orange Cyan)],
  1843.  22     );
  1844.  23
  1845.  
  1846. This declaration is very important.  It tells C<CGI::MxScreen> that the
  1847. screen makes use of a field named C<"color">, and whose value should be
  1848. stored in the global persistent hash under the key C<"color"> (as per
  1849. the C<-storage> indication).
  1850.  
  1851. The remaining attributes are simply collected to be passed to the
  1852. C<popup_menu()> routine via C<$color->properties> below.  They could be
  1853. omitted, and added inline when C<popup_menu()> is called, but it's best
  1854. to regroup common things together.
  1855.  
  1856. The underlying object created by C<record_field()> will be serialized
  1857. and included in the C<CGI::MxScreen> context (only the relevant attributes
  1858. are serialized, i.e. C<CGI> parameters such as C<-values> are not).
  1859. This will allow the processing engine to honour some meaningful actions,
  1860. such as validation, storage, or on-the-fly patching.
  1861.  
  1862. Another important property of those objects is that C<CGI::MxScreen> will
  1863. update the value attribute, which would be noticeable if there was no
  1864. C<-default> line: you could query C<$color->value> to get the current
  1865. CGI parameter value, as submitted.
  1866.  
  1867.  24     print p("You told me your favorite weekday was", $self->vars->{weekday})
  1868.  25         if exists $self->vars->{weekday};
  1869.  26
  1870.  
  1871. If we have been in the C<Weekday> screen, then the key C<"weekday"> will
  1872. be existing in the global hash C<$self-E<gt>vars>, because it is created by
  1873. the C<init()> routine of that object, at line 46.  If we tried to access
  1874. the key without protecting by the C<exists> test on line 25, we'd get
  1875. a fatal error saying:
  1876.  
  1877.     access to unknown key 'weekday'
  1878.  
  1879. This protection can be disabled if you want it so, but it is on by default.
  1880. It will probably save you one day, but unfortunately this is a runtime check.
  1881.  
  1882.  27     print p("Your favorite color is", popup_menu($color->properties));
  1883.  28
  1884.  
  1885. The above is generating the sole input of this screen, i.e. a popup
  1886. menu so that you can select your favorite color.  Note that we're passing
  1887. C<popup_menu()>, which is a routine from the C<CGI> module, a list of
  1888. arguments derived from the recorded field C<$color>, created at line 16.
  1889.  
  1890.  29     my $ok = $self->record_button(
  1891.  30         -name   => "Next",
  1892.  31         -target => "Weekday");
  1893.  32
  1894.  
  1895. This declaration is also very important.  We're using C<record_button()>
  1896. to declare a state transition: we wish to move to the C<Weekday> screen
  1897. when the button I<Next> is pressed.
  1898.  
  1899.  33     my $redraw = $self->record_button(
  1900.  34         -name   => "Redraw",
  1901.  35         -target => $self->current_screen);
  1902.  36
  1903.  
  1904. The I<Redraw> button simply redisplays the current screen, i.e. there
  1905. is no transition to another screen (state).  The I<current_screen>
  1906. routine returns the name of the current screen we're in, along with all
  1907. the parameters we were called with, so that the transition is indeed towards
  1908. the exact same state.
  1909.  
  1910.  37     print submit($ok->properties), submit($redraw->properties);
  1911.  38 }
  1912.  39
  1913.  
  1914. We're finishing the C<display> routine by calling the C<submit()> routine
  1915. from the C<CGI> module to generate the submit buttons.  Here again, we're
  1916. calling C<properties()> on each button object to expand the CGI parameters,
  1917. just like we did for the field on line 27.
  1918.  
  1919.  40 package Weekday; use base qw(CGI::MxScreen::Screen);
  1920.  41
  1921.  42 use CGI qw/:standard/;
  1922.  43
  1923.  
  1924. This defines the second state, C<Weekday>.  It inherits from
  1925. C<CGI::MxScreen::Screen>, as it should.  We also import the C<CGI>
  1926. functions in that new package.
  1927.  
  1928. Note that the name of the class need not be the name of the state.
  1929. The association between state name and classes is done during the creation
  1930. of the manager object (see lines 78-82).
  1931.  
  1932.  44 sub init {
  1933.  45     my $self = shift;
  1934.  46     $self->vars->{weekday} = "";
  1935.  47 }
  1936.  48
  1937.  
  1938. Recall that C<init()> is called when the screen is created.  Since screen
  1939. objects are made persistent for the duration of the whole session (i.e.
  1940. while the user is interacting with the script's forms), that means the
  1941. routine is called I<once> for every screen that gets created.
  1942.  
  1943. Here, we initialize the C<"weekday"> key, which is necessary because we're
  1944. going to use it line 58 below...
  1945.  
  1946.  49 sub display {
  1947.  50     my $self = shift;
  1948.  51     print h1($self->screen_title);
  1949.  52
  1950.  
  1951. This is the C<display()> routine for the screen C<Weekday>.  It will be
  1952. called by the C<CGI::MxScreen> manager when the selected state is C<"Weekday">
  1953. (name determined line 81 below).
  1954.  
  1955.  53     print p("You told me your favorite color was", $self->vars->{color});
  1956.  54
  1957.  
  1958. We remind them about the color they have chosen in the previous screen.
  1959. Note that we don't rely on a hidden parameter to propagate that value:
  1960. because it is held in the global persistent hash, it gets part of the
  1961. session context and is there for the duration of the session.
  1962.  
  1963.  55     my $weekday = $self->record_field(
  1964.  56         -name       => "day",
  1965.  57         -storage    => "weekday",
  1966.  58         -default    => $self->vars->{weekday} || "Mon",
  1967.  59         -override   => 1,
  1968.  60         -values     => [qw(Mon Tue Wed Thu Fri Sat Sun)],
  1969.  61     );
  1970.  62
  1971.  
  1972. The declaration of the field used to ask them about their preferred week day.
  1973. It looks a lot like the one we did for the color, on lines 16-22, with the
  1974. exception that the field name is C<"day"> but the storage in the context
  1975. is C<"weekday"> (we used the same string C<"color"> previously).
  1976.  
  1977.  63     print p("Your favorite weekday is", popup_menu($weekday->properties));
  1978.  64
  1979.  
  1980. The above line generates the popup.  This will create a selection list
  1981. whose CGI name is C<"day">.  However, upon reception of that parameter,
  1982. C<CGI::MxScreen> will immediately save the value to the location identified
  1983. by the C<-storage> line, thereby making the value available to the application
  1984. via the C<$self-E<gt>vars> hash.
  1985.  
  1986.  65     my $back = $self->record_button(
  1987.  66         -name       => "Back",
  1988.  67         -target     => $self->spring_screen,
  1989.  68     );
  1990.  69
  1991.  
  1992. We declare a button named I<Back>, which will bring us back to the screen
  1993. we were when we sprang into the current screen.  That's what C<spring_screen>
  1994. is about: it refers to the previous stable screen.  Here, since there is
  1995. no possibility to remain in the current screen, it will be the previous screen.
  1996. But if we had a I<redraw> button like we had in the I<Color> screen, which
  1997. would make a transition to the same state, then C<spring_screen> will still
  1998. correctly point to C<Color>, whereas C<previous_screen> would be C<Weekday>
  1999. in that case.
  2000.  
  2001.  70     print submit($back->properties);
  2002.  71 }
  2003.  72
  2004.  
  2005. This closes the C<display()> routine by generating the sole submit button
  2006. for that screen.
  2007.  
  2008.  73 package main;
  2009.  74
  2010.  
  2011. We now leave the screen definition and enter the main part, where the
  2012. C<CGI::MxScreen> manager gets created and invoked.  In real life, the code
  2013. for screens would not be inlined but stored in a dedicated file, one file
  2014. for each class, and the CGI script would only contain the following code,
  2015. plus some additional configuration.
  2016.  
  2017.  75 require CGI::MxScreen;
  2018.  76
  2019.  
  2020. We're not "using" it, only "requiring" since we're creating an object,
  2021. not using any exported routine.
  2022.  
  2023.  77 my $manager = CGI::MxScreen->make(
  2024.  78     -screens    =>
  2025.  79         {
  2026.  80             'Color'     => [-class => 'Color',   -title => "Choose Color" ],
  2027.  81             'Weekday'   => [-class => 'Weekday', -title => "Choose Day" ],
  2028.  82         },
  2029.  83     -initial    => ['Color'],
  2030.  84 );
  2031.  85
  2032.  
  2033. The states of our state machine are described above.  The keys of the
  2034. C<-screens> argument are the valid state names, and each state name is
  2035. associated with a class, and a screen title.  This screen title will be
  2036. available to each screen with C<$self-E<gt>title>, but there's no
  2037. obligation for screens to display that information.  However, the manager
  2038. needs to know because when the C<display()> routine for the script is called,
  2039. the HTML header has already been generated, and that includes the title.
  2040.  
  2041. The act of creating the manager object raises some underlying processing:
  2042. the session context is retrieved, incoming parameters are processed and
  2043. silently validated.
  2044.  
  2045.  86 $manager->play();
  2046.  87
  2047.  
  2048. This finally launches the state machine: the next state is computed, action
  2049. callbacks are fired, and the target screen is displayed.
  2050.  
  2051. =head2 More Readings
  2052.  
  2053. To learn about the interface of the C<CGI::MxScreen> manager object,
  2054. see L<"INTERFACE"> below.
  2055.  
  2056. To learn about the screen interface, i.e. what you must implement when you
  2057. derive your own objects, what you can redefine, what you should not override
  2058. (the other features that you cannot redefine, so to speak), please
  2059. read L<CGI::MxScreen::Screen>.
  2060.  
  2061. To learn more about the configuration options, see L<CGI::MxScreen::Config>.
  2062.  
  2063. For information on the processing done on recorded fields, read
  2064. L<CGI::MxScreen::Form::Field> and L<CGI::MxScreen::Form::Utils>.
  2065.  
  2066. For information on the state transitions that can be recorded, and the
  2067. associated actions, see L<CGI::MxScreen::Form::Button>.
  2068.  
  2069. The various session management schemes offered are described in
  2070. L<CGI::MxScreen::Session::Medium>.
  2071.  
  2072. The layering hooks allowing you to control where the generated HTML for
  2073. the current screen goes in your grand formatting scheme are described
  2074. in L<CGI::MxScreen::Layout>.
  2075.  
  2076. Finally, the extra HTML-generating routines that are not implemented by
  2077. the C<CGI> module are presented in L<CGI::MxScreen::HMTL>.
  2078.  
  2079. =head1 SPECIFIC DATA TYPES
  2080.  
  2081. This sections documents in a central place the I<state> and I<callback>
  2082. representations that can be used throughout the C<CGI::MxScreen> framework.
  2083.  
  2084. Those specifications must be serializable, therefore all callbacks
  2085. are expressed in various symbolic forms, avoiding code references.
  2086.  
  2087. Do not forget that I<all> the arguments you specify in callbacks and screens
  2088. get serialized into the context.  Therefore, you must make sure your
  2089. objects are indeed serializable by the serializer (which is C<Storable>
  2090. by default, well, actually C<CGI::MxScreen::Serializer::Storable>, which is
  2091. wrapping the C<Storable> interface to something C<CGI::MxScreen> understands).
  2092. See L<CGI::MxScreen::Config> to learn how to change the
  2093. serializer, and L<CGI::MxScreen::Serializer> for the interface it must
  2094. follow.
  2095.  
  2096. =head2 States
  2097.  
  2098. A state is a screen name plus all the arguments that are given to its
  2099. C<display()> routine.  However, the language used throughout this
  2100. documentation is not too strict, and we tend to blurr the distinction between
  2101. a state and a screen by forgetting about the parameters.  That is because,
  2102. in practice, the parameters are simply there to offer a slight variation
  2103. of the overall screen dispay, but it is fundamentally the same screen.
  2104.  
  2105. Anyway, a state can be either given as:
  2106.  
  2107. =over 4
  2108.  
  2109. =item *
  2110.  
  2111. A plain scalar, in which case it must be the name of a screen, as
  2112. configured via C<-screens> (see L<Creation Routine> below), and the
  2113. screen's C<display()> routine will be called without any parameter.
  2114.  
  2115. =item *
  2116.  
  2117. An array ref, whose first item is the screen name, followed by
  2118. arguments to be given to C<display()>.  For instance:
  2119.  
  2120.     ["Color", "blue"]
  2121.  
  2122. would represent the state obtained by calling C<display("blue")> on the
  2123. screen object known as I<Color>.
  2124.  
  2125. =back
  2126.  
  2127. =head2 Callbacks
  2128.  
  2129. When an argument expects a I<callback>, you may provide it under the
  2130. foloowing forms.
  2131.  
  2132. =over 4
  2133.  
  2134. =item *
  2135.  
  2136. As a scalar name, e.g. C<'validate'>.
  2137.  
  2138. The exact interpretation of this form depends on the object where you
  2139. specify it.  Withing a C<CGI::MxScreen::Form::Button>, it specifies a
  2140. routine to call on the screen object, without any user parameter.  However,
  2141. within a C<CGI::MxScreen::Form::Field>, it could be a routine to lookup
  2142. within the utility namespaces.  More on the latter in L<Utility Path>.
  2143.  
  2144. =item *
  2145.  
  2146. As a list reference, starting with a scalar name:
  2147.  
  2148.     ['routine', @args]
  2149.  
  2150. This specifies that C<routine(@args)> should be called on the screen object.
  2151.  
  2152. =item *
  2153.  
  2154. As a list reference, beginning with an object reference:
  2155.  
  2156.     [$obj, 'routine', @args]
  2157.  
  2158. which specifies that <$obj-E<gt>routine(@args)> should be called, i.e.
  2159. the target object is no longer the screen object.
  2160. It is available to C<CGI::MxScreen::Form::Button> objects only.
  2161.  
  2162. =back
  2163.  
  2164. =head1 INTERFACE
  2165.  
  2166. The public interface with the manager object is quite limited.
  2167. The main entry points are the creation routine, which configures the
  2168. overall operating mode, and the C<play()> routine, which launches the
  2169. state machine resolution.
  2170.  
  2171. =head2 Creation Routine
  2172.  
  2173. As usual, the creation routine is called C<make()>.  It takes a list of
  2174. named arguments, some of which are optional:
  2175.  
  2176. =over 4
  2177.  
  2178. =item C<-bgcolor> => I<color>
  2179.  
  2180. Optional, sets the default background color to be used for all screens.
  2181. If unspecified, the value is I<gray75>, aka C<"#bfbfbf">, which is the
  2182. default background in Netscape on Unix.  The value you supply will be
  2183. used in the BGCOLOR HTML tag, so any legal value there can be used.
  2184. For instance:
  2185.  
  2186.     -bgcolor    => "beige"
  2187.  
  2188. You may override the default background on a screen basis, as explained
  2189. in L<CGI::MxScreen::Screen/"Creation Routine">.
  2190.  
  2191. =item C<-initial> => I<scalar> | I<array_ref>
  2192.  
  2193. Mandatory, defines the initial state.  See L<States> above for the
  2194. actual format details.
  2195.  
  2196. The following two forms have identical effects:
  2197.  
  2198.     -initial    => ["Color"]
  2199.     -initial    => "Color"
  2200.  
  2201. and both define a state I<Color> whose C<display()> routine is called
  2202. without arguments.
  2203.  
  2204. =item C<-layout> => I<layout_object>
  2205.  
  2206. Optional, provides a C<CGI::MxScreen::Layout> object to be used for laying
  2207. out the screen's HTML generated by C<display()>.  See L<CGI::MxScreen::Layout>
  2208. for details.
  2209.  
  2210. =item C<-screens> => I<hash_ref>
  2211.  
  2212. Mandatory, defines the list of valid states, whose class will handle it, and
  2213. what the title of the page should be in that state.  Usually, there is
  2214. identity between a screen and a state, but via the C<display()> parameters,
  2215. you can have the same screen object used in two different states, with a
  2216. slightly different mode of operation.
  2217.  
  2218. The hash reference given here is indexed by state names.  The values must
  2219. be array references, and their content is the list of arguments to supply
  2220. to the screen's creation routine, plus a C<-class> argument defining the
  2221. class to use.  See L<CGI::MxScreen::Screen/"Creation Routine">.
  2222.  
  2223. Example of I<hash_ref>:
  2224.  
  2225.     {
  2226.         'Color'     => [-class => 'Color',   -title => "Choose Color" ],
  2227.         'Weekday'   => [-class => 'Weekday', -title => "Choose Day" ],
  2228.     }
  2229.  
  2230. The above sequence defines two states, each implemented by its own class.
  2231.  
  2232. =item C<-timeout> => I<seconds>
  2233.  
  2234. Optional, defines a session timeout, which will be enforced by C<CGI::MxScreen>
  2235. when retrieving the session context.  It must be smaller than the session
  2236. cleaning timout, if sessions are not stored within the browser.
  2237.  
  2238. When the session is expired, there is an error message stating so and the
  2239. user is invited to restart a new session.
  2240.  
  2241. =item C<-version> => I<string>
  2242.  
  2243. Defines the script's version.  This is I<your> versioning scheme, which has
  2244. nothing to do with the one used by C<CGI::MxScreen>.
  2245.  
  2246. You should use this to track changes in the screen objects that would
  2247. make deserialization of previous ones (from an old session) improper.  For
  2248. instance, if you add attributes to your screen objects and depend on them
  2249. being set up, an old screen will not bear them, and your application will
  2250. fail in mysterious ways.
  2251.  
  2252. By upgrading C<-version> each time such an incompatibility is introduced,
  2253. you let C<CGI::MxScreen> trap the error and produce an error message.
  2254.  
  2255. =back
  2256.  
  2257. =head2 Features
  2258.  
  2259. =over 4
  2260.  
  2261. =item C<internal_error> I<string>
  2262.  
  2263. Immediately abort current processing and emit the error message I<string>.
  2264. If a layout is defined, it is honoured during the generation of the
  2265. error message.
  2266.  
  2267. If you buffer STDOUT (which is the case by default), then all the output
  2268. currently generated will be discarded cleanly.  Otherwise, users might have
  2269. to scroll down to see the error message.
  2270.  
  2271. =item C<log>
  2272.  
  2273. Gives you access to the C<Log::Agent::Logger> logging object.  There is
  2274. always an object, whether or not you enabled logging, if only to redirect
  2275. all the logs to C</dev/null>.  This is the same object used by
  2276. C<CGI::MxScreen> to do its hardwired logging.
  2277.  
  2278. See L<Log::Agent::Logger> to learn what can be done with such objects.
  2279.  
  2280. =item C<play>
  2281.  
  2282. The entry point that dispatches the state machine handling.  Upon return,
  2283. the whole HTML has been generated and sent back to the browser.
  2284.  
  2285. =back
  2286.  
  2287. =head2 Utility Path
  2288.  
  2289. The concept of I<utility path> stems from the need to keep all callback
  2290. specification serializable.  Since C<Storable> cannot handle CODE references,
  2291. C<CGI::MxScreen> uses function names.  In some cases, we have a default
  2292. object to call the method on (e.g. during action callbacks), or one can
  2293. specify an object.  In some other case, a plain name must be used, and you
  2294. must tell C<CGI::MxScreen> in which packages it should look to find that name.
  2295.  
  2296. This is analogous to the PATH search done by the shell.  Unless you specify
  2297. an absolute path, the shell looks throughout your defined PATH directories,
  2298. stopping at the first match.
  2299.  
  2300. Here, we're looking through package namespaces.  For instance, given the
  2301. name "is_num", we could check C<main::is_num>, then C<Your::Module::is_num>,
  2302. etc...  That's what the utility path is.
  2303.  
  2304. The routine C<CGI::MxScreen::add_utils_path> must be used I<before> the
  2305. creation of the C<CGI::MxScreen> manager, and takes a list of strings,
  2306. which define the package namespaces to look through for field validation
  2307. callbacks and patching routines.  The reason it must be done I<before>
  2308. is that incoming CGI parameters are currently processed during the
  2309. manager's creation routine.
  2310.  
  2311. =head1 LOGGING
  2312.  
  2313. During its operation, C<CGI::MxScreen> can emit application logs.  The
  2314. amount emitted depends on the configuration, as described in
  2315. L<CGI::MxScreen::Config>.
  2316.  
  2317. Logs are emitted with the session number prefixed, for instance:
  2318.  
  2319.     (192.168.0.3-29592) t=0.13s usr=0.12s sys=0.01s [screen computation]
  2320.  
  2321. The logged session number is the IP address of the remote machine, and the
  2322. PID of the script when the session started.  It remains constant throughout
  2323. all the session.
  2324.  
  2325. There is also some timestamping and process pre-fixing done by the
  2326. underlying logging channel.  See L<Log::Agent::Stamping> for details.
  2327. The so-called "own" date stamping format is used by C<CGI::MxScreen>,
  2328. and it looks like this:
  2329.  
  2330.     01/04/18 12:08:22 script:
  2331.  
  2332. showing the date in yy/mm/dd format, and the time in HH::MM::SS format.
  2333. The C<script:> part is the process name, here the name of your CGI script.
  2334.  
  2335. At the "debug" logging level, you'll get this whole list of logs for
  2336. every intial script invocation:
  2337.  
  2338.     [main/0] t=0s u="ram" q="id=4"
  2339.     using "Mozilla/4.75 [en] (X11; U; Linux 2.4.3-ac4 i686)"
  2340.     t=0.20s usr=0.17s sys=0.01s [context restore + log init]
  2341.     t=1.15s usr=0.86s sys=0.05s [parameter init]
  2342.     t=1.71s usr=0.61s sys=0.07s [outside CGI::MxScreen]
  2343.     main()
  2344.     t=0.13s usr=0.12s sys=0.01s [screen computation]
  2345.     t=46.46s usr=43.42s sys=1.67s ["main" display]
  2346.     t=0.30s usr=0.29s sys=0.02s [context save]
  2347.     t=50.01s usr=45.53s sys=1.83s [total time] T=52.45s
  2348.  
  2349. The C<t=0s> indicates the start of a new session, and C<u="ram"> signals
  2350. that the request is made for an HTTP-authenticated user named I<ram>.
  2351. The C<[main/0]> indicates that we're in the state called I<main>, and C<0>
  2352. is the interaction counter (incremented at each roundtrip).
  2353. The C<q="id=4"> traces the query string.
  2354.  
  2355. The next line traces the user agent, and is only emitted at the start of
  2356. a new session.  May be useful if something goes wrong later on, so that
  2357. you can suspect the user's browser.
  2358.  
  2359. Then follows a bunch of timing lines, each indicating what was timed
  2360. in trailing square brackets.  The final total summs up all the other lines,
  2361. and also provides a precious C<T=52.45s> priece of statistics, measuring the
  2362. total wallclock time since the script startup.  This helps you evaluate
  2363. the overhead of loading the various modules.
  2364.  
  2365. The single C<main()> line traces the state information.  Here, since this
  2366. is the start of a new session, we enter the initial state and there's no
  2367. state transition.
  2368.  
  2369. Note the very large time spent by the C<display()> routine for that
  2370. screen.  This is because C<Carp::Datum> was on, and there was a lot of
  2371. activity to trace.
  2372.  
  2373. Compare this to the following log, where the user pressed a button
  2374. called I<refresh>, which simply re-displays the same screen, and where
  2375. C<Carp::Datum> was turned off:
  2376.  
  2377.     [main/1] t=1m11s d=19s u="ram"
  2378.     t=0.90s usr=0.83s sys=0.08s [context restore + log init]
  2379.     t=0.01s usr=0.00s sys=0.00s [parameter init]
  2380.     t=0.02s usr=0.02s sys=0.00s [outside CGI::MxScreen]
  2381.     main() -> main() on "refresh" pressed
  2382.     t=0.02s usr=0.01s sys=0.00s [screen computation]
  2383.     t=0.56s usr=0.58s sys=0.00s ["main" display]
  2384.     t=0.05s usr=0.05s sys=0.00s [context save]
  2385.     t=1.56s usr=1.50s sys=0.08s [total time] T=3.24s
  2386.  
  2387. The new C<d=19s> item on the first line indicates the elapsed time since
  2388. the end of the first invocation of the script, and this new one.  It is
  2389. the time the user contemplated the screen before pressing a button.
  2390.  
  2391. Note that there is no C<q="id=4"> shown: C<CGI::MxScreen> uses POST requests
  2392. between its invocations, and does not propagate the initial query string.
  2393. It is up to you to save any relevant information into the context.
  2394.  
  2395. The following table indicates the logging level used to emit each of the
  2396. logging lines outlined above:
  2397.  
  2398.    Level    Logging Line Exerpt
  2399.    -------  --------------------------------
  2400.    warning  [main/1] ...
  2401.    info     using "Mozilla/4.75...
  2402.    debug    ... [context restore + log init]
  2403.    debug    ... [parameter init]
  2404.    debug    ... [outside CGI::MxScreen]
  2405.    notice   main() -> main() on "refresh"...
  2406.    debug    ... [screen computation]
  2407.    debug    ... ["main" display]
  2408.    debug    ... [context save]
  2409.    info     ... [total time] T=3.24s
  2410.  
  2411. All timing logs but the last one summarizing the total time are made at
  2412. the I<debug> level.  All state transitions (button press, or even bounce
  2413. exceptions) are logged at the I<notice> level.  Invocations are logged
  2414. at the I<warning> level, in order to trace them more systematically.
  2415.  
  2416. =head1 BUGS
  2417.  
  2418. There are still some rough edges.  Time will certainly help polishing them.
  2419.  
  2420. If you find any bug, please contact both authors with the same message.
  2421.  
  2422. =head1 HISTORY AND CREDITS
  2423.  
  2424. C<CGI::MxScreen> began when Raphael Manfredi, who knew next to nothing about
  2425. CGI programming, stumbled on the wonderful C<MxScreen> program, by
  2426. Tom Christiansen, circa 1998.  It was a graphical query compiler for his
  2427. I<Magic: The Gathering> database. I confess I learned eveything there was to
  2428. learn about by studying this program.  I owed so much to that C<MxScreen>
  2429. script that I decided to keep the name in the module.
  2430.  
  2431. However, C<MxScreen> was a single application, very well written, but not
  2432. reusable without doing massive cut-and-paste, and rather monolithic.
  2433. The first C<CGI::MxScreen> version was written by Raphael Manfredi to
  2434. modularize the various concepts in late 1998 and early 1999.  It was
  2435. never published, and was too procedural.
  2436.  
  2437. In late 1999, I introduced my C<CGI::MxScreen> to Christophe Dehaudt.
  2438. After studying it for a while, he bought the overall concept, but
  2439. proposed to drop the procedural approach and switch to a pure object-oriented
  2440. design, to make the framework easier to work with.  I agreed.
  2441.  
  2442.  
  2443. The current version of C<CGI::MxScreen> is the result of a joint work
  2444. between us.  Christophe did the initial experimenting with the new ideas,
  2445. and Raphael consolidated the work, then wrote the whole documentation
  2446. and regression test suite.  We discussed the various implementation
  2447. decisions together, and although the result is necessarily a compromise,
  2448. I (Raphael) believe it is a good compromise.
  2449.  
  2450. We managed to use C<CGI::MxScreen> in the industrial development of a
  2451. web-based project time tracking system.  The source was well over 20000
  2452. lines of pure Perl code (comments and blank lines stripped), and we reused
  2453. more than 50000 lines of CPAN code.  I don't think we would have succeeded
  2454. without C<CGI::MxScreen>, and without CPAN.
  2455.  
  2456. The public release of C<CGI::MxScreen> was delayed more than a year because
  2457. the dependencies of the module needed to be released first, and also
  2458. we were lacking C<CGI::Test> which was developped only recently.  Without
  2459. it, writing the regression test suite of C<CGI::MxScreen> would have been
  2460. a real pain, due to its context-sensitive nature.  See L<CGI::Test> if
  2461. you're curious.
  2462.  
  2463. =head1 AUTHORS
  2464.  
  2465. The original authors are
  2466. Raphael Manfredi F<E<lt>Raphael_Manfredi@pobox.comE<gt>>
  2467. and
  2468. Christophe Dehaudt F<E<lt>Christophe.Dehaudt@teamlog.frE<gt>>.
  2469.  
  2470. Send bug reports, suggestions, problems or questions to
  2471. Jason Purdy F<E<lt>Jason@Purdy.INFOE<gt>>
  2472.  
  2473. =head1 SEE ALSO
  2474.  
  2475. CGI::MxScreen::Config(3), CGI::MxScreen::Screen(3), CGI::MxScreen::Layout(3).
  2476.  
  2477. =cut
  2478.  
  2479.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement