+
+And then have a F<custom/error> template like so:
+
+ [% PROCESS header %]
+ <H2> There was some kind of error... </H2>
+ <P>
+ I'm sorry, something went so badly wrong, we couldn't recover. This
+ may help:
+ </P>
+ <DIV CLASS="messages"> [% error %] </DIV>
+
+Now in your actions you can say things like this:
+
+ if (1 == 0) { return $r->error("Sky fell!") }
+
+This essentially uses the template switcheroo hack to always display the
+error template, while populating the template with an C<error> parameter.
+Since you C<return $r-E<gt>error>, this will terminate the processing
+of the current action.
+
+The really, really neat thing about this hack is that since C<error>
+returns C<OK>, you can even use it in your C<authenticate> routine:
+
+ sub authenticate {
+ my ($self, $r) = @_;
+ $r->get_user;
+ return $r->error("You do not exist. Go away.")
+ if $r->{user} and $r->{user}->status ne "real";
+ ...
+ }
+
+This will bail out processing the authentication, the model class, and
+everything, and just skip to displaying the error message.
+
+Non-showstopper errors or other notifications are best handled by tacking a
+C<messages> template variable onto the request:
+
+ if ((localtime)[6] == 1) {
+ push @{$r->{template_args}{messages}}, "Warning: Today is Monday";
+ }
+
+Now F<custom/messages> can contain:
+
+ [% IF messages %]
+ <DIV class="messages">
+ <UL>
+ [% FOR message = messages %]
+ <LI> [% message %] </LI>
+ [% END %]
+ </UL>
+ </DIV>
+ [% END %]
+
+And you can display messages to your user by adding C<PROCESS messages> at an
+appropriate point in your template; you may also want to use a template
+switcheroo to ensure that you're displaying a page that has the messages box in
+it.
+
+=head2 Authentication hacks
+
+The next series of hacks deals with providing the concept of a "user" for
+a site, and what you do with one when you've got one.
+
+=head3 Logging In
+
+You need the concept of a "current user".
+
+B<Solution>: Use something like
+C<Maypole::Authentication::UserSessionCookie> to authenticate a user
+against a user class and store a current user object in the request
+object.
+
+C<UserSessionCookie> provides the C<get_user> method which tries to get
+a user object, either based on the cookie for an already authenticated
+session, or by comparing C<username> and C<password> form parameters
+against a C<user> table in the database. Its behaviour is highly
+customizable, so see the documentation, or the authentication paper at
+C<http://maypole.simon-cozens.org/docs/authentication.html> for examples.
+
+=head3 Pass-through login
+
+You want to intercept a request from a non-logged-in user and have
+them log in before sending them on their way to wherever they were
+originally going.
+
+B<Solution>:
+
+ sub authenticate {
+ my ($self, $r) = @_;
+ $r->get_user;
+ return OK if $r->{user};
+ # Force them to the login page.
+ $r->{template} = "login";
+ return OK;
+ }
+
+This will display the C<login> template, which should look something
+like this:
+
+ [% INCLUDE header %]
+
+ <h2> You need to log in </h2>
+
+ <DIV class="login">
+ [% IF login_error %]
+ <FONT COLOR="#FF0000"> [% login_error %] </FONT>
+ [% END %]
+ <FORM ACTION="/[% request.path%]" METHOD="post">
+ Username:
+ <INPUT TYPE="text" NAME="[% config.auth.user_field || "user" %]"> <BR>
+ Password: <INPUT TYPE="password" NAME="password"> <BR>
+ <INPUT TYPE="submit">
+ </FORM>
+ </DIV>
+
+Notice that this request gets C<POST>ed back to wherever it came from, using
+C<request.path>. This is because if the user submits correct credentials,
+C<get_user> will now return a valid user object, and the request will pass
+through unhindered to the original URL.
+
+=head3 Logging Out
+
+Now your users are logged in, you want a way of having them log out
+again and taking the authentication cookie away from them, sending
+them back to the front page as an unprivileged user.
+
+B<Solution>: This action, on the user class, is probably overkill, but
+it does the job:
+
+ sub logout :Exported {
+ my ($class, $r) = @_;
+ # Remove the user from the request object
+ my $user = delete $r->{user};
+ # Destroy the session
+ tied(%{$r->{session}})->delete;
+ # Send a new cookie which expires the previous one
+ my $cookie = Apache::Cookie->new($r->{ar},
+ -name => $r->config->{auth}{cookie_name},
+ -value => undef,
+ -path => "/"
+ -expires => "-10m"
+ );
+ $cookie->bake();
+ # Template switcheroo
+ $r->template("frontpage");
+ }
+
+=head3 Multi-level Authentication
+
+You have both a global site access policy (for instance, requiring a
+user to be logged in except for certain pages) and a policy for
+particular tables. (Only allowing an admin to delete records in some
+tables, say, or not wanting people to get at the default set of methods
+provided by the model class.)
+
+You don't know whether to override the global C<authenticate> method or
+provide one for each class.
+
+B<Solution>: Do both. Have a global C<authenticate> method which calls
+a C<sub_authenticate> method based on the class:
+
+ sub authenticate {
+ ...
+ if ($r->{user}) {
+ return $r->model_class->sub_authenticate($r)
+ if $r->model_class->can("sub_authenticate");
+ return OK;
+ }
+ ...
+ }
+
+And now your C<sub_authenticate> methods can specify the policy for
+each table:
+
+ sub sub_authenticate { # Ensure we can only create, reject or accept
+ my ($self, $r) = @_;
+ return OK if $r->{action} =~ /^(issue|accept|reject|do_edit)$/;
+ return;
+ }
+
+=head2 Creating and editing hacks
+
+These hacks particularly deal with issues related to the C<do_edit>
+built-in action.
+
+=head3 Limiting data for display
+
+You want the user to be able to type in some text that you're later
+going to display on the site, but you don't want them to stick images in
+it, launch cross-site scripting attacks or otherwise insert messy HTML.
+
+B<Solution>: Use the C<CGI::Untaint::html> module to sanitize the HTML
+on input. C<CGI::Untaint::html> uses C<HTML::Sanitizer> to ensure that
+tags are properly closed and can restrict the use of certain tags and
+attributes to a pre-defined list.
+
+Simply replace:
+
+ App::Table->untaint_columns(
+ text => [qw/name description/]
+ );
+
+with:
+
+ App::Table->untaint_columns(
+ html => [qw/name description/]
+ );
+
+And incoming HTML will be checked and cleaned before it is written to
+the database.
+
+=head3 Getting data from external sources
+
+You want to supplement the data received from a form with additional
+data from another source.
+
+B<Solution>: Munge the contents of C< $r-E<gt>params > before jumping
+to the original C<do_edit> routine. For instance, in this method,
+we use a C<Net::Amazon> object to fill in some fields of a database row based
+on an ISBN:
+
+ sub create_from_isbn :Exported {
+ my ($self, $r) = @_;
+ my $response = $ua->search(asin => $r->{params}{isbn});
+ my ($prop) = $response->properties;
+ # Rewrite the CGI parameters with the ones from Amazon
+ @{$r->{params}{qw(title publisher author year)} =
+ ($prop->title,
+ $prop->publisher,
+ (join "/", $prop->authors()),
+ $prop->year());
+ # And jump to the usual edit/create routine
+ $self->do_edit($r);
+ }
+
+The request will carry on as though it were a normal C<do_edit> POST, but
+with the additional fields we have provided.
+
+=head3 Catching errors in a form
+
+A user has submitted erroneous input to an edit/create form. You want to
+send him back to the form with errors displayed against the erroneous
+fields, but have the other fields maintain the values that the user
+submitted.
+
+B<Solution>: This is basically what the default C<edit> template and
+C<do_edit> method conspire to do, but it's worth highlighting again how
+they work.
+
+If there are any errors, these are placed in a hash, with each error
+keyed to the erroneous field. The hash is put into the template as
+C<errors>, and we process the same F<edit> template again:
+
+ $r->{template_args}{errors} = \%errors;
+ $r->{template} = "edit";
+
+This throws us back to the form, and so the form's template should take
+note of the errors, like so:
+
+ FOR col = classmetadata.columns;
+ NEXT IF col == "id";
+ "<P>";
+ "<B>"; classmetadata.colnames.$col; "</B>";
+ ": ";
+ item.to_field(col).as_HTML;
+ "</P>";
+ IF errors.$col;
+ "<FONT COLOR=\"#ff0000\">"; errors.$col; "</FONT>";
+ END;
+ END;
+
+If we're designing our own templates, instead of using generic ones, we
+can make this process a lot simpler. For instance:
+
+ <TR><TD>
+ First name: <INPUT TYPE="text" NAME="forename">
+ </TD>
+ <TD>
+ Last name: <INPUT TYPE="text" NAME="surname">
+ </TD></TR>
+
+ [% IF errors.forename OR errors.surname %]
+ <TR>
+ <TD><SPAN class="error">[% errors.forename %]</SPAN> </TD>
+ <TD><SPAN class="error">[% errors.surname %]</SPAN> </TD>
+ </TR>
+ [% END %]
+
+The next thing we want to do is to put the originally-submitted values
+back into the form. We can do this relatively easily because Maypole
+passes the Maypole request object to the form, and the POST parameters
+are going to be stored in a hash as C<request.params>. Hence:
+
+ <TR><TD>
+ First name: <INPUT TYPE="text" NAME="forename"
+ VALUE="[%request.params.forename%]">
+ </TD>
+ <TD>
+ Last name: <INPUT TYPE="text" NAME="surname"
+ VALUE="[%request.params.surname%]">
+ </TD></TR>
+
+Finally, we might want to only re-fill a field if it is not erroneous, so
+that we don't get the same bad input resubmitted. This is easy enough:
+
+ <TR><TD>
+ First name: <INPUT TYPE="text" NAME="forename"
+ VALUE="[%request.params.forename UNLESS errors.forename%]">
+ </TD>
+ <TD>
+ Last name: <INPUT TYPE="text" NAME="surname"
+ VALUE="[%request.params.surname UNLESS errors.surname%]">
+ </TD></TR>
+
+=head3 Uploading files and other data
+
+You want the user to be able to upload files to store in the database.
+
+B<Solution>: It's messy.
+
+First, we set up an upload form, in an ordinary dummy action. Here's
+the action:
+
+ sub upload_picture : Exported {}
+
+And here's the template:
+
+ <FORM action="/user/do_upload" enctype="multipart/form-data" method="POST">
+
+ <P> Please provide a picture in JPEG, PNG or GIF format:
+ </P>
+ <INPUT TYPE="file" NAME="picture">
+ <BR>
+ <INPUT TYPE="submit">
+ </FORM>
+
+(Although you'll probably want a bit more HTML around it than that.)
+
+Now we need to write the C<do_upload> action. At this point we have to get a
+little friendly with the front-end system. If we're using C<Apache::Request>,
+then the C<upload> method of the C<Apache::Request> object (which
+C<Apache::MVC> helpfully stores in C<$r-E<gt>{ar}>) will work for us:
+
+ sub do_upload :Exported {
+ my ($class, $r) = @_;
+ my $user = $r->{user};
+ my $upload = $r->{ar}->upload("picture");
+
+This returns a C<Apache::Upload> object, which we can query for its
+content type and a file handle from which we can read the data. It's
+also worth checking the image isn't going to be too massive before we
+try reading it and running out of memory, and that the content type is
+something we're prepared to deal with.
+
+ if ($upload) {
+ my $ct = $upload->info("Content-type");
+ return $r->error("Unknown image file type $ct")
+ if $ct !~ m{image/(jpeg|gif|png)};
+ return $r->error("File too big! Maximum size is ".MAX_IMAGE_SIZE)
+ if $upload->size > MAX_IMAGE_SIZE;
+
+ my $fh = $upload->fh;
+ my $image = do { local $/; <$fh> };
+
+Now we can store the content type and data into our database, store it
+into a file, or whatever:
+
+ $r->{user}->photo_type($ct);
+ $r->{user}->photo($image);
+ }
+
+And finally, we use our familiar template switcheroo hack to get back to
+a useful page:
+
+ $r->objects([ $user ]);
+ $r->{template} = "view";
+ }
+
+Now, as we've mentioned, this only works because we're getting familiar with
+C<Apache::Request> and its C<Apache::Upload> objects. If we're planning to use
+C<CGI::Maypole> instead, or want to write our application in a generic way so
+that it'll work regardless of front-end, then we need to replace the C<upload>
+call with an equivalent which uses the C<CGI> module to get the upload data.
+This is convoluted and horrific and we're not going to show it here, but it's
+possible.
+
+Combine with the "Displaying pictures" hack above for a happy time.