# ================================================================== # Gossamer Threads Module Library - http://gossamer-threads.com/ # # GT::CGI::EventLoop # Author : Scott Beck # CVS Info : # $Id: EventLoop.pm,v 1.5 2004/09/07 23:35:14 jagerman Exp $ # # Copyright (c) 2004 Gossamer Threads Inc. All Rights Reserved. # ================================================================== # # Description: Impliments an EventLoop API for CGI programming # package GT::CGI::EventLoop; # ================================================================== use vars qw/$ATTRIBS $ERRORS @EXPORT_OK %EXPORT_TAGS/; use strict; use bases 'GT::Base' => ''; # GT::Base inherits from Exporter use constants STOP => 1, EXIT => 2, CONT => 3, HEAP => 0, EVENT => 1, IN => 2, CGI => 3, ARG0 => 4, ARG1 => 5, ARG2 => 6, ARG3 => 7, ARG4 => 8, ARG5 => 9, ARG6 => 10, ARG7 => 11, ARG8 => 12, ARG9 => 13; use GT::CGI; use GT::MIMETypes; $ERRORS = { NOACTION => 'No action was passed from CGI input and no default action was set', NOFUNC => 'No function in %s' }; $ATTRIBS = { do => 'do', format_page_tags => undef, default_do => undef, init_events => undef, init_events_name => undef, default_page => 'home', default_group => undef, default_page_pre_event => undef, default_page_post_event => undef, default_group_pre_event => undef, default_group_post_event => undef, needs_array_input => undef, plugin_object => undef, template_path => undef, pre_package => '', cgi => undef, in => {}, heap => {}, page_events => {}, page_pre_events => {}, page_post_events => {}, group_pre_events => {}, group_post_events => {}, groups => {}, group => undef, page => undef, print_page => \>::CGI::EventLoop::print_page, status => CONT, cookies => [] }; @EXPORT_OK = qw/ STOP EXIT CONT HEAP EVENT IN CGI ARG0 ARG1 ARG2 ARG3 ARG4 ARG5 ARG6 ARG7 ARG8 ARG9 /; %EXPORT_TAGS = ( all => [@EXPORT_OK], status => [qw/STOP EXIT CONT/], args => [qw/ HEAP EVENT IN CGI ARG0 ARG1 ARG2 ARG3 ARG4 ARG5 ARG6 ARG7 ARG8 ARG9 /] ); sub init { # -------------------------------------------------------------------- my $self = shift; $self->set( @_ ) if @_; $self->{cgi} ||= new GT::CGI; for ( $self->{cgi}->param ) { my @val = $self->{cgi}->param($_); my $val; my $match; for my $field ( @{$self->{needs_array_input}} ) { if ( $_ eq $field ) { $match = 1; last; } } if ( !$match ) { $val = $val[0]; } else { $val = \@val; } $self->{in}{$_} = $val; } } sub mainloop { # -------------------------------------------------------------------- my $self = shift; $self->init( @_ ) if @_; if ( !defined $self->{in}{$self->{do}} ) { if ( defined $self->{default_do} ) { $self->{in}{$self->{do}} = $self->{default_do}; } else { $self->fatal( 'NOACTION' ); } } if ( $self->{init_events} ) { local $self->{in}{$self->{do}} = $self->{init_events_name} if $self->{init_events_name}; $self->dispatch( $self->{init_events} ); return if $self->{status} == EXIT; } $self->_call_group; $self->_call_page; } sub do_param { # -------------------------------------------------------------------- my $self = shift; if ( @_ ) { $self->add_hidden( $self->{do} => $_[0] ); } return $self->{in}{$self->{do}}; } sub stop { $_[0]->{status} = STOP } sub exit { $_[0]->{status} = EXIT } sub cont { $_[0]->{status} = CONT } sub _call_group { # -------------------------------------------------------------------- my ( $self ) = @_; $self->{group} ||= $self->{in}{$self->{do}} || $self->{default_do}; my $orig_group = $self->{group}; # FIXME Add infinite recursion checks! for ( keys %{$self->{groups}} ) { if ( index( $self->{group}, $_ ) == 0 ) { if ( exists $self->{group_pre_events}{$_} ) { $self->dispatch( $self->{group_pre_events}{$_} ); return if $self->{status} == EXIT; if ( $self->{group} ne $orig_group ) { return $self->_call_group; } } elsif ( defined $self->{default_group_pre_event} ) { $self->dispatch( $self->{default_group_pre_event} ); return if $self->{status} == EXIT; if ( $self->{group} ne $orig_group ) { return $self->_call_group; } } $self->dispatch( $self->{groups}{$_} ); if ( $self->{group} ne $orig_group ) { return $self->_call_group; } if ( exists $self->{group_post_events}{$_} ) { $self->dispatch( $self->{group_post_events}{$_} ); return if $self->{status} == EXIT; if ( $self->{group} ne $orig_group ) { return $self->_call_group; } } elsif ( defined $self->{default_group_post_event} ) { $self->dispatch( $self->{default_group_post_event} ); return if $self->{status} == EXIT; if ( $self->{group} ne $orig_group ) { return $self->_call_group; } } return; } } # Default group $self->dispatch( $self->{default_group} ) if $self->{default_group}; if ( $self->{default_group} and $self->{group} ne $orig_group ) { return $self->_call_group; } } sub _call_page { # -------------------------------------------------------------------- my ( $self ) = @_; if ( !$self->{page} ) { $self->page( $self->{default_page} ); } my $orig_page = $self->{page}; if ( exists $self->{page_pre_events}{$self->{page}} ) { $self->dispatch( $self->{page_pre_events}{$self->{page}} ); return if $self->{status} == EXIT; if ( $self->{page} ne $orig_page ) { return $self->_call_page; } } elsif ( defined $self->{default_page_pre_event} ) { $self->dispatch( $self->{default_page_pre_event} ); return if $self->{status} == EXIT; if ( $self->{page} ne $orig_page ) { return $self->_call_page; } } $self->{print_page}->( $self ); # Run post page events, can't change the page on a post event if ( exists $self->{page_post_events}{$self->{page}} ) { $self->dispatch( $self->{page_post_events}{$self->{page}} ); } elsif ( defined $self->{default_page_post_event} ) { $self->dispatch( $self->{default_page_post_event} ); } } sub cookie_jar { # -------------------------------------------------------------------- # $obj->cookie_jar($cookie_object); # --------------------------------- # Stores cookies for printing when print_page is called. # $cookie_object should be a GT::CGI::Cookie object. Passing undef # will empty the cookies array ref. # my $self = shift; if ( !defined( $_[0] ) and @_ > 0 ) { $self->{cookies} = []; } elsif ( @_ > 0 ) { push( @{$self->{cookies}}, $_[0] ); } return $self->{cookies}; } sub add_hidden { # -------------------------------------------------------------------- my $self = shift; if ( @_ and !defined( $_[0] ) ) { $self->{hidden} = {}; } elsif ( @_ ) { $self->{hidden}{$_[0]} = $_[1]; } } sub remove_hidden { # -------------------------------------------------------------------- my $self = shift; return delete $self->{hidden}{$_[0]}; } sub get_url_hidden { # -------------------------------------------------------------------- my ( $self ) = @_; my $ret = ''; for ( keys %{$self->{hidden}} ) { next unless defined $self->{hidden}{$_}; $ret .= $self->{cgi}->escape( $_ ).'='.$self->{cgi}->escape( $self->{hidden}{$_} ).';'; } return $ret; } sub get_form_hidden { # -------------------------------------------------------------------- my ( $self ) = @_; my $ret = ''; for ( keys %{$self->{hidden}} ) { next unless defined $self->{hidden}{$_}; $ret .= ''; } return $ret; } sub page { # -------------------------------------------------------------------- my $self = shift; if ( @_ > 0 ) { $self->{page} = $self->guess_page( $_[0] ); $self->debug( "Set page to $self->{page}" ) if $self->{_debug}; $self->yield( $self->{page_events} ) if $self->{page_events}; } return $self->{page}; } sub guess_page { # -------------------------------------------------------------------- my ( $self, $page ) = @_; if ( -e "$self->{template_path}/$page.htm" ) { $page = "$page.htm"; } elsif ( -e "$self->{template_path}/$page.html" ) { $page = "$page.html"; } return $page; } sub tags { # -------------------------------------------------------------------- my $self = shift; my ( %tags ) = ref( $_[0] ) eq 'HASH' ? %{$_[0]} : @_; for ( keys %tags ) { $self->{tags}{$_} = $tags{$_}; } return $self->{tags}; } sub default_tags { # -------------------------------------------------------------------- my ( $self, %tags ) = @_; my $set; for ( keys %tags ) { $set->{$_} = ( defined( $self->{in}{$_} ) and length( $self->{in}{$_} ) ? $self->{in}{$_} : $tags{$_} ); } $self->tags( %$set ); } sub print_page { # -------------------------------------------------------------------- my ( $self ) = @_; my $form_hidden = $self->get_form_hidden; my $url_hidden = $self->get_url_hidden; my $tags = $self->tags( url_hidden => \$url_hidden, form_hidden => \$form_hidden ); $tags = $self->yield( $self->{format_page_tags}, $tags ) if defined $self->{format_page_tags}; my $page = $self->page || 'index.htm'; # Cookies can be set with CGI input my $cookies = []; if ( $self->{in}{'set-cookie'} ) { foreach my $key ( keys %{$self->{in}} ) { if ( $key =~ /^cookie-(.*)/ ) { push @$cookies, $self->{cgi}->cookie( -name => $1, -value => $self->{in}{$key}, -path => '/' ); } } } # See if we have any cookies in out cookie jar (used through program operation to set cookies without printing # a header) if ( @{$self->cookie_jar} ) { push @$cookies, @{$self->cookie_jar}; } # If we have cookie header to print print them print @{$cookies} ? $self->{cgi}->header( -cookie => $cookies, -type => GT::MIMETypes->guess_type( $page ) ) : $self->{cgi}->header( GT::MIMETypes->guess_type( $page ) ); my $base = $self->{template_path}; # Make sure the template exists and is readable -e "$base/$page" or die "No page ($base/$page)"; -r _ or die "Page isn't readable by this process ($< $>) ($base/$page)"; require GT::Template; GT::Template->parse( $page, $tags, { root => $base, escape => 1, print => 1, heap => [ $self->func_args ] } ); } sub page_pre_events { # -------------------------------------------------------------------- my ( $self, %in ) = @_; if ( keys %in ) { $self->{page_pre_events} = {}; for ( keys %in ) { my $val = ref( $in{$_} ) eq 'ARRAY' ? $in{$_} : [ $in{$_} ]; $self->{page_pre_events}{$self->guess_page( $_ )} = $val; } } return $self->{page_pre_events}; } sub page_post_events { # -------------------------------------------------------------------- my ( $self, %in ) = @_; if ( keys %in ) { $self->{page_post_events} = {}; for ( keys %in ) { my $val = ref( $in{$_} ) eq 'ARRAY' ? $in{$_} : [ $in{$_} ]; $self->{page_post_events}{$self->guess_page( $_ )} = $val; } } return $self->{page_post_events}; } sub group_pre_events { # -------------------------------------------------------------------- my ( $self, %in ) = @_; if ( keys %in ) { $self->{group_pre_events} = {}; for ( keys %in ) { my $val = ref( $in{$_} ) eq 'ARRAY' ? $in{$_} : [ $in{$_} ]; $self->{group_pre_events}{$_} = $val; } } return $self->{group_pre_events}; } sub group_post_events { # -------------------------------------------------------------------- my ( $self, %in ) = @_; if ( keys %in ) { $self->{group_post_events} = {}; for ( keys %in ) { my $val = ref( $in{$_} ) eq 'ARRAY' ? $in{$_} : [ $in{$_} ]; $self->{group_post_events}{$_} = $val; } } return $self->{group_post_events}; } sub dispatch { # -------------------------------------------------------------------- my ( $self, $pfunc, @args ) = @_; $pfunc = ref( $pfunc ) eq 'ARRAY' ? $pfunc : [ $pfunc ]; for ( @$pfunc ) { $self->yield( $_, @args ); return if $self->{status} == EXIT or $self->{status} == STOP; } } sub yield { # -------------------------------------------------------------------- my ( $self, $pfunc, @args ) = @_; if ( !ref( $pfunc ) ) { $self->debug( "Yielding $pfunc" ) if $self->{_debug} > 1; my ( $pkg, $func ); if ( index( $pfunc, '::' ) != -1 ) { ($pkg, $func) = $pfunc =~ /^(.*)::(.*)$/; } else { $func = $pfunc; } defined( $func ) or $self->fatal( 'NOFUNC', $pfunc ); $pkg = $self->{pre_package}.$pkg if $self->{pre_package} and $pkg; $pkg ||= $self->{pre_package} if $self->{pre_package}; $pkg ||= 'main'; $pkg =~ s/::$//; no strict 'refs'; unless ( defined %{$pkg . '::'} ) { eval "require $pkg"; die "Could not compile $pkg; Reason: $@" if $@; } if ( defined $self->{plugin_object} ) { $self->debug( "dispatching --> $pkg\::$func" ) if $self->{_debug}; return $self->{plugin_object}->dispatch( $pkg.'::'.$func, \&{$pkg.'::'.$func}, $self->func_args(@args) ); } else { no strict 'refs'; $self->debug( "Calling $pkg\::$func" ) if $self->{_debug}; return &{$pkg.'::'.$func}( $self->func_args(@args) ); } $self->yield( $_, @args ); } elsif ( ref( $pfunc ) eq 'CODE' ) { $self->debug( "In yeild with code ref.") if $self->{_debug}; if ( defined $self->{plugin_object} ) { $self->debug( "dispatching --> $self->{in}{$self->{do}}" ) if $self->{_debug}; return $self->{plugin_object}->dispatch( $self->{in}{$self->{do}}, $pfunc, $self->func_args(@args) ); } else { $self->debug( "Calling code ref" ) if $self->{_debug}; return $pfunc->( $self->func_args(@args) ); } } } sub func_args { $_[0]->{heap}, $_[0], $_[0]->{in}, $_[0]->{cgi}, @_[1 .. $#_] } 1;