/** * Enforces singleton use * * ***/ public static function instance() { if (empty(self::$instance)) { self::$instance = new vB_Akismet(); } return self::$instance; }
/** * 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; }
/** * 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; }