All Data Structures Functions Variables Pages
User.php
1 <?php
2  /*********************************************************************************
3  * Zurmo is a customer relationship management program developed by
4  * Zurmo, Inc. Copyright (C) 2017 Zurmo Inc.
5  *
6  * Zurmo is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU Affero General Public License version 3 as published by the
8  * Free Software Foundation with the addition of the following permission added
9  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
10  * IN WHICH THE COPYRIGHT IS OWNED BY ZURMO, ZURMO DISCLAIMS THE WARRANTY
11  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12  *
13  * Zurmo is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Affero General Public License along with
19  * this program; if not, see http://www.gnu.org/licenses or write to the Free
20  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21  * 02110-1301 USA.
22  *
23  * You can contact Zurmo, Inc. with a mailing address at 27 North Wacker Drive
24  * Suite 370 Chicago, IL 60606. or at email address contact@zurmo.com.
25  *
26  * The interactive user interfaces in original and modified versions
27  * of this program must display Appropriate Legal Notices, as required under
28  * Section 5 of the GNU Affero General Public License version 3.
29  *
30  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
31  * these Appropriate Legal Notices must retain the display of the Zurmo
32  * logo and Zurmo copyright notice. If the display of the logo is not reasonably
33  * feasible for technical reasons, the Appropriate Legal Notices must display the words
34  * "Copyright Zurmo Inc. 2017. All rights reserved".
35  ********************************************************************************/
36 
37  class User extends Permitable
38  {
39  const AVATAR_TYPE_DEFAULT = 1;
40  const AVATAR_TYPE_PRIMARY_EMAIL = 2;
41  const AVATAR_TYPE_CUSTOM_EMAIL = 3;
42 
43  private $avatarImageUrl;
44 
49  public static function getByUsername($username)
50  {
51  assert('is_string($username)');
52  assert('$username != ""');
53  $bean = ZurmoRedBean::findOne('_user', "username = :username ", array(':username' => $username));
54  assert('$bean === false || $bean instanceof RedBean_OODBBean');
55  if ($bean === false)
56  {
57  throw new NotFoundException();
58  }
59  RedBeansCache::cacheBean($bean, static::getTableName() . $bean->id);
60  return self::makeModel($bean);
61  }
62 
72  public static function authenticate($username, $password)
73  {
74  assert('is_string($username)');
75  assert('$username != ""');
76  assert('is_string($password)');
77  $user = static::getByUsername($username);
78  if (!static::compareWithCurrentPasswordHash($password, $user))
79  {
80  throw new BadPasswordException();
81  }
82  self::resolveAuthenticatedUserCanLogin($user);
83  $user->login();
84  return $user;
85  }
86 
93  protected static function compareWithCurrentPasswordHash($password, User $user)
94  {
95  $phpassHashObject = static::resolvePhpassHashObject();
96  $hashedPassword = static::hashPassword($password);
97  $databaseHash = $user->hash;
98  return $phpassHashObject->checkPassword($hashedPassword, $databaseHash);
99  }
100 
108  public static function resolveAuthenticatedUserCanLogin(User $user)
109  {
110  if (Right::ALLOW != $user->getEffectiveRight('UsersModule', UsersModule::RIGHT_LOGIN_VIA_WEB) &&
112  {
113  throw new NoRightWebLoginException();
114  }
115  if (Right::ALLOW != $user->getEffectiveRight('UsersModule', UsersModule::RIGHT_LOGIN_VIA_WEB_API) &&
117  {
118  throw new ApiNoRightWebApiLoginException();
119  }
120  if ($user->isSystemUser && !ApiRequest::isApiRequest())
121  {
122  throw new NoRightWebLoginException();
123  }
124  if ($user->isSystemUser && ApiRequest::isApiRequest())
125  {
126  throw new ApiNoRightWebApiLoginException();
127  }
128  return true;
129  }
130 
135  protected function constructDerived($bean, $setDefaults)
136  {
137  assert('$bean === null || $bean instanceof RedBean_OODBBean');
138  assert('is_bool($setDefaults)');
139  // Does a subset of what RedBeanModel::__construct does
140  // in order to mix in the Person - this is metadata wise,
141  // User doesn't get any functionality from Person.
142  $modelClassName = 'Person';
143  $tableName = $modelClassName::getTableName();
144  if ($bean === null)
145  {
146  $personBean = ZurmoRedBean::dispense($tableName);
147  }
148  else
149  {
150  $userBean = $this->getClassBean('User');
151  $personBean = ZurmoRedBeanLinkManager::getBean($userBean, $tableName);
152  assert('$personBean !== null');
153  }
154  //This is a hack to recover from a bug we cannot figure out how to solve.
155  //Rarely the person attributes are not part of the user, memcache needs to be restarted to solve this
156  //problem as you can't use the system once this occurs. this check below will clear the specific cache
157  //that causes this. Still need to figure out what is setting the cache wrong to begin with
158  if (!static::isAnAttribute('lastName'))
159  {
160  static::forgetBeanModel('User');
161  }
162  $this->setClassBean ($modelClassName, $personBean);
163  $this->mapAndCacheMetadataAndSetHints($modelClassName, $personBean);
164  parent::constructDerived($bean, $setDefaults);
165  }
166 
167  protected function unrestrictedDelete()
168  {
169  // Does a subset of what RedBeanModel::unrestrictedDelete
170  // does to the classes in the class hierarchy but to Person
171  // which is mixed in.
172  $modelClassName = 'Person';
173  $this->deleteOwnedRelatedModels ($modelClassName);
174  $this->deleteForeignRelatedModels($modelClassName);
175  return parent::unrestrictedDelete();
176  }
177 
178  public static function getMixedInModelClassNames()
179  {
180  return array('Person');
181  }
182 
183  protected function linkBeans()
184  {
185  // Link the beans up the inheritance hierarchy, skipping
186  // the person bean, then link that to the user. So the
187  // user is linked to both the person and the permitable,
188  // to complete the mixing in of the Person's data.
189  $baseBean = null;
190  foreach ($this->modelClassNameToBean as $modelClassName => $bean)
191  {
192  if ($modelClassName == 'Person')
193  {
194  continue;
195  }
196  if ($baseBean !== null)
197  {
198  ZurmoRedBeanLinkManager::link($bean, $baseBean);
199  }
200  $baseBean = $bean;
201  }
202  $userBean = $this->modelClassNameToBean['User'];
203  $personBean = $this->modelClassNameToBean['Person'];
204  ZurmoRedBeanLinkManager::link($userBean, $personBean);
205  }
206 
207  // Because no functionality is mixed in, because this is
208  // purely and RedBeanModel trick, and php knows nothing about
209  // it, a couple fof Person methods must be duplicated in User.
210  public function __toString()
211  {
212  $fullName = $this->getFullName();
213  if ($fullName == '')
214  {
215  return Zurmo::t('Core', '(Unnamed)');
216  }
217  return $fullName;
218  }
219 
220  public static function getModuleClassName()
221  {
222  return 'UsersModule';
223  }
224 
225  public function getFullName()
226  {
227  $fullName = array();
228  if ($this->firstName != '')
229  {
230  $fullName[] = $this->firstName;
231  }
232  if ($this->lastName != '')
233  {
234  $fullName[] = $this->lastName;
235  }
236  return join(' ' , $fullName);
237  }
238 
239  public function save($runValidation = true, array $attributeNames = null)
240  {
241  $passwordChanged = array_key_exists('hash', $this->originalAttributeValues);
242  unset($this->originalAttributeValues['hash']);
243  assert('!isset($this->originalAttributeValues["hash"])');
244  $saved = parent::save($runValidation, $attributeNames);
245 
246  if ($saved && $passwordChanged)
247  {
248  AuditEvent::
249  logAuditEvent('UsersModule', UsersModule::AUDIT_EVENT_USER_PASSWORD_CHANGED, $this->username, $this);
250  }
251  if ($saved)
252  {
253  $this->setIsActive();
254  }
255  return $saved;
256  }
257 
263  protected function afterSave()
264  {
265  if (((isset($this->originalAttributeValues['role'])) || $this->isNewModel) &&
266  $this->role != null && $this->role->id > 0)
267  {
270  $this->onChangeRights();
271  $this->onChangePolicies();
272  }
273  if ($this->isNewModel)
274  {
276  }
277  if (isset($this->originalAttributeValues['role']) && $this->originalAttributeValues['role'][1] > 0)
278  {
280  }
281  if (isset($this->originalAttributeValues['language']) && Yii::app()->user->userModel != null &&
282  Yii::app()->user->userModel == $this)
283  {
284  Yii::app()->languageHelper->setActive($this->language);
285  }
286  parent::afterSave();
287  }
288 
294  protected function beforeSave()
295  {
296  if (parent::beforeSave())
297  {
298  if (isset($this->originalAttributeValues['role']) && $this->originalAttributeValues['role'][1] > 0)
299  {
300  AllPermissionsOptimizationUtil::userBeingRemovedFromRole($this, Role::getById($this->originalAttributeValues['role'][1]));
301  $this->onChangeRights();
302  $this->onChangePolicies();
303  }
304  return true;
305  }
306  else
307  {
308  return false;
309  }
310  }
311 
312  protected function beforeDelete()
313  {
314  if (!parent::beforeDelete())
315  {
316  return false;
317  }
319  return true;
320  }
321 
322  protected function afterDelete()
323  {
324  parent::afterDelete();
325  ReadPermissionsSubscriptionUtil::deleteUserItemsFromAllReadSubscriptionTables($this->id);
326  }
327 
328  protected function logAuditEventsListForModified($newModel)
329  {
330  if ($newModel)
331  {
332  // When the first user is created there can be no
333  // current user. Log the first user as creating themselves.
334  if (Yii::app()->user->userModel == null || !Yii::app()->user->userModel->id > 0)
335  {
336  Yii::app()->user->userModel = $this;
337  }
338  }
339  else
340  {
342  }
343  }
344 
345  public static function getMetadata()
346  {
347  $className = get_called_class();
348  try
349  {
350  // not using default value to save cpu cycles on requests that follow the first exception.
351  return GeneralCache::getEntry($className . 'Metadata');
352  }
353  catch (NotFoundException $e)
354  {
355  $defaultMetadata = self::getDefaultMetadata();
356  $metadata = parent::getMetadata();
357  $modelClassName = 'Person';
358  try
359  {
360  $globalMetadata = GlobalMetadata::getByClassName($modelClassName);
361  $metadata[$modelClassName] = unserialize($globalMetadata->serializedMetadata);
362  }
363  catch (NotFoundException $e)
364  {
365  if (isset($defaultMetadata[$modelClassName]))
366  {
367  $metadata[$modelClassName] = $defaultMetadata[$modelClassName];
368  }
369  }
370  if (YII_DEBUG)
371  {
372  self::assertMetadataIsValid($metadata);
373  }
374  }
375  GeneralCache::cacheEntry($className . 'Metadata', $metadata);
376  return $metadata;
377  }
378 
379  public static function setMetadata(array $metadata)
380  {
381  if (YII_DEBUG)
382  {
383  self::assertMetadataIsValid($metadata);
384  }
385  // Save the mixed in Person metadata.
386  if (isset($metadata['Person']))
387  {
388  $modelClassName = 'Person';
389  try
390  {
391  $globalMetadata = GlobalMetadata::getByClassName($modelClassName);
392  }
393  catch (NotFoundException $e)
394  {
395  $globalMetadata = new GlobalMetadata();
396  $globalMetadata->className = $modelClassName;
397  }
398  $globalMetadata->serializedMetadata = serialize($metadata[$modelClassName]);
399  $saved = $globalMetadata->save();
400  assert('$saved');
401  }
402  if (isset($metadata['User']))
403  {
404  parent::setMetadata($metadata);
405  }
406  GeneralCache::forgetEntry(get_called_class() . 'Metadata');
407  }
408 
409  public function setPassword($password)
410  {
411  assert('is_string($password)');
412  $this->hash = self::encryptPassword($password);
413  }
414 
415  public static function encryptPassword($password)
416  {
417  $hashedPassword = static::hashPassword($password);
418  $phpassHashObject = static::resolvePhpassHashObject();
419  $passwordHash = $phpassHashObject->hashPassword($hashedPassword);
420  return $passwordHash;
421  }
422 
423  public static function hashPassword($password)
424  {
425  // we keep this for legacy purposes
426  return md5($password);
427  }
428 
429  public static function resolvePhpassHashObject()
430  {
431  // workaround to get namespaces working.
432  // we don't need any special autoloading care thanks to author embedding that logic in Loader.php
433  Yii::setPathOfAlias('Phpass', Yii::getPathOfAlias('application.extensions.phpass.src.Phpass'));
434  $phpassHash = new \Phpass\Hash;
435  return $phpassHash;
436  }
437 
438  public function serializeAndSetAvatarData(Array $avatar)
439  {
440  $this->serializedAvatarData = serialize($avatar);
441  }
442 
443  public function getAvatarImage($size = 250, $addScheme = false)
444  {
445  $avatarUrl = $this->getAvatarImageUrl($size, $addScheme);
446  return ZurmoHtml::image($avatarUrl, $this->getFullName(), array('class' => 'gravatar',
447  'width' => $size,
448  'height' => $size));
449  }
450 
451  public function getAvatarImageUrl($size, $addScheme = false)
452  {
453  assert('is_int($size)');
454  {
455  if (isset($this->serializedAvatarData))
456  {
457  $avatar = unserialize($this->serializedAvatarData);
458  }
459  // Begin Not Coding Standard
460  $baseGravatarUrl = '//www.gravatar.com/avatar/%s?s=' . $size . '&r=g';
461  $gravatarUrlFormat = $baseGravatarUrl . '&d=identicon';
462  $gravatarDefaultUrlFormat = $baseGravatarUrl . '&d=mm';
463  // End Not Coding Standard
464  if (isset($avatar['avatarType']) && $avatar['avatarType'] == static::AVATAR_TYPE_DEFAULT)
465  {
466  $avatarUrl = sprintf($gravatarDefaultUrlFormat, '');
467  }
468  elseif (isset($avatar['avatarType']) && $avatar['avatarType'] == static::AVATAR_TYPE_PRIMARY_EMAIL)
469  {
470  $email = $this->primaryEmail->emailAddress;
471  $emailHash = md5(strtolower(trim($email)));
472  $avatarUrl = sprintf($gravatarUrlFormat, $emailHash);
473  }
474  elseif (isset($avatar['avatarType']) && $avatar['avatarType'] == static::AVATAR_TYPE_CUSTOM_EMAIL)
475  {
476  $email = $avatar['customAvatarEmailAddress'];
477  $emailHash = md5(strtolower(trim($email)));
478  $avatarUrl = sprintf($gravatarUrlFormat, $emailHash);
479  }
480  else
481  {
482  $avatarUrl = sprintf($gravatarDefaultUrlFormat, '');
483  }
484  if (isset($this->avatarImageUrl))
485  {
486  $this->avatarImageUrl = $avatarUrl;
487  }
488  else
489  {
490  if (CurlUtil::urlExists('http:' . $avatarUrl))
491  {
492  $this->avatarImageUrl = $avatarUrl;
493  }
494  else
495  {
496  $this->avatarImageUrl = Yii::app()->theme->baseUrl . '/images/offline_user.png';
497  }
498  }
499  if ($addScheme)
500  {
501  return 'http:' . $this->avatarImageUrl;
502  }
503  return $this->avatarImageUrl;
504  }
505  }
506 
507  public static function mangleTableName()
508  {
509  return true;
510  }
511 
512  protected static function translatedAttributeLabels($language)
513  {
514  return array_merge(Person::getTranslatedAttributeLabels($language),
515  array_merge(parent::translatedAttributeLabels($language),
516  array(
517  'currency' => Zurmo::t('ZurmoModule', 'Currency', array(), null, $language),
518  'emailAccounts' => Zurmo::t('EmailMessagesModule', 'Email Accounts', array(), null, $language),
519  'emailBoxes' => Zurmo::t('EmailMessagesModule', 'Email Boxes', array(), null, $language),
520  'emailSignatures' => Zurmo::t('EmailMessagesModule', 'Email Signatures', array(), null, $language),
521  'fullName' => Zurmo::t('Core', 'Name', array(), null, $language),
522  'groups' => Zurmo::t('ZurmoModule', 'Groups', array(), null, $language),
523  'hash' => Zurmo::t('UsersModule', 'Hash', array(), null, $language),
524  'isActive' => Zurmo::t('UsersModule', 'Is Active', array(), null, $language),
525  'isRootUser' => Zurmo::t('UsersModule', 'Is Root User', array(), null, $language),
526  'hideFromSelecting' => Zurmo::t('UsersModule', 'Hide from selecting', array(), null, $language),
527  'hideFromLeaderboard' => Zurmo::t('UsersModule', 'Hide from leaderboard', array(), null, $language),
528  'isSystemUser' => Zurmo::t('UsersModule', 'Is System User', array(), null, $language),
529  'language' => Zurmo::t('Core', 'Language', array(), null, $language),
530  'locale' => Zurmo::t('UsersModule', 'Locale', array(), null, $language),
531  'manager' => Zurmo::t('UsersModule', 'Manager', array(), null, $language),
532  'primaryEmail' => Zurmo::t('EmailMessagesModule', 'Email', array(), null, $language),
533  'primaryAddress' => Zurmo::t('ZurmoModule', 'Address', array(), null, $language),
534  'role' => Zurmo::t('ZurmoModule', 'Role', array(), null, $language),
535  'timeZone' => Zurmo::t('ZurmoModule', 'Time Zone', array(), null, $language),
536  'title' => Zurmo::t('ZurmoModule', 'Salutation', array(), null, $language),
537  'username' => Zurmo::t('ZurmoModule', 'Username', array(), null, $language),
538  'lastLoginDateTime' => Zurmo::t('UsersModule', 'Last Login', array(), null, $language),
539  'secondaryEmail' => Zurmo::t('ZurmoModule', 'Secondary Email', array(), null, $language),
540  )
541  ));
542  }
543 
544  public function getActualRight($moduleName, $rightName)
545  {
546  assert('is_string($moduleName)');
547  assert('is_string($rightName)');
548  assert('$moduleName != ""');
549  assert('$rightName != ""');
550  $identifier = $this->id . $moduleName . $rightName . 'ActualRight';
551  if (!SECURITY_OPTIMIZED)
552  {
553  // The slow way will remain here as documentation
554  // for what the optimized way is doing.
555  try
556  {
557  // not using default value to save cpu cycles on requests that follow the first exception.
558  return RightsCache::getEntry($identifier);
559  }
560  catch (NotFoundException $e)
561  {
562  if (Group::getByName(Group::SUPER_ADMINISTRATORS_GROUP_NAME)->contains($this))
563  {
564  $actualRight = Right::ALLOW;
565  }
566  else
567  {
568  $actualRight = parent::getActualRight($moduleName, $rightName);
569  }
570  RightsCache::cacheEntry($identifier, $actualRight);
571  }
572  }
573  else
574  {
575  try
576  {
577  // not using default value to save cpu cycles on requests that follow the first exception.
578  return RightsCache::getEntry($identifier);
579  }
580  catch (NotFoundException $e)
581  {
582  // Optimizations work on the database,
583  // anything not saved will not work.
584  assert('$this->id > 0');
585  $actualRight = intval(ZurmoDatabaseCompatibilityUtil::
586  callFunction("get_user_actual_right({$this->id}, '$moduleName', '$rightName')"));
587  RightsCache::cacheEntry($identifier, $actualRight);
588  }
589  }
590  return $actualRight;
591  }
592 
593  public function getPropagatedActualAllowRight($moduleName, $rightName)
594  {
595  if (!SECURITY_OPTIMIZED)
596  {
597  return $this->recursiveGetPropagatedActualAllowRight($this->role, $moduleName, $rightName);
598  }
599  else
600  {
601  // Optimizations work on the database,
602  // anything not saved will not work.
603  assert('$this->id > 0');
604  return intval(ZurmoDatabaseCompatibilityUtil::
605  callFunction("get_user_propagated_actual_allow_right({$this->id}, '$moduleName', '$rightName')"));
606  }
607  }
608 
609  protected function recursiveGetPropagatedActualAllowRight(Role $role, $moduleName, $rightName)
610  {
611  if (!SECURITY_OPTIMIZED)
612  {
613  // The slow way will remain here as documentation
614  // for what the optimized way is doing.
615  foreach ($role->roles as $subRole)
616  {
617  foreach ($subRole->users as $userInSubRole)
618  {
619  if ($userInSubRole->getActualRight($moduleName, $rightName) == Right::ALLOW)
620  {
621  return Right::ALLOW;
622  }
623  }
624  if ($this->recursiveGetPropagatedActualAllowRight($subRole, $moduleName, $rightName) == Right::ALLOW)
625  {
626  return Right::ALLOW;
627  }
628  }
629  return Right::NONE;
630  }
631  else
632  {
633  // It should never get here because the optimized version
634  // of getPropagatedActualAllowRight will call
635  // get_user_propagated_actual_allow_right.
636  throw new NotSupportedException();
637  }
638  }
639 
645  public function getInheritedActualRight($moduleName, $rightName)
646  {
647  assert('is_string($moduleName)');
648  assert('is_string($rightName)');
649  assert('$moduleName != ""');
650  assert('$rightName != ""');
651  if (!SECURITY_OPTIMIZED)
652  {
653  return parent::getInheritedActualRight($moduleName, $rightName);
654  }
655  else
656  {
657  // Optimizations work on the database,
658  // anything not saved will not work.
659  assert('$this->id > 0');
660  return intval(ZurmoDatabaseCompatibilityUtil::
661  callFunction("get_user_inherited_actual_right({$this->id}, '$moduleName', '$rightName')"));
662  }
663  }
664 
671  protected function getInheritedActualRightIgnoringEveryone($moduleName, $rightName)
672  {
673  assert('is_string($moduleName)');
674  assert('is_string($rightName)');
675  assert('$moduleName != ""');
676  assert('$rightName != ""');
677  if (!SECURITY_OPTIMIZED)
678  {
679  // The slow way will remain here as documentation
680  // for what the optimized way is doing.
681  $combinedRight = Right::NONE;
682  foreach ($this->groups as $group)
683  {
684  $combinedRight |= $group->getExplicitActualRight ($moduleName, $rightName) |
685  $group->getInheritedActualRightIgnoringEveryone($moduleName, $rightName);
686  }
687  if (($combinedRight & Right::DENY) == Right::DENY)
688  {
689  return Right::DENY;
690  }
691  assert('in_array($combinedRight, array(Right::NONE, Right::ALLOW))');
692  return $combinedRight;
693  }
694  else
695  {
696  // It should never get here because the optimized version
697  // of getInheritedActualRight will call
698  // get_user_inherited_actual_right_ignoring_everyone.
699  throw new NotSupportedException();
700  }
701  }
702 
708  protected function getInheritedActualPolicyIgnoringEveryone($moduleName, $policyName)
709  {
710  assert('is_string($moduleName)');
711  assert('is_string($policyName)');
712  assert('$moduleName != ""');
713  assert('$policyName != ""');
714  $values = array();
715  foreach ($this->groups as $group)
716  {
717  $value = $group->getExplicitActualPolicy($moduleName, $policyName);
718  if ($value !== null)
719  {
720  $values[] = $value;
721  }
722  else
723  {
724  $value = $group->getInheritedActualPolicyIgnoringEveryone($moduleName, $policyName);
725  if ($value !== null)
726  {
727  $values[] = $value;
728  }
729  }
730  }
731  if (count($values) > 0)
732  {
733  return $moduleName::getStrongerPolicy($policyName, $values);
734  }
735  return null;
736  }
737 
738  public static function canSaveMetadata()
739  {
740  return true;
741  }
742 
743  public static function getDefaultMetadata()
744  {
745  // User is going to have a Person bean.
746  // As far as Php is concerned User is not a
747  // Person - because it isn't inheriting it,
748  // but the RedBeanModel essentially uses the
749  // Php inheritance to accumulate the data
750  // it needs in the getDefaultMetadata() methods
751  // to connect everything up in the database
752  // in the same order as the inheritance.
753  // By getting the person metadata from Person
754  // and mixing it into the metadata for User
755  // and the construction of User overriding
756  // to create and connect the Person bean,
757  // the User effectively is a Person from
758  // a data point of view.
759  $personMetadata = Person::getDefaultMetadata();
760  $metadata = parent::getDefaultMetadata();
761  $metadata['Person'] = $personMetadata['Person'];
762  $metadata[__CLASS__] = array(
763  'members' => array(
764  'hash',
765  'language',
766  'locale',
767  'timeZone',
768  'username',
769  'serializedAvatarData',
770  'isActive',
771  'lastLoginDateTime',
772  'isRootUser',
773  'hideFromSelecting',
774  'isSystemUser',
775  'hideFromLeaderboard'
776  ),
777  'relations' => array(
778  'currency' => array(static::HAS_ONE, 'Currency'),
779  'groups' => array(static::MANY_MANY, 'Group'),
780  'manager' => array(static::HAS_ONE, 'User',
781  static::NOT_OWNED, static::LINK_TYPE_SPECIFIC, 'manager'),
782  'role' => array(static::HAS_MANY_BELONGS_TO, 'Role'),
783  'emailBoxes' => array(static::HAS_MANY, 'EmailBox'),
784  'emailAccounts' => array(static::HAS_MANY, 'EmailAccount'),
785  'emailSignatures' => array(static::HAS_MANY, 'EmailSignature',
786  static::OWNED),
787  'secondaryEmail' => array(static::HAS_ONE, 'Email', static::OWNED,
788  static::LINK_TYPE_SPECIFIC, 'secondaryEmail'),
789  ),
790  'foreignRelations' => array(
791  'Dashboard',
792  'Portlet',
793  ),
794  'rules' => array(
795  array('hash', 'type', 'type' => 'string'),
796  array('hash', 'length', 'min' => 60, 'max' => 60),
797  array('language', 'type', 'type' => 'string'),
798  array('language', 'length', 'max' => 10),
799  array('locale', 'type', 'type' => 'string'),
800  array('locale', 'length', 'max' => 10),
801  array('timeZone', 'type', 'type' => 'string'),
802  array('timeZone', 'length', 'max' => 64),
803  array('timeZone', 'UserDefaultTimeZoneDefaultValueValidator'),
804  array('timeZone', 'ValidateTimeZone'),
805  array('username', 'required'),
806  array('username', 'unique'),
807  array('username', 'UsernameLengthValidator', 'on' => 'createUser, editUser'),
808  array('username', 'type', 'type' => 'string'),
809  array('username', 'match', 'pattern' => '/^[^A-Z]+$/', // Not Coding Standard
810  'message' => 'Username must be lowercase.'),
811  array('username', 'length', 'max' => 64),
812  array('username', 'filter', 'filter' => 'trim'),
813  array('serializedAvatarData', 'type', 'type' => 'string'),
814  array('isActive', 'readOnly'),
815  array('isActive', 'boolean'),
816  array('isRootUser', 'readOnly'),
817  array('isRootUser', 'boolean'),
818  array('hideFromSelecting', 'boolean'),
819  array('isSystemUser', 'readOnly'),
820  array('isSystemUser', 'boolean'),
821  array('hideFromLeaderboard', 'boolean'),
822  array('lastLoginDateTime', 'type', 'type' => 'datetime'),
823  ),
824  'elements' => array(
825  'currency' => 'CurrencyDropDown',
826  'language' => 'LanguageStaticDropDown',
827  'locale' => 'LocaleStaticDropDown',
828  'role' => 'Role',
829  'timeZone' => 'TimeZoneStaticDropDown',
830  'secondaryEmail' => 'EmailAddressInformation',
831  ),
832  'defaultSortAttribute' => 'lastName',
833  'noExport' => array(
834  'hash'
835  ),
836  'noApiExport' => array(
837  'hash'
838  ),
839  'noAudit' => array(
840  'serializedAvatarData',
841  ),
842  'indexes' => array(
843  'permitable_id' => array(
844  'members' => array('permitable_id'),
845  'unique' => false),
846  ),
847  );
848  return $metadata;
849  }
850 
855  public function beforeValidate()
856  {
857  if (!parent::beforeValidate())
858  {
859  return false;
860  }
861 
862  $hasErrors = false;
863  if (isset($this->primaryEmail) &&
864  !empty($this->primaryEmail->emailAddress) &&
865  !$this->isUserEmailUnique($this->primaryEmail->emailAddress))
866  {
867  $this->primaryEmail->addError('emailAddress', Zurmo::t('UsersModule', 'Email address already exists in system.'));
868  $hasErrors = true;
869  }
870  if (isset($this->primaryEmail) && !empty($this->primaryEmail->emailAddress) &&
871  isset($this->secondaryEmail) && !empty($this->secondaryEmail->emailAddress) &&
872  $this->primaryEmail->emailAddress == $this->secondaryEmail->emailAddress)
873  {
874  $this->secondaryEmail->addError('emailAddress',
875  Zurmo::t('UsersModule', 'Secondary email address cannot be the same as the primary email address.'));
876  $hasErrors = true;
877  }
878  elseif (isset($this->secondaryEmail) &&
879  !empty($this->secondaryEmail->emailAddress) &&
880  !$this->isUserEmailUnique($this->secondaryEmail->emailAddress))
881  {
882  $this->secondaryEmail->addError('emailAddress', Zurmo::t('UsersModule', 'Email address already exists in system.'));
883  $hasErrors = true;
884  }
885  if ($hasErrors)
886  {
887  return false;
888  }
889  return true;
890  }
891 
897  public function isUserEmailUnique($email)
898  {
899  if (!$email)
900  {
901  return true;
902  }
903 
904  $searchAttributeData['clauses'] = array(
905  1 => array(
906  'attributeName' => 'primaryEmail',
907  'relatedAttributeName' => 'emailAddress',
908  'operatorType' => 'equals',
909  'value' => $email,
910  ),
911  2 => array(
912  'attributeName' => 'secondaryEmail',
913  'relatedAttributeName' => 'emailAddress',
914  'operatorType' => 'equals',
915  'value' => $email,
916  ),
917  );
918 
919  if ($this->id > 0)
920  {
921  $searchAttributeData['clauses'][3] = array(
922  'attributeName' => 'id',
923  'operatorType' => 'doesNotEqual',
924  'value' => $this->id,
925  );
926  $searchAttributeData['structure'] = '(1 OR 2) AND 3';
927  }
928  else
929  {
930  $searchAttributeData['structure'] = '1 OR 2';
931  }
932 
933  $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('User');
934  $where = RedBeanModelDataProvider::makeWhere('User', $searchAttributeData, $joinTablesAdapter);
935  $models = static::getSubset($joinTablesAdapter, null, null, $where, null);
936 
937  if (count($models) > 0 && is_array($models))
938  {
939  return false;
940  }
941  return true;
942  }
943 
944  public static function getActiveUserCount($includeRootUser = false)
945  {
946  $searchAttributeData = self::makeActiveUsersQuerySearchAttributeData($includeRootUser);
947  $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('User');
948  $where = RedBeanModelDataProvider::makeWhere('User', $searchAttributeData, $joinTablesAdapter);
949  return static::getCount($joinTablesAdapter, $where, null);
950  }
951 
952  public static function getByCriteria($active = true, $groupId = null)
953  {
954  $searchAttributeData['clauses'] = array(
955  1 => array(
956  'attributeName' => 'isActive',
957  'operatorType' => 'equals',
958  'value' => (bool)$active,
959  ),
960  );
961  $searchAttributeData['structure'] = '1';
962 
963  if (isset($groupId))
964  {
965  $searchAttributeData['clauses'][2] = array(
966  'attributeName' => 'groups',
967  'relatedAttributeName' => 'id',
968  'operatorType' => 'equals',
969  'value' => $groupId,
970  );
971  $searchAttributeData['structure'] .= ' and 2';
972  }
973  $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('User');
974  $where = RedBeanModelDataProvider::makeWhere('User', $searchAttributeData, $joinTablesAdapter);
975  return static::getSubset($joinTablesAdapter, null, null, $where);
976  }
977 
978  public static function getRootUserCount()
979  {
980  $searchAttributeData['clauses'] = array(
981  1 => array(
982  'attributeName' => 'isRootUser',
983  'operatorType' => 'equals',
984  'value' => true,
985  ),
986  );
987  $searchAttributeData['structure'] = '1';
988  $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('User');
989  $where = RedBeanModelDataProvider::makeWhere('User', $searchAttributeData, $joinTablesAdapter);
990  return static::getCount($joinTablesAdapter, $where, null);
991  }
992 
993  public static function isTypeDeletable()
994  {
995  return true;
996  }
997 
1003  public function getEmailSignature()
1004  {
1005  if ($this->emailSignatures->count() == 0)
1006  {
1007  $emailSignature = new EmailSignature();
1008  $emailSignature->user = $this;
1009  $this->emailSignatures->add($emailSignature);
1010  $this->save();
1011  }
1012  else
1013  {
1014  $emailSignature = $this->emailSignatures[0];
1015  }
1016  return $emailSignature;
1017  }
1018 
1019  public function isDeletable()
1020  {
1021  $superAdminGroup = Group::getByName(Group::SUPER_ADMINISTRATORS_GROUP_NAME);
1022  if ($superAdminGroup->users->count() == 1 && $superAdminGroup->contains($this))
1023  {
1024  return false;
1025  }
1026  return parent::isDeletable();
1027  }
1028 
1033  public function setIsRootUser()
1034  {
1035  if (static::getRootUserCount() > 0)
1036  {
1037  throw new ExistingRootUserException();
1038  }
1039  $this->unrestrictedSet('isRootUser', true);
1040  }
1041 
1042  public function setIsSystemUser()
1043  {
1044  $this->unrestrictedSet('isSystemUser', true);
1045  }
1046 
1050  protected function setIsActive()
1051  {
1052  if ( Right::DENY == $this->getExplicitActualRight ('UsersModule', UsersModule::RIGHT_LOGIN_VIA_WEB) ||
1053  Right::DENY == $this->getExplicitActualRight ('UsersModule', UsersModule::RIGHT_LOGIN_VIA_MOBILE) ||
1054  Right::DENY == $this->getExplicitActualRight ('UsersModule', UsersModule::RIGHT_LOGIN_VIA_WEB_API))
1055  {
1056  $isActive = false;
1057  }
1058  else
1059  {
1060  $isActive = true;
1061  }
1062  if ($this->isActive != $isActive)
1063  {
1064  $data = array(strval($this), array('isActive'),
1065  BooleanUtil::boolToString((boolean) $this->isActive),
1066  BooleanUtil::boolToString((boolean) $isActive));
1067  AuditEvent::logAuditEvent('ZurmoModule', ZurmoModule::AUDIT_EVENT_ITEM_MODIFIED,
1068  $data, $this);
1069  $this->unrestrictedSet('isActive', $isActive);
1070  $this->save();
1071  }
1072  }
1073 
1077  public static function getSortAttributesByAttribute($attribute)
1078  {
1079  if ($attribute == 'firstName')
1080  {
1081  return array('firstName', 'lastName');
1082  }
1083  return parent::getSortAttributesByAttribute($attribute);
1084  }
1085 
1086  protected function login()
1087  {
1088  if (!ApiRequest::isApiRequest())
1089  {
1090  $this->unrestrictedSet('lastLoginDateTime', DateTimeUtil::convertTimestampToDbFormatDateTime(time()));
1091  $this->save();
1092  }
1093  }
1094 
1098  public function isAllowedToSetReadOnlyAttribute($attributeName)
1099  {
1100  if ($this->getScenario() == 'importModel' || $this->getScenario() == 'searchModel')
1101  {
1102  if ( in_array($attributeName, array('isActive',
1103  'isRootUser',
1104  'isSystemUser')))
1105  {
1106  return true;
1107  }
1108  else
1109  {
1110  return parent::isAllowedToSetReadOnlyAttribute($attributeName);
1111  }
1112  }
1113  }
1114 
1115  public function setIsNotRootUser()
1116  {
1117  $this->unrestrictedSet('isRootUser', false);
1118  }
1119 
1120  public function setIsNotSystemUser()
1121  {
1122  $this->unrestrictedSet('isSystemUser', false);
1123  }
1124 
1129  public function isSuperAdministrator()
1130  {
1131  if ($this->id < 0)
1132  {
1133  throw new NotSupportedException();
1134  }
1135  return Group::isUserASuperAdministrator($this);
1136  }
1137 
1143  public static function makeActiveUsersQuerySearchAttributeData($includeRootUser = false)
1144  {
1145  assert('is_bool($includeRootUser)');
1146  $searchAttributeData['clauses'] = array(
1147  1 => array(
1148  'attributeName' => 'isActive',
1149  'operatorType' => 'equals',
1150  'value' => true,
1151  ),
1152  2 => array(
1153  'attributeName' => 'isSystemUser',
1154  'operatorType' => 'equals',
1155  'value' => 0,
1156  ),
1157  3 => array(
1158  'attributeName' => 'isSystemUser',
1159  'operatorType' => 'isNull',
1160  'value' => null,
1161  ),
1162  );
1163  $searchAttributeData['structure'] = '1 and (2 or 3)';
1164  if ($includeRootUser == false)
1165  {
1166  $searchAttributeData['clauses'][4] = array(
1167  'attributeName' => 'isRootUser',
1168  'operatorType' => 'equals',
1169  'value' => 0,
1170  );
1171  $searchAttributeData['clauses'][5] = array(
1172  'attributeName' => 'isRootUser',
1173  'operatorType' => 'isNull',
1174  'value' => null,
1175  );
1176  $searchAttributeData['structure'] = '1 and (2 or 3) and (4 or 5)';
1177  }
1178  return $searchAttributeData;
1179  }
1180 
1185  public static function getActiveUsers($includeRootUser = false)
1186  {
1187  $searchAttributeData = self::makeActiveUsersQuerySearchAttributeData($includeRootUser);
1188  $joinTablesAdapter = new RedBeanModelJoinTablesQueryAdapter('User');
1189  $where = RedBeanModelDataProvider::makeWhere('User', $searchAttributeData, $joinTablesAdapter);
1190  return User::getSubset($joinTablesAdapter, null, null, $where);
1191  }
1192  }
setIsRootUser()
Definition: User.php:1033
static getTranslatedAttributeLabels($language)
Definition: Person.php:85
getExplicitActualRight($moduleName, $rightName)
Definition: Permitable.php:112
Definition: Role.php:37
static getByUsername($username)
Definition: User.php:49
Definition: User.php:37
static compareWithCurrentPasswordHash($password, User $user)
Definition: User.php:93
isUserEmailUnique($email)
Definition: User.php:897
static resolveAuthenticatedUserCanLogin(User $user)
Definition: User.php:108
getEmailSignature()
Definition: User.php:1003
static getByClassName($className)
static makeWhere($modelClassName, array $metadata, &$joinTablesAdapter)
mapAndCacheMetadataAndSetHints($modelClassName, RedBean_OODBBean $bean)
isSuperAdministrator()
Definition: User.php:1129
beforeValidate()
Definition: User.php:855
getInheritedActualPolicyIgnoringEveryone($moduleName, $policyName)
Definition: User.php:708
getInheritedActualRight($moduleName, $rightName)
Definition: User.php:645
setIsActive()
Definition: User.php:1050
static getSubset(RedBeanModelJoinTablesQueryAdapter $joinTablesAdapter=null, $offset=null, $count=null, $where=null, $orderBy=null, $modelClassName=null, $selectDistinct=false)
getEffectiveRight($moduleName, $rightName)
Definition: Permitable.php:63
static isApiRequest($moduleName= 'api')
Definition: ApiRequest.php:159
getInheritedActualRightIgnoringEveryone($moduleName, $rightName)
Definition: User.php:671
setClassBean($modelClassName, RedBean_OODBBean $bean)
static logAuditEventsListForChangedAttributeValues(Item $item, array $attributeNames=array(), RedBeanModel $ownedModel=null)
Definition: AuditUtil.php:102
static getByName($name)
Definition: Group.php:57
static getSortAttributesByAttribute($attribute)
Definition: User.php:1077
beforeSave()
Definition: User.php:294
unrestrictedSet($attributeName, $value)
static makeActiveUsersQuerySearchAttributeData($includeRootUser=false)
Definition: User.php:1143
constructDerived($bean, $setDefaults)
Definition: User.php:135
static getById($id, $modelClassName=null)
static getActiveUsers($includeRootUser=false)
Definition: User.php:1185
static boolToString($var)
Definition: BooleanUtil.php:64
static cacheBean(RedBean_OODBBean $bean, $beanIdentifier)
afterSave()
Definition: User.php:263
static userBeingRemovedFromRole(User $user, Role $role)
static authenticate($username, $password)
Definition: User.php:72
isAllowedToSetReadOnlyAttribute($attributeName)
Definition: User.php:1098
Generated on Sat Mar 28 2020 07:10:44