use Maypole::Config;
use Maypole::Constants;
use Maypole::Headers;
+use URI();
-our $VERSION = '2.05';
+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
+# - double leading underscore - private to the current package
+
+=head1 NAME
+
+Maypole - MVC web application framework
+
+=head1 SYNOPSIS
+
+See L<Maypole::Application>.
+
+=head1 DESCRIPTION
+
+This documents the Maypole request object. See the L<Maypole::Manual>, 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<BeerDB> package used as an example in the manual.
+
+This needs to first use L<Maypole::Application> which will make your package
+inherit from the appropriate platform driver such as C<Apache::MVC> or
+C<CGI::Maypole>. Then, the driver calls C<setup>. This sets up the model classes and
+configures your application. The default model class for Maypole uses
+L<Class::DBI> to map a database to classes, but this can be changed by altering
+configuration (B<before> 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<Maypole::Manual> 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<MasonX::Maypole> distribution.
+
+=item beerfb.riverside-cms.co.uk
+
+A demo of L<Maypole::FormBuilder>. This site is running on the set of Mason
+templates included in the L<Maypole::FormBuilder> distribution. See the
+synopsis of L<Maypole::Plugin::FormBuilder> for an example driver
+
+=back
+
+=cut
__PACKAGE__->mk_classdata($_) for qw( config init_done view_object );
+
__PACKAGE__->mk_accessors(
- qw( ar params query objects model_class template_args output path
+ 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<Maypole::Config> object
+
+=item setup
+
+ My::App->setup($data_source, $user, $password, \%attr);
+
+Initialise the Maypole application and plugins and model classes - see
+L<Maypole::Manual::Plugins>.
+
+If your model is based on L<Maypole::Model::CDBI>, the C<\%attr> hashref can
+contain options that are passed directly to L<Class::DBI::Loader>, to control
+how the model hierarchy is constructed.
+
+Your application should call this B<after> setting up configuration data via
+L<"config">.
-sub setup {
+=cut
+
+sub setup
+{
+ my $class = shift;
+
+ $class->setup_model(@_);
+}
+
+=item setup_model
+
+Called by C<setup>. This method builds the Maypole model hierarchy.
+
+A likely target for over-riding, if you need to build a customised model.
+
+=cut
+
+sub setup_model
+{
my $calling_class = shift;
+
$calling_class = ref $calling_class if ref $calling_class;
- {
- no strict 'refs';
- no warnings 'redefine';
-
- # Naughty.
- *{ $calling_class . "::handler" } =
- sub { Maypole::handler( $calling_class, @_ ) };
- }
+
my $config = $calling_class->config;
- $config->model || $config->model("Maypole::Model::CDBI");
- $config->model->require;
- die "Couldn't load the model class $config->model: $@" if $@;
- $config->model->setup_database( $config, $calling_class, @_ );
- for my $subclass ( @{ $config->classes } ) {
+
+ $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, @_);
+
+ foreach my $subclass ( @{ $config->classes } )
+ {
no strict 'refs';
unshift @{ $subclass . "::ISA" }, $config->model;
$config->model->adopt($subclass)
if $config->model->can("adopt");
+
+ # TODO: I think we should also load these classes, in case there is any
+ # custom code. It would save the developer from needing to put
+ # lots of use MyApp::SomeTable statements in the driver, and should
+ # help eliminate some of those annoying silent errors if there's a
+ # syntax error.
}
}
-sub init {
+=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<Maypole::Manual::Plugins>.
+
+=cut
+
+sub init
+{
my $class = shift;
my $config = $class->config;
$config->view || $config->view("Maypole::View::TT");
|| $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;
}
-sub handler {
+=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<debug> flag via L<Maypole::Application>.
+
+=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<Maypole::Config/"template_root">
+
+=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<handler_guts>.
+
+=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
+# more information <http://modperlbook.org/html/ch25_01.html>
+sub handler : method
+{
# See Maypole::Workflow before trying to understand this.
- my ( $class, $req ) = @_;
+ my ($class, $req) = @_;
+
$class->init unless $class->init_done;
- my $r = bless { template_args => {}, config => $class->config }, $class;
- $r->headers_out(Maypole::Headers->new);
- $r->get_request($req);
- $r->parse_location();
- my $status = $r->handler_guts();
+
+ my $self = $class->new;
+
+ # initialise the request
+ $self->headers_out(Maypole::Headers->new);
+ $self->get_request($req);
+ $self->parse_location;
+
+ # hook useful for declining static requests e.g. images
+ my $status = $self->start_request_hook;
+ return $status unless $status == Maypole::Constants::OK();
+
+ $self->session($self->get_session);
+
+ $status = $self->handler_guts;
+
+ # moving this here causes unit test failures - need to check why
+ # before committing the move
+ #$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;
}
-# The root of all evil
-sub handler_guts {
- my $r = shift;
- $r->model_class( $r->config->model->class_of( $r, $r->{table} ) );
+=item handler_guts
- my $applicable = $r->is_applicable;
- unless ( $applicable == OK ) {
+This is the main request handling method and calls various methods to handle the
+request/response and defines the workflow within Maypole.
- # It's just a plain template
- delete $r->{model_class};
- $r->{path} =~ s{/$}{}; # De-absolutify
- $r->template( $r->{path} );
- }
+B<Currently undocumented and liable to be refactored without warning>.
+
+=cut
+
+# The root of all evil
+sub handler_guts
+{
+ my ($self) = @_;
+
+ $self->__load_model;
+
+ my $applicable = $self->is_model_applicable;
+
+ $self->__setup_plain_template unless $applicable;
- # We authenticate every request, needed for proper session management
my $status;
- eval { $status = $r->call_authenticate };
- if ( my $error = $@ ) {
- $status = $r->call_exception($error);
- if ( $status != OK ) {
+
+ eval { $status = $self->call_authenticate };
+
+ if ( my $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 ) {
- $r->view_object->error( $r,
+
+ if ( $self->debug and $status != OK and $status != DECLINED )
+ {
+ $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;
- if ( $applicable == OK ) {
- eval { $r->model_class->process($r) };
- if ( my $error = $@ ) {
- $status = $r->call_exception($error);
- if ( $status != OK ) {
+ $self->additional_data;
+
+ if ($applicable)
+ {
+ eval { $self->model_class->process($self) };
+
+ if ( my $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;
}
}
}
- if ( !$r->{output} ) { # You might want to do it yourself
- eval { $status = $r->view_object->process($r) };
- if ( my $error = $@ ) {
- $status = $r->call_exception($error);
- if ( $status != OK ) {
- warn "caught view error: $error" if $r->debug;
- return $r->debug ? $r->view_object->error( $r, $error ) : ERROR;
- }
+
+ # 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 $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
+# will be skipped, but need to remove the model anyway so the template can't
+# access it.
+sub __setup_plain_template
+{
+ my ($self) = @_;
+
+ # It's just a plain template
+ $self->model_class(undef);
+
+ my $path = $self->path;
+ $path =~ s{/$}{}; # De-absolutify
+ $self->path($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 $self->output
+sub __call_process_view
+{
+ my ($self) = @_;
+
+ my $status;
+
+ eval { $status = $self->view_object->process($self) };
+
+ if ( my $error = $@ )
+ {
+ $status = $self->call_exception($error);
+
+ if ( $status != OK )
+ {
+ warn "caught view error: $error" if $self->debug;
+ return $self->debug ?
+ $self->view_object->error($self, $error) : ERROR;
}
- return $status;
}
- else { return OK; }
+
+ return $status;
}
-sub is_applicable {
- my $self = shift;
+=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<path>, and invoking C<parse_path> and
+C<parse_args>.
+
+You should only need to define this method if you are writing a new Maypole
+backend.
+
+=cut
+
+sub parse_location
+{
+ die "parse_location is a virtual method. Do not use Maypole directly; " .
+ "use Apache::MVC or similar";
+}
+
+=item start_request_hook
+
+This is called immediately after setting up the basic request. The default
+method simply returns C<Maypole::Constants::OK>.
+
+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<This method is deprecated> as of version 2.11. If you have overridden it,
+please override C<is_model_applicable> 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 ($self) = @_;
+
+ # 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";
- warn "We don't have that table ($self->{table}).\n"
- . "Available tables are: "
- . join( ",", @{ $config->{display_tables} } )
- if $self->debug
- and not $config->ok_tables->{ $self->{table} }
- and $self->{action};
- return DECLINED() unless exists $config->ok_tables->{ $self->{table} };
-
- # Is it public?
- return DECLINED unless $self->model_class->is_public( $self->{action} );
- return OK();
+ if ref $config->ok_tables eq "ARRAY";
+
+ my $ok_tables = $config->ok_tables;
+
+ # Does this request concern a table to be processed by the model?
+ my $table = $self->table;
+
+ my $ok = 0;
+
+ if (exists $ok_tables->{$table})
+ {
+ $ok = 1;
+ }
+
+ 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 0;
}
-sub call_authenticate {
- my $self = shift;
+=item get_session
- # Check if we have a model class
- if ( $self->{model_class} ) {
- return $self->model_class->authenticate($self)
- if $self->model_class->can("authenticate");
- }
- return $self->authenticate($self); # Interface consistency is a Good Thing
+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 ($self) = @_;
+
+ # 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');
+
+ # 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);
}
-sub call_exception {
- my $self = shift;
- my ($error) = @_;
+=item authenticate
+
+Returns a Maypole::Constant to indicate whether the user is authenticated for
+the Maypole request.
+
+The default implementation returns C<OK>
+
+=cut
+
+sub authenticate { return OK }
- # Check if we have a model class
- if ( $self->{model_class}
- && $self->model_class->can('exception') )
+
+=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 ($self, $error) = @_;
+
+ # Check if we have a model class with an exception() to delegate to
+ if ( $self->model_class && $self->model_class->can('exception') )
{
my $status = $self->model_class->exception( $self, $error );
return $status if $status == OK;
}
+
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 }
-sub parse_path {
- my $self = shift;
- $self->{path} ||= "frontpage";
- my @pi = split /\//, $self->{path};
- shift @pi while @pi and !$pi[0];
- $self->{table} = shift @pi;
- $self->{action} = shift @pi;
- $self->{args} = \@pi;
-}
+=item additional_data
-sub get_template_root { "." }
-sub get_request { }
+Called before the model processes the request, this method gives you a chance to
+do some processing for each request, for example, manipulating C<template_args>.
-sub parse_location {
- die "Do not use Maypole directly; use Apache::MVC or similar";
-}
+=cut
-sub send_output {
- die "Do not use Maypole directly; use Apache::MVC or similar";
-}
+sub additional_data { }
-=head1 NAME
+=item send_output
-Maypole - MVC web application framework
+Sends the output and additional headers to the user.
-=head1 SYNOPSIS
+=cut
-See L<Maypole::Application>.
+sub send_output {
+ die "send_output is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
+}
-=head1 DESCRIPTION
-This documents the Maypole request object. For user documentation, see
-L<Maypole::Manual>.
-=head2 CLASS METHODS
+=item redirect_request
-=head3 config
+Sets output headers to redirect based on the arguments provided
-Returns the L<Maypole::Config> object
+Accepts either a single argument of the full url to redirect to, or a hash of
+named parameters :
-=head3 setup
+$r->redirect_request('http://www.example.com/path');
- My::App->setup($data_source, $user, $password, \%attr);
+or
-Initialise the maypole application and model classes. Your application should
-call this after setting configuration via L<"config">
+$r->redirect_request(protocol=>'https', domain=>'www.example.com', path=>'/path/file?arguments', status=>'302', url=>'..');
-=head3 init
+The named parameters are protocol, domain, path, status and url
-You should not call this directly, but you may wish to override this to
-add
-application-specific initialisation.
+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 view_object
+=cut
-Get/set the Maypole::View object
+sub redirect_request {
+ die "redirect_request is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
+}
-=head3 debug
+=item redirect_internal_request
- sub My::App::debug {1}
+=cut
-Returns the debugging flag. Override this in your application class to
-enable/disable debugging.
+sub redirect_internal_request {
-=head2 INSTANCE METHODS
+}
-=head3 parse_location
+=back
-Turns the backend request (e.g. Apache::MVC, Maypole, CGI) into a
-Maypole
-request. It does this by setting the C<path>, and invoking C<parse_path>
-and
-C<parse_args>.
+=head2 Path processing and manipulation
-You should only need to define this method if you are writing a new
-Maypole
-backend.
+=over 4
-=head3 path
+=item path
Returns the request path
-=head3 parse_path
+=item parse_path
-Parses the request path and sets the C<args>, C<action> and C<table>
-properties
+Parses the request path and sets the C<args>, C<action> and C<table>
+properties. Calls C<preprocess_path> before parsing path and setting properties.
-=head3 table
+=cut
-The table part of the Maypole request path
+sub parse_path
+{
+ my ($self) = @_;
+
+ # 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/;
+
+ $self->preprocess_path;
+
+ $self->path || $self->path('frontpage');
+
+ my @pi = grep {length} split '/', $self->path;
+
+ $self->table || $self->table(shift @pi);
+ $self->action || $self->action( shift @pi or 'index' );
+ $self->args || $self->args(\@pi);
+}
-=head3 action
+=item preprocess_path
-The action part of the Maypole request 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.
-=head3 args
+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.
-A list of remaining parts of the request path after table and action
-have been
-removed
+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
-=head3 headers_in
+=cut
-A L<Maypole::Headers> object containing HTTP headers for the request
+sub preprocess_path { };
-=head3 headers_out
+=item make_path( %args or \%args or @args )
-A L<HTTP::Headers> object that contains HTTP headers for the output
+This is the counterpart to C<parse_path>. It generates a path to use
+in links, form actions etc. To implement your own path scheme, just override
+this method and C<parse_path>.
-=head3 parse_args
+ %args = ( table => $table,
+ action => $action,
+ additional => $additional, # optional - generally an object ID
+ );
+
+ \%args = as above, but a ref
+
+ @args = ( $table, $action, $additional ); # $additional is optional
-Turns post data and query string paramaters into a hash of C<params>.
+C<id> can be used as an alternative key to C<additional>.
-You should only need to define this method if you are writing a new
-Maypole
-backend.
+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.
-=head3 params
+=cut
-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.
+sub make_path
+{
+ my $r = shift;
+
+ my %args;
+
+ 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 = @_;
+ }
+
+ do { die "no $_" unless $args{$_} } for qw( table action );
+
+ 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;
+}
-B<Note:> Where muliple values of a parameter were supplied, the
-C<params>
-value
-will be an array reference.
-=head3 get_template_root
-Implimentation-specific path to template root.
+=item make_uri( @segments )
-You should only need to define this method if you are writing a new
-Maypole
-backend. Otherwise, see L<Maypole::Config/"template_root">
+Make a L<URI> object given table, action etc. Automatically adds
+the C<uri_base>.
-=head3 get_request
+If the final element in C<@segments> is a hash ref, C<make_uri> will render it
+as a query string.
-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 make_uri
+{
+ my ($r, @segments) = @_;
+
+ 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;
+}
-=head3 is_applicable
+=item parse_args
-Returns a Maypole::Constant to indicate whether the request is valid.
+Turns post data and query string paramaters into a hash of C<params>.
-The default implimentation checks that C<$r-E<gt>table> is publicly
-accessible
-and that the model class is configured to handle the C<$r-E<gt>action>
+You should only need to define this method if you are writing a new Maypole
+backend.
-=head3 authenticate
+=back
-Returns a Maypole::Constant to indicate whether the user is
-authenticated for
-the Maypole request.
+=head2 Request properties
-The default implimentation returns C<OK>
+=over 4
-=head3 model_class
+=item model_class
Returns the perl package name that will serve as the model for the
request. It corresponds to the request C<table> attribute.
-=head3 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<template_args>.
+=item objects
-=head3 objects
-
-Get/set a list of model objects. The objects will be accessible in the
-view
+Get/set a list of model objects. The objects will be accessible in the view
templates.
-If the first item in C<$r-E<gt>args> can be C<retrieve()>d by the model
-class,
-it will be removed from C<args> and the retrieved object will be added
-to the
-C<objects> list. See L<Maypole::Model> for more information.
+If the first item in C<$self-E<gt>args> can be C<retrieve()>d by the model
+class, it will be removed from C<args> and the retrieved object will be added to
+the C<objects> list. See L<Maypole::Model> for more information.
-=head3 template_args
+=item template_args
- $r->template_args->{foo} = 'bar';
+ $self->template_args->{foo} = 'bar';
Get/set a hash of template variables.
-=head3 template
+=item stash
-Get/set the template to be used by the view. By default, it returns
-C<$r-E<gt>action>
+A place to put custom application data. Not used by Maypole itself.
-=head3 exception
+=item template
-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.
+Get/set the template to be used by the view. By default, it returns
+C<$self-E<gt>action>
-=head3 error
+
+=item error
Get/set a request error
-=head3 output
+=item output
-Get/set the response output. This is usually populated by the view
-class. You
+Get/set the response output. This is usually populated by the view class. You
can skip view processing by setting the C<output>.
-=head3 document_encoding
+=item table
+
+The table part of the Maypole request path
+
+=item action
+
+The action part of the Maypole request path
+
+=item args
+
+A list of remaining parts of the request path after table and action
+have been
+removed
+
+=item headers_in
+
+A L<Maypole::Headers> object containing HTTP headers for the request
+
+=item headers_out
+
+A L<HTTP::Headers> object that contains HTTP headers for the output
+
+=item document_encoding
Get/set the output encoding. Default: utf-8.
-=head3 content_type
+=item content_type
Get/set the output content type. Default: text/html
-=head3 send_output
+=item get_protocol
-Sends the output and additional headers to the user.
+Returns the protocol the request was made with, i.e. https
-=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 get_protocol {
+ die "get_protocol is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
+}
+=back
-=head3 call_exception
+=head2 Request parameters
-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.
+The source of the parameters may vary depending on the Maypole backend, but they
+are usually populated from request query string and POST data.
-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.
+Maypole supplies several approaches for accessing the request parameters. Note
+that the current implementation (via a hashref) of C<query> and C<params> is
+likely to change in a future version of Maypole. So avoid direct access to these
+hashrefs:
+
+ $r->{params}->{foo} # bad
+ $r->params->{foo} # better
+
+ $r->{query}->{foo} # bad
+ $r->query->{foo} # better
+ $r->param('foo') # best
-=head3 handler
+=over 4
-This method sets up the class if it's not done yet, sets some
-defaults and leaves the dirty work to handler_guts.
+=item param
-=head3 handler_guts
+An accessor (get or set) for request parameters. It behaves similarly to
+CGI::param() for accessing CGI parameters, i.e.
-This is the core of maypole. You don't want to know.
+ $r->param # returns list of keys
+ $r->param($key) # returns value for $key
+ $r->param($key => $value) # returns old value, sets to new value
+
+=cut
+
+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;
+}
+
+
+=item params
+
+Returns a hashref of request parameters.
+
+B<Note:> Where muliple values of a parameter were supplied, the C<params> value
+will be an array reference.
+
+=item query
+
+Alias for C<params>.
+
+=item make_random_id
+
+returns a unique id for this request can be used to prevent or detect repeat
+submissions.
+
+=cut
+
+# Session and Repeat Submission Handling
+sub make_random_id {
+ use Maypole::Session;
+ return Maypole::Session::generate_unique_id();
+}
+
+=back
=head1 SEE ALSO
-There's more documentation, examples, and a wiki at the Maypole web
-site:
+There's more documentation, examples, and a information on our mailing lists
+at the Maypole web site:
-http://maypole.perl.org/
+L<http://maypole.perl.org/>
-L<Maypole::Application>,L<Apache::MVC>, L<CGI::Maypole>.
+L<Maypole::Application>, L<Apache::MVC>, L<CGI::Maypole>.
=head1 AUTHOR
-Sebastian Riedel, c<sri@oook.de>
+Maypole is currently maintained by Aaron Trevena
=head1 AUTHOR EMERITUS
-Simon Cozens, C<simon@cpan.org>
+Simon Cozens, C<simon#cpan.org>
+
+Sebastian Riedel, C<sri#oook.de> maintained Maypole from 1.99_01 to 2.04
=head1 THANKS TO
-Danijel Milicevic, Dave Slack, Jesse Sheidlower, Jody Belka, Marcus Ramberg,
-Mickael Joanne, Randal Schwartz, Simon Flack, Steve Simms, Veljko Vidovic
-and all the others who've helped.
+Sebastian Riedel, Danijel Milicevic, Dave Slack, Jesse Sheidlower, Jody Belka,
+Marcus Ramberg, Mickael Joanne, Randal Schwartz, Simon Flack, Steve Simms,
+Veljko Vidovic and all the others who've helped.
=head1 LICENSE