]> git.decadent.org.uk Git - maypole.git/blob - lib/Maypole.pm
added missing file
[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_pre2';
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 = $self->{path} =~ m{([^/]+)/?}g;
190     $self->{table}  = shift @pi;
191     $self->{action} = shift @pi;
192     $self->{action} ||= "index";
193     $self->{args}   = \@pi;
194 }
195
196 sub param { # like CGI::param(), but read-only
197     my $r = shift;
198     my ($key) = @_;
199     if (defined $key) {
200         unless (exists $r->{params}{$key}) {
201             return wantarray() ? () : undef;
202         }
203         my $val = $r->{params}{$key};
204         if (wantarray()) {
205             return ref $val ? @$val : $val;
206         } else {
207             return ref $val ? $val->[0] : $val;
208         }
209     } else {
210         return keys %{$r->{params}};
211     }
212 }
213
214 sub get_template_root { "." }
215 sub get_request       { }
216
217 sub parse_location {
218     die "Do not use Maypole directly; use Apache::MVC or similar";
219 }
220
221 sub send_output {
222     die "Do not use Maypole directly; use Apache::MVC or similar";
223 }
224
225 # Session and Repeat Submission Handling
226
227 sub make_random_id {
228     use Maypole::Session;
229     return Maypole::Session::generate_unique_id();
230 }
231
232 =head1 NAME
233
234 Maypole - MVC web application framework
235
236 =head1 SYNOPSIS
237
238 See L<Maypole::Application>.
239
240 =head1 DESCRIPTION
241
242 This documents the Maypole request object. See the L<Maypole::Manual>, for a
243 detailed guide to using Maypole.
244
245 Maypole is a Perl web application framework similar to Java's struts. It is 
246 essentially completely abstracted, and so doesn't know anything about
247 how to talk to the outside world.
248
249 To use it, you need to create a package which represents your entire
250 application. In our example above, this is the C<BeerDB> package.
251
252 This needs to first use L<Maypole::Application> which will make your package
253 inherit from the appropriate platform driver such as C<Apache::MVC> or
254 C<CGI::Maypole>, and then call setup.  This sets up the model classes and
255 configures your application. The default model class for Maypole uses
256 L<Class::DBI> to map a database to classes, but this can be changed by altering
257 configuration. (B<Before> calling setup.)
258
259 =head2 CLASS METHODS
260
261 =head3 config
262
263 Returns the L<Maypole::Config> object
264
265 =head3 setup
266
267     My::App->setup($data_source, $user, $password, \%attr);
268
269 Initialise the maypole application and model classes. Your application should
270 call this after setting configuration via L<"config">
271
272 =head3 init
273
274 You should not call this directly, but you may wish to override this to
275 add
276 application-specific initialisation.
277
278 =head3 view_object
279
280 Get/set the Maypole::View object
281
282 =head3 debug
283
284     sub My::App::debug {1}
285
286 Returns the debugging flag. Override this in your application class to
287 enable/disable debugging.
288
289 =head2 INSTANCE METHODS
290
291 =head3 parse_location
292
293 Turns the backend request (e.g. Apache::MVC, Maypole, CGI) into a
294 Maypole
295 request. It does this by setting the C<path>, and invoking C<parse_path>
296 and
297 C<parse_args>.
298
299 You should only need to define this method if you are writing a new
300 Maypole
301 backend.
302
303 =head3 path
304
305 Returns the request path
306
307 =head3 parse_path
308
309 Parses the request path and sets the C<args>, C<action> and C<table> 
310 properties
311
312 =head3 table
313
314 The table part of the Maypole request path
315
316 =head3 action
317
318 The action part of the Maypole request path
319
320 =head3 args
321
322 A list of remaining parts of the request path after table and action
323 have been
324 removed
325
326 =head3 headers_in
327
328 A L<Maypole::Headers> object containing HTTP headers for the request
329
330 =head3 headers_out
331
332 A L<HTTP::Headers> object that contains HTTP headers for the output
333
334 =head3 parse_args
335
336 Turns post data and query string paramaters into a hash of C<params>.
337
338 You should only need to define this method if you are writing a new
339 Maypole
340 backend.
341
342 =head3 param
343
344 An accessor for request parameters. It behaves similarly to CGI::param() for
345 accessing CGI parameters.
346
347 =head3 params
348
349 Returns a hash of request parameters. The source of the parameters may vary
350 depending on the Maypole backend, but they are usually populated from request
351 query string and POST data.
352
353 B<Note:> Where muliple values of a parameter were supplied, the
354 C<params> 
355 value
356 will be an array reference.
357
358 =head3 get_template_root
359
360 Implementation-specific path to template root.
361
362 You should only need to define this method if you are writing a new
363 Maypole
364 backend. Otherwise, see L<Maypole::Config/"template_root">
365
366 =head3 get_request
367
368 You should only need to define this method if you are writing a new
369 Maypole backend. It should return something that looks like an Apache
370 or CGI request object, it defaults to blank.
371
372
373 =head3 is_applicable
374
375 Returns a Maypole::Constant to indicate whether the request is valid.
376
377 The default implementation checks that C<$r-E<gt>table> is publicly
378 accessible
379 and that the model class is configured to handle the C<$r-E<gt>action>
380
381 =head3 authenticate
382
383 Returns a Maypole::Constant to indicate whether the user is
384 authenticated for
385 the Maypole request.
386
387 The default implementation returns C<OK>
388
389 =head3 model_class
390
391 Returns the perl package name that will serve as the model for the
392 request. It corresponds to the request C<table> attribute.
393
394 =head3 additional_data
395
396 Called before the model processes the request, this method gives you a
397 chance
398 to do some processing for each request, for example, manipulating
399 C<template_args>.
400
401 =head3 objects
402
403 Get/set a list of model objects. The objects will be accessible in the
404 view
405 templates.
406
407 If the first item in C<$r-E<gt>args> can be C<retrieve()>d by the model
408 class,
409 it will be removed from C<args> and the retrieved object will be added
410 to the
411 C<objects> list. See L<Maypole::Model> for more information.
412
413 =head3 template_args
414
415     $r->template_args->{foo} = 'bar';
416
417 Get/set a hash of template variables.
418
419 =head3 template
420
421 Get/set the template to be used by the view. By default, it returns
422 C<$r-E<gt>action>
423
424 =head3 exception
425
426 This method is called if any exceptions are raised during the
427 authentication 
428 or
429 model/view processing. It should accept the exception as a parameter and 
430 return
431 a Maypole::Constant to indicate whether the request should continue to
432 be
433 processed.
434
435 =head3 error
436
437 Get/set a request error
438
439 =head3 output
440
441 Get/set the response output. This is usually populated by the view
442 class. You
443 can skip view processing by setting the C<output>.
444
445 =head3 document_encoding
446
447 Get/set the output encoding. Default: utf-8.
448
449 =head3 content_type
450
451 Get/set the output content type. Default: text/html
452
453 =head3 send_output
454
455 Sends the output and additional headers to the user.
456
457 =head3 call_authenticate
458
459 This method first checks if the relevant model class
460 can authenticate the user, or falls back to the default
461 authenticate method of your Maypole application.
462
463
464 =head3 call_exception
465
466 This model is called to catch exceptions, first after authenticate, then after
467 processing the model class, and finally to check for exceptions from the view
468 class.
469
470 This method first checks if the relevant model class
471 can handle exceptions the user, or falls back to the default
472 exception method of your Maypole application.
473
474 =head3 make_random_id
475
476 returns a unique id for this request can be used to prevent or detect repeat submissions.
477
478 =head3 handler
479
480 This method sets up the class if it's not done yet, sets some
481 defaults and leaves the dirty work to handler_guts.
482
483 =head3 handler_guts
484
485 This is the core of maypole. You don't want to know.
486
487 =head1 SEE ALSO
488
489 There's more documentation, examples, and a information on our mailing lists
490 at the Maypole web site:
491
492 L<http://maypole.perl.org/>
493
494 L<Maypole::Application>, L<Apache::MVC>, L<CGI::Maypole>.
495
496 =head1 AUTHOR
497
498 Maypole is currently maintained by Simon Flack C<simonflk#cpan.org>
499
500 =head1 AUTHOR EMERITUS
501
502 Simon Cozens, C<simon#cpan.org>
503
504 Sebastian Riedel, C<sri#oook.de> maintained Maypole from 1.99_01 to 2.04
505
506 =head1 THANKS TO
507
508 Sebastian Riedel, Danijel Milicevic, Dave Slack, Jesse Sheidlower, Jody Belka,
509 Marcus Ramberg, Mickael Joanne, Randal Schwartz, Simon Flack, Steve Simms,
510 Veljko Vidovic and all the others who've helped.
511
512 =head1 LICENSE
513
514 You may distribute this code under the same terms as Perl itself.
515
516 =cut
517
518 1;