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 
577  # get code for user to submit to confirm registration
578  public function GetActivationCode()
579  {
580  # code is MD5 sum based on user name and encrypted password
581  $ActivationCodeLength = 6;
582  return $this->GetUniqueCode("Activation", $ActivationCodeLength);
583  }
584 
585  # check whether confirmation code is valid
586  public function IsActivationCodeGood($Code)
587  {
588  return (strtoupper(trim($Code)) == $this->GetActivationCode())
589  ? TRUE : FALSE;
590  }
591 
592  # get/set whether user registration has been confirmed
593  public function IsActivated($NewValue = DB_NOVALUE)
594  {
595  return $this->UpdateValue("RegistrationConfirmed", $NewValue);
596  }
597 
598  # get code for user to submit to confirm password reset
599  public function GetResetCode()
600  {
601  # code is MD5 sum based on user name and encrypted password
602  $ResetCodeLength = 10;
603  return $this->GetUniqueCode("Reset", $ResetCodeLength);
604  }
605 
606  # check whether password reset code is valid
607  public function IsResetCodeGood($Code)
608  {
609  return (strtoupper(trim($Code)) == $this->GetResetCode())
610  ? TRUE : FALSE;
611  }
612 
613  # get code for user to submit to confirm mail change request
614  public function GetMailChangeCode()
615  {
616  $ResetCodeLength = 10;
617  return $this->GetUniqueCode("MailChange".$this->Get("EMail")
618  .$this->Get("EMailNew"),
619  $ResetCodeLength);
620  }
621 
622  public function IsMailChangeCodeGood($Code)
623  {
624  return (strtoupper(trim($Code)) == $this->GetMailChangeCode())
625  ? TRUE : FALSE;
626  }
627 
628  # send e-mail to user (returns TRUE on success)
629  public function SendEMail(
630  $TemplateTextOrFileName, $FromAddress = NULL, $MoreSubstitutions = NULL,
631  $ToAddress = NULL)
632  {
633  # if template is file name
634  if (@is_file($TemplateTextOrFileName))
635  {
636  # load in template from file
637  $Template = file($TemplateTextOrFileName, 1);
638 
639  # report error to caller if template load failed
640  if ($Template == FALSE)
641  {
642  $this->Status = U_TEMPLATENOTFOUND;
643  return $this->Status;
644  }
645 
646  # join into one text block
647  $TemplateTextOrFileName = join("", $Template);
648  }
649 
650  # split template into lines
651  $Template = explode("\n", $TemplateTextOrFileName);
652 
653  # strip any comments out of template
654  $FilteredTemplate = array();
655  foreach ($Template as $Line)
656  {
657  if (!preg_match("/^[\\s]*#/", $Line))
658  {
659  $FilteredTemplate[] = $Line;
660  }
661  }
662 
663  # split subject line out of template (first non-comment line in file)
664  $EMailSubject = array_shift($FilteredTemplate);
665  $EMailBody = join("\n", $FilteredTemplate);
666 
667  # set up our substitutions
668  $Substitutions = array(
669  "X-USERNAME-X" => $this->Get("UserName"),
670  "X-EMAILADDRESS-X" => $this->Get("EMail"),
671  "X-ACTIVATIONCODE-X" => $this->GetActivationCode(),
672  "X-RESETCODE-X" => $this->GetResetCode(),
673  "X-CHANGECODE-X" => $this->GetMailChangeCode(),
674  "X-IPADDRESS-X" => @$_SERVER["REMOTE_ADDR"],
675  );
676 
677  # if caller provided additional substitutions
678  if (is_array($MoreSubstitutions))
679  {
680  # add in entries from caller to substitution list
681  $Substitutions = array_merge(
682  $Substitutions, $MoreSubstitutions);
683  }
684 
685  # perform substitutions on subject and body of message
686  $EMailSubject = str_replace(array_keys($Substitutions),
687  array_values($Substitutions), $EMailSubject);
688  $EMailBody = str_replace(array_keys($Substitutions),
689  array_values($Substitutions), $EMailBody);
690 
691  $AdditionalHeaders = "Auto-Submitted: auto-generated";
692 
693  # if caller provided "From" address
694  if ($FromAddress)
695  {
696  # prepend "From" address onto message
697  $AdditionalHeaders .= "\r\nFrom: ".$FromAddress;
698  }
699 
700  # send out mail message
701  if (is_callable(self::$EmailFunc))
702  {
703  $Result = call_user_func(self::$EmailFunc,
704  is_null($ToAddress)?$this->Get("EMail"):$ToAddress,
705  $EMailSubject, $EMailBody, $AdditionalHeaders);
706  }
707  else
708  {
709  $Result = mail(is_null($ToAddress)?$this->Get("EMail"):$ToAddress,
710  $EMailSubject,
711  $EMailBody, $AdditionalHeaders);
712  }
713 
714  # report result of mailing attempt to caller
715  $this->Status = ($Result == TRUE) ? U_OKAY : U_MAILINGERROR;
716  return ($this->Status == U_OKAY);
717  }
718 
719 
720  # ---- Privilege Functions -----------------------------------------------
721 
730  public function HasPriv($Privilege, $Privileges = NULL)
731  {
732  # return FALSE if not associated with a particular user
733  if ($this->UserId === NULL) { return FALSE; }
734 
735  # bail out if empty array of privileges passed in
736  if (is_array($Privilege) && !count($Privilege) && (func_num_args() < 2))
737  { return FALSE; }
738 
739  # set up beginning of database query
740  $Query = "SELECT COUNT(*) AS PrivCount FROM APUserPrivileges "
741  ."WHERE UserId='".$this->UserId."' AND (";
742 
743  # add first privilege(s) to query (first arg may be single value or array)
744  if (is_array($Privilege))
745  {
746  $Sep = "";
747  foreach ($Privilege as $Priv)
748  {
749  $Query .= $Sep."Privilege='".addslashes($Priv)."'";
750  $Sep = " OR ";
751  }
752  }
753  else
754  {
755  $Query .= "Privilege='".$Privilege."'";
756  $Sep = " OR ";
757  }
758 
759  # add any privileges from additional args to query
760  $Args = func_get_args();
761  array_shift($Args);
762  foreach ($Args as $Arg)
763  {
764  $Query .= $Sep."Privilege='".$Arg."'";
765  $Sep = " OR ";
766  }
767 
768  # close out query
769  $Query .= ")";
770 
771  # look for privilege in database
772  $PrivCount = $this->DB->Query($Query, "PrivCount");
773 
774  # return value to caller
775  return ($PrivCount > 0) ? TRUE : FALSE;
776  }
777 
786  public static function GetSqlQueryForUsersWithPriv($Privilege, $Privileges = NULL)
787  {
788  # set up beginning of database query
789  $Query = "SELECT DISTINCT UserId FROM APUserPrivileges "
790  ."WHERE ";
791 
792  # add first privilege(s) to query (first arg may be single value or array)
793  if (is_array($Privilege))
794  {
795  $Sep = "";
796  foreach ($Privilege as $Priv)
797  {
798  $Query .= $Sep."Privilege='".addslashes($Priv)."'";
799  $Sep = " OR ";
800  }
801  }
802  else
803  {
804  $Query .= "Privilege='".$Privilege."'";
805  $Sep = " OR ";
806  }
807 
808  # add any privileges from additional args to query
809  $Args = func_get_args();
810  array_shift($Args);
811  foreach ($Args as $Arg)
812  {
813  $Query .= $Sep."Privilege='".$Arg."'";
814  $Sep = " OR ";
815  }
816 
817  # return query to caller
818  return $Query;
819  }
820 
829  public static function GetSqlQueryForUsersWithoutPriv($Privilege, $Privileges = NULL)
830  {
831  # set up beginning of database query
832  $Query = "SELECT DISTINCT UserId FROM APUserPrivileges "
833  ."WHERE ";
834 
835  # add first privilege(s) to query (first arg may be single value or array)
836  if (is_array($Privilege))
837  {
838  $Sep = "";
839  foreach ($Privilege as $Priv)
840  {
841  $Query .= $Sep."Privilege != '".addslashes($Priv)."'";
842  $Sep = " AND ";
843  }
844  }
845  else
846  {
847  $Query .= "Privilege != '".$Privilege."'";
848  $Sep = " AND ";
849  }
850 
851  # add any privileges from additional args to query
852  $Args = func_get_args();
853  array_shift($Args);
854  foreach ($Args as $Arg)
855  {
856  $Query .= $Sep."Privilege != '".$Arg."'";
857  $Sep = " AND ";
858  }
859 
860  # return query to caller
861  return $Query;
862  }
863 
864  public function GrantPriv($Privilege)
865  {
866  # return error if not associated with a particular user
867  if ($this->UserId === NULL) { return U_NOTLOGGEDIN; }
868 
869  # if privilege value is invalid
870  if (intval($Privilege) != trim($Privilege))
871  {
872  # set code to indicate error
873  $this->Result = U_ERROR;
874  }
875  else
876  {
877  # if user does not already have privilege
878  $PrivCount = $this->DB->Query("SELECT COUNT(*) AS PrivCount"
879  ." FROM APUserPrivileges"
880  ." WHERE UserId='".$this->UserId."'"
881  ." AND Privilege='".$Privilege."'",
882  "PrivCount");
883  if ($PrivCount == 0)
884  {
885  # add privilege for this user to database
886  $this->DB->Query("INSERT INTO APUserPrivileges"
887  ." (UserId, Privilege) VALUES"
888  ." ('".$this->UserId."', ".$Privilege.")");
889  }
890 
891  # set code to indicate success
892  $this->Result = U_OKAY;
893  }
894 
895  # report result to caller
896  return $this->Result;
897  }
898 
899  public function RevokePriv($Privilege)
900  {
901  # return error if not associated with a particular user
902  if ($this->UserId === NULL) { return U_NOTLOGGEDIN; }
903 
904  # remove privilege from database (if present)
905  $this->DB->Query("DELETE FROM APUserPrivileges"
906  ." WHERE UserId = '".$this->UserId."'"
907  ." AND Privilege = '".$Privilege."'");
908 
909  # report success to caller
910  $this->Result = U_OKAY;
911  return $this->Result;
912  }
913 
914  public function GetPrivList()
915  {
916  # return empty list if not associated with a particular user
917  if ($this->UserId === NULL) { return array(); }
918 
919  # read privileges from database and return array to caller
920  $this->DB->Query("SELECT Privilege FROM APUserPrivileges"
921  ." WHERE UserId='".$this->UserId."'");
922  return $this->DB->FetchColumn("Privilege");
923  }
924 
925  public function SetPrivList($NewPrivileges)
926  {
927  # return error if not associated with a particular user
928  if ($this->UserId === NULL) { return U_NOTLOGGEDIN; }
929 
930  # clear old priv list values
931  $this->DB->Query("DELETE FROM APUserPrivileges"
932  ." WHERE UserId='".$this->UserId."'");
933 
934  # for each priv value passed in
935  foreach ($NewPrivileges as $Privilege)
936  {
937  # set priv for user
938  $this->GrantPriv($Privilege);
939  }
940  }
941 
948  public static function GetAnonymousUser()
949  {
950  # if we have a UserId in the session, move it aside
951  if (isset($_SESSION["APUserId"]))
952  {
953  $OldUserId = $_SESSION["APUserId"];
954  unset($_SESSION["APUserId"]);
955  }
956 
957  # create a new anonymous user
958  $CalledClass = get_called_class();
959 
960  $Result = new $CalledClass();
961 
962  # restore the $_SESSION value
963  if (isset($OldUserId))
964  {
965  $_SESSION["APUserId"] = $OldUserId;
966  }
967 
968  # return our anonymous user
969  return $Result;
970  }
971 
972  # ---- Miscellaneous Functions -------------------------------------------
973 
974  # get unique alphanumeric code for user
975  public function GetUniqueCode($SeedString, $CodeLength)
976  {
977  # return NULL if not associated with a particular user
978  if ($this->UserId === NULL) { return NULL; }
979 
980  return substr(strtoupper(md5(
981  $this->Get("UserName").$this->Get("UserPassword").$SeedString)),
982  0, $CodeLength);
983  }
984 
985 
986  # ---- PRIVATE INTERFACE -------------------------------------------------
987 
988  protected $DB; # handle to SQL database we use to store user information
989  protected $UserId = NULL; # user ID number for reference into database
990  protected $Result; # result of last operation
991  protected $LoggedIn; # flag indicating whether user is logged in
992  protected $Status;
993 
994  private $DBFields; # used for caching user values
995 
996  # optional mail function to use instead of mail()
997  private static $EmailFunc = NULL;
998 
999  # check whether a user name is valid
1000  # (alphanumeric string of 2-24 chars that starts with a letter)
1001  public static function IsValidUserName($UserName)
1002  {
1003  if (preg_match("/^[a-zA-Z][a-zA-Z0-9]{1,23}$/", $UserName))
1004  {
1005  return TRUE;
1006  }
1007  return FALSE;
1008  }
1009 
1010  # check whether a password is valid (at least 6 characters)
1011  public static function IsValidPassword(
1012  $Password, $UserName, $Email)
1013  {
1014  return count(self::CheckPasswordForErrors(
1015  $Password, $UserName, $Email)) == 0 ?
1016  TRUE : FALSE ;
1017  }
1018 
1028  public static function CheckPasswordForErrors(
1029  $Password, $UserName = NULL, $Email = NULL)
1030  {
1031  # start off assuming no errors
1032  $Errors = array();
1033 
1034  # normalize incoming password
1035  $Password = self::NormalizePassword($Password);
1036 
1037  # username provided and password contains username
1038  if ($UserName !== NULL &&
1039  stripos($Password, $UserName) !== FALSE)
1040  {
1041  $Errors[]= U_PASSWORDCONTAINSUSERNAME;
1042  }
1043 
1044  # email provided and password contains email
1045  if ($Email !== NULL &&
1046  stripos($Password, $Email) !== FALSE)
1047  {
1048  $Errors[]= U_PASSWORDCONTAINSEMAIL;
1049  }
1050 
1051  # length requirement
1052  if (strlen($Password) == 0)
1053  {
1054  $Errors[]= U_EMPTYPASSWORD;
1055  }
1056  elseif (strlen($Password) < self::$PasswordMinLength)
1057  {
1058  $Errors[]= U_PASSWORDTOOSHORT;
1059  }
1060 
1061  # unique characters requirement
1062  $UniqueChars = count(array_unique(
1063  preg_split('//u', $Password, NULL, PREG_SPLIT_NO_EMPTY)));
1064 
1065  if ($UniqueChars < self::$PasswordMinUniqueChars)
1066  {
1067  $Errors[]= U_PASSWORDTOOSIMPLE;
1068  }
1069 
1070  # for the following complexity checks, use unicode character properties
1071  # in PCRE as in: http://php.net/manual/en/regexp.reference.unicode.php
1072 
1073  # check for punctuation, uppercase letters, and numbers as per the system
1074  # configuration
1075  if (self::$PasswordRules & self::PW_REQUIRE_PUNCTUATION &&
1076  !preg_match('/\p{P}/u', $Password) )
1077  {
1078  $Errors[]= U_PASSWORDNEEDSPUNCTUATION;
1079  }
1080 
1081  if (self::$PasswordRules & self::PW_REQUIRE_MIXEDCASE &&
1082  (!preg_match('/\p{Lu}/u', $Password) ||
1083  !preg_match('/\p{Ll}/u', $Password) ) )
1084 
1085  {
1086  $Errors[]= U_PASSWORDNEEDSMIXEDCASE;
1087  }
1088 
1089  if (self::$PasswordRules & self::PW_REQUIRE_DIGITS &&
1090  !preg_match('/\p{N}/u', $Password))
1091  {
1092  $Errors[]= U_PASSWORDNEEDSDIGIT;
1093  }
1094 
1095  return $Errors;
1096  }
1097 
1098  # check whether an e-mail address looks valid
1099  public static function IsValidLookingEMailAddress($EMail)
1100  {
1101  if (preg_match("/^[a-zA-Z0-9._\-]+@[a-zA-Z0-9._\-]+\.[a-zA-Z]{2,3}$/",
1102  $EMail))
1103  {
1104  return TRUE;
1105  }
1106  else
1107  {
1108  return FALSE;
1109  }
1110  }
1111 
1112  # get normalized version of e-mail address
1113  public static function NormalizeEMailAddress($EMailAddress)
1114  {
1115  return strtolower(trim($EMailAddress));
1116  }
1117 
1118  # get normalized version of user name
1119  public static function NormalizeUserName($UserName)
1120  {
1121  return trim($UserName);
1122  }
1123 
1124  # get normalized version of password
1125  public static function NormalizePassword($Password)
1126  {
1127  return trim($Password);
1128  }
1129 
1130  # generate random password
1131  public function GetRandomPassword($PasswordMinLength = 6, $PasswordMaxLength = 8)
1132  {
1133  # seed random number generator
1134  mt_srand((double)microtime() * 1000000);
1135 
1136  # generate password of requested length
1137  return sprintf("%06d", mt_rand(pow(10, ($PasswordMinLength - 1)),
1138  (pow(10, $PasswordMaxLength) - 1)));
1139  }
1140 
1141  # convenience function to supply parameters to Database->UpdateValue()
1142  public function UpdateValue($FieldName, $NewValue = DB_NOVALUE)
1143  {
1144  return $this->DB->UpdateValue("APUsers", $FieldName, $NewValue,
1145  "UserId = '".$this->UserId."'", $this->DBFields);
1146  }
1147 
1148  # methods for backward compatibility with earlier versions of User
1149  public function GivePriv($Privilege)
1150  {
1151  $this->GrantPriv($Privilege);
1152  }
1153 
1159  public static function SetPasswordRules($NewValue)
1160  {
1161  self::$PasswordRules = $NewValue;
1162  }
1163 
1168  public static function SetPasswordMinLength($NewValue)
1169  {
1170  self::$PasswordMinLength = $NewValue;
1171  }
1172 
1177  public static function SetPasswordMinUniqueChars($NewValue)
1178  {
1179  self::$PasswordMinUniqueChars = $NewValue;
1180  }
1181 
1186  public static function GetPasswordRulesDescription()
1187  {
1188  return "Passwords are case-sensitive, cannot contain your username or email, "
1189  ."must be at least ".self::$PasswordMinLength
1190  ." characters long, "
1191  ." have at least ".self::$PasswordMinUniqueChars
1192  ." different characters"
1193  .(self::$PasswordRules & self::PW_REQUIRE_PUNCTUATION ?
1194  ", include punctuation":"")
1195  .(self::$PasswordRules & self::PW_REQUIRE_MIXEDCASE ?
1196  ", include capital and lowercase letters":"")
1197  .(self::$PasswordRules & self::PW_REQUIRE_DIGITS ?
1198  ", include a number":"").".";
1199  }
1200 
1204  private static function GetSaltForCrypt()
1205  {
1206  # generate a password salt by grabbing CRYPT_SALT_LENGTH
1207  # random bytes, then base64 encoding while filtering out
1208  # non-alphanumeric characters to get a string all the hashes
1209  # accept as a salt
1210  $Salt = preg_replace("/[^A-Za-z0-9]/","",
1211  base64_encode(openssl_random_pseudo_bytes(
1212  CRYPT_SALT_LENGTH) ));
1213 
1214  # select the best available hashing algorithm, provide a salt
1215  # in the correct format for that algorithm
1216  if (CRYPT_SHA512==1)
1217  {
1218  return '$6$'.substr($Salt, 0, 16);
1219  }
1220  elseif (CRYPT_SHA256==1)
1221  {
1222  return '$5$'.substr($Salt, 0, 16);
1223  }
1224  elseif (CRYPT_BLOWFISH==1)
1225  {
1226  return '$2y$'.substr($Salt, 0, 22);
1227  }
1228  elseif (CRYPT_MD5==1)
1229  {
1230  return '$1$'.substr($Salt, 0, 12);
1231  }
1232  elseif (CRYPT_EXT_DES==1)
1233  {
1234  return '_'.substr($Salt, 0, 8);
1235  }
1236  else
1237  {
1238  return substr($Salt, 0, 2);
1239  }
1240  }
1241 
1242  private static $PasswordMinLength = 6;
1243  private static $PasswordMinUniqueChars = 4;
1244 
1245  # default to no additional requirements beyond length
1246  private static $PasswordRules = 0;
1247 }
Get($FieldName)
Definition: User.php:276
$DB
Definition: User.php:988
GetRandomPassword($PasswordMinLength=6, $PasswordMaxLength=8)
Definition: User.php:1131
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:948
static NormalizeUserName($UserName)
Definition: User.php:1119
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:1099
static SetPasswordMinLength($NewValue)
Set password minimum length.
Definition: User.php:1168
GetMailChangeCode()
Definition: User.php:614
GetUniqueCode($SeedString, $CodeLength)
Definition: User.php:975
GivePriv($Privilege)
Definition: User.php:1149
__construct($UserInfoOne=NULL, $UserInfoTwo=NULL)
Definition: User.php:57
GrantPriv($Privilege)
Definition: User.php:864
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:1028
IsMailChangeCodeGood($Code)
Definition: User.php:622
const U_EMAILSDONTMATCH
Definition: User.php:23
GetResetCode()
Definition: User.php:599
UpdateValue($FieldName, $NewValue=DB_NOVALUE)
Definition: User.php:1142
static NormalizePassword($Password)
Definition: User.php:1125
const U_EMPTYPASSWORD
Definition: User.php:29
const U_PASSWORDNEEDSPUNCTUATION
Definition: User.php:44
GetActivationCode()
Definition: User.php:578
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:607
const PW_REQUIRE_MIXEDCASE
Definition: User.php:52
static IsValidUserName($UserName)
Definition: User.php:1001
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:786
GetPasswordSalt($UserName)
Definition: User.php:433
LastLocation($NewLocation=NULL)
Definition: User.php:246
static SetPasswordMinUniqueChars($NewValue)
Set password minimum unique characters.
Definition: User.php:1177
GetPrivList()
Definition: User.php:914
static SetPasswordRules($NewValue)
Set password requirements.
Definition: User.php:1159
IsActivated($NewValue=DB_NOVALUE)
Definition: User.php:593
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:730
const U_PASSWORDNEEDSMIXEDCASE
Definition: User.php:45
$LoggedIn
Definition: User.php:991
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:629
const U_PASSWORDCONTAINSUSERNAME
Definition: User.php:40
const U_PASSWORDTOOSIMPLE
Definition: User.php:43
$Status
Definition: User.php:992
Set($FieldName, $NewValue)
Definition: User.php:308
const DB_NOVALUE
Definition: Database.php:1784
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:1186
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:1011
static NormalizeEMailAddress($EMailAddress)
Definition: User.php:1113
GetDate($FieldName, $Format="")
Definition: User.php:285
const U_DUPLICATEEMAIL
Definition: User.php:38
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:990
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:989
SetPrivList($NewPrivileges)
Definition: User.php:925
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:586
const U_ILLEGALUSERNAME
Definition: User.php:25
RevokePriv($Privilege)
Definition: User.php:899
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:829
StatusMessage()
Definition: User.php:123
const U_PASSWORDSDONTMATCH
Definition: User.php:22