1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage User
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 use Joomla\Registry\Registry;
13 use Joomla\Utilities\ArrayHelper;
14
15 /**
16 * User class. Handles all application interaction with a user
17 *
18 * @since 11.1
19 */
20 class JUser extends JObject
21 {
22 /**
23 * A cached switch for if this user has root access rights.
24 *
25 * @var boolean
26 * @since 11.1
27 */
28 protected $isRoot = null;
29
30 /**
31 * Unique id
32 *
33 * @var integer
34 * @since 11.1
35 */
36 public $id = null;
37
38 /**
39 * The user's real name (or nickname)
40 *
41 * @var string
42 * @since 11.1
43 */
44 public $name = null;
45
46 /**
47 * The login name
48 *
49 * @var string
50 * @since 11.1
51 */
52 public $username = null;
53
54 /**
55 * The email
56 *
57 * @var string
58 * @since 11.1
59 */
60 public $email = null;
61
62 /**
63 * MD5 encrypted password
64 *
65 * @var string
66 * @since 11.1
67 */
68 public $password = null;
69
70 /**
71 * Clear password, only available when a new password is set for a user
72 *
73 * @var string
74 * @since 11.1
75 */
76 public $password_clear = '';
77
78 /**
79 * Block status
80 *
81 * @var integer
82 * @since 11.1
83 */
84 public $block = null;
85
86 /**
87 * Should this user receive system email
88 *
89 * @var integer
90 * @since 11.1
91 */
92 public $sendEmail = null;
93
94 /**
95 * Date the user was registered
96 *
97 * @var datetime
98 * @since 11.1
99 */
100 public $registerDate = null;
101
102 /**
103 * Date of last visit
104 *
105 * @var datetime
106 * @since 11.1
107 */
108 public $lastvisitDate = null;
109
110 /**
111 * Activation hash
112 *
113 * @var string
114 * @since 11.1
115 */
116 public $activation = null;
117
118 /**
119 * User parameters
120 *
121 * @var Registry
122 * @since 11.1
123 */
124 public $params = null;
125
126 /**
127 * Associative array of user names => group ids
128 *
129 * @var array
130 * @since 11.1
131 */
132 public $groups = array();
133
134 /**
135 * Guest status
136 *
137 * @var boolean
138 * @since 11.1
139 */
140 public $guest = null;
141
142 /**
143 * Last Reset Time
144 *
145 * @var string
146 * @since 12.2
147 */
148 public $lastResetTime = null;
149
150 /**
151 * Count since last Reset Time
152 *
153 * @var int
154 * @since 12.2
155 */
156 public $resetCount = null;
157
158 /**
159 * Flag to require the user's password be reset
160 *
161 * @var int
162 * @since 3.2
163 */
164 public $requireReset = null;
165
166 /**
167 * User parameters
168 *
169 * @var Registry
170 * @since 11.1
171 */
172 protected $_params = null;
173
174 /**
175 * Authorised access groups
176 *
177 * @var array
178 * @since 11.1
179 */
180 protected $_authGroups = null;
181
182 /**
183 * Authorised access levels
184 *
185 * @var array
186 * @since 11.1
187 */
188 protected $_authLevels = null;
189
190 /**
191 * Authorised access actions
192 *
193 * @var array
194 * @since 11.1
195 */
196 protected $_authActions = null;
197
198 /**
199 * Error message
200 *
201 * @var string
202 * @since 11.1
203 */
204 protected $_errorMsg = null;
205
206 /**
207 * JUserWrapperHelper object
208 *
209 * @var JUserWrapperHelper
210 * @since 3.4
211 */
212 protected $userHelper = null;
213
214 /**
215 * @var array JUser instances container.
216 * @since 11.3
217 */
218 protected static $instances = array();
219
220 /**
221 * Constructor activating the default information of the language
222 *
223 * @param integer $identifier The primary key of the user to load (optional).
224 * @param JUserWrapperHelper $userHelper The JUserWrapperHelper for the static methods.
225 *
226 * @since 11.1
227 */
228 public function __construct($identifier = 0, JUserWrapperHelper $userHelper = null)
229 {
230 if (null === $userHelper)
231 {
232 $userHelper = new JUserWrapperHelper;
233 }
234
235 $this->userHelper = $userHelper;
236
237 // Create the user parameters object
238 $this->_params = new Registry;
239
240 // Load the user if it exists
241 if (!empty($identifier))
242 {
243 $this->load($identifier);
244 }
245 else
246 {
247 // Initialise
248 $this->id = 0;
249 $this->sendEmail = 0;
250 $this->aid = 0;
251 $this->guest = 1;
252 }
253 }
254
255 /**
256 * Returns the global User object, only creating it if it doesn't already exist.
257 *
258 * @param integer $identifier The primary key of the user to load (optional).
259 * @param JUserWrapperHelper $userHelper The JUserWrapperHelper for the static methods.
260 *
261 * @return JUser The User object.
262 *
263 * @since 11.1
264 */
265 public static function getInstance($identifier = 0, JUserWrapperHelper $userHelper = null)
266 {
267 if (null === $userHelper)
268 {
269 $userHelper = new JUserWrapperHelper;
270 }
271
272 // Find the user id
273 if (!is_numeric($identifier))
274 {
275 if (!$id = $userHelper->getUserId($identifier))
276 {
277 // If the $identifier doesn't match with any id, just return an empty JUser.
278 return new JUser;
279 }
280 }
281 else
282 {
283 $id = $identifier;
284 }
285
286 // If the $id is zero, just return an empty JUser.
287 // Note: don't cache this user because it'll have a new ID on save!
288 if ($id === 0)
289 {
290 return new JUser;
291 }
292
293 // Check if the user ID is already cached.
294 if (empty(self::$instances[$id]))
295 {
296 $user = new JUser($id, $userHelper);
297 self::$instances[$id] = $user;
298 }
299
300 return self::$instances[$id];
301 }
302
303 /**
304 * Method to get a parameter value
305 *
306 * @param string $key Parameter key
307 * @param mixed $default Parameter default value
308 *
309 * @return mixed The value or the default if it did not exist
310 *
311 * @since 11.1
312 */
313 public function getParam($key, $default = null)
314 {
315 return $this->_params->get($key, $default);
316 }
317
318 /**
319 * Method to set a parameter
320 *
321 * @param string $key Parameter key
322 * @param mixed $value Parameter value
323 *
324 * @return mixed Set parameter value
325 *
326 * @since 11.1
327 */
328 public function setParam($key, $value)
329 {
330 return $this->_params->set($key, $value);
331 }
332
333 /**
334 * Method to set a default parameter if it does not exist
335 *
336 * @param string $key Parameter key
337 * @param mixed $value Parameter value
338 *
339 * @return mixed Set parameter value
340 *
341 * @since 11.1
342 */
343 public function defParam($key, $value)
344 {
345 return $this->_params->def($key, $value);
346 }
347
348 /**
349 * Method to check JUser object authorisation against an access control
350 * object and optionally an access extension object
351 *
352 * @param string $action The name of the action to check for permission.
353 * @param string $assetname The name of the asset on which to perform the action.
354 *
355 * @return boolean True if authorised
356 *
357 * @since 11.1
358 */
359 public function authorise($action, $assetname = null)
360 {
361 // Make sure we only check for core.admin once during the run.
362 if ($this->isRoot === null)
363 {
364 $this->isRoot = false;
365
366 // Check for the configuration file failsafe.
367 $rootUser = JFactory::getConfig()->get('root_user');
368
369 // The root_user variable can be a numeric user ID or a username.
370 if (is_numeric($rootUser) && $this->id > 0 && $this->id == $rootUser)
371 {
372 $this->isRoot = true;
373 }
374 elseif ($this->username && $this->username == $rootUser)
375 {
376 $this->isRoot = true;
377 }
378 elseif ($this->id > 0)
379 {
380 // Get all groups against which the user is mapped.
381 $identities = $this->getAuthorisedGroups();
382 array_unshift($identities, $this->id * -1);
383
384 if (JAccess::getAssetRules(1)->allow('core.admin', $identities))
385 {
386 $this->isRoot = true;
387
388 return true;
389 }
390 }
391 }
392
393 return $this->isRoot ? true : (bool) JAccess::check($this->id, $action, $assetname);
394 }
395
396 /**
397 * Method to return a list of all categories that a user has permission for a given action
398 *
399 * @param string $component The component from which to retrieve the categories
400 * @param string $action The name of the section within the component from which to retrieve the actions.
401 *
402 * @return array List of categories that this group can do this action to (empty array if none). Categories must be published.
403 *
404 * @since 11.1
405 */
406 public function getAuthorisedCategories($component, $action)
407 {
408 // Brute force method: get all published category rows for the component and check each one
409 // TODO: Modify the way permissions are stored in the db to allow for faster implementation and better scaling
410 $db = JFactory::getDbo();
411
412 $subQuery = $db->getQuery(true)
413 ->select('id,asset_id')
414 ->from('#__categories')
415 ->where('extension = ' . $db->quote($component))
416 ->where('published = 1');
417
418 $query = $db->getQuery(true)
419 ->select('c.id AS id, a.name AS asset_name')
420 ->from('(' . (string) $subQuery . ') AS c')
421 ->join('INNER', '#__assets AS a ON c.asset_id = a.id');
422 $db->setQuery($query);
423 $allCategories = $db->loadObjectList('id');
424 $allowedCategories = array();
425
426 foreach ($allCategories as $category)
427 {
428 if ($this->authorise($action, $category->asset_name))
429 {
430 $allowedCategories[] = (int) $category->id;
431 }
432 }
433
434 return $allowedCategories;
435 }
436
437 /**
438 * Gets an array of the authorised access levels for the user
439 *
440 * @return array
441 *
442 * @since 11.1
443 */
444 public function getAuthorisedViewLevels()
445 {
446 if ($this->_authLevels === null)
447 {
448 $this->_authLevels = array();
449 }
450
451 if (empty($this->_authLevels))
452 {
453 $this->_authLevels = JAccess::getAuthorisedViewLevels($this->id);
454 }
455
456 return $this->_authLevels;
457 }
458
459 /**
460 * Gets an array of the authorised user groups
461 *
462 * @return array
463 *
464 * @since 11.1
465 */
466 public function getAuthorisedGroups()
467 {
468 if ($this->_authGroups === null)
469 {
470 $this->_authGroups = array();
471 }
472
473 if (empty($this->_authGroups))
474 {
475 $this->_authGroups = JAccess::getGroupsByUser($this->id);
476 }
477
478 return $this->_authGroups;
479 }
480
481 /**
482 * Clears the access rights cache of this user
483 *
484 * @return void
485 *
486 * @since 3.4.0
487 */
488 public function clearAccessRights()
489 {
490 $this->_authLevels = null;
491 $this->_authGroups = null;
492 $this->isRoot = null;
493 JAccess::clearStatics();
494 }
495
496 /**
497 * Pass through method to the table for setting the last visit date
498 *
499 * @param integer $timestamp The timestamp, defaults to 'now'.
500 *
501 * @return boolean True on success.
502 *
503 * @since 11.1
504 */
505 public function setLastVisit($timestamp = null)
506 {
507 // Create the user table object
508 $table = $this->getTable();
509 $table->load($this->id);
510
511 return $table->setLastVisit($timestamp);
512 }
513
514 /**
515 * Method to get the user parameters
516 *
517 * This method used to load the user parameters from a file.
518 *
519 * @return object The user parameters object.
520 *
521 * @since 11.1
522 * @deprecated 12.3 (Platform) & 4.0 (CMS) - Instead use JUser::getParam()
523 */
524 public function getParameters()
525 {
526 // @codeCoverageIgnoreStart
527 JLog::add('JUser::getParameters() is deprecated. JUser::getParam().', JLog::WARNING, 'deprecated');
528
529 return $this->_params;
530
531 // @codeCoverageIgnoreEnd
532 }
533
534 /**
535 * Method to get the user timezone.
536 *
537 * If the user didn't set a timezone, it will return the server timezone
538 *
539 * @return DateTimeZone
540 *
541 * @since 3.7.0
542 */
543 public function getTimezone()
544 {
545 $timezone = $this->getParam('timezone', JFactory::getApplication()->get('offset', 'GMT'));
546
547 return new DateTimeZone($timezone);
548 }
549
550 /**
551 * Method to get the user parameters
552 *
553 * @param object $params The user parameters object
554 *
555 * @return void
556 *
557 * @since 11.1
558 */
559 public function setParameters($params)
560 {
561 $this->_params = $params;
562 }
563
564 /**
565 * Method to get the user table object
566 *
567 * This function uses a static variable to store the table name of the user table to
568 * instantiate. You can call this function statically to set the table name if
569 * needed.
570 *
571 * @param string $type The user table name to be used
572 * @param string $prefix The user table prefix to be used
573 *
574 * @return object The user table object
575 *
576 * @note At 4.0 this method will no longer be static
577 * @since 11.1
578 */
579 public static function getTable($type = null, $prefix = 'JTable')
580 {
581 static $tabletype;
582
583 // Set the default tabletype;
584 if (!isset($tabletype))
585 {
586 $tabletype['name'] = 'user';
587 $tabletype['prefix'] = 'JTable';
588 }
589
590 // Set a custom table type is defined
591 if (isset($type))
592 {
593 $tabletype['name'] = $type;
594 $tabletype['prefix'] = $prefix;
595 }
596
597 // Create the user table object
598 return JTable::getInstance($tabletype['name'], $tabletype['prefix']);
599 }
600
601 /**
602 * Method to bind an associative array of data to a user object
603 *
604 * @param array &$array The associative array to bind to the object
605 *
606 * @return boolean True on success
607 *
608 * @since 11.1
609 */
610 public function bind(&$array)
611 {
612 // Let's check to see if the user is new or not
613 if (empty($this->id))
614 {
615 // Check the password and create the crypted password
616 if (empty($array['password']))
617 {
618 $array['password'] = $this->userHelper->genRandomPassword();
619 $array['password2'] = $array['password'];
620 }
621
622 // Not all controllers check the password, although they should.
623 // Hence this code is required:
624 if (isset($array['password2']) && $array['password'] != $array['password2'])
625 {
626 JFactory::getApplication()->enqueueMessage(JText::_('JLIB_USER_ERROR_PASSWORD_NOT_MATCH'), 'error');
627
628 return false;
629 }
630
631 $this->password_clear = ArrayHelper::getValue($array, 'password', '', 'string');
632
633 $array['password'] = $this->userHelper->hashPassword($array['password']);
634
635 // Set the registration timestamp
636 $this->set('registerDate', JFactory::getDate()->toSql());
637
638 // Check that username is not greater than 150 characters
639 $username = $this->get('username');
640
641 if (strlen($username) > 150)
642 {
643 $username = substr($username, 0, 150);
644 $this->set('username', $username);
645 }
646 }
647 else
648 {
649 // Updating an existing user
650 if (!empty($array['password']))
651 {
652 if ($array['password'] != $array['password2'])
653 {
654 $this->setError(JText::_('JLIB_USER_ERROR_PASSWORD_NOT_MATCH'));
655
656 return false;
657 }
658
659 $this->password_clear = ArrayHelper::getValue($array, 'password', '', 'string');
660
661 // Check if the user is reusing the current password if required to reset their password
662 if ($this->requireReset == 1 && $this->userHelper->verifyPassword($this->password_clear, $this->password))
663 {
664 $this->setError(JText::_('JLIB_USER_ERROR_CANNOT_REUSE_PASSWORD'));
665
666 return false;
667 }
668
669 $array['password'] = $this->userHelper->hashPassword($array['password']);
670
671 // Reset the change password flag
672 $array['requireReset'] = 0;
673 }
674 else
675 {
676 $array['password'] = $this->password;
677 }
678 }
679
680 if (array_key_exists('params', $array))
681 {
682 $this->_params->loadArray($array['params']);
683
684 if (is_array($array['params']))
685 {
686 $params = (string) $this->_params;
687 }
688 else
689 {
690 $params = $array['params'];
691 }
692
693 $this->params = $params;
694 }
695
696 // Bind the array
697 if (!$this->setProperties($array))
698 {
699 $this->setError(JText::_('JLIB_USER_ERROR_BIND_ARRAY'));
700
701 return false;
702 }
703
704 // Make sure its an integer
705 $this->id = (int) $this->id;
706
707 return true;
708 }
709
710 /**
711 * Method to save the JUser object to the database
712 *
713 * @param boolean $updateOnly Save the object only if not a new user
714 * Currently only used in the user reset password method.
715 *
716 * @return boolean True on success
717 *
718 * @since 11.1
719 * @throws RuntimeException
720 */
721 public function save($updateOnly = false)
722 {
723 // Create the user table object
724 $table = $this->getTable();
725 $this->params = (string) $this->_params;
726 $table->bind($this->getProperties());
727
728 // Allow an exception to be thrown.
729 try
730 {
731 // Check and store the object.
732 if (!$table->check())
733 {
734 $this->setError($table->getError());
735
736 return false;
737 }
738
739 // If user is made a Super Admin group and user is NOT a Super Admin
740
741 // @todo ACL - this needs to be acl checked
742
743 $my = JFactory::getUser();
744
745 // Are we creating a new user
746 $isNew = empty($this->id);
747
748 // If we aren't allowed to create new users return
749 if ($isNew && $updateOnly)
750 {
751 return true;
752 }
753
754 // Get the old user
755 $oldUser = new JUser($this->id);
756
757 // Access Checks
758
759 // The only mandatory check is that only Super Admins can operate on other Super Admin accounts.
760 // To add additional business rules, use a user plugin and throw an Exception with onUserBeforeSave.
761
762 // Check if I am a Super Admin
763 $iAmSuperAdmin = $my->authorise('core.admin');
764
765 $iAmRehashingSuperadmin = false;
766
767 if (($my->id == 0 && !$isNew) && $this->id == $oldUser->id && $oldUser->authorise('core.admin') && $oldUser->password != $this->password)
768 {
769 $iAmRehashingSuperadmin = true;
770 }
771
772 // We are only worried about edits to this account if I am not a Super Admin.
773 if ($iAmSuperAdmin != true && $iAmRehashingSuperadmin != true)
774 {
775 // I am not a Super Admin, and this one is, so fail.
776 if (!$isNew && JAccess::check($this->id, 'core.admin'))
777 {
778 throw new RuntimeException('User not Super Administrator');
779 }
780
781 if ($this->groups != null)
782 {
783 // I am not a Super Admin and I'm trying to make one.
784 foreach ($this->groups as $groupId)
785 {
786 if (JAccess::checkGroup($groupId, 'core.admin'))
787 {
788 throw new RuntimeException('User not Super Administrator');
789 }
790 }
791 }
792 }
793
794 // Fire the onUserBeforeSave event.
795 JPluginHelper::importPlugin('user');
796 $dispatcher = JEventDispatcher::getInstance();
797
798 $result = $dispatcher->trigger('onUserBeforeSave', array($oldUser->getProperties(), $isNew, $this->getProperties()));
799
800 if (in_array(false, $result, true))
801 {
802 // Plugin will have to raise its own error or throw an exception.
803 return false;
804 }
805
806 // Store the user data in the database
807 $result = $table->store();
808
809 // Set the id for the JUser object in case we created a new user.
810 if (empty($this->id))
811 {
812 $this->id = $table->get('id');
813 }
814
815 if ($my->id == $table->id)
816 {
817 $registry = new Registry($table->params);
818 $my->setParameters($registry);
819 }
820
821 // Fire the onUserAfterSave event
822 $dispatcher->trigger('onUserAfterSave', array($this->getProperties(), $isNew, $result, $this->getError()));
823 }
824 catch (Exception $e)
825 {
826 $this->setError($e->getMessage());
827
828 return false;
829 }
830
831 return $result;
832 }
833
834 /**
835 * Method to delete the JUser object from the database
836 *
837 * @return boolean True on success
838 *
839 * @since 11.1
840 */
841 public function delete()
842 {
843 JPluginHelper::importPlugin('user');
844
845 // Trigger the onUserBeforeDelete event
846 $dispatcher = JEventDispatcher::getInstance();
847 $dispatcher->trigger('onUserBeforeDelete', array($this->getProperties()));
848
849 // Create the user table object
850 $table = $this->getTable();
851
852 if (!$result = $table->delete($this->id))
853 {
854 $this->setError($table->getError());
855 }
856
857 // Trigger the onUserAfterDelete event
858 $dispatcher->trigger('onUserAfterDelete', array($this->getProperties(), $result, $this->getError()));
859
860 return $result;
861 }
862
863 /**
864 * Method to load a JUser object by user id number
865 *
866 * @param mixed $id The user id of the user to load
867 *
868 * @return boolean True on success
869 *
870 * @since 11.1
871 */
872 public function load($id)
873 {
874 // Create the user table object
875 $table = $this->getTable();
876
877 // Load the JUserModel object based on the user id or throw a warning.
878 if (!$table->load($id))
879 {
880 // Reset to guest user
881 $this->guest = 1;
882
883 JLog::add(JText::sprintf('JLIB_USER_ERROR_UNABLE_TO_LOAD_USER', $id), JLog::WARNING, 'jerror');
884
885 return false;
886 }
887
888 /*
889 * Set the user parameters using the default XML file. We might want to
890 * extend this in the future to allow for the ability to have custom
891 * user parameters, but for right now we'll leave it how it is.
892 */
893
894 $this->_params->loadString($table->params);
895
896 // Assuming all is well at this point let's bind the data
897 $this->setProperties($table->getProperties());
898
899 // The user is no longer a guest
900 if ($this->id != 0)
901 {
902 $this->guest = 0;
903 }
904 else
905 {
906 $this->guest = 1;
907 }
908
909 return true;
910 }
911
912 /**
913 * Method to allow serialize the object with minimal properties.
914 *
915 * @return array The names of the properties to include in serialization.
916 *
917 * @since 3.6.0
918 */
919 public function __sleep()
920 {
921 return array('id');
922 }
923
924 /**
925 * Method to recover the full object on unserialize.
926 *
927 * @return void
928 *
929 * @since 3.6.0
930 */
931 public function __wakeup()
932 {
933 // Initialise some variables
934 $this->userHelper = new JUserWrapperHelper;
935 $this->_params = new Registry;
936
937 // Load the user if it exists
938 if (!empty($this->id))
939 {
940 $this->load($this->id);
941 }
942 else
943 {
944 // Initialise
945 $this->id = 0;
946 $this->sendEmail = 0;
947 $this->aid = 0;
948 $this->guest = 1;
949 }
950 }
951 }
952