/**
  *	Enforces singleton use
  *
  *
  ***/
 public static function instance()
 {
     if (empty(self::$instance)) {
         self::$instance = new vB_StopForumSpam();
     }
     return self::$instance;
 }
Example #2
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;
 }
Example #3
0
 /** Permanently/Temporarily deletes a set of nodes
  *	@param	array	The nodeids of the records to be deleted
  *	@param	bool	hard/soft delete
  *	@param	string	the reason for soft delete (not used for hard delete)
  *	@param	bool	Log the deletes in moderator log
  *  @param	bool	Report node content to spam service
  *
  *	@return	array nodeids that were deleted
  **/
 public function deleteNodes($deleteNodeIds, $hard, $reason, $ancestorsId, $starters, $modlog = true, $reportspam = false)
 {
     $loginfo = array();
     $starters = array();
     $needRebuild = false;
     $vboptions = vB::getDatastore()->getValue('options');
     if ((!$vboptions['vb_antispam_type'] or !$vboptions['vb_antispam_key']) and !$vboptions['vb_antispam_sfs_key']) {
         $reportspam = false;
     }
     //if we are doing a hard delete we need to first delete the type-specific data.
     if ($reportspam) {
         $nodes = $this->getContentforNodes($deleteNodeIds);
         $akismet = vB_Akismet::instance();
         $stopForumSpam = vB_StopForumSpam::instance();
         foreach ($nodes as $node) {
             if ($node['content']['rawtext']) {
                 $text = vB_String::stripBbcode($node['content']['rawtext'], true);
                 $akismet->markAsSpam(array('comment_type' => 'comment', 'comment_author' => $node['content']['authorname'], 'comment_content' => $text, 'user_ip' => $node['content']['ipaddress']));
                 if ($vboptions['vb_antispam_sfs_key']) {
                     $userinfo = vB_User::fetchUserinfo($node['content']['userid']);
                     // must have email address
                     $stopForumSpam->markAsSpam($userinfo['username'], $node['content']['ipaddress'], $text, $userinfo['email']);
                 }
             }
         }
     } else {
         $nodes = $this->getNodes($deleteNodeIds, false);
     }
     if ($hard) {
         $infractionTypeid = vB_Types::instance()->getContentTypeId('vBForum_Infraction');
         foreach ($nodes as $node) {
             if ($node['contenttypeid'] == $infractionTypeid) {
                 throw new vB_Exception_Api('cannot_delete_infraction_nodes');
             }
             //see if we need a rebuild
             if ($node['contenttypeid'] == vB_Types::instance()->getContentTypeID('vBForum_Channel')) {
                 $needRebuild = true;
             }
             try {
                 $starters[] = $node['starter'];
                 // content delete method handle counts updating
                 vB_Api_Content::getContentApi($node['contenttypeid'])->delete($node['nodeid']);
             } catch (vB_Exception_Api $e) {
                 //nothing to do.
                 // Actually, it's possible that the content API will throw a no_permission exception if it fails the
                 // validate() check for "action delete." In that case, we're just silently continuing, which could
                 // result in a node not being deleted with 0 indication that something went wrong.
                 // We should think of a better way to output this situation to the moderator.
                 // If the node did not get deleted. Possibly due to lack of permission to delete.
                 // 1. check if node still exists (which means there was actually a problem deleting, not that it was a non-existent node)
                 // 2. if "yes", remove the node from $deleteNodeIds so the return value doesn't include it and so no further processing happens
                 // 3. skip to the next iteration of this loop, so this nodeid is not included in the mod log.
                 $check = vB::getDbAssertor()->getRows('vBForum:node', array('nodeid' => $node['nodeid']));
                 if (count($check) == 1) {
                     // $deleteNodeIds can be an array or a single nodeid
                     if (is_array($deleteNodeIds)) {
                         $key = array_search($node['nodeid'], (array) $deleteNodeIds, true);
                         if ($key !== false) {
                             unset($deleteNodeIds[$key]);
                         }
                     } else {
                         if ($deleteNodeIds == $node['nodeid']) {
                             $deleteNodeIds = '';
                         }
                     }
                     continue;
                 }
             }
             // Note: Do not decrement user post count here. That is done
             // in the content library for hard-deletes.
             if ($modlog) {
                 $loginfo[] = array('nodeid' => $node['nodeid'], 'nodetitle' => $node['title'], 'nodeusername' => $node['authorname'], 'nodeuserid' => $node['userid']);
             }
         }
         vB_Library_Admin::logModeratorAction($loginfo, 'node_hard_deleted_by_x');
     } else {
         $fields = array('unpublishdate' => vB::getRequest()->getTimeNow(), 'deletereason' => $reason, 'deleteuserid' => vB::getCurrentSession()->get('userid'), 'approved' => 1, 'showapproved' => 1);
         $result = vB::getDbAssertor()->update('vBForum:node', $fields, array('nodeid' => $deleteNodeIds));
         $errors = array();
         foreach ($nodes as $node) {
             //see if we need a rebuild
             if ($node['contenttypeid'] == vB_Types::instance()->getContentTypeID('vBForum_Channel')) {
                 $needRebuild = true;
             }
             $starters[] = $node['starter'];
             $nodeUpdates = $this->unpublishChildren($node['nodeid']);
             $this->updateChangedNodeParentCounts($node, $nodeUpdates);
             // Update user post count (soft delete)
             vB_Library_Content::getContentLib($node['contenttypeid'])->decrementUserPostCount($node, 'unpublish');
             if ($modlog) {
                 $loginfo[] = array('nodeid' => $node['nodeid'], 'nodetitle' => $node['title'], 'nodeusername' => $node['authorname'], 'nodeuserid' => $node['userid']);
             }
             if (!empty($node['setfor'])) {
                 vB_Cache::instance()->allCacheEvent('fUserContentChg_' . $node['setfor']);
             }
         }
         if (!empty($errors)) {
             return array('errors' => $errors);
         }
         vB_Library_Admin::logModeratorAction($loginfo, 'node_soft_deleted_by_x');
     }
     $starters = array_unique($starters);
     // reset last content for all parents that have the deleted nodes
     $cache = vB_Cache::instance(vB_Cache::CACHE_FAST);
     $fastCache = vB_Cache::instance(vB_Cache::CACHE_FAST);
     $events = array();
     foreach ($starters as $starter) {
         $events[] = "nodeChg_" . $starter;
     }
     foreach ($deleteNodeIds as $nodeid) {
         $events[] = "nodeChg_" . $nodeid;
     }
     $events = array_unique($events);
     $cache->allCacheEvent($events);
     vB_Api::instance('Search')->purgeCacheForCurrentUser();
     if ($needRebuild) {
         vB::getUserContext()->rebuildGroupAccess();
         vB_Channel::rebuildChannelTypes();
     }
     return $deleteNodeIds;
 }