5 # An Object for Handling User Information
7 # Copyright 1999-2001 Axis Data
8 # This code is free software that can be used or redistributed under the
9 # terms of Version 2 of the GNU General Public License, as published by the
10 # Free Software Foundation (http://www.fsf.org).
12 # Author: Edward Almasy (almasy@axisdata.com)
14 # Part of the AxisPHP library v1.2.4
15 # For more information see http://www.axisdata.com/AxisPHP/
18 # status values (error codes)
21 define(
"U_BADPASSWORD", 2);
22 define(
"U_NOSUCHUSER", 3);
23 define(
"U_PASSWORDSDONTMATCH", 4);
24 define(
"U_EMAILSDONTMATCH", 5);
25 define(
"U_DUPLICATEUSERNAME", 6);
26 define(
"U_ILLEGALUSERNAME", 7);
27 define(
"U_EMPTYUSERNAME", 8);
28 define(
"U_ILLEGALPASSWORD", 9);
29 define(
"U_ILLEGALPASSWORDAGAIN",10);
30 define(
"U_EMPTYPASSWORD", 11);
31 define(
"U_EMPTYPASSWORDAGAIN", 12);
32 define(
"U_ILLEGALEMAIL", 13);
33 define(
"U_ILLEGALEMAILAGAIN", 14);
34 define(
"U_EMPTYEMAIL", 15);
35 define(
"U_EMPTYEMAILAGAIN", 16);
36 define(
"U_NOTLOGGEDIN", 17);
37 define(
"U_MAILINGERROR", 18);
38 define(
"U_TEMPLATENOTFOUND", 19);
39 define(
"U_DUPLICATEEMAIL", 20);
40 define(
"U_NOTACTIVATED", 21);
45 # ---- PUBLIC INTERFACE --------------------------------------------------
47 function User($UserInfoOne = NULL, $UserInfoTwo = NULL)
49 # assume constructor will succeed and user is not logged in
51 $this->LoggedIn = FALSE;
53 # create database connection
56 # if user info passed in
57 if (is_int($UserInfoOne) || is_string($UserInfoOne)
58 || is_int($UserInfoTwo) || is_string($UserInfoTwo))
60 # if user ID was passed in
61 if (is_int($UserInfoOne) || is_int($UserInfoTwo))
64 $this->UserId = is_int($UserInfoOne) ? $UserInfoOne : $UserInfoTwo;
66 # get whether the user is logged in
67 $this->LoggedIn = (bool) $this->DB->Query(
"
68 SELECT LoggedIn FROM APUsers
69 WHERE UserId='".addslashes($this->UserId).
"'",
74 # look up user ID in database
75 $UserInfoTwo = is_string($UserInfoOne) ? $UserInfoOne : $UserInfoTwo;
76 $this->DB->Query(
"SELECT UserId, LoggedIn FROM APUsers"
77 .
" WHERE UserName='".addslashes($UserInfoTwo).
"'");
78 $Record = $this->DB->FetchRow();
81 $this->UserId = $Record[
"UserId"];
82 $this->LoggedIn = $Record[
"LoggedIn"];
85 # if user ID was not found
86 if ($Record === FALSE)
88 # if name looks like it could actually be a user ID
89 if (preg_match(
"/^[-]*[0-9]+$/", $UserInfoTwo))
91 # assume name was user ID
92 $this->UserId = intval($UserInfoTwo);
96 # set code indicating no user found
105 # if user ID is available from session
106 if (isset($_SESSION[
"APUserId"]))
109 $this->UserId = $_SESSION[
"APUserId"];
111 # set flag indicating user is currently logged in
112 $this->LoggedIn = TRUE;
122 # return text message corresponding to current (or specified) status code
125 $APUserStatusMessages = array(
126 U_OKAY =>
"The operation was successful.",
127 U_ERROR =>
"There has been an error.",
138 .
" short, too long, or contains"
139 .
" illegal characters.",
141 .
" too short, too long, or"
142 .
" contains illegal characters.",
144 .
" appears to be invalid.",
147 .
" to send e-mail. Please notify"
148 .
" the system administrator.",
150 .
" to generate e-mail. Please"
151 .
" notify the system administrator.",
153 .
" has an account associated with it.",
156 return ($StatusCode === NULL) ? $APUserStatusMessages[
$this->Result]
157 : $APUserStatusMessages[$StatusCode];
162 # clear priv list values
163 $this->DB->Query(
"DELETE FROM APUserPrivileges WHERE UserId = '".$this->UserId.
"'");
165 # delete user record from database
166 $this->DB->Query(
"DELETE FROM APUsers WHERE UserId = '".$this->UserId.
"'");
168 # report to caller that everything succeeded
180 if (is_callable($NewValue))
182 self::$EmailFunc = $NewValue;
187 # ---- Getting/Setting Values --------------------------------------------
195 return $this->
Get(
"UserName");
205 $RealName = $this->
Get(
"RealName");
207 # the real name is available, so use it
208 if (strlen(trim($RealName)))
213 # the real name isn't available, so use the user name
214 return $this->
Get(
"UserName");
219 # return NULL if not associated with a particular user
220 if ($this->UserId === NULL) {
return NULL; }
224 $this->DB->Query(
"UPDATE APUsers SET"
225 .
" LastLocation = '".addslashes($NewLocation).
"',"
226 .
" LastActiveDate = NOW(),"
227 .
" LastIPAddress = '".$_SERVER[
"REMOTE_ADDR"].
"'"
228 .
" WHERE UserId = '".addslashes($this->UserId).
"'");
229 if (isset($this->DBFields))
231 $this->DBFields[
"LastLocation"] = $NewLocation;
232 $this->DBFields[
"LastActiveDate"] = date(
"Y-m-d H:i:s");
235 return $this->
Get(
"LastLocation");
239 return $this->
Get(
"LastActiveDate");
243 return $this->
Get(
"LastIPAddress");
246 # get value from specified field
249 # return NULL if not associated with a particular user
250 if ($this->UserId === NULL) {
return NULL; }
255 # get value (formatted as a date) from specified field
258 # return NULL if not associated with a particular user
259 if ($this->UserId === NULL) {
return NULL; }
261 # retrieve specified value from database
262 if (strlen($Format) > 0)
264 $this->DB->Query(
"SELECT DATE_FORMAT(`".addslashes($FieldName).
"`, '".addslashes($Format).
"') AS `".addslashes($FieldName).
"` FROM APUsers WHERE UserId='".$this->UserId.
"'");
268 $this->DB->Query(
"SELECT `".addslashes($FieldName).
"` FROM APUsers WHERE UserId='".$this->UserId.
"'");
270 $Record = $this->DB->FetchRow();
272 # return value to caller
273 return $Record[$FieldName];
276 # set value in specified field
277 function Set($FieldName, $NewValue)
279 # return error if not associated with a particular user
288 # ---- Login Functions ---------------------------------------------------
290 function Login($UserName, $Password, $IgnorePassword = FALSE)
292 # if user not found in DB
293 $this->DB->Query(
"SELECT * FROM APUsers"
294 .
" WHERE UserName = '"
295 .addslashes(self::NormalizeUserName($UserName)).
"'");
296 if ($this->DB->NumRowsSelected() < 1)
298 # result is no user by that name
303 # if user account not yet activated
304 $Record = $this->DB->FetchRow();
305 if (!$Record[
"RegistrationConfirmed"])
307 # result is user registration not confirmed
312 # grab password from DB
313 $StoredPassword = $Record[
"UserPassword"];
315 if (isset($Password[0]) && $Password[0] ==
" ")
317 $Challenge = md5(date(
"Ymd").$_SERVER[
"REMOTE_ADDR"]);
318 $StoredPassword = md5( $Challenge . $StoredPassword );
320 $EncryptedPassword = trim($Password);
324 # if supplied password matches encrypted password
325 $EncryptedPassword = crypt($Password, $StoredPassword);
328 if (($EncryptedPassword == $StoredPassword) || $IgnorePassword)
333 # store user ID for session
334 $this->UserId = $Record[
"UserId"];
337 # update last login date
338 $this->DB->Query(
"UPDATE APUsers SET LastLoginDate = NOW(),"
340 .
" WHERE UserId = '".$this->UserId.
"'");
342 # Check for old format hashes, and rehash if possible
343 if ($EncryptedPassword === $StoredPassword &&
344 substr($StoredPassword,0,3) !==
"$1$" &&
345 $Password[0] !==
" " &&
348 $NewPassword = crypt($Password);
350 "UPDATE APUsers SET UserPassword='".addslashes($NewPassword).
"' "
351 .
"WHERE UserId='".$this->UserId.
"'");
354 # since self::DBFields might already have been set to false if
355 # the user wasn't logged in when this is called, populate it
356 # with user data so that a call to self::UpdateValue will be
357 # able to properly fetch the data associated with the user
358 $this->DBFields = $Record;
360 # set flag to indicate we are logged in
361 $this->LoggedIn = TRUE;
365 # result is bad password
371 # return result to caller
378 # clear user ID (if any) for session
379 unset($_SESSION[
"APUserId"]);
381 # if user is marked as logged in
384 # set flag to indicate user is no longer logged in
385 $this->LoggedIn = FALSE;
387 # clear login flag in database
389 "UPDATE APUsers SET LoggedIn = '0' "
390 .
"WHERE UserId='".$this->UserId.
"'");
397 "SELECT * FROM APUsers WHERE UserName = '"
398 .addslashes(self::NormalizeUserName($UserName)).
"'");
400 if ($this->DB->NumRowsSelected() < 1)
402 # result is no user by that name, generate a fake salt
403 # to discourage user enumeration. Make it be an old-format
404 # crypt() salt so that it's harder.
405 $SaltString = $_SERVER[
"SERVER_ADDR"].$UserName;
406 $Result = substr(base64_encode(md5($SaltString)),0,2);
410 # grab password from DB
411 # Assumes that we used php's crypt() for the passowrd
412 # management stuff, and will need to be changed if we
413 # go to something else.
414 $Record = $this->DB->FetchRow();
415 $StoredPassword = $Record[
"UserPassword"];
417 if (substr($StoredPassword,0,3)===
"$1$")
419 $Result = substr($StoredPassword, 0,12);
423 $Result = substr($StoredPassword, 0,2);
430 # report whether this user is or is not currently logged in
435 # ---- Password Functions ------------------------------------------------
437 # set new password (with checks against old password)
440 # return error if not associated with a particular user
443 # if old password is not correct
444 $StoredPassword = $this->DB->Query(
"SELECT UserPassword FROM APUsers"
445 .
" WHERE UserId='".$this->UserId.
"'",
"UserPassword");
446 $EncryptedPassword = crypt($OldPassword, $StoredPassword);
447 if ($EncryptedPassword != $StoredPassword)
449 # set status to indicate error
452 # else if new password is not legal
455 # set status to indicate error
458 # else if both instances of new password do not match
459 elseif (self::NormalizePassword($NewPassword)
460 != self::NormalizePassword($NewPasswordAgain))
462 # set status to indicate error
470 # set status to indicate password successfully changed
474 # report to caller that everything succeeded
481 # generate encrypted password
482 $EncryptedPassword = crypt(self::NormalizePassword($NewPassword));
484 # save encrypted password
485 $this->
UpdateValue(
"UserPassword", $EncryptedPassword);
489 $UserName, $EMail, $EMailAgain,
490 $TemplateFile =
"Axis--User--EMailTemplate.txt")
493 $UserName, $EMail, $EMailAgain, $TemplateFile);
497 $UserName, $EMail, $EMailAgain,
498 $TemplateFile =
"Axis--User--EMailTemplate.txt")
500 # load e-mail template from file (first line is subject)
501 $Template = file($TemplateFile, 1);
502 $EMailSubject = array_shift($Template);
503 $EMailBody = join(
"", $Template);
506 $UserName, $EMail, $EMailAgain, $EMailSubject, $EMailBody);
510 $UserName, $EMail, $EMailAgain, $EMailSubject, $EMailBody)
512 # make sure e-mail addresses match
513 if ($EMail != $EMailAgain)
519 # make sure e-mail address looks valid
526 # generate random password
529 # attempt to create new user with password
530 $Result = $this->CreateNewUser($UserName, $Password, $Password);
532 # if user creation failed
535 # report error result to caller
541 # set e-mail address in user record
542 $this->
Set(
"EMail", $EMail);
544 # plug appropriate values into subject and body of e-mail message
545 $EMailSubject = str_replace(
"X-USERNAME-X", $UserName, $EMailSubject);
546 $EMailBody = str_replace(
"X-USERNAME-X", $UserName, $EMailBody);
547 $EMailBody = str_replace(
"X-PASSWORD-X", $Password, $EMailBody);
549 # send out e-mail message with new account info
550 if (is_Callable(self::$EmailFunc))
552 $Result = call_user_func(self::$EmailFunc,
553 $EMail, $EMailSubject, $EMailBody,
554 "Auto-Submitted: auto-generated");
558 $Result = mail($EMail, $EMailSubject, $EMailBody,
559 "Auto-Submitted: auto-generated");
562 # if mailing attempt failed
565 # report error to caller
572 # report success to caller
579 # get code for user to submit to confirm registration
582 # code is MD5 sum based on user name and encrypted password
583 $ActivationCodeLength = 6;
584 return $this->
GetUniqueCode(
"Activation", $ActivationCodeLength);
587 # check whether confirmation code is valid
594 # get/set whether user registration has been confirmed
597 return $this->
UpdateValue(
"RegistrationConfirmed", $NewValue);
600 # get code for user to submit to confirm password reset
603 # code is MD5 sum based on user name and encrypted password
604 $ResetCodeLength = 10;
608 # check whether password reset code is valid
611 return (strtoupper(trim($Code)) == $this->
GetResetCode())
615 # get code for user to submit to confirm mail change request
618 $ResetCodeLength = 10;
630 # send e-mail to user (returns TRUE on success)
632 $TemplateTextOrFileName, $FromAddress = NULL, $MoreSubstitutions = NULL,
635 # if template is file name
636 if (@is_file($TemplateTextOrFileName))
638 # load in template from file
639 $Template = file($TemplateTextOrFileName, 1);
641 # report error to caller if template load failed
642 if ($Template == FALSE)
645 return $this->Status;
648 # join into one text block
649 $TemplateTextOrFileName = join(
"", $Template);
652 # split template into lines
653 $Template = explode(
"\n", $TemplateTextOrFileName);
655 # strip any comments out of template
656 $FilteredTemplate = array();
657 foreach ($Template as $Line)
659 if (!preg_match(
"/^[\\s]*#/", $Line))
661 $FilteredTemplate[] = $Line;
665 # split subject line out of template (first non-comment line in file)
666 $EMailSubject = array_shift($FilteredTemplate);
667 $EMailBody = join(
"\n", $FilteredTemplate);
669 # set up our substitutions
670 $Substitutions = array(
671 "X-USERNAME-X" => $this->
Get(
"UserName"),
672 "X-EMAILADDRESS-X" => $this->
Get(
"EMail"),
676 "X-IPADDRESS-X" => @$_SERVER[
"REMOTE_ADDR"],
679 # if caller provided additional substitutions
680 if (is_array($MoreSubstitutions))
682 # add in entries from caller to substitution list
683 $Substitutions = array_merge(
684 $Substitutions, $MoreSubstitutions);
687 # perform substitutions on subject and body of message
688 $EMailSubject = str_replace(array_keys($Substitutions),
689 array_values($Substitutions), $EMailSubject);
690 $EMailBody = str_replace(array_keys($Substitutions),
691 array_values($Substitutions), $EMailBody);
693 $AdditionalHeaders =
"Auto-Submitted: auto-generated";
695 # if caller provided "From" address
698 # prepend "From" address onto message
699 $AdditionalHeaders .=
"\r\nFrom: ".$FromAddress;
702 # send out mail message
703 if (is_Callable(self::$EmailFunc))
705 $Result = call_user_func(self::$EmailFunc,
706 is_null($ToAddress)?$this->
Get(
"EMail"):$ToAddress,
707 $EMailSubject, $EMailBody, $AdditionalHeaders);
711 $Result = mail(is_null($ToAddress)?$this->
Get(
"EMail"):$ToAddress,
713 $EMailBody, $AdditionalHeaders);
716 # report result of mailing attempt to caller
722 # ---- Privilege Functions -----------------------------------------------
732 function HasPriv($Privilege, $Privileges = NULL)
734 # return FALSE if not associated with a particular user
735 if ($this->UserId === NULL) {
return FALSE; }
737 # bail out if empty array of privileges passed in
738 if (is_array($Privilege) && !count($Privilege) && (func_num_args() < 2))
741 # set up beginning of database query
742 $Query =
"SELECT COUNT(*) AS PrivCount FROM APUserPrivileges "
743 .
"WHERE UserId='".$this->UserId.
"' AND (";
745 # add first privilege(s) to query (first arg may be single value or array)
746 if (is_array($Privilege))
749 foreach ($Privilege as $Priv)
751 $Query .= $Sep.
"Privilege='".addslashes($Priv).
"'";
757 $Query .=
"Privilege='".$Privilege.
"'";
761 # add any privileges from additional args to query
762 $Args = func_get_args();
764 foreach ($Args as $Arg)
766 $Query .= $Sep.
"Privilege='".$Arg.
"'";
773 # look for privilege in database
774 $PrivCount = $this->DB->Query($Query,
"PrivCount");
776 # return value to caller
777 return ($PrivCount > 0) ? TRUE : FALSE;
790 # set up beginning of database query
791 $Query =
"SELECT DISTINCT UserId FROM APUserPrivileges "
794 # add first privilege(s) to query (first arg may be single value or array)
795 if (is_array($Privilege))
798 foreach ($Privilege as $Priv)
800 $Query .= $Sep.
"Privilege='".addslashes($Priv).
"'";
806 $Query .=
"Privilege='".$Privilege.
"'";
810 # add any privileges from additional args to query
811 $Args = func_get_args();
813 foreach ($Args as $Arg)
815 $Query .= $Sep.
"Privilege='".$Arg.
"'";
819 # return query to caller
833 # set up beginning of database query
834 $Query =
"SELECT DISTINCT UserId FROM APUserPrivileges "
837 # add first privilege(s) to query (first arg may be single value or array)
838 if (is_array($Privilege))
841 foreach ($Privilege as $Priv)
843 $Query .= $Sep.
"Privilege != '".addslashes($Priv).
"'";
849 $Query .=
"Privilege != '".$Privilege.
"'";
853 # add any privileges from additional args to query
854 $Args = func_get_args();
856 foreach ($Args as $Arg)
858 $Query .= $Sep.
"Privilege != '".$Arg.
"'";
862 # return query to caller
868 # return error if not associated with a particular user
871 # if privilege value is invalid
872 if (intval($Privilege) != trim($Privilege))
874 # set code to indicate error
879 # if user does not already have privilege
880 $PrivCount = $this->DB->Query(
"SELECT COUNT(*) AS PrivCount"
881 .
" FROM APUserPrivileges"
882 .
" WHERE UserId='".$this->UserId.
"'"
883 .
" AND Privilege='".$Privilege.
"'",
887 # add privilege for this user to database
888 $this->DB->Query(
"INSERT INTO APUserPrivileges"
889 .
" (UserId, Privilege) VALUES"
890 .
" ('".$this->UserId.
"', ".$Privilege.
")");
893 # set code to indicate success
897 # report result to caller
903 # return error if not associated with a particular user
906 # remove privilege from database (if present)
907 $this->DB->Query(
"DELETE FROM APUserPrivileges"
908 .
" WHERE UserId = '".$this->UserId.
"'"
909 .
" AND Privilege = '".$Privilege.
"'");
911 # report success to caller
918 # return empty list if not associated with a particular user
919 if ($this->UserId === NULL) {
return array(); }
921 # read privileges from database and return array to caller
922 $this->DB->Query(
"SELECT Privilege FROM APUserPrivileges"
923 .
" WHERE UserId='".$this->UserId.
"'");
924 return $this->DB->FetchColumn(
"Privilege");
929 # return error if not associated with a particular user
932 # clear old priv list values
933 $this->DB->Query(
"DELETE FROM APUserPrivileges"
934 .
" WHERE UserId='".$this->UserId.
"'");
936 # for each priv value passed in
937 foreach ($NewPrivileges as $Privilege)
945 # ---- Miscellaneous Functions -------------------------------------------
947 # get unique alphanumeric code for user
950 # return NULL if not associated with a particular user
951 if ($this->UserId === NULL) {
return NULL; }
953 return substr(strtoupper(md5(
954 $this->
Get(
"UserName").$this->
Get(
"UserPassword").$SeedString)),
959 # ---- PRIVATE INTERFACE -------------------------------------------------
961 protected $DB; # handle to SQL database we use to store user information
962 protected $UserId = NULL; # user ID number
for reference into database
964 protected $LoggedIn; # flag indicating whether user is logged in
965 private $DBFields; # used
for caching user values
967 # optional mail function to use instead of mail()
968 private static $EmailFunc = NULL;
970 # check whether a user name is valid (alphanumeric string of 2-24 chars)
973 if (preg_match(
"/^[a-zA-Z0-9]{2,24}$/", $UserName)) {
return TRUE; }
else {
return FALSE; }
976 # check whether a password is valid (at least 6 characters)
979 if (strlen(self::NormalizePassword($Password)) < 6)
980 {
return FALSE; }
else {
return TRUE; }
983 # check whether an e-mail address looks valid
986 if (preg_match(
"/^[a-zA-Z0-9._\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]{2,3}$/", $EMail)) {
return TRUE; }
else {
return FALSE; }
989 # get normalized version of e-mail address
992 return strtolower(trim($EMailAddress));
995 # get normalized version of user name
998 return trim($UserName);
1001 # get normalized version of password
1004 return trim($Password);
1007 # generate random password
1010 # seed random number generator
1011 mt_srand((
double)microtime() * 1000000);
1013 # generate password of requested length
1014 return sprintf(
"%06d", mt_rand(pow(10, ($PasswordMinLength - 1)),
1015 (pow(10, $PasswordMaxLength) - 1)));
1018 # convenience function to supply parameters to Database->UpdateValue()
1021 return $this->DB->UpdateValue(
"APUsers", $FieldName, $NewValue,
1022 "UserId = '".$this->UserId.
"'", $this->DBFields);
1025 # methods for backward compatibility with earlier versions of User
GetRandomPassword($PasswordMinLength=6, $PasswordMaxLength=8)
static NormalizeUserName($UserName)
static IsValidLookingEMailAddress($EMail)
GetUniqueCode($SeedString, $CodeLength)
SQL database abstraction object with smart query caching.
IsMailChangeCodeGood($Code)
UpdateValue($FieldName, $NewValue=DB_NOVALUE)
static NormalizePassword($Password)
CreateNewUserAndMailPassword($UserName, $EMail, $EMailAgain, $EMailSubject, $EMailBody)
static IsValidPassword($Password)
Login($UserName, $Password, $IgnorePassword=FALSE)
User($UserInfoOne=NULL, $UserInfoTwo=NULL)
StatusMessage($StatusCode=NULL)
static IsValidUserName($UserName)
static GetSqlQueryForUsersWithPriv($Privilege, $Privileges=NULL)
Get an SQL query that will return IDs of all users that have the specified privilege flags...
GetPasswordSalt($UserName)
LastLocation($NewLocation=NULL)
IsActivated($NewValue=DB_NOVALUE)
HasPriv($Privilege, $Privileges=NULL)
Check whether user has specified privilege(s).
const U_DUPLICATEUSERNAME
GetBestName()
Get the best available name associated with a user, i.e., the real name or, if it isn't available...
SendEMail($TemplateTextOrFileName, $FromAddress=NULL, $MoreSubstitutions=NULL, $ToAddress=NULL)
Set($FieldName, $NewValue)
const U_PASSWORDSDONTMATCH
static SetEmailFunction($NewValue)
Set email function to use instead of mail().
static NormalizeEMailAddress($EMailAddress)
GetDate($FieldName, $Format="")
CreateNewUserWithEMailedPassword($UserName, $EMail, $EMailAgain, $TemplateFile="Axis--User--EMailTemplate.txt")
CreateNewUserAndMailPasswordFromFile($UserName, $EMail, $EMailAgain, $TemplateFile="Axis--User--EMailTemplate.txt")
SetPassword($NewPassword)
SetPrivList($NewPrivileges)
ChangePassword($OldPassword, $NewPassword, $NewPasswordAgain)
IsActivationCodeGood($Code)
static GetSqlQueryForUsersWithoutPriv($Privilege, $Privileges=NULL)
Get an SQL query that will return IDs of all users that do not have the specified privilege flags...