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