use UNIVERSAL::require;
use strict;
use warnings;
+use Data::Dumper;
use Maypole::Config;
use Maypole::Constants;
use Maypole::Headers;
-use Maypole::Components;
use URI();
+use URI::QueryParam;
+use NEXT;
use File::MMagic::XS qw(:compat);
our $VERSION = '2.11';
__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 stash status)
+ headers_in headers_out stash status parent)
);
__PACKAGE__->config( Maypole::Config->new() );
=cut
-sub debug { 0 }
+sub debug { 1 }
=item config
Maypole::Model::CDBI is the base class.
My::App->setup($data_source, $user, $password,
- { opitons => { # These are DB connection options
+ { options => { # These are DB connection options
AutoCommit => 0,
RaiseError => 1,
...
# among other things, this populates $config->classes
$config->model->setup_database($config, $class, @_);
-
+
foreach my $subclass ( @{ $config->classes } ) {
next if $subclass->isa("Maypole::Model::Base");
no strict 'refs';
- unshift @{ $subclass . "::ISA" }, $config->model;
-
+ unshift @{ $subclass . "::ISA" }, $config->model;
}
# Load custom model code, if it exists - nb this must happen after the
sub new
{
my ($class) = @_;
-
my $self = bless {
- template_args => {},
config => $class->config,
}, $class;
+
+ $self->stash({});
+ $self->params({});
+ $self->query({});
+ $self->template_args({});
+ $self->args([]);
+ $self->objects([]);
return $self;
}
# 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, or perhaps for
# sanitizing request parameters
$self->status(Maypole::Constants::OK()); # set the default
$self->__call_hook('start_request_hook');
return $self->status unless $self->status == Maypole::Constants::OK();
-
die "status undefined after start_request_hook()" unless defined
$self->status;
-
$self->get_session;
$self->get_user;
-
my $status = $self->handler_guts;
return $status unless $status == OK;
-
# TODO: require send_output to return a status code
$self->send_output;
-
return $status;
}
+=item component
+
+ Run Maypole sub-requests as a component of the request
+
+ [% request.component("/beer/view_as_component/20") %]
+
+ Allows you to integrate the results of a Maypole request into an existing
+request. You'll need to set up actions and templates
+which return fragments of HTML rather than entire pages, but once you've
+done that, you can use the C<component> method of the Maypole request object
+to call those actions. You may pass a query string in the usual URL style.
+
+You should not fully qualify the Maypole URLs.
+
+Note: any HTTP POST or URL parameters passed to the parent are not passed to the
+component sub-request, only what is included in the url passed as an argyument
+to the method
+
+=cut
+
sub component {
- my ($r,$path) = @_;
- my $component = Maypole::Components->new(@_);
- return $component->handler($path);
+ my ( $r, $path ) = @_;
+ my $self = bless { parent => $r, config => $r->{config}, } , ref $r;
+ $self->stash({});
+ $self->params({});
+ $self->query({});
+ $self->template_args({});
+ $self->args([]);
+ $self->objects([]);
+
+ $self->get_user;
+ my $url = URI->new($path);
+ warn "path : $path\n";
+ $self->{path} = $url->path;
+ $self->parse_path;
+ $self->params( $url->query_form_hash );
+ $self->handler_guts;
+ return $self->output;
}
+sub get_template_root {
+ my $self = shift;
+ my $r = shift;
+ return $r->parent->get_template_root if $r->{parent};
+ return $self->NEXT::DISTINCT::get_template_root( $r, @_ );
+}
+
+sub view_object {
+ my $self = shift;
+ my $r = shift;
+ return $r->parent->view_object if $r->{parent};
+ return $self->NEXT::DISTINCT::view_object( $r, @_ );
+}
# Instead of making plugin authors use the NEXT::DISTINCT hoopla to ensure other
# plugins also get to call the hook, we can cycle through the application's
$self->__load_request_model;
my $applicable = $self->is_model_applicable == OK;
-
- $self->__setup_plain_template unless $applicable;
my $status;
+ # handle authentication
eval { $status = $self->call_authenticate };
-
if ( my $error = $@ )
{
$status = $self->call_exception($error, "authentication");
-
if ( $status != OK )
{
warn "caught authenticate error: $error";
$self->view_object->error($self, $error) : ERROR;
}
}
-
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
$self->additional_data;
-
- if ($applicable)
- {
- eval { $self->model_class->process($self) };
-
- if ( my $error = $@ )
+
+ if ($applicable) {
+ eval { $self->model_class->process($self) };
+ if ( my $error = $@ )
{
- $status = $self->call_exception($error, "model");
-
- if ( $status != OK )
+ $status = $self->call_exception($error, "model");
+ if ( $status != OK )
{
- warn "caught model error: $error";
- return $self->debug ?
- $self->view_object->error($self, $error) : ERROR;
+ warn "caught model error: $error";
+ return $self->debug ?
+ $self->view_object->error($self, $error) : ERROR;
}
}
+ } else {
+ $self->__setup_plain_template;
}
-
+
# less frequent path - perhaps output has been set to an error message
return OK if $self->output;
$self->{content_type} ||= $self->__get_mime_type();
$self->{document_encoding} ||= "utf-8";
+
return $processed_view_ok;
}
sub __get_mime_type {
my $self = shift;
- my $type;
+ my $type = 'text/html';
if ($self->path =~ m/.*\.(\w{3,4})$/) {
$type = $filetypes{$1};
} else {
- $type = $mmagic->checktype_contents($self->output);
+ my $output = $self->output;
+ if (defined $output) {
+ $type = $mmagic->checktype_contents($output);
+ }
}
return $type;
}
sub __load_request_model
{
my ($self) = @_;
- $self->model_class( $self->config->model->class_of($self, $self->table) );
+ # We may get a made up class from class_of
+ my $mclass = $self->config->model->class_of($self, $self->table);
+ if ( eval {$mclass->isa('Maypole::Model::Base')} ) {
+ $self->model_class( $mclass );
+ }
+ elsif ($self->debug) {
+ warn "***Warning: No $mclass class appropriate for model. @_";
+ }
}
+
# 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);
# 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, "view");
-
- if ( $status != OK )
- {
- warn "caught view error: $error" if $self->debug;
- return $self->debug ?
- $self->view_object->error($self, $error) : ERROR;
- }
+sub __call_process_view {
+ my ($self) = @_;
+
+ my $status = eval { $self->view_object->process($self) };
+
+ my $error = $@ || $self->{error};
+
+ if ( $error ) {
+ $status = $self->call_exception($error, "view");
+
+ if ( $status != OK ) {
+ warn "caught view error: $error" if $self->debug;
+ return $self->debug ?
+ $self->view_object->error($self, $error) : ERROR;
}
-
- return $status;
+ }
+
+ return $status;
}
=item get_request
sub exception {
my ($self, $error, $when) = @_;
- if ($self->view_object->can("report_error") and $self->debug) {
+ if (ref $self->view_object && $self->view_object->can("report_error") and $self->debug) {
$self->view_object->report_error($self, $error, $when);
return OK;
}
}
-
-
=back
=head2 Path processing and manipulation
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');
=cut
-sub get_template_root {'.'}
-
=back
=head2 Request properties
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.
+
+=item object
+
+Alias to get/set the first/only model object. The object will be accessible
+in the view templates.
+
+When used to set the object, will overwrite the request objects
+with a single object.
+
+=cut
+
+sub object {
+ my ($r,$object) = @_;
+ $r->objects([$object]) if ($object);
+ return undef unless $r->objects();
+ return $r->objects->[0];
+}
+
=item template_args
$self->template_args->{foo} = 'bar';
=item stash
-A place to put custom application data. Not used by Maypole itself.
+A place to put custom application data. Not used by Maypole itself.
=item template
=head1 AUTHOR
-Maypole is currently maintained by Aaron Trevena, David Baird, Dave Howorth and
-Peter Speltz.
+Maypole is currently maintained by Aaron Trevena.
=head1 AUTHOR EMERITUS