XXX
+We also want to be able to refer to the current user from the templates,
+so we use the overridable C<additional_data> method to give us a C<my>
+template variable:
+
+ sub additional_data {
+ my $r = shift; $r->{template_args}{my} = $r->{user};
+ }
+
+I've called it C<my> rather than C<me> because we it lets us check
+C<[% my.name %]>, and so on.
+
=head2 Viewing a user
The first page that a user will see after logging in will be their own
=head2 Invitations
+We need to do two things to invitations working: first provide a way to
+issue an invitation, and then provide a way to accept it. Since what
+we're doing in issuing an invitation is essentially creating a new
+one, we'll use our usual practice of having a page to display the form
+to offer an invitation, and then use a C<do_edit> method to actually do
+the work. So our C<issue> method is just an empty action:
+
+ sub issue :Exported {}
+
+and the template proceeds as normal:
+
+ [% PROCESS header %]
+ <h2> Invite a friend </h2>
+
+ <FORM ACTION="[%base%]/invitation/do_edit/" METHOD="post">
+ <TABLE>
+
+Now we use the "Catching errors in a form" recipe from L<Request.pod> and
+write our form template:
+
+ <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>
+ [% 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 %]
+ <TR>
+ ...
+
+Now we need to work on the C<do_edit> action. This has to validate the
+form parameters, create the invited user, create the row in the C<invitation>
+table, and send an email to the new user asking them to join.
+
+We'd normally use C<create_from_cgi> to do the first two stages, but this time
+we handle the untainting manually, because there are a surprising number of
+things we need to check before we actually do the create. So here's the
+untainting of the parameters:
+
+ my ($self, $r) = @_;
+ my $h = CGI::Untaint->new(%{$r->{params}});
+ my (%errors, %ex);
+ for (qw( email forename surname )) {
+ $ex{$_} = $h->extract(
+ "-as_".($_ eq "email" ? "email" : "printable") => $_
+ ) or $errors{$_} = $h->error;
+ }
+
+Next, we do the usual dance of throwing the user back at the form in case
+of errors:
+
+ if (keys %errors) {
+ $r->{template_args}{message} = "There was something wrong with that...";
+ $r->{template_args}{errors} = \%errors;
+ $r->{template} = "issue";
+ return;
+ }
+
+We've introduced a new template variable here, C<message>, which we'll use
+to display any important messages to the user.
+
+The first check we need to do is whether or not we already have a user
+with that email address. If we have, and they're a real user, then we
+abort the invite progress and instead redirect them to viewing that user's
+profile.
+
+ my ($user) = Flox::User->search({ email => $ex{email} });
+ if ($user) {
+ if ($user->status eq "real") {
+ $r->{template_args}{message} =
+ "That user already seems to exist on Flox. ".
+ "Is this the one you meant?";
+
+ $self->redirect_to_user($r,$user);
+ }
+
+Where C<redirect_to_user> looks like this:
+
+ sub redirect_to_user {
+ my ($self, $r, $user) = @_;
+ $r->{objects} = [ $user ];
+ $r->{template} = "view";
+ $r->{model_class} = "Flox::User"; # Naughty.
+ }
+
+This is, as the comment quite rightly points out, naughty. We're currently
+doing a C</invitation/do_edit/> and we want to turn this into a
+C</user/view/xxx>, changing the table, template and arguments all at once.
+To do this, we have to change the Maypole request object's idea of the model
+class, since this determines where to look for the template: if we didn't,
+we'd end up with C<invitation/view> instead of C<user/view>.
+
+Ideally, we'd do this with a Apache redirect, but we want to get that
+C<message> in there as well, so this will have to do. This isn't good practice;
+we put it into a subroutine so that we can fix it up if we find a better way
+to do it.
+
+Anyway, this is what we should do if a user already exists on the system
+and has accepted an invite already. What if we're trying to invite a user but
+someone else has invited them first and they haven't replied yet?
+
+ } else {
+ # Put it back to the form
+ $r->{template_args}{message} =
+ "That user has already been invited; ".
+ "please wait for them to accept";
+ $r->{template} = "issue";
+ }
+ return;
+ }
+
+Race conditions suck.
+
+Okay. Now we know that the user doesn't exist, and so can create the new
+one:
+
+ my $new_user = Flox::User->create({
+ email => $ex{email},
+ first_name => $ex{forename},
+ last_name => $ex{surname},
+ status => "invitee"
+ });
+
+We want to give the invitee a URL that they can go to in order to
+accept the invite. Now we don't just want the IDs of our invites to
+be sequential, since someone could get one invite, and then guess the
+rest of the invite codes. We provide a relatively secure MD5 hash as
+the invite ID:
+
+ my $random = md5_hex(time.(0+{}).$$.rand);
+
+For additional security, we're going to have the URL in the form
+C</invitation/accept/I<id>/I<from_id>/I<to_id>>, encoding the user ids
+of the two users. Now we can send email to the invitee to ask them to
+visit that URL:
+
+ my $newid = $new_user->id;
+ my $myid = $r->{user}->id;
+ _send_mail(to => $ex{email}, url => "$random/$myid/$newid",
+ user => $r->{user});
+
+I'm not going to show the C<_send_mail> routine, since it's boring.
+We haven't actually created the C<Invitation> object yet, so let's
+do that now.
+
+ Flox::Invitation->create({
+ id => $random,
+ issuer => $r->{user},
+ recipient => $new_user,
+ expires => Time::Piece->new(time + LIFETIME)->datetime
+ });
+
+You can also imagine a daily cron job that cleans up the C<Invitation>
+table looking for invitations that ever got replied to within their
+lifetime:
+
+ ($_->expires > localtime && $_->delete)
+ for Flox::Invitation->retrieve_all;
+
+Notice that we don't check whether the ID is already used. We could, but,
+you know, if MD5 sums start colliding, we have much bigger problems on
+our hands.
+
+Anyway, now we've got the invitation created, we can go back to whence we
+came: viewing the original user:
+
+ $self->redirect_to_user($r, $r->{user});
+
+Now our invitee has an email, and goes B<click> on the URL. What happens?
+
+XXX
+
=head2 Friendship Connections
=head2
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.