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