00001 <?PHP
00002
00003 #
00004 # Axis--UserFactory.php
00005 # An Meta-Object for Handling User Information
00006 #
00007 # Copyright 2003 Axis Data
00008 # This code is free software that can be used or redistributed under the
00009 # terms of Version 2 of the GNU General Public License, as published by the
00010 # Free Software Foundation (http://www.fsf.org).
00011 #
00012 # Author: Edward Almasy (almasy@axisdata.com)
00013 #
00014 # Part of the AxisPHP library v1.2.4
00015 # For more information see http://www.axisdata.com/AxisPHP/
00016 #
00017
00018 class UserFactory {
00019
00020 # ---- PUBLIC INTERFACE --------------------------------------------------
00021
00022 # object constructor
00023 function UserFactory($SessionOrDb)
00024 {
00025 # if a session was passed in
00026 if (is_object($SessionOrDb) && method_exists($SessionOrDb, "Session"))
00027 {
00028 # swipe database handle from session
00029 $this->DB = $SessionOrDb->DB;
00030
00031 # save session
00032 $this->Session = $SessionOrDb;
00033 }
00034 # else if database handle was passed in
00035 elseif (is_object($SessionOrDb) && method_exists($SessionOrDb, "Database"))
00036 {
00037 # save database handle
00038 $this->DB = $SessionOrDb;
00039
00040 # create session
00041 $this->Session = new Session($this->DB);
00042 }
00043 else
00044 {
00045 # error out
00046 $this->Result = U_ERROR;
00047 exit(1);
00048 }
00049 }
00050
00063 function CreateNewUser(
00064 $UserName, $Password, $PasswordAgain, $EMail, $EMailAgain,
00065 $IgnoreErrorCodes = NULL)
00066 {
00067 # check incoming values
00068 $ErrorCodes = $this->TestNewUserValues(
00069 $UserName, $Password, $PasswordAgain, $EMail, $EMailAgain);
00070
00071 # discard any errors we are supposed to ignore
00072 if ($IgnoreErrorCodes)
00073 {
00074 $ErrorCodes = array_diff($ErrorCodes, $IgnoreErrorCodes);
00075 }
00076
00077 # if error found in incoming values return error codes to caller
00078 if (count($ErrorCodes)) { return $ErrorCodes; }
00079
00080 # add user to database
00081 $UserName = User::NormalizeUserName($UserName);
00082 $this->DB->Query("INSERT INTO APUsers"
00083 ." (UserName, CreationDate)"
00084 ." VALUES ('".addslashes($UserName)."', NOW())");
00085
00086 # create new user object
00087 $UserId = $this->DB->LastInsertId("APUsers");
00088 $User = new User($this->DB, (int)$UserId);
00089
00090 # if new user object creation failed return error code to caller
00091 if ($User->Status() != U_OKAY) { return array($User->Status()); }
00092
00093 # set password and e-mail address
00094 $User->SetPassword($Password);
00095 $User->Set("EMail", $EMail);
00096
00097 # return new user object to caller
00098 return $User;
00099 }
00100
00101 # test new user creation values (returns array of error codes)
00102 function TestNewUserValues(
00103 $UserName, $Password, $PasswordAgain, $EMail, $EMailAgain)
00104 {
00105 $ErrorCodes = array();
00106 if (strlen(User::NormalizeUserName($UserName)) == 0)
00107 { $ErrorCodes[] = U_EMPTYUSERNAME; }
00108 elseif (!User::IsValidUserName($UserName))
00109 { $ErrorCodes[] = U_ILLEGALUSERNAME; }
00110 elseif ($this->UserNameExists($UserName))
00111 { $ErrorCodes[] = U_DUPLICATEUSERNAME; }
00112
00113 if ($this->EMailAddressIsInUse($EMail))
00114 { $ErrorCodes[] = U_DUPLICATEEMAIL; }
00115
00116 $FoundOtherPasswordError = FALSE;
00117 if (strlen(User::NormalizePassword($Password)) == 0)
00118 {
00119 $ErrorCodes[] = U_EMPTYPASSWORD;
00120 $FoundOtherPasswordError = TRUE;
00121 }
00122 elseif (!User::IsValidPassword($Password))
00123 {
00124 $ErrorCodes[] = U_ILLEGALPASSWORD;
00125 $FoundOtherPasswordError = TRUE;
00126 }
00127
00128 if (strlen(User::NormalizePassword($PasswordAgain)) == 0)
00129 {
00130 $ErrorCodes[] = U_EMPTYPASSWORDAGAIN;
00131 $FoundOtherPasswordError = TRUE;
00132 }
00133 elseif (!User::IsValidPassword($PasswordAgain))
00134 {
00135 $ErrorCodes[] = U_ILLEGALPASSWORDAGAIN;
00136 $FoundOtherPasswordError = TRUE;
00137 }
00138
00139 if ($FoundOtherPasswordError == FALSE)
00140 {
00141 if (User::NormalizePassword($Password)
00142 != User::NormalizePassword($PasswordAgain))
00143 {
00144 $ErrorCodes[] = U_PASSWORDSDONTMATCH;
00145 }
00146 }
00147
00148 $FoundOtherEMailError = FALSE;
00149 if (strlen(User::NormalizeEMailAddress($EMail)) == 0)
00150 {
00151 $ErrorCodes[] = U_EMPTYEMAIL;
00152 $FoundOtherEMailError = TRUE;
00153 }
00154 elseif (!User::IsValidLookingEMailAddress($EMail))
00155 {
00156 $ErrorCodes[] = U_ILLEGALEMAIL;
00157 $FoundOtherEMailError = TRUE;
00158 }
00159
00160 if (strlen(User::NormalizeEMailAddress($EMailAgain)) == 0)
00161 {
00162 $ErrorCodes[] = U_EMPTYEMAILAGAIN;
00163 $FoundOtherEMailError = TRUE;
00164 }
00165 elseif (!User::IsValidLookingEMailAddress($EMailAgain))
00166 {
00167 $ErrorCodes[] = U_ILLEGALEMAILAGAIN;
00168 $FoundOtherEMailError = TRUE;
00169 }
00170
00171 if ($FoundOtherEMailError == FALSE)
00172 {
00173 if (User::NormalizeEMailAddress($EMail)
00174 != User::NormalizeEMailAddress($EMailAgain))
00175 {
00176 $ErrorCodes[] = U_EMAILSDONTMATCH;
00177 }
00178 }
00179
00180 return $ErrorCodes;
00181 }
00182
00188 function GetUserCount($Condition = NULL)
00189 {
00190 return $this->DB->Query("SELECT COUNT(*) AS UserCount FROM APUsers"
00191 .($Condition ? " WHERE ".$Condition : ""), "UserCount");
00192 }
00193
00194 # return total number of user that matched last GetMatchingUsers call
00195 # before the return size was limited
00196 function GetMatchingUserCount()
00197 {
00198 return $this->MatchingUserCount;
00199 }
00200
00201 # return array of users currently logged in
00202 function GetLoggedInUsers()
00203 {
00204 # start with empty array (to prevent array errors)
00205 $ReturnValue = array();
00206
00207 # load array of logged in user
00208 $UserIds = $this->Session->GetFromAllSessions("APUserId");
00209
00210 # for each logged in user
00211 foreach ($UserIds as $UserId)
00212 {
00213 # load all data values for user
00214 $this->DB->Query("SELECT * FROM APUsers WHERE UserId = '".$UserId."'");
00215 $ReturnValue[$UserId] = $this->DB->FetchRow();
00216 }
00217
00218 # return array of user data to caller
00219 return $ReturnValue;
00220 }
00221
00222 # return array of users recently logged in. returns 10 users by default
00223 function GetRecentlyLoggedInUsers($Since = NULL, $Limit = 10)
00224 {
00225 # start with empty array (to prevent array errors)
00226 $ReturnValue = array();
00227
00228 # get users recently logged in during the last 24 hours if no date given
00229 if (is_null($Since))
00230 {
00231 $Date = date("Y-m-d H:i:s", time()-86400);
00232 }
00233
00234 else
00235 {
00236 $Date = date("Y-m-d H:i:s", strtotime($Since));
00237 }
00238
00239 # query for the users who were logged in since the given date
00240 $this->DB->Query("
00241 SELECT U.*
00242 FROM APUsers U
00243 LEFT JOIN
00244 -- the dummy table is an optimization. see the comment by Vimal
00245 -- Gupta in the MySQL docs:
00246 -- http://dev.mysql.com/doc/refman/5.0/en/in-subquery-optimization.html
00247 (SELECT DataValue
00248 FROM APSessionData
00249 WHERE DataName = 'APUserId'
00250 -- allows fetching distinct DataValue values but with GROUP BY
00251 -- optimizations
00252 GROUP BY DataValue) as Dummy
00253 -- using this convoluted method because DataValue is an integer
00254 -- (UserId) serialized by PHP to a string
00255 ON CONCAT('s:', CHAR_LENGTH(CAST(U.UserId AS CHAR)),
00256 ':\"', U.UserId,'\";') = Dummy.DataValue
00257 WHERE U.LastActiveDate >= '".$Date."'
00258 AND Dummy.DataValue IS NULL
00259 ORDER BY U.LastActiveDate DESC
00260 LIMIT ".intval($Limit));
00261
00262 while (FALSE !== ($Row = $this->DB->FetchRow()))
00263 {
00264 $ReturnValue[$Row["UserId"]] = $Row;
00265 }
00266
00267 # return array of user data to caller
00268 return $ReturnValue;
00269 }
00270
00271 # return array of user names who have the specified privileges
00272 # (array index is user IDs)
00273 function GetUsersWithPrivileges()
00274 {
00275 # start with query string that will return all users
00276 $QueryString = "SELECT DISTINCT APUsers.UserId, UserName FROM APUsers, APUserPrivileges";
00277
00278 # for each specified privilege
00279 $Args = func_get_args();
00280 foreach ($Args as $Index => $Arg)
00281 {
00282 # add condition to query string
00283 $QueryString .= ($Index == 0) ? " WHERE (" : " OR";
00284 $QueryString .= " APUserPrivileges.Privilege = ".$Arg;
00285 }
00286
00287 # close privilege condition in query string and add user ID condition
00288 $QueryString.= count($Args) ? ") AND APUsers.UserId = APUserPrivileges.UserId" : "";
00289
00290 # perform query
00291 $this->DB->Query($QueryString);
00292
00293 # copy query result into user info array
00294 $Users = $this->DB->FetchColumn("UserName", "UserId");
00295
00296 # return array of users to caller
00297 return $Users;
00298 }
00299
00300 # return array of user objects who have values matching search string
00301 # (array indexes are user IDs)
00302 function FindUsers($SearchString, $FieldName = "UserName",
00303 $SortFieldName = "UserName", $Offset = 0, $Count = 9999999)
00304 {
00305 # retrieve matching user IDs
00306 $UserNames = $this->FindUserNames(
00307 $SearchString, $FieldName, $SortFieldName, $Offset, $Count);
00308
00309 # create user objects
00310 $Users = array();
00311 foreach ($UserNames as $UserId => $UserName)
00312 {
00313 $Users[$UserId] = new User($this->DB, intval($UserId));
00314 }
00315
00316 # return array of user objects to caller
00317 return $Users;
00318 }
00319
00320 # return array of user names/IDs who have values matching search string
00321 # (array indexes are user IDs, array values are user names)
00322 function FindUserNames($SearchString, $FieldName = "UserName",
00323 $SortFieldName = "UserName", $Offset = 0, $Count = 9999999)
00324 {
00325 # Construct a database query:
00326 $QueryString = "SELECT UserId, UserName FROM APUsers WHERE";
00327
00328 # If the search string is a valid username which is shorter than the
00329 # minimum word length indexed by the FTS, just do a normal
00330 # equality test instead of using the index.
00331 # Otherwise, FTS away.
00332 $MinWordLen = $this->DB->Query(
00333 "SHOW VARIABLES WHERE variable_name='ft_min_word_len'", "Value");
00334 if (User::IsValidUserName($SearchString) &&
00335 strlen($SearchString) < $MinWordLen )
00336 {
00337 $QueryString .= " UserName='".addslashes($SearchString)."'";
00338 }
00339 else
00340 {
00341 # massage search string to use AND logic
00342 $Words = preg_split("/[\s]+/", trim($SearchString));
00343 $NewSearchString = "";
00344 $InQuotedString = FALSE;
00345 foreach ($Words as $Word)
00346 {
00347 if ($InQuotedString == FALSE) { $NewSearchString .= "+"; }
00348 if (preg_match("/^\"/", $Word)) { $InQuotedString = TRUE; }
00349 if (preg_match("/\"$/", $Word)) { $InQuotedString = FALSE; }
00350 $NewSearchString .= $Word." ";
00351 }
00352 $QueryString .= " MATCH (".$FieldName.")"
00353 ." AGAINST ('".addslashes(trim($NewSearchString))."'"
00354 ." IN BOOLEAN MODE)";
00355 }
00356 $QueryString .= " ORDER BY ".$SortFieldName
00357 ." LIMIT ".$Offset.", ".$Count;
00358
00359 # retrieve matching user IDs
00360 $this->DB->Query($QueryString);
00361 $UserNames = $this->DB->FetchColumn("UserName", "UserId");
00362
00363 # return names/IDs to caller
00364 return $UserNames;
00365 }
00366
00367 # return array of users who have values matching search string (in specific field if requested)
00368 # (search string respects POSIX-compatible regular expressions)
00369 # optimization: $SearchString = ".*." and $FieldName = NULL will return all
00370 # users ordered by $SortFieldName
00371 function GetMatchingUsers($SearchString, $FieldName = NULL,
00372 $SortFieldName = "UserName",
00373 $ResultsStartAt = 0, $ReturnNumber = NULL)
00374 {
00375 # start with empty array (to prevent array errors)
00376 $ReturnValue = array();
00377
00378 # if search string supplied
00379 if (strlen(trim($SearchString)) > 0)
00380 {
00381 # build the sorting clause
00382 $QueryOrderBy = " ORDER BY $SortFieldName";
00383
00384 if ($ReturnNumber)
00385 {
00386 $QueryLimit = " LIMIT ";
00387 if ($ResultsStartAt)
00388 {
00389 $QueryLimit .= "$ResultsStartAt, $ReturnNumber";
00390 }
00391 else
00392 $QueryLimit .= $ReturnNumber;
00393 }
00394 else
00395 {
00396 $QueryLimit = "";
00397 }
00398
00399 $Query = "SELECT * FROM APUsers";
00400
00401 # the Criteria Query will be used to get the total number
00402 # of results without the limit clause
00403 $CriteriaQuery = $Query;
00404
00405
00406 # if specific field comparison requested
00407 if ($FieldName != NULL)
00408 {
00409 # append queries with criteria
00410 $Query .= " WHERE ".$FieldName." REGEXP '".addslashes($SearchString)."'";
00411
00412 $CriteriaQuery = $Query;
00413
00414 # tack on ordering and limiting
00415 $Query .= $QueryOrderBy.$QueryLimit;
00416
00417 }
00418 # optimize for returning all users
00419 elseif (strcasecmp($SearchString, ".*.") == 0)
00420 {
00421 $Query .= $QueryOrderBy.$QueryLimit;
00422
00423 # set field name to username - this would be the first field
00424 # returned by a field to field search using the above RegExp
00425 $FieldName = "UserName";
00426 }
00427 else
00428 {
00429 # search all fields - can't limit here, but we can order by
00430 $Query .= $QueryOrderBy;
00431 }
00432
00433 # execute query
00434 $this->DB->Query($Query);
00435
00436 # process query return
00437 while ($Record = $this->DB->FetchRow())
00438 {
00439
00440 # if specific field or all users requested
00441 if ($FieldName != NULL)
00442 {
00443 # add user to return array
00444 $ReturnValue[$Record["UserId"]] = $Record;
00445
00446 # add matching search field to return array
00447 $ReturnValue[$Record["UserId"]]["APMatchingField"] = $FieldName;
00448 }
00449 else
00450 {
00451 # for each user data field
00452 foreach ($Record as $FName => $FValue)
00453 {
00454 # if search string appears in data field
00455 if (ereg($SearchString, $Record[$FName]))
00456 {
00457 # add user to return array
00458 $ReturnValue[$Record["UserId"]] = $Record;
00459
00460 # add matching search field to return array
00461 $ReturnValue[$Record["UserId"]]["APMatchingField"] = $FName;
00462
00463 # move on to next user
00464 continue;
00465 }
00466 }
00467
00468 # cut return array down to requested size
00469 if (isset($ReturnNumber))
00470 {
00471 $ReturnValue = array_slice($ReturnValue, $ResultsStartAt, $ReturnNumber, true);
00472 }
00473 }
00474 }
00475
00476 if (!isset($this->MatchingUserCount))
00477 {
00478 $this->DB->Query($CriteriaQuery);
00479 $this->MatchingUserCount = $this->DB->NumRowsSelected();
00480 }
00481
00482 }
00483
00484 # return array of matching users to caller
00485 return $ReturnValue;
00486 }
00487
00488 # check whether user name currently exists
00489 function UserNameExists($UserName)
00490 {
00491 # normalize user name
00492 $UserName = User::NormalizeUserName($UserName);
00493
00494 # check whether user name is already in use
00495 $NameCount = $this->DB->Query(
00496 "SELECT COUNT(*) AS NameCount FROM APUsers"
00497 ." WHERE UserName = '".addslashes($UserName)."'",
00498 "NameCount");
00499
00500 # report to caller whether name exists
00501 return ($NameCount > 0);
00502 }
00503
00504 # check whether e-mail address currently has account associated with it
00505 function EMailAddressIsInUse($Address)
00506 {
00507 # normalize address
00508 $UserName = User::NormalizeEMailAddress($Address);
00509
00510 # check whether address is already in use
00511 $AddressCount = $this->DB->Query(
00512 "SELECT COUNT(*) AS AddressCount FROM APUsers"
00513 ." WHERE EMail = '".addslashes($Address)."'",
00514 "AddressCount");
00515
00516 # report to caller whether address is in use
00517 return ($AddressCount > 0);
00518 }
00519
00525 public function GetNewestUsers($Limit = 5)
00526 {
00527 # assume no users will be found
00528 $Users = array();
00529
00530 # fetch the newest users
00531 $this->DB->Query("SELECT *"
00532 ." FROM APUsers"
00533 ." ORDER BY CreationDate DESC"
00534 ." LIMIT ".intval($Limit));
00535 $UserIds = $this->DB->FetchColumn("UserId");
00536
00537 # for each user id found
00538 foreach ($UserIds as $UserId)
00539 {
00540 $Users[$UserId] = new SPTUser($UserId);
00541 }
00542
00543 # return the newest users
00544 return $Users;
00545 }
00546
00547 # ---- PRIVATE INTERFACE -------------------------------------------------
00548
00549 var $DB;
00550 var $Session;
00551 var $SortFieldName;
00552 var $MatchingUserCount;
00553
00554 # callback function for sorting users
00555 function CompareUsersForSort($UserA, $UserB)
00556 {
00557 return strcasecmp($UserA[$this->SortFieldName], $UserB[$this->SortFieldName]);
00558 }
00559 };
00560
00561
00562 ?>