コード例 #1
0
ファイル: akismet.php プロジェクト: cedwards-reisys/nexus-web
 /**
  *	Enforces singleton use
  *
  *
  ***/
 public static function instance()
 {
     if (empty(self::$instance)) {
         self::$instance = new vB_Akismet();
     }
     return self::$instance;
 }
コード例 #2
0
 /**
  * This is a pre_save method that only applies to the subclasses that have post
  * fields as their members (ie, not _Thread). Likely only called in those class's
  * pre_save methods.
  *
  * @return	bool	True on success, false on failure
  */
 function pre_save_post($doquery = true)
 {
     if ($this->info['forum']['podcast'] and $this->info['podcasturl'] and empty($this->info['podcastsize'])) {
         require_once DIR . '/includes/class_upload.php';
         $upload = new vB_Upload_Abstract($this->registry);
         if (!($this->info['podcastsize'] = intval($upload->fetch_remote_filesize($this->info['podcasturl'])))) {
             $this->error('invalid_podcasturl');
             return false;
         }
     }
     if (!$this->condition) {
         if ($this->fetch_field('userid', 'post') == 0 and $this->fetch_field('username', 'post') == '') {
             $this->error('nousername');
             return false;
         }
         if ($this->fetch_field('dateline', 'post') === null) {
             $this->set('dateline', TIMENOW);
         }
         if ($this->fetch_field('ipaddress', 'post') === null) {
             $this->set('ipaddress', $this->registry->options['logip'] ? IPADDRESS : '');
         }
         // flood check
         if ($this->registry->options['floodchecktime'] > 0 and empty($this->info['preview']) and empty($this->info['is_automated']) and $this->fetch_field('userid', 'post')) {
             if (!$this->info['user']) {
                 $this->info['user'] = fetch_userinfo($this->fetch_field('userid', 'post'));
             }
             $user =& $this->info['user'];
             if ($user['lastpost'] <= TIMENOW and !can_moderate($this->info['forum']['forumid'], '', $user['userid'], $user['usergroupid'] . (trim($user['membergroupids']) ? ",{$user['membergroupids']}" : ''))) {
                 if (!class_exists('vB_FloodCheck')) {
                     require_once DIR . '/includes/class_floodcheck.php';
                 }
                 $this->floodcheck =& new vB_FloodCheck($this->registry, 'user', 'lastpost');
                 $this->floodcheck->commit_key($this->registry->userinfo['userid'], TIMENOW, TIMENOW - $this->registry->options['floodchecktime']);
                 if ($this->floodcheck->is_flooding()) {
                     $this->error('postfloodcheck', $this->registry->options['floodchecktime'], $this->floodcheck->flood_wait());
                     return false;
                 }
                 if ($this->errors) {
                     // if we already have errors, the save won't happen, so rollback now...
                     $this->floodcheck->rollback();
                 } else {
                     // ...or, in case we have a new error
                     $this->set_failure_callback(array(&$this->floodcheck, 'rollback'));
                 }
             }
         }
     }
     if (!$this->verify_image_count('pagetext', 'allowsmilie', $this->info['forum']['forumid'], 'post')) {
         return false;
     }
     if ($this->info['posthash']) {
         $this->info['newattach'] = $this->fetch_attachment_count($this->info['posthash'], $this->fetch_field('userid', 'post'));
         $this->set('attach', intval($this->fetch_field('attach')) + $this->info['newattach']);
     }
     // New posts that aren't automated and are visible should be scanned
     if (!$this->condition and !empty($this->registry->options['vb_antispam_key']) and empty($this->info['is_automated']) and $this->fetch_field('visible') == 1 and (!$this->registry->options['vb_antispam_posts'] or $this->registry->userinfo['posts'] < $this->registry->options['vb_antispam_posts']) and !can_moderate()) {
         require_once DIR . '/includes/class_akismet.php';
         $akismet = new vB_Akismet($this->registry);
         $akismet->akismet_board = $this->registry->options['bburl'];
         $akismet->akismet_key = $this->registry->options['vb_antispam_key'];
         if ($akismet->verify_text(array('user_ip' => IPADDRESS, 'user_agent' => USER_AGENT, 'comment_type' => 'post', 'comment_author' => $this->registry->userinfo['userid'] ? $this->registry->userinfo['username'] : $this->fetch_field('username', 'post'), 'comment_author_email' => $this->registry->userinfo['email'], 'comment_author_url' => $this->registry->userinfo['homepage'], 'comment_content' => $this->fetch_field('pagetext', 'post'))) === 'spam') {
             $this->set('visible', 0);
             $this->spamlog_insert = true;
         }
     }
     return true;
 }
コード例 #3
0
 /**
  * Code to run before saving
  *
  * @param	boolean Do the query?
  *
  * @return	boolean	Whether this code executed correctly
  *
  */
 function pre_save($doquery = true)
 {
     if ($this->presave_called !== null) {
         return $this->presave_called;
     }
     if (!$this->condition) {
         if ($this->fetch_field('state') === null) {
             $this->set('state', 'visible');
         }
         if ($this->fetch_field('dateline') === null) {
             $this->set('dateline', TIMENOW);
         }
         if ($this->fetch_field('ipaddress') === null) {
             $this->set('ipaddress', $this->registry->options['logip'] ? IPADDRESS : '');
         }
         if (!$this->info['preview']) {
             if ($this->registry->options['floodchecktime'] > 0 and empty($this->info['is_automated']) and $this->fetch_field('postuserid') and $this->is_flooding() or $this->is_duplicate()) {
                 return false;
             }
         }
         // Posting to own picture, lets assume we've read it
         if ($this->info['pictureuser']['userid'] and $this->info['pictureuser']['userid'] == $this->registry->userinfo['userid']) {
             $this->set('messageread', true);
         }
     }
     if (!$this->verify_image_count('pagetext', 'allowsmilie', 'socialmessage')) {
         return false;
     }
     // New posts that aren't automated and are visible should be scanned
     if (!$this->condition and !empty($this->registry->options['vb_antispam_key']) and empty($this->info['is_automated']) and $this->fetch_field('state') == 'visible' and (!$this->registry->options['vb_antispam_posts'] or $this->info['user']['posts'] < $this->registry->options['vb_antispam_posts']) and !can_moderate()) {
         require_once DIR . '/includes/class_akismet.php';
         $akismet = new vB_Akismet($this->registry);
         $akismet->akismet_board = $this->registry->options['bburl'];
         $akismet->akismet_key = $this->registry->options['vb_antispam_key'];
         if ($akismet->verify_text(array('user_ip' => IPADDRESS, 'user_agent' => USER_AGENT, 'comment_type' => 'post', 'comment_author' => $this->info['user']['userid'] ? $this->info['user']['username'] : $this->fetch_field('postusername'), 'comment_content' => $this->fetch_field('pagetext'))) === 'spam') {
             $this->set('state', 'moderation');
             $this->spamlog_insert = true;
         }
     }
     if (in_coventry($this->fetch_field('postuserid'), true)) {
         $this->set('messageread', true);
     }
     $return_value = true;
     ($hook = vBulletinHook::fetch_hook('picturecommentdata_presave')) ? eval($hook) : false;
     $this->presave_called = $return_value;
     return $return_value;
 }
コード例 #4
0
ファイル: inlinemod.php プロジェクト: holandacz/nb4
                 }
                 $user_dms[$userid]->pre_save();
             }
             foreach ($user_dms as $userdm) {
                 $userdm->save();
             }
             break;
         default:
             ($hook = vBulletinHook::fetch_hook('inlinemod_deletespam_defaultaction')) ? eval($hook) : false;
     }
 }
 // report
 if ($vbulletin->GPC['report'] and !empty($vbulletin->options['vb_antispam_key'])) {
     // report to Akismet
     require_once DIR . '/includes/class_akismet.php';
     $akismet = new vB_Akismet($vbulletin);
     $akismet->akismet_board = $vbulletin->options['bburl'];
     $akismet->akismet_key = $vbulletin->options['vb_antispam_key'];
     if ($vbulletin->GPC['type'] == 'thread') {
         $posts = $db->query_read("\n\t\t\t\tSELECT post.*, postlog.*\n\t\t\t\tFROM " . TABLE_PREFIX . "thread AS thread\n\t\t\t\tINNER JOIN " . TABLE_PREFIX . "post AS post ON (post.postid = thread.firstpostid)\n\t\t\t\tINNER JOIN " . TABLE_PREFIX . "postlog AS postlog ON (postlog.postid = post.postid)\n\t\t\t\tWHERE thread.threadid IN (" . implode(',', $threadids) . ")\n\t\t\t");
     } else {
         $posts = $db->query_read("\n\t\t\t\tSELECT post.*, postlog.*\n\t\t\t\tFROM " . TABLE_PREFIX . "post AS post\n\t\t\t\tINNER JOIN " . TABLE_PREFIX . "postlog AS postlog ON (postlog.postid = post.postid)\n\t\t\t\tWHERE post.postid IN (" . implode(',', $postids) . ")\n\t\t\t");
     }
     while ($post = $db->fetch_array($posts)) {
         $akismet->mark_as_spam(array('user_ip' => long2ip($post['ip']), 'user_agent' => $post['useragent'], 'comment_type' => 'post', 'comment_author' => $post['username'], 'comment_content' => $post['pagetext']));
     }
 }
 // delete threads that are defined explicitly as spam by being ticked
 $physicaldel = $vbulletin->GPC['deletetype'] == 2 ? true : false;
 $skipped_user_prune = array();
 if ($vbulletin->GPC['deleteother'] and !empty($user_cache) and can_moderate(-1, 'canmassprune')) {
コード例 #5
0
ファイル: node.php プロジェクト: cedwards-reisys/nexus-web
 /**
  * Sets or unsets the approved field
  * @param array $nodeids
  * @param boolean $approved - set or unset the approved field
  * @throws vB_Exception_Api
  * @return array - the nodeids that have the permission to be changed
  */
 public function setApproved($approveNodeIds, $approved = true)
 {
     if (empty($approveNodeIds)) {
         return false;
     }
     $loginfo = array();
     $nodeIds = array();
     foreach ($approveNodeIds as $idx => $id) {
         $nodeInfo = $this->getNode($id);
         if ($nodeInfo['deleteuserid']) {
             // Do not do approve/unapprove actions on deleted posts
             continue;
         }
         if (!empty($nodeInfo['errors'])) {
             continue;
         }
         if (!$nodeInfo['approved'] and !$approved) {
             continue;
         }
         if ($nodeInfo['approved'] and $approved) {
             continue;
         }
         $nodeIds[] = $nodeInfo['nodeid'];
         $loginfo[] = array('nodeid' => $nodeInfo['nodeid'], 'nodetitle' => $nodeInfo['title'], 'nodeusername' => $nodeInfo['authorname'], 'nodeuserid' => $nodeInfo['userid']);
     }
     if (empty($nodeIds)) {
         return false;
     }
     $errors = array();
     $assertor = vB::getDbAssertor();
     $result = $assertor->update('vBForum:node', array('approved' => $approved), array('nodeid' => $nodeIds));
     if (!empty($result['errors'])) {
         $errors[] = $result['errors'];
     }
     $method = empty($approved) ? 'unapproveNode' : 'approveNode';
     $result = $assertor->assertQuery('vBForum:' . $method, array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_STORED, 'nodeid' => $nodeIds));
     // Report as ham if this node was spam..
     if ($method == 'approveNode') {
         $vboptions = vB::getDatastore()->getValue('options');
         if ($vboptions['vb_antispam_type'] and $vboptions['vb_antispam_key']) {
             $spamids = array();
             $spamcheck = $assertor->getRows('spamlog', array('nodeid' => $nodeIds));
             foreach ($spamcheck as $spam) {
                 $spamids[] = $spam['nodeid'];
             }
             if ($spamids) {
                 $nodes = $this->getContentforNodes($spamids);
                 $akismet = vB_Akismet::instance();
                 foreach ($nodes as $node) {
                     if ($node['content']['rawtext']) {
                         $text = vB_String::stripBbcode($node['content']['rawtext'], true);
                         $akismet->markAsHam(array('comment_type' => 'comment', 'comment_author' => $node['content']['authorname'], 'comment_content' => $text, 'user_ip' => $node['content']['ipaddress']));
                     }
                 }
                 $assertor->delete('spamlog', array('nodeid' => $spamids));
             }
         }
     }
     if (!empty($result['errors'])) {
         $errors[] = $result['errors'];
     }
     $nodeIds = array_unique($nodeIds);
     $searchAPI = vB_Api::instanceInternal('Search');
     foreach ($nodeIds as $nodeid) {
         $node = $this->getNode($nodeid);
         $parent = $this->getNodeBare($node['parentid']);
         if ($node['showpublished']) {
             $nodeUpdates = $this->publishChildren($node['nodeid']);
         } else {
             $nodeUpdates = $this->unpublishChildren($node['nodeid']);
         }
         //we must update the last nodes for the subtree before handling the parents, otherwise it won't work.
         $this->updateLastForSubtree($nodeid);
         $this->updateChangedNodeParentCounts($node, $nodeUpdates);
         //$assertor->assertQuery('vBForum:updateLastData', array('parentid' => $nodeid, 'timenow' => vB::getRequest()->getTimeNow()));
         // Update the user post count (approve / unapprove)
         vB_Cache::allCacheEvent('nodeChg_' . $nodeid);
         if ($approved) {
             vB_Library_Content::getContentLib($node['contenttypeid'])->incrementUserPostCount($node);
         } else {
             vB_Library_Content::getContentLib($node['contenttypeid'])->decrementUserPostCount($node, 'unapprove');
         }
         $searchAPI->attributeChanged($node['nodeid']);
     }
     $this->clearCacheEvents($nodeIds);
     $this->clearChildCache($nodeIds);
     if (!empty($errors)) {
         return array('errors' => $errors);
     }
     vB_Library_Admin::logModeratorAction($loginfo, $approved ? 'node_approved_by_x' : 'node_unapproved_by_x');
     return $nodeIds;
 }
コード例 #6
0
ファイル: blog_functions.php プロジェクト: hungnv0789/vhtm
/**
* Verifies that an akismet key is valid
*
* @param	string	The akismet key to check for validity
* @param	string	The URL that the key is going to be used on
* @param	fields	Extra information that should be submitted to akismet
*
* @return	boolean	Returns true if the key is valid else false
*/
function verify_akismet_status($key, $url, $fields = array())
{
	global $vbulletin;

	require_once(DIR . '/includes/class_akismet.php');
	$akismet = new vB_Akismet($vbulletin);

	$akismet->akismet_key = $key;
	$akismet->akismet_board = $url;

	return $akismet->verify_text($fields);
}
コード例 #7
0
	/**
	 * Pre-Save code for a SG Message
	 *
	 * @param	boolean	Do we actually run the query?
	 *
	 * @return	boolean	Did this function run successfully?
	 */
	function pre_save($doquery = true)
	{
		if ($this->presave_called !== null)
		{
			return $this->presave_called;
		}

		if (!$this->condition)
		{
			if ($this->fetch_field('state') === null)
			{
				$this->set('state', 'visible');
			}

			if ($this->fetch_field('dateline') === null)
			{
				$this->set('dateline', TIMENOW);
			}

			if ($this->fetch_field('ipaddress') === null)
			{
				$this->set('ipaddress', ($this->registry->options['logip'] ? IPADDRESS : ''));
			}

			if (!$this->info['preview'])
			{
				if (($this->registry->options['floodchecktime'] > 0 AND empty($this->info['is_automated']) AND $this->fetch_field('postuserid') AND $this->is_flooding()) OR $this->is_duplicate())
				{
					return false;
				}
			}
		}

		if (!$this->verify_image_count('pagetext', 'allowsmilie', 'socialmessage'))
		{
			return false;
		}

		// New posts that aren't automated and are visible should be scanned
		if (!$this->condition AND !empty($this->registry->options['vb_antispam_key']) AND empty($this->info['is_automated']) AND $this->fetch_field('state') == 'visible' AND (!$this->registry->options['vb_antispam_posts'] OR $this->info['user']['posts'] < $this->registry->options['vb_antispam_posts']) AND !can_moderate())
		{
			require_once(DIR . '/includes/class_akismet.php');
			$akismet = new vB_Akismet($this->registry);
			$akismet->akismet_board = $this->registry->options['bburl'];
			$akismet->akismet_key = $this->registry->options['vb_antispam_key'];
			if ($akismet->verify_text(array('user_ip' => IPADDRESS, 'user_agent' => USER_AGENT, 'comment_type' => 'post', 'comment_author' => ($this->info['user']['userid'] ? $this->info['user']['username'] : $this->fetch_field('postusername')), 'comment_author_email' => $this->info['user']['email'], 'comment_author_url' => $this->info['user']['homepage'], 'comment_content' => $this->fetch_field('pagetext'))) === 'spam')
			{
				$this->set('state', 'moderation');
				$this->spamlog_insert = true;
			}
		}

		$return_value = true;
		($hook = vBulletinHook::fetch_hook('groupmessagedata_presave')) ? eval($hook) : false;

		$this->presave_called = $return_value;
		return $return_value;
	}
コード例 #8
0
	/**
	* Verifies the akismet key is 0-9a-z
	*
	* @param	string	Page text
	*
	* @param	bool	Whether the text is valid
	*/
	function verify_akismet(&$akismet_key)
	{
		if (!empty($akismet_key))
		{
			if (!preg_match('#^[a-z0-9]+$#i', $akismet_key))
			{
				$this->error('akismet_key_invalid', $akismet_key);
				return false;
			}

			require_once(DIR . '/includes/class_akismet.php');
			$akismet = new vB_Akismet($this->registry);

			$akismet->akismet_key = $akismet_key;
			$akismet->akismet_board = $this->registry->options['bburl'] . '/blog.php';

			if (!$akismet->_build())
			{
				$this->error('akismet_key_invalid', $akismet_key);
				return false;
			}
		}
		return true;
	}
コード例 #9
0
ファイル: text.php プロジェクト: cedwards-reisys/nexus-web
 /**
  * Adds a new node.
  *
  * @param	mixed		Array of field => value pairs which define the record.
  *  -- htmlstate
  *  -- parentid
  *  -- disable_bbcode
  *  -- rawtext
  *  -- and others
  * @param	array		Array of options for the content being created
  * 						Understands skipTransaction, skipFloodCheck, floodchecktime, skipDupCheck, skipNotification, nl2br, autoparselinks.
  *							- nl2br: if TRUE, all \n will be converted to <br /> so that it's not removed by the html parser (e.g. comments).
  * @param	bool		Convert text to bbcode
  *
  * 	@return	mixed		array with nodeid (int), success (bool), cacheEvents (array of strings), nodeVals (array of field => value), attachments (array of attachment records).
  */
 public function add($data, array $options = array(), $convertWysiwygTextToBbcode = true)
 {
     //Store this so we know whether we should call afterAdd()
     $skipTransaction = !empty($options['skipTransaction']);
     // html permission already checked in the api
     if (isset($data['htmlstate']) and $data['htmlstate'] == 'on' and isset($data['disable_bbcode']) and $data['disable_bbcode'] == 1) {
         // article 'static html' type
         $convertWysiwygTextToBbcode = false;
         if (isset($options['nl2br'])) {
             $options['nl2br'] = false;
         }
     }
     if (empty($data['parentid'])) {
         throw new Exception('need_parent_node');
     }
     // Get parents for cleaning cache and checking permissions
     $parents = vB_Library::instance('node')->getParents($data['parentid']);
     $parents = array_reverse($parents);
     // convert to bbcode for saving
     if (isset($data['rawtext']) and !empty($data['rawtext'])) {
         // Converts new lines when CKEditor is not in use (plain text area) VBV-11279
         // also used for the mobile app.
         if (isset($options['nl2br']) and $options['nl2br']) {
             $data['rawtext'] = nl2br($data['rawtext']);
         }
         if ($convertWysiwygTextToBbcode) {
             $channelType = vB_Types::instance()->getContentTypeId('vBForum_Channel');
             // check if we can autoparselinks
             $options['autoparselinks'] = true;
             foreach ($parents as $parent) {
                 // currently only groups and blogs seem to disallow this
                 if (($parent['contenttypeid'] == $channelType and vB_Api::instanceInternal('socialgroup')->isSGNode($parent['nodeid']) or vB_Api::instanceInternal('blog')->isBlogNode($parent['nodeid'])) and $channelOptions = vB_Library::instance('node')->getNodeOptions($parent['nodeid'])) {
                     $options['autoparselinks'] = $channelOptions['autoparselinks'];
                 }
             }
             $data['rawtext'] = vB_Api::instanceInternal('bbcode')->convertWysiwygTextToBbcode($data['rawtext'], $options);
             if (empty($data['description'])) {
                 $data['description'] = vB_String::getPreviewText($this->parseAndStrip($data['rawtext']));
             } else {
                 $data['description'] = vB_String::getPreviewText($this->parseAndStrip($data['description']));
             }
         }
     } else {
         if (empty($data['description'])) {
             $data['description'] = isset($data['title']) ? vB_String::getPreviewText($this->parseAndStrip($data['title'])) : '';
         } else {
             $data['description'] = vB_String::getPreviewText($this->parseAndStrip($data['description']));
         }
     }
     if (empty($data['userid'])) {
         $user = vB::getCurrentSession()->fetch_userinfo();
         $data['authorname'] = $user['username'];
         $userid = $data['userid'] = $user['userid'];
     } else {
         $userid = $data['userid'];
         if (empty($data['authorname'])) {
             $data['authorname'] = vB_Api::instanceInternal('user')->fetchUserName($userid);
         }
         $user = vB_Api::instance('user')->fetchUserinfo($userid);
     }
     $userContext = vB::getUserContext($userid);
     $isSpam = false;
     $skipSpam = ($userContext->getChannelPermission('forumpermissions2', 'exemptfromspamcheck', $data['parentid']) or $userContext->getChannelPermission('moderatorpermissions', 'canmoderateposts', $data['parentid']) or isset($options['skipSpamCheck']) and $options['skipSpamCheck'] and vB::getUserContext()->getChannelPermission('moderatorpermissions', 'canmoderateposts', $data['parentid']));
     //run the spam check.
     if (!$skipSpam and $this->spamType !== false) {
         if (empty($this->akismet)) {
             $this->akismet = vB_Akismet::instance();
         }
         $params = array('comment_type' => 'user_post', 'comment_content' => $data['rawtext']);
         $params['comment_author'] = $data['authorname'];
         $params['comment_author_email'] = $user['email'];
         $result = $this->akismet->verifyText($params);
         if ($result == 'spam') {
             $data['approved'] = 0;
             $data['showapproved'] = 0;
             $isSpam = true;
         }
     }
     if (!$skipSpam and !$isSpam and $blacklist = trim($this->options['vb_antispam_badwords'])) {
         $badwords = preg_split('#\\s+#', $blacklist, -1, PREG_SPLIT_NO_EMPTY);
         if (str_replace($badwords, '', strtolower($data['rawtext'])) != strtolower($data['rawtext'])) {
             $data['approved'] = 0;
             $data['showapproved'] = 0;
             $isSpam = true;
         }
     }
     if (!$skipSpam and !$isSpam) {
         preg_match_all('#\\[(url|email).*\\[/(\\1)\\]#siU', $data['rawtext'], $matches);
         if (isset($matches[0]) and count($matches[0]) > intval($this->options['vb_antispam_maxurl'])) {
             $data['approved'] = 0;
             $data['showapproved'] = 0;
             $isSpam = true;
         }
     }
     //We need a copy of the data, maybe
     $updates = array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_STORED);
     //Set the "hasvideo" value;
     if (!empty($data['rawtext'])) {
         $filter = '~\\[video.*\\[\\/video~i';
         $matches = array();
         $count = preg_match_all($filter, $data['rawtext'], $matches);
         if ($count > 0) {
             $data['hasvideo'] = 1;
         } else {
             $data['hasvideo'] = 0;
         }
     }
     //publishdate is set in the parent class and api. If not set in data, it'll be set to vB::getRequest()->getTimeNow() in parent::add()
     if (isset($data['publishdate'])) {
         $updates['lastcontent'] = $data['publishdate'];
     } else {
         $updates['lastcontent'] = vB::getRequest()->getTimeNow();
     }
     if (isset($data['userid'])) {
         $updates['lastauthorid'] = $data['userid'];
     } else {
         $updates['lastauthorid'] = $data['userid'] = vB::getCurrentSession()->get('userid');
     }
     if (isset($data['authorname'])) {
         $updates['lastcontentauthor'] = $data['authorname'];
     } else {
         $author = $this->assertor->getRow('user', array(vB_Db_Query::TYPE_KEY => vB_dB_Query::QUERY_SELECT, 'userid' => $data['userid']));
         $data['authorname'] = $author['username'];
         $updates['lastcontentauthor'] = $author['username'];
     }
     $published = $this->isPublished($data);
     try {
         if (!$skipTransaction) {
             $this->assertor->beginTransaction();
         }
         $options['skipTransaction'] = true;
         $results = parent::add($data, $options);
         $newNode = $this->getFullContent($results['nodeid']);
         $newNode = array_pop($newNode);
         // Obtain and set generic conversation route
         $conversation = $this->getConversationParent($results['nodeid']);
         $routeid = vB_Api::instanceInternal('route')->getChannelConversationRoute($conversation['parentid']);
         $this->assertor->update('vBForum:node', array('routeid' => $routeid), array('nodeid' => $results['nodeid']));
         if (!$skipTransaction) {
             $this->assertor->commitTransaction();
         }
     } catch (exception $e) {
         if (!$skipTransaction) {
             $this->assertor->rollbackTransaction();
         }
         throw $e;
     }
     //set the last post and count data.
     /* 	We do something similar (vBForum:fixNodeLast) that ends up affect parent last data in the parent add(),
      *	downstream of updateParentCounts(). We should probably refactor to just do it in one place. I tried to
      *	comment this section out, but the node test told me that they're not *quite* the same and this is
      *	necessary.
      */
     $approved = (isset($data['showapproved']) ? $data['showapproved'] : true and isset($data['approved']) ? $data['approved'] : true);
     if ($published and $approved and (!isset($options['skipUpdateLastContent']) or !$options['skipUpdateLastContent'])) {
         $updates['nodeid'] = $results['nodeid'];
         $updates['lastcontentid'] = $results['nodeid'];
         $parentids = array();
         foreach ($parents as $node) {
             $parentids[] = $node['nodeid'];
         }
         $updates['parentlist'] = $parentids;
         $this->qryAfterAdd[] = array('definition' => 'vBForum:setLastDataParentList', 'data' => $updates);
     }
     if (!$skipTransaction) {
         //The child classes that have their own transactions all set this to true so afterAdd is always called just once.
         $this->afterAdd($results['nodeid'], $data, $options, $results['cacheEvents'], $results['nodeVals']);
     }
     if ($isSpam) {
         $this->assertor->assertQuery('spamlog', array(vB_dB_Query::TYPE_KEY => vB_dB_Query::QUERY_INSERTIGNORE, 'nodeid' => $results['nodeid']));
     }
     $cachedNodes = array($results['nodeid']);
     foreach ($parents as $node) {
         $cachedNodes[] = $node['nodeid'];
     }
     $this->nodeApi->clearCacheEvents($cachedNodes);
     $this->nodeApi->clearCacheEvents(array($results['nodeid'], $data['parentid']));
     return $results;
 }