]> git.decadent.org.uk Git - maypole.git/blobdiff - lib/Maypole.pm
tested and it seems to work
[maypole.git] / lib / Maypole.pm
index 5db199b484ae12a13c492aa3c16e64e419476e51..d476b56a46e55ed9dad22695f8a10e33b9e0db5d 100644 (file)
@@ -3,11 +3,13 @@ use base qw(Class::Accessor::Fast Class::Data::Inheritable);
 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';
@@ -33,6 +35,9 @@ The canonical example used in the Maypole documentation is the beer database:
     
     # choose a frontend, initialise the config object, and load a plugin
     use Maypole::Application qw/Relationship/;
+
+    # set everything up
+    __PACKAGE__->setup("dbi:SQLite:t/beerdb.db");
     
     # get the empty config object created by Maypole::Application
     my $config = __PACKAGE__->config;
@@ -60,8 +65,8 @@ The canonical example used in the Maypole documentation is the beer database:
         date => [ qw/date/],
     );
 
-    # set everything up
-    __PACKAGE__->setup("dbi:SQLite:t/beerdb.db");
+    # note : set up model before calling this method
+    BeerDB::Beer->required_columns([qw/name/]); 
 
     1;    
 
@@ -89,7 +94,7 @@ configuration (B<before> calling setup.)
 
 Note that some details in some of these resources may be out of date.
 
-=over 4 
+=over 4
 
 =item The Maypole Manual
 
@@ -118,7 +123,7 @@ 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].
+http://www.aarontrevena.co.uk/opensource/maypole/maypole-tutorial.pdf [228KB].
 
 =item A Database-Driven Web Application in 18 Lines of Code
 
@@ -146,7 +151,7 @@ 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
+very useful: http://www.aarontrevena.co.uk/opensource/maypole/authentication.html
 
 =item CheatSheet
 
@@ -171,30 +176,6 @@ 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 model_classes_loaded);
@@ -202,7 +183,7 @@ __PACKAGE__->mk_classdata($_) for qw( config init_done view_object model_classes
 __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() );
@@ -273,7 +254,7 @@ documentation. However here is the most usage of setup where
 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,
                ...
@@ -317,12 +298,11 @@ sub setup_model {
 
   # 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
@@ -400,11 +380,16 @@ Constructs a very minimal new Maypole request object.
 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;
 }
@@ -444,35 +429,78 @@ sub handler : method  {
   # 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 
@@ -524,17 +552,14 @@ sub handler_guts
     $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";
@@ -542,35 +567,33 @@ sub handler_guts
                     $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;
 
@@ -580,6 +603,7 @@ sub handler_guts
     $self->{content_type}      ||= $self->__get_mime_type();
     $self->{document_encoding} ||= "utf-8";
 
+
     return $processed_view_ok;
 }
 
@@ -592,11 +616,14 @@ my %filetypes = (
 
 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;
 }
@@ -604,16 +631,24 @@ sub __get_mime_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);
     
@@ -626,27 +661,24 @@ sub __setup_plain_template
 
 # 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
@@ -883,7 +915,7 @@ processed.
 
 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;
     }
@@ -910,8 +942,6 @@ sub send_output {
 }
 
 
-
-
 =back
 
 =head2 Path processing and manipulation
@@ -932,13 +962,12 @@ properties. Calls C<preprocess_path> before parsing path and setting properties.
 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');
 
@@ -1080,8 +1109,6 @@ backend. Otherwise, see L<Maypole::Config/"template_root">
 
 =cut
 
-sub get_template_root {'.'}
-
 =back
 
 =head2 Request properties
@@ -1103,15 +1130,38 @@ 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.
 
+
+=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';
 
 Get/set a hash of template variables.
 
+Maypole reserved words for template variables will over-ride values in template_variables.
+
+Reserved words are : r, request, object, objects, base, config and errors, as well as the
+current class or object name.
+
 =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
 
@@ -1405,8 +1455,7 @@ L<Maypole::Application>, L<Apache::MVC>, L<CGI::Maypole>.
 
 =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