]> git.decadent.org.uk Git - maypole.git/commitdiff
LOADS of documentation!
authorSimon Cozens <simon@simon-cozens.org>
Tue, 6 Apr 2004 15:54:24 +0000 (15:54 +0000)
committerSimon Cozens <simon@simon-cozens.org>
Tue, 6 Apr 2004 15:54:24 +0000 (15:54 +0000)
git-svn-id: http://svn.maypole.perl.org/Maypole/trunk@123 48953598-375a-da11-a14b-00016c27c3ee

doc/Flox.pod
doc/Model.pod
doc/Request.pod

index b8d3d7a5f02108f7c18c32a13cacf073dbd306b6..3c637cd6c0b52d4efa87461c382a8045c625e75c 100644 (file)
 Friendster, Tribe, and now Google's Orkut - it seems like in early 2004,
 everyone wanted to be a social networking site. At the time, I was too
 busy to be a social networking site, as I was working on my own project
-at the time, a web application server called Maypole. However, I
-realised that if I could implement a social networking system using
-Maypole, then Maypole could probably do anything.
+at the time - Maypole. However, I realised that if I could implement a
+social networking system using Maypole, then Maypole could probably do
+anything.
 
 I'd already decided there was room for a free, open-source networking
 site, and then Peter Sergeant came up with the hook - localizing it to
 universities and societies, and tying in meet-ups with restaurant
 bookings. I called it Flox, partially because it flocks people together
 and partially because it's localised for my home town of Oxford and its
-university.
+university student population.
 
-Flox is still in, uh, flux, but it does the essentials. In this chapter,
-we're going to see how it was put together, and how the techniques shown
-in the L<Request.pod> chapter can help to create a sophisticated web
+Flox is still in, uh, flux, but it does the essentials. We're going to
+see how it was put together, and how the techniques shown in the
+L<Request.pod> chapter can help to create a sophisticated web
 application. Of course, I didn't have this manual available at the time,
 so it took a bit longer than it should have done...
+
+=head2 Mapping the concepts
+
+Any Maypole application should start with two things: a database schema,
+and some idea of what the pages involved are going to look like.
+Usually, these pages will be tying to displaying or editing some element
+of the database, so these two concepts should come hand in hand.
+
+When I started looking at social networking sites, I began by
+identifying the concepts which were going to make up the tables of the
+application. At its most basic, a site like Orkut or Flox has two
+distinct concepts: a user, and a connection between two users.
+Additionally, there's the idea of an invitation to a new user, which can
+be extended, accepted, declined or ignored. These three will make up the
+key tables; there are an extra two tables in Flox, but they're
+essentially enumerations that are a bit easier to edit: each user has an
+affiliation to a particular college or department, and a status in the
+university. (Undergraduate, graduate, and so on.)
+
+For this first run-through, we're going to ignore the ideas of societies
+and communities, and end up with a schema like so:
+
+    CREATE TABLE user (
+        id int not null auto_increment primary key,
+        first_name varchar(50),
+        last_name varchar(50),
+        email varchar(255),
+        profile text,
+        password varchar(255),
+        affiliation int,
+        unistatus int,
+        status ENUM("real", "invitee"),
+        photo blob,
+        photo_type varchar(30)
+    );
+
+    CREATE TABLE connection (
+        id int not null auto_increment primary key,
+        from_user int,
+        to_user int,
+        status ENUM("offered", "confirmed")
+    );
+
+    CREATE TABLE invitation (
+        id char(32) not null primary key,
+        issuer int,
+        recipient int,
+        expires date
+    );
+
+Plus the definition of our two auxilliary tables:
+
+    CREATE TABLE affiliation (
+        id int not null auto_increment primary key,
+        name varchar(255)
+    );
+
+    CREATE TABLE unistatus (
+        id int not null auto_increment primary key,
+        name varchar(255)
+    );
+
+Notice that, for simplicity, invitations and friendship connections are
+quite similar: they are extended from one user to another. This means
+that people who haven't accepted an invite yet still have a place in the
+user table, with a different C<status>. Similarly, a connection between
+users can be offered, and when it is accepted, its status is changed to
+"confirmed" and a reciprocal relationship put in place.
+
+We also have some idea, based on what we want to happen, of what pages
+and actions we're going to define. Leaving the user aside for the
+moment, we want an action which extends an invitation from the current
+user to a new user. We want a page the new user can go to in order to
+accept that invitation. Similarly, we want an action which offers a
+friendship connection to an existing user, and a page the user can go to
+to accept or reject it. This gives us five pages so far:
+
+    invitation/issue
+    invitation/accept
+
+    user/befriend
+    connection/accept
+    connection/reject
+
+Notice that the C<befriend> action is performed on a user, not a
+connection. This is distinct from C<invitation/issue> because when
+befriending, we have a real user on the system that we want to do
+something to. This makes sense if you think of it in terms of object
+oriented programming - we could say
+
+    Flox::Connection->create(to => $user)
+
+but it's clearer to say
+
+    $user->befriend
+
+Similarly, we could say
+
+    Flox::User->create({ ... })->issue_invitation_to
+
+but it's clearer to say
+
+    Flox::Invitation->issue( to => Flox::User->create({ ... }) )
+
+because it more accurately reflects the principal subject and object of
+these actions.
+
+Returning to look at the user class, we want to be able to view a user's
+profile, edit one's own profile, set up the profile for the first
+time, upload pictures and display pictures. We also need to handle the
+concepts of logging in and logging out.
+
+As usual, though, we'll start with a handler class which sets up the
+database:
+
+    package Flox;
+    use base qw(Apache::MVC);
+    Flox->setup("dbi:mysql:flox");
+    Flox->config->{display_tables} = [qw[user invitation connection]];
+    1;
+
+Very simple, as these things are meant to be. Now let's build on it.
+
+=head2 Authentication
+
+The concept of a current user is absolutely critical in a site like
+Flox; it represents "me", the viewer of the page, as the site explores
+the connections in my world. We've described the authentication hacks
+briefly in the L<Request.pod> chapter, but now it's time to go into a
+little more detail about how user handling is done.
+
+XXX
+
+=head2 Viewing a user
+
+The first page that a user will see after logging in will be their own
+profile, so in order to speed development, we'll start by getting a
+C<user/view> page up.
+
+The only difference from a programming point of view between this action
+and the default C<view> action is that, if no user ID is given, then we
+want to view "me", the current user. Remembering that the default view
+action does nothing, our C<Flox::User::view> action only needs to do
+nothing plus ensure it has a user in the C<objects> slot, putting
+C<$r-E<gt>{user}> in there if not:
+
+    sub view :Exported {
+        my ($class, $r) = @_;
+        $r->{objects} = [ $r->{user} ] unless @{$r->{objects}||[]};
+    }
+
+Maypole, unfortunately, is very good at making programming boring. The
+downside of having to write very little code at all is that we now have
+to spend most of our time writing nice HTML for the templates.
+
+XXX
+
+The next stage is viewing the user's photo. Assuming we've got the photo
+stored in the database already (which is a reasonable assumption for the
+moment since we don't have a way to upload a photo quite yet) then we
+can use the a variation of the "Displaying pictures" hack from the 
+Requests chapter:
+
+    sub view_picture :Exported {
+        my ($self, $r) = @_;
+        my $user = $r->{objects}->[0] || $r->{user};
+        if ($r->{content_type} = $user->photo_type) {
+           $r->{output} = $user->photo;
+        } else {
+           # Read no-photo photo
+           $r->{content_type} = "image/png";
+           $r->{output} = slurp_file("images/no-photo.png");
+        }
+    }
+
+We begin by getting a user object, just like in the C<view> action: either
+the user whose ID was passed in on the URL, or the current user. Then
+we check if a C<photo_type> has been set in this user's record. If so,
+then we'll use that as the content type for this request, and the data
+in the C<photo> attribute as the data to send out. The trick here is
+that setting C<$r-E<gt>{output}> overrides the whole view class processing
+and allows us to write the content out directly.
+
+In our template, we can now say
+
+    <IMG SRC="/user/view_picture/[% user.id %]">
+
+and the appropriate user's mugshot will appear.
+
+However, if we're throwing big chunks of data around like C<photo>, it's
+now worth optimizing the C<User> class to ensure that only pertitent
+data is fetched by default, and C<photo> and friends are only fetched on
+demand. The "lazy population" section of C<Class::DBI>'s man page
+explains how to group the columns by usage so that we can optimize
+fetches:
+
+    Flox::User->columns(Primary   => qw/id/);
+    Flox::User->columns(Essential => qw/status/);
+    Flox::User->columns(Helpful   => qw/ first_name last_name email password/)
+    Flox::User->columns(Display   => qw/ profile affiliation unistatus /);
+    Flox::User->columns(Photo     => qw/ photo photo_type /);
+
+This means that the status and ID columns will always be retrieved when
+we deal with a user; next, any one of the name, email or password
+columns will cause that group of data to be retrieved; if we go on to
+display more information about a user, we also load up the profile,
+affiliation and university status; finally, if we're throwing around
+photos, then we load in the photo type and photo data.
+
+These groupings are somewhat arbitrary, and there needs to be a lot of
+profiling to determine the most efficient groupings of columns to load,
+but they demonstrate one principle about working in Maypole: this is the
+first time in dealing with Maypole that we've had to explicitly list the
+columns of a table, but Maypole has so far Just Worked. There's a
+difference, though, between Maypole just working and Maypole working
+well, and if you want to optimize your application, then you need to
+start putting in the code to do that. The beauty of Maypole is that you
+can do as much or as little of such optimization as you want or need.
+
+So now we can view users and their photos. It's time to allow the users
+to edit their profiles and upload a new photo.
+
+=head2 Editing users
+
+XXX Editing a profile
+
+I introduced Flox to a bunch of friends and told them to be as ruthless
+as possible in finding bugs and trying to break it. And break it they
+did; within an hour the screens were thoroughly messed up as users had
+nasty HTML tags in their profiles, names, email addresses and so on. 
+This spawned another hack in the request cookbook: "Limiting data for
+display". I changed the untaint columns to use C<html> untainting, and
+all was better:
+
+    Flox::User->untaint_columns(
+        html      => [qw/first_name last_name profile/],
+        printable => [qw/password/],
+        integer   => [qw/affiliation unistatus /],
+        email     => [qw/email/]
+    );
+
+The next stage was the ability to upload a photo. We unleash the "Uploading
+files" recipe, with an additional check to make sure the photo is of a
+sensible size:
+
+    use constant MAX_IMAGE_SIZE => 512 * 1024;
+    sub do_upload :Exported {
+        my ($class, $r) = @_;
+        my $user = $r->{user};
+        my $upload = $r->{ar}->upload("picture");
+        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> };
+
+            use Image::Size;
+            my ($x, $y) = imgsize(\$image);
+            return $r->error("Image too big! ($x, $y) Maximum size is 350x350")
+                if $y > 350 or $x > 350;
+            $r->{user}->photo_type($ct);
+            $r->{user}->photo($image);
+        }
+
+        $r->objects([ $user ]);
+        $r->{template} = "view";
+    }
+
+Now we've gone as far as we want to go about user editing at the moment.
+Let's have a look at the real meat of a social networking site: getting
+other people involved, and registering connections between users. 
+
+=head2 Invitations
+
+=head2 Friendship Connections
+
+=head2 
index 1468de21a7e73f27b85d5a1b252baee0dae298a4..c5f5b18ec9a3db77fd09d8742899433967887750 100644 (file)
@@ -4,6 +4,8 @@
 
 =head2 Maypole::Model::CDBI
 
+=head2 Extending a model class with actions
+
 =head2 What Maypole wants from a model
 
 =head2 Building your own model class
index 6af82784ba5263dc2e78a07a681251bf63e1bbd2..80e4037cd9bd216f3bf707880efa213327ff456f 100644 (file)
@@ -1,43 +1,89 @@
 =head1 Maypole Request Hacking Cookbook
 
+Hacks; design patterns; recipes: call it what you like, this chapter is a
+developing collection of techniques which can be slotted in to Maypole
+applications to solve common problems or make the development process easier.
+
+As Maypole developers, we don't necessarily know the "best practice" for
+developing Maypole applications ourselves, in the same way that Larry Wall
+didn't know all about the best Perl programming style as soon as he wrote
+Perl. These techniques are what we're using at the moment, but they may
+be refined, modularized, or rendered irrelevant over time. But they've
+certainly saved us a bunch of hours work.
+
 =head2 Frontend hacks
 
+These hacks deal with changing the way Maypole relates to the outside world;
+alternate front-ends to the Apache and CGI interfaces, or subclassing chunks
+of the front-end modules to alter Maypole's behaviour in particular ways.
+
+=head3 Debugging with the command line
+
+You're seeing bizarre problems with Maypole output, and you want to test it in
+some place outside of the whole Apache/mod_perl/HTTP/Internet/browser circus.
+
+B<Solution>: Use the C<Maypole::CLI> module to go directly from a URL to
+standard output, bypassing Apache and the network altogether.
+
+C<Maypole::CLI> is not a standalone front-end, but to allow you to debug your
+applications without having to change the front-end they use, it temporarily
+"borgs" an application. If you run it from the command line, you're expected
+to use it like so:
+
+    perl -MMaypole::CLI=Application -e1 'http://your.server/path/table/action'
+
+For example:
+
+    perl -MMaypole::CLI=BeerDB -e1 'http://localhost/beerdb/beer/view/1?o2=desc'
+
+You can also use the C<Maypole::CLI> module programatically to create
+test suites for your application. See the Maypole tests themselves or
+the documentation to C<Maypole::CLI> for examples of this.
+
 =head3 Changing how params are parsed
 
 =head3 REST
 
-=head2 Authentication hacks
+=head2 Content display hacks
 
-=head2 Creating and editing hacks
+These hacks deal primarily with the presentation of data to the user,
+modifying the C<view> template or changing the way that the results of
+particular actions are displayed.
 
-=head3 Getting data from external sources
+=head3 Template Switcheroo
 
-You want to supplement the data received from a form with additional
-data from another source.
+An action doesn't have any data of its own to display, but needs to display
+B<something>.
 
-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:
+B<Solution>: This is an B<extremely> common hack. You've just issued an
+action like C<beer/do_edit>, which updates the database. You don't want
+to display a page that says "Record updated" or similar. Lesser
+application servers would issue a redirect to have the browser request
+C</beer/view/I<id>> instead, but we can actually modify the Maypole
+request on the fly and, after doing the update, pretend that we were
+going to C</beer/view/I<id>> all along. We do this by setting the
+objects in the C<objects> slot and changing the C<template> to the
+one we wanted to go to.
 
-    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);
+In this example from L<Flox.pod>, we've just performed an C<accept>
+method on a C<Flox::Invitation> object and we want to go back to viewing
+a user's page.
+
+    sub accept :Exported {
+        my ($self, $r) = @_;
+        my $invitation = $r->objects->[0];
+        # [... do stuff to $invitation ...]
+        $r->{objects} = [$r->{user}];
+        $r->{model_class} = "Flox::User";
+        $r->{template} = "view";
     }
 
-The request will carry on as though it were a normal C<do_edit> POST, but
-with the additional fields we have provided.
+This hack is so common that it's expected that there'll be a neater
+way of doing this in the future.
 
-=head2 Content display hacks
+=head3 Maypole for mobile devices
+
+XXX
 
 =head3 XSLT
 
@@ -107,8 +153,8 @@ transformation, and this will fill out all the HTML we need. Job done.
 
 =head3 Displaying pictures
 
-You want to serve a picture, or something else which doesn't have a
-content type of C<text/html>, out of your database.
+You want to serve a picture, a Word document, or something else which
+doesn't have a content type of C<text/html>, out of your database.
 
 B<Solution>: Fill the content and content-type yourself.
 
@@ -120,8 +166,368 @@ We also set the C<content_type> using one from the database.
 
     sub view_picture :Exported {
         my ($self, $r) = @_;
-        my $user = $r->{objects}->[0] || $r->{user};
-        if ($r->{content_type} = $user->photo_type) {
-           $r->{output} = $user->photo;
+        my $user = $r->{objects}->[0];
+        $r->{content_type} = $user->photo_type;
+        $r->{output} = $user->photo;
+    }
+
+Of course, the file doesn't necessarily need to be in the database
+itself; if your file is stored in the filesystem, but you have a file
+name or some other pointer in the database, you can still arrange for
+the data to be fetched and inserted into C<$r-E<gt>{output}>.
+
+=head3 Component-based Pages
+
+You're designing something like a portal site which has a number of
+components, all displaying different bits of information about different
+objects. You want to include the output of one Maypole request call while
+building up another. 
+
+B<Solution>: Use C<Maypole::Component>. By inheriting from this, you can
+call the C<component> method on the Maypole request object to make a 
+"sub-request". For instance, if you have a template
+
+    <DIV class="latestnews">
+    [% request.component("/news/latest_comp") %]
+    </DIV>
+
+    <DIV class="links">
+    [% request.component("/links/list_comp") %]
+    </DIV>
+
+then the results of calling the C</news/latest_comp> action and template
+will be inserted in the C<latestnews> DIV, and the results of calling
+C</links/list_comp> will be placed in the C<links> DIV. Naturally, you're
+responsible for exporting actions and creating templates which return 
+fragments of HTML suitable for inserting into the appropriate locations.
+
+=head3 Bailing out with an error
+
+Maypole's error handling sucks. Something really bad has happened to the
+current request, and you want to stop processing now and tell the user about
+it.
+
+B<Solution>: Maypole's error handling sucks because you haven't written it
+yet. Maypole doesn't know what you want to do with an error, so it doesn't
+guess. One common thing to do is to display a template with an error message
+in it somewhere.
+
+Put this in your driver class:
+
+    sub error { 
+        my ($r, $message) = @_;
+        $r->{template} = "error";
+        $r->{template_args}{error} = $message;
+        return OK;
+    }
+
+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 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.