]> git.decadent.org.uk Git - maypole.git/blob - lib/Maypole.pm
Moved doc/*.pod to lib/Maypole/Manual/ and added new Maypole.pm
[maypole.git] / lib / Maypole.pm
1 package Maypole;
2 use base qw(Class::Accessor::Fast Class::Data::Inheritable);
3 use attributes ();
4 use UNIVERSAL::require;
5 use strict;
6 use warnings;
7 use Maypole::Config;
8 our $VERSION = '2.0';
9 __PACKAGE__->mk_classdata($_) for qw( config init_done view_object );
10 __PACKAGE__->mk_accessors(
11     qw( ar params query objects model_class template_args output path
12       args action template error document_encoding content_type table)
13 );
14 __PACKAGE__->config( Maypole::Config->new() );
15 __PACKAGE__->init_done(0);
16 use Maypole::Constants;
17
18 sub debug { 0 }
19
20 sub setup {
21     my $calling_class = shift;
22     $calling_class = ref $calling_class if ref $calling_class;
23     {
24         no strict 'refs';
25
26         # Naughty.
27         *{ $calling_class . "::handler" } =
28           sub { Maypole::handler( $calling_class, @_ ) };
29     }
30     my $config = $calling_class->config;
31     $config->model || $config->model("Maypole::Model::CDBI");
32     $config->model->require;
33     die "Couldn't load the model class $config->model: $@" if $@;
34     $config->model->setup_database( $config, $calling_class, @_ );
35     for my $subclass ( @{ $config->classes } ) {
36         no strict 'refs';
37         unshift @{ $subclass . "::ISA" }, $config->model;
38         $config->model->adopt($subclass)
39           if $config->model->can("adopt");
40     }
41 }
42
43 sub init {
44     my $class  = shift;
45     my $config = $class->config;
46     $config->view || $config->view("Maypole::View::TT");
47     $config->view->require;
48     die "Couldn't load the view class " . $config->view . ": $@" if $@;
49     $config->display_tables
50       || $config->display_tables( [ $class->config->tables ] );
51     $class->view_object( $class->config->view->new );
52     $class->init_done(1);
53
54 }
55
56 sub handler {
57
58     # See Maypole::Workflow before trying to understand this.
59     my ( $class, $req ) = @_;
60     $class->init unless $class->init_done;
61     my $r = bless { config => $class->config }, $class;
62     $r->get_request($req);
63     $r->parse_location();
64     my $status = $r->handler_guts();
65     return $status unless $status == OK;
66     $r->send_output;
67     return $status;
68 }
69
70 # The root of all evil
71 sub handler_guts {
72     my $r = shift;
73     $r->model_class( $r->config->model->class_of( $r, $r->{table} ) );
74     my $applicable = $r->is_applicable;
75     unless ( $applicable == OK ) {
76
77         # It's just a plain template
78         delete $r->{model_class};
79         $r->{path} =~ s{/$}{};    # De-absolutify
80         $r->template( $r->{path} );
81     }
82
83     # We authenticate every request, needed for proper session management
84     my $status;
85     eval { $status = $r->call_authenticate };
86     if ( my $error = $@ ) {
87         $status = $r->call_exception($error);
88         if ( $status != OK ) {
89             warn "caught authenticate error: $error";
90             return $r->debug ? $r->view_object->error( $r, $error ) : ERROR;
91         }
92     }
93     if ( $r->debug and $status != OK and $status != DECLINED ) {
94         $r->view_object->error( $r,
95             "Got unexpected status $status from calling authentication" );
96     }
97     return $status unless $status == OK;
98
99     # We run additional_data for every request
100     $r->additional_data;
101     if ( $applicable == OK ) {
102         eval { $r->model_class->process($r) };
103         if ( my $error = $@ ) {
104             $status = $r->call_exception($error);
105             if ( $status != OK ) {
106                 warn "caught model error: $error";
107                 return $r->debug ? $r->view_object->error( $r, $error ) : ERROR;
108             }
109         }
110     }
111     if ( !$r->{output} ) {    # You might want to do it yourself
112         eval { $status = $r->view_object->process($r) };
113         if ( my $error = $@ ) {
114             $status = $r->call_exception($error);
115             if ( $status != OK ) {
116                 warn "caught view error: $error" if $r->debug;
117                 return $r->debug ? $r->view_object->error( $r, $error ) : ERROR;
118             }
119         }
120         return $status;
121     }
122     else { return OK; }
123 }
124
125 sub is_applicable {
126     my $self   = shift;
127     my $config = $self->config;
128     $config->ok_tables || $config->ok_tables( $config->display_tables );
129     $config->ok_tables( { map { $_ => 1 } @{ $config->ok_tables } } )
130       if ref $config->ok_tables eq "ARRAY";
131     warn "We don't have that table ($self->{table})"
132       if $self->debug
133       and not $config->ok_tables->{ $self->{table} };
134     return DECLINED() unless exists $config->ok_tables->{ $self->{table} };
135
136     # Does the action method exist?
137     my $cv = $self->model_class->can( $self->{action} );
138     warn "We don't have that action ($self->{action})"
139       if $self->debug and not $cv;
140     return DECLINED() unless $cv;
141
142     # Is it exported?
143     $self->{method_attribs} = join " ", attributes::get($cv);
144     do {
145         warn "$self->{action} not exported" if $self->debug;
146         return DECLINED();
147     } unless $self->{method_attribs} =~ /\bExported\b/i;
148     return OK();
149 }
150
151 sub call_authenticate {
152     my $self = shift;
153
154     # Check if we have a model class
155     if ( $self->{model_class} ) {
156         return $self->model_class->authenticate($self)
157           if $self->model_class->can("authenticate");
158     }
159     return $self->authenticate($self);   # Interface consistency is a Good Thing
160 }
161
162 sub call_exception {
163     my $self = shift;
164     my ($error) = @_;
165
166     # Check if we have a model class
167     if (   $self->{model_class}
168         && $self->model_class->can('exception') )
169     {
170         my $status = $self->model_class->exception( $self, $error );
171         return $status if $status == OK;
172     }
173     return $self->exception($error);
174 }
175
176 sub additional_data { }
177
178 sub authenticate { return OK }
179
180 sub exception { return ERROR }
181
182 sub parse_path {
183     my $self = shift;
184     $self->{path} ||= "frontpage";
185     my @pi = split /\//, $self->{path};
186     shift @pi while @pi and !$pi[0];
187     $self->{table}  = shift @pi;
188     $self->{action} = shift @pi;
189     $self->{args}   = \@pi;
190 }
191
192 sub get_template_root { "." }
193 sub get_request       { }
194
195 sub parse_location {
196     die "Do not use Maypole directly; use Apache::MVC or similar";
197 }
198
199 sub send_output {
200     die "Do not use Maypole directly; use Apache::MVC or similar";
201 }
202
203 =head1 NAME
204
205 Maypole - MVC web application framework
206
207 =head1 SYNOPSIS
208
209 See L<Maypole::Application>.
210
211 =head1 DESCRIPTION
212
213 This documents the Maypole request object. For user documentation, see
214 L<Maypole::Tutorial>.
215
216 =head2 CLASS METHODS
217
218 =head3 config
219
220 Returns the L<Maypole::Config> object
221
222 =head3 setup
223
224     My::App->setup();
225
226     Initialise the maypole application and model classes. Your
227     application should
228     call this after setting configuration via L<"config">
229
230 =head3 init
231
232 You should not call this directly, but you may wish to override this to
233 add
234 application-specific initialisation.
235
236 =head3 view_object
237
238 Get/set the Maypole::View object
239
240 =head3 debug
241
242     sub My::App::debug {1}
243
244     Returns the debugging flag. Override this in your application class
245     to
246     enable/disable debugging.
247
248 =head2 INSTANCE METHODS
249
250 =head3 parse_location
251
252 Turns the backend request (e.g. Apache::MVC, Maypole, CGI) into a
253 Maypole
254 request. It does this by setting the C<path>, and invoking C<parse_path>
255 and
256 C<parse_args>.
257
258 You should only need to define this method if you are writing a new
259 Maypole
260 backend.
261
262 =head3 path
263
264 Returns the request path
265
266 =head3 parse_path
267
268 Parses the request path and sets the C<args>, C<action> and C<table> 
269 properties
270
271 =head3 table
272
273 The table part of the Maypole request path
274
275 =head3 action
276
277 The action part of the Maypole request path
278
279 =head3 args
280
281 A list of remaining parts of the request path after table and action
282 have been
283 removed
284
285 =head3 parse_args
286
287 Turns post data and query string paramaters into a hash of C<params>.
288
289 You should only need to define this method if you are writing a new
290 Maypole
291 backend.
292
293 =head3 params
294
295 Returns a hash of request parameters. The source of the parameters may
296 vary
297 depending on the Maypole backend, but they are usually populated from
298 request
299 query string and POST data.
300
301 B<Note:> Where muliple values of a parameter were supplied, the
302 C<params> 
303 value
304 will be an array reference.
305
306 =head3 get_template_root
307
308 Implimentation-specific path to template root.
309
310 You should only need to define this method if you are writing a new
311 Maypole
312 backend. Otherwise, see L<Maypole::Config/"template_root">
313
314 =head3 is_applicable
315
316 Returns a Maypole::Constant to indicate whether the request is valid.
317
318 The default implimentation checks that C<$r-E<gt>table> is publicly
319 accessible
320 and that the model class is configured to handle the C<$r-E<gt>action>
321
322 =head3 authenticate
323
324 Returns a Maypole::Constant to indicate whether the user is
325 authenticated for
326 the Maypole request.
327
328 The default implimentation returns C<OK>
329
330 =head3 model_class
331
332 Returns the perl package name that will serve as the model for the
333 request. It corresponds to the request C<table> attribute.
334
335 =head3 additional_data
336
337 Called before the model processes the request, this method gives you a
338 chance
339 to do some processing for each request, for example, manipulating
340 C<template_args>.
341
342 =head3 objects
343
344 Get/set a list of model objects. The objects will be accessible in the
345 view
346 templates.
347
348 If the first item in C<$r-E<gt>args> can be C<retrieve()>d by the model
349 class,
350 it will be removed from C<args> and the retrieved object will be added
351 to the
352 C<objects> list. See L<Maypole::Model> for more information.
353
354 =head3 template_args
355
356     $r->template_args->{foo} = 'bar';
357
358     Get/set a hash of template variables.
359
360 =head3 template
361
362 Get/set the template to be used by the view. By default, it returns
363 C<$r-E<gt>action>
364
365 =head3 exception
366
367 This method is called if any exceptions are raised during the
368 authentication 
369 or
370 model/view processing. It should accept the exception as a parameter and 
371 return
372 a Maypole::Constant to indicate whether the request should continue to
373 be
374 processed.
375
376 =head3 error
377
378 Get/set a request error
379
380 =head3 output
381
382 Get/set the response output. This is usually populated by the view
383 class. You
384 can skip view processing by setting the C<output>.
385
386 =head3 document_encoding
387
388 Get/set the output encoding. Default: utf-8.
389
390 =head3 content_type
391
392 Get/set the output content type. Default: text/html
393
394 =head3 send_output
395
396 Sends the output and additional headers to the user.
397
398 =head1 SEE ALSO
399
400 There's more documentation, examples, and a wiki at the Maypole web
401 site:
402
403 http://maypole.perl.org/
404
405 L<Maypole::Application>,L<Apache::MVC>, L<CGI::Maypole>.
406
407 =head1 MAINTAINER
408
409 Sebastian Riedel, c<sri@oook.de>
410
411 =head1 AUTHOR
412
413 Simon Cozens, C<simon@cpan.org>
414
415 =head1 THANK YOU
416
417 Danijel Milicevic, Jesse Scheidlower, Jody Belka, Marcus Ramberg,
418 Mickael Joanne, Simon Flack, Veljko Vidovic and all the others who've
419 helped.
420
421 =head1 LICENSE
422
423 You may distribute this code under the same terms as Perl itself.
424
425 =cut
426
427 1;