+=head1 NAME\r
+\r
+Maypole::Manual::Cookbook - Maypole Cookbook\r
+\r
+=head1 DESCRIPTION\r
+\r
+Hacks; design patterns; recipes: call it what you like, this chapter is a\r
+developing collection of techniques which can be slotted in to Maypole\r
+applications to solve common problems or make the development process easier.\r
+\r
+As Maypole developers, we don't necessarily know the "best practice" for\r
+developing Maypole applications ourselves, in the same way that Larry Wall\r
+didn't know all about the best Perl programming style as soon as he wrote\r
+Perl. These techniques are what we're using at the moment, but they may\r
+be refined, modularized, or rendered irrelevant over time. But they've\r
+certainly saved us a bunch of hours work.\r
+\r
+=head2 Frontend hacks\r
+\r
+These hacks deal with changing the way Maypole relates to the outside world;\r
+alternate front-ends to the Apache and CGI interfaces, or subclassing chunks\r
+of the front-end modules to alter Maypole's behaviour in particular ways.\r
+\r
+=head3 Separate model class modules\r
+\r
+You want to put all the C<BeerDB::Beer> routines in a separate module,\r
+so you say:\r
+\r
+ package BeerDB::Beer;\r
+ BeerDB::Beer->has_a(brewery => "BeerDB::Brewery");\r
+ sub foo :Exported {}\r
+\r
+And in F<BeerDB.pm>, you put:\r
+\r
+ use BeerDB::Beer;\r
+\r
+It doesn't work.\r
+\r
+B<Solution>: It doesn't work because of the timing of the module loading.\r
+C<use BeerDB::Beer> will try to set up the C<has_a> relationships\r
+at compile time, when the database tables haven't even been set up,\r
+since they're set up by\r
+\r
+ BeerDB->setup("...")\r
+\r
+which does its stuff at runtime. There are two ways around this; you can\r
+either move the C<setup> call to compile time, like so:\r
+\r
+ BEGIN { BeerDB->setup("...") }\r
+\r
+or move the module loading to run-time (my preferred solution):\r
+\r
+ BeerDB->setup("...");\r
+ BeerDB::Beer->require;\r
+\r
+=head3 Redirecting to SSL for sensitive information\r
+\r
+You have a website with forms that people will be entering sensitive information into,\r
+such as credit cards or login details. You want to make sure that they aren't sent\r
+in plain text but over SSL instead.\r
+\r
+B<Solution>\r
+\r
+The solution is a bit tricky for 2 reasons :\r
+\r
+Firstly -- Many browsers and web clients will change a redirected \r
+POST request into a GET request (which displays all that sensitive information in the\r
+browser, or access logs and possibly elsewhere) and/or drops the values on the floor.\r
+\r
+Secondly -- If somebody has sent that sensitive information in plain text already, then\r
+sending it again over SSL won't solve the problem.\r
+\r
+Redirecting a request is actually rather simple :\r
+\r
+$r->redirect_request('https://www.example.com/path'); # perldoc Maypole for API\r
+\r
+.. as is checking the protocol :\r
+\r
+$r->get_protocol(); # returns 'http' or 'https'\r
+ \r
+You should check that the action that generates the form that people will enter\r
+the sensitive information into is https and redirect if not.\r
+\r
+You should also check that no information is lost when redirecting, possibly by \r
+storing it in a session and retrieving it later - see Maypole::Plugin::Session\r
+\r
+=head3 Debugging with the command line\r
+\r
+You're seeing bizarre problems with Maypole output, and you want to test it in\r
+some place outside of the whole Apache/mod_perl/HTTP/Internet/browser circus.\r
+\r
+B<Solution>: Use the L<Maypole::CLI> module to go directly from a URL to\r
+standard output, bypassing Apache and the network altogether.\r
+\r
+L<Maypole::CLI> is not a standalone front-end, but to allow you to debug your\r
+applications without having to change the front-end they use, it temporarily\r
+"borgs" an application. If you run it from the command line, you're expected\r
+to use it like so:\r
+\r
+ perl -MMaypole::CLI=Application -e1 'http://your.server/path/table/action'\r
+\r
+For example:\r
+\r
+ perl -MMaypole::CLI=BeerDB -e1 'http://localhost/beerdb/beer/view/1?o2=desc'\r
+\r
+You can also use the C<Maypole::CLI> module programatically to create\r
+test suites for your application. See the Maypole tests themselves or\r
+the documentation to C<Maypole::CLI> for examples of this.\r
+\r
+Don't forget also to turn on debugging output in your application:\r
+\r
+ package BeerDB;\r
+ use strict;\r
+ use warnings;\r
+ use Maypole::Application qw(-Debug);\r
+\r
+=head3 Changing how URLs are parsed\r
+\r
+You don't like the way Maypole URLs look, and want something that either\r
+fits in with the rest of your site or hides the internal workings of the\r
+system.\r
+\r
+B<Solution>: So far we've been using the C</table/action/id/args> form\r
+of a URL as though it was "the Maypole way"; well, there is no Maypole\r
+way. Maypole is just a framework and absolutely everything about it is \r
+overridable. \r
+\r
+If we want to provide our own URL handling, the method to override in\r
+the driver class is C<parse_path>. This is responsible for taking\r
+C<$r-E<gt>path> and filling the C<table>, C<action> and C<args> slots\r
+of the request object. Normally it does this just by splitting the path\r
+on 'C</>' characters, but you can do it any way you want, including\r
+getting the information from C<POST> form parameters or session variables. \r
+\r
+For instance, suppose we want our URLs to be of the form\r
+C<ProductDisplay.html?id=123>, we could provide a C<parse_path> method\r
+like so:\r
+\r
+ sub parse_path {\r
+ my $r = shift;\r
+ $r->path("ProductList.html") unless $r->path;\r
+ ($r->path =~ /^(.*?)([A-Z]\w+)\.html/);\r
+ $r->table(lc $1);\r
+ $r->action(lc $2);\r
+ my %query = $r->ar->args;\r
+ $self->args([ $query{id} ]);\r
+ }\r
+\r
+This takes the path, which already has the query parameters stripped off\r
+and parsed, and finds the table and action portions of the filename,\r
+lower-cases them, and then grabs the C<id> from the query. Later methods\r
+will confirm whether or not these tables and actions exist.\r
+\r
+See the L<iBuySpy Portal|Maypole::Manual::BuySpy> for another\r
+example of custom URL processing.\r
+\r
+=head3 Maypole for mobile devices\r
+\r
+You want Maypole to use different templates to display on particular\r
+browsers.\r
+\r
+B<Solution>: There are several ways to do this, but here's the neatest\r
+we've found. Maypole chooses where to get its templates either by\r
+looking at the C<template_root> config parameter or, if this is not\r
+given, calling the C<get_template_root> method to ask the front-end to\r
+try to work it out. We can give the front-end a little bit of help, by\r
+putting this method in our driver class:\r
+\r
+ sub get_template_root {\r
+ my $r = shift;\r
+ my $browser = $r->headers_in->get('User-Agent');\r
+ if ($browser =~ /mobile|palm|nokia/i) {\r
+ "/home/myapp/templates/mobile";\r
+ } else {\r
+ "/home/myapp/templates/desktop";\r
+ }\r
+ }\r
+\r
+(Maybe there's a better way to detect a mobile browser, but you get the\r
+idea.)\r
+\r
+=head2 Content display hacks\r
+\r
+These hacks deal primarily with the presentation of data to the user,\r
+modifying the F<view> template or changing the way that the results of\r
+particular actions are displayed.\r
+\r
+=head3 Null Action\r
+\r
+You need an "action" which doesn't really do anything, but just formats\r
+up a template.\r
+\r
+B<Solution>: There are two ways to do this, depending on what precisely\r
+you need. If you just need to display a template, C<Apache::Template>\r
+style, with no Maypole objects in it, then you don't need to write any\r
+code; just create your template, and it will be available in the usual\r
+way.\r
+\r
+If, on the other hand, you want to display some data, and what you're\r
+essentially doing is a variant of the C<view> action, then you need to\r
+ensure that you have an exported action, as described in the\r
+L<templates and actions|Maypole::Manual::StandardTemplates/"C<view> and C<edit>">\r
+chapter:\r
+\r
+ sub my_view :Exported { }\r
+\r
+=head3 Template Switcheroo\r
+\r
+An action doesn't have any data of its own to display, but needs to display\r
+B<something>.\r
+\r
+B<Solution>: This is an B<extremely> common hack. You've just issued an\r
+action like C<beer/do_edit>, which updates the database. You don't want\r
+to display a page that says "Record updated" or similar. Lesser\r
+application servers would issue a redirect to have the browser request\r
+C</beer/view/I<id>> instead, but we can actually modify the Maypole\r
+request on the fly and, after doing the update, pretend that we were\r
+going to C</beer/view/I<id>> all along. We do this by setting the\r
+objects in the C<objects> slot and changing the C<template> to the\r
+one we wanted to go to.\r
+\r
+In this example from L<Flox|Maypole::Manual::Flox>, we've just\r
+performed an C<accept> method on a C<Flox::Invitation> object and we\r
+want to go back to viewing a user's page.\r
+\r
+ sub accept :Exported {\r
+ my ($self, $r) = @_;\r
+ my $invitation = $r->objects->[0];\r
+ # [... do stuff to $invitation ...]\r
+ $r->objects([$r->user]);\r
+ $r->model_class("Flox::User");\r
+ $r->template("view");\r
+ }\r
+\r
+This hack is so common that it's expected that there'll be a neater\r
+way of doing this in the future.\r
+\r
+=head3 XSLT\r
+\r
+Here's a hack I've used a number of times. You want to store structured\r
+data in a database and to abstract out its display.\r
+\r
+B<Solution>: You have your data as XML, because handling big chunks of\r
+XML is a solved problem. Build your database schema as usual around the\r
+important elements that you want to be able to search and browse on. For\r
+instance, I have an XML format for songs which has a header section of\r
+the key, title and so on, plus another section for the lyrics and\r
+chords:\r
+\r
+ <song>\r
+ <header>\r
+ <title>Layla</title>\r
+ <artist>Derek and the Dominos</artist>\r
+ <key>Dm</key>\r
+ </header>\r
+ <lyrics>\r
+ <verse>...</verse>\r
+ <chorus>\r
+ <line> <sup>A</sup>Lay<sup>Dm</sup>la <sup>Bb</sup> </line> \r
+ <line> <sup>C</sup>Got me on my <sup>Dm</sup>knees </line> \r
+ ...\r
+\r
+I store the title, artist and key in the database, as well as an "xml"\r
+field which contains the whole song as XML.\r
+\r
+To load the songs into the database, I can C<use> the driver class for\r
+my application, since that's a handy way of setting up the database classes\r
+we're going to need to use. Then the handy L<XML::TreeBuilder> will handle\r
+the XML parsing for us:\r
+\r
+ use Songbook;\r
+ use XML::TreeBuilder;\r
+ my $t = XML::TreeBuilder->new;\r
+ $t->parse_file("songs.xml");\r
+\r
+ for my $song ($t->find("song")) {\r
+ my ($key) = $song->find("key"); $key &&= $key->as_text;\r
+ my ($title) = $song->find("title"); $title = $title->as_text;\r
+ my ($artist) = $song->find("artist"); $artist = $artist->as_text;\r
+ my ($first_line) = $song->find("line");\r
+ $first_line = join "", grep { !ref } $first_line->content_list;\r
+ $first_line =~ s/[,\.\?!]\s*$//;\r
+ Songbook::Song->find_or_create({\r
+ title => $title,\r
+ first_line => $first_line,\r
+ song_key => Songbook::SongKey->find_or_create({name => $key}),\r
+ artist => Songbook::Artist->find_or_create({name => $artist}),\r
+ xml => $song->as_XML\r
+ });\r
+ }\r
+\r
+Now we need to set up the custom display for each song; thankfully, with\r
+the L<Template::Plugin::XSLT> module, this is as simple as putting the\r
+following into F<templates/song/view>:\r
+\r
+ [%\r
+ USE transform = XSLT("song.xsl");\r
+ song.xml | $transform\r
+ %]\r
+\r
+We essentially pipe the XML for the selected song through to an XSL\r
+transformation, and this will fill out all the HTML we need. Job done.\r
+\r
+=head3 Displaying pictures\r
+\r
+You want to serve a picture, a Word document, or something else which\r
+doesn't have a content type of C<text/html>, out of your database.\r
+\r
+B<Solution>: Fill the content and content-type yourself.\r
+\r
+Here's a subroutine which displays the C<photo> for either a specified\r
+user or the currently logged in user. We set the C<output> slot of the\r
+Maypole request object: if this is done then the view class is not called\r
+upon to process a template, since we already have some output to display.\r
+We also set the C<content_type> using one from the database.\r
+\r
+ sub view_picture :Exported {\r
+ my ($self, $r) = @_;\r
+ my $user = $r->objects->[0];\r
+ $r->content_type($user->photo_type);\r
+ $r->output($user->photo);\r
+ }\r
+\r
+Of course, the file doesn't necessarily need to be in the database\r
+itself; if your file is stored in the filesystem, but you have a file\r
+name or some other pointer in the database, you can still arrange for\r
+the data to be fetched and inserted into C<$r-E<gt>output>.\r
+\r
+=head3 REST\r
+\r
+You want to provide a programmatic interface to your Maypole site.\r
+\r
+B<Solution>: The best way to do this is with C<REST>, which uses a\r
+descriptive URL to encode the request. For instance, in\r
+L<Flox|Maypole::Manual::Flox> we\r
+describe a social networking system. One neat thing you can do with\r
+social networks is to use them for reputation tracking, and we can use\r
+that information for spam detection. So if a message arrives from\r
+C<person@someco.com>, we want to know if they're in our network of\r
+friends or not and mark the message appropriately. We'll do this by\r
+having a web agent (say, L<WWW::Mechanize> or L<LWP::UserAgent>) request\r
+a URL of the form\r
+C<http://flox.simon-cozens.org/user/relationship_by_email/person%40someco.com>.\r
+Naturally, they'll need to present the appropriate cookie just like a\r
+normal browser, but that's a solved problem. We're just interested in\r
+the REST request.\r
+\r
+The request will return a single integer status code: 0 if they're not\r
+in the system at all, 1 if they're in the system, and 2 if they're our\r
+friend.\r
+\r
+All we need to do to implement this is provide the C<relationship_by_email>\r
+action, and use it to fill in the output in the same way as we did when\r
+displaying a picture. Since C<person%40someco.com> is not the ID of a\r
+row in the user table, it will appear in the C<args> array:\r
+\r
+ use URI::Escape;\r
+ sub relationship_by_email :Exported {\r
+ my ($self, $r) = @_;\r
+ my $email = uri_unescape($r->args->[0]);\r
+ $r->content_type("text/plain");\r
+ my $user;\r
+ unless (($user) = Flox::User->search(email => $email)) {\r
+ $r->content("0\n"); return;\r
+ }\r
+\r
+ if ($r->user->is_friend($user)) { $r->contenti("2\n"); return; };\r
+ $r->content("1\n"); return;\r
+ }\r
+\r
+=head3 Component-based Pages\r
+\r
+You're designing something like a portal site which has a number of\r
+components, all displaying different bits of information about different\r
+objects. You want to include the output of one Maypole request call while\r
+building up another. \r
+\r
+B<Solution>: Use L<Maypole::Plugin::Component>. By inheriting like this:\r
+\r
+ package BeerDB;\r
+ use Maypole::Application qw(Component);\r
+\r
+you can call the C<component> method on the Maypole request object to\r
+make a "sub-request". For instance, if you have a template\r
+\r
+ <DIV class="latestnews">\r
+ [% request.component("/news/latest_comp") %]\r
+ </DIV>\r
+\r
+ <DIV class="links">\r
+ [% request.component("/links/list_comp") %]\r
+ </DIV>\r
+\r
+then the results of calling the C</news/latest_comp> action and template\r
+will be inserted in the C<latestnews> DIV, and the results of calling\r
+C</links/list_comp> will be placed in the C<links> DIV. Naturally, you're\r
+responsible for exporting actions and creating templates which return \r
+fragments of HTML suitable for inserting into the appropriate locations.\r
+\r
+Alternatively, if you've already got all the objects you need, you can\r
+probably just C<[% PROCESS %]> the templates directly.\r
+\r
+=head3 Bailing out with an error\r
+\r
+Maypole's error handling sucks. Something really bad has happened to the\r
+current request, and you want to stop processing now and tell the user about\r
+it.\r
+\r
+B<Solution>: Maypole's error handling sucks because you haven't written it\r
+yet. Maypole doesn't know what you want to do with an error, so it doesn't\r
+guess. One common thing to do is to display a template with an error message\r
+in it somewhere.\r
+\r
+Put this in your driver class:\r
+\r
+ sub error { \r
+ my ($r, $message) = @_;\r
+ $r->template("error");\r
+ $r->template_args->{error} = $message;\r
+ return OK;\r
+ }\r
+\r
+And then have a F<custom/error> template like so:\r
+\r
+ [% PROCESS header %]\r
+ <H2> There was some kind of error... </H2>\r
+ <P>\r
+ I'm sorry, something went so badly wrong, we couldn't recover. This\r
+ may help:\r
+ </P>\r
+ <DIV CLASS="messages"> [% error %] </DIV>\r
+\r
+Now in your actions you can say things like this:\r
+\r
+ if (1 == 0) { return $r->error("Sky fell!") }\r
+\r
+This essentially uses the template switcheroo hack to always display the\r
+error template, while populating the template with an C<error> parameter.\r
+Since you C<return $r-E<gt>error>, this will terminate the processing\r
+of the current action.\r
+\r
+The really, really neat thing about this hack is that since C<error>\r
+returns C<OK>, you can even use it in your C<authenticate> routine:\r
+\r
+ sub authenticate {\r
+ my ($self, $r) = @_;\r
+ $r->get_user;\r
+ return $r->error("You do not exist. Go away.")\r
+ if $r->user and $r->user->status ne "real";\r
+ ...\r
+ }\r
+\r
+This will bail out processing the authentication, the model class, and\r
+everything, and just skip to displaying the error message. \r
+\r
+Non-showstopper errors or other notifications are best handled by tacking a\r
+C<messages> template variable onto the request:\r
+\r
+ if ((localtime)[6] == 1) {\r
+ push @{$r->template_args->{messages}}, "Warning: Today is Monday";\r
+ }\r
+\r
+Now F<custom/messages> can contain:\r
+\r
+ [% IF messages %]\r
+ <DIV class="messages">\r
+ <UL>\r
+ [% FOR message = messages %]\r
+ <LI> [% message %] </LI>\r
+ [% END %]\r
+ </UL>\r
+ </DIV>\r
+ [% END %]\r
+\r
+And you can display messages to your user by adding C<PROCESS messages> at an\r
+appropriate point in your template; you may also want to use a template\r
+switcheroo to ensure that you're displaying a page that has the messages box in\r
+it.\r
+\r
+=head2 Authentication and Authorization hacks\r
+\r
+The next series of hacks deals with providing the concept of a "user" for\r
+a site, and what you do with one when you've got one.\r
+\r
+=head3 Logging In\r
+\r
+You need the concept of a "current user".\r
+\r
+B<Solution>: Use something like\r
+L<Maypole::Plugin::Authentication::UserSessionCookie> to authenticate\r
+a user against a user class and store a current user object in the\r
+request object.\r
+\r
+C<UserSessionCookie> provides the C<get_user> method which tries to get\r
+a user object, either based on the cookie for an already authenticated\r
+session, or by comparing C<user> and C<password> form parameters\r
+against a C<user> table in the database. Its behaviour is highly\r
+customizable and described in its documentation.\r
+\r
+=head3 Pass-through login\r
+\r
+You want to intercept a request from a non-logged-in user and have\r
+them log in before sending them on their way to wherever they were\r
+originally going. Override C<Maypole::authenticate> in your driver\r
+class, something like this:\r
+\r
+B<Solution>:\r
+\r
+ use Maypole::Constants; # Otherwise it will silently fail!\r
+\r
+ sub authenticate {\r
+ my ($self, $r) = @_;\r
+ $r->get_user;\r
+ return OK if $r->user;\r
+ # Force them to the login page.\r
+ $r->template("login");\r
+ return OK;\r
+ }\r
+\r
+This will display the C<login> template, which should look something\r
+like this:\r
+\r
+ [% INCLUDE header %]\r
+\r
+ <h2> You need to log in </h2>\r
+\r
+ <DIV class="login">\r
+ [% IF login_error %]\r
+ <FONT COLOR="#FF0000"> [% login_error %] </FONT>\r
+ [% END %]\r
+ <FORM ACTION="[% base ; '/' ; request.path %]" METHOD="post">\r
+ Username: \r
+ <INPUT TYPE="text" NAME="[% config.auth.user_field || "user" %]"><BR>\r
+ Password: <INPUT TYPE="password" NAME="password"> <BR>\r
+ <INPUT TYPE="submit">\r
+ </FORM>\r
+ </DIV>\r
+ [% INCLUDE footer %]\r
+\r
+Notice that this request gets C<POST>ed back to wherever it came from, using\r
+C<request.path>. This is because if the user submits correct credentials,\r
+C<get_user> will now return a valid user object, and the request will pass\r
+through unhindered to the original URL.\r
+\r
+=head3 Logging Out\r
+\r
+Now your users are logged in, you want a way of having them log out\r
+again and taking the authentication cookie away from them, sending\r
+them back to the front page as an unprivileged user.\r
+\r
+B<Solution>: Just call the C<logout> method of\r
+C<Maypole::Plugin::Authentication::UserSessionCookie>. You may also want\r
+to use the template switcheroo hack to send them back to the frontpage.\r
+\r
+=head3 Multi-level Authorization\r
+\r
+You have both a global site access policy (for instance, requiring a\r
+user to be logged in except for certain pages) and a policy for\r
+particular tables. (Only allowing an admin to delete records in some\r
+tables, say, or not wanting people to get at the default set of methods\r
+provided by the model class.) \r
+\r
+You don't know whether to override the global C<authenticate> method or\r
+provide one for each class.\r
+\r
+B<Solution>: Do both.\r
+Maypole checks whether there is an C<authenticate> method for the model\r
+class (e.g. BeerDB::Beer) and if so calls that. If there's no such\r
+method, it calls the default global C<authenticate> method in C<Maypole>,\r
+which always succeeds. You can override the global method as we saw\r
+above, and you can provide methods in the model classes.\r
+\r
+To use per-table access control you can just add methods to your model\r
+subclasses that specify individual policies, perhaps like this:\r
+\r
+ sub authenticate { # Ensure we can only create, reject or accept\r
+ my ($self, $r) = @_;\r
+ return OK if $r->action =~ /^(issue|accept|reject|do_edit)$/;\r
+ return; # fail if any other action\r
+ }\r
+\r
+If you define a method like this, the global C<authenticate> method will\r
+not be called, so if you want it to be called you need to do so\r
+explicitly:\r
+\r
+ sub authenticate { # Ensure we can only create, reject or accept\r
+ my ($self, $r) = @_;\r
+ return unless $r->authenticate($r) == OK; # fail if not logged in\r
+ # now it's safe to use $r->user\r
+ return OK if $r->action =~ /^(accept|reject)$/\r
+ or ($r->user eq 'fred' and $r->action =~ /^(issue|do_edit)$/);\r
+ return; # fail if any other action\r
+ }\r
+\r
+=head2 Creating and editing hacks\r
+\r
+These hacks particularly deal with issues related to the C<do_edit>\r
+built-in action.\r
+\r
+=head3 Limiting data for display\r
+\r
+You want the user to be able to type in some text that you're later\r
+going to display on the site, but you don't want them to stick images in\r
+it, launch cross-site scripting attacks or otherwise insert messy HTML.\r
+\r
+B<Solution>: Use the L<CGI::Untaint::html> module to sanitize the HTML\r
+on input. C<CGI::Untaint::html> uses L<HTML::Sanitizer> to ensure that\r
+tags are properly closed and can restrict the use of certain tags and\r
+attributes to a pre-defined list.\r
+\r
+Simply replace:\r
+\r
+ App::Table->untaint_columns(\r
+ text => [qw/name description/]\r
+ );\r
+\r
+with:\r
+\r
+ App::Table->untaint_columns(\r
+ html => [qw/name description/]\r
+ );\r
+\r
+And incoming HTML will be checked and cleaned before it is written to\r
+the database.\r
+\r
+=head3 Getting data from external sources\r
+\r
+You want to supplement the data received from a form with additional\r
+data from another source.\r
+\r
+B<Solution>: Munge the contents of C< $r-E<gt>params > before jumping\r
+to the original C<do_edit> routine. For instance, in this method,\r
+we use a L<Net::Amazon> object to fill in some fields of a database row\r
+based on an ISBN:\r
+\r
+ use Net::Amazon;\r
+ my $amazon = Net::Amazon->new(token => 'YOUR_AMZN_TOKEN');\r
+\r
+ ...\r
+\r
+ sub create_from_isbn :Exported {\r
+ my ($self, $r) = @_;\r
+ my $book_info = $amazon->search(asin => $r->params->{isbn})->properties;\r
+\r
+ # Rewrite the CGI parameters with the ones from Amazon\r
+ $r->params->{title} = $book_info->title;\r
+ $r->params->{publisher} = $book_info->publisher;\r
+ $r->params->{year} = $book_info->year;\r
+ $r->params->{author} = join('and', $book_info->authors());\r
+ \r
+ # And jump to the usual edit/create routine\r
+ $self->do_edit($r);\r
+ }\r
+\r
+The request will carry on as though it were a normal C<do_edit> POST, but\r
+with the additional fields we have provided.\r
+You might also want to add a template switcheroo so the user can verify\r
+the details you imported.\r
+\r
+=head3 Catching errors in a form\r
+\r
+A user has submitted erroneous input to an edit/create form. You want to\r
+send him back to the form with errors displayed against the erroneous\r
+fields, but have the other fields maintain the values that the user\r
+submitted.\r
+\r
+B<Solution>: This is basically what the default C<edit> template and\r
+C<do_edit> method conspire to do, but it's worth highlighting again how\r
+they work. \r
+\r
+If there are any errors, these are placed in a hash, with each error\r
+keyed to the erroneous field. The hash is put into the template as\r
+C<errors>, and we process the same F<edit> template again:\r
+\r
+ $r->template_args->{errors} = \%errors;\r
+ $r->template("edit");\r
+\r
+This throws us back to the form, and so the form's template should take\r
+note of the errors, like so:\r
+\r
+ FOR col = classmetadata.columns;\r
+ NEXT IF col == "id";\r
+ "<P>";\r
+ "<B>"; classmetadata.colnames.$col; "</B>";\r
+ ": ";\r
+ item.to_field(col).as_HTML;\r
+ "</P>";\r
+ IF errors.$col;\r
+ "<FONT COLOR=\"#ff0000\">"; errors.$col; "</FONT>";\r
+ END;\r
+ END;\r
+\r
+If we're designing our own templates, instead of using generic ones, we\r
+can make this process a lot simpler. For instance:\r
+\r
+ <TR><TD>\r
+ First name: <INPUT TYPE="text" NAME="forename">\r
+ </TD>\r
+ <TD>\r
+ Last name: <INPUT TYPE="text" NAME="surname">\r
+ </TD></TR>\r
+\r
+ [% IF errors.forename OR errors.surname %]\r
+ <TR>\r
+ <TD><SPAN class="error">[% errors.forename %]</SPAN> </TD>\r
+ <TD><SPAN class="error">[% errors.surname %]</SPAN> </TD>\r
+ </TR>\r
+ [% END %]\r
+\r
+The next thing we want to do is to put the originally-submitted values\r
+back into the form. We can do this relatively easily because Maypole\r
+passes the Maypole request object to the form, and the POST parameters\r
+are going to be stored in a hash as C<request.params>. Hence:\r
+\r
+ <TR><TD>\r
+ First name: <INPUT TYPE="text" NAME="forename"\r
+ VALUE="[%request.params.forename%]">\r
+ </TD>\r
+ <TD>\r
+ Last name: <INPUT TYPE="text" NAME="surname"\r
+ VALUE="[%request.params.surname%]"> \r
+ </TD></TR>\r
+\r
+Finally, we might want to only re-fill a field if it is not erroneous, so\r
+that we don't get the same bad input resubmitted. This is easy enough:\r
+\r
+ <TR><TD>\r
+ First name: <INPUT TYPE="text" NAME="forename"\r
+ VALUE="[%request.params.forename UNLESS errors.forename%]">\r
+ </TD>\r
+ <TD>\r
+ Last name: <INPUT TYPE="text" NAME="surname"\r
+ VALUE="[%request.params.surname UNLESS errors.surname%]"> \r
+ </TD></TR>\r
+\r
+=head3 Uploading files and other data\r
+\r
+You want the user to be able to upload files to store in the database.\r
+\r
+B<Solution>: It's messy.\r
+\r
+First, we set up an upload form, in an ordinary dummy action. Here's\r
+the action:\r
+\r
+ sub upload_picture : Exported {}\r
+\r
+And here's the F<custom/upload_picture> template:\r
+\r
+ <FORM action="/user/do_upload" enctype="multipart/form-data" method="POST">\r
+\r
+ <P> Please provide a picture in JPEG, PNG or GIF format:\r
+ </P>\r
+ <INPUT TYPE="file" NAME="picture">\r
+ <BR>\r
+ <INPUT TYPE="submit">\r
+ </FORM>\r
+\r
+(Although you'll probably want a bit more HTML around it than that.)\r
+\r
+Now we need to write the C<do_upload> action. At this point we have to get a\r
+little friendly with the front-end system. If we're using L<Apache::Request>,\r
+then the C<upload> method of the C<Apache::Request> object (which\r
+L<Apache::MVC> helpfully stores in C<$r-E<gt>{ar}>) will work for us:\r
+\r
+ sub do_upload :Exported {\r
+ my ($class, $r) = @_;\r
+ my $user = $r->user;\r
+ my $upload = $r->ar->upload("picture");\r
+\r
+This returns a L<Apache::Upload> object, which we can query for its\r
+content type and a file handle from which we can read the data. It's\r
+also worth checking the image isn't going to be too massive before we\r
+try reading it and running out of memory, and that the content type is\r
+something we're prepared to deal with. \r
+\r
+ if ($upload) {\r
+ my $ct = $upload->info("Content-type");\r
+ return $r->error("Unknown image file type $ct")\r
+ if $ct !~ m{image/(jpeg|gif|png)};\r
+ return $r->error("File too big! Maximum size is ".MAX_IMAGE_SIZE)\r
+ if $upload->size > MAX_IMAGE_SIZE;\r
+\r
+ my $fh = $upload->fh;\r
+ my $image = do { local $/; <$fh> };\r
+\r
+Don't forget C<binmode()> in there if you're on a platform that needs it.\r
+Now we can store the content type and data into our database, store it\r
+into a file, or whatever:\r
+\r
+ $r->user->photo_type($ct);\r
+ $r->user->photo($image);\r
+ }\r
+\r
+And finally, we use our familiar template switcheroo hack to get back to\r
+a useful page:\r
+\r
+ $r->objects([ $user ]);\r
+ $r->template("view");\r
+ }\r
+\r
+Now, as we've mentioned, this only works because we're getting familiar with\r
+C<Apache::Request> and its C<Apache::Upload> objects. If we're using\r
+L<CGI::Maypole> instead, we can write the action in a similar style:\r
+\r
+ sub do_upload :Exported {\r
+ my ($class, $r) = @_;\r
+ my $user = $r->user;\r
+ my $cgi = $r->cgi;\r
+ if ($cgi->upload == 1) { # if there was one file uploaded\r
+ my $filename = $cgi->param('picture');\r
+ my $ct = $cgi->upload_info($filename, 'mime');\r
+ return $r->error("Unknown image file type $ct")\r
+ if $ct !~ m{image/(jpeg|gif|png)};\r
+ return $r->error("File too big! Maximum size is ".MAX_IMAGE_SIZE)\r
+ if $cgi->upload_info($filename, 'size') > MAX_IMAGE_SIZE;\r
+ my $fh = $cgi->upload($filename);\r
+ my $image = do { local $/; <$fh> };\r
+ $r->user->photo_type($ct);\r
+ $r->user->photo($image);\r
+ }\r
+\r
+ $r->objects([ $user ]);\r
+ $r->template("view");\r
+ }\r
+\r
+It's easy to adapt this to upload multiple files if desired.\r
+You will also need to enable uploads in your driver initialization,\r
+with the slightly confusing statement:\r
+\r
+ $CGI::Simple::DISABLE_UPLOADS = 0; # enable uploads\r
+\r
+Combine with the "Displaying pictures" hack above for a happy time.\r
+\r
+=head2 Links\r
+\r
+L<Contents|Maypole::Manual>,\r
+Next L<Flox|Maypole::Manual::Flox>,\r
+Previous L<The Beer Database, Twice|Maypole::Manual::Beer>\r
+\r