]> git.decadent.org.uk Git - maypole.git/blob - lib/Maypole.pm
Implemented daveh's fixes for is_applicable() -
[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 = $self->is_model_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_model_applicable 
229 {
230     my ($self) = @_;
231     
232     # cater for applications that are using obsolete version
233     if ($self->can('is_applicable')) 
234     {
235         warn "DEPRECATION WARNING: rewrite is_applicable to the interface ".
236                 "of Maypole::is_model_applicable\n";
237         return $self->is_applicable == OK;
238     }
239
240     # Establish which tables should be processed by the model
241     my $config = $self->config;
242     
243     $config->ok_tables || $config->ok_tables( $config->display_tables );
244     
245     $config->ok_tables( { map { $_ => 1 } @{ $config->ok_tables } } )
246         if ref $config->ok_tables eq "ARRAY";
247         
248     my $ok_tables = $config->ok_tables;
249       
250     # Does this request concern a table to be processed by the model?
251     my $table = $self->table;
252     
253     my $ok = 0;
254     
255     if (exists $ok_tables->{$table}) 
256     {
257         $ok = 1;
258     } 
259 # implements tj's default_table_view(), but there's no _default_table_view()
260 # or _have_default_table_view() yet
261 #    else 
262 #    {
263 #        $ok = $self->default_table_view($self->path, $self->args)
264 #            if $self->_have_default_table_view;
265 #    }
266
267     if (not $ok) 
268     {
269         warn "We don't have that table ($table).\n"
270             . "Available tables are: "
271             . join( ",", keys %$ok_tables )
272                 if $self->debug and not $ok_tables->{$table};
273                 
274         return 0;
275     }
276     
277     # Is the action public?
278     my $action = $self->action;
279     return 1 if $self->model_class->is_public($action);
280     
281     warn "The action '$action' is not applicable to the table $table"
282         if $self->debug;
283     
284     return 0;
285 }
286
287 # *only* intended for translating the return code from is_applicable()
288 sub __to_boolean { $_[0] == OK ? 1 : 0 }
289
290 sub call_authenticate 
291 {
292     my ($self) = @_;
293
294     # Check if we have a model class with an authenticate() to delegate to
295     return $self->model_class->authenticate($self) 
296         if $self->model_class and $self->model_class->can('authenticate');
297     
298     # Interface consistency is a Good Thing - 
299     # the invocant and the argument may one day be different things 
300     # (i.e. controller and request), like they are when authenticate() 
301     # is called on a model class (i.e. model and request)
302     return $self->authenticate($self);   
303 }
304
305 sub call_exception 
306 {
307     my ($self, $error) = @_;
308
309     # Check if we have a model class with an exception() to delegate to
310     if ( $self->model_class && $self->model_class->can('exception') )
311     {
312         my $status = $self->model_class->exception( $self, $error );
313         return $status if $status == OK;
314     }
315     
316     return $self->exception($error);
317 }
318
319 sub default_table_view {
320   my ($self,$path,$args) = @_;
321   my $path_is_ok = 0;
322   my $default_table_view = __PACKAGE__->_default_table_view;
323   # (path class action field)
324   my @path = $self->{path} =~ m{([^/]+)/?}g;
325   my $search_value = shift(@path);
326   if ($default_table_view->{path}) {
327     if ($default_table_view->{path} eq $search_value) {
328       $search_value = shift(@path);
329     } else {
330       return 0;
331     }
332   }
333
334   $self->{table} = $default_table_view->{class};
335   $self->{action} = $default_table_view->{action};
336   $self->{args} = [ $search_value,@path ];
337   return $path_is_ok;
338 }
339
340 sub additional_data { }
341
342 sub authenticate { return OK }
343
344 sub exception { return ERROR }
345
346 sub parse_path 
347 {
348     my ($self) = @_;
349     
350     $self->path || $self->path('frontpage');
351     
352     my @pi = grep {length} split '/', $self->path;
353     
354     $self->table(shift @pi);
355     
356     $self->action( shift @pi or 'index' );
357     
358     $self->args(\@pi);
359 }
360
361 # like CGI::param(), but read only 
362 sub param 
363
364     my ($self, $key) = @_;
365     
366     return keys %{$self->params} unless defined $key;
367     
368     return unless exists $self->params->{$key};
369     
370     my $val = $self->params->{$key};
371     
372     return ref $val ? @$val : ($val) if wantarray;
373         
374     return ref $val ? $val->[0] : $val;
375 }
376
377 sub get_template_root {'.'}
378 sub get_request       { }
379
380 sub get_protocol {
381   die "get_protocol is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
382 }
383
384 sub parse_location {
385     die "parse_location is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
386 }
387
388 sub redirect_request {
389   die "parse_location is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
390 }
391
392 sub redirect_internal_request {
393
394 }
395
396 sub send_output {
397     die "send_output is a virtual method. Do not use Maypole directly; use Apache::MVC or similar";
398 }
399
400 # Session and Repeat Submission Handling
401
402 sub make_random_id {
403     use Maypole::Session;
404     return Maypole::Session::generate_unique_id();
405 }
406
407 =head1 NAME
408
409 Maypole - MVC web application framework
410
411 =head1 SYNOPSIS
412
413 See L<Maypole::Application>.
414
415 =head1 DESCRIPTION
416
417 This documents the Maypole request object. See the L<Maypole::Manual>, for a
418 detailed guide to using Maypole.
419
420 Maypole is a Perl web application framework similar to Java's struts. It is 
421 essentially completely abstracted, and so doesn't know anything about
422 how to talk to the outside world.
423
424 To use it, you need to create a package which represents your entire
425 application. In our example above, this is the C<BeerDB> package.
426
427 This needs to first use L<Maypole::Application> which will make your package
428 inherit from the appropriate platform driver such as C<Apache::MVC> or
429 C<CGI::Maypole>, and then call setup.  This sets up the model classes and
430 configures your application. The default model class for Maypole uses
431 L<Class::DBI> to map a database to classes, but this can be changed by altering
432 configuration. (B<Before> calling setup.)
433
434 =head2 CLASS METHODS
435
436 =head3 config
437
438 Returns the L<Maypole::Config> object
439
440 =head3 setup
441
442     My::App->setup($data_source, $user, $password, \%attr);
443
444 Initialise the maypole application and model classes. Your application should
445 call this after setting configuration via L<"config">
446
447 =head3 init
448
449 You should not call this directly, but you may wish to override this to
450 add
451 application-specific initialisation.
452
453 =head3 new
454
455 Constructs a very minimal new Maypole request object.
456
457 =head3 view_object
458
459 Get/set the Maypole::View object
460
461 =head3 debug
462
463     sub My::App::debug {1}
464
465 Returns the debugging flag. Override this in your application class to
466 enable/disable debugging.
467
468 =head2 INSTANCE METHODS
469
470 =head3 parse_location
471
472 Turns the backend request (e.g. Apache::MVC, Maypole, CGI) into a
473 Maypole
474 request. It does this by setting the C<path>, and invoking C<parse_path>
475 and
476 C<parse_args>.
477
478 You should only need to define this method if you are writing a new
479 Maypole
480 backend.
481
482 =head3 path
483
484 Returns the request path
485
486 =head3 parse_path
487
488 Parses the request path and sets the C<args>, C<action> and C<table> 
489 properties
490
491 =head3 table
492
493 The table part of the Maypole request path
494
495 =head3 action
496
497 The action part of the Maypole request path
498
499 =head3 args
500
501 A list of remaining parts of the request path after table and action
502 have been
503 removed
504
505 =head3 headers_in
506
507 A L<Maypole::Headers> object containing HTTP headers for the request
508
509 =head3 headers_out
510
511 A L<HTTP::Headers> object that contains HTTP headers for the output
512
513 =head3 parse_args
514
515 Turns post data and query string paramaters into a hash of C<params>.
516
517 You should only need to define this method if you are writing a new
518 Maypole
519 backend.
520
521 =head3 param
522
523 An accessor for request parameters. It behaves similarly to CGI::param() for
524 accessing CGI parameters.
525
526 =head3 params
527
528 Returns a hash of request parameters. The source of the parameters may vary
529 depending on the Maypole backend, but they are usually populated from request
530 query string and POST data.
531
532 B<Note:> Where muliple values of a parameter were supplied, the
533 C<params> 
534 value
535 will be an array reference.
536
537 =head3 get_template_root
538
539 Implementation-specific path to template root.
540
541 You should only need to define this method if you are writing a new
542 Maypole
543 backend. Otherwise, see L<Maypole::Config/"template_root">
544
545 =head3 get_request
546
547 You should only need to define this method if you are writing a new
548 Maypole backend. It should return something that looks like an Apache
549 or CGI request object, it defaults to blank.
550
551 =head3 default_table_view
552
553 =head3 is_applicable
554
555 Returns a Maypole::Constant to indicate whether the request is valid.
556
557 B<This method is deprecated> as of version 2.11. If you have overridden it,
558 please override C<is_model_applicable> instead, and change the return type
559 from Maypole:Constants to true/false.
560
561 =head3 is_model_applicable
562
563 Returns true or false to indicate whether the request is valid.
564
565 The default implementation checks that C<< $r->table >> is publicly
566 accessible and that the model class is configured to handle the
567 C<< $r->action >>.
568
569 =head3 authenticate
570
571 Returns a Maypole::Constant to indicate whether the user is
572 authenticated for
573 the Maypole request.
574
575 The default implementation returns C<OK>
576
577 =head3 model_class
578
579 Returns the perl package name that will serve as the model for the
580 request. It corresponds to the request C<table> attribute.
581
582 =head3 additional_data
583
584 Called before the model processes the request, this method gives you a
585 chance
586 to do some processing for each request, for example, manipulating
587 C<template_args>.
588
589 =head3 objects
590
591 Get/set a list of model objects. The objects will be accessible in the
592 view
593 templates.
594
595 If the first item in C<$self-E<gt>args> can be C<retrieve()>d by the model
596 class,
597 it will be removed from C<args> and the retrieved object will be added
598 to the
599 C<objects> list. See L<Maypole::Model> for more information.
600
601 =head3 template_args
602
603     $self->template_args->{foo} = 'bar';
604
605 Get/set a hash of template variables.
606
607 =head3 stash
608
609 A place to put custom application data. Not used by Maypole itself. 
610
611 =head3 template
612
613 Get/set the template to be used by the view. By default, it returns
614 C<$self-E<gt>action>
615
616 =head3 exception
617
618 This method is called if any exceptions are raised during the
619 authentication 
620 or
621 model/view processing. It should accept the exception as a parameter and 
622 return
623 a Maypole::Constant to indicate whether the request should continue to
624 be
625 processed.
626
627 =head3 error
628
629 Get/set a request error
630
631 =head3 output
632
633 Get/set the response output. This is usually populated by the view
634 class. You
635 can skip view processing by setting the C<output>.
636
637 =head3 document_encoding
638
639 Get/set the output encoding. Default: utf-8.
640
641 =head3 content_type
642
643 Get/set the output content type. Default: text/html
644
645 =head3 send_output
646
647 Sends the output and additional headers to the user.
648
649 =head3 call_authenticate
650
651 This method first checks if the relevant model class
652 can authenticate the user, or falls back to the default
653 authenticate method of your Maypole application.
654
655
656 =head3 call_exception
657
658 This model is called to catch exceptions, first after authenticate, then after
659 processing the model class, and finally to check for exceptions from the view
660 class.
661
662 This method first checks if the relevant model class
663 can handle exceptions the user, or falls back to the default
664 exception method of your Maypole application.
665
666 =head3 make_random_id
667
668 returns a unique id for this request can be used to prevent or detect repeat
669 submissions.
670
671 =head3 get_protocol
672
673 Returns the protocol the request was made with, i.e. https
674
675 =head3 redirect_request
676
677 Sets output headers to redirect based on the arguments provided
678
679 Accepts either a single argument of the full url to redirect to, or a hash of named parameters :
680
681 $r->redirect_request('http://www.example.com/path');
682
683 or
684
685 $r->redirect_request(protocol=>'https', domain=>'www.example.com', path=>'/path/file?arguments', status=>'302', url=>'..');
686
687 The named parameters are protocol, domain, path, status and url
688
689 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.
690
691 =head3 handler
692
693 This method sets up the class if it's not done yet, sets some
694 defaults and leaves the dirty work to handler_guts.
695
696 =head3 handler_guts
697
698 This is the main request handling method and calls various methods to handle the request/response
699 and defines the workflow within Maypole.
700
701 Currently undocumented and liable to be refactored without warning.
702
703 =head1 SEE ALSO
704
705 There's more documentation, examples, and a information on our mailing lists
706 at the Maypole web site:
707
708 L<http://maypole.perl.org/>
709
710 L<Maypole::Application>, L<Apache::MVC>, L<CGI::Maypole>.
711
712 =head1 AUTHOR
713
714 Maypole is currently maintained by Simon Flack C<simonflk#cpan.org>
715
716 =head1 AUTHOR EMERITUS
717
718 Simon Cozens, C<simon#cpan.org>
719
720 Sebastian Riedel, C<sri#oook.de> maintained Maypole from 1.99_01 to 2.04
721
722 =head1 THANKS TO
723
724 Sebastian Riedel, Danijel Milicevic, Dave Slack, Jesse Sheidlower, Jody Belka,
725 Marcus Ramberg, Mickael Joanne, Randal Schwartz, Simon Flack, Steve Simms,
726 Veljko Vidovic and all the others who've helped.
727
728 =head1 LICENSE
729
730 You may distribute this code under the same terms as Perl itself.
731
732 =cut
733
734 1;