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