]> git.decadent.org.uk Git - maypole.git/blob - lib/Maypole.pm
begin 2.06, fix t/apache_mvc.t, restore Sebastian's name to Maypole.pm
[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.06';
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 param
337
338 An accessor for request parameters. It behaves similarly to CGI::param() for
339 accessing CGI parameters.
340
341 =head3 params
342
343 Returns a hash of request parameters. The source of the parameters may vary
344 depending on the Maypole backend, but they are usually populated from request
345 query string and POST data.
346
347 B<Note:> Where muliple values of a parameter were supplied, the
348 C<params> 
349 value
350 will be an array reference.
351
352 =head3 get_template_root
353
354 Implementation-specific path to template root.
355
356 You should only need to define this method if you are writing a new
357 Maypole
358 backend. Otherwise, see L<Maypole::Config/"template_root">
359
360 =head3 get_request
361
362 You should only need to define this method if you are writing a new
363 Maypole backend. It should return something that looks like an Apache
364 or CGI request object, it defaults to blank.
365
366
367 =head3 is_applicable
368
369 Returns a Maypole::Constant to indicate whether the request is valid.
370
371 The default implementation checks that C<$r-E<gt>table> is publicly
372 accessible
373 and that the model class is configured to handle the C<$r-E<gt>action>
374
375 =head3 authenticate
376
377 Returns a Maypole::Constant to indicate whether the user is
378 authenticated for
379 the Maypole request.
380
381 The default implementation returns C<OK>
382
383 =head3 model_class
384
385 Returns the perl package name that will serve as the model for the
386 request. It corresponds to the request C<table> attribute.
387
388 =head3 additional_data
389
390 Called before the model processes the request, this method gives you a
391 chance
392 to do some processing for each request, for example, manipulating
393 C<template_args>.
394
395 =head3 objects
396
397 Get/set a list of model objects. The objects will be accessible in the
398 view
399 templates.
400
401 If the first item in C<$r-E<gt>args> can be C<retrieve()>d by the model
402 class,
403 it will be removed from C<args> and the retrieved object will be added
404 to the
405 C<objects> list. See L<Maypole::Model> for more information.
406
407 =head3 template_args
408
409     $r->template_args->{foo} = 'bar';
410
411 Get/set a hash of template variables.
412
413 =head3 template
414
415 Get/set the template to be used by the view. By default, it returns
416 C<$r-E<gt>action>
417
418 =head3 exception
419
420 This method is called if any exceptions are raised during the
421 authentication 
422 or
423 model/view processing. It should accept the exception as a parameter and 
424 return
425 a Maypole::Constant to indicate whether the request should continue to
426 be
427 processed.
428
429 =head3 error
430
431 Get/set a request error
432
433 =head3 output
434
435 Get/set the response output. This is usually populated by the view
436 class. You
437 can skip view processing by setting the C<output>.
438
439 =head3 document_encoding
440
441 Get/set the output encoding. Default: utf-8.
442
443 =head3 content_type
444
445 Get/set the output content type. Default: text/html
446
447 =head3 send_output
448
449 Sends the output and additional headers to the user.
450
451 =head3 call_authenticate
452
453 This method first checks if the relevant model class
454 can authenticate the user, or falls back to the default
455 authenticate method of your Maypole application.
456
457
458 =head3 call_exception
459
460 This model is called to catch exceptions, first after authenticate, then after
461 processing the model class, and finally to check for exceptions from the view
462 class.
463
464 This method first checks if the relevant model class
465 can handle exceptions the user, or falls back to the default
466 exception method of your Maypole application.
467
468
469 =head3 handler
470
471 This method sets up the class if it's not done yet, sets some
472 defaults and leaves the dirty work to handler_guts.
473
474 =head3 handler_guts
475
476 This is the core of maypole. You don't want to know.
477
478 =head1 SEE ALSO
479
480 There's more documentation, examples, and a information on our mailing lists
481 at the Maypole web site:
482
483 L<http://maypole.perl.org/>
484
485 L<Maypole::Application>, L<Apache::MVC>, L<CGI::Maypole>.
486
487 =head1 AUTHOR
488
489 Maypole is currently maintained by Simon Flack C<simonflk#cpan.org>
490
491 =head1 AUTHOR EMERITUS
492
493 Simon Cozens, C<simon#cpan.org>
494
495 Sebastian Riedel, C<sri#oook.de> maintained Maypole from 1.8 to 2.04
496
497 =head1 THANKS TO
498
499 Sebastian Riedel, Danijel Milicevic, Dave Slack, Jesse Sheidlower, Jody Belka,
500 Marcus Ramberg, Mickael Joanne, Randal Schwartz, Simon Flack, Steve Simms,
501 Veljko Vidovic and all the others who've helped.
502
503 =head1 LICENSE
504
505 You may distribute this code under the same terms as Perl itself.
506
507 =cut
508
509 1;