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;
68 # look up user ID in database
69 $UserInfoTwo = is_string($UserInfoOne) ? $UserInfoOne : $UserInfoTwo;
70 $this->DB->Query(
"SELECT UserId, LoggedIn FROM APUsers"
71 .
" WHERE UserName='".addslashes($UserInfoTwo).
"'");
72 $Record = $this->DB->FetchRow();
75 $this->UserId = $Record[
"UserId"];
76 $this->LoggedIn = $Record[
"LoggedIn"];
79 # if user ID was not found
80 if ($Record === FALSE)
82 # if name looks like it could actually be a user ID
83 if (preg_match(
"/^[-]*[0-9]+$/", $UserInfoTwo))
85 # assume name was user ID
86 $this->UserId = intval($UserInfoTwo);
90 # set code indicating no user found
99 # if user ID is available from session
100 if (isset($_SESSION[
"APUserId"]))
103 $this->UserId = $_SESSION[
"APUserId"];
105 # set flag indicating user is currently logged in
106 $this->LoggedIn = TRUE;
113 return $this->Result;
116 # return text message corresponding to current (or specified) status code
119 $APUserStatusMessages = array(
120 U_OKAY =>
"The operation was successful.",
121 U_ERROR =>
"There has been an error.",
132 .
" short, too long, or contains"
133 .
" illegal characters.",
135 .
" too short, too long, or"
136 .
" contains illegal characters.",
138 .
" appears to be invalid.",
141 .
" to send e-mail. Please notify"
142 .
" the system administrator.",
144 .
" to generate e-mail. Please"
145 .
" notify the system administrator.",
147 .
" has an account associated with it.",
150 return ($StatusCode === NULL) ? $APUserStatusMessages[$this->Result]
151 : $APUserStatusMessages[$StatusCode];
156 # clear priv list values
157 $this->DB->Query(
"DELETE FROM APUserPrivileges WHERE UserId = '".$this->UserId.
"'");
159 # delete user record from database
160 $this->DB->Query(
"DELETE FROM APUsers WHERE UserId = '".$this->UserId.
"'");
162 # report to caller that everything succeeded
164 return $this->Result;
174 if (is_callable($NewValue))
176 self::$EmailFunc = $NewValue;
181 # ---- Getting/Setting Values --------------------------------------------
185 return $this->UserId;
189 return $this->
Get(
"UserName");
195 $this->DB->Query(
"UPDATE APUsers SET"
196 .
" LastLocation = '".addslashes($NewLocation).
"',"
197 .
" LastActiveDate = NOW(),"
198 .
" LastIPAddress = '".$_SERVER[
"REMOTE_ADDR"].
"'"
199 .
" WHERE UserId = '".addslashes($this->UserId).
"'");
200 if (isset($this->DBFields))
202 $this->DBFields[
"LastLocation"] = $NewLocation;
203 $this->DBFields[
"LastActiveDate"] = date(
"Y-m-d H:i:s");
206 return $this->
Get(
"LastLocation");
210 return $this->
Get(
"LastActiveDate");
214 return $this->
Get(
"LastIPAddress");
217 # get value from specified field
223 # get value (formatted as a date) from specified field
226 # retrieve specified value from database
227 if (strlen($Format) > 0)
229 $this->DB->Query(
"SELECT DATE_FORMAT(`".addslashes($FieldName).
"`, '".addslashes($Format).
"') AS `".addslashes($FieldName).
"` FROM APUsers WHERE UserId='".$this->UserId.
"'");
233 $this->DB->Query(
"SELECT `".addslashes($FieldName).
"` FROM APUsers WHERE UserId='".$this->UserId.
"'");
235 $Record = $this->DB->FetchRow();
237 # return value to caller
238 return $Record[$FieldName];
241 # set value in specified field
242 function Set($FieldName, $NewValue)
246 return $this->Result;
250 # ---- Login Functions ---------------------------------------------------
252 function Login($UserName, $Password, $IgnorePassword = FALSE)
254 # if user not found in DB
255 $this->DB->Query(
"SELECT * FROM APUsers"
256 .
" WHERE UserName = '"
257 .addslashes(self::NormalizeUserName($UserName)).
"'");
258 if ($this->DB->NumRowsSelected() < 1)
260 # result is no user by that name
265 # if user account not yet activated
266 $Record = $this->DB->FetchRow();
267 if (!$Record[
"RegistrationConfirmed"])
269 # result is user registration not confirmed
274 # grab password from DB
275 $StoredPassword = $Record[
"UserPassword"];
277 if (isset($Password[0]) && $Password[0] ==
" ")
279 $Challenge = md5(date(
"Ymd").$_SERVER[
"REMOTE_ADDR"]);
280 $StoredPassword = md5( $Challenge . $StoredPassword );
282 $EncryptedPassword = trim($Password);
286 # if supplied password matches encrypted password
287 $EncryptedPassword = crypt($Password, $StoredPassword);
290 if (($EncryptedPassword == $StoredPassword) || $IgnorePassword)
295 # store user ID for session
296 $this->UserId = $Record[
"UserId"];
297 $_SESSION[
"APUserId"] = $this->UserId;
299 # update last login date
300 $this->DB->Query(
"UPDATE APUsers SET LastLoginDate = NOW(),"
302 .
" WHERE UserId = '".$this->UserId.
"'");
304 # Check for old format hashes, and rehash if possible
305 if ($EncryptedPassword === $StoredPassword &&
306 substr($StoredPassword,0,3) !==
"$1$" &&
307 $Password[0] !==
" " &&
310 $NewPassword = crypt($Password);
312 "UPDATE APUsers SET UserPassword='".addslashes($NewPassword).
"' "
313 .
"WHERE UserId='".$this->UserId.
"'");
316 # since self::DBFields might already have been set to false if
317 # the user wasn't logged in when this is called, populate it
318 # with user data so that a call to self::UpdateValue will be
319 # able to properly fetch the data associated with the user
320 $this->DBFields = $Record;
322 # set flag to indicate we are logged in
323 $this->LoggedIn = TRUE;
327 # result is bad password
333 # return result to caller
334 return $this->Result;
340 # clear user ID (if any) for session
341 unset($_SESSION[
"APUserId"]);
343 # if user is marked as logged in
346 # set flag to indicate user is no longer logged in
347 $this->LoggedIn = FALSE;
349 # clear login flag in database
351 "UPDATE APUsers SET LoggedIn = '0' "
352 .
"WHERE UserId='".$this->UserId.
"'");
359 "SELECT * FROM APUsers WHERE UserName = '"
360 .addslashes(self::NormalizeUserName($UserName)).
"'");
362 if ($this->DB->NumRowsSelected() < 1)
364 # result is no user by that name, generate a fake salt
365 # to discourage user enumeration. Make it be an old-format
366 # crypt() salt so that it's harder.
367 $SaltString = $_SERVER[
"SERVER_ADDR"].$UserName;
368 $Result = substr(base64_encode(md5($SaltString)),0,2);
372 # grab password from DB
373 # Assumes that we used php's crypt() for the passowrd
374 # management stuff, and will need to be changed if we
375 # go to something else.
376 $Record = $this->DB->FetchRow();
377 $StoredPassword = $Record[
"UserPassword"];
379 if (substr($StoredPassword,0,3)===
"$1$")
381 $Result = substr($StoredPassword, 0,12);
385 $Result = substr($StoredPassword, 0,2);
392 # report whether this user is or is not currently logged in
397 # ---- Password Functions ------------------------------------------------
399 # set new password (with checks against old password)
402 # make sure a user is logged in
406 return $this->Result;
409 # if old password is not correct
410 $StoredPassword = $this->DB->Query(
"SELECT UserPassword FROM APUsers"
411 .
" WHERE UserId='".$this->UserId.
"'",
"UserPassword");
412 $EncryptedPassword = crypt($OldPassword, $StoredPassword);
413 if ($EncryptedPassword != $StoredPassword)
415 # set status to indicate error
418 # else if new password is not legal
421 # set status to indicate error
424 # else if both instances of new password do not match
425 elseif (self::NormalizePassword($NewPassword)
426 != self::NormalizePassword($NewPasswordAgain))
428 # set status to indicate error
436 # set status to indicate password successfully changed
440 # report to caller that everything succeeded
441 return $this->Result;
447 # generate encrypted password
448 $EncryptedPassword = crypt(self::NormalizePassword($NewPassword));
450 # save encrypted password
451 $this->
UpdateValue(
"UserPassword", $EncryptedPassword);
455 $UserName, $EMail, $EMailAgain,
456 $TemplateFile =
"Axis--User--EMailTemplate.txt")
459 $UserName, $EMail, $EMailAgain, $TemplateFile);
463 $UserName, $EMail, $EMailAgain,
464 $TemplateFile =
"Axis--User--EMailTemplate.txt")
466 # load e-mail template from file (first line is subject)
467 $Template = file($TemplateFile, 1);
468 $EMailSubject = array_shift($Template);
469 $EMailBody = join(
"", $Template);
472 $UserName, $EMail, $EMailAgain, $EMailSubject, $EMailBody);
476 $UserName, $EMail, $EMailAgain, $EMailSubject, $EMailBody)
478 # make sure e-mail addresses match
479 if ($EMail != $EMailAgain)
482 return $this->Result;
485 # make sure e-mail address looks valid
489 return $this->Result;
492 # generate random password
495 # attempt to create new user with password
496 $Result = $this->CreateNewUser($UserName, $Password, $Password);
498 # if user creation failed
501 # report error result to caller
507 # set e-mail address in user record
508 $this->
Set(
"EMail", $EMail);
510 # plug appropriate values into subject and body of e-mail message
511 $EMailSubject = str_replace(
"X-USERNAME-X", $UserName, $EMailSubject);
512 $EMailBody = str_replace(
"X-USERNAME-X", $UserName, $EMailBody);
513 $EMailBody = str_replace(
"X-PASSWORD-X", $Password, $EMailBody);
515 # send out e-mail message with new account info
516 if (is_Callable(self::$EmailFunc))
518 $Result = call_user_func(self::$EmailFunc,
519 $EMail, $EMailSubject, $EMailBody,
520 "Auto-Submitted: auto-generated");
524 $Result = mail($EMail, $EMailSubject, $EMailBody,
525 "Auto-Submitted: auto-generated");
528 # if mailing attempt failed
531 # report error to caller
533 return $this->Result;
538 # report success to caller
540 return $this->Result;
545 # get code for user to submit to confirm registration
548 # code is MD5 sum based on user name and encrypted password
549 $ActivationCodeLength = 6;
550 return $this->
GetUniqueCode(
"Activation", $ActivationCodeLength);
553 # check whether confirmation code is valid
560 # get/set whether user registration has been confirmed
563 return $this->
UpdateValue(
"RegistrationConfirmed", $NewValue);
566 # get code for user to submit to confirm password reset
569 # code is MD5 sum based on user name and encrypted password
570 $ResetCodeLength = 10;
574 # check whether password reset code is valid
577 return (strtoupper(trim($Code)) == $this->
GetResetCode())
581 # get code for user to submit to confirm mail change request
584 $ResetCodeLength = 10;
596 # send e-mail to user (returns TRUE on success)
598 $TemplateTextOrFileName, $FromAddress = NULL, $MoreSubstitutions = NULL,
601 # if template is file name
602 if (@is_file($TemplateTextOrFileName))
604 # load in template from file
605 $Template = file($TemplateTextOrFileName, 1);
607 # report error to caller if template load failed
608 if ($Template == FALSE)
611 return $this->Status;
614 # join into one text block
615 $TemplateTextOrFileName = join(
"", $Template);
618 # split template into lines
619 $Template = explode(
"\n", $TemplateTextOrFileName);
621 # strip any comments out of template
622 $FilteredTemplate = array();
623 foreach ($Template as $Line)
625 if (!preg_match(
"/^[\\s]*#/", $Line))
627 $FilteredTemplate[] = $Line;
631 # split subject line out of template (first non-comment line in file)
632 $EMailSubject = array_shift($FilteredTemplate);
633 $EMailBody = join(
"\n", $FilteredTemplate);
635 # set up our substitutions
636 $Substitutions = array(
637 "X-USERNAME-X" => $this->
Get(
"UserName"),
638 "X-EMAILADDRESS-X" => $this->
Get(
"EMail"),
642 "X-IPADDRESS-X" => @$_SERVER[
"REMOTE_ADDR"],
645 # if caller provided additional substitutions
646 if (is_array($MoreSubstitutions))
648 # add in entries from caller to substitution list
649 $Substitutions = array_merge(
650 $Substitutions, $MoreSubstitutions);
653 # perform substitutions on subject and body of message
654 $EMailSubject = str_replace(array_keys($Substitutions),
655 array_values($Substitutions), $EMailSubject);
656 $EMailBody = str_replace(array_keys($Substitutions),
657 array_values($Substitutions), $EMailBody);
659 $AdditionalHeaders =
"Auto-Submitted: auto-generated";
661 # if caller provided "From" address
664 # prepend "From" address onto message
665 $AdditionalHeaders .=
"\r\nFrom: ".$FromAddress;
668 # send out mail message
669 if (is_Callable(self::$EmailFunc))
671 $Result = call_user_func(self::$EmailFunc,
672 is_null($ToAddress)?$this->
Get(
"EMail"):$ToAddress,
673 $EMailSubject, $EMailBody, $AdditionalHeaders);
677 $Result = mail(is_null($ToAddress)?$this->
Get(
"EMail"):$ToAddress,
679 $EMailBody, $AdditionalHeaders);
682 # report result of mailing attempt to caller
688 # ---- Privilege Functions -----------------------------------------------
698 function HasPriv($Privilege, $Privileges = NULL)
700 # make sure a user is logged in (no privileges if not logged in)
701 if ($this->
IsLoggedIn() == FALSE) {
return FALSE; }
703 # bail out if empty array of privileges passed in
704 if (is_array($Privilege) && !count($Privilege) && (func_num_args() < 2))
707 # set up beginning of database query
708 $Query =
"SELECT COUNT(*) AS PrivCount FROM APUserPrivileges "
709 .
"WHERE UserId='".$this->UserId.
"' AND (";
711 # add first privilege(s) to query (first arg may be single value or array)
712 if (is_array($Privilege))
715 foreach ($Privilege as $Priv)
717 $Query .= $Sep.
"Privilege='".addslashes($Priv).
"'";
723 $Query .=
"Privilege='".$Privilege.
"'";
727 # add any privileges from additional args to query
728 $Args = func_get_args();
730 foreach ($Args as $Arg)
732 $Query .= $Sep.
"Privilege='".$Arg.
"'";
739 # look for privilege in database
740 $PrivCount = $this->DB->Query($Query,
"PrivCount");
742 # return value to caller
743 return ($PrivCount > 0) ? TRUE : FALSE;
756 # set up beginning of database query
757 $Query =
"SELECT UserId FROM APUserPrivileges "
760 # add first privilege(s) to query (first arg may be single value or array)
761 if (is_array($Privilege))
764 foreach ($Privilege as $Priv)
766 $Query .= $Sep.
"Privilege='".addslashes($Priv).
"'";
772 $Query .=
"Privilege='".$Privilege.
"'";
776 # add any privileges from additional args to query
777 $Args = func_get_args();
779 foreach ($Args as $Arg)
781 $Query .= $Sep.
"Privilege='".$Arg.
"'";
785 # return query to caller
791 # if privilege value is invalid
792 if (intval($Privilege) != trim($Privilege))
794 # set code to indicate error
799 # if user does not already have privilege
800 $PrivCount = $this->DB->Query(
"SELECT COUNT(*) AS PrivCount"
801 .
" FROM APUserPrivileges"
802 .
" WHERE UserId='".$this->UserId.
"'"
803 .
" AND Privilege='".$Privilege.
"'",
807 # add privilege for this user to database
808 $this->DB->Query(
"INSERT INTO APUserPrivileges"
809 .
" (UserId, Privilege) VALUES"
810 .
" ('".$this->UserId.
"', ".$Privilege.
")");
813 # set code to indicate success
817 # report result to caller
818 return $this->Result;
823 # remove privilege from database (if present)
824 $this->DB->Query(
"DELETE FROM APUserPrivileges"
825 .
" WHERE UserId = '".$this->UserId.
"'"
826 .
" AND Privilege = '".$Privilege.
"'");
828 # report success to caller
830 return $this->Result;
835 # read privileges from database and return array to caller
836 $this->DB->Query(
"SELECT Privilege FROM APUserPrivileges"
837 .
" WHERE UserId='".$this->UserId.
"'");
838 return $this->DB->FetchColumn(
"Privilege");
843 # clear old priv list values
844 $this->DB->Query(
"DELETE FROM APUserPrivileges"
845 .
" WHERE UserId='".$this->UserId.
"'");
847 # for each priv value passed in
848 foreach ($NewPrivileges as $Privilege)
856 # ---- Miscellaneous Functions -------------------------------------------
858 # get unique alphanumeric code for user
861 return substr(strtoupper(md5(
862 $this->
Get(
"UserName").$this->
Get(
"UserPassword").$SeedString)),
867 # ---- PRIVATE INTERFACE -------------------------------------------------
869 protected $DB; # handle to SQL database we use to store user information
870 private $UserId; # user ID number
for reference into database
871 private $Result; # result of last operation
872 private $LoggedIn; # flag indicating whether user is logged in
873 private $DBFields; # used
for caching user values
875 # optional mail function to use instead of mail()
876 private static $EmailFunc = NULL;
878 # check whether a user name is valid (alphanumeric string of 2-24 chars)
881 if (preg_match(
"/^[a-zA-Z0-9]{2,24}$/", $UserName)) {
return TRUE; }
else {
return FALSE; }
884 # check whether a password is valid (at least 6 characters)
887 if (strlen(self::NormalizePassword($Password)) < 6)
888 {
return FALSE; }
else {
return TRUE; }
891 # check whether an e-mail address looks valid
894 if (preg_match(
"/^[a-zA-Z0-9._\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]{2,3}$/", $EMail)) {
return TRUE; }
else {
return FALSE; }
897 # get normalized version of e-mail address
900 return strtolower(trim($EMailAddress));
903 # get normalized version of user name
906 return trim($UserName);
909 # get normalized version of password
912 return trim($Password);
915 # generate random password
918 # seed random number generator
919 mt_srand((
double)microtime() * 1000000);
921 # generate password of requested length
922 return sprintf(
"%06d", mt_rand(pow(10, ($PasswordMinLength - 1)),
923 (pow(10, $PasswordMaxLength) - 1)));
926 # convenience function to supply parameters to Database->UpdateValue()
929 return $this->DB->UpdateValue(
"APUsers", $FieldName, $NewValue,
930 "UserId = '".$this->UserId.
"'", $this->DBFields);
933 # methods for backward compatibility with earlier versions of User