webgui/lib/WebGUI/Session.pm
2003-11-15 11:53:07 +00:00

645 lines
18 KiB
Perl

package WebGUI::Session;
=head1 LEGAL
-------------------------------------------------------------------
WebGUI is Copyright 2001-2003 Plain Black LLC.
-------------------------------------------------------------------
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;
use Date::Calc;
use DBI;
use Exporter;
use Parse::PlainConfig;
use strict;
use Tie::CPHash;
use WebGUI::ErrorHandler;
use WebGUI::SQL;
use WebGUI::Utility;
our @ISA = qw(Exporter);
our @EXPORT = qw(%session);
our %session = ();
tie %session, 'Tie::CPHash';
=head1 NAME
Package WebGUI::Session
=head1 DESCRIPTION
This package is the heart and lifeblood of WebGUI. Without it WebGUI could not exist. By using this package a package gains access to WebGUI's $session variable which contains everything WebGUI needs to know to operate.
NOTE: It is important to distinguish the difference between a WebGUI session and a user session. A user session is attached to a WebGUI session. A WebGUI session is all of the basic data the WebGUI needs to operate.
TIP: The $session variable is a case-insensitive hash. The contents of the has vary, but can be seen by adding debug=1 to the end of any WebGUI URL while logged in as an admin user.
=head1 SYNOPSIS
use WebGUI::Session;
WebGUI::Session::close();
WebGUI::Session::convertVisitorToUser($sessionId,$userId);
WebGUI::Session::deleteScratch($name);
WebGUI::Session::end($sessionId);
$header = WebGUI::Session::httpHeader();
$header = WebGUI::Session::httpRedirect($url);
WebGUI::Session::open($webguiRoot,$configFilename);
WebGUI::Session::refreshPageInfo($pageId);
WebGUI::Session::refreshSessionVars($sessionId);
WebGUI::Session::refreshUserInfo($userId);
WebGUI::Session::setCookie($name,$value);
WebGUI::Session::setScratch($name,$value);
WebGUI::Session::start($userId);
=head1 METHODS
These subroutines are available from this package:
=cut
#-------------------------------------------------------------------
sub _setupPageInfo {
my (%page, $pageId, $pageName);
tie %page, 'Tie::CPHash';
($pageId) = $_[0];
if ($pageId eq "") {
$pageName = lc($ENV{PATH_INFO});
$pageName =~ s/\/$//;
$pageName =~ s/\///;
$pageName =~ s/\'//;
$pageName =~ s/\"//;
if ($pageName ne "") {
($pageId) = WebGUI::SQL->quickArray("select pageId from page where urlizedTitle='".$pageName."'");
if ($pageId eq "") {
$pageId = $session{setting}{notFoundPage};
if($ENV{"MOD_PERL"}) {
my $r = Apache->request;
if(defined($r)) {
$r->custom_response(404, $session{page}{url} );
$r->status(404);
}
} else {
$session{header}{status} = '404';
}
}
} else {
$pageId = $session{setting}{defaultPage};
}
}
%page = WebGUI::SQL->quickHash("select * from page where pageId='".$pageId."'");
$page{url} = $session{config}{scripturl}."/".$page{urlizedTitle};
$session{page} = \%page;
}
#-------------------------------------------------------------------
sub _setupSessionVars {
my (%vars, $uid, $encryptedPassword);
tie %vars, 'Tie::CPHash';
if ($_[0] ne "") {
%vars = WebGUI::SQL->quickHash("select * from userSession where sessionId='$_[0]'");
if ($vars{sessionId} ne "") {
$session{scratch} = WebGUI::SQL->buildHashRef("select name,value from userSessionScratch
where sessionId=".quote($_[0]));
if (($session{setting}{proxiedClientAddress} eq "1") && ($ENV{HTTP_X_FORWARDED_FOR} ne "")) {
WebGUI::SQL->write("update userSession set lastPageView="._time().",
lastIP='$ENV{HTTP_X_FORWARDED_FOR}',
expires=".(_time()+$session{setting}{sessionTimeout})
." where sessionId='$_[0]'");
} else {
WebGUI::SQL->write("update userSession set lastPageView="._time().", lastIP='$ENV{REMOTE_ADDR}',
expires=".(_time()+$session{setting}{sessionTimeout})." where sessionId='$_[0]'");
}
} else {
start(1,$_[0]);
}
}
$session{var} = \%vars;
}
#-------------------------------------------------------------------
sub _setupUserInfo {
my (%default, $key, %user, $uid, %profile, $value);
tie %user, 'Tie::CPHash';
$uid = $_[0] || 1;
%user = WebGUI::SQL->quickHash("select * from users where userId='$uid'");
if ($user{userId} eq "") {
_setupUserInfo("1");
} else {
%profile = WebGUI::SQL->buildHash("select userProfileField.fieldName, userProfileData.fieldData
from userProfileData, userProfileField where userProfileData.fieldName=userProfileField.fieldName
and userProfileData.userId='$user{userId}'");
%user = (%user, %profile);
$user{language} = $session{page}{languageId} if ($user{userId} == 1);
%default = WebGUI::SQL->buildHash("select fieldName, dataDefault from userProfileField
where profileCategoryId=4");
foreach $key (keys %default) {
if ($user{$key} eq "") {
$value = eval($default{$key});
if (ref $value eq "ARRAY") {
$user{$key} = $$value[0];
} else {
$user{$key} = $value;
}
}
}
$session{user} = \%user;
if ($session{env}{MOD_PERL}) {
my $r = Apache->request;
if(defined($r)) {
$r->user($session{user}{username});
}
}
$session{user}{alias} = $session{user}{username} if ($session{user}{alias} =~ /^\W+$/);
$session{user}{alias} = $session{user}{username} if ($session{user}{alias} eq "");
}
}
#-------------------------------------------------------------------
sub _time {
return Date::Calc::Date_to_Time(Date::Calc::Today_and_Now());
}
#-------------------------------------------------------------------
sub _loadAuthentication {
foreach my $namespace (@{$session{config}{authMethods}}) {
my $cmd = "use WebGUI::Authentication::".$namespace;
eval($cmd);
if ($@) {
WebGUI::ErrorHandler::warn("Authentication module failed to compile: $namespace. ".$@);
}
}
}
#-------------------------------------------------------------------
sub _loadMacros {
foreach my $key (keys %{$session{config}{macros}}) {
my $cmd = "use WebGUI::Macro::".$session{config}{macros}{$key};
eval($cmd);
WebGUI::ErrorHandler::fatalError("Macro failed to compile: $key.".$@) if($@);
}
}
#-------------------------------------------------------------------
sub _loadWobjects {
foreach my $namespace (@{$session{config}{wobjects}}) {
my $cmd = "use WebGUI::Wobject::".$namespace;
eval($cmd);
if ($@) {
WebGUI::ErrorHandler::warn("Wobject failed to compile: $namespace. ".$@);
}
}
}
#-------------------------------------------------------------------
# This routine returns an unique session Id.
sub _uniqueSessionId {
my $sessionId = crypt((_time()*rand(1000)),rand(99));
my ($isDuplicate) = WebGUI::SQL->buildArray("select count(*) from userSession where sessionId =".quote($sessionId));
if ($isDuplicate) {
return _uniqueSessionId();
} else {
return $sessionId;
}
}
#-------------------------------------------------------------------
=head2 close
Cleans up a WebGUI session information from memory and disconnects from any resources opened by the session.
=cut
sub close {
$session{'dbh'}->disconnect();
undef %session;
$ENV{PATH_INFO} = "/"; #work around to fix a bug in mod_perl (win32)
}
#-------------------------------------------------------------------
=head2 convertVisitorToUser ( sessionId, userId )
Converts a visitor session to a user session.
=over
=item sessionId
The session to convert.
=item userId
The user for the session to become.
=back
=cut
sub convertVisitorToUser {
WebGUI::SQL->write("update userSession set userId=$_[1] where sessionId=".quote($_[0]));
$session{var}{userId} = $_[1];
refreshUserInfo($_[1]);
}
#-------------------------------------------------------------------
=head2 deleteScratch ( name )
Deletes a scratch variable.
=over
=item name
The name of the scratch variable.
=back
=cut
sub deleteScratch {
my ($name) = @_;
return "" unless ($session{var}{sessionId} ne "" && $name ne "");
WebGUI::SQL->write("delete from userSessionScratch where sessionId=".quote($session{var}{sessionId})." and name=".quote($name));
$session{scratch}{$name} = "";
}
#-------------------------------------------------------------------
=head2 end ( sessionId )
Removes the specified user session from memory and database.
=over
=item sessionId
The session to end.
=back
=cut
sub end {
WebGUI::SQL->write("delete from userSession where sessionId='$_[0]'",$session{dbh});
WebGUI::SQL->write("delete from userSessionScratch where sessionId='$_[0]'",$session{dbh});
if ($_[0] eq $session{var}{sessionId}) {
delete $session{user};
delete $session{isInGroup};
delete $session{var};
delete $session{scratch};
}
}
#-------------------------------------------------------------------
=head2 httpHeader ( )
Generates an HTTP header.
=cut
sub httpHeader {
unless ($session{header}{charset}) {
$session{header}{charset} = $session{language}{characterSet} || "ISO-8859-1";
}
if ($session{header}{filename} && $session{header}{mimetype} eq "text/html") {
$session{header}{mimetype} = "application/octet-stream";
}
return $session{cgi}->header(
-type => $session{header}{mimetype}.'; charset='.$session{header}{charset},
-cookie => $session{header}{cookie},
-status => $session{header}{status},
-attachment => $session{header}{filename}
);
}
#-------------------------------------------------------------------
=head2 httpRedirect ( url )
Generates an HTTP header for redirect.
=over
=item url
The URL to redirect to.
=back
=cut
sub httpRedirect {
return $session{cgi}->redirect($_[0]);
}
#-------------------------------------------------------------------
=head2 open ( webguiRoot [ , configFile ] )
Opens a closed ( or new ) WebGUI session.
=over
=item webguiRoot
The path to the WebGUI files.
=item configFile
The filename of the config file that WebGUI should operate from.
=back
=cut
sub open {
my ($key, $config);
###----------------------------
### operating system specific things
$session{os}{name} = $^O;
if ($session{os}{name} =~ /^Win/i) {
$session{os}{type} = "Windowsish";
$session{os}{slash} = "\\";
} else {
$session{os}{type} = "Linuxish";
$session{os}{slash} = "/";
}
###----------------------------
### config variables
$session{config}{webguiRoot} = $_[0];
$session{config}{configFile} = $_[1] || "WebGUI.conf";
$config = Parse::PlainConfig->new('DELIM' => '=',
'FILE' => $session{config}{webguiRoot}.'/etc/'.$session{config}{configFile},
'PURGE' => 1);
foreach ($config->directives) {
$session{config}{$_} = $config->get($_);
}
if (ref $session{config}{authMethods} ne "ARRAY") {
$session{config}{authMethods} = [$session{config}{authMethods}];
}
if( defined( $session{config}{scripturl} ) ) {
# get rid of leading "/" if present.
$session{config}{scripturl} =~ s/^\///;
} else {
# default to the "real" path to script.
$session{config}{scripturl} = $ENV{SCRIPT_NAME};
}
$session{config}{extrasURL} = $session{config}{extrasURL} || $session{config}{extras} || "/extras";
$session{config}{extras} = $session{config}{extras} || $session{config}{extrasURL}; # for backward compatibility
$session{config}{extrasPath} = $session{config}{extrasPath} || "/data/WebGUI/www/extras";
###----------------------------
### default database handler object
$session{dbh} = DBI->connect($session{config}{dsn},$session{config}{dbuser},$session{config}{dbpass},{ RaiseError=>0,AutoCommit=>1 });
if ( $session{config}{dsn} =~ /Oracle/ ) { # Set Oracle specific attributes
$session{dbh}->{LongReadLen} = 512 * 1024;
$session{dbh}->{LongTruncOk} = 1;
}
###----------------------------
### global system settings (from settings table)
$session{setting} = WebGUI::SQL->buildHashRef("select name,value from settings");
###----------------------------
### CGI object
$CGI::POST_MAX=1024 * $session{setting}{maxAttachmentSize};
$session{cgi} = CGI->new();
if ($session{cgi}->cgi_error =~ /^413/) {
$session{header}{status} = $session{cgi}->cgi_error;
WebGUI::ErrorHandler::warn("File upload too big. May need to adjust Max File Size setting.");
$CGI::POST_MAX=-1;
$session{cgi} = CGI->new();
}
###----------------------------
### header variables
$session{header}{mimetype} = 'text/html';
###----------------------------
### evironment variables from web server
$session{env} = \%ENV;
### check to see if client is proxied and adjust remote_addr as necessary
if (($session{setting}{proxiedClientAddress} eq "1") && ($ENV{HTTP_X_FORWARDED_FOR} ne "")) {
$session{env}{REMOTE_ADDR} = $ENV{HTTP_X_FORWARDED_FOR};
}
###----------------------------
### form variables
foreach ($session{cgi}->param) {
$session{form}{$_} = $session{cgi}->param($_);
}
###----------------------------
### cookies
foreach ($session{cgi}->cookie) {
$session{cookie}{$_} = $session{cgi}->cookie($_);
}
###----------------------------
### session variables
if ($session{cookie}{wgSession} eq "") {
start(1); #setting up a visitor session
} else {
_setupSessionVars($session{cookie}{wgSession});
}
###----------------------------
### current page's properties (from page table)
_setupPageInfo("");
###----------------------------
### current user's account and profile information (from users and userProfileData tables)
_setupUserInfo($session{var}{userId});
###----------------------------
### language settings
$session{language} = WebGUI::SQL->quickHashRef("select * from language where languageId=$session{user}{language}");
###----------------------------
### loading plugins
_loadWobjects();
_loadMacros();
_loadAuthentication();
}
#-------------------------------------------------------------------
=head2 refreshPageInfo ( [ pageId ] )
Updates the WebGUI session to reflect new page information.
=over
=item pageId
Defaults to page id "1". Specify the page id to change this WebGUI session to use.
=back
=cut
sub refreshPageInfo {
my ($pageId);
if ($_[0] == 0) {
$pageId = $session{page}{pageId};
} else {
$pageId = $_[0];
}
_setupPageInfo($pageId);
}
#-------------------------------------------------------------------
=head2 refreshSessionVars ( sessionId )
Updates the user session variables from the database.
NOTE: This also updates the user information.
=over
=item sessionId
The session id to update.
=back
=cut
sub refreshSessionVars {
_setupSessionVars($_[0]);
refreshUserInfo($session{var}{userId});
}
#-------------------------------------------------------------------
=head2 refreshUserInfo ( userId )
Refreshes the user's information from the database into this user session.
=over
=item userId
The user id to refresh into this session.
=back
=cut
sub refreshUserInfo {
_setupUserInfo($_[0]);
$session{isInGroup} = ();
}
#-------------------------------------------------------------------
=head2 setCookie ( name, value [ , timeToLive ] )
Sends a cookie to the browser.
=over
=item name
The name of the cookie to set. Must be unique from all other cookies from this domain or it will overwrite that cookie.
=item value
The value to set.
=item timeToLive
The time that the cookie should remain in the browser. Defaults to "+10y" (10 years from now).
=back
=cut
sub setCookie {
my $ttl = $_[2] || '+10y';
#my $domain = $session{env}{SERVER_NAME} if ($session{env}{HTTP_USER_AGENT} =~ m/MSIE/i);
my $domain;
push @{$session{header}{cookie}}, $session{cgi}->cookie(
-name=>$_[0],
-value=>$_[1],
-expires=>$ttl,
-path=>'/',
-domain=>$domain
);
}
#-------------------------------------------------------------------
=head2 setScratch ( name, value )
Sets a scratch variable for this user session. Scratch variables are just arbitrary bits of data that a programmer may wish to store in a user session from page to page.
=over
=item name
The name of the scratch variable.
=item value
The value of the scratch variable. If the value is blank but defined or if it is set to "-delete-" then the scratch variable will be removed from the user session.
=back
=cut
sub setScratch {
my ($name, $value) = @_;
return "" unless ($session{var}{sessionId} ne "" && $name ne "" && defined $value);
if ($value eq "-delete-" || (defined $value && $value eq "")) {
deleteScratch($name);
$value = "";
} elsif ($session{scratch}{$name} ne "") {
WebGUI::SQL->write("update userSessionScratch set value=".quote($value)."
where sessionId=".quote($session{var}{sessionId})." and name=".quote($name));
} else {
WebGUI::SQL->write("insert into userSessionScratch (sessionId,name,value) values
(".quote($session{var}{sessionId}).", ".quote($name).", ".quote($value).")");
}
$session{scratch}{$name} = $value;
}
#-------------------------------------------------------------------
=head2 start ( userId [ , sessionId ] )
Start a new user session.
=over
=item userId
The user id of the user to create a session for.
=item sessionId
Session id will be generated if not specified. In almost every case you should let the system generate the session id.
=back
=cut
sub start {
my ($sessionId);
$sessionId = $_[1] || _uniqueSessionId();
if (($session{setting}{proxiedClientAddress} eq "1") && ($ENV{HTTP_X_FORWARDED_FOR} ne "")) {
WebGUI::SQL->write("insert into userSession values ('$sessionId', ".
(_time()+$session{setting}{sessionTimeout}).", "._time().", 0, '$ENV{HTTP_X_FORWARDED_FOR}', $_[0])");
} else {
WebGUI::SQL->write("insert into userSession values ('$sessionId', ".
(_time()+$session{setting}{sessionTimeout}).", "._time().", 0, '$ENV{REMOTE_ADDR}', $_[0])");
}
setCookie("wgSession",$sessionId);
refreshSessionVars($sessionId);
}
1;