]> git.decadent.org.uk Git - maypole.git/blob - lib/Maypole.pm
Added make_path() and make_uri() methods,
[maypole.git] / lib / Maypole.pm
1 package Maypole;
2 use base qw(Class::Accessor::Fast Class::Data::Inheritable);
3 use UNIVERSAL::require;
4 use strict;
5 use warnings;
6 use Maypole::Config;
7 use Maypole::Constants;
8 use Maypole::Headers;
9 use URI();
10
11 our $VERSION = '2.11';
12
13 # proposed privacy conventions:
14 # - no leading underscore     - public to custom application code and plugins
15 # - single leading underscore - private to the main Maypole stack - *not*
16 #     including plugins
17 # - double leading underscore - private to the current package
18
19 __PACKAGE__->mk_classdata($_) for qw( config init_done view_object );
20 __PACKAGE__->mk_accessors(
21     qw( params query objects model_class template_args output path
22         args action template error document_encoding content_type table
23         headers_in headers_out stash)
24 );
25 __PACKAGE__->config( Maypole::Config->new() );
26 __PACKAGE__->init_done(0);
27
28 sub debug { 0 }
29
30 sub setup 
31 {
32     my $calling_class = shift;
33     
34     $calling_class = ref $calling_class if ref $calling_class;
35     
36     my $config = $calling_class->config;
37     
38     $config->model || $config->model('Maypole::Model::CDBI');
39     
40     $config->model->require or die sprintf 
41         "Couldn't load the model class %s: %s", $config->model, $@;
42     
43     $config->model->setup_database($config, $calling_class, @_);
44     
45     foreach my $subclass ( @{ $config->classes } ) 
46     {
47         no strict 'refs';
48         unshift @{ $subclass . "::ISA" }, $config->model;
49         $config->model->adopt($subclass)
50           if $config->model->can("adopt");
51     }
52 }
53
54 sub init 
55 {
56     my $class  = shift;
57     my $config = $class->config;
58     $config->view || $config->view("Maypole::View::TT");
59     $config->view->require;
60     die "Couldn't load the view class " . $config->view . ": $@" if $@;
61     $config->display_tables
62       || $config->display_tables( $class->config->tables );
63     $class->view_object( $class->config->view->new );
64     $class->init_done(1);
65
66 }
67
68 sub new
69 {
70     my ($class) = @_;
71     
72     my $self = bless {
73         template_args => {},
74         config        => $class->config,
75     }, $class;
76     
77     return $self;
78 }
79
80 # handler() has a method attribute so that mod_perl will invoke
81 # BeerDB->handler() as a method rather than a plain function
82 # BeerDB::handler() and so this inherited implementation will be
83 # found. See e.g. "Practical mod_perl"  by Bekman & Cholet for
84 # more information <http://modperlbook.org/html/ch25_01.html>
85 sub handler : method 
86 {
87     # See Maypole::Workflow before trying to understand this.
88     my ($class, $req) = @_;
89     
90     $class->init unless $class->init_done;
91
92     my $self = $class->new;
93     
94     # initialise the request
95     $self->headers_out(Maypole::Headers->new);
96     $self->get_request($req);
97     $self->parse_location;
98     
99     my $status = $self->handler_guts;
100     
101     # moving this here causes unit test failures - need to check why
102     # before committing the move
103     #$status = $self->__call_process_view unless $self->output;
104     
105     return $status unless $status == OK;
106     
107     $self->send_output;
108     
109     return $status;
110 }
111
112 # The root of all evil
113 sub handler_guts 
114 {
115     my ($self) = @_;
116     
117     $self->__load_model;
118
119     my $applicable = $self->is_model_applicable;
120     
121     $self->__setup_plain_template unless $applicable;
122     
123     # We authenticate every request, needed for proper session management
124     my $status;
125     
126     eval { $status = $self->call_authenticate };
127     
128     if ( my $error = $@ ) 
129     {
130         $status = $self->call_exception($error);
131         
132         if ( $status != OK ) 
133         {
134             warn "caught authenticate error: $error";
135             return $self->debug ? 
136                     $self->view_object->error($self, $error) : ERROR;
137         }
138     }
139     
140     if ( $self->debug and $status != OK and $status != DECLINED ) 
141     {
142         $self->view_object->error( $self,
143             "Got unexpected status $status from calling authentication" );
144     }
145     
146     return $status unless $status == OK;
147
148     # We run additional_data for every request
149     $self->additional_data;
150     
151     if ($applicable) 
152     {
153         eval { $self->model_class->process($self) };
154         
155         if ( my $error = $@ ) 
156         {
157             $status = $self->call_exception($error);
158             
159             if ( $status != OK ) 
160             {
161                 warn "caught model error: $error";
162                 return $self->debug ? 
163                     $self->view_object->error($self, $error) : ERROR;
164             }
165         }
166     }
167     
168     # less frequent path - perhaps output has been set to an error message
169     return OK if $self->output;
170     
171     # normal path - no output has been generated yet
172     return $self->__call_process_view;
173 }
174
175 # is_applicable() returned false, so set up a plain template. Model processing 
176 # will be skipped, but need to remove the model anyway so the template can't 
177 # access it. 
178 sub __setup_plain_template
179 {
180     my ($self) = @_;
181     
182     # It's just a plain template
183     $self->model_class(undef);
184     
185     my $path = $self->path;
186     $path =~ s{/$}{};    # De-absolutify
187     $self->path($path);
188     
189     $self->template($self->path);
190 }
191
192 # The model has been processed or skipped (if is_applicable returned false), 
193 # any exceptions have been handled, and there's no content in $self->output
194 sub __call_process_view
195 {
196     my ($self) = @_;
197     
198     my $status;
199     
200     eval { $status = $self->view_object->process($self) };
201     
202     if ( my $error = $@ ) 
203     {
204         $status = $self->call_exception($error);
205         
206         if ( $status != OK ) 
207         {
208             warn "caught view error: $error" if $self->debug;
209             return $self->debug ? 
210                 $self->view_object->error($self, $error) : ERROR;
211         }
212     }
213     
214     return $status;
215 }
216
217 sub __load_model
218 {
219     my ($self) = @_;
220     $self->model_class( $self->config->model->class_of($self, $self->table) );
221 }
222
223 # is_applicable() should return true or false, not OK or DECLINED, because 
224 # the return value is never used as the return value from handler(). There's 
225 # probably a lot of code out there supplying the return codes though, so
226 # instead of changing is_applicable() to return 0 or 1, the return value is
227 # passed through __to_boolean. I think it helps handler_guts() if we don't
228 # have multiple sets of return codes being checked for different things -drb.
229 sub is_model_applicable 
230 {
231     my ($self) = @_;
232     
233     # cater for applications that are using obsolete version
234     if ($self->can('is_applicable')) 
235     {
236         warn "DEPRECATION WARNING: rewrite is_applicable to the interface ".
237                 "of Maypole::is_model_applicable\n";
238         return $self->is_applicable == OK;
239     }
240
241     # Establish which tables should be processed by the model
242     my $config = $self->config;
243     
244     $config->ok_tables || $config->ok_tables( $config->display_tables );
245     
246     $config->ok_tables( { map { $_ => 1 } @{ $config->ok_tables } } )
247         if ref $config->ok_tables eq "ARRAY";
248         
249     my $ok_tables = $config->ok_tables;
250       
251     # Does this request concern a table to be processed by the model?
252     my $table = $self->table;
253     
254     my $ok = 0;
255     
256     if (exists $ok_tables->{$table}) 
257     {
258         $ok = 1;
259     } 
260 # implements tj's default_table_view(), but there's no _default_table_view()
261 # or _have_default_table_view() yet
262 #    else 
263 #    {
264 #        $ok = $self->default_table_view($self->path, $self->args)
265 #            if $self->_have_default_table_view;
266 #    }
267
268     if (not $ok) 
269     {
270         warn "We don't have that table ($table).\n"
271             . "Available tables are: "
272             . join( ",", keys %$ok_tables )
273                 if $self->debug and not $ok_tables->{$table};
274                 
275         return 0;
276     }
277     
278     # Is the action public?
279     my $action = $self->action;
280     return 1 if $self->model_class->is_public($action);
281     
282     warn "The action '$action' is not applicable to the table $table"
283         if $self->debug;
284     
285     return 0;
286 }
287
288 sub call_authenticate 
289 {
290     my ($self) = @_;
291
292     # Check if we have a model class with an authenticate() to delegate to
293     return $self->model_class->authenticate($self) 
294         if $self->model_class and $self->model_class->can('authenticate');
295     
296     # Interface consistency is a Good Thing - 
297     # the invocant and the argument may one day be different things 
298     # (i.e. controller and request), like they are when authenticate() 
299     # is called on a model class (i.e. model and request)
300     return $self->authenticate($self);   
301 }
302
303 sub call_exception 
304 {
305     my ($self, $error) = @_;
306
307     # Check if we have a model class with an exception() to delegate to
308     if ( $self->model_class && $self->model_class->can('exception') )
309     {
310         my $status = $self->model_class->exception( $self, $error );
311         return $status if $status == OK;
312     }
313     
314     return $self->exception($error);
315 }
316
317 sub default_table_view {
318   my ($self,$path,$args) = @_;
319   my $path_is_ok = 0;
320   my $default_table_view = __PACKAGE__->_default_table_view;
321   # (path class action field)
322   my @path = $self->{path} =~ m{([^/]+)/?}g;
323   my $search_value = shift(@path);
324   if ($default_table_view->{path}) {
325     if ($default_table_view->{path} eq $search_value) {
326       $search_value = shift(@path);
327     } else {
328       return 0;
329     }
330   }
331
332   $self->{table} = $default_table_view->{class};
333   $self->{action} = $default_table_view->{action};
334   $self->{args} = [ $search_value,@path ];
335   return $path_is_ok;
336 }
337
338 sub additional_data { }
339
340 sub authenticate { return OK }
341
342 sub exception { return ERROR }
343
344 sub parse_path 
345 {
346     my ($self) = @_;
347     
348     $self->path || $self->path('frontpage');
349     
350     my @pi = grep {length} split '/', $self->path;
351     
352     $self->table(shift @pi);
353     
354     $self->action( shift @pi or 'index' );
355     
356     $self->args(\@pi);
357 }
358
359 =head3 make_path( %args or \%args or @args )
360
361 This is the counterpart to C<Maypole::parse_path>. It generates a path to use
362 in links, form actions etc. To implement your own path scheme, just override
363 this method and C<parse_path>.
364
365     %args = ( table      => $table,
366               action     => $action,        
367               additional => $additional,    # optional - generally an object ID
368               );
369               
370     \%args = as above, but a ref
371     
372     @args = ( $table, $action, $additional );   # $additional is optional
373
374 C<id> can be used as an alternative key to C<additional>.
375
376 C<$additional> can be a string, an arrayref, or a hashref. An arrayref is
377 expanded into extra path elements, whereas a hashref is translated into a query
378 string. 
379
380 =cut
381
382 sub make_path
383 {
384     my $r = shift;
385     
386     my %args;
387     
388     if (@_ == 1 and ref $_[0] and ref $_[0] eq 'HASH')
389     {
390         %args = %{$_[0]};
391     }
392     elsif ( @_ > 1 and @_ < 4 )
393     {
394         $args{table}      = shift;
395         $args{action}     = shift;
396         $args{additional} = shift;
397     }
398     else
399     {
400         %args = @_;
401     }
402     
403     do { die "no $_" unless $args{$_} } for qw( table action );    
404
405     my $additional = $args{additional} || $args{id};
406     
407     my @add = ();
408     
409     if ($additional)
410     {
411         # if $additional is a href, make_uri() will transform it into a query
412         @add = (ref $additional eq 'ARRAY') ? @$additional : ($additional);
413     }    
414     
415     my $uri = $r->make_uri($args{table}, $args{action}, @add);
416     
417     return $uri->as_string;
418 }
419
420 =head3 make_uri( @segments )
421
422 Make a L<URI> object given table, action etc. Automatically adds
423 the C<uri_base>. 
424
425 If the final element in C<@segments> is a hash ref, C<make_uri> will render it
426 as a query string.
427
428 =cut
429
430 sub make_uri
431 {
432     my ($r, @segments) = @_;
433
434     my $query = (ref $segments[-1] eq 'HASH') ? pop(@segments) : undef;
435     
436     my $base = $r->config->uri_base; 
437     $base =~ s|/$||;
438     
439     my $uri = URI->new($base);
440     $uri->path_segments($uri->path_segments, grep {length} @segments);
441     
442     my $abs_uri = $uri->abs('/');
443     $abs_uri->query_form($query) if $query;
444     return $abs_uri;
445 }
446
447
448 # like CGI::param(), but read only 
449 sub param 
450
451     my ($self, $key) = @_;
452     
453     return keys %{$self->params} unless defined $key;
454     
455     return unless exists $self->params->{$key};
456     
457     my $val = $self->params->{$key};
458     
459     return ref $val ? @$val : ($val) if wantarray;
460         
461     return ref $val ? $val->[0] : $val;
462 }
463
464 sub get_template_root {'.'}
465 sub get_request       { }
466
467 sub get_protocol {
468   die "get_protocol is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
469 }
470
471 sub parse_location {
472     die "parse_location is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
473 }
474
475 sub redirect_request {
476   die "parse_location is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
477 }
478
479 sub redirect_internal_request {
480
481 }
482
483 sub send_output {
484     die "send_output is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
485 }
486
487 # Session and Repeat Submission Handling
488
489 sub make_random_id {
490     use Maypole::Session;
491     return Maypole::Session::generate_unique_id();
492 }
493
494 =head1 NAME
495
496 Maypole - MVC web application framework
497
498 =head1 SYNOPSIS
499
500 See L<Maypole::Application>.
501
502 =head1 DESCRIPTION
503
504 This documents the Maypole request object. See the L<Maypole::Manual>, for a
505 detailed guide to using Maypole.
506
507 Maypole is a Perl web application framework similar to Java's struts. It is 
508 essentially completely abstracted, and so doesn't know anything about
509 how to talk to the outside world.
510
511 To use it, you need to create a package which represents your entire
512 application. In our example above, this is the C<BeerDB> package.
513
514 This needs to first use L<Maypole::Application> which will make your package
515 inherit from the appropriate platform driver such as C<Apache::MVC> or
516 C<CGI::Maypole>, and then call setup.  This sets up the model classes and
517 configures your application. The default model class for Maypole uses
518 L<Class::DBI> to map a database to classes, but this can be changed by altering
519 configuration. (B<Before> calling setup.)
520
521 =head2 CLASS METHODS
522
523 =head3 config
524
525 Returns the L<Maypole::Config> object
526
527 =head3 setup
528
529     My::App->setup($data_source, $user, $password, \%attr);
530
531 Initialise the maypole application and model classes. Your application should
532 call this after setting configuration via L<"config">
533
534 =head3 init
535
536 You should not call this directly, but you may wish to override this to
537 add
538 application-specific initialisation.
539
540 =head3 new
541
542 Constructs a very minimal new Maypole request object.
543
544 =head3 view_object
545
546 Get/set the Maypole::View object
547
548 =head3 debug
549
550     sub My::App::debug {1}
551
552 Returns the debugging flag. Override this in your application class to
553 enable/disable debugging.
554
555 =head2 INSTANCE METHODS
556
557 =head3 parse_location
558
559 Turns the backend request (e.g. Apache::MVC, Maypole, CGI) into a
560 Maypole
561 request. It does this by setting the C<path>, and invoking C<parse_path>
562 and
563 C<parse_args>.
564
565 You should only need to define this method if you are writing a new
566 Maypole
567 backend.
568
569 =head3 path
570
571 Returns the request path
572
573 =head3 parse_path
574
575 Parses the request path and sets the C<args>, C<action> and C<table> 
576 properties
577
578 =head3 table
579
580 The table part of the Maypole request path
581
582 =head3 action
583
584 The action part of the Maypole request path
585
586 =head3 args
587
588 A list of remaining parts of the request path after table and action
589 have been
590 removed
591
592 =head3 headers_in
593
594 A L<Maypole::Headers> object containing HTTP headers for the request
595
596 =head3 headers_out
597
598 A L<HTTP::Headers> object that contains HTTP headers for the output
599
600 =head3 parse_args
601
602 Turns post data and query string paramaters into a hash of C<params>.
603
604 You should only need to define this method if you are writing a new
605 Maypole
606 backend.
607
608 =head3 param
609
610 An accessor for request parameters. It behaves similarly to CGI::param() for
611 accessing CGI parameters.
612
613 =head3 params
614
615 Returns a hash of request parameters. The source of the parameters may vary
616 depending on the Maypole backend, but they are usually populated from request
617 query string and POST data.
618
619 B<Note:> Where muliple values of a parameter were supplied, the
620 C<params> 
621 value
622 will be an array reference.
623
624 =head3 get_template_root
625
626 Implementation-specific path to template root.
627
628 You should only need to define this method if you are writing a new
629 Maypole
630 backend. Otherwise, see L<Maypole::Config/"template_root">
631
632 =head3 get_request
633
634 You should only need to define this method if you are writing a new
635 Maypole backend. It should return something that looks like an Apache
636 or CGI request object, it defaults to blank.
637
638 =head3 default_table_view
639
640 =head3 is_applicable
641
642 Returns a Maypole::Constant to indicate whether the request is valid.
643
644 B<This method is deprecated> as of version 2.11. If you have overridden it,
645 please override C<is_model_applicable> instead, and change the return type
646 from Maypole:Constants to true/false.
647
648 =head3 is_model_applicable
649
650 Returns true or false to indicate whether the request is valid.
651
652 The default implementation checks that C<< $r->table >> is publicly
653 accessible and that the model class is configured to handle the
654 C<< $r->action >>.
655
656 =head3 authenticate
657
658 Returns a Maypole::Constant to indicate whether the user is
659 authenticated for
660 the Maypole request.
661
662 The default implementation returns C<OK>
663
664 =head3 model_class
665
666 Returns the perl package name that will serve as the model for the
667 request. It corresponds to the request C<table> attribute.
668
669 =head3 additional_data
670
671 Called before the model processes the request, this method gives you a
672 chance
673 to do some processing for each request, for example, manipulating
674 C<template_args>.
675
676 =head3 objects
677
678 Get/set a list of model objects. The objects will be accessible in the
679 view
680 templates.
681
682 If the first item in C<$self-E<gt>args> can be C<retrieve()>d by the model
683 class,
684 it will be removed from C<args> and the retrieved object will be added
685 to the
686 C<objects> list. See L<Maypole::Model> for more information.
687
688 =head3 template_args
689
690     $self->template_args->{foo} = 'bar';
691
692 Get/set a hash of template variables.
693
694 =head3 stash
695
696 A place to put custom application data. Not used by Maypole itself. 
697
698 =head3 template
699
700 Get/set the template to be used by the view. By default, it returns
701 C<$self-E<gt>action>
702
703 =head3 exception
704
705 This method is called if any exceptions are raised during the
706 authentication 
707 or
708 model/view processing. It should accept the exception as a parameter and 
709 return
710 a Maypole::Constant to indicate whether the request should continue to
711 be
712 processed.
713
714 =head3 error
715
716 Get/set a request error
717
718 =head3 output
719
720 Get/set the response output. This is usually populated by the view
721 class. You
722 can skip view processing by setting the C<output>.
723
724 =head3 document_encoding
725
726 Get/set the output encoding. Default: utf-8.
727
728 =head3 content_type
729
730 Get/set the output content type. Default: text/html
731
732 =head3 send_output
733
734 Sends the output and additional headers to the user.
735
736 =head3 call_authenticate
737
738 This method first checks if the relevant model class
739 can authenticate the user, or falls back to the default
740 authenticate method of your Maypole application.
741
742
743 =head3 call_exception
744
745 This model is called to catch exceptions, first after authenticate, then after
746 processing the model class, and finally to check for exceptions from the view
747 class.
748
749 This method first checks if the relevant model class
750 can handle exceptions the user, or falls back to the default
751 exception method of your Maypole application.
752
753 =head3 make_random_id
754
755 returns a unique id for this request can be used to prevent or detect repeat
756 submissions.
757
758 =head3 get_protocol
759
760 Returns the protocol the request was made with, i.e. https
761
762 =head3 redirect_request
763
764 Sets output headers to redirect based on the arguments provided
765
766 Accepts either a single argument of the full url to redirect to, or a hash of named parameters :
767
768 $r->redirect_request('http://www.example.com/path');
769
770 or
771
772 $r->redirect_request(protocol=>'https', domain=>'www.example.com', path=>'/path/file?arguments', status=>'302', url=>'..');
773
774 The named parameters are protocol, domain, path, status and url
775
776 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.
777
778 =head3 redirect_internal_request 
779
780 =head3 handler
781
782 This method sets up the class if it's not done yet, sets some
783 defaults and leaves the dirty work to handler_guts.
784
785 =head3 handler_guts
786
787 This is the main request handling method and calls various methods to handle the request/response
788 and defines the workflow within Maypole.
789
790 Currently undocumented and liable to be refactored without warning.
791
792 =head1 SEE ALSO
793
794 There's more documentation, examples, and a information on our mailing lists
795 at the Maypole web site:
796
797 L<http://maypole.perl.org/>
798
799 L<Maypole::Application>, L<Apache::MVC>, L<CGI::Maypole>.
800
801 =head1 AUTHOR
802
803 Maypole is currently maintained by Simon Flack C<simonflk#cpan.org>
804
805 =head1 AUTHOR EMERITUS
806
807 Simon Cozens, C<simon#cpan.org>
808
809 Sebastian Riedel, C<sri#oook.de> maintained Maypole from 1.99_01 to 2.04
810
811 =head1 THANKS TO
812
813 Sebastian Riedel, Danijel Milicevic, Dave Slack, Jesse Sheidlower, Jody Belka,
814 Marcus Ramberg, Mickael Joanne, Randal Schwartz, Simon Flack, Steve Simms,
815 Veljko Vidovic and all the others who've helped.
816
817 =head1 LICENSE
818
819 You may distribute this code under the same terms as Perl itself.
820
821 =cut
822
823 1;