3 Maypole::Manual::Cookbook - Maypole Cookbook
\r
7 Hacks; design patterns; recipes: call it what you like, this chapter is a
\r
8 developing collection of techniques which can be slotted in to Maypole
\r
9 applications to solve common problems or make the development process easier.
\r
11 As Maypole developers, we don't necessarily know the "best practice" for
\r
12 developing Maypole applications ourselves, in the same way that Larry Wall
\r
13 didn't know all about the best Perl programming style as soon as he wrote
\r
14 Perl. These techniques are what we're using at the moment, but they may
\r
15 be refined, modularized, or rendered irrelevant over time. But they've
\r
16 certainly saved us a bunch of hours work.
\r
18 =head2 Frontend hacks
\r
20 These hacks deal with changing the way Maypole relates to the outside world;
\r
21 alternate front-ends to the Apache and CGI interfaces, or subclassing chunks
\r
22 of the front-end modules to alter Maypole's behaviour in particular ways.
\r
24 =head3 Separate model class modules
\r
26 You want to put all the C<BeerDB::Beer> routines in a separate module,
\r
29 package BeerDB::Beer;
\r
30 BeerDB::Beer->has_a(brewery => "BeerDB::Brewery");
\r
31 sub foo :Exported {}
\r
33 And in F<BeerDB.pm>, you put:
\r
39 B<Solution>: It doesn't work because of the timing of the module loading.
\r
40 C<use BeerDB::Beer> will try to set up the C<has_a> relationships
\r
41 at compile time, when the database tables haven't even been set up,
\r
42 since they're set up by
\r
44 BeerDB->setup("...")
\r
46 which does its stuff at runtime. There are two ways around this; you can
\r
47 either move the C<setup> call to compile time, like so:
\r
49 BEGIN { BeerDB->setup("...") }
\r
51 or move the module loading to run-time (my preferred solution):
\r
53 BeerDB->setup("...");
\r
54 BeerDB::Beer->require;
\r
56 =head3 Redirecting to SSL for sensitive information
\r
58 You have a website with forms that people will be entering sensitive information into,
\r
59 such as credit cards or login details. You want to make sure that they aren't sent
\r
60 in plain text but over SSL instead.
\r
64 The solution is a bit tricky for 2 reasons :
\r
66 Firstly -- Many browsers and web clients will change a redirected
\r
67 POST request into a GET request (which displays all that sensitive information in the
\r
68 browser, or access logs and possibly elsewhere) and/or drops the values on the floor.
\r
70 Secondly -- If somebody has sent that sensitive information in plain text already, then
\r
71 sending it again over SSL won't solve the problem.
\r
73 Redirecting a request is actually rather simple :
\r
75 $r->redirect_request('https://www.example.com/path'); # perldoc Maypole for API
\r
77 .. as is checking the protocol :
\r
79 $r->get_protocol(); # returns 'http' or 'https'
\r
81 You should check that the action that generates the form that people will enter
\r
82 the sensitive information into is https and redirect if not.
\r
84 You should also check that no information is lost when redirecting, possibly by
\r
85 storing it in a session and retrieving it later - see Maypole::Plugin::Session
\r
87 =head3 Debugging with the command line
\r
89 You're seeing bizarre problems with Maypole output, and you want to test it in
\r
90 some place outside of the whole Apache/mod_perl/HTTP/Internet/browser circus.
\r
92 B<Solution>: Use the L<Maypole::CLI> module to go directly from a URL to
\r
93 standard output, bypassing Apache and the network altogether.
\r
95 L<Maypole::CLI> is not a standalone front-end, but to allow you to debug your
\r
96 applications without having to change the front-end they use, it temporarily
\r
97 "borgs" an application. If you run it from the command line, you're expected
\r
100 perl -MMaypole::CLI=Application -e1 'http://your.server/path/table/action'
\r
104 perl -MMaypole::CLI=BeerDB -e1 'http://localhost/beerdb/beer/view/1?o2=desc'
\r
106 You can also use the C<Maypole::CLI> module programatically to create
\r
107 test suites for your application. See the Maypole tests themselves or
\r
108 the documentation to C<Maypole::CLI> for examples of this.
\r
110 Don't forget also to turn on debugging output in your application:
\r
115 use Maypole::Application qw(-Debug);
\r
117 =head3 Changing how URLs are parsed
\r
119 You don't like the way Maypole URLs look, and want something that either
\r
120 fits in with the rest of your site or hides the internal workings of the
\r
123 B<Solution>: So far we've been using the C</table/action/id/args> form
\r
124 of a URL as though it was "the Maypole way"; well, there is no Maypole
\r
125 way. Maypole is just a framework and absolutely everything about it is
\r
128 If we want to provide our own URL handling, the method to override in
\r
129 the driver class is C<parse_path>. This is responsible for taking
\r
130 C<$r-E<gt>path> and filling the C<table>, C<action> and C<args> slots
\r
131 of the request object. Normally it does this just by splitting the path
\r
132 on 'C</>' characters, but you can do it any way you want, including
\r
133 getting the information from C<POST> form parameters or session variables.
\r
135 For instance, suppose we want our URLs to be of the form
\r
136 C<ProductDisplay.html?id=123>, we could provide a C<parse_path> method
\r
141 $r->path("ProductList.html") unless $r->path;
\r
142 ($r->path =~ /^(.*?)([A-Z]\w+)\.html/);
\r
145 my %query = $r->ar->args;
\r
146 $self->args([ $query{id} ]);
\r
149 This takes the path, which already has the query parameters stripped off
\r
150 and parsed, and finds the table and action portions of the filename,
\r
151 lower-cases them, and then grabs the C<id> from the query. Later methods
\r
152 will confirm whether or not these tables and actions exist.
\r
154 See the L<iBuySpy Portal|Maypole::Manual::BuySpy> for another
\r
155 example of custom URL processing.
\r
157 =head3 Maypole for mobile devices
\r
159 You want Maypole to use different templates to display on particular
\r
162 B<Solution>: There are several ways to do this, but here's the neatest
\r
163 we've found. Maypole chooses where to get its templates either by
\r
164 looking at the C<template_root> config parameter or, if this is not
\r
165 given, calling the C<get_template_root> method to ask the front-end to
\r
166 try to work it out. We can give the front-end a little bit of help, by
\r
167 putting this method in our driver class:
\r
169 sub get_template_root {
\r
171 my $browser = $r->headers_in->get('User-Agent');
\r
172 if ($browser =~ /mobile|palm|nokia/i) {
\r
173 "/home/myapp/templates/mobile";
\r
175 "/home/myapp/templates/desktop";
\r
179 (Maybe there's a better way to detect a mobile browser, but you get the
\r
182 =head2 Content display hacks
\r
184 These hacks deal primarily with the presentation of data to the user,
\r
185 modifying the F<view> template or changing the way that the results of
\r
186 particular actions are displayed.
\r
190 You need an "action" which doesn't really do anything, but just formats
\r
193 B<Solution>: There are two ways to do this, depending on what precisely
\r
194 you need. If you just need to display a template, C<Apache::Template>
\r
195 style, with no Maypole objects in it, then you don't need to write any
\r
196 code; just create your template, and it will be available in the usual
\r
199 If, on the other hand, you want to display some data, and what you're
\r
200 essentially doing is a variant of the C<view> action, then you need to
\r
201 ensure that you have an exported action, as described in the
\r
202 L<templates and actions|Maypole::Manual::StandardTemplates/"C<view> and C<edit>">
\r
205 sub my_view :Exported { }
\r
207 =head3 Template Switcheroo
\r
209 An action doesn't have any data of its own to display, but needs to display
\r
212 B<Solution>: This is an B<extremely> common hack. You've just issued an
\r
213 action like C<beer/do_edit>, which updates the database. You don't want
\r
214 to display a page that says "Record updated" or similar. Lesser
\r
215 application servers would issue a redirect to have the browser request
\r
216 C</beer/view/I<id>> instead, but we can actually modify the Maypole
\r
217 request on the fly and, after doing the update, pretend that we were
\r
218 going to C</beer/view/I<id>> all along. We do this by setting the
\r
219 objects in the C<objects> slot and changing the C<template> to the
\r
220 one we wanted to go to.
\r
222 In this example from L<Flox|Maypole::Manual::Flox>, we've just
\r
223 performed an C<accept> method on a C<Flox::Invitation> object and we
\r
224 want to go back to viewing a user's page.
\r
226 sub accept :Exported {
\r
227 my ($self, $r) = @_;
\r
228 my $invitation = $r->objects->[0];
\r
229 # [... do stuff to $invitation ...]
\r
230 $r->objects([$r->user]);
\r
231 $r->model_class("Flox::User");
\r
232 $r->template("view");
\r
235 This hack is so common that it's expected that there'll be a neater
\r
236 way of doing this in the future.
\r
240 Here's a hack I've used a number of times. You want to store structured
\r
241 data in a database and to abstract out its display.
\r
243 B<Solution>: You have your data as XML, because handling big chunks of
\r
244 XML is a solved problem. Build your database schema as usual around the
\r
245 important elements that you want to be able to search and browse on. For
\r
246 instance, I have an XML format for songs which has a header section of
\r
247 the key, title and so on, plus another section for the lyrics and
\r
252 <title>Layla</title>
\r
253 <artist>Derek and the Dominos</artist>
\r
259 <line> <sup>A</sup>Lay<sup>Dm</sup>la <sup>Bb</sup> </line>
\r
260 <line> <sup>C</sup>Got me on my <sup>Dm</sup>knees </line>
\r
263 I store the title, artist and key in the database, as well as an "xml"
\r
264 field which contains the whole song as XML.
\r
266 To load the songs into the database, I can C<use> the driver class for
\r
267 my application, since that's a handy way of setting up the database classes
\r
268 we're going to need to use. Then the handy L<XML::TreeBuilder> will handle
\r
269 the XML parsing for us:
\r
272 use XML::TreeBuilder;
\r
273 my $t = XML::TreeBuilder->new;
\r
274 $t->parse_file("songs.xml");
\r
276 for my $song ($t->find("song")) {
\r
277 my ($key) = $song->find("key"); $key &&= $key->as_text;
\r
278 my ($title) = $song->find("title"); $title = $title->as_text;
\r
279 my ($artist) = $song->find("artist"); $artist = $artist->as_text;
\r
280 my ($first_line) = $song->find("line");
\r
281 $first_line = join "", grep { !ref } $first_line->content_list;
\r
282 $first_line =~ s/[,\.\?!]\s*$//;
\r
283 Songbook::Song->find_or_create({
\r
285 first_line => $first_line,
\r
286 song_key => Songbook::SongKey->find_or_create({name => $key}),
\r
287 artist => Songbook::Artist->find_or_create({name => $artist}),
\r
288 xml => $song->as_XML
\r
292 Now we need to set up the custom display for each song; thankfully, with
\r
293 the L<Template::Plugin::XSLT> module, this is as simple as putting the
\r
294 following into F<templates/song/view>:
\r
297 USE transform = XSLT("song.xsl");
\r
298 song.xml | $transform
\r
301 We essentially pipe the XML for the selected song through to an XSL
\r
302 transformation, and this will fill out all the HTML we need. Job done.
\r
304 =head3 Displaying pictures
\r
306 You want to serve a picture, a Word document, or something else which
\r
307 doesn't have a content type of C<text/html>, out of your database.
\r
309 B<Solution>: Fill the content and content-type yourself.
\r
311 Here's a subroutine which displays the C<photo> for either a specified
\r
312 user or the currently logged in user. We set the C<output> slot of the
\r
313 Maypole request object: if this is done then the view class is not called
\r
314 upon to process a template, since we already have some output to display.
\r
315 We also set the C<content_type> using one from the database.
\r
317 sub view_picture :Exported {
\r
318 my ($self, $r) = @_;
\r
319 my $user = $r->objects->[0];
\r
320 $r->content_type($user->photo_type);
\r
321 $r->output($user->photo);
\r
324 Of course, the file doesn't necessarily need to be in the database
\r
325 itself; if your file is stored in the filesystem, but you have a file
\r
326 name or some other pointer in the database, you can still arrange for
\r
327 the data to be fetched and inserted into C<$r-E<gt>output>.
\r
331 You want to provide a programmatic interface to your Maypole site.
\r
333 B<Solution>: The best way to do this is with C<REST>, which uses a
\r
334 descriptive URL to encode the request. For instance, in
\r
335 L<Flox|Maypole::Manual::Flox> we
\r
336 describe a social networking system. One neat thing you can do with
\r
337 social networks is to use them for reputation tracking, and we can use
\r
338 that information for spam detection. So if a message arrives from
\r
339 C<person@someco.com>, we want to know if they're in our network of
\r
340 friends or not and mark the message appropriately. We'll do this by
\r
341 having a web agent (say, L<WWW::Mechanize> or L<LWP::UserAgent>) request
\r
343 C<http://flox.simon-cozens.org/user/relationship_by_email/person%40someco.com>.
\r
344 Naturally, they'll need to present the appropriate cookie just like a
\r
345 normal browser, but that's a solved problem. We're just interested in
\r
348 The request will return a single integer status code: 0 if they're not
\r
349 in the system at all, 1 if they're in the system, and 2 if they're our
\r
352 All we need to do to implement this is provide the C<relationship_by_email>
\r
353 action, and use it to fill in the output in the same way as we did when
\r
354 displaying a picture. Since C<person%40someco.com> is not the ID of a
\r
355 row in the user table, it will appear in the C<args> array:
\r
358 sub relationship_by_email :Exported {
\r
359 my ($self, $r) = @_;
\r
360 my $email = uri_unescape($r->args->[0]);
\r
361 $r->content_type("text/plain");
\r
363 unless (($user) = Flox::User->search(email => $email)) {
\r
364 $r->content("0\n"); return;
\r
367 if ($r->user->is_friend($user)) { $r->contenti("2\n"); return; };
\r
368 $r->content("1\n"); return;
\r
371 =head3 Component-based Pages
\r
373 You're designing something like a portal site which has a number of
\r
374 components, all displaying different bits of information about different
\r
375 objects. You want to include the output of one Maypole request call while
\r
376 building up another.
\r
378 B<Solution>: Use L<Maypole::Plugin::Component>. By inheriting like this:
\r
381 use Maypole::Application qw(Component);
\r
383 you can call the C<component> method on the Maypole request object to
\r
384 make a "sub-request". For instance, if you have a template
\r
386 <DIV class="latestnews">
\r
387 [% request.component("/news/latest_comp") %]
\r
390 <DIV class="links">
\r
391 [% request.component("/links/list_comp") %]
\r
394 then the results of calling the C</news/latest_comp> action and template
\r
395 will be inserted in the C<latestnews> DIV, and the results of calling
\r
396 C</links/list_comp> will be placed in the C<links> DIV. Naturally, you're
\r
397 responsible for exporting actions and creating templates which return
\r
398 fragments of HTML suitable for inserting into the appropriate locations.
\r
400 Alternatively, if you've already got all the objects you need, you can
\r
401 probably just C<[% PROCESS %]> the templates directly.
\r
403 =head3 Bailing out with an error
\r
405 Maypole's error handling sucks. Something really bad has happened to the
\r
406 current request, and you want to stop processing now and tell the user about
\r
409 B<Solution>: Maypole's error handling sucks because you haven't written it
\r
410 yet. Maypole doesn't know what you want to do with an error, so it doesn't
\r
411 guess. One common thing to do is to display a template with an error message
\r
414 Put this in your driver class:
\r
417 my ($r, $message) = @_;
\r
418 $r->template("error");
\r
419 $r->template_args->{error} = $message;
\r
423 And then have a F<custom/error> template like so:
\r
425 [% PROCESS header %]
\r
426 <H2> There was some kind of error... </H2>
\r
428 I'm sorry, something went so badly wrong, we couldn't recover. This
\r
431 <DIV CLASS="messages"> [% error %] </DIV>
\r
433 Now in your actions you can say things like this:
\r
435 if (1 == 0) { return $r->error("Sky fell!") }
\r
437 This essentially uses the template switcheroo hack to always display the
\r
438 error template, while populating the template with an C<error> parameter.
\r
439 Since you C<return $r-E<gt>error>, this will terminate the processing
\r
440 of the current action.
\r
442 The really, really neat thing about this hack is that since C<error>
\r
443 returns C<OK>, you can even use it in your C<authenticate> routine:
\r
446 my ($self, $r) = @_;
\r
448 return $r->error("You do not exist. Go away.")
\r
449 if $r->user and $r->user->status ne "real";
\r
453 This will bail out processing the authentication, the model class, and
\r
454 everything, and just skip to displaying the error message.
\r
456 Non-showstopper errors or other notifications are best handled by tacking a
\r
457 C<messages> template variable onto the request:
\r
459 if ((localtime)[6] == 1) {
\r
460 push @{$r->template_args->{messages}}, "Warning: Today is Monday";
\r
463 Now F<custom/messages> can contain:
\r
466 <DIV class="messages">
\r
468 [% FOR message = messages %]
\r
469 <LI> [% message %] </LI>
\r
475 And you can display messages to your user by adding C<PROCESS messages> at an
\r
476 appropriate point in your template; you may also want to use a template
\r
477 switcheroo to ensure that you're displaying a page that has the messages box in
\r
480 =head2 Authentication and Authorization hacks
\r
482 The next series of hacks deals with providing the concept of a "user" for
\r
483 a site, and what you do with one when you've got one.
\r
487 You need the concept of a "current user".
\r
489 B<Solution>: Use something like
\r
490 L<Maypole::Plugin::Authentication::UserSessionCookie> to authenticate
\r
491 a user against a user class and store a current user object in the
\r
494 C<UserSessionCookie> provides the C<get_user> method which tries to get
\r
495 a user object, either based on the cookie for an already authenticated
\r
496 session, or by comparing C<user> and C<password> form parameters
\r
497 against a C<user> table in the database. Its behaviour is highly
\r
498 customizable and described in its documentation.
\r
500 =head3 Pass-through login
\r
502 You want to intercept a request from a non-logged-in user and have
\r
503 them log in before sending them on their way to wherever they were
\r
504 originally going. Override C<Maypole::authenticate> in your driver
\r
505 class, something like this:
\r
509 use Maypole::Constants; # Otherwise it will silently fail!
\r
512 my ($self, $r) = @_;
\r
514 return OK if $r->user;
\r
515 # Force them to the login page.
\r
516 $r->template("login");
\r
520 This will display the C<login> template, which should look something
\r
523 [% INCLUDE header %]
\r
525 <h2> You need to log in </h2>
\r
527 <DIV class="login">
\r
528 [% IF login_error %]
\r
529 <FONT COLOR="#FF0000"> [% login_error %] </FONT>
\r
531 <FORM ACTION="[% base ; '/' ; request.path %]" METHOD="post">
\r
533 <INPUT TYPE="text" NAME="[% config.auth.user_field || "user" %]"><BR>
\r
534 Password: <INPUT TYPE="password" NAME="password"> <BR>
\r
535 <INPUT TYPE="submit">
\r
538 [% INCLUDE footer %]
\r
540 Notice that this request gets C<POST>ed back to wherever it came from, using
\r
541 C<request.path>. This is because if the user submits correct credentials,
\r
542 C<get_user> will now return a valid user object, and the request will pass
\r
543 through unhindered to the original URL.
\r
547 Now your users are logged in, you want a way of having them log out
\r
548 again and taking the authentication cookie away from them, sending
\r
549 them back to the front page as an unprivileged user.
\r
551 B<Solution>: Just call the C<logout> method of
\r
552 C<Maypole::Plugin::Authentication::UserSessionCookie>. You may also want
\r
553 to use the template switcheroo hack to send them back to the frontpage.
\r
555 =head3 Multi-level Authorization
\r
557 You have both a global site access policy (for instance, requiring a
\r
558 user to be logged in except for certain pages) and a policy for
\r
559 particular tables. (Only allowing an admin to delete records in some
\r
560 tables, say, or not wanting people to get at the default set of methods
\r
561 provided by the model class.)
\r
563 You don't know whether to override the global C<authenticate> method or
\r
564 provide one for each class.
\r
566 B<Solution>: Do both.
\r
567 Maypole checks whether there is an C<authenticate> method for the model
\r
568 class (e.g. BeerDB::Beer) and if so calls that. If there's no such
\r
569 method, it calls the default global C<authenticate> method in C<Maypole>,
\r
570 which always succeeds. You can override the global method as we saw
\r
571 above, and you can provide methods in the model classes.
\r
573 To use per-table access control you can just add methods to your model
\r
574 subclasses that specify individual policies, perhaps like this:
\r
576 sub authenticate { # Ensure we can only create, reject or accept
\r
577 my ($self, $r) = @_;
\r
578 return OK if $r->action =~ /^(issue|accept|reject|do_edit)$/;
\r
579 return; # fail if any other action
\r
582 If you define a method like this, the global C<authenticate> method will
\r
583 not be called, so if you want it to be called you need to do so
\r
586 sub authenticate { # Ensure we can only create, reject or accept
\r
587 my ($self, $r) = @_;
\r
588 return unless $r->authenticate($r) == OK; # fail if not logged in
\r
589 # now it's safe to use $r->user
\r
590 return OK if $r->action =~ /^(accept|reject)$/
\r
591 or ($r->user eq 'fred' and $r->action =~ /^(issue|do_edit)$/);
\r
592 return; # fail if any other action
\r
595 =head2 Creating and editing hacks
\r
597 These hacks particularly deal with issues related to the C<do_edit>
\r
600 =head3 Limiting data for display
\r
602 You want the user to be able to type in some text that you're later
\r
603 going to display on the site, but you don't want them to stick images in
\r
604 it, launch cross-site scripting attacks or otherwise insert messy HTML.
\r
606 B<Solution>: Use the L<CGI::Untaint::html> module to sanitize the HTML
\r
607 on input. C<CGI::Untaint::html> uses L<HTML::Sanitizer> to ensure that
\r
608 tags are properly closed and can restrict the use of certain tags and
\r
609 attributes to a pre-defined list.
\r
613 App::Table->untaint_columns(
\r
614 text => [qw/name description/]
\r
619 App::Table->untaint_columns(
\r
620 html => [qw/name description/]
\r
623 And incoming HTML will be checked and cleaned before it is written to
\r
626 =head3 Getting data from external sources
\r
628 You want to supplement the data received from a form with additional
\r
629 data from another source.
\r
631 B<Solution>: Munge the contents of C< $r-E<gt>params > before jumping
\r
632 to the original C<do_edit> routine. For instance, in this method,
\r
633 we use a L<Net::Amazon> object to fill in some fields of a database row
\r
637 my $amazon = Net::Amazon->new(token => 'YOUR_AMZN_TOKEN');
\r
641 sub create_from_isbn :Exported {
\r
642 my ($self, $r) = @_;
\r
643 my $book_info = $amazon->search(asin => $r->params->{isbn})->properties;
\r
645 # Rewrite the CGI parameters with the ones from Amazon
\r
646 $r->params->{title} = $book_info->title;
\r
647 $r->params->{publisher} = $book_info->publisher;
\r
648 $r->params->{year} = $book_info->year;
\r
649 $r->params->{author} = join('and', $book_info->authors());
\r
651 # And jump to the usual edit/create routine
\r
652 $self->do_edit($r);
\r
655 The request will carry on as though it were a normal C<do_edit> POST, but
\r
656 with the additional fields we have provided.
\r
657 You might also want to add a template switcheroo so the user can verify
\r
658 the details you imported.
\r
660 =head3 Catching errors in a form
\r
662 A user has submitted erroneous input to an edit/create form. You want to
\r
663 send him back to the form with errors displayed against the erroneous
\r
664 fields, but have the other fields maintain the values that the user
\r
667 B<Solution>: This is basically what the default C<edit> template and
\r
668 C<do_edit> method conspire to do, but it's worth highlighting again how
\r
671 If there are any errors, these are placed in a hash, with each error
\r
672 keyed to the erroneous field. The hash is put into the template as
\r
673 C<errors>, and we process the same F<edit> template again:
\r
675 $r->template_args->{errors} = \%errors;
\r
676 $r->template("edit");
\r
678 This throws us back to the form, and so the form's template should take
\r
679 note of the errors, like so:
\r
681 FOR col = classmetadata.columns;
\r
682 NEXT IF col == "id";
\r
684 "<B>"; classmetadata.colnames.$col; "</B>";
\r
686 item.to_field(col).as_HTML;
\r
689 "<FONT COLOR=\"#ff0000\">"; errors.$col; "</FONT>";
\r
693 If we're designing our own templates, instead of using generic ones, we
\r
694 can make this process a lot simpler. For instance:
\r
697 First name: <INPUT TYPE="text" NAME="forename">
\r
700 Last name: <INPUT TYPE="text" NAME="surname">
\r
703 [% IF errors.forename OR errors.surname %]
\r
705 <TD><SPAN class="error">[% errors.forename %]</SPAN> </TD>
\r
706 <TD><SPAN class="error">[% errors.surname %]</SPAN> </TD>
\r
710 The next thing we want to do is to put the originally-submitted values
\r
711 back into the form. We can do this relatively easily because Maypole
\r
712 passes the Maypole request object to the form, and the POST parameters
\r
713 are going to be stored in a hash as C<request.params>. Hence:
\r
716 First name: <INPUT TYPE="text" NAME="forename"
\r
717 VALUE="[%request.params.forename%]">
\r
720 Last name: <INPUT TYPE="text" NAME="surname"
\r
721 VALUE="[%request.params.surname%]">
\r
724 Finally, we might want to only re-fill a field if it is not erroneous, so
\r
725 that we don't get the same bad input resubmitted. This is easy enough:
\r
728 First name: <INPUT TYPE="text" NAME="forename"
\r
729 VALUE="[%request.params.forename UNLESS errors.forename%]">
\r
732 Last name: <INPUT TYPE="text" NAME="surname"
\r
733 VALUE="[%request.params.surname UNLESS errors.surname%]">
\r
736 =head3 Uploading files and other data
\r
738 You want the user to be able to upload files to store in the database.
\r
740 B<Solution>: It's messy.
\r
742 First, we set up an upload form, in an ordinary dummy action. Here's
\r
745 sub upload_picture : Exported {}
\r
747 And here's the F<custom/upload_picture> template:
\r
749 <FORM action="/user/do_upload" enctype="multipart/form-data" method="POST">
\r
751 <P> Please provide a picture in JPEG, PNG or GIF format:
\r
753 <INPUT TYPE="file" NAME="picture">
\r
755 <INPUT TYPE="submit">
\r
758 (Although you'll probably want a bit more HTML around it than that.)
\r
760 Now we need to write the C<do_upload> action. At this point we have to get a
\r
761 little friendly with the front-end system. If we're using L<Apache::Request>,
\r
762 then the C<upload> method of the C<Apache::Request> object (which
\r
763 L<Apache::MVC> helpfully stores in C<$r-E<gt>{ar}>) will work for us:
\r
765 sub do_upload :Exported {
\r
766 my ($class, $r) = @_;
\r
767 my $user = $r->user;
\r
768 my $upload = $r->ar->upload("picture");
\r
770 This returns a L<Apache::Upload> object, which we can query for its
\r
771 content type and a file handle from which we can read the data. It's
\r
772 also worth checking the image isn't going to be too massive before we
\r
773 try reading it and running out of memory, and that the content type is
\r
774 something we're prepared to deal with.
\r
777 my $ct = $upload->info("Content-type");
\r
778 return $r->error("Unknown image file type $ct")
\r
779 if $ct !~ m{image/(jpeg|gif|png)};
\r
780 return $r->error("File too big! Maximum size is ".MAX_IMAGE_SIZE)
\r
781 if $upload->size > MAX_IMAGE_SIZE;
\r
783 my $fh = $upload->fh;
\r
784 my $image = do { local $/; <$fh> };
\r
786 Don't forget C<binmode()> in there if you're on a platform that needs it.
\r
787 Now we can store the content type and data into our database, store it
\r
788 into a file, or whatever:
\r
790 $r->user->photo_type($ct);
\r
791 $r->user->photo($image);
\r
794 And finally, we use our familiar template switcheroo hack to get back to
\r
797 $r->objects([ $user ]);
\r
798 $r->template("view");
\r
801 Now, as we've mentioned, this only works because we're getting familiar with
\r
802 C<Apache::Request> and its C<Apache::Upload> objects. If we're using
\r
803 L<CGI::Maypole> instead, we can write the action in a similar style:
\r
805 sub do_upload :Exported {
\r
806 my ($class, $r) = @_;
\r
807 my $user = $r->user;
\r
809 if ($cgi->upload == 1) { # if there was one file uploaded
\r
810 my $filename = $cgi->param('picture');
\r
811 my $ct = $cgi->upload_info($filename, 'mime');
\r
812 return $r->error("Unknown image file type $ct")
\r
813 if $ct !~ m{image/(jpeg|gif|png)};
\r
814 return $r->error("File too big! Maximum size is ".MAX_IMAGE_SIZE)
\r
815 if $cgi->upload_info($filename, 'size') > MAX_IMAGE_SIZE;
\r
816 my $fh = $cgi->upload($filename);
\r
817 my $image = do { local $/; <$fh> };
\r
818 $r->user->photo_type($ct);
\r
819 $r->user->photo($image);
\r
822 $r->objects([ $user ]);
\r
823 $r->template("view");
\r
826 It's easy to adapt this to upload multiple files if desired.
\r
827 You will also need to enable uploads in your driver initialization,
\r
828 with the slightly confusing statement:
\r
830 $CGI::Simple::DISABLE_UPLOADS = 0; # enable uploads
\r
832 Combine with the "Displaying pictures" hack above for a happy time.
\r
836 L<Contents|Maypole::Manual>,
\r
837 Next L<Flox|Maypole::Manual::Flox>,
\r
838 Previous L<The Beer Database, Twice|Maypole::Manual::Beer>
\r