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