package WebGUI::Auth;

=head1 LEGAL

 -------------------------------------------------------------------
  WebGUI is Copyright 2001-2009 Plain Black Corporation.
 -------------------------------------------------------------------
  Please read the legal notices (docs/legal.txt) and the license
  (docs/license.txt) that came with this distribution before using
  this software.
 -------------------------------------------------------------------
  http://www.plainblack.com                     info@plainblack.com
 -------------------------------------------------------------------

=cut

use CGI::Util qw(rearrange);
use DBI;
use strict qw(subs vars);
use Tie::IxHash;
use WebGUI::HTML;
use WebGUI::HTMLForm;
use WebGUI::International;
use WebGUI::Macro;
use WebGUI::TabForm;
use WebGUI::Asset::Template;
use WebGUI::Utility;
use WebGUI::User;
use WebGUI::Operation::Shared;
use WebGUI::Operation::Profile;
use WebGUI::Workflow::Instance;
use WebGUI::Shop::AddressBook;
use WebGUI::Inbox;
use WebGUI::Friends;
use URI;

# Profile field name for the number of times the showMessageOnLogin has been
# seen.
my $LOGIN_MESSAGE_SEEN  = 'showMessageOnLoginSeen';

=head1 NAME

Package WebGUI::Auth

=head1 DESCRIPTION

An abstract class for all authentication modules to extend.

=head1 SYNOPSIS

 use WebGUI::Auth;
 our @ISA = qw(WebGUI::Auth);

=head1 METHODS

These methods are available from this class:

=cut

#-------------------------------------------------------------------
sub _isDuplicateUsername {
	my $self = shift;
	my $username = shift;
	#Return false if the user is already logged in, but not changing their username.
	return 0 if($self->isRegistered && $self->session->user->username eq $username);
	my ($otherUser) = $self->session->db->quickArray("select count(*) from users where username=".$self->session->db->quote($username));
	return 0 if !$otherUser;
	my $i18n = WebGUI::International->new($self->session);
	$self->error('<li>'.sprintf($i18n->get(77), $username,$username,$username,$self->session->datetime->epochToHuman(time(),"%y")).'</li>');
	return 1;
}

#-------------------------------------------------------------------

=head2 _isValidUsername ( username )

Validates the username passed in.

=cut

sub _isValidUsername {
	my $self = shift;
	my $username = shift;
	my $error = "";

	return 1 if($self->isRegistered && $self->session->user->username eq $username);

    my $i18n = WebGUI::International->new($self->session);

    my $filteredUsername = WebGUI::HTML::filter($username, 'all');
    if ($username ne $filteredUsername) {
        $error .= '<li>' . $i18n->get('username no html') . '</li>';
    }

	if ($username =~ /^\s/ || $username =~ /\s$/) {
		$error .= '<li>'.$i18n->get(724).'</li>';
	}
	if ($username eq "") {
		$error .= '<li>'.$i18n->get(725).'</li>';
	}
	$self->error($error);
	return $error eq "";
}

#-------------------------------------------------------------------
sub _logLogin {
	my $self = shift;
    $self->timeRecordSession;
	$self->session->db->write("insert into userLoginLog values (?,?,?,?,?,?,?)",
		[ 
            $_[0],
            $_[1],
            time(),
            $self->session->env->getIp,
            $self->session->env->get("HTTP_USER_AGENT"),
            $self->session->getId,
            time(),
        ]
    );
}


#-------------------------------------------------------------------

=head2 authenticate ( )

Superclass method that performs standard login routines.  This method returns true if login success, otherwise returns false.

=cut

sub authenticate {
	my $self = shift;
	my $username = shift;
	my $i18n = WebGUI::International->new($self->session);
	my $user = $self->session->db->quickHashRef("select userId,authMethod,status from users where username=".$self->session->db->quote($username));
	my $uid = $user->{userId};
	#If userId does not exist or is not active, fail login
	if (!$uid) {
		$self->authenticationError;
		return 0;
	} elsif($user->{status} ne 'Active') {
		$self->error($i18n->get(820));
		$self->_logLogin($uid, "failure");
		return 0;
	}

	#Set User Id
	$self->user(WebGUI::User->new($self->session,$uid));
	return 1;
}

#-------------------------------------------------------------------

=head2 authenticationError

This subroutine is called by authenticate and its subclasses to make
sure these subroutines return the same errormessage on login failure.
Different errormessages would reveil if a username exists after
which only the password has to be guessed by brute force for example.

=cut

sub authenticationError {
	my $self = shift;
	my $i18n = WebGUI::International->new($self->session);
	return ($self->error('<li>'.$i18n->get(68).'</li>'));
}

#-------------------------------------------------------------------

=head2 authMethod ( [authMethod] )

Gets or sets the authMethod in the Auth Object.  Returns 'WebGUI' as the
default method if a user has been created without an authMethod.

=head3 authMethod

   A string which sets the auth method for an instance of this class

=cut

sub authMethod {
	my $self = shift;
	if (!$_[0]) {
        return $self->{authMethod} || 'WebGUI';
    }
	$self->{authMethod} = $_[0];
}

#-------------------------------------------------------------------

=head2 createAccount ( method [,vars] )

Superclass method that performs general functionality for creating new accounts.

=head3 method

Auth method that the form for creating users should call

=head3 vars

Array ref of template vars from subclass

=cut

sub createAccount {
    my $self    = shift;
    my $method  = shift;
    my $vars    = shift;
    my $i18n    = WebGUI::International->new($self->session);
    $vars->{title} = $i18n->get(54);
    
    $vars->{'create.form.header'}
        = WebGUI::Form::formHeader($self->session)
        . WebGUI::Form::hidden($self->session,{"name"=>"op","value"=>"auth"})
        . WebGUI::Form::hidden($self->session,{"name"=>"method","value"=>$method})
        ;
    
    # User Defined Options
    my $userInvitation = $self->session->setting->get('inboxInviteUserEnabled');
    $vars->{'create.form.profile'} = [];
    foreach my $field (@{WebGUI::ProfileField->getRegistrationFields($self->session)}) {
        my $id         = $field->getId;
        my $label      = $field->getLabel;
        my $required   = $field->isRequired;
        
        my $properties = {};
        if ($required) {
            my $fieldValue = $self->session->form->process($field->getId,$field->get("fieldType"));
            $properties->{extras} = $self->getExtrasStyle($fieldValue);
        }

        my $formField;
        # Get the default email from the invitation
        if ($field->get('fieldName') eq "email" && $userInvitation ) {
            my $code = $self->session->form->get('code')
                    || $self->session->form->get('uniqueUserInvitationCode');
            my $defaultValue 
                = $self->session->db->quickScalar(
                    'SELECT email FROM userInvitations WHERE inviteId=?',
                    [$code]
                );
            $vars->{'create.form.header'} .= WebGUI::Form::hidden($self->session, {name=>"uniqueUserInvitationCode", value=>$code});
            $formField   = $field->formField($properties, undef, undef, undef, $defaultValue);
        }
        else {
            $formField   = $field->formField($properties);
        }
       

        # Old-style field loop.
        push @{$vars->{'create.form.profile'}}, { 
            'profile.formElement'       => $formField,
            'profile.formElement.label' => $label,
            'profile.required'          => $required,
        };

        # Individual field template vars.
        my $prefix = 'create.form.profile.'.$id.'.';
        $vars->{ $prefix . 'formElement'        } = $formField;
        $vars->{ $prefix . 'formElement.label'  } = $label;
        $vars->{ $prefix . 'required'           } = $required;
    }

    $vars->{'create.form.submit'} = WebGUI::Form::submit($self->session,{});
    $vars->{'create.form.footer'} = WebGUI::Form::formFooter($self->session,);

    $vars->{'login.url'} = $self->session->url->page('op=auth;method=init');
    $vars->{'login.label'} = $i18n->get(58);

    return WebGUI::Asset::Template->new($self->session,$self->getCreateAccountTemplateId)->process($vars);
}

#-------------------------------------------------------------------

=head2 createAccountSave ( username,properties [,password,profile] )

Superclass method that performs general functionality for saving new accounts.  Based
on various settings and user actions, it may return output that should be displayed
to the user.

=head3 username

Username for the account being created

=head3 properties

Properties from the subclass that should be saved as authentication parameters

=head3 password

Password entered by the user.  This is only used in for sending the user a notification by email of his/her username/password

=head3 profile

Hashref of profile values returned by the function WebGUI::User::validateProfileDataFromForm($fields);

=cut

sub createAccountSave {
	my $self = shift;
	my $username = $_[0];
	my $properties = $_[1];
	my $password = $_[2];
	my $profile = $_[3];
	
	my $i18n = WebGUI::International->new($self->session);
	
	
	my $u = WebGUI::User->new($self->session,"new");
	$self->user($u);
	my $userId = $u->userId;
	$u->username($username);
	$u->authMethod($self->authMethod);
	if (!$profile->{'language'} && $self->session->scratch->getLanguageOverride) {
	    $profile->{'language'} = $self->session->scratch->getLanguageOverride;
	}
	$u->karma($self->session->setting->get("karmaPerLogin"),"Login","Just for logging in.") if ($self->session->setting->get("useKarma"));
	$u->updateProfileFields($profile) if ($profile);
    #Update the shop address
    my $address          = {};
    my $address_mappings = WebGUI::Shop::AddressBook->getProfileAddressMappings;
    foreach my $fieldId (keys %$profile) {
        #set the shop address fields
        my $address_key          = $address_mappings->{$fieldId};
        $address->{$address_key} = $profile->{$fieldId} if ($address_key);
    }

    #Update or create and update the shop address
    if ( keys %$address ) {
        $address->{'isProfile'        } = 1;

        #Get home address only mappings to avoid creating addresses with just firstName, lastName, email
        my %home_address_map = %{$address_mappings};
        foreach my $exclude ( qw{ firstName lastName email } ) {
            delete $home_address_map{$exclude};
        }
        #Add the profile address for the user if there are homeAddress fields
        if( grep { $address->{$_} } values %home_address_map ) {
            #Create the address book for the user
            my $addressBook    = WebGUI::Shop::AddressBook->newByUserId($self->session,$userId);
            $address->{label} = "Profile Address";
            my $new_address = $addressBook->addAddress($address);
            #Set this as the default address if one doesn't already exist
            $addressBook->update( { defaultAddressId => $new_address->getId } );
        }
    }

    $self->saveParams($userId,$self->authMethod,$properties);

	if ($self->getSetting("sendWelcomeMessage")){
        my $var;
        $var->{welcomeMessage}      = $self->getSetting("welcomeMessage");
        $var->{newUser_username}    = $username;
        $var->{newUser_password}    = $password;
        my $message = WebGUI::Asset::Template->new($self->session,$self->getSetting('welcomeMessageTemplate'))->process($var);
        WebGUI::Macro::process($self->session,\$message);
        WebGUI::Inbox->new($self->session)->addMessage({
            message => $message,
			subject	=> $i18n->get(870),
			userId	=> $self->userId,
            status  => 'completed',
		});
	}

	$self->session->user({user=>$u});
	$self->_logLogin($userId,"success");

	if ($self->session->setting->get("runOnRegistration")) {
		WebGUI::Workflow::Instance->create($self->session, {
			workflowId=>$self->session->setting->get("runOnRegistration"),
			methodName=>"new",
			className=>"WebGUI::User",
			parameters=>$self->session->user->userId,
			priority=>1
			})->start;
	}

    ##Finalize the record in the user invitation table.
    my $inviteId = $self->session->form->get('uniqueUserInvitationCode');
    if ($inviteId) {
        $self->session->db->setRow('userInvitations','inviteId',{
            inviteId    => $inviteId,
            newUserId   => $u->userId,
            dateCreated => WebGUI::DateTime->new($self->session, time)->toMysqlDate,
        });
        #Get the invite record
        my $inviteRecord = $self->session->db->getRow('userInvitations','inviteId',$inviteId);
        #Get the user
        my $inviteUser   = WebGUI::User->new($self->session,$inviteRecord->{userId});
        #Automatically add the friend that invited the user and vice versa if the friend has friends enabled
        if($inviteUser->acceptsFriendsRequests($u)) {
            my $friends  = WebGUI::Friends->new($self->session,$u);
            $friends->add([$inviteUser->userId]);
        }
    }

    # If we have something to do after login, do it
    if ( $self->session->setting->get( 'showMessageOnLogin' ) ) {
        return $self->showMessageOnLogin;
    }
    elsif ($self->session->form->get('returnUrl')) {
        $self->session->http->setRedirect( $self->session->form->get('returnUrl') );
        $self->session->scratch->delete("redirectAfterLogin");
    }
    elsif ($self->session->scratch->get("redirectAfterLogin")) {
        my $url = $self->session->scratch->delete("redirectAfterLogin");
        $self->session->http->setRedirect($url);
        return undef;
    } 
    else {
        $self->session->http->setStatus(201,"Account Registration Successful");
    }

	return undef;
}

#-------------------------------------------------------------------

=head2 deactivateAccount ( method )

Superclass method that displays a confirm message for deactivating a user's account.

=head3 method

Auth method that the form for creating users should call

=cut

sub deactivateAccount {
	my $self = shift;
	my $method = $_[0];
	return $self->session->privilege->vitalComponent() if($self->isVisitor || $self->isAdmin);
	return $self->session->privilege->adminOnly() if(!$self->session->setting->get("selfDeactivation"));
	my $i18n = WebGUI::International->new($self->session);
	my %var;
  	$var{title} = $i18n->get(42);
   	$var{question} =  $i18n->get(60);
   	$var{'yes.url'} = $self->session->url->page('op=auth;method='.$method);
	$var{'yes.label'} = $i18n->get(44);
   	$var{'no.url'} = $self->session->url->page();
	$var{'no.label'} = $i18n->get(45);
	return WebGUI::Asset::Template->new($self->session,$self->getDeactivateAccountTemplateId)->process(\%var);
}

#-------------------------------------------------------------------

=head2 deactivateAccountConfirm ( )

Superclass method that performs general functionality for deactivating accounts.

=cut

sub deactivateAccountConfirm {
    my $self = shift;
    
    # Cannot deactivate "Visitor" or "Admin" users this way
    return $self->session->privilege->vitalComponent 
        if $self->isVisitor || $self->isAdmin;

    my $i18n    = WebGUI::International->new($self->session);

    # Change user's status
    my $user    = $self->user;
    $user->status("Selfdestructed");
    
    # TODO: Fix displayLogin in all subclasses to have the same prototype. THIS WILL BREAK API!
    # Show the login form 
    #$self->logout;
    #return $self->displayLogin(undef, {
        #'login.message' => sprintf( $i18n->get("deactivateAccount success"), $user->username )
    #});

    $self->logout;
    return undef;
}

#-------------------------------------------------------------------

=head2 deleteParams (  )

Removes the user's authentication parameters from the database for all 
authentication methods. This is primarily useful when deleting the user's 
account.

=cut

sub deleteParams {
	my $self = shift;
	$self->session->db->write("delete from authentication where userId=".$self->session->db->quote($self->userId));
}

#-------------------------------------------------------------------

=head2 deleteSingleParam ( )

Removes a single authentication parameter from the database.

=cut

sub deleteSingleParam {
       my $self = shift;
       my ($userId, $authMethod, $fieldName) = @_;

       $self->session->db->write('delete from authentication where userId = ? and authMethod = ? and fieldName = ?', [$userId, $authMethod, $fieldName]);

}

#-------------------------------------------------------------------

=head2 displayAccount ( method [,vars] )

Superclass method that performs general functionality for viewing editable fields related to a user's account.

=head3 method

Auth method that the form for updating a user's account should call

=head3 vars

Array ref of template vars from subclass

=cut

sub displayAccount {
	my $self   = shift;
	my $method = shift;
	my $vars   = shift;
    
	my $i18n = WebGUI::International->new($self->session);
	$vars->{title} = $i18n->get(61);

	$vars->{'account.form.header'} = WebGUI::Form::formHeader($self->session,{});
	$vars->{'account.form.header'} .= WebGUI::Form::hidden($self->session,{"name"=>"op","value"=>"auth"});
	$vars->{'account.form.header'} .= WebGUI::Form::hidden($self->session,{"name"=>"method","value"=>$method});
	if ($self->session->setting->get("useKarma")) {
		$vars->{'account.form.karma'} = $self->session->user->karma;
		$vars->{'account.form.karma.label'} = $i18n->get(537);
	}
	$vars->{'account.form.submit'} = WebGUI::Form::submit($self->session,{});
	$vars->{'account.form.footer'} = WebGUI::Form::formFooter($self->session,);
    
    ########### ACCOUNT SHUNT
    #The following is a shunt which allows the displayAccount page to be displayed in the
    #Account system.  This shunt will be replaced in WebGUI 8 when the API can be broken
    my $output = WebGUI::Asset::Template->new($self->session,$self->getAccountTemplateId)->process($vars);
    #If the account system is calling this method, just return the template
    my $op = $self->session->form->get("op");
    if($op eq "account") {
        return $output;
    }
    #Otherwise wrap the template into the account layout
    my $instance = WebGUI::Content::Account->createInstance($self->session,"user");
    return $instance->displayContent($output,1);
}

#-------------------------------------------------------------------

=head2 displayLogin ( [method,vars] )

Superclass method that performs general functionality for creating new accounts.

=head3 method

Auth method that the form for performing the login routine should call

=head3 vars

Array ref of template vars from subclass

=cut

sub displayLogin {
    my $self = shift;
    my $method = $_[0] || "login";
    my $vars = $_[1];
    # Automatically set redirectAfterLogin unless we've linked here directly
    # or it's already been set to perform another operation
    unless (
        $self->session->form->process("op") eq "auth" 
            || ($self->session->scratch->get("redirectAfterLogin") =~ /op=\w+/) 
        ) {
        my $returnUrl
            = $self->session->form->get('returnUrl')
            || $self->session->url->page( $self->session->env->get('QUERY_STRING') )
            ;
        $self->session->scratch->set("redirectAfterLogin", $returnUrl);
    }
    my $i18n = WebGUI::International->new($self->session);
    $vars->{title} = $i18n->get(66);
    my $action;
    if ($self->session->config->get('sslEnabled') && $self->session->setting->get("encryptLogin")) {
        my $uri = URI->new($self->session->url->page(undef,1));
        $uri->scheme('https');
        $uri->host_port($uri->host);
        $action = $uri->as_string;
    }
    $vars->{'login.form.header'} = WebGUI::Form::formHeader($self->session,{action=>$action});
    $vars->{'login.form.hidden'} = WebGUI::Form::hidden($self->session,{"name"=>"op","value"=>"auth"});
    $vars->{'login.form.hidden'} .= WebGUI::Form::hidden($self->session,{"name"=>"method","value"=>$method});
    $vars->{'login.form.username'} = WebGUI::Form::text($self->session,{"name"=>"username"});
    $vars->{'login.form.username.label'} = $i18n->get(50);
    $vars->{'login.form.password'} = WebGUI::Form::password($self->session,{"name"=>"identifier"});
    $vars->{'login.form.password.label'} = $i18n->get(51);
    $vars->{'login.form.submit'} = WebGUI::Form::submit($self->session,{"value"=>$i18n->get(52)});
    $vars->{'login.form.footer'} = WebGUI::Form::formFooter($self->session,);
    $vars->{'anonymousRegistration.isAllowed'} = ($self->session->setting->get("anonymousRegistration"));
    $vars->{'createAccount.url'} = $self->session->url->page('op=auth;method=createAccount');
    $vars->{'createAccount.label'} = $i18n->get(67);
    my $template = $self->getLoginTemplate;
    return $template->process($vars);
}

#-------------------------------------------------------------------

=head2 editUserForm (  )

Creates user form elements specific to this Auth Method.

=cut

sub editUserForm {
	#Added for interface purposes only.  Needs to be implemented in the subclass.
}

#-------------------------------------------------------------------

=head2 editUserFormSave ( properties )

Saves user elements unique to this authentication method

=cut

sub editUserFormSave {
	my $self = shift;
	$self->saveParams($self->userId,$self->authMethod,$_[0]);
}

#-------------------------------------------------------------------

=head2 editUserSettingsForm ( )

You need to override this method in your auth module. It needs to return a the rows in a form for the stuff you want to be configured through webgui settings.

=cut

sub editUserSettingsForm {
}

#-------------------------------------------------------------------

=head2 editUserSettingsFormSave ( )

You need to override this method in your auth module. It's the save for the editUserSettingsFormSave method.

=cut

sub editUserSettingsFormSave {
}

#-------------------------------------------------------------------

=head2 error ( [errorMsg] )

Sets or returns the error currently stored in the object

=cut

sub error {
	my $self = shift;
	return $self->{error} if (!$_[0]);
	$self->{error} = $_[0];
}

#-------------------------------------------------------------------

=head2 getAccountTemplateId ( )

This method should be overridden by the subclass and should return the template ID for the display/edit account screen.

=cut

sub getAccountTemplateId {
	return "PBtmpl0000000000000010";
}

#-------------------------------------------------------------------

=head2 getCreateAccountTemplateId ( )

This method should be overridden by the subclass and should return the template ID for the create account screen.

=cut

sub getCreateAccountTemplateId {
	return "PBtmpl0000000000000011";
}

#-------------------------------------------------------------------

=head2 getDeactivateAccountTemplateId ( )

This method should be overridden by the subclass and should return the template ID for the deactivate account screen.

=cut

sub getDeactivateAccountTemplateId {
	return "PBtmpl0000000000000057";
}

#-------------------------------------------------------------------

=head2 getDefaultLoginTemplateId ( )

This method should be overridden by the subclass and should return the default template ID for the login screen.

=cut

sub getDefaultLoginTemplateId {
	return "PBtmpl0000000000000013";
}

#-------------------------------------------------------------------

=head2 getExtrasStyle ( )

This method returns the proper field to display for required fields.

=cut

sub getExtrasStyle {
    my $self  = shift;
    my $value = shift;
    
    my $requiredStyleOff = q{class="authfield_required_off"}; 
    my $requiredStyle    = q{class="authfield_required"};
    my $errorStyle       = q{class="authfield_error"};     #Required Field Not Filled In and Error Returend

    return $errorStyle if($self->error && $value eq "");
    return $requiredStyle unless($value);
    return $requiredStyleOff;
}

#-------------------------------------------------------------------

=head2 getLoginTemplate ( )

Returns a WebGUI::Asset::Template object for the login template.  If the configured
template cannot be used, then it returns a default template object.

=cut

sub getLoginTemplate {
    my $self    = shift;
    my $session = $self->session;
    my $templateId = $self->getLoginTemplateId;
    my $template   = WebGUI::Asset::Template->newByDynamicClass($session, $templateId);
    if (!$template) {
        $templateId = $self->getDefaultLoginTemplateId;
        $template   = WebGUI::Asset::Template->newByDynamicClass($session, $templateId);
    }
	return $template;
}

#-------------------------------------------------------------------

=head2 getLoginTemplateId ( )

This method should be overridden by the subclass and should return the template ID for the login screen.

=cut

sub getLoginTemplateId {
	return "PBtmpl0000000000000013";
}

#-------------------------------------------------------------------

=head2 getParams ( )

Returns a hash reference with the user's authentication information.  This method uses data stored in the instance of the object.

=cut

sub getParams {
	my $self       = shift;
	my $userId     = $_[0] || $self->userId;
	my $authMethod = $_[1] || $self->authMethod;
	return $self->session->db->buildHashRef("select fieldName, fieldData from authentication where userId=".$self->session->db->quote($userId)." and authMethod=".$self->session->db->quote($authMethod));
}

#-------------------------------------------------------------------

=head2 getSetting (  setting  )

Returns a setting for this authMethod instance.  If none is specified, returns the system authMethod setting

=head3 setting

Specify a setting to retrieve

=cut

sub getSetting {
	my $self = shift;
	my $setting = $_[0];
	$setting = lc($self->authMethod).ucfirst($setting);
	return $self->session->setting->get($setting);
}

#-------------------------------------------------------------------

=head2 init ( )

Initialization function for these auth routines.  Default is a superclass function called displayLogin.
Override this method in your subclass to change the initialization for custom authentication methods

=cut

sub init {
	my $self = shift;
	return $self->displayLogin;
}

#-------------------------------------------------------------------

=head2 isAdmin ()

Returns 1 if the user is user 3 (admin).

=cut

sub isAdmin {
	my $self = shift;
	return $self->userId eq '3';
}

#-------------------------------------------------------------------

=head2 isCallable ( method )

Returns whether or not a method is callable

=cut

sub isCallable {
	my $self = shift;
	return 1 if isIn($_[0],@{$self->{callable}});
        return 1 if $self->can( 'www_' . $_[0] );
        return 0;
}

#-------------------------------------------------------------------

=head2 isRegistered ()

Returns 1 if the user is not a visitor.

=cut

sub isRegistered {
	my $self = shift;
	return $self->userId ne '1';
}

#-------------------------------------------------------------------

=head2 isVisitor ()

Returns 1 if the user is a visitor.

=cut

sub isVisitor {
	my $self = shift;
	return $self->userId eq '1';
}

#-------------------------------------------------------------------

=head2 login ( )

Superclass method that performs standard login routines.  This is what should happen after a user has been authenticated.
Authentication should always happen in the subclass routine.

Open version tag is reclaimed if user is in site wide or singlePerUser mode.

=cut

sub login {
	my $self = shift;

	#Create a new user
	my $uid = $self->userId;
	my $u = WebGUI::User->new($self->session,$uid);
   	$self->session->user({user=>$u});
	$u->karma($self->session->setting->get("karmaPerLogin"),"Login","Just for logging in.") if ($self->session->setting->get("useKarma"));
	$self->_logLogin($uid,"success");

        # Run on login
	my $command = $self->session->config->get("runOnLogin");
	if ($command ne "") {
		WebGUI::Macro::process($self->session,\$command);
		my $error = qx($command);
		$self->session->errorHandler->warn($error) if $error;
	}
	

    # Set the proper redirect
    if ( $self->session->setting->get( 'showMessageOnLogin' ) 
        && $self->user->profileField( $LOGIN_MESSAGE_SEEN ) 
            < $self->session->setting->get( 'showMessageOnLoginTimes' ) 
    ) {
        return $self->showMessageOnLogin;
    }
    elsif ( $self->session->form->get('returnUrl') ) {
		$self->session->http->setRedirect( $self->session->form->get('returnUrl') );
	  	$self->session->scratch->delete("redirectAfterLogin");
    }
	elsif ( my $url = $self->session->scratch->delete("redirectAfterLogin") ) {
		$self->session->http->setRedirect($url);
	}
    elsif ( $self->session->setting->get("redirectAfterLoginUrl") ) {
        $self->session->http->setRedirect($self->session->setting->get("redirectAfterLoginUrl"));
        $self->session->scratch->delete("redirectAfterLogin");
    }
	elsif ($self->session->config->get('sslEnabled') && $self->session->setting->get('encryptLogin')) {
		my $currentUrl = URI->new($self->session->url->page(undef,1));
        $currentUrl->scheme('http');
        $currentUrl->port($self->session->config->get('webServerPort') || 80);
		$self->session->http->setRedirect($currentUrl->canonical->as_string);
	}

    # Get open version tag. This is needed if we want
    # to reclaim a version right after login (singlePerUser and siteWide mode)
    # and to have the correct version displayed.
    WebGUI::VersionTag->getWorking($self->session(), q{noCreate});

	return undef;
}

#-------------------------------------------------------------------

=head2 logout ( )

Superclass method that performs standard logout routines.

=cut

sub logout {
	my $self = shift;
	$self->session->var->end($self->session->var->get("sessionId"));
	$self->session->user({userId=>'1'});
	my $u = WebGUI::User->new($self->session,1);
	$self->{user} = $u;
	
	my $command = $self->session->config->get("runOnLogout");
    if ($command ne "") {
       WebGUI::Macro::process($self->session,\$command);
       my $error = qx($command);
       $self->session->errorHandler->warn($error) if $error;
    }

    # Do not allow caching of the logout page (to ensure the page gets requested)
    $self->session->http->setCacheControl( "none" );
   
	return undef;
}

#-------------------------------------------------------------------

=head2 new ( session, authMethod [,userId,callable] )

Constructor.

=head3 session

=head3 authMethod

This object's authentication method

=head3 userId

userId for the user requesting authentication.  This defaults to $self->session->user->userId

=head3 callable

Array reference of methods allowed to be called externally;

=cut

sub new {
	my $self = {};
	my $class = shift;
	$self->{_session} = shift;
	$self->{authMethod} = shift;
	my $userId = shift || $self->{_session}->user->userId;
	# Can't do this... if you're updating the account of a user that's not you, this will not work
	#$self->{user} = $self->{_session}->user;
	$self->{user} = WebGUI::User->new($self->{_session}, $userId);
	$self->{error} = "";
	$self->{profile} = ();
	$self->{warning} = "";
	my $call = shift;
	my @callable = ('init', 'showMessageOnLogin', @{$call});
	$self->{callable} = \@callable;
	bless $self, $class;
	return $self;
}

#-------------------------------------------------------------------

=head2 profile ( )

Sets or returns the Profile hash for a user.

=cut

sub profile {
	my $self = shift;
	return $self->{profile} if (!$_[0]);
	$self->{profile} = $_[0];
}

#-------------------------------------------------------------------

sub session {
	my $self = shift;
	return $self->{_session};
}

#-------------------------------------------------------------------

=head2 setCallable ( callableMethods )

adds elements to the callable routines list.  This list determines whether or not a method in this instance is 
allowed to be called externally

=head3 callableMethods

Array reference containing a list of methods for this authentication instance that can be called externally

=cut

sub setCallable {
	my $self = shift;
	my @callable = @{$self->{callable}};
	@callable = (@callable,@{$_[0]});
    $self->{callable} = \@callable;
}

#-------------------------------------------------------------------

=head2 saveParams ( userId, authMethod, data )

Saves the user's authentication parameters to the database.

=head3 userId

Specify a user id.

=head3 authMethod

Specify the authentication method to save these paramaters under.

=head3 data

A hash reference containing parameter names and values to be saved.

=cut

sub saveParams {
	my $self = shift;
	my ($uid, $authMethod, $data) = @_;
	foreach (keys %{$data}) {
		$self->session->db->write("delete from authentication where userId=".$self->session->db->quote($uid)." and authMethod=".$self->session->db->quote($authMethod)." and fieldName=".$self->session->db->quote($_));
		$self->session->db->write("insert into authentication (userId,authMethod,fieldData,fieldName) values (".$self->session->db->quote($uid).",".$self->session->db->quote($authMethod).",".$self->session->db->quote($data->{$_}).",".$self->session->db->quote($_).")");
	}
}

#----------------------------------------------------------------------------

=head2 showMessageOnLogin ( )

Show the requested message after the user logs in. Add another tally to the 
number of times the message has been displayed. Show a link to the next
stage for the user.

=cut

sub showMessageOnLogin {
    my $self        = shift;
    my $i18n        = WebGUI::International->new( $self->session, 'Auth' );

    # Increment the number of time seen.
    $self->user->profileField( $LOGIN_MESSAGE_SEEN, 
        $self->user->profileField( $LOGIN_MESSAGE_SEEN ) + 1
    );

    # Show the message, processing for macros
    my $output  =  $self->session->setting->get( 'showMessageOnLoginBody' );
    WebGUI::Macro::process( $self->session, \$output );

    # Add the link to continue
    my $session = $self->session;
    my $redirectUrl =  $session->form->get( 'returnUrl' )
                    || $session->setting->get("redirectAfterLoginUrl")
                    || $session->scratch->get( 'redirectAfterLogin' )
                    || $session->url->getBackToSiteURL
                    ;

    if ($self->session->config->get('sslEnabled') && $session->setting->get('encryptLogin') && ( ! $redirectUrl =~ /^http/)) {
        ##A scheme-less URL has been supplied.  We need to make it an absolute one
        ##with a non-encrypted scheme.  Otherwise the user will stay in SSL mode.
        ##We assume that the user put the gateway URL into their URL.
        my $uri = URI->new_abs($redirectUrl, $session->url->getSiteURL);
        $uri->scheme('http');
        $redirectUrl = $uri->as_string;
    }
    $output     .= '<p><a href="' . $redirectUrl . '">' . $i18n->get( 'showMessageOnLogin return' ) 
                .  '</a></p>'
                ;

    # No matter what, we won't be redirecting after this
    $session->scratch->delete( 'redirectAfterLogin' );

    return $output;
}

#----------------------------------------------------------------------------

=head2 timeRecordSession 

Record the last page viewed and the time viewed for the user

=cut

sub timeRecordSession {
    my $self = shift;
    my ($nonTimeRecordedRows) = $self->session->db->quickArray("select count(*) from userLoginLog where lastPageViewed = timeStamp and sessionId = ? ", [$self->session->getId] );
    if ($nonTimeRecordedRows eq "1") {
        # We would normally expect to only find one entry
        $self->session->db->write("update userLoginLog set lastPageViewed = (select lastPageView from userSession where sessionId = ?) where lastPageViewed = timeStamp and sessionId = ? ",
            [ $self->session->getId,
            $self->session->getId]);
    } elsif ($nonTimeRecordedRows eq "0") {
        # Do nothing
    } else {
        # If something strange happened and we ended up with > 1 matching rows, cut our losses and remove offending userLoginLog rows (otherwise we
        # could end up with ridiculously long user recorded times)
        $self->session->errorHandler->warn("More than 1 old userLoginLog rows found, removing offending rows");
        $self->session->db->write("delete from userLoginLog where lastPageViewed = timeStamp and sessionId = ? ", [$self->session->getId] );
    }
}

#-------------------------------------------------------------------

=head2 user ( [user] )

Sets or Returns the user object stored in the wobject

=cut

sub user {
	my $self = shift;
	return $self->{user} if (!$_[0]);
	$self->{user} = $_[0];
}

#-------------------------------------------------------------------

=head2 userId ( )

Returns the userId currently stored in the object

=cut

sub userId {
	my $self = shift;
	my $u = $self->user;
	return $u->userId;
}

#-------------------------------------------------------------------

=head2 username ( )

Returns the username currently stored in the object

=cut

sub username {
	my $self = shift;
	my $u = $self->user;
	return $u->username;
}

#-------------------------------------------------------------------

=head2 validUsername ( username )

Validates the a username.

=cut

sub validUsername {
	my $self = shift;
	my $username = shift;
	my $error = "";

	if ($self->_isDuplicateUsername($username)) {
		$error .= $self->error;
	}

	if (!$self->_isValidUsername($username)) {
		$error .= $self->error;
	}

	$self->error($error);
	return $error eq "";
}

#-------------------------------------------------------------------

=head2 warning ( [warningMsg] )

Sets or Returns a warning in the object

=cut

sub warning {
	my $self = shift;
	return $self->{warning} if (!$_[0]);
	$self->{warning} = $_[0];
}

1;
