]> git.decadent.org.uk Git - maypole.git/blob - lib/Maypole.pm
merged in TEEJAY Changes with current head
[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 )
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->display_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 send_output {
381     die "send_output is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
382 }
383
384 # Session and Repeat Submission Handling
385
386 sub make_random_id {
387     use Maypole::Session;
388     return Maypole::Session::generate_unique_id();
389 }
390
391 =head1 NAME
392
393 Maypole - MVC web application framework
394
395 =head1 SYNOPSIS
396
397 See L<Maypole::Application>.
398
399 =head1 DESCRIPTION
400
401 This documents the Maypole request object. See the L<Maypole::Manual>, for a
402 detailed guide to using Maypole.
403
404 Maypole is a Perl web application framework similar to Java's struts. It is 
405 essentially completely abstracted, and so doesn't know anything about
406 how to talk to the outside world.
407
408 To use it, you need to create a package which represents your entire
409 application. In our example above, this is the C<BeerDB> package.
410
411 This needs to first use L<Maypole::Application> which will make your package
412 inherit from the appropriate platform driver such as C<Apache::MVC> or
413 C<CGI::Maypole>, and then call setup.  This sets up the model classes and
414 configures your application. The default model class for Maypole uses
415 L<Class::DBI> to map a database to classes, but this can be changed by altering
416 configuration. (B<Before> calling setup.)
417
418 =head2 CLASS METHODS
419
420 =head3 config
421
422 Returns the L<Maypole::Config> object
423
424 =head3 setup
425
426     My::App->setup($data_source, $user, $password, \%attr);
427
428 Initialise the maypole application and model classes. Your application should
429 call this after setting configuration via L<"config">
430
431 =head3 init
432
433 You should not call this directly, but you may wish to override this to
434 add
435 application-specific initialisation.
436
437 =head3 new
438
439 Constructs a very minimal new Maypole request object.
440
441 =head3 view_object
442
443 Get/set the Maypole::View object
444
445 =head3 debug
446
447     sub My::App::debug {1}
448
449 Returns the debugging flag. Override this in your application class to
450 enable/disable debugging.
451
452 =head2 INSTANCE METHODS
453
454 =head3 parse_location
455
456 Turns the backend request (e.g. Apache::MVC, Maypole, CGI) into a
457 Maypole
458 request. It does this by setting the C<path>, and invoking C<parse_path>
459 and
460 C<parse_args>.
461
462 You should only need to define this method if you are writing a new
463 Maypole
464 backend.
465
466 =head3 path
467
468 Returns the request path
469
470 =head3 parse_path
471
472 Parses the request path and sets the C<args>, C<action> and C<table> 
473 properties
474
475 =head3 table
476
477 The table part of the Maypole request path
478
479 =head3 action
480
481 The action part of the Maypole request path
482
483 =head3 args
484
485 A list of remaining parts of the request path after table and action
486 have been
487 removed
488
489 =head3 headers_in
490
491 A L<Maypole::Headers> object containing HTTP headers for the request
492
493 =head3 headers_out
494
495 A L<HTTP::Headers> object that contains HTTP headers for the output
496
497 =head3 parse_args
498
499 Turns post data and query string paramaters into a hash of C<params>.
500
501 You should only need to define this method if you are writing a new
502 Maypole
503 backend.
504
505 =head3 param
506
507 An accessor for request parameters. It behaves similarly to CGI::param() for
508 accessing CGI parameters.
509
510 =head3 params
511
512 Returns a hash of request parameters. The source of the parameters may vary
513 depending on the Maypole backend, but they are usually populated from request
514 query string and POST data.
515
516 B<Note:> Where muliple values of a parameter were supplied, the
517 C<params> 
518 value
519 will be an array reference.
520
521 =head3 get_template_root
522
523 Implementation-specific path to template root.
524
525 You should only need to define this method if you are writing a new
526 Maypole
527 backend. Otherwise, see L<Maypole::Config/"template_root">
528
529 =head3 get_request
530
531 You should only need to define this method if you are writing a new
532 Maypole backend. It should return something that looks like an Apache
533 or CGI request object, it defaults to blank.
534
535
536 =head3 is_applicable
537
538 Returns a Maypole::Constant to indicate whether the request is valid.
539
540 The default implementation checks that C<$self-E<gt>table> is publicly
541 accessible
542 and that the model class is configured to handle the C<$self-E<gt>action>
543
544 =head3 authenticate
545
546 Returns a Maypole::Constant to indicate whether the user is
547 authenticated for
548 the Maypole request.
549
550 The default implementation returns C<OK>
551
552 =head3 model_class
553
554 Returns the perl package name that will serve as the model for the
555 request. It corresponds to the request C<table> attribute.
556
557 =head3 additional_data
558
559 Called before the model processes the request, this method gives you a
560 chance
561 to do some processing for each request, for example, manipulating
562 C<template_args>.
563
564 =head3 objects
565
566 Get/set a list of model objects. The objects will be accessible in the
567 view
568 templates.
569
570 If the first item in C<$self-E<gt>args> can be C<retrieve()>d by the model
571 class,
572 it will be removed from C<args> and the retrieved object will be added
573 to the
574 C<objects> list. See L<Maypole::Model> for more information.
575
576 =head3 template_args
577
578     $self->template_args->{foo} = 'bar';
579
580 Get/set a hash of template variables.
581
582 =head3 template
583
584 Get/set the template to be used by the view. By default, it returns
585 C<$self-E<gt>action>
586
587 =head3 exception
588
589 This method is called if any exceptions are raised during the
590 authentication 
591 or
592 model/view processing. It should accept the exception as a parameter and 
593 return
594 a Maypole::Constant to indicate whether the request should continue to
595 be
596 processed.
597
598 =head3 error
599
600 Get/set a request error
601
602 =head3 output
603
604 Get/set the response output. This is usually populated by the view
605 class. You
606 can skip view processing by setting the C<output>.
607
608 =head3 document_encoding
609
610 Get/set the output encoding. Default: utf-8.
611
612 =head3 content_type
613
614 Get/set the output content type. Default: text/html
615
616 =head3 send_output
617
618 Sends the output and additional headers to the user.
619
620 =head3 call_authenticate
621
622 This method first checks if the relevant model class
623 can authenticate the user, or falls back to the default
624 authenticate method of your Maypole application.
625
626
627 =head3 call_exception
628
629 This model is called to catch exceptions, first after authenticate, then after
630 processing the model class, and finally to check for exceptions from the view
631 class.
632
633 This method first checks if the relevant model class
634 can handle exceptions the user, or falls back to the default
635 exception method of your Maypole application.
636
637 =head3 make_random_id
638
639 returns a unique id for this request can be used to prevent or detect repeat
640 submissions.
641
642 =head3 get_protocol
643
644 Returns the protocol the request was made with, i.e. https
645
646 =head3 redirect_request
647
648 Sets output headers to redirect based on the arguments provided
649
650 Accepts either a single argument of the full url to redirect to, or a hash of named parameters :
651
652 $r->redirect_request('http://www.example.com/path');
653
654 or
655
656 $r->redirect_request(protocol=>'https', domain=>'www.example.com', path=>'/path/file?arguments', status=>'302', url=>'..');
657
658 The named parameters are protocol, domain, path, status and url
659
660 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.
661
662 =head3 handler
663
664 This method sets up the class if it's not done yet, sets some
665 defaults and leaves the dirty work to handler_guts.
666
667 =head3 handler_guts
668
669 This is the main request handling method and calls various methods to handle the request/response
670 and defines the workflow within Maypole.
671
672 Currently undocumented and liable to be refactored without warning.
673
674 =head1 SEE ALSO
675
676 There's more documentation, examples, and a information on our mailing lists
677 at the Maypole web site:
678
679 L<http://maypole.perl.org/>
680
681 L<Maypole::Application>, L<Apache::MVC>, L<CGI::Maypole>.
682
683 =head1 AUTHOR
684
685 Maypole is currently maintained by Simon Flack C<simonflk#cpan.org>
686
687 =head1 AUTHOR EMERITUS
688
689 Simon Cozens, C<simon#cpan.org>
690
691 Sebastian Riedel, C<sri#oook.de> maintained Maypole from 1.99_01 to 2.04
692
693 =head1 THANKS TO
694
695 Sebastian Riedel, Danijel Milicevic, Dave Slack, Jesse Sheidlower, Jody Belka,
696 Marcus Ramberg, Mickael Joanne, Randal Schwartz, Simon Flack, Steve Simms,
697 Veljko Vidovic and all the others who've helped.
698
699 =head1 LICENSE
700
701 You may distribute this code under the same terms as Perl itself.
702
703 =cut
704
705 1;