Example #1
0
 /**
  * Moves nodes to a new parent
  *
  * @param	array	Node ids
  * @param	int	New parent node id
  * @param	bool	Make topic
  * @param	bool	New title
  * @param	bool	Mod log
  * @param	array	Information to leave a thread redirect. If empty, no redirect is created.
  *			If not empty, should contain these items:
  *				redirect (string) - perm|expires	Permanent or expiring redirect
  *				frame (string) - h|d|w|m|y	Hours, days, weeks, months, years (valid only for expiring redirects)
  *				period (int) - 1-10	How many hours, days, weeks etc, for the expiring redirect
  *
  * @return
  */
 public function moveNodes($nodeids, $to_parent, $makeTopic = false, $newtitle = false, $modlog = true, array $leaveRedirectData = array())
 {
     $movedNodes = array();
     $oldnodeInfo = array();
     $to_parent = $this->assertNodeidStr($to_parent);
     $userContext = vB::getUserContext();
     $currentUserid = vB::getCurrentSession()->get('userid');
     $channelAPI = vB_Api::instanceInternal('content_channel');
     $newparent = $this->getNode($to_parent);
     // If all the nodes to be moved are content nodes and are currently in the same
     // channel, then we are only merging posts/topics and need to check different permissions.
     // To detemine this, we iterate through all the nodes to be moved and get all the "starter"
     // nodeids. We then iterate through all the starters and check if they all have the same
     // parentid, which is the channel they are in.
     $isTopicMerge = false;
     $checkNodes = $this->getNodes(array_merge((array) $nodeids, (array) $to_parent));
     $skipCheck = false;
     $checkStarterIds = array();
     foreach ($checkNodes as $checkNode) {
         if (empty($checkNode['starter'])) {
             // we can skip this check because one of the nodes is a channel
             $skipCheck = true;
             break;
         } else {
             $checkStarterIds[] = $checkNode['starter'];
         }
     }
     if (!$skipCheck) {
         $checkStarters = $this->getNodes($checkStarterIds);
         $starterParents = array();
         foreach ($checkStarters as $checkStarter) {
             $starterParents[$checkStarter['parentid']] = $checkStarter['parentid'];
         }
         if (count($starterParents) === 1) {
             // the parent node of all the starters is the same, so we are
             // only moving topics / posts around inside the same channel,
             // this is likely a topic merge request and we can check a separate
             // permission below
             $isTopicMerge = true;
         }
         unset($checkStarters, $checkStarter, $starterParents);
     }
     unset($checkNode, $checkStarterIds, $skipCheck);
     //If the current user has can moderator canmove on the current nodes, or if the user can create in
     //the new channel and is the owner of the moved nodes and has forum canmove, then they can move
     if (!$userContext->getChannelPermission('forumpermissions', 'canmove', $to_parent) and !$userContext->getChannelPermission('moderatorpermissions', 'canmassmove', $to_parent) and (!$isTopicMerge or !$userContext->getChannelPermission('moderatorpermissions', 'canmanagethreads', $to_parent))) {
         throw new vB_Exception_Api('no_permission');
     }
     $nodes = vB::getDbAssertor()->getRows('vBForum:node', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, 'nodeid' => $nodeids), array('field' => array('publishdate'), 'direction' => array(vB_dB_Query::SORT_ASC)), 'nodeid');
     $needRebuild = false;
     $firstTitle = false;
     $loginfo = array();
     $parent = $this->getNodeFullContent($to_parent);
     $parent = $parent[$to_parent];
     $cacheEvents = array($to_parent);
     $oldparents = array();
     $channelTypeid = vB_Types::instance()->getContentTypeId('vBForum_Channel');
     $infractionTypeid = vB_Types::instance()->getContentTypeId('vBForum_Infraction');
     foreach ($nodes as $node) {
         if ($node['contenttypeid'] == $infractionTypeid) {
             throw new vB_Exception_Api('cannot_move_infraction_nodes');
         }
         if ($node['contenttypeid'] == $channelTypeid) {
             // If any of the moved nodes are channels, the target must be a channel.
             if ($newparent['contenttypeid'] != $channelTypeid) {
                 throw new vB_Exception_Api('invalid_request');
             }
             // We should not allow the moving of channels from one root channel to another.
             if ($channelAPI->getTopLevelChannel($newparent['nodeid']) != $channelAPI->getTopLevelChannel($node['nodeid'])) {
                 throw new vB_Exception_Api('cant_change_top_level');
             }
         }
         //Only channels can be moved to categories, UI shouldn't allow this
         if ($parent['contenttypeid'] == $channelTypeid) {
             $newrouteid = vB_Api::instanceInternal('route')->getChannelConversationRoute($to_parent);
             if ($node['contenttypeid'] != $channelTypeid and (empty($newrouteid) or !empty($parent['category']))) {
                 // The node we want to move is not a channel and the parent cannot have conversations
                 // (e.g. categories, the root blog channel, the root forum channel)
                 throw new vB_Exception_Api('invalid_request');
             }
         }
         if ($node['contenttypeid'] == vB_Types::instance()->getContentTypeID('vBForum_Channel')) {
             $needRebuild = true;
         }
         if ($userContext->getChannelPermission('moderatorpermissions', 'canmassmove', $node['nodeid']) or $currentUserid == $node['userid'] and $userContext->getChannelPermission('forumpermissions', 'canmove', $node['nodeid'], false, $node['parentid']) or $isTopicMerge and $userContext->getChannelPermission('moderatorpermissions', 'canmanagethreads', $to_parent)) {
             if (empty($movedNodes)) {
                 if (empty($node['title']) and !empty($node['starter'])) {
                     $starter = vB_Library::instance('node')->getNodeBare($node['starter']);
                     $firstTitle = $starter['title'];
                 } else {
                     $firstTitle = $node['title'];
                 }
             }
             $movedNodes[] = $node['nodeid'];
             $oldnodeInfo[$node['nodeid']] = $node;
             $oldparents[$node['nodeid']] = $node['parentid'];
             $this->contentLibs[$node['nodeid']] = vB_Library::instance('Content_' . vB_Types::instance()->getContentTypeClass($node['contenttypeid']));
             if ($modlog) {
                 $oldparent = $this->getNode($node['parentid']);
                 $extra = array('fromnodeid' => $oldparent['nodeid'], 'fromtitle' => $oldparent['title'], 'tonodeid' => $newparent['nodeid'], 'totitle' => $newparent['title']);
                 $loginfo[] = array('nodeid' => $node['nodeid'], 'nodetitle' => $node['title'], 'nodeusername' => $node['authorname'], 'nodeuserid' => $node['userid'], 'action' => $extra);
             }
             if (!in_array($node['parentid'], $cacheEvents)) {
                 $cacheEvents[] = $node['parentid'];
             }
             if (!in_array($node['starter'], $cacheEvents) and intval($node['starter'])) {
                 $cacheEvents[] = $node['starter'];
             }
         } else {
             throw new vB_Exception_Api('no_permission');
         }
     }
     if (empty($movedNodes)) {
         return false;
     }
     //can't move a node to its decendant, we like proper trees.
     $db = vB::getDbAssertor();
     $row = $db->getRow('vBForum:closure', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, 'parent' => $movedNodes, 'child' => $to_parent, vB_Db_Query::PARAM_LIMIT => 1));
     if (!empty($row)) {
         throw new vB_Exception_Api('move_node_to_child');
     }
     //back out counts for the nodes about to be moved.
     //keep track of the parentids to so we can update the last content after
     //we've moved things around.
     $lastContentParents = array();
     foreach ($movedNodes as $nodeid) {
         $node = $this->getNode($nodeid);
         $movedNodeParents = $this->getParents($nodeid);
         $parentids = array();
         foreach ($movedNodeParents as $movedNodeParent) {
             if ($movedNodeParent['nodeid'] != $nodeid) {
                 $parentids[] = $movedNodeParent['nodeid'];
                 $lastContentParents[] = $movedNodeParent['nodeid'];
             }
         }
         $this->updateAddRemovedNodeParentCounts($node, $node, $node['showpublished'], false, $parentids);
     }
     if ($parent['contenttypeid'] == $channelTypeid and $makeTopic) {
         if (empty($newtitle)) {
             if (!empty($firstTitle)) {
                 $newtitle = $firstTitle;
             } else {
                 throw new vB_Exception_Api('notitle');
             }
         }
         $newchildid = $movedNodes[0];
         $this->moveNodesInternal(array($newchildid), $newparent);
         // We need to promote give the new node the correct title
         vB::getDbAssertor()->assertQuery('vBForum:node', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_UPDATE, 'routeid' => vB_Api::instanceInternal('route')->getChannelConversationRoute($to_parent), 'title' => $newtitle, 'htmltitle' => vB_String::htmlSpecialCharsUni(vB_String::stripTags($newtitle), false), 'urlident' => vB_String::getUrlIdent($newtitle), 'description' => $newtitle, vB_dB_Query::CONDITIONS_KEY => array('nodeid' => $movedNodes[0])));
         if (count($movedNodes) > 1) {
             $grandchildren = array_slice($movedNodes, 1);
             $this->moveNodesInternal($grandchildren, $checkNodes[$newchildid]);
         }
         //moving the grandchildren under the first node may have changed it's last child info
         $db->assertQuery('vBForum:updateLastData', array('parentid' => $newchildid, 'timenow' => vB::getRequest()->getTimeNow()));
         $node = $this->getNode($newchildid);
         $this->updateSubTreePublishStatus($node, $newparent['showpublished']);
     } else {
         $this->moveNodesInternal($movedNodes, $newparent);
         foreach ($movedNodes as $nodeid) {
             $node = $this->getNode($nodeid);
             $this->updateSubTreePublishStatus($node, $newparent['showpublished']);
         }
     }
     //dedup the array (so we don't update a node more than once) but we need to
     //do so in a particular way.  We always want to keep the *last* occurance
     //of any id in the array.  This ensures that a node is not updated before
     //any of its decendants (which could cause bad results).
     $seen = array();
     foreach ($lastContentParents as $key => $parentid) {
         if (isset($seen[$parentid])) {
             unset($lastContentParents[$seen[$parentid]]);
         }
         $seen[$parentid] = $key;
     }
     //we can't do this before we move the nodes because the parent/child relationships haven't
     //changed yet.
     foreach ($lastContentParents as $parentid) {
         $this->fixNodeLast($parentid);
     }
     $userid = vB::getCurrentSession()->get('userid');
     // afterMove requires some ancestors info which we just changed above, let's clear cache before updating
     $searchAPI = vB_Api::instanceInternal('search');
     foreach ($movedNodes as $nodeid) {
         vB_Cache::instance()->allCacheEvent('nodeChg_' . $nodeid);
         //some search information may have changed, let's check
         $searchAPI->attributeChanged($nodeid);
     }
     // Leave a thread redirect if required
     // Note: UI only allows leaving a redirect when moving a thread which is one node
     if (!empty($leaveRedirectData) and count($movedNodes) == 1 and count($nodes) == 1) {
         $node = reset($nodes);
         $redirectData = array('title' => $node['title'], 'urlident' => $node['urlident'], 'parentid' => $node['parentid'], 'tonodeid' => $node['nodeid'], 'userid' => $node['userid'], 'publishdate' => $node['publishdate'], 'created' => $node['created']);
         // handle expiring redirects
         if (isset($leaveRedirectData['redirect']) and $leaveRedirectData['redirect'] == 'expires') {
             $period = (int) isset($leaveRedirectData['period']) ? $leaveRedirectData['period'] : 1;
             $frame = (string) isset($leaveRedirectData['frame']) ? $leaveRedirectData['frame'] : 'm';
             $period = max(min($period, 10), 1);
             $frame = in_array($frame, array('h', 'd', 'w', 'm', 'y'), true) ? $frame : 'm';
             $frames = array('h' => 3600, 'd' => 86400, 'w' => 86400 * 7, 'm' => 86400 * 30, 'y' => 86400 * 365);
             $redirectData['unpublishdate'] = vB::getRequest()->getTimeNow() + $period * $frames[$frame];
         }
         // skip any text spam checks, because a redirect has no text to check.
         vB_Library::instance('content_redirect')->add($redirectData, array('skipSpamCheck' => true));
     }
     vB_Api::instance('Search')->purgeCacheForCurrentUser();
     vB_Library_Admin::logModeratorAction($loginfo, 'node_moved_by_x');
     $cacheEvents = array_unique(array_merge($movedNodes, $cacheEvents, array($to_parent)));
     $this->clearChildCache($cacheEvents);
     $this->clearCacheEvents($cacheEvents);
     if ($needRebuild) {
         vB::getUserContext()->rebuildGroupAccess();
         vB_Channel::rebuildChannelTypes();
     }
     return $movedNodes;
 }
Example #2
0
 print_description_row(construct_link_code($vbphrase['restart'], "modlog.php?" . vB::getCurrentSession()->get('sessionurl') . ""), 0, 5, 'thead', vB_Template_Runtime::fetchStyleVar('right'));
 print_table_header(construct_phrase($vbphrase['moderator_log_viewer_page_x_y_there_are_z_total_log_entries'], vb_number_format($vbulletin->GPC['pagenumber']), vb_number_format($totalpages), vb_number_format($counter['total'])), 6);
 $headings = array();
 $headings[] = $vbphrase['id'];
 $headings[] = "<a href=\"modlog.php?" . vB::getCurrentSession()->get('sessionurl') . "do=view&modaction=" . $vbulletin->GPC['modaction'] . "&u=" . $vbulletin->GPC['userid'] . "&pp=" . $vbulletin->GPC['perpage'] . "&orderby=user&page=" . $vbulletin->GPC['pagenumber'] . "\">" . str_replace(' ', '&nbsp;', $vbphrase['username']) . "</a>";
 $headings[] = "<a href=\"modlog.php?" . vB::getCurrentSession()->get('sessionurl') . "do=view&modaction=" . $vbulletin->GPC['modaction'] . "&u=" . $vbulletin->GPC['userid'] . "&pp=" . $vbulletin->GPC['perpage'] . "&orderby=date&page=" . $vbulletin->GPC['pagenumber'] . "\">" . $vbphrase['date'] . "</a>";
 $headings[] = "<a href=\"modlog.php?" . vB::getCurrentSession()->get('sessionurl') . "do=view&modaction=" . $vbulletin->GPC['modaction'] . "&u=" . $vbulletin->GPC['userid'] . "&pp=" . $vbulletin->GPC['perpage'] . "&orderby=modaction&page=" . $vbulletin->GPC['pagenumber'] . "\">" . $vbphrase['action'] . "</a>";
 $headings[] = str_replace(' ', '&nbsp;', $vbphrase['ip_address']);
 print_cells_row($headings, 1, 0, -3);
 foreach ($logs as $log) {
     $cell = array();
     $cell[] = $log['moderatorlogid'];
     $cell[] = "<a href=\"user.php?" . vB::getCurrentSession()->get('sessionurl') . "do=edit&u={$log['userid']}\"><b>{$log['username']}</b></a>";
     $cell[] = '<span class="smallfont">' . vbdate($vbulletin->options['logdateformat'], $log['dateline']) . '</span>';
     if ($log['type']) {
         $phrase = vB_Library_Admin::GetModlogAction($log['type']);
         if (!$log['nodeid']) {
             // Pre vB5 logs
             if ($unserialized = @unserialize($log['action'])) {
                 array_unshift($unserialized, $vbphrase[$phrase]);
                 $action = call_user_func_array('construct_phrase', $unserialized);
             } else {
                 $action = construct_phrase($vbphrase[$phrase], $log['action']);
             }
             if ($log['threadtitle']) {
                 $action .= ', \'' . $log['threadtitle'] . '\'';
             }
         } else {
             // vB5 logs
             $temp = array();
             $logdata = @unserialize($log['action']);
Example #3
0
        } else {
            $product['title'] = htmlspecialchars_uni($product['title']);
        }
        if ($group_by == 'hook') {
            if ($hook['hookname'] != $prevgroup) {
                $prevgroup = $hook['hookname'];
                print_description_row($vbphrase['hook_location'] . ' : ' . $hook['hookname'], 0, 7, 'tfoot', '" style="text-align: left;');
            }
        } else {
            if ($product['title'] != $prevgroup) {
                $prevgroup = $product['title'];
                print_description_row($vbphrase['product'] . ' : ' . $product['title'], 0, 7, 'tfoot', '" style="text-align: left;');
            }
        }
        if (!$product['active']) {
            $product['title'] = '<strike>' . $product['title'] . '</strike>';
        }
        $title = htmlspecialchars_uni($hook['title']);
        $title = ($hook['active'] and $product['active']) ? $title : "<strike>{$title}</strike>";
        print_cells_row(array(vB_Library_Admin::buildElementCell('hook' . $hook['hookid'], $title, 0, false, 'hook.php', 'edit&amp;hookid=' . $hook['hookid'], vB::getCurrentSession()->get('sessionurl')), vB_Library_Admin::buildDisplayCell($group_by == 'hook' ? $product['title'] : $hook['hookname']), vB_Library_Admin::buildTextInputCell('order[' . $hook['hookid'] . ']', $hook['hookorder'], 1, $title = 'Execution Order'), vB_Library_Admin::buildDisplayCell($hook['template']), vB_Library_Admin::buildDisplayCell($hook['arguments'] ? 'Yes' : 'No'), vB_Library_Admin::buildCheckboxCell('active[' . $hook['hookid'] . ']', 1, 'hook' . $hook['hookid'], $hook['active'], false, false, false), vB_Library_Admin::buildElementCell('edit' . $hook['hookid'], '[' . $vbphrase['edit'] . ']', 0, false, 'hook.php', 'edit&amp;hookid=' . $hook['hookid'], vB::getCurrentSession()->get('sessionurl')) . vB_Library_Admin::buildElementCell('delete' . $hook['hookid'], '[' . $vbphrase['delete'] . ']', 0, false, 'hook.php', 'delete&amp;hookid=' . $hook['hookid'], vB::getCurrentSession()->get('sessionurl'))));
    }
    print_submit_row($vbphrase['save_status'], false, 7);
    echo '<p align="center">' . vB_Library_Admin::buildElementCell('', '[' . $vbphrase['add_new_hook'] . ']', 0, false, 'hook.php', 'add', vB::getCurrentSession()->get('sessionurl')) . '</p>';
}
print_cp_footer();
/*=========================================================================*\
|| #######################################################################
|| # Downloaded: 15:45, Tue Sep 8th 2015
|| # CVS: $RCSfile$ - $Revision: 83435 $
|| #######################################################################
\*=========================================================================*/
Example #4
0
 /**
  * Fetches the string associated with a moderator log action integer value
  *
  * @param	integer	The moderator log action
  *
  * @return	string
  */
 public static function GetModlogAction($logtype)
 {
     if (empty(self::$modlogactions)) {
         self::$modlogactions = array_flip(self::$modlogtypes);
     }
     return !empty(self::$modlogactions[$logtype]) ? self::$modlogactions[$logtype] : '';
 }
Example #5
0
            if (isset($extn['__failed'])) {
                $result = each($extn['__failed']);
                $failed[] = array('dir' => $result['key'], 'file' => $result['value'][0]);
                continue;
            }
            if ($product != $extn['product']) {
                print_description_row($vbphrase['package'] . ': ' . ucfirst(strtolower($extn['package'])), false, 7, 'boldrow');
                $product = $extn['product'];
            }
            if (!$vbulletin->options['enablehooks'] or defined('DISABLE_HOOKS')) {
                $extn['enabled'] = false;
            }
            $title = htmlspecialchars_uni($extn['title']);
            $title = $extn['enabled'] ? $title : "<strike>{$title}</strike>";
            $enabled = $extn['enabled'] ? $vbphrase['yes'] : ($extn['dependancy'] ? $vbphrase['no_dep'] : $vbphrase['no']);
            print_cells_row(array(vB_Library_Admin::buildElementCell('', $title, 0, false, '', '', '', $extn['version'], $extn['developer']), vB_Library_Admin::buildDisplayCell($extn['class']), vB_Library_Admin::buildDisplayCell($enabled, $extn['enabled'] and $extn['compatible']), vB_Library_Admin::buildDisplayCell($extn['minver']), vB_Library_Admin::buildDisplayCell($extn['maxver']), vB_Library_Admin::buildDisplayCell($extn['compatible'] ? $vbphrase['yes'] : $vbphrase['no'], $extn['compatible']), vB_Library_Admin::buildDisplayCell($extn['order'])), false, false, 0.5);
        }
        if (!empty($failed)) {
            print_table_break();
            print_description_row($vbphrase['failed_extensions'], false, 2, 'boldrow');
            print_label_row($vbphrase['directory'], $vbphrase['filename_gcpglobal'], 'boldrow');
            foreach ($failed as $info) {
                print_label_row($info['dir'], $info['file']);
            }
        }
    } else {
        print_description_row($vbphrase['no_extensions'], false, 7, 'boldrow');
    }
    print_table_footer();
}
print_cp_footer();
Example #6
0
 /**
  * Fetches the moderator logs for a node
  * @param int $nodeid
  * @return array $logs list of log records
  */
 public function fetchModLogs($nodeid)
 {
     if (!vB::getUserContext()->getChannelPermission('moderatorpermissions', 'caneditthreads', $nodeid)) {
         $node = $this->getNode($nodeid);
         if ($node['userid'] != vB::getCurrentSession()->get('userid')) {
             throw new vB_Exception_Api('no_permission');
         } else {
             return array();
         }
     }
     $logs = array();
     $log_res = vB::getDbAssertor()->assertQuery('getModLogs', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_STORED, 'nodeid' => $nodeid));
     foreach ($log_res as $log) {
         $phrase_name = vB_Library_Admin::GetModlogAction($log['type']);
         $phrase = vB_Api::instanceInternal('phrase')->fetch($phrase_name);
         if (!isset($phrase[$phrase_name])) {
             continue;
         }
         $phrase = $phrase[$phrase_name];
         $log['action'] = vsprintf($phrase, $log['username']);
         $logs[] = $log;
     }
     return $logs;
 }
Example #7
0
 /**
  * updates a record
  *
  *	@param	mixed		array of nodeid's
  *	@param	mixed		array of permissions that should be checked.
  *
  * 	@return	boolean
  */
 public function update($nodeid, $data)
 {
     $channelContentTypeId = vB_Types::instance()->getContentTypeId('vBForum_Channel');
     // Verify prefixid
     if ($this->contenttypeid != $channelContentTypeId and isset($data['prefixid'])) {
         $this->verifyPrefixid($data['prefixid']);
     } else {
         // Channel can't have a prefix
         unset($data['prefixid']);
     }
     // Verify post iconid
     if ($this->contenttypeid != $channelContentTypeId and isset($data['iconid'])) {
         $this->verifyPostIconid($data['iconid']);
     } else {
         // Channels can't have a post icon
         unset($data['iconid']);
     }
     $timeNow = vB::getRequest()->getTimeNow();
     $userContext = vB::getUserContext();
     //If this user doesn't have the featured permission and they are trying to set it,
     //Let's just quietly unset it.
     if (isset($data['featured'])) {
         if (!$userContext->getChannelPermission('moderatorpermissions', 'cansetfeatured', $data['parentid'])) {
             unset($data['featured']);
         }
     }
     //We can't allow directly setting parentid. That should only happen through the node api move function
     //And there are number of other fields that shouldn't be changed here. We have methods for the ones that can be changed at all.
     foreach (array('open', 'showopen', 'approved', 'showapproved', 'protected') as $field) {
         if (isset($data[$field])) {
             unset($data[$field]);
         }
     }
     if (isset($data['parentid'])) {
         //Only allow for articles.
         $content = $this->nodeApi->getNodeFullContent($nodeid);
         $content = array_pop($content);
         // you can't move it to the category it's already in
         if ($data['parentid'] != $content['parentid']) {
             // only allow this for articles (currently)
             if ($content['channeltype'] == 'article') {
                 if (!$userContext->getChannelPermission('forumpermissions', 'canmove', $data['parentid']) and !$userContext->getChannelPermission('moderatorpermissions', 'canmassmove', $data['parentid'])) {
                     throw new vB_Exception_Api('no_permission');
                 }
                 //If we got here, we're O.K. to move. let's do that now.
                 vB_Library::instance('node')->moveNodes($nodeid, $data['parentid']);
             }
         }
         unset($data['parentid']);
     }
     //We need to see if we need to update.
     $prior = vB_Library::instance('node')->getNodeBare($nodeid);
     if ($this->contenttypeid != $channelContentTypeId) {
         $content = $this->getFullContent($nodeid);
     }
     if (isset($data['publish_now']) and !empty($data['publish_now'])) {
         $data['publishdate'] = vB::getRequest()->getTimeNow();
     }
     if (empty($data['htmltitle']) and !empty($data['title'])) {
         $data['htmltitle'] = vB_String::htmlSpecialCharsUni(vB_String::stripTags($data['title']), false);
     }
     if (empty($data['urlident']) and !empty($data['title'])) {
         $data['urlident'] = vB_String::getUrlIdent($data['title']);
     }
     // Do not change publishdate or showpublished status unless it was explicitly set while calling update().
     if ((!isset($data['publishdate']) or empty($data['publishdate']) and $data['publishdate'] !== 0) and !empty($prior['publishdate'])) {
         $data['publishdate'] = $prior['publishdate'];
     }
     if ($this->isPublished($data)) {
         $published = 1;
     } else {
         $published = 0;
     }
     $nodevals = array();
     if ($published != $prior['showpublished']) {
         $nodevals['showpublished'] = $published;
     }
     // set default node options
     if ((empty($data['nodeoptions']) or !is_numeric($data['nodeoptions'])) and $prior['contenttypeid'] != $channelContentTypeId) {
         $parentFullContent = vB_Library::instance('node')->getNodeFullContent($prior['parentid']);
         if (!empty($parentFullContent[$prior['parentid']]['channeltype'])) {
             $data['nodeoptions'] = self::$defaultNodeOptions[$parentFullContent[$prior['parentid']]['channeltype']];
         } else {
             $data['nodeoptions'] = self::$defaultNodeOptions['default'];
         }
         // Add or remove any nodeoptions that have been explicitly passed in.
         // This would have otherwise happened in updateNodeOptions/setNodeOptions
         // (which is where it happens when adding a node as opposed to updating
         // a node), but since $data['nodeoptions'] is now defined, setNodeOptions
         // won't take care of setting these (it will just apply the int
         // nodeoptions value).
         $baseNodeOptions = vB_Api::instanceInternal('node')->getOptions();
         foreach ($baseNodeOptions as $baseOptionKey => $baseOptionVal) {
             if (isset($data[$baseOptionKey])) {
                 if (intval($data[$baseOptionKey])) {
                     $data['nodeoptions'] = $data['nodeoptions'] | intval($baseOptionVal);
                 } else {
                     $data['nodeoptions'] = $data['nodeoptions'] & ~intval($baseOptionVal);
                 }
             }
         }
     }
     //node table data.
     $data[vB_dB_Query::TYPE_KEY] = vB_dB_Query::QUERY_UPDATE;
     $data['nodeid'] = $nodeid;
     $data['lastupdate'] = $timeNow;
     //If the field passed is in the $nodeFields array then we update the node table.
     foreach ($data as $field => $value) {
         if (in_array($field, $this->nodeFields)) {
             $nodevals[$field] = $value;
         }
     }
     $index = empty($data['noIndex']);
     unset($data['noIndex']);
     // Update the content-type specific data
     if (!is_array($this->tablename)) {
         $tables = array($this->tablename);
     } else {
         $tables = $this->tablename;
     }
     $success = true;
     foreach ($tables as $table) {
         $structure = $this->assertor->fetchTableStructure('vBForum:' . $table);
         if (empty($structure) or empty($structure['structure'])) {
             throw new vB_Exception_Api('invalid_query_parameters');
         }
         $queryData = array();
         $queryData[vB_dB_Query::TYPE_KEY] = vB_dB_Query::QUERY_UPDATE;
         $queryData['nodeid'] = $nodeid;
         foreach ($structure['structure'] as $fieldname) {
             if (isset($data[$fieldname])) {
                 $queryData[$fieldname] = $data[$fieldname];
             }
         }
         //Now we have at least a query type and a nodeid. We put those in above. So if we don't
         //have at least one other value there's no reason to try an update.
         if (count($queryData) > 2) {
             $success = $success and $this->assertor->assertQuery('vBForum:' . $table, $queryData);
         }
     }
     if ($success) {
         // Handle Attachments
         // The text library (and most derivatives) can have attachments,
         // for the others, this is a no-op.
         $this->handleAttachments('update', $nodeid, $data);
         //Clear cached query info that would be significantly impacted
         $events = array('fUserContentChg_' . $prior['userid']);
         if ($prior['starter']) {
             $starterNodeInfo = vB_Library::instance('node')->getNodeBare($prior['starter']);
             $events[] = 'fUserContentChg_' . $starterNodeInfo['userid'];
         } else {
             if ($prior['parentid']) {
                 $starterNodeInfo = vB_Library::instance('node')->getNodeBare($prior['parentid']);
                 $events[] = 'fUserContentChg_' . $starterNodeInfo['userid'];
             }
         }
         $this->nodeApi->clearCacheEvents($nodeid);
         vB_Cache::instance()->allCacheEvent($events);
         if (isset($nodevals['publishdate']) and $nodevals['publishdate'] > $timeNow) {
             if (empty($nodevals['unpublishdate']) or $nodevals['unpublishdate'] > $nodevals['publishdate']) {
                 $nodevals['nextupdate'] = $nodevals['publishdate'];
             }
         } else {
             if (isset($nodevals['unpublishdate']) and $nodevals['unpublishdate'] > $timeNow) {
                 $nodevals['nextupdate'] = $nodevals['unpublishdate'];
             }
         }
         // handle approved
         if (isset($nodevals['approved'])) {
             if ($nodevals['approved']) {
                 $approved = 1;
                 $queryName = 'approveNode';
             } else {
                 $approved = 0;
                 $queryName = 'unapproveNode';
             }
             // set approved to parent...
             $this->assertor->assertQuery('vBForum:node', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_UPDATE, 'nodeid' => $nodeid, 'approved' => $approved));
             // and handle showapproved
             $this->assertor->assertQuery('vBForum:' . $queryName, array('nodeid' => $nodeid));
             unset($nodevals['approved']);
         }
         if (isset($nodevals)) {
             $nodevals[vB_dB_Query::TYPE_KEY] = vB_dB_Query::QUERY_UPDATE;
             $nodevals['nodeid'] = $nodeid;
             $success = $this->assertor->assertQuery('vBForum:node', $nodevals);
         }
         //We need to compare the current publishdate and unpublishdate values against the
         // parent.
         //But we can skip this if neither publish or unpublishdate is set
         $updateParents = false;
         if ($published != $prior['showpublished']) {
             $updateParents = true;
             //We are concerned about two possibilities. It could have gone from published to unpublished.
             //In either case we change by totalcount +1 (for ourselves.
             //Remember that published is always unpublished.
             //From unpublished to published.
             if ($published) {
                 $nodeUpdates = $this->nodeLibrary->publishChildren($nodeid);
                 // if $nodeUpdates is empty, that means no change was made to this node or its descendants,
                 // and no parent count changes are necessary. If it's not empty but doesn't have totalcount set,
                 // that means it possibly failed with a DB error. In such a case, we will just not update the
                 // counts but continue updating the node.
                 if (!empty($nodeUpdates) and isset($nodeUpdates['totalcount'])) {
                     // text-counts only change by 1 (or 0 for non-text types), because it only affects the immediate parent
                     $textChange = $this->textCountChange;
                     $textUnPubChange = -1 * $textChange;
                     // Note, below assumes that a DB had been diligent about
                     // keeping track of the count fields correctly.
                     $totalPubChange = $nodeUpdates['totalcount'] - $prior['totalcount'] + $textChange;
                     // we add the text change because self counts for ancestors' total counts
                     $totalUnPubChange = -1 * $totalPubChange;
                 } else {
                     $updateParents = false;
                 }
             } else {
                 $nodeUpdates = $this->nodeLibrary->unpublishChildren($nodeid);
                 if (!empty($nodeUpdates) and isset($nodeUpdates['totalunpubcount'])) {
                     $textUnPubChange = $this->textCountChange;
                     $textChange = -1 * $textUnPubChange;
                     $totalUnPubChange = $nodeUpdates['totalunpubcount'] - $prior['totalunpubcount'] + $textUnPubChange;
                     $totalPubChange = -1 * $totalUnPubChange;
                 } else {
                     $updateParents = false;
                 }
             }
             vB_Library::instance('node')->clearChildCache($nodeid);
         }
         //update the parent count if necessary
         if ($updateParents) {
             vB_Library::instance('node')->updateParentCounts($nodeid, $textChange, $textUnPubChange, $totalPubChange, $totalUnPubChange, $published);
         }
         //update viewperms from childs if needed, do we want this channel specific?
         if (isset($nodevals['viewperms']) and isset($prior['viewperms']) and $nodevals['viewperms'] != $prior['viewperms']) {
             vB_Api::instanceInternal('node')->setNodePerms($nodeid, array('viewperms' => $nodevals['viewperms']));
         }
         if ($index) {
             vB_Api::instanceInternal('Search')->index($nodeid);
         }
         // update user tags
         $tags = !empty($data['tags']) ? explode(',', $data['tags']) : array();
         $tagRet = vB_Api::instanceInternal('tags')->updateUserTags($nodeid, $tags);
         $this->updateNodeOptions($nodeid, $data);
         // Update childs nodeoptions
         $this->assertor->assertQuery('vBForum:updateChildsNodeoptions', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_STORED, 'parentid' => $nodeid));
         $this->nodeApi->clearCacheEvents(array($nodeid, $prior['parentid']));
         $loginfo = array('nodeid' => $prior['nodeid'], 'nodetitle' => $prior['title'], 'nodeusername' => $prior['authorname'], 'nodeuserid' => $prior['userid']);
         $extra = array();
         if ($nodevals !== null && isset($nodevals['title'])) {
             if ($prior['title'] != $nodevals['title']) {
                 $extra = array('newtitle' => $nodevals['title']);
             }
         }
         vB_Library_Admin::logModeratorAction($loginfo, 'node_edited_by_x', $extra);
         $updateEditLog = true;
         if (!vB::getUserContext()->hasPermission('genericoptions', 'showeditedby') and (isset($content[$nodeid]['edit_reason']) and $data['reason'] == $content[$nodeid]['edit_reason'] or !isset($content[$nodeid]['edit_reason']) and empty($data['reason']))) {
             $updateEditLog = false;
         }
         // Clear autosave table of this items entry
         if (vB::getCurrentSession()->get('userid') and !empty($data['rawtext'])) {
             $this->assertor->delete('vBForum:autosavetext', array('userid' => vB::getCurrentSession()->get('userid'), 'nodeid' => $nodeid, 'parentid' => $content[$nodeid]['parentid']));
         }
         // Log edit by info
         if ($updateEditLog and $this->contenttypeid != $channelContentTypeId and isset($content[$nodeid]['rawtext']) and isset($data['rawtext']) and $content[$nodeid]['rawtext'] != $data['rawtext'] and !empty($data['publishdate']) and $prior['publishdate'] and (!empty($data['reason']) or $data['publishdate'] < vB::getRequest()->getTimeNow() - $this->options['noeditedbytime'] * 60)) {
             $userinfo = vB::getCurrentSession()->fetch_userinfo();
             // save the postedithistory
             if ($this->options['postedithistory']) {
                 $record = $this->assertor->getRow('vBForum:postedithistory', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, 'original' => 1, 'nodeid' => $nodeid));
                 // insert original post on first edit
                 if (empty($record)) {
                     $this->assertor->assertQuery('vBForum:postedithistory', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_INSERT, 'nodeid' => $nodeid, 'userid' => $content[$nodeid]['userid'], 'username' => $content[$nodeid]['authorname'], 'dateline' => $data['publishdate'], 'pagetext' => $content[$nodeid]['rawtext'], 'original' => 1));
                 }
                 // insert the new version
                 $this->assertor->assertQuery('vBForum:postedithistory', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_INSERT, 'nodeid' => $nodeid, 'userid' => $userinfo['userid'], 'username' => $userinfo['username'], 'dateline' => vB::getRequest()->getTimeNow(), 'reason' => isset($data['reason']) ? vB5_String::htmlSpecialCharsUni($data['reason']) : '', 'pagetext' => isset($data['rawtext']) ? $data['rawtext'] : ''));
             }
             $this->assertor->assertQuery('editlog_replacerecord', array('nodeid' => $nodeid, 'userid' => $userinfo['userid'], 'username' => $userinfo['username'], 'timenow' => vB::getRequest()->getTimeNow(), 'reason' => isset($data['reason']) ? vB5_String::htmlSpecialCharsUni($data['reason']) : '', 'hashistory' => intval($this->options['postedithistory'])));
         }
         return true;
     }
     $this->nodeApi->clearCacheEvents(array($nodeid, $prior['parentid']));
     return false;
 }