CWIS Developer Documentation
User.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # User.php
4 # An Object for Handling User Information
5 #
6 # Copyright 1999-2001 Axis Data
7 # This code is free software that can be used or redistributed under the
8 # terms of Version 2 of the GNU General Public License, as published by the
9 # Free Software Foundation (http://www.fsf.org).
10 #
11 # Author: Edward Almasy (almasy@axisdata.com)
12 #
13 # Part of the AxisPHP library v1.2.4
14 # For more information see http://www.axisdata.com/AxisPHP/
15 #
16 
17 # status values (error codes)
18 define("U_OKAY", 0);
19 define("U_ERROR", 1);
20 define("U_BADPASSWORD", 2);
21 define("U_NOSUCHUSER", 3);
22 define("U_PASSWORDSDONTMATCH", 4);
23 define("U_EMAILSDONTMATCH", 5);
24 define("U_DUPLICATEUSERNAME", 6);
25 define("U_ILLEGALUSERNAME", 7);
26 define("U_EMPTYUSERNAME", 8);
27 define("U_ILLEGALPASSWORD", 9);
28 define("U_ILLEGALPASSWORDAGAIN", 10);
29 define("U_EMPTYPASSWORD", 11);
30 define("U_EMPTYPASSWORDAGAIN", 12);
31 define("U_ILLEGALEMAIL", 13);
32 define("U_ILLEGALEMAILAGAIN", 14);
33 define("U_EMPTYEMAIL", 15);
34 define("U_EMPTYEMAILAGAIN", 16);
35 define("U_NOTLOGGEDIN", 17);
36 define("U_MAILINGERROR", 18);
37 define("U_TEMPLATENOTFOUND", 19);
38 define("U_DUPLICATEEMAIL", 20);
39 define("U_NOTACTIVATED", 21);
40 define("U_PASSWORDCONTAINSUSERNAME", 22);
41 define("U_PASSWORDCONTAINSEMAIL", 23);
42 define("U_PASSWORDTOOSHORT", 24);
43 define("U_PASSWORDTOOSIMPLE", 25);
44 define("U_PASSWORDNEEDSPUNCTUATION", 26);
45 define("U_PASSWORDNEEDSMIXEDCASE", 27);
46 define("U_PASSWORDNEEDSDIGIT", 28);
47 
48 class User
49 {
50  # ---- CLASS CONSTANTS ---------------------------------------------------
53  const PW_REQUIRE_DIGITS = 4;
54 
55  # ---- PUBLIC INTERFACE --------------------------------------------------
56 
57  public function __construct($UserInfoOne = NULL, $UserInfoTwo = NULL)
58  {
59  # create database connection
60  $this->DB = new Database();
61 
62  # if we're looking up a user by UserId
63  if (is_numeric($UserInfoOne) || is_numeric($UserInfoTwo))
64  {
65  $UserId = is_numeric($UserInfoOne) ? $UserInfoOne : $UserInfoTwo;
66  $this->DB->Query("SELECT * FROM APUsers"
67  ." WHERE UserId='".intval($UserId)."'");
68  $Record = $this->DB->FetchRow();
69  }
70  # if we're looking up a user by name or email address
71  elseif (is_string($UserInfoOne) || is_string($UserInfoTwo))
72  {
73  $UserName = is_string($UserInfoOne) ? $UserInfoOne : $UserInfoTwo;
74  $this->DB->Query("SELECT * FROM APUsers"
75  ." WHERE UserName='".addslashes($UserName)."'");
76  $Record = $this->DB->FetchRow();
77 
78  if ($Record === FALSE)
79  {
80  $this->DB->Query("SELECT * FROM APUsers"
81  ." WHERE EMail='".addslashes(
82  self::NormalizeEMailAddress($UserName))."'");
83  $Record = $this->DB->FetchRow();
84  }
85  }
86  # if a UserId is available from the session
87  elseif (isset($_SESSION["APUserId"]))
88  {
89  $UserId = $_SESSION["APUserId"];
90  $this->DB->Query("SELECT * FROM APUsers"
91  ." WHERE UserId='".intval($UserId)."'");
92  $Record = $this->DB->FetchRow();
93  }
94  # otherwise, create an anonymous user
95  else
96  {
97  $Record = array(
98  "UserId" => NULL,
99  "LoggedIn" => FALSE);
100  }
101 
102  # if a record was found, load data from it
103  if ($Record !== FALSE)
104  {
105  $this->DBFields = $Record;
106  $this->UserId = $Record["UserId"];
107  $this->LoggedIn = $Record["LoggedIn"] ? TRUE : FALSE;
108  $this->Result = U_OKAY;
109  }
110  else
111  {
112  # otherwise, set code indicating no user found
113  $this->Result = U_NOSUCHUSER;
114  }
115  }
116 
117  public function Status()
118  {
119  return $this->Result;
120  }
121 
122  # return text message corresponding to current status code
123  public function StatusMessage()
124  {
125  return self::GetStatusMessageForCode($this->Result);
126  }
127 
133  public static function GetStatusMessageForCode($StatusCode)
134  {
135  $APUserStatusMessages = array(
136  U_OKAY => "The operation was successful.",
137  U_ERROR => "There has been an error.",
138  U_BADPASSWORD => "The password you entered was incorrect.",
139  U_NOSUCHUSER => "No such user name was found.",
141  "The new passwords you entered do not match.",
143  "The e-mail addresses you entered do not match.",
145  "The user name you requested is already in use.",
147  "The user name you requested is too short, too long, "
148  ."does not start with a letter, "
149  ."or contains illegal characters.",
151  "The new password you requested is not valid.",
152  U_ILLEGALEMAIL =>
153  "The e-mail address you entered appears to be invalid.",
154  U_NOTLOGGEDIN => "The user is not logged in.",
155  U_MAILINGERROR =>
156  "An error occurred while attempting to send e-mail. "
157  ."Please notify the system administrator.",
159  "An error occurred while attempting to generate e-mail. "
160  ."Please notify the system administrator.",
162  "The e-mail address you supplied already has an account "
163  ."associated with it.",
165  "The password you entered contains your username.",
167  "The password you entered contains your email address.",
168  U_EMPTYPASSWORD => "Passwords may not be empty.",
170  "Passwords must be at least ".self::$PasswordMinLength
171  ." characters long.",
173  "Passwords must have at least ".self::$PasswordMinUniqueChars
174  ." different characters.",
176  "Passwords must contain at least one punctuation character.",
178  "Passwords must contain a mixture of uppercase and "
179  ."lowercase letters",
181  "Passwords must contain at least one number.",
182  );
183 
184  return (isset($APUserStatusMessages[$StatusCode]) ?
185  $APUserStatusMessages[$StatusCode] :
186  "Unknown user status code: ".$StatusCode );
187  }
188 
189  public function Delete()
190  {
191  # clear priv list values
192  $this->DB->Query("DELETE FROM APUserPrivileges WHERE UserId = '"
193  .$this->UserId."'");
194 
195  # delete user record from database
196  $this->DB->Query("DELETE FROM APUsers WHERE UserId = '".$this->UserId."'");
197 
198  # report to caller that everything succeeded
199  $this->Result = U_OKAY;
200  return $this->Result;
201  }
202 
208  public static function SetEmailFunction($NewValue)
209  {
210  if (is_callable($NewValue))
211  {
212  self::$EmailFunc = $NewValue;
213  }
214  }
215 
216  # ---- Getting/Setting Values --------------------------------------------
217 
218  public function Id()
219  {
220  return $this->UserId;
221  }
222  public function Name()
223  {
224  return $this->Get("UserName");
225  }
226 
232  public function GetBestName()
233  {
234  $RealName = $this->Get("RealName");
235 
236  # the real name is available, so use it
237  if (strlen(trim($RealName)))
238  {
239  return $RealName;
240  }
241 
242  # the real name isn't available, so use the user name
243  return $this->Get("UserName");
244  }
245 
246  public function LastLocation($NewLocation = NULL)
247  {
248  # return NULL if not associated with a particular user
249  if ($this->UserId === NULL) { return NULL; }
250 
251  if ($NewLocation)
252  {
253  $this->DB->Query("UPDATE APUsers SET"
254  ." LastLocation = '".addslashes($NewLocation)."',"
255  ." LastActiveDate = NOW(),"
256  ." LastIPAddress = '".$_SERVER["REMOTE_ADDR"]."'"
257  ." WHERE UserId = '".addslashes($this->UserId)."'");
258  if (isset($this->DBFields))
259  {
260  $this->DBFields["LastLocation"] = $NewLocation;
261  $this->DBFields["LastActiveDate"] = date("Y-m-d H:i:s");
262  }
263  }
264  return $this->Get("LastLocation");
265  }
266  public function LastActiveDate()
267  {
268  return $this->Get("LastActiveDate");
269  }
270  public function LastIPAddress()
271  {
272  return $this->Get("LastIPAddress");
273  }
274 
275  # get value from specified field
276  public function Get($FieldName)
277  {
278  # return NULL if not associated with a particular user
279  if ($this->UserId === NULL) { return NULL; }
280 
281  return $this->UpdateValue($FieldName);
282  }
283 
284  # get value (formatted as a date) from specified field
285  public function GetDate($FieldName, $Format = "")
286  {
287  # return NULL if not associated with a particular user
288  if ($this->UserId === NULL) { return NULL; }
289 
290  # retrieve specified value from database
291  if (strlen($Format) > 0)
292  {
293  $this->DB->Query("SELECT DATE_FORMAT(`".addslashes($FieldName)
294  ."`, '".addslashes($Format)."') AS `".addslashes($FieldName)
295  ."` FROM APUsers WHERE UserId='".$this->UserId."'");
296  }
297  else
298  {
299  $this->DB->Query("SELECT `".addslashes($FieldName)."` FROM APUsers WHERE UserId='".$this->UserId."'");
300  }
301  $Record = $this->DB->FetchRow();
302 
303  # return value to caller
304  return $Record[$FieldName];
305  }
306 
307  # set value in specified field
308  public function Set($FieldName, $NewValue)
309  {
310  # return error if not associated with a particular user
311  if ($this->UserId === NULL) { return U_NOTLOGGEDIN; }
312 
313  # transform booleans to 0 or 1 for storage
314  if (is_bool($NewValue))
315  {
316  $NewValue = $NewValue ? 1 : 0;
317  }
318 
319 
320  $this->UpdateValue($FieldName, $NewValue);
321  $this->Result = U_OKAY;
322  return $this->Result;
323  }
324 
325 
326  # ---- Login Functions ---------------------------------------------------
327 
328  public function Login($UserName, $Password, $IgnorePassword = FALSE)
329  {
330  # if user not found in DB
331  $this->DB->Query("SELECT * FROM APUsers"
332  ." WHERE UserName = '"
333  .addslashes(self::NormalizeUserName($UserName))."'");
334  if ($this->DB->NumRowsSelected() < 1)
335  {
336  # result is no user by that name
337  $this->Result = U_NOSUCHUSER;
338  }
339  else
340  {
341  # if user account not yet activated
342  $Record = $this->DB->FetchRow();
343  if (!$Record["RegistrationConfirmed"])
344  {
345  # result is user registration not confirmed
346  $this->Result = U_NOTACTIVATED;
347  }
348  else
349  {
350  # grab password from DB
351  $StoredPassword = $Record["UserPassword"];
352 
353  if (isset($Password[0]) && $Password[0] == " ")
354  {
355  $Challenge = md5(date("Ymd").$_SERVER["REMOTE_ADDR"]);
356  $StoredPassword = md5( $Challenge . $StoredPassword );
357 
358  $EncryptedPassword = trim($Password);
359  }
360  else
361  {
362  # if supplied password matches encrypted password
363  $EncryptedPassword = crypt($Password, $StoredPassword);
364  }
365 
366  if (($EncryptedPassword == $StoredPassword) || $IgnorePassword)
367  {
368  # result is success
369  $this->Result = U_OKAY;
370 
371  # store user ID for session
372  $this->UserId = $Record["UserId"];
373  $_SESSION["APUserId"] = $this->UserId;
374 
375  # update last login date
376  $this->DB->Query("UPDATE APUsers SET LastLoginDate = NOW(),"
377  ." LoggedIn = '1'"
378  ." WHERE UserId = '".$this->UserId."'");
379 
380  # Check for old format hashes, and rehash if possible
381  if ($EncryptedPassword === $StoredPassword &&
382  substr($StoredPassword, 0, 3) !== "$1$" &&
383  $Password[0] !== " " &&
384  CRYPT_MD5 )
385  {
386  $NewPassword = crypt($Password, self::GetSaltForCrypt() );
387  $this->DB->Query(
388  "UPDATE APUsers SET UserPassword='"
389  .addslashes($NewPassword)."' "
390  ."WHERE UserId='".$this->UserId."'");
391  }
392 
393  # since self::DBFields might already have been set to false if
394  # the user wasn't logged in when this is called, populate it
395  # with user data so that a call to self::UpdateValue will be
396  # able to properly fetch the data associated with the user
397  $this->DBFields = $Record;
398 
399  # set flag to indicate we are logged in
400  $this->LoggedIn = TRUE;
401  }
402  else
403  {
404  # result is bad password
405  $this->Result = U_BADPASSWORD;
406  }
407  }
408  }
409 
410  # return result to caller
411  return $this->Result;
412  }
413 
414  # log this user out
415  public function Logout()
416  {
417  # clear user ID (if any) for session
418  unset($_SESSION["APUserId"]);
419 
420  # if user is marked as logged in
421  if ($this->IsLoggedIn())
422  {
423  # set flag to indicate user is no longer logged in
424  $this->LoggedIn = FALSE;
425 
426  # clear login flag in database
427  $this->DB->Query(
428  "UPDATE APUsers SET LoggedIn = '0' "
429  ."WHERE UserId='".$this->UserId."'");
430  }
431  }
432 
433  public function GetPasswordSalt($UserName)
434  {
435  $this->DB->Query(
436  "SELECT * FROM APUsers WHERE UserName = '"
437  .addslashes(self::NormalizeUserName($UserName))."'");
438 
439  if ($this->DB->NumRowsSelected() < 1)
440  {
441  # result is no user by that name, generate a fake salt
442  # to discourage user enumeration. Make it be an old-format
443  # crypt() salt so that it's harder.
444  $SaltString = $_SERVER["SERVER_ADDR"].$UserName;
445  $Result = substr(base64_encode(md5($SaltString)), 0, 2);
446  }
447  else
448  {
449  # grab password from DB
450  # Assumes that we used php's crypt() for the passowrd
451  # management stuff, and will need to be changed if we
452  # go to something else.
453  $Record = $this->DB->FetchRow();
454  $StoredPassword = $Record["UserPassword"];
455 
456  if (substr($StoredPassword, 0, 3) === "$1$")
457  {
458  $Result = substr($StoredPassword, 0, 12);
459  }
460  else
461  {
462  $Result = substr($StoredPassword, 0, 2);
463  }
464  }
465 
466  return $Result;
467  }
468 
474  public function IsLoggedIn()
475  {
476  if (!isset($this->LoggedIn))
477  {
478  $this->LoggedIn = $this->DB->Query("
479  SELECT LoggedIn FROM APUsers
480  WHERE UserId='".addslashes($this->UserId)."'",
481  "LoggedIn") ? TRUE : FALSE;
482  }
483  return $this->LoggedIn;
484  }
485 
491  public function IsNotLoggedIn()
492  {
493  return $this->IsLoggedIn() ? FALSE : TRUE;
494  }
495 
500  public function IsAnonymous()
501  {
502  return ($this->UserId === NULL) ? TRUE : FALSE;
503  }
504 
505 
506  # ---- Password Functions ------------------------------------------------
507 
508  # set new password (with checks against old password)
509 
516  public function ChangePassword($OldPassword, $NewPassword, $NewPasswordAgain)
517  {
518  # return error if not associated with a particular user
519  if ($this->UserId === NULL)
520  {
521  return U_NOTLOGGEDIN;
522  }
523 
524  # if old password is not correct
525  $StoredPassword = $this->DB->Query("SELECT UserPassword FROM APUsers"
526  ." WHERE UserId='".$this->UserId."'", "UserPassword");
527  $EncryptedPassword = crypt($OldPassword, $StoredPassword);
528  if ($EncryptedPassword != $StoredPassword)
529  {
530  # set status to indicate error
531  $this->Result = U_BADPASSWORD;
532  }
533  # else if both instances of new password do not match
534  elseif (self::NormalizePassword($NewPassword)
535  != self::NormalizePassword($NewPasswordAgain))
536  {
537  # set status to indicate error
538  $this->Result = U_PASSWORDSDONTMATCH;
539  }
540  # perform other validity checks
541  elseif (!self::IsValidPassword(
542  $NewPassword, $this->Get("UserName"), $this->Get("EMail")) )
543  {
544  # set status to indicate error
545  $this->Result = U_ILLEGALPASSWORD;
546  }
547  else
548  {
549  # set new password
550  $this->SetPassword($NewPassword);
551 
552  # set status to indicate password successfully changed
553  $this->Result = U_OKAY;
554  }
555 
556  # report to caller that everything succeeded
557  return $this->Result;
558  }
559 
560  # set new password
561  public function SetPassword($NewPassword)
562  {
563  # generate encrypted password
564  $EncryptedPassword = crypt(self::NormalizePassword($NewPassword),
565  self::GetSaltForCrypt() );
566 
567  # save encrypted password
568  $this->UpdateValue("UserPassword", $EncryptedPassword);
569  }
570 
571  public function SetEncryptedPassword($NewEncryptedPassword)
572  {
573  # save encrypted password
574  $this->UpdateValue("UserPassword", $NewEncryptedPassword);
575  }
576 
578  $UserName, $EMail, $EMailAgain,
579  $TemplateFile = "Axis--User--EMailTemplate.txt")
580  {
582  $UserName, $EMail, $EMailAgain, $TemplateFile);
583  }
584 
586  $UserName, $EMail, $EMailAgain,
587  $TemplateFile = "Axis--User--EMailTemplate.txt")
588  {
589  # load e-mail template from file (first line is subject)
590  $Template = file($TemplateFile, 1);
591  $EMailSubject = array_shift($Template);
592  $EMailBody = join("", $Template);
593 
595  $UserName, $EMail, $EMailAgain, $EMailSubject, $EMailBody);
596  }
597 
599  $UserName, $EMail, $EMailAgain, $EMailSubject, $EMailBody)
600  {
601  # make sure e-mail addresses match
602  if ($EMail != $EMailAgain)
603  {
604  $this->Result = U_EMAILSDONTMATCH;
605  return $this->Result;
606  }
607 
608  # make sure e-mail address looks valid
609  if ($this->IsValidLookingEMailAddress($EMail) == FALSE)
610  {
611  $this->Result = U_ILLEGALEMAIL;
612  return $this->Result;
613  }
614 
615  # generate random password
616  $Password = $this->GetRandomPassword();
617 
618  # attempt to create new user with password
619  $Result = $this->CreateNewUser($UserName, $Password, $Password);
620 
621  # if user creation failed
622  if ($Result != U_OKAY)
623  {
624  # report error result to caller
625  return $Result;
626  }
627  # else
628  else
629  {
630  # set e-mail address in user record
631  $this->Set("EMail", $EMail);
632 
633  # plug appropriate values into subject and body of e-mail message
634  $EMailSubject = str_replace("X-USERNAME-X", $UserName, $EMailSubject);
635  $EMailBody = str_replace("X-USERNAME-X", $UserName, $EMailBody);
636  $EMailBody = str_replace("X-PASSWORD-X", $Password, $EMailBody);
637 
638  # send out e-mail message with new account info
639  if (is_Callable(self::$EmailFunc))
640  {
641  $Result = call_user_func(self::$EmailFunc,
642  $EMail, $EMailSubject, $EMailBody,
643  "Auto-Submitted: auto-generated");
644  }
645  else
646  {
647  $Result = mail($EMail, $EMailSubject, $EMailBody,
648  "Auto-Submitted: auto-generated");
649  }
650 
651  # if mailing attempt failed
652  if ($Result != TRUE)
653  {
654  # report error to caller
655  $this->Result = U_MAILINGERROR;
656  return $this->Result;
657  }
658  # else
659  else
660  {
661  # report success to caller
662  $this->Result = U_OKAY;
663  return $this->Result;
664  }
665  }
666  }
667 
668  # get code for user to submit to confirm registration
669  public function GetActivationCode()
670  {
671  # code is MD5 sum based on user name and encrypted password
672  $ActivationCodeLength = 6;
673  return $this->GetUniqueCode("Activation", $ActivationCodeLength);
674  }
675 
676  # check whether confirmation code is valid
677  public function IsActivationCodeGood($Code)
678  {
679  return (strtoupper(trim($Code)) == $this->GetActivationCode())
680  ? TRUE : FALSE;
681  }
682 
683  # get/set whether user registration has been confirmed
684  public function IsActivated($NewValue = DB_NOVALUE)
685  {
686  return $this->UpdateValue("RegistrationConfirmed", $NewValue);
687  }
688 
689  # get code for user to submit to confirm password reset
690  public function GetResetCode()
691  {
692  # code is MD5 sum based on user name and encrypted password
693  $ResetCodeLength = 10;
694  return $this->GetUniqueCode("Reset", $ResetCodeLength);
695  }
696 
697  # check whether password reset code is valid
698  public function IsResetCodeGood($Code)
699  {
700  return (strtoupper(trim($Code)) == $this->GetResetCode())
701  ? TRUE : FALSE;
702  }
703 
704  # get code for user to submit to confirm mail change request
705  public function GetMailChangeCode()
706  {
707  $ResetCodeLength = 10;
708  return $this->GetUniqueCode("MailChange".$this->Get("EMail")
709  .$this->Get("EMailNew"),
710  $ResetCodeLength);
711  }
712 
713  public function IsMailChangeCodeGood($Code)
714  {
715  return (strtoupper(trim($Code)) == $this->GetMailChangeCode())
716  ? TRUE : FALSE;
717  }
718 
719  # send e-mail to user (returns TRUE on success)
720  public function SendEMail(
721  $TemplateTextOrFileName, $FromAddress = NULL, $MoreSubstitutions = NULL,
722  $ToAddress = NULL)
723  {
724  # if template is file name
725  if (@is_file($TemplateTextOrFileName))
726  {
727  # load in template from file
728  $Template = file($TemplateTextOrFileName, 1);
729 
730  # report error to caller if template load failed
731  if ($Template == FALSE)
732  {
733  $this->Status = U_TEMPLATENOTFOUND;
734  return $this->Status;
735  }
736 
737  # join into one text block
738  $TemplateTextOrFileName = join("", $Template);
739  }
740 
741  # split template into lines
742  $Template = explode("\n", $TemplateTextOrFileName);
743 
744  # strip any comments out of template
745  $FilteredTemplate = array();
746  foreach ($Template as $Line)
747  {
748  if (!preg_match("/^[\\s]*#/", $Line))
749  {
750  $FilteredTemplate[] = $Line;
751  }
752  }
753 
754  # split subject line out of template (first non-comment line in file)
755  $EMailSubject = array_shift($FilteredTemplate);
756  $EMailBody = join("\n", $FilteredTemplate);
757 
758  # set up our substitutions
759  $Substitutions = array(
760  "X-USERNAME-X" => $this->Get("UserName"),
761  "X-EMAILADDRESS-X" => $this->Get("EMail"),
762  "X-ACTIVATIONCODE-X" => $this->GetActivationCode(),
763  "X-RESETCODE-X" => $this->GetResetCode(),
764  "X-CHANGECODE-X" => $this->GetMailChangeCode(),
765  "X-IPADDRESS-X" => @$_SERVER["REMOTE_ADDR"],
766  );
767 
768  # if caller provided additional substitutions
769  if (is_array($MoreSubstitutions))
770  {
771  # add in entries from caller to substitution list
772  $Substitutions = array_merge(
773  $Substitutions, $MoreSubstitutions);
774  }
775 
776  # perform substitutions on subject and body of message
777  $EMailSubject = str_replace(array_keys($Substitutions),
778  array_values($Substitutions), $EMailSubject);
779  $EMailBody = str_replace(array_keys($Substitutions),
780  array_values($Substitutions), $EMailBody);
781 
782  $AdditionalHeaders = "Auto-Submitted: auto-generated";
783 
784  # if caller provided "From" address
785  if ($FromAddress)
786  {
787  # prepend "From" address onto message
788  $AdditionalHeaders .= "\r\nFrom: ".$FromAddress;
789  }
790 
791  # send out mail message
792  if (is_Callable(self::$EmailFunc))
793  {
794  $Result = call_user_func(self::$EmailFunc,
795  is_null($ToAddress)?$this->Get("EMail"):$ToAddress,
796  $EMailSubject, $EMailBody, $AdditionalHeaders);
797  }
798  else
799  {
800  $Result = mail(is_null($ToAddress)?$this->Get("EMail"):$ToAddress,
801  $EMailSubject,
802  $EMailBody, $AdditionalHeaders);
803  }
804 
805  # report result of mailing attempt to caller
806  $this->Status = ($Result == TRUE) ? U_OKAY : U_MAILINGERROR;
807  return ($this->Status == U_OKAY);
808  }
809 
810 
811  # ---- Privilege Functions -----------------------------------------------
812 
821  public function HasPriv($Privilege, $Privileges = NULL)
822  {
823  # return FALSE if not associated with a particular user
824  if ($this->UserId === NULL) { return FALSE; }
825 
826  # bail out if empty array of privileges passed in
827  if (is_array($Privilege) && !count($Privilege) && (func_num_args() < 2))
828  { return FALSE; }
829 
830  # set up beginning of database query
831  $Query = "SELECT COUNT(*) AS PrivCount FROM APUserPrivileges "
832  ."WHERE UserId='".$this->UserId."' AND (";
833 
834  # add first privilege(s) to query (first arg may be single value or array)
835  if (is_array($Privilege))
836  {
837  $Sep = "";
838  foreach ($Privilege as $Priv)
839  {
840  $Query .= $Sep."Privilege='".addslashes($Priv)."'";
841  $Sep = " OR ";
842  }
843  }
844  else
845  {
846  $Query .= "Privilege='".$Privilege."'";
847  $Sep = " OR ";
848  }
849 
850  # add any privileges from additional args to query
851  $Args = func_get_args();
852  array_shift($Args);
853  foreach ($Args as $Arg)
854  {
855  $Query .= $Sep."Privilege='".$Arg."'";
856  $Sep = " OR ";
857  }
858 
859  # close out query
860  $Query .= ")";
861 
862  # look for privilege in database
863  $PrivCount = $this->DB->Query($Query, "PrivCount");
864 
865  # return value to caller
866  return ($PrivCount > 0) ? TRUE : FALSE;
867  }
868 
877  public static function GetSqlQueryForUsersWithPriv($Privilege, $Privileges = NULL)
878  {
879  # set up beginning of database query
880  $Query = "SELECT DISTINCT UserId FROM APUserPrivileges "
881  ."WHERE ";
882 
883  # add first privilege(s) to query (first arg may be single value or array)
884  if (is_array($Privilege))
885  {
886  $Sep = "";
887  foreach ($Privilege as $Priv)
888  {
889  $Query .= $Sep."Privilege='".addslashes($Priv)."'";
890  $Sep = " OR ";
891  }
892  }
893  else
894  {
895  $Query .= "Privilege='".$Privilege."'";
896  $Sep = " OR ";
897  }
898 
899  # add any privileges from additional args to query
900  $Args = func_get_args();
901  array_shift($Args);
902  foreach ($Args as $Arg)
903  {
904  $Query .= $Sep."Privilege='".$Arg."'";
905  $Sep = " OR ";
906  }
907 
908  # return query to caller
909  return $Query;
910  }
911 
920  public static function GetSqlQueryForUsersWithoutPriv($Privilege, $Privileges = NULL)
921  {
922  # set up beginning of database query
923  $Query = "SELECT DISTINCT UserId FROM APUserPrivileges "
924  ."WHERE ";
925 
926  # add first privilege(s) to query (first arg may be single value or array)
927  if (is_array($Privilege))
928  {
929  $Sep = "";
930  foreach ($Privilege as $Priv)
931  {
932  $Query .= $Sep."Privilege != '".addslashes($Priv)."'";
933  $Sep = " AND ";
934  }
935  }
936  else
937  {
938  $Query .= "Privilege != '".$Privilege."'";
939  $Sep = " AND ";
940  }
941 
942  # add any privileges from additional args to query
943  $Args = func_get_args();
944  array_shift($Args);
945  foreach ($Args as $Arg)
946  {
947  $Query .= $Sep."Privilege != '".$Arg."'";
948  $Sep = " AND ";
949  }
950 
951  # return query to caller
952  return $Query;
953  }
954 
955  public function GrantPriv($Privilege)
956  {
957  # return error if not associated with a particular user
958  if ($this->UserId === NULL) { return U_NOTLOGGEDIN; }
959 
960  # if privilege value is invalid
961  if (intval($Privilege) != trim($Privilege))
962  {
963  # set code to indicate error
964  $this->Result = U_ERROR;
965  }
966  else
967  {
968  # if user does not already have privilege
969  $PrivCount = $this->DB->Query("SELECT COUNT(*) AS PrivCount"
970  ." FROM APUserPrivileges"
971  ." WHERE UserId='".$this->UserId."'"
972  ." AND Privilege='".$Privilege."'",
973  "PrivCount");
974  if ($PrivCount == 0)
975  {
976  # add privilege for this user to database
977  $this->DB->Query("INSERT INTO APUserPrivileges"
978  ." (UserId, Privilege) VALUES"
979  ." ('".$this->UserId."', ".$Privilege.")");
980  }
981 
982  # set code to indicate success
983  $this->Result = U_OKAY;
984  }
985 
986  # report result to caller
987  return $this->Result;
988  }
989 
990  public function RevokePriv($Privilege)
991  {
992  # return error if not associated with a particular user
993  if ($this->UserId === NULL) { return U_NOTLOGGEDIN; }
994 
995  # remove privilege from database (if present)
996  $this->DB->Query("DELETE FROM APUserPrivileges"
997  ." WHERE UserId = '".$this->UserId."'"
998  ." AND Privilege = '".$Privilege."'");
999 
1000  # report success to caller
1001  $this->Result = U_OKAY;
1002  return $this->Result;
1003  }
1004 
1005  public function GetPrivList()
1006  {
1007  # return empty list if not associated with a particular user
1008  if ($this->UserId === NULL) { return array(); }
1009 
1010  # read privileges from database and return array to caller
1011  $this->DB->Query("SELECT Privilege FROM APUserPrivileges"
1012  ." WHERE UserId='".$this->UserId."'");
1013  return $this->DB->FetchColumn("Privilege");
1014  }
1015 
1016  public function SetPrivList($NewPrivileges)
1017  {
1018  # return error if not associated with a particular user
1019  if ($this->UserId === NULL) { return U_NOTLOGGEDIN; }
1020 
1021  # clear old priv list values
1022  $this->DB->Query("DELETE FROM APUserPrivileges"
1023  ." WHERE UserId='".$this->UserId."'");
1024 
1025  # for each priv value passed in
1026  foreach ($NewPrivileges as $Privilege)
1027  {
1028  # set priv for user
1029  $this->GrantPriv($Privilege);
1030  }
1031  }
1032 
1039  public static function GetAnonymousUser()
1040  {
1041  # if we have a UserId in the session, move it aside
1042  if (isset($_SESSION["APUserId"]))
1043  {
1044  $OldUserId = $_SESSION["APUserId"];
1045  unset($_SESSION["APUserId"]);
1046  }
1047 
1048  # create a new anonymous user
1049  $CalledClass = get_called_class();
1050 
1051  $Result = new $CalledClass();
1052 
1053  # restore the $_SESSION value
1054  if (isset($OldUserId))
1055  {
1056  $_SESSION["APUserId"] = $OldUserId;
1057  }
1058 
1059  # return our anonymous user
1060  return $Result;
1061  }
1062 
1063  # ---- Miscellaneous Functions -------------------------------------------
1064 
1065  # get unique alphanumeric code for user
1066  public function GetUniqueCode($SeedString, $CodeLength)
1067  {
1068  # return NULL if not associated with a particular user
1069  if ($this->UserId === NULL) { return NULL; }
1070 
1071  return substr(strtoupper(md5(
1072  $this->Get("UserName").$this->Get("UserPassword").$SeedString)),
1073  0, $CodeLength);
1074  }
1075 
1076 
1077  # ---- PRIVATE INTERFACE -------------------------------------------------
1078 
1079  protected $DB; # handle to SQL database we use to store user information
1080  protected $UserId = NULL; # user ID number for reference into database
1081  protected $Result; # result of last operation
1082  protected $LoggedIn; # flag indicating whether user is logged in
1083  private $DBFields; # used for caching user values
1084 
1085  # optional mail function to use instead of mail()
1086  private static $EmailFunc = NULL;
1087 
1088  # check whether a user name is valid
1089  # (alphanumeric string of 2-24 chars that starts with a letter)
1090  public static function IsValidUserName($UserName)
1091  {
1092  if (preg_match("/^[a-zA-Z][a-zA-Z0-9]{1,23}$/", $UserName))
1093  {
1094  return TRUE;
1095  }
1096  return FALSE;
1097  }
1098 
1099  # check whether a password is valid (at least 6 characters)
1100  public static function IsValidPassword(
1101  $Password, $UserName, $Email)
1102  {
1103  return count(self::CheckPasswordForErrors(
1104  $Password, $UserName, $Email)) == 0 ?
1105  TRUE : FALSE ;
1106  }
1107 
1117  public static function CheckPasswordForErrors(
1118  $Password, $UserName = NULL, $Email = NULL)
1119  {
1120  # start off assuming no errors
1121  $Errors = array();
1122 
1123  # normalize incoming password
1124  $Password = self::NormalizePassword($Password);
1125 
1126  # username provided and password contains username
1127  if ($UserName !== NULL &&
1128  stripos($Password, $UserName) !== FALSE)
1129  {
1130  $Errors[]= U_PASSWORDCONTAINSUSERNAME;
1131  }
1132 
1133  # email provided and password contains email
1134  if ($Email !== NULL &&
1135  stripos($Password, $Email) !== FALSE)
1136  {
1137  $Errors[]= U_PASSWORDCONTAINSEMAIL;
1138  }
1139 
1140  # length requirement
1141  if (strlen($Password) == 0)
1142  {
1143  $Errors[]= U_EMPTYPASSWORD;
1144  }
1145  elseif (strlen($Password) < self::$PasswordMinLength)
1146  {
1147  $Errors[]= U_PASSWORDTOOSHORT;
1148  }
1149 
1150  # unique characters requirement
1151  $UniqueChars = count(array_unique(
1152  preg_split('//u', $Password, NULL, PREG_SPLIT_NO_EMPTY)));
1153 
1154  if ($UniqueChars < self::$PasswordMinUniqueChars)
1155  {
1156  $Errors[]= U_PASSWORDTOOSIMPLE;
1157  }
1158 
1159  # for the following complexity checks, use unicode character properties
1160  # in PCRE as in: http://php.net/manual/en/regexp.reference.unicode.php
1161 
1162  # check for punctuation, uppercase letters, and numbers as per the system
1163  # configuration
1164  if (self::$PasswordRules & self::PW_REQUIRE_PUNCTUATION &&
1165  !preg_match('/\p{P}/u', $Password) )
1166  {
1167  $Errors[]= U_PASSWORDNEEDSPUNCTUATION;
1168  }
1169 
1170  if (self::$PasswordRules & self::PW_REQUIRE_MIXEDCASE &&
1171  (!preg_match('/\p{Lu}/u', $Password) ||
1172  !preg_match('/\p{Ll}/u', $Password) ) )
1173 
1174  {
1175  $Errors[]= U_PASSWORDNEEDSMIXEDCASE;
1176  }
1177 
1178  if (self::$PasswordRules & self::PW_REQUIRE_DIGITS &&
1179  !preg_match('/\p{N}/u', $Password))
1180  {
1181  $Errors[]= U_PASSWORDNEEDSDIGIT;
1182  }
1183 
1184  return $Errors;
1185  }
1186 
1187  # check whether an e-mail address looks valid
1188  public static function IsValidLookingEMailAddress($EMail)
1189  {
1190  if (preg_match("/^[a-zA-Z0-9._\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]{2,3}$/",
1191  $EMail))
1192  {
1193  return TRUE;
1194  }
1195  else
1196  {
1197  return FALSE;
1198  }
1199  }
1200 
1201  # get normalized version of e-mail address
1202  public static function NormalizeEMailAddress($EMailAddress)
1203  {
1204  return strtolower(trim($EMailAddress));
1205  }
1206 
1207  # get normalized version of user name
1208  public static function NormalizeUserName($UserName)
1209  {
1210  return trim($UserName);
1211  }
1212 
1213  # get normalized version of password
1214  public static function NormalizePassword($Password)
1215  {
1216  return trim($Password);
1217  }
1218 
1219  # generate random password
1220  public function GetRandomPassword($PasswordMinLength = 6, $PasswordMaxLength = 8)
1221  {
1222  # seed random number generator
1223  mt_srand((double)microtime() * 1000000);
1224 
1225  # generate password of requested length
1226  return sprintf("%06d", mt_rand(pow(10, ($PasswordMinLength - 1)),
1227  (pow(10, $PasswordMaxLength) - 1)));
1228  }
1229 
1230  # convenience function to supply parameters to Database->UpdateValue()
1231  public function UpdateValue($FieldName, $NewValue = DB_NOVALUE)
1232  {
1233  return $this->DB->UpdateValue("APUsers", $FieldName, $NewValue,
1234  "UserId = '".$this->UserId."'", $this->DBFields);
1235  }
1236 
1237  # methods for backward compatibility with earlier versions of User
1238  public function GivePriv($Privilege)
1239  {
1240  $this->GrantPriv($Privilege);
1241  }
1242 
1248  public static function SetPasswordRules($NewValue)
1249  {
1250  self::$PasswordRules = $NewValue;
1251  }
1252 
1257  public static function SetPasswordMinLength($NewValue)
1258  {
1259  self::$PasswordMinLength = $NewValue;
1260  }
1261 
1266  public static function SetPasswordMinUniqueChars($NewValue)
1267  {
1268  self::$PasswordMinUniqueChars = $NewValue;
1269  }
1270 
1275  public static function GetPasswordRulesDescription()
1276  {
1277  return "Passwords are case-sensitive, cannot contain your username or email, "
1278  ."must be at least ".self::$PasswordMinLength
1279  ." characters long, "
1280  ." have at least ".self::$PasswordMinUniqueChars
1281  ." different characters"
1282  .(self::$PasswordRules & self::PW_REQUIRE_PUNCTUATION ?
1283  ", include punctuation":"")
1284  .(self::$PasswordRules & self::PW_REQUIRE_MIXEDCASE ?
1285  ", include capital and lowercase letters":"")
1286  .(self::$PasswordRules & self::PW_REQUIRE_DIGITS ?
1287  ", include a number":"").".";
1288  }
1289 
1293  private static function GetSaltForCrypt()
1294  {
1295  # generate a password salt by grabbing CRYPT_SALT_LENGTH
1296  # random bytes, then base64 encoding while filtering out
1297  # non-alphanumeric characters to get a string all the hashes
1298  # accept as a salt
1299  $Salt = preg_replace("/[^A-Za-z0-9]/","",
1300  base64_encode(openssl_random_pseudo_bytes(
1301  CRYPT_SALT_LENGTH) ));
1302 
1303  # select the best available hashing algorithm, provide a salt
1304  # in the correct format for that algorithm
1305  if (CRYPT_SHA512==1)
1306  {
1307  return '$6$'.substr($Salt, 0, 16);
1308  }
1309  elseif (CRYPT_SHA256==1)
1310  {
1311  return '$5$'.substr($Salt, 0, 16);
1312  }
1313  elseif (CRYPT_BLOWFISH==1)
1314  {
1315  return '$2y$'.substr($Salt, 0, 22);
1316  }
1317  elseif (CRYPT_MD5==1)
1318  {
1319  return '$1$'.substr($Salt, 0, 12);
1320  }
1321  elseif (CRYPT_EXT_DES==1)
1322  {
1323  return '_'.substr($Salt, 0, 8);
1324  }
1325  else
1326  {
1327  return substr($Salt, 0, 2);
1328  }
1329  }
1330 
1331  private static $PasswordMinLength = 6;
1332  private static $PasswordMinUniqueChars = 4;
1333 
1334  # default to no additional requirements beyond length
1335  private static $PasswordRules = 0;
1336 }
Get($FieldName)
Definition: User.php:276
$DB
Definition: User.php:1079
GetRandomPassword($PasswordMinLength=6, $PasswordMaxLength=8)
Definition: User.php:1220
static GetAnonymousUser()
Get the anonymous user (i.e., the User object that exists when no user is logged in), useful when a permission check needs to know if something should be visible to the general public.
Definition: User.php:1039
static NormalizeUserName($UserName)
Definition: User.php:1208
const U_TEMPLATENOTFOUND
Definition: User.php:37
IsLoggedIn()
Report whether user is currently logged in.
Definition: User.php:474
static IsValidLookingEMailAddress($EMail)
Definition: User.php:1188
static SetPasswordMinLength($NewValue)
Set password minimum length.
Definition: User.php:1257
GetMailChangeCode()
Definition: User.php:705
GetUniqueCode($SeedString, $CodeLength)
Definition: User.php:1066
GivePriv($Privilege)
Definition: User.php:1238
__construct($UserInfoOne=NULL, $UserInfoTwo=NULL)
Definition: User.php:57
GrantPriv($Privilege)
Definition: User.php:955
SQL database abstraction object with smart query caching.
Definition: Database.php:22
static CheckPasswordForErrors($Password, $UserName=NULL, $Email=NULL)
Determine if a provided password complies with the configured rules, optionally checking that it does...
Definition: User.php:1117
IsMailChangeCodeGood($Code)
Definition: User.php:713
const U_EMAILSDONTMATCH
Definition: User.php:23
GetResetCode()
Definition: User.php:690
UpdateValue($FieldName, $NewValue=DB_NOVALUE)
Definition: User.php:1231
static NormalizePassword($Password)
Definition: User.php:1214
CreateNewUserAndMailPassword($UserName, $EMail, $EMailAgain, $EMailSubject, $EMailBody)
Definition: User.php:598
const U_EMPTYPASSWORD
Definition: User.php:29
const U_PASSWORDNEEDSPUNCTUATION
Definition: User.php:44
GetActivationCode()
Definition: User.php:669
const U_ERROR
Definition: User.php:19
const U_PASSWORDNEEDSDIGIT
Definition: User.php:46
const U_NOTLOGGEDIN
Definition: User.php:35
const U_BADPASSWORD
Definition: User.php:20
const U_ILLEGALEMAIL
Definition: User.php:31
Login($UserName, $Password, $IgnorePassword=FALSE)
Definition: User.php:328
Definition: User.php:48
SetEncryptedPassword($NewEncryptedPassword)
Definition: User.php:571
const U_PASSWORDCONTAINSEMAIL
Definition: User.php:41
Delete()
Definition: User.php:189
IsResetCodeGood($Code)
Definition: User.php:698
const PW_REQUIRE_MIXEDCASE
Definition: User.php:52
static IsValidUserName($UserName)
Definition: User.php:1090
static GetSqlQueryForUsersWithPriv($Privilege, $Privileges=NULL)
Get an SQL query that will return IDs of all users that have the specified privilege flags...
Definition: User.php:877
GetPasswordSalt($UserName)
Definition: User.php:433
LastLocation($NewLocation=NULL)
Definition: User.php:246
static SetPasswordMinUniqueChars($NewValue)
Set password minimum unique characters.
Definition: User.php:1266
GetPrivList()
Definition: User.php:1005
static SetPasswordRules($NewValue)
Set password requirements.
Definition: User.php:1248
IsActivated($NewValue=DB_NOVALUE)
Definition: User.php:684
static GetStatusMessageForCode($StatusCode)
Get text error message for a specified error code.
Definition: User.php:133
const PW_REQUIRE_PUNCTUATION
Definition: User.php:51
HasPriv($Privilege, $Privileges=NULL)
Check whether user has specified privilege(s).
Definition: User.php:821
const U_PASSWORDNEEDSMIXEDCASE
Definition: User.php:45
$LoggedIn
Definition: User.php:1082
GetBestName()
Get the best available name associated with a user, i.e., the real name or, if it isn&#39;t available...
Definition: User.php:232
SendEMail($TemplateTextOrFileName, $FromAddress=NULL, $MoreSubstitutions=NULL, $ToAddress=NULL)
Definition: User.php:720
const U_PASSWORDCONTAINSUSERNAME
Definition: User.php:40
const U_PASSWORDTOOSIMPLE
Definition: User.php:43
Set($FieldName, $NewValue)
Definition: User.php:308
const DB_NOVALUE
Definition: Database.php:1738
const U_MAILINGERROR
Definition: User.php:36
IsAnonymous()
Report whether user is anonymous user.
Definition: User.php:500
static GetPasswordRulesDescription()
Get a string describing the password rules.
Definition: User.php:1275
const U_DUPLICATEUSERNAME
Definition: User.php:24
static SetEmailFunction($NewValue)
Set email function to use instead of mail().
Definition: User.php:208
const U_OKAY
Definition: User.php:18
static IsValidPassword($Password, $UserName, $Email)
Definition: User.php:1100
static NormalizeEMailAddress($EMailAddress)
Definition: User.php:1202
GetDate($FieldName, $Format="")
Definition: User.php:285
const U_DUPLICATEEMAIL
Definition: User.php:38
CreateNewUserWithEMailedPassword($UserName, $EMail, $EMailAgain, $TemplateFile="Axis--User--EMailTemplate.txt")
Definition: User.php:577
Status()
Definition: User.php:117
IsNotLoggedIn()
Report whether user is not currently logged in.
Definition: User.php:491
Logout()
Definition: User.php:415
LastActiveDate()
Definition: User.php:266
$Result
Definition: User.php:1081
CreateNewUserAndMailPasswordFromFile($UserName, $EMail, $EMailAgain, $TemplateFile="Axis--User--EMailTemplate.txt")
Definition: User.php:585
LastIPAddress()
Definition: User.php:270
const U_NOSUCHUSER
Definition: User.php:21
Id()
Definition: User.php:218
SetPassword($NewPassword)
Definition: User.php:561
const U_NOTACTIVATED
Definition: User.php:39
$UserId
Definition: User.php:1080
SetPrivList($NewPrivileges)
Definition: User.php:1016
ChangePassword($OldPassword, $NewPassword, $NewPasswordAgain)
Check provided password and set a new one if it war correct.
Definition: User.php:516
IsActivationCodeGood($Code)
Definition: User.php:677
const U_ILLEGALUSERNAME
Definition: User.php:25
RevokePriv($Privilege)
Definition: User.php:990
const U_PASSWORDTOOSHORT
Definition: User.php:42
Name()
Definition: User.php:222
const PW_REQUIRE_DIGITS
Definition: User.php:53
const U_ILLEGALPASSWORD
Definition: User.php:27
static GetSqlQueryForUsersWithoutPriv($Privilege, $Privileges=NULL)
Get an SQL query that will return IDs of all users that do not have the specified privilege flags...
Definition: User.php:920
StatusMessage()
Definition: User.php:123
const U_PASSWORDSDONTMATCH
Definition: User.php:22