예제 #1
0
 /**
  * Insert or Update an user
  *
  * @param integer $userid Userid to be updated. Set to 0 if you want to insert a new user.
  * @param string $password Password for the user. Empty means no change.  May be overriden by the $extra array
  * @param array $user Basic user information such as email or home page
  * 	* username
  * 	* email
  * 	* usertitle
  * 	* birthday
  * 	* usergroupid (will get no_permissions exception without administrate user permissions)
  * 	* membergroupids (will get no_permissions exception without administrate user permissions)
  *	* list not complete
  * @param array $options vB options for the user
  * @param array $adminoptions Admin Override Options for the user
  * @param array $userfield User's User Profile Field data
  * @param array $notificationOptions
  * @param array $hvinput Human Verify input data. @see vB_Api_Hv::verifyToken()
  * @param array $extra Generic flags or data to affect processing.
  *	* registration
  *	* email
  *	* newpass
  *	* password
  *	* acnt_settings
  * @return integer New or updated userid.
  */
 public function save($userid, $password, $user, $options, $adminoptions, $userfield, $notificationOptions = array(), $hvinput = array(), $extra = array())
 {
     $db = vB::getDbAssertor();
     $vboptions = vB::getDatastore()->getValue('options');
     $userContext = vB::getUserContext();
     $currentUserId = $userContext->fetchUserId();
     $userid = intval($userid);
     $coppauser = false;
     //set up some booleans to control behavior.  This is done to simply/document the later code
     $newuser = !$userid;
     $canadminusers = $this->hasAdminPermission('canadminusers');
     $adminoverride = ($canadminusers and empty($extra['acnt_settings']) and empty($extra['acnt_settings']));
     $changingCurrentUser = $userid == $currentUserId;
     // Not sure why we do this at all.  The caller should handle this appropriately.
     // We shouldn't set $userid = $currentUserId if $userid == 0 here
     // Cause we may need to allow logged-in user to register again
     if ($userid < 0 and $currentUserId) {
         $userid = $currentUserId;
     }
     //we'll need this all over the place if this isn't a new user.
     if (!$newuser) {
         $userinfo = vB_User::fetchUserInfo($userid);
     }
     //check some permissions.  If we can admin users we can skip all of these checks.  Some checks
     //only apply to some cases, such as registering a newuser.  We also check various fields
     //in some cases and not others.
     if (!$canadminusers) {
         if ($newuser) {
             // Check if registration is allowed
             if (!$vboptions['allowregistration']) {
                 throw new vB_Exception_Api('noregister');
             }
             // Check Multiple Registrations Per User
             if ($currentUserId and !$vboptions['allowmultiregs']) {
                 $currentUser = vB::getCurrentSession()->fetch_userinfo();
                 throw new vB_Exception_Api('signing_up_but_currently_logged_in_msg', array($currentUser['username'], $vboptions['frontendurl'] . '/auth/logout?logouthash=' . $currentUser['logouthash']));
             }
             // If it's a new registration, we need to verify the HV
             // VBV-9386: HV is disabled when accessing through the VB_API in vb4.
             // Tere is also a comment saying that it should be enabled once it goes live???
             if (!defined('VB_API') or defined('VB_API') and VB_API !== true) {
                 vB_Api::instanceInternal('hv')->verifyToken($hvinput, 'register');
             }
             // Verify Stop Forum Spam
             $nospam = vB_StopForumSpam::instance();
             if (!$nospam->checkRegistration($user['username'], vB::getRequest()->getIpAddress(), $user['email'])) {
                 throw new vB_Exception_Api('noregister');
             }
         } else {
             //attempting to update somebody else's profile -- only admins can do this
             if (!$changingCurrentUser) {
                 throw new vB_Exception_Api('no_permission');
             }
             //we need to handle this more gracefully -- this is kindof weird.
             if (!$userContext->hasPermission('genericpermissions', 'canmodifyprofile')) {
                 // User can only update email and password
                 return $this->saveEmailPassword($extra);
             }
             if (isset($user['privacy_options']) and !$userContext->hasPermission('usercsspermissions', 'caneditprivacy')) {
                 // User doesn't have permission to update privacy
                 throw new vB_Exception_Api('no_permission');
             }
             if (isset($options['invisible']) and !empty($options['invisible']) and !$userContext->hasPermission('genericpermissions', 'caninvisible')) {
                 // User doesn't have permission to go invisible
                 throw new vB_Exception_Api('no_permission');
             }
         }
         //handle some fields that users should not be able to set (the admin can do what he wants)
         if (isset($user['usergroupid'])) {
             throw new vB_Exception_Api('no_permission');
         }
         if (isset($user['membergroupids'])) {
             throw new vB_Exception_Api('no_permission');
         }
     }
     /*
      * Some checks for all cases.
      */
     //check the user title length.  Skip for any administrator.  Not sure if we should be checking for edit user permissions or not, but
     //it's not a major issue if admins can set their own titles to something really long so changing it at this point is not wise.
     if (isset($user['usertitle']) and vB_String::vbStrlen($user['usertitle']) > $vboptions['ctMaxChars'] and !$userContext->isAdministrator()) {
         throw new vB_Exception_Api('please_enter_user_title_with_at_least_x_characters', $vboptions['ctMaxChars']);
     }
     //don't allow changes to an unalterable user unless the user themselves requests it.  We might want to lock down what the
     //user can edit in this case.
     require_once DIR . '/includes/adminfunctions.php';
     if (!$changingCurrentUser and is_unalterable_user($userid)) {
         throw new vB_Exception_Api('user_is_protected_from_alteration_by_undeletableusers_var');
     }
     $olduser = array();
     if ($userid != 0) {
         // Get old user information
         $olduser = $db->getRow('user_fetchforupdating', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_STORED, 'userid' => $userid));
         if (!$olduser) {
             throw new vB_Exception_Api('invalid_user_specified');
         }
     }
     // if birthday is required
     if ($vboptions['reqbirthday'] and empty($olduser['birthday']) and empty($user['birthday'])) {
         if (count($userfield)) {
             throw new vB_Exception_Api('birthdayfield');
         } else {
             throw new vB_Exception_Api('birthdayfield_nonprofile_tab');
         }
     }
     /*
      *	If we are changing the password or email from the account setting we need to validate the users
      *	existing password.
      */
     //we allow stuff for the account profile page to be passed separately in the $extra array.
     //we shouldn't but cleaning that up is a larger task.
     if (!empty($extra['acnt_settings'])) {
         if (!empty($extra['email'])) {
             $user['email'] = $extra['email'];
         }
         //new password to set
         if (!empty($extra['newpass'])) {
             $password = $extra['newpass'];
         }
         //the user's existing password -- needed to verify to set certain sensative fields.
         if (!empty($extra['password'])) {
             $user['password'] = $extra['password'];
         }
     }
     //if we are setting the password or the email we may need to check the user's existing
     //password as an extra precaution.
     // * If this is an existing user
     // * If we are changing the password or email
     // * If we are not overriding as an admin
     if (!$newuser and (!empty($password) or !empty($user['email'])) and !$adminoverride) {
         $loginlib = vB_Library::instance('login');
         if (!$user['password']) {
             throw new vB_Exception_Api('enter_current_password');
         }
         $login = array_intersect_key($userinfo, array_flip(array('userid', 'token', 'scheme')));
         $auth = $loginlib->verifyPasswordFromInfo($login, array(array('password' => $user['password'], 'encoding' => 'text')));
         if (!$auth['auth']) {
             throw new vB_Exception_Api('badpassword', vB5_Route::buildUrl('lostpw|fullurl'));
         }
     }
     //this is the user's existing password which we don't need now that we've verified it.
     //attempting to set it to the DM, which we do below for all user fields causes problems.
     unset($user['password']);
     //if this is a newuser we need to have a password -- even if this is an admin creating the user
     if ($newuser and empty($password)) {
         throw new vB_Exception_Api('invalid_password_specified');
     }
     /*
      *	If we got this far, we basically have permission to update the user in the way we requested.
      */
     $bf_misc_useroptions = vB::getDatastore()->getValue('bf_misc_useroptions');
     $bf_misc_adminoptions = vB::getDatastore()->getValue('bf_misc_adminoptions');
     $bf_misc_notificationoptions = vB::getDatastore()->getValue('bf_misc_usernotificationoptions');
     $usergroupcache = vB::getDatastore()->getValue('usergroupcache');
     $user['ipaddress'] = vB::getRequest()->getIpAddress();
     $olduser = array_merge($olduser, convert_bits_to_array($olduser['options'], $bf_misc_useroptions));
     $olduser = array_merge($olduser, convert_bits_to_array($olduser['adminoptions'], $bf_misc_adminoptions));
     $olduser = array_merge($olduser, convert_bits_to_array($olduser['notification_options'], $bf_misc_notificationoptions));
     // get threaded mode options
     if (isset($olduser['threadedmode']) and ($olduser['threadedmode'] == 1 or $olduser['threadedmode'] == 2)) {
         $threaddisplaymode = $olduser['threadedmode'];
     } else {
         if (isset($olduser['postorder']) and $olduser['postorder'] == 0) {
             $threaddisplaymode = 0;
         } else {
             $threaddisplaymode = 3;
         }
     }
     $olduser['threadedmode'] = $threaddisplaymode;
     // Let's handle this at API level, ignore list is causing problems in the data manager
     //handle ignorelist
     if (isset($user['ignorelist'])) {
         $user['ignorelist'] = $this->updateIgnorelist($userid, explode(',', $user['ignorelist']));
     } else {
         $user['ignorelist'] = array();
     }
     // init data manager
     $userdata = new vB_Datamanager_User(vB_DataManager_Constants::ERRTYPE_ARRAY_UNPROCESSED);
     /*
      * If this was called from the account settings or registration pages
      * (not the Admin Control Panel) then we shouldn't be setting admin override.
      * Should also make sure that the admin is logged in and its not just a case of someone
      * telling the API that we're in the ACP
      */
     if ($adminoverride) {
         $userdata->adminoverride = true;
     }
     $updateUGPCache = false;
     // set existing info if this is an update
     if (!$newuser) {
         // birthday
         if (!$adminoverride and $user['birthday'] and $olduser['birthday'] and $user['birthday'] != $olduser['birthday'] and $vboptions['reqbirthday']) {
             throw new vB_Exception_Api('has_no_permission_change_birthday');
         }
         // update buddy list
         $user['buddylist'] = array();
         foreach (explode(' ', $userinfo['buddylist']) as $buddy) {
             if (in_array($buddy, $user['ignorelist']) === false) {
                 $user['buddylist'][] = $buddy;
             }
         }
         $userinfo['posts'] = intval($user['posts']);
         // update usergroups cache if needed...
         $uInfoMUgpIds = explode(',', trim($userinfo['membergroupids']));
         $uInfoUgpId = trim($userinfo['usergroupid']);
         $uIGpIds = explode(',', trim($userinfo['infractiongroupids']));
         $mUgpIds = isset($user['membergroupids']) ? $user['membergroupids'] : false;
         $ugpId = isset($user['usergroupid']) ? trim($user['usergroupid']) : false;
         $iGpIds = isset($user['infractiongroupids']) ? explode(',', trim($user['infractiongroupids'])) : false;
         if ($ugpId and $uInfoUgpId != $ugpId or $mUgpIds and array_diff($uInfoMUgpIds, $mUgpIds) or $iGpIds and array_diff($iGpIds, $uIGpIds)) {
             $updateUGPCache = true;
         }
         $userdata->set_existing($userinfo);
     } else {
         if ($this->useCoppa()) {
             if (empty($user['birthday'])) {
                 throw new vB_Exception_Api('under_thirteen_registration_denied');
             }
             if ($this->needsCoppa($user['birthday'])) {
                 if ($vboptions['usecoppa'] == 2) {
                     throw new vB_Exception_Api('under_thirteen_registration_denied');
                 } else {
                     if (empty($user['parentemail'])) {
                         throw new vB_Exception_Api('coppa_rules_description');
                     }
                     $userdata->set_info('coppauser', true);
                     $userdata->set_info('coppapassword', $password);
                     $options['coppauser'] = 1;
                     $coppauser = true;
                 }
             } else {
                 if ($vboptions['moderatenewmembers']) {
                     $userdata->set_info('usergroupid', 4);
                 } else {
                     if ($vboptions['verifyemail']) {
                         $userdata->set_info('usergroupid', 3);
                     } else {
                         $userdata->set_info('usergroupid', 2);
                     }
                 }
             }
         }
     }
     //should not be required with the new password code.
     // if no username is provided then is taken from old userinfo, datamanager needs username always set to perform password checks.
     //$username = (empty($user['username']) ? $userinfo['username'] : $user['username']);
     //$userdata->set('username', $username);
     //unset($user['username']);
     // user options
     foreach ($bf_misc_useroptions as $key => $val) {
         if (isset($options["{$key}"])) {
             $userdata->set_bitfield('options', $key, $options["{$key}"]);
         } else {
             if (isset($olduser["{$key}"])) {
                 $userdata->set_bitfield('options', $key, $olduser["{$key}"]);
             }
         }
     }
     foreach ($adminoptions as $key => $val) {
         $userdata->set_bitfield('adminoptions', $key, $val);
     }
     // notification options
     foreach ($notificationOptions as $key => $val) {
         // @TODO related to VBV-92
         if ($olduser["{$key}"] != $val) {
             $userdata->set_bitfield('notification_options', $key, $val);
         } else {
             if ($olduser["{$key}"] == $val) {
                 $userdata->set_bitfield('notification_options', $key, $olduser["{$key}"]);
             }
         }
     }
     $displaygroupid = (array_key_exists('displaygroupid', $user) and intval($user['displaygroupid'])) ? $user['displaygroupid'] : '';
     if (isset($user['usergroupid']) and $user['usergroupid']) {
         $displaygroupid = $user['usergroupid'];
     } elseif (isset($olduser['usergroupid']) and $olduser['usergroupid']) {
         $displaygroupid = $olduser['usergroupid'];
     }
     // custom user title
     if (isset($user['usertitle']) and $user['usertitle']) {
         $userdata->set_usertitle($user['usertitle'], $user['customtitle'] ? false : true, $usergroupcache["{$displaygroupid}"], $userContext->hasPermission('genericpermissions', 'canusecustomtitle'), $userContext->isAdministrator());
         unset($user['usertitle'], $user['customtitle']);
     } else {
         if (isset($user['usertitle']) and empty($user['usertitle']) and empty($user['customtitle'])) {
             $userdata->set_usertitle('', true, $usergroupcache["{$displaygroupid}"], $userContext->hasPermission('genericpermissions', 'canusecustomtitle'), $userContext->isAdministrator());
             unset($user['usertitle'], $user['customtitle']);
         }
     }
     // privacy_options
     $privacyChanged = false;
     if (isset($user['privacy_options']) and $user['privacy_options']) {
         foreach ($user['privacy_options'] as $opt => $val) {
             if (!in_array($opt, $this->privacyOptions)) {
                 unset($user['privacy_options'][$opt]);
             }
         }
         // check if we need to update cached values...
         if ($olduser['privacy_options']) {
             $check = unserialize($olduser['privacy_options']);
             $diff = array_diff_assoc($user['privacy_options'], $check);
             if (!empty($diff)) {
                 $privacyChanged = true;
             }
         }
         $user['privacy_options'] = serialize($user['privacy_options']);
     }
     // Update from user fields
     foreach ($user as $key => $val) {
         if (!$userid or $olduser["{$key}"] != $val) {
             $userdata->set($key, $val);
         }
     }
     $membergroupids = false;
     if (isset($user['membergroupids']) and is_array($user['membergroupids'])) {
         $membergroupids = $user['membergroupids'];
     }
     //add facebook user group for new users being registered with FB
     //not entirely thrilled with putting this here, but doing it in a less
     //fragile way requires a greater refactoring of the registration code
     if ($newuser and $vboptions['facebookusergroupid']) {
         $fblib = vB_Library::instance('facebook');
         if ($fblib->isFacebookEnabled() and $fblib->userIsLoggedIn()) {
             if (is_array($membergroupids)) {
                 $membergroupids[] = $vboptions['facebookusergroupid'];
             } else {
                 $membergroupids = array($vboptions['facebookusergroupid']);
             }
         }
     }
     //actually set the usergroup array if we have one
     if (is_array($membergroupids)) {
         $userdata->set('membergroupids', $membergroupids);
     }
     // custom profile fields
     if (!empty($userfield) and is_array($userfield)) {
         $userdata->set_userfields($userfield, true, 'admin');
     }
     // handles ignorelist and buddylist correctly
     $userdata->set('ignorelist', $user['ignorelist']);
     $userdata->set('buddylist', isset($user['buddylist']) ? $user['buddylist'] : array());
     // timezone
     if (empty($user['timezoneoffset']) and $newuser) {
         $userdata->set('timezoneoffset', $vboptions['timeoffset']);
     }
     //the secret really isn't related to the password, but we want to change it
     //periodically and for now "every time the user changes their password"
     //works (we previously used the password salt so that's when it got changed
     //prior to the refactor).
     if (!empty($password)) {
         $userdata->set('secret', vB_Library::instance('user')->generateUserSecret());
     }
     // save data
     $newuserid = $userdata->save();
     if ($userdata->has_errors(false)) {
         throw $userdata->get_exception();
     }
     //a bit of a hack.  If the DM save function runs an update of an existing user then
     //it returns true rather than the userid (despite what the comments say). However its
     //not clear how to handle that in the DM (which looks like it could be use to alter
     //multiple users wholesale, in which case we really don't have an ID.  Better to catch it here.
     if ($newuserid === true) {
         $newuserid = $userid;
     }
     //if we have a new password, then let's set it.
     if (!empty($password)) {
         try {
             //lookup the history for the user we are editing, which is not necesarily the
             //user that we currently are.
             if ($changinCurrentUser) {
                 $history = $userContext->getUsergroupLimit('passwordhistory');
             } else {
                 if ($adminoverride) {
                     $history = 0;
                 } else {
                     $history = vB::getUserContext($userid)->getUsergroupLimit('passwordhistory');
                 }
             }
             $loginlib = vB_Library::instance('login');
             $loginlib->setPassword($newuserid, $password, array('passwordhistorylength' => $history), array('passwordhistory' => $adminoverride));
         } catch (Exception $e) {
             //if this is a new user, deleted it if we fail to set the intial password.
             if ($newuser) {
                 $db->delete('user', array('userid' => $newuserid));
             }
             throw $e;
         }
     }
     if ($updateUGPCache) {
         vB_Cache::instance(vB_Cache::CACHE_FAST)->event('perms_changed');
     }
     if ($privacyChanged) {
         vB_Cache::instance()->event('userPrivacyChg_' . $userid);
     }
     // clear user info cached
     $this->library->clearUserInfo(array($newuserid));
     // update session's languageid, VBV-11318
     if (isset($user['languageid'])) {
         vB::getCurrentSession()->set('languageid', $user['languageid']);
     }
     if ($newuser and $vboptions['newuseremail'] != '') {
         // Prepare email data
         $customfields = '';
         if (!empty($userfield) and is_array($userfield)) {
             $customfields = $userdata->set_userfields($userfield, true, 'register');
         }
         $maildata = vB_Api::instanceInternal('phrase')->fetchEmailPhrases('newuser', array($user['username'], vB::getDatastore()->getOption('bbtitle'), vB5_Route::buildUrl('profile|fullurl', array('userid' => $user['userid'])), $user['email'], $user['birthday'], $user['ipaddress'], $customfields), array(vB::getDatastore()->getOption('bbtitle')));
         // Send out the emails
         $newemails = explode(' ', $vboptions['newuseremail']);
         foreach ($newemails as $toemail) {
             if (trim($toemail)) {
                 vB_Mail::vbmail($toemail, $maildata['subject'], $maildata['message'], false);
             }
         }
     }
     // Check if we need to send out activate email
     $verifyEmail = (defined('VB_AREA') and VB_AREA == 'AdminCP') ? false : true;
     if ($newuser and $vboptions['verifyemail'] and $verifyEmail) {
         $this->library->sendActivateEmail($newuserid);
     }
     // Check if we need to send out welcome email
     if ($newuser and $userdata->fetch_field('usergroupid') == 2 and $vboptions['welcomemail']) {
         // Send welcome mail
         $username = trim(unhtmlspecialchars($user['username']));
         $maildata = vB_Api::instanceInternal('phrase')->fetchEmailPhrases('welcomemail', array($username, $vboptions['bbtitle']), array($vboptions['bbtitle']), isset($user['languageid']) ? $user['languageid'] : vB::getDatastore()->getOption('languageid'));
         vB_Mail::vbmail($user['email'], $maildata['subject'], $maildata['message'], true);
     }
     return $newuserid;
 }