package Invite; # Invite.pm version 0.1 # By James Stewart : September 2004 # This work is licensed under a Creative Commons License # http://creativecommons.org/licenses/by-nc-sa/2.0/ use base 'CGI::Application'; use DBI; use CGI::Application::Session; use File::Spec; use Crypt::GeneratePassword qw (word chars); use lib 'lib/'; # Here are our config variables my $sendmail = '| /usr/sbin/sendmail -t'; my $script_uri = 'events.jamesandkari.co.uk/site.cgi'; my @database = ("dbhost","database","username",'password'); my $email_now = 0; # Set to 1 to have this script send emails. 0 if using separate email agent sub cgiapp_init { my $self = shift; # Configure the session $self->session_config( CGI_SESSION_OPTIONS => [ "driver:File;serializer:Storable", $self->query, {Directory =>'/tmp'} ], COOKIE_PARAMS => { -expires => '+60d', -path => '/', }, SEND_COOKIE => 1, ); } sub cgiapp_prerun { my $self = shift; $self->param('dbh',DBI->connect("DBI:mysql:".$database[1].":".$database[0], $database[2], $database[3])); # this is where we instantiate our session if ($self->query->param('username')) { my $test_details = $self->param('dbh')->prepare("SELECT * FROM user WHERE email = ? AND password = ?"); $test_details->execute($self->query->param('username'),$self->query->param('password')); if ($test_details->rows) { my $user_details = $test_details->fetchrow_hashref; $self->session->param('~logged-in',$user_details); } } else { unless ( $self->session->param('~logged-in') || $self->get_current_runmode() eq 'welcome' || $self->get_current_runmode() eq 'login') { $self->session->param('original', $self->get_current_runmode()); $self->prerun_mode('login'); } } } sub setup { my $self = shift; $self->start_mode('welcome'); $self->mode_param('jys'); $self->run_modes( 'welcome' => 'welcome', 'show_invite' => 'show_invite', 'add_invite' => 'add_invite', 'edit_invite' => 'edit_invite', 'login' => 'login', 'user' => 'user', 'logout' => 'logout' ); } sub welcome { my $self = shift; my $page = $self->load_tmpl('welcome.tmpl'); # If user logged in, we'll want to change things on the front page if ($self->session->param('~logged-in')) { $page->param("ADDEVENT" => 1) if $self->session->param('~logged-in')->{'permissions'} == 0; $page->param("LOGGED_IN" => 1); my $event_query = $self->param('dbh')->prepare("SELECT event.* FROM event,user_event WHERE user_event.user = ? AND event.id = user_event.event"); $event_query->execute($self->session->param('~logged-in')->{id}); my @events; while (@line = $event_query->fetchrow_array()) { if ($line[2] == $self->session->param('~logged-in')->{id}) { push @events, { admin => 1, "EDIT_URI" => "?jys=edit_invite&iid=".$line[0], "EVENT_URI" => "?jys=show_invite&iid=".$line[0], "EVENT_NAME" => $line[1] }; } else { push @events, { "EVENT_URI" => "?jys=show_invite&iid=".$line[0], "EVENT_NAME" => $line[1] }; } } $page->param("EVENTS" => \@events, NAME => $self->session->param('~logged-in')->{name}); } else { $page->param("jys" => 'welcome'); } return $page->output; } sub login { my $self = shift; my $page = $self->load_tmpl('login.tmpl'); $page->param("jys" => $self->session->param('original')); my @parameters; foreach $param ($self->query->param) { push @parameters, { 'element' => $self->safe_output($param), 'value' => $self->safe_output($self->query->param($param)) } unless $param eq 'jys'; } # If user logged in, we'll want to change things $page->param( 'input' => 1, 'inputs' => \@parameters ); return $page->output; } sub safe_output { # TODO: This could do with being generalised and used throughout. Tidy this API! my $self = shift; my $save = shift; $save =~ s/["''">;<\/]//g; return $save; } sub show_invite { my $self = shift; # Get invitation ID and test it my $invitation_id = $self->query->param("iid"); return $self->error_parse("That is not a valid invitation ID") if ($invitation_id !~ /[A-Za-z0-9]{16}/); # get the user id and compare with db entry for this event my $user_id = $self->session->param('~logged-in')->{id}; my $event = $self->param('dbh')->prepare(" SELECT event.*,user_event.* FROM event,user,user_event WHERE user_event.user = '$user_id' AND user_event.event = event.id AND event.id = '$invitation_id'"); $event->execute; return $self->error_parse("You have not been invited to that event, or there is a database error") if (! $event->rows); # If we're still here, the user can view this event and we can show it to them as it is all in # $event my $response = $event->fetchrow_hashref; if ($self->query->param('reply')) { # TODO test that input is safe $update = $self->param('dbh')->prepare("UPDATE user_event SET attending = ? WHERE event = ? AND user = ?"); $update->execute($self->query->param('reply'),$invitation_id,$user_id); } my $page = $self->load_tmpl('event.tmpl'); if ($response->{'attending'} || $self->query->param('reply')) { my %replying = ( 1 => 'may', 2=> 'won\'t', 3 => 'will' ); $yes_no = $replying{$self->query->param('reply')} || $replying{$response->{attending}}; $page->param(replied => 1, response => $yes_no); } %starthash = $self->proc_dates($response->{'start'}); %endhash = $self->proc_dates($response->{'end'}); if ( $starthash{'year'} == $endhash{'year'} && $starthash{'month'} == $endhash{'month'} && $starthash{'day'} == $endhash{'day'} ) { $times = $starthash{month}."/".$starthash{day}."/".$starthash{year}." ".$starthash{hour}.":".$starthash{minute}." to ".$endhash{'hour'}.":".$endhash{'minute'}; } else { $times = $starthash{month}."/".$starthash{day}."/".$starthash{year}." ".$starthash{hour}.":".$starthash{minute}." to ENDYEAR ".$endhash{'hour'}.":".$endhash{'minute'}; } $page->param( IID => $invitation_id, NOTES => $response->{'notes'}, TIMES => $times, location_name => $response->{'location_name'}, location_street => $response->{'location_street'}, location_city => $response->{'location_city'}, location_postal => $response->{'location_postal'}, location_country => $response->{'location_country'}, TITLE => $response->{'title'}, user => $self->session->param('~logged-in')->{name} || $self->session->param('~logged-in')->{email}, image_uri => $response->{'image_uri'} ); my $replies = $self->param('dbh')->prepare("SELECT user.name,user.email,user_event.attending FROM user,user_event WHERE user_event.user = user.id AND user_event.event = ? ORDER BY attending DESC, name"); $replies->execute($invitation_id); if ($replies->rows) { my @responses; my @replies = ("not replied","maybe","can't come","coming"); while (my @respondee = $replies->fetchrow_array()) { push @responses, { person => $respondee[0] || $respondee[1], theysaid => $replies[$respondee[2]] }; } $page->param( others => 1, replyers => \@responses ); } return $page->output; } sub add_invite { my $self = shift; # Is user able to add invitations? return $self->error_parse("You are not allowed to add events.") if $self->session->param('~logged-in')->{'permissions'} == 0; # Do we have the data? if not, show the form unless ($self->query->param("start")) { my $page = $self->load_tmpl('add_form.tmpl'); $page->param(ACTION => 'add_invite'); return $page->output; } # If so, check it, add it and confirm $invitation = word(16,16); return $self->error_parse("invalid date") unless $self->query->param('start') =~ /[0-9:\/]*/; return $self->error_parse("invalid date") unless $self->query->param('end') =~ /[0-9:\/]*/; my %start_array = $self->proc_input_dates($self->query->param('start').":00"); my %end_array = $self->proc_input_dates($self->query->param('end').":00"); $title = $self->query->param('title'); $title =~ s/'/\\'/g; $add_event = $self->param('dbh')->prepare("INSERT INTO event VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)"); $add_event->execute( $invitation, $title, $self->session->param('~logged-in')->{id}, $start_array{year}."-".$start_array{month}."-".$start_array{day}." ".$start_array{hour}.":".$start_array{minute}.":00", $end_array{year}."-".$end_array{month}."-".$end_array{day}." ".$end_array{hour}.":".$end_array{minute}.":00", $self->query->param('notes'), $self->query->param('image'), $self->query->param('name'), $self->query->param('street'), $self->query->param('city'), $self->query->param('state'), $self->query->param('postal'), $self->query->param('country') ) || return $self->error_parse("Database Error: ".$self->param('dbh')->errstr); # Check users, add them, and if set to send email immediately do so $self->user_prepare; my @users = split /\n/, $self->query->param("invitees"); foreach $user (@users) { $self->add_user_to_invite($user,$invitation); } if ($email_now) { return $self->error_parse("Email sending failed") unless $emails = $self->generate_email($invitation); } # TODO: Confirmation page! return $self->error_parse("Event added. $emails emails sent."); } sub user_prepare { my $self = shift; $self->param('test_user', $self->param('dbh')->prepare("SELECT id FROM user WHERE email = ?")); $self->param('add_user', $self->param('dbh')->prepare("INSERT INTO user VALUES (NULL,?,?,0,?)")); $self->param('invite_user', $self->param('dbh')->prepare("INSERT INTO user_event VALUES (NULL,?,?,NULL,NULL)")); } sub get_user_db_id { my $self = shift; # user_details are supplied in scalar form with the format 'NAME, EMAIL' my @user_details = split /,/, shift; $user_details[0] =~ s/^\s*//g; $user_details[0] =~ s/\s*$//g; $user_details[1] =~ s/^\s*//g; $user_details[1] =~ s/\s*$//g; # Check if they are in the db. Query is pre-prepared by the calling function $self->param('test_user')->execute($user_details[1]); # If not in DB, create password and add unless ($self->param('test_user')->rows) { # generate password. add to db. query pre-prepared $password = word(6,12); $self->param('add_user')->execute($user_details[1],$user_details[0],$password) || return $self->error_parse("DB insert failed: ".$self->param('dbh')->errstr); return $self->param('dbh')->{'mysql_insertid'} || return $self->error_parse("Last insert id failed: ".$self->param('dbh')->errstr); } else { my @a = $self->param('test_user')->fetchrow_array(); return $a[0]; } } sub add_user_to_invite { my $self = shift; my $user = shift; my $invitation = shift; my $user_id = $self->get_user_db_id($user); $self->param('invite_user')->execute($user_id,$invitation); } sub generate_email { my $self = shift; my $event = shift; my $event_query = $self->param('dbh')->prepare("SELECT event.*,user.* FROM event,user WHERE event.creator = user.id AND event.id = ?") || die $self->param('dbh')->errstr; $event_query->execute($event) || return $self->error_parse("Database Error: ".$self->param('dbh')->errstr); return $event.' problem with event query: '.$event.' . no' if (! $event_query->rows); my $details = $event_query->fetchrow_hashref(); my %date_array = $self->proc_dates($details->{start}); my $query = $self->param('dbh')->prepare("SELECT user.name,user.email,user.password,user.id FROM user,user_event WHERE user_event.user = user.id AND user_event.event = ? AND user_event.emailed IS NULL"); $query->execute($event); return "no" if (! $query->rows); my @emails; my $email = $self->load_tmpl('email.tmpl'); $details->{title} =~ s/\\'/'/g; $email->param( DATE => $date_array{'month'}."/".$date_array{'day'}."/".$date_array{'year'}, FROM => $details->{name}, LOCATION => $details ->{location_name}, START => $date_array{'hour'}.":".$date_array{'minute'}, TITLE => $details->{'title'}, URL => "http://$script_uri?jys=show_invite&iid=$event" ); # Ideally I want to use Mail::Bulkmail or similar to speed up mail sending. # For now, I just want things to work. # With this being so slow, we should probably push something to the web browser at this point. while (my @guest = $query->fetchrow_array()) { $email->param(PASSWORD => $guest[2]); push @emails, $guest[3]; open (MAIL,"| /usr/sbin/sendmail -t"); print MAIL "To: ".$guest[1]."\n"; print MAIL "From: ".$details->{email}."\n"; print MAIL "Subject: Invitation to: ".$details->{title}."\n\n"; print MAIL $email->output; close MAIL; #push @addresses, $guest[1]; } my $tell_it = $self->param('dbh')->prepare("UPDATE user_event SET emailed = 1 WHERE user IN (".join(",",@emails).") AND event = '$event'"); $tell_it->execute; return $query->rows; } sub edit_invite { my $self = shift; # Is user able to add invitations? return $self->error_parse("You are not allowed to add events.") if $self->session->param('~logged-in')->{'permissions'} == 0; return $self->error_parse("You need to specify an event ID") if ! $self->query->param("iid"); # Do we have the data? if not, load from DB and show the form unless ($self->query->param("start")) { my $get_data = $self->param('dbh')->prepare("SELECT * FROM event WHERE id = ?"); $get_data->execute($self->query->param("iid")); my $values = $get_data->fetchrow_hashref(); return $self->error_parse("You must have created an event to edit it.") if ($self->session->param('~logged-in')->{id} != $values->{creator}); my $page = $self->load_tmpl('add_form.tmpl'); $page->param( ACTION => 'edit_invite', EDIT => 1, IID => $values->{id}, TITLE => $values->{title}, START => $values->{start}, END => $values->{end}, IMAGE => $values->{image_uri}, LOCATION => $values->{location_name}, STREET => $values->{location_street}, CITY => $values->{location_city}, STATE => $values->{location_state}, PoSTAL => $values->{location_postal}, COUNTRY => $values->{location_country}, NOTES => $values->{notes} ); return $page->output; } # TODO: check input. particularly, check iid is owned by this user $add_event = $self->param('dbh')->prepare("REPLACE INTO event VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)"); $add_event->execute( $self->query->param('iid'), $self->query->param('title'), $self->session->param('~logged-in')->{id}, $self->query->param('start'), $self->query->param('end'), $self->query->param('notes'), $self->query->param('image'), $self->query->param('name'), $self->query->param('street'), $self->query->param('city'), $self->query->param('state'), $self->query->param('postal'), $self->query->param('country') ); if ($self->query->param("invitees") && $self->query->param("invitees") ne 'Name, Email') { $self->user_prepare; my @users = split /\n/, $self->query->param("invitees"); foreach $user (@users) { $self->add_user_to_invite($user,$self->query->param("iid")); } if ($email_now) { return $self->error_parse("Email sending failed") unless $emails = $self->generate_email($self->query->param('iid')); } } else { $emails = 'No new'; } # TODO: Confirmation page! return $self->error_parse("Event updated. $emails emails sent."); } sub user { my $self = shift; # Get user ID my $user = $self->session->param('~logged-in')->{id}; my $perms = $self->session->param('~logged-in')->{permissions}; # Do we have input to process? unless ($self->query->param('uid')) { my $page = $self->load_tmpl('edit_user.tmpl'); $page->param( UID => $self->session->param('~logged-in')->{id}, NAME => $self->session->param('~logged-in')->{name}, EMAIL => $self->session->param('~logged-in')->{email}, PASSWORD => $self->session->param('~logged-in')->{password} ); return $page->output; } return $self->error_parse("You're not supposed to be doing that") unless $self->query->param('uid') == $user; # Make changes to page and move on # TODO: should changing email require email confirmation? # TODO: should changing password require confirmation? $replace = $self->param('dbh')->prepare("REPLACE INTO user VALUES (?,?,?,?,?)"); $replace->execute($user,$self->query->param("email"),$self->query->param("name"),$perms,$self->query->param("passwd")); # TODO: Confirmation page! return $self->error_parse("Your record was updated."); } sub error_parse { my $self = shift; my $error = shift; $page = $self->load_tmpl('error.tmpl'); $page->param( "ERROR" => $error ); return $page->output; } sub logout { my $self = shift; $self->session->clear('~logged-in'); my $tmpl = $self->load_tmpl('welcome.tmpl'); $tmpl->param(MSG => 1, MESSAGE => "You have been successfully logged out"); return $tmpl->output; } sub proc_dates { my $self = shift; my $date = shift; my @parts = split / /, $date; my %datehash; my @dateparts = split /-/, $parts[0]; $datehash{year} = $dateparts[0]; $datehash{month} = $dateparts[1]; $datehash{day} = $dateparts[2] ; my @timeparts = split /:/, $parts[1]; $datehash{hour} = $timeparts[0]; $datehash{minute} = $timeparts[1]; $datehash{second} = $timeparts[2]; return %datehash; } sub proc_input_dates { my $self = shift; my $date = shift; my @parts = split / /, $date; my %datehash; my @dateparts = split /\//, $parts[0]; $datehash{year} = $dateparts[2]; $datehash{month} = $dateparts[0]; $datehash{day} = $dateparts[1] ; my @timeparts = split /:/, $parts[1]; $datehash{hour} = $timeparts[0]; $datehash{minute} = $timeparts[1]; $datehash{second} = $timeparts[2]; return %datehash; } 1;