X-Git-Url: https://git.decadent.org.uk/gitweb/?a=blobdiff_plain;f=lib%2FMaypole.pm;h=6c6ff52c0fc8ef0cad989205b9925382ccb03014;hb=ea4ae8a93a09e21354465c485471e5f10582b784;hp=3077bfddc1e4466471667a1222f3f6a35160f406;hpb=d73958197b312ceaea79b355ae2223b59b8fe248;p=maypole.git diff --git a/lib/Maypole.pm b/lib/Maypole.pm index 3077bfd..6c6ff52 100644 --- a/lib/Maypole.pm +++ b/lib/Maypole.pm @@ -6,39 +6,225 @@ use warnings; use Maypole::Config; use Maypole::Constants; use Maypole::Headers; +use URI(); -our $VERSION = '2.10'; +our $VERSION = '2.11'; # proposed privacy conventions: # - no leading underscore - public to custom application code and plugins -# - single leading underscore - private to the main Maypole stack - *not* including plugins +# - single leading underscore - private to the main Maypole stack - *not* +# including plugins # - double leading underscore - private to the current package +=head1 NAME + +Maypole - MVC web application framework + +=head1 SYNOPSIS + +See L. + +=head1 DESCRIPTION + +This documents the Maypole request object. See the L, for a +detailed guide to using Maypole. + +Maypole is a Perl web application framework similar to Java's struts. It is +essentially completely abstracted, and so doesn't know anything about +how to talk to the outside world. + +To use it, you need to create a driver package which represents your entire +application. This is the C package used as an example in the manual. + +This needs to first use L which will make your package +inherit from the appropriate platform driver such as C or +C. Then, the driver calls C. This sets up the model classes and +configures your application. The default model class for Maypole uses +L to map a database to classes, but this can be changed by altering +configuration (B calling setup.) + + +=head1 DOCUMENTATION AND SUPPORT + +Note that some details in some of these resources may be out of date. + +=over 4 + +=item The Maypole Manual + +The primary documentation is the Maypole manual. This lives in the +C pod documents included with the distribution. + +=item Embedded POD + +Individual packages within the distribution contain (more or less) detailed +reference documentation for their API. + +=item Mailing lists + +There are two mailing lists - maypole-devel and maypole-users - see +http://maypole.perl.org/?MailingList + +=item The Maypole Wiki + +The Maypole wiki provides a useful store of extra documentation - +http://maypole.perl.org + +In particular, there's a FAQ (http://maypole.perl.org/?FAQ) and a cookbook +(http://maypole.perl.org/?Cookbook). Again, certain information on these pages +may be out of date. + +=item Web applications with Maypole + +A tutorial written by Simon Cozens for YAPC::EU 2005 - +http://www.droogs.org/perl/maypole/maypole-tutorial.pdf [228KB]. + +=item A Database-Driven Web Application in 18 Lines of Code + +By Paul Barry, published in Linux Journal, March 2005. + +http://www.linuxjournal.com/article/7937 + +"From zero to Web-based database application in eight easy steps". + +Maypole won a 2005 Linux Journal Editor's Choice Award +(http://www.linuxjournal.com/article/8293) after featuring in this article. + +=item Build Web apps with Maypole + +By Simon Cozens, on IBM's DeveloperWorks website, May 2004. + +http://www-128.ibm.com/developerworks/linux/library/l-maypole/ + +=item Rapid Web Application Deployment with Maypole + +By Simon Cozens, on O'Reilly's Perl website, April 2004. + +http://www.perl.com/pub/a/2004/04/15/maypole.html + +=item Authentication + +Some notes written by Simon Cozens. A little bit out of date, but still +very useful: http://www.droogs.org/perl/maypole/authentication.html + +=item CheatSheet + +There's a refcard for the Maypole (and Class::DBI) APIs on the wiki - +http://maypole.perl.org/?CheatSheet. Probably a little out of date now - it's a +wiki, so feel free to fix any errors! + +=item Plugins and add-ons + +There are a large and growing number of plugins and other add-on modules +available on CPAN - http://search.cpan.org/search?query=maypole&mode=module + +=item del.icio.us + +You can find a range of useful Maypole links, particularly to several thoughtful +blog entries, starting here: http://del.icio.us/search/?all=maypole + +=item CPAN ratings + +There are a couple of short reviews here: +http://cpanratings.perl.org/dist/Maypole + +=back + +=head1 DEMOS + +A couple of demos are available, sometimes with source code and configs. + +=over 4 + +=item http://maypole.perl.org/beerdb/ + +The standard BeerDB example, using the TT factory templates supplied in the +distribution. + +=item beerdb.riverside-cms.co.uk + +The standard BeerDB example, running on Mason, using the factory templates +supplied in the L distribution. + +=item beerfb.riverside-cms.co.uk + +A demo of L. This site is running on the set of Mason +templates included in the L distribution. See the +synopsis of L for an example driver + +=back + +=cut + __PACKAGE__->mk_classdata($_) for qw( config init_done view_object ); + __PACKAGE__->mk_accessors( qw( params query objects model_class template_args output path args action template error document_encoding content_type table - headers_in headers_out ) + headers_in headers_out stash session) ); + __PACKAGE__->config( Maypole::Config->new() ); + __PACKAGE__->init_done(0); -sub debug { 0 } +=head1 CLASS METHODS + +=over 4 + +=item config + +Returns the L object + +=item setup + + My::App->setup($data_source, $user, $password, \%attr); + +Initialise the Maypole application and plugins and model classes - see +L. + +If your model is based on L, the C<\%attr> hashref can +contain options that are passed directly to L, to control +how the model hierarchy is constructed. + +Your application should call this B setting up configuration data via +L<"config">. + +=cut + +sub setup +{ + my $class = shift; + + $class->setup_model(@_); +} + +=item setup_model -sub setup +Called by C. This method builds the Maypole model hierarchy. + +A likely target for over-riding, if you need to build a customised model. + +This method also ensures any code in custom model classes is loaded, so you +don't need to load them in the driver. + +=cut + +sub setup_model { - my $calling_class = shift; + my $class = shift; - $calling_class = ref $calling_class if ref $calling_class; + $class = ref $class if ref $class; - my $config = $calling_class->config; + my $config = $class->config; $config->model || $config->model('Maypole::Model::CDBI'); $config->model->require or die sprintf "Couldn't load the model class %s: %s", $config->model, $@; - $config->model->setup_database($config, $calling_class, @_); + # among other things, this populates $config->classes + $config->model->setup_database($config, $class, @_); foreach my $subclass ( @{ $config->classes } ) { @@ -46,9 +232,24 @@ sub setup unshift @{ $subclass . "::ISA" }, $config->model; $config->model->adopt($subclass) if $config->model->can("adopt"); + + # Load custom model code, if it exists - nb this must happen after the + # unshift, to allow code attributes to work + eval "use $subclass"; + die "Error loading $subclass: $@" + if $@ and $@ !~ /Can\'t locate \S+ in \@INC/; } } +=item init + +Loads the view class and instantiates the view object. + +You should not call this directly, but you may wish to override this to add +application-specific initialisation - see L. + +=cut + sub init { my $class = shift; @@ -60,13 +261,73 @@ sub init || $config->display_tables( $class->config->tables ); $class->view_object( $class->config->view->new ); $class->init_done(1); +} +=item new + +Constructs a very minimal new Maypole request object. + +=cut + +sub new +{ + my ($class) = @_; + + my $self = bless { + template_args => {}, + config => $class->config, + }, $class; + + return $self; } +=item view_object + +Get/set the Maypole::View object + +=item debug + + sub My::App::debug {1} + +Returns the debugging flag. Override this in your application class to +enable/disable debugging. + +You can also set the C flag via L. + +=cut + +sub debug { 0 } + +=item get_template_root + +Implementation-specific path to template root. + +You should only need to define this method if you are writing a new Maypole +backend. Otherwise, see L + +=cut + +sub get_template_root {'.'} + +=back + +=head1 INSTANCE METHODS + +=head2 Workflow + +=over 4 + +=item handler + +This method sets up the class if it's not done yet, sets some defaults and +leaves the dirty work to C. + +=cut + # handler() has a method attribute so that mod_perl will invoke # BeerDB->handler() as a method rather than a plain function # BeerDB::handler() and so this inherited implementation will be -# found. See e.g. "Practical mod_perl" by Bekman & Cholet for +# found. See e.g. "Practical mod_perl" by Bekman & Cholet for # more information sub handler : method { @@ -75,90 +336,108 @@ sub handler : method $class->init unless $class->init_done; - # Create the request object - my $r = bless { - template_args => {}, - config => $class->config - }, $class; + my $self = $class->new; - $r->headers_out(Maypole::Headers->new); + # initialise the request + $self->headers_out(Maypole::Headers->new); + $self->get_request($req); + $self->parse_location; - $r->get_request($req); + # hook useful for declining static requests e.g. images + my $status = $self->start_request_hook; + return $status unless $status == Maypole::Constants::OK(); - $r->parse_location; + $self->session($self->get_session); - my $status = $r->handler_guts; + $status = $self->handler_guts; # moving this here causes unit test failures - need to check why # before committing the move - #$status = $r->__call_process_view unless $r->output; + #$status = $self->__call_process_view unless $self->output; return $status unless $status == OK; - $r->send_output; + # TODO: require send_output to return a status code + $self->send_output; return $status; } +=item handler_guts + +This is the main request handling method and calls various methods to handle the +request/response and defines the workflow within Maypole. + +B. + +=cut + # The root of all evil sub handler_guts { - my ($r) = @_; + my ($self) = @_; - $r->__load_model; + $self->__load_model; - my $applicable = __to_boolean( $r->is_applicable ); + my $applicable = $self->is_model_applicable; - $r->__setup_plain_template unless $applicable; - - # We authenticate every request, needed for proper session management + $self->__setup_plain_template unless $applicable; + my $status; - - eval { $status = $r->call_authenticate }; + + eval { $status = $self->call_authenticate }; if ( my $error = $@ ) { - $status = $r->call_exception($error); + $status = $self->call_exception($error); if ( $status != OK ) { warn "caught authenticate error: $error"; - return $r->debug ? $r->view_object->error($r, $error) : ERROR; + return $self->debug ? + $self->view_object->error($self, $error) : ERROR; } } - if ( $r->debug and $status != OK and $status != DECLINED ) + if ( $self->debug and $status != OK and $status != DECLINED ) { - $r->view_object->error( $r, + $self->view_object->error( $self, "Got unexpected status $status from calling authentication" ); } return $status unless $status == OK; # We run additional_data for every request - $r->additional_data; + $self->additional_data; if ($applicable) { - eval { $r->model_class->process($r) }; + eval { $self->model_class->process($self) }; if ( my $error = $@ ) { - $status = $r->call_exception($error); + $status = $self->call_exception($error); if ( $status != OK ) { warn "caught model error: $error"; - return $r->debug ? $r->view_object->error($r, $error) : ERROR; + return $self->debug ? + $self->view_object->error($self, $error) : ERROR; } } } - # unusual path - perhaps output has been set to an error message - return OK if $r->output; + # less frequent path - perhaps output has been set to an error message + return OK if $self->output; # normal path - no output has been generated yet - return $r->__call_process_view; + return $self->__call_process_view; +} + +sub __load_model +{ + my ($self) = @_; + $self->model_class( $self->config->model->class_of($self, $self->table) ); } # is_applicable() returned false, so set up a plain template. Model processing @@ -166,425 +445,621 @@ sub handler_guts # access it. sub __setup_plain_template { - my ($r) = @_; + my ($self) = @_; # It's just a plain template - $r->model_class(undef); + $self->model_class(undef); - my $path = $r->path; + my $path = $self->path; $path =~ s{/$}{}; # De-absolutify - $r->path($path); + $self->path($path); - $r->template($r->path); + $self->template($self->path); } # The model has been processed or skipped (if is_applicable returned false), -# any exceptions have been handled, and there's no content in $r->output +# any exceptions have been handled, and there's no content in $self->output sub __call_process_view { - my ($r) = @_; + my ($self) = @_; my $status; - eval { $status = $r->view_object->process($r) }; + eval { $status = $self->view_object->process($self) }; if ( my $error = $@ ) { - $status = $r->call_exception($error); + $status = $self->call_exception($error); if ( $status != OK ) { - warn "caught view error: $error" if $r->debug; - return $r->debug ? $r->view_object->error($r, $error) : ERROR; + warn "caught view error: $error" if $self->debug; + return $self->debug ? + $self->view_object->error($self, $error) : ERROR; } } return $status; } -sub __load_model +=item get_request + +You should only need to define this method if you are writing a new +Maypole backend. It should return something that looks like an Apache +or CGI request object, it defaults to blank. + +=cut + +sub get_request { } + +=item parse_location + +Turns the backend request (e.g. Apache::MVC, Maypole, CGI) into a Maypole +request. It does this by setting the C, and invoking C and +C. + +You should only need to define this method if you are writing a new Maypole +backend. + +=cut + +sub parse_location { - my ($r) = @_; - $r->model_class( $r->config->model->class_of($r, $r->table) ); + die "parse_location is a virtual method. Do not use Maypole directly; " . + "use Apache::MVC or similar"; } -# is_applicable() should return true or false, not OK or DECLINED, because -# the return value is never used as the return value from handler(). There's -# probably a lot of code out there supplying the return codes though, so instead -# of changing is_applicable() to return 0 or 1, the return value is passed through -# __to_boolean. I think it helps handler_guts() if we don't have multiple sets of -# return codes being checked for different things. -sub is_applicable +=item start_request_hook + +This is called immediately after setting up the basic request. The default +method simply returns C. + +Any other return value causes Maypole to abort further processing of the +request. This is useful for filtering out requests for static files, e.g. +images, which should not be processed by Maypole or by the templating engine: + + sub start_request_hook + { + my ($r) = @_; + + return Maypole::Constants::DECLINED if $r->path =~ /\.jpg$/; + return Maypole::Constants::OK; + } + +=cut + +sub start_request_hook { Maypole::Constants::OK } + +=item is_applicable + +B as of version 2.11. If you have overridden it, +please override C instead, and change the return type +from a Maypole:Constant to a true/false value. + +Returns a Maypole::Constant to indicate whether the request is valid. + +=item is_model_applicable + +Returns true or false to indicate whether the request is valid. + +The default implementation checks that C<< $r->table >> is publicly +accessible and that the model class is configured to handle the +C<< $r->action >>. + +=cut + +sub is_model_applicable { - my ($r) = @_; + my ($self) = @_; - my $config = $r->config; + # cater for applications that are using obsolete version + if ($self->can('is_applicable')) + { + warn "DEPRECATION WARNING: rewrite is_applicable to the interface ". + "of Maypole::is_model_applicable\n"; + return $self->is_applicable == OK; + } + + # Establish which tables should be processed by the model + my $config = $self->config; $config->ok_tables || $config->ok_tables( $config->display_tables ); $config->ok_tables( { map { $_ => 1 } @{ $config->ok_tables } } ) - if ref $config->ok_tables eq "ARRAY"; + if ref $config->ok_tables eq "ARRAY"; + + my $ok_tables = $config->ok_tables; - my $table = $r->table; + # Does this request concern a table to be processed by the model? + my $table = $self->table; + + my $ok = 0; - warn "We don't have that table ($table).\n" - . "Available tables are: " - . join( ",", @{ $config->display_tables } ) - if $r->debug - and not $config->ok_tables->{$table} - and $r->action; # I think this is always true - - return DECLINED unless exists $config->ok_tables->{$table}; + if (exists $ok_tables->{$table}) + { + $ok = 1; + } - # Is it public? - return DECLINED unless $r->model_class->is_public( $r->action ); + if (not $ok) + { + warn "We don't have that table ($table).\n" + . "Available tables are: " + . join( ",", keys %$ok_tables ) + if $self->debug and not $ok_tables->{$table}; + + return 0; + } + + # Is the action public? + my $action = $self->action; + return 1 if $self->model_class->is_public($action); + + warn "The action '$action' is not applicable to the table $table" + if $self->debug; - return OK; + return 0; } -# *only* intended for translating the return code from is_applicable() -sub __to_boolean { $_[0] == OK ? 1 : 0 } +=item get_session +The default method is empty. + +=cut + +sub get_session { } + +=item call_authenticate + +This method first checks if the relevant model class +can authenticate the user, or falls back to the default +authenticate method of your Maypole application. +=cut sub call_authenticate { - my ($r) = @_; + my ($self) = @_; - # Check if we have a model class - return $r->model_class->authenticate($r) - if $r->model_class - and $r->model_class->can('authenticate'); + # Check if we have a model class with an authenticate() to delegate to + return $self->model_class->authenticate($self) + if $self->model_class and $self->model_class->can('authenticate'); - # passing $r is unnecessary and redundant, but there's probably - # a lot of code out there now using the 2nd instead of the 1st $r, - # so we may as well leave it - return $r->authenticate($r); + # Interface consistency is a Good Thing - + # the invocant and the argument may one day be different things + # (i.e. controller and request), like they are when authenticate() + # is called on a model class (i.e. model and request) + return $self->authenticate($self); } +=item authenticate + +Returns a Maypole::Constant to indicate whether the user is authenticated for +the Maypole request. + +The default implementation returns C + +=cut + +sub authenticate { return OK } + + +=item call_exception + +This model is called to catch exceptions, first after authenticate, then after +processing the model class, and finally to check for exceptions from the view +class. + +This method first checks if the relevant model class +can handle exceptions the user, or falls back to the default +exception method of your Maypole application. + +=cut + sub call_exception { - my ($r, $error) = @_; + my ($self, $error) = @_; - # Check if we have a model class - if ( $r->model_class && $r->model_class->can('exception') ) + # Check if we have a model class with an exception() to delegate to + if ( $self->model_class && $self->model_class->can('exception') ) { - my $status = $r->model_class->exception( $r, $error ); + my $status = $self->model_class->exception( $self, $error ); return $status if $status == OK; } - return $r->exception($error); + return $self->exception($error); } -sub additional_data { } +=item exception -sub authenticate { return OK } +This method is called if any exceptions are raised during the authentication or +model/view processing. It should accept the exception as a parameter and return +a Maypole::Constant to indicate whether the request should continue to be +processed. + +=cut sub exception { return ERROR } +=item additional_data + +Called before the model processes the request, this method gives you a chance to +do some processing for each request, for example, manipulating C. + +=cut + +sub additional_data { } + +=item send_output + +Sends the output and additional headers to the user. + +=cut + +sub send_output { + die "send_output is a virtual method. Do not use Maypole directly; use Apache::MVC or similar"; +} + + + + +=back + +=head2 Path processing and manipulation + +=over 4 + +=item path + +Returns the request path + +=item parse_path + +Parses the request path and sets the C, C and C +properties. Calls C before parsing path and setting properties. + +=cut + sub parse_path { - my ($r) = @_; - - $r->path || $r->path('frontpage'); + my ($self) = @_; - my @pi = grep {length} split '/', $r->path; + # Previous versions unconditionally set table, action and args to whatever + # was in @pi (or else to defaults, if @pi is empty). + # Adding preprocess_path(), and then setting table, action and args + # conditionally, broke lots of tests, hence this: + $self->$_(undef) for qw/action table args/; - $r->table(shift @pi); + $self->preprocess_path; + + $self->path || $self->path('frontpage'); - $r->action( shift @pi or 'index' ); + my @pi = grep {length} split '/', $self->path; - $r->args(\@pi); + $self->table || $self->table(shift @pi); + $self->action || $self->action( shift @pi or 'index' ); + $self->args || $self->args(\@pi); } -# like CGI::param(), but read only -sub param -{ - my ($r, $key) = @_; +=item preprocess_path + +Sometimes when you don't want to rewrite or over-ride parse_path but +want to rewrite urls or extract data from them before it is parsed. + +This method is called after parse_location has populated the request +information and before parse_path has populated the model and action +information, and is passed the request object. + +You can set action, args or table in this method and parse_path will +then leave those values in place or populate them if not present + +=cut + +sub preprocess_path { }; + +=item make_path( %args or \%args or @args ) + +This is the counterpart to C. It generates a path to use +in links, form actions etc. To implement your own path scheme, just override +this method and C. + + %args = ( table => $table, + action => $action, + additional => $additional, # optional - generally an object ID + ); + + \%args = as above, but a ref - return keys %{$r->params} unless defined $key; + @args = ( $table, $action, $additional ); # $additional is optional + +C can be used as an alternative key to C. + +C<$additional> can be a string, an arrayref, or a hashref. An arrayref is +expanded into extra path elements, whereas a hashref is translated into a query +string. + +=cut + +sub make_path +{ + my $r = shift; - return unless exists $r->params->{$key}; + my %args; - my $val = $r->params->{$key}; + if (@_ == 1 and ref $_[0] and ref $_[0] eq 'HASH') + { + %args = %{$_[0]}; + } + elsif ( @_ > 1 and @_ < 4 ) + { + $args{table} = shift; + $args{action} = shift; + $args{additional} = shift; + } + else + { + %args = @_; + } - return ref $val ? @$val : ($val) if wantarray; - - return ref $val ? $val->[0] : $val; -} + do { die "no $_" unless $args{$_} } for qw( table action ); -sub get_template_root {'.'} -sub get_request { } - -sub parse_location { - die "Do not use Maypole directly; use Apache::MVC or similar"; -} - -sub send_output { - die "Do not use Maypole directly; use Apache::MVC or similar"; + my $additional = $args{additional} || $args{id}; + + my @add = (); + + if ($additional) + { + # if $additional is a href, make_uri() will transform it into a query + @add = (ref $additional eq 'ARRAY') ? @$additional : ($additional); + } + + my $uri = $r->make_uri($args{table}, $args{action}, @add); + + return $uri->as_string; } -# Session and Repeat Submission Handling -sub make_random_id { - use Maypole::Session; - return Maypole::Session::generate_unique_id(); -} -=head1 NAME +=item make_uri( @segments ) -Maypole - MVC web application framework +Make a L object given table, action etc. Automatically adds +the C. -=head1 SYNOPSIS +If the final element in C<@segments> is a hash ref, C will render it +as a query string. -See L. +=cut -=head1 DESCRIPTION +sub make_uri +{ + my ($r, @segments) = @_; -This documents the Maypole request object. See the L, for a -detailed guide to using Maypole. + my $query = (ref $segments[-1] eq 'HASH') ? pop(@segments) : undef; + + my $base = $r->config->uri_base; + $base =~ s|/$||; + + my $uri = URI->new($base); + $uri->path_segments($uri->path_segments, grep {length} @segments); + + my $abs_uri = $uri->abs('/'); + $abs_uri->query_form($query) if $query; + return $abs_uri; +} -Maypole is a Perl web application framework similar to Java's struts. It is -essentially completely abstracted, and so doesn't know anything about -how to talk to the outside world. +=item parse_args -To use it, you need to create a package which represents your entire -application. In our example above, this is the C package. +Turns post data and query string paramaters into a hash of C. -This needs to first use L which will make your package -inherit from the appropriate platform driver such as C or -C, and then call setup. This sets up the model classes and -configures your application. The default model class for Maypole uses -L to map a database to classes, but this can be changed by altering -configuration. (B calling setup.) +You should only need to define this method if you are writing a new Maypole +backend. -=head2 CLASS METHODS +=back -=head3 config +=head2 Request properties -Returns the L object +=over 4 -=head3 setup +=item model_class - My::App->setup($data_source, $user, $password, \%attr); +Returns the perl package name that will serve as the model for the +request. It corresponds to the request C
attribute. -Initialise the maypole application and model classes. Your application should -call this after setting configuration via L<"config"> -=head3 init +=item objects -You should not call this directly, but you may wish to override this to -add -application-specific initialisation. +Get/set a list of model objects. The objects will be accessible in the view +templates. -=head3 view_object +If the first item in C<$self-Eargs> can be Cd by the model +class, it will be removed from C and the retrieved object will be added to +the C list. See L for more information. -Get/set the Maypole::View object +=item template_args -=head3 debug + $self->template_args->{foo} = 'bar'; - sub My::App::debug {1} +Get/set a hash of template variables. -Returns the debugging flag. Override this in your application class to -enable/disable debugging. +=item stash -=head2 INSTANCE METHODS +A place to put custom application data. Not used by Maypole itself. -=head3 parse_location +=item template -Turns the backend request (e.g. Apache::MVC, Maypole, CGI) into a -Maypole -request. It does this by setting the C, and invoking C -and -C. +Get/set the template to be used by the view. By default, it returns +C<$self-Eaction> -You should only need to define this method if you are writing a new -Maypole -backend. -=head3 path +=item error -Returns the request path +Get/set a request error -=head3 parse_path +=item output -Parses the request path and sets the C, C and C
-properties +Get/set the response output. This is usually populated by the view class. You +can skip view processing by setting the C. -=head3 table +=item table The table part of the Maypole request path -=head3 action +=item action The action part of the Maypole request path -=head3 args +=item args A list of remaining parts of the request path after table and action have been removed -=head3 headers_in +=item headers_in A L object containing HTTP headers for the request -=head3 headers_out +=item headers_out A L object that contains HTTP headers for the output -=head3 parse_args - -Turns post data and query string paramaters into a hash of C. - -You should only need to define this method if you are writing a new -Maypole -backend. - -=head3 param - -An accessor for request parameters. It behaves similarly to CGI::param() for -accessing CGI parameters. - -=head3 params - -Returns a hash of request parameters. The source of the parameters may vary -depending on the Maypole backend, but they are usually populated from request -query string and POST data. +=item document_encoding -B Where muliple values of a parameter were supplied, the -C -value -will be an array reference. +Get/set the output encoding. Default: utf-8. -=head3 get_template_root +=item content_type -Implementation-specific path to template root. +Get/set the output content type. Default: text/html -You should only need to define this method if you are writing a new -Maypole -backend. Otherwise, see L +=item get_protocol -=head3 get_request +Returns the protocol the request was made with, i.e. https -You should only need to define this method if you are writing a new -Maypole backend. It should return something that looks like an Apache -or CGI request object, it defaults to blank. +=cut +sub get_protocol { + die "get_protocol is a virtual method. Do not use Maypole directly; use Apache::MVC or similar"; +} -=head3 is_applicable +=back -Returns a Maypole::Constant to indicate whether the request is valid. +=head2 Request parameters -The default implementation checks that C<$r-Etable> is publicly -accessible -and that the model class is configured to handle the C<$r-Eaction> +The source of the parameters may vary depending on the Maypole backend, but they +are usually populated from request query string and POST data. -=head3 authenticate +Maypole supplies several approaches for accessing the request parameters. Note +that the current implementation (via a hashref) of C and C is +likely to change in a future version of Maypole. So avoid direct access to these +hashrefs: -Returns a Maypole::Constant to indicate whether the user is -authenticated for -the Maypole request. + $r->{params}->{foo} # bad + $r->params->{foo} # better -The default implementation returns C + $r->{query}->{foo} # bad + $r->query->{foo} # better -=head3 model_class + $r->param('foo') # best -Returns the perl package name that will serve as the model for the -request. It corresponds to the request C
attribute. +=over 4 -=head3 additional_data +=item param -Called before the model processes the request, this method gives you a -chance -to do some processing for each request, for example, manipulating -C. +An accessor (get or set) for request parameters. It behaves similarly to +CGI::param() for accessing CGI parameters, i.e. -=head3 objects + $r->param # returns list of keys + $r->param($key) # returns value for $key + $r->param($key => $value) # returns old value, sets to new value -Get/set a list of model objects. The objects will be accessible in the -view -templates. +=cut -If the first item in C<$r-Eargs> can be Cd by the model -class, -it will be removed from C and the retrieved object will be added -to the -C list. See L for more information. +sub param +{ + my ($self, $key) = (shift, shift); + + return keys %{$self->params} unless defined $key; + + return unless exists $self->params->{$key}; + + my $val = $self->params->{$key}; + + if (@_) + { + my $new_val = shift; + $self->params->{$key} = $new_val; + } + + return ref $val ? @$val : ($val) if wantarray; + + return ref $val ? $val->[0] : $val; +} -=head3 template_args - $r->template_args->{foo} = 'bar'; +=item params -Get/set a hash of template variables. +Returns a hashref of request parameters. -=head3 template +B Where muliple values of a parameter were supplied, the C value +will be an array reference. -Get/set the template to be used by the view. By default, it returns -C<$r-Eaction> +=item query -=head3 exception +Alias for C. -This method is called if any exceptions are raised during the -authentication -or -model/view processing. It should accept the exception as a parameter and -return -a Maypole::Constant to indicate whether the request should continue to -be -processed. +=back -=head3 error +=head3 Utility methods -Get/set a request error +=over 4 -=head3 output +=item redirect_request -Get/set the response output. This is usually populated by the view -class. You -can skip view processing by setting the C. +Sets output headers to redirect based on the arguments provided -=head3 document_encoding +Accepts either a single argument of the full url to redirect to, or a hash of +named parameters : -Get/set the output encoding. Default: utf-8. +$r->redirect_request('http://www.example.com/path'); -=head3 content_type +or -Get/set the output content type. Default: text/html +$r->redirect_request(protocol=>'https', domain=>'www.example.com', path=>'/path/file?arguments', status=>'302', url=>'..'); -=head3 send_output +The named parameters are protocol, domain, path, status and url -Sends the output and additional headers to the user. +Only 1 named parameter is required but other than url, they can be combined as +required and current values (from the request) will be used in place of any +missing arguments. The url argument must be a full url including protocol and +can only be combined with status. -=head3 call_authenticate +=cut -This method first checks if the relevant model class -can authenticate the user, or falls back to the default -authenticate method of your Maypole application. +sub redirect_request { + die "redirect_request is a virtual method. Do not use Maypole directly; use Apache::MVC or similar"; +} +=item redirect_internal_request -=head3 call_exception +=cut -This model is called to catch exceptions, first after authenticate, then after -processing the model class, and finally to check for exceptions from the view -class. +sub redirect_internal_request { -This method first checks if the relevant model class -can handle exceptions the user, or falls back to the default -exception method of your Maypole application. +} -=head3 make_random_id -returns a unique id for this request can be used to prevent or detect repeat submissions. +=item make_random_id -=head3 handler +returns a unique id for this request can be used to prevent or detect repeat +submissions. -This method sets up the class if it's not done yet, sets some -defaults and leaves the dirty work to handler_guts. +=cut -=head3 handler_guts +# Session and Repeat Submission Handling +sub make_random_id { + use Maypole::Session; + return Maypole::Session::generate_unique_id(); +} -This is the core of maypole. You don't want to know. +=back =head1 SEE ALSO @@ -597,7 +1072,7 @@ L, L, L. =head1 AUTHOR -Maypole is currently maintained by Simon Flack C +Maypole is currently maintained by Aaron Trevena =head1 AUTHOR EMERITUS