Check whether or not the record is spam.
public static isSpam ( string $RecordType, array $Data, array $Options = [] ) | ||
$RecordType | string | By default, this should be one of the following: - Comment: A comment. - Discussion: A discussion. - User: A user registration. |
$Data | array | The record data. |
$Options | array | Options for fine-tuning this method call. - Log: Log the record if it is found to be spam. |
/** * Inserts or updates the discussion via form values. * * Events: BeforeSaveDiscussion, AfterSaveDiscussion. * * @since 2.0.0 * @access public * * @param array $FormPostValues Data sent from the form model. * @param array $Settings Currently unused. * @return int $DiscussionID Unique ID of the discussion. */ public function save($FormPostValues, $Settings = false) { $Session = Gdn::session(); // Define the primary key in this model's table. $this->defineSchema(); // Add & apply any extra validation rules: $this->Validation->applyRule('Body', 'Required'); $this->Validation->addRule('MeAction', 'function:ValidateMeAction'); $this->Validation->applyRule('Body', 'MeAction'); $MaxCommentLength = Gdn::config('Vanilla.Comment.MaxLength'); if (is_numeric($MaxCommentLength) && $MaxCommentLength > 0) { $this->Validation->SetSchemaProperty('Body', 'Length', $MaxCommentLength); $this->Validation->applyRule('Body', 'Length'); } // Validate category permissions. $CategoryID = val('CategoryID', $FormPostValues); if ($CategoryID > 0) { $Category = CategoryModel::categories($CategoryID); if ($Category && !$Session->checkPermission('Vanilla.Discussions.Add', true, 'Category', val('PermissionCategoryID', $Category))) { $this->Validation->addValidationResult('CategoryID', 'You do not have permission to post in this category'); } } // Get the DiscussionID from the form so we know if we are inserting or updating. $DiscussionID = val('DiscussionID', $FormPostValues, ''); // See if there is a source ID. if (val('SourceID', $FormPostValues)) { $DiscussionID = $this->SQL->getWhere('Discussion', arrayTranslate($FormPostValues, array('Source', 'SourceID')))->value('DiscussionID'); if ($DiscussionID) { $FormPostValues['DiscussionID'] = $DiscussionID; } } elseif (val('ForeignID', $FormPostValues)) { $DiscussionID = $this->SQL->getWhere('Discussion', array('ForeignID' => $FormPostValues['ForeignID']))->value('DiscussionID'); if ($DiscussionID) { $FormPostValues['DiscussionID'] = $DiscussionID; } } $Insert = $DiscussionID == '' ? true : false; $this->EventArguments['Insert'] = $Insert; if ($Insert) { unset($FormPostValues['DiscussionID']); // If no categoryid is defined, grab the first available. if (!val('CategoryID', $FormPostValues) && !c('Vanilla.Categories.Use')) { $FormPostValues['CategoryID'] = val('CategoryID', CategoryModel::defaultCategory(), -1); } $this->addInsertFields($FormPostValues); // The UpdateUserID used to be required. Just add it if it still is. if (!$this->Schema->getProperty('UpdateUserID', 'AllowNull', true)) { $FormPostValues['UpdateUserID'] = $FormPostValues['InsertUserID']; } // $FormPostValues['LastCommentUserID'] = $Session->UserID; $FormPostValues['DateLastComment'] = $FormPostValues['DateInserted']; } else { // Add the update fields. $this->addUpdateFields($FormPostValues); } // Set checkbox values to zero if they were unchecked if (val('Announce', $FormPostValues, '') === false) { $FormPostValues['Announce'] = 0; } if (val('Closed', $FormPostValues, '') === false) { $FormPostValues['Closed'] = 0; } if (val('Sink', $FormPostValues, '') === false) { $FormPostValues['Sink'] = 0; } // Prep and fire event $this->EventArguments['FormPostValues'] =& $FormPostValues; $this->EventArguments['DiscussionID'] = $DiscussionID; $this->fireEvent('BeforeSaveDiscussion'); // Validate the form posted values $this->validate($FormPostValues, $Insert); $ValidationResults = $this->validationResults(); // If the body is not required, remove it's validation errors. $BodyRequired = c('Vanilla.DiscussionBody.Required', true); if (!$BodyRequired && array_key_exists('Body', $ValidationResults)) { unset($ValidationResults['Body']); } if (count($ValidationResults) == 0) { // If the post is new and it validates, make sure the user isn't spamming if (!$Insert || !$this->checkForSpam('Discussion')) { // Get all fields on the form that relate to the schema $Fields = $this->Validation->schemaValidationFields(); // Check for spam. $spam = SpamModel::isSpam('Discussion', $Fields); if ($spam) { return SPAM; } // Get DiscussionID if one was sent $DiscussionID = intval(val('DiscussionID', $Fields, 0)); // Remove the primary key from the fields for saving. unset($Fields['DiscussionID']); $StoredCategoryID = false; if ($DiscussionID > 0) { // Updating $Stored = $this->getID($DiscussionID, DATASET_TYPE_OBJECT); // Block Format change if we're forcing the formatter. if (c('Garden.ForceInputFormatter')) { unset($Fields['Format']); } // Clear the cache if necessary. $CacheKeys = array(); if (val('Announce', $Stored) != val('Announce', $Fields)) { $CacheKeys[] = $this->getAnnouncementCacheKey(); $CacheKeys[] = $this->getAnnouncementCacheKey(val('CategoryID', $Stored)); } if (val('CategoryID', $Stored) != val('CategoryID', $Fields)) { $CacheKeys[] = $this->getAnnouncementCacheKey(val('CategoryID', $Fields)); } foreach ($CacheKeys as $CacheKey) { Gdn::cache()->remove($CacheKey); } self::serializeRow($Fields); $this->SQL->put($this->Name, $Fields, array($this->PrimaryKey => $DiscussionID)); setValue('DiscussionID', $Fields, $DiscussionID); LogModel::logChange('Edit', 'Discussion', (array) $Fields, $Stored); if (val('CategoryID', $Stored) != val('CategoryID', $Fields)) { $StoredCategoryID = val('CategoryID', $Stored); } } else { // Inserting. if (!val('Format', $Fields) || c('Garden.ForceInputFormatter')) { $Fields['Format'] = c('Garden.InputFormatter', ''); } if (c('Vanilla.QueueNotifications')) { $Fields['Notified'] = ActivityModel::SENT_PENDING; } // Check for approval $ApprovalRequired = checkRestriction('Vanilla.Approval.Require'); if ($ApprovalRequired && !val('Verified', Gdn::session()->User)) { LogModel::insert('Pending', 'Discussion', $Fields); return UNAPPROVED; } // Create discussion $this->serializeRow($Fields); $DiscussionID = $this->SQL->insert($this->Name, $Fields); $Fields['DiscussionID'] = $DiscussionID; // Update the cache. if ($DiscussionID && Gdn::cache()->activeEnabled()) { $CategoryCache = array('LastDiscussionID' => $DiscussionID, 'LastCommentID' => null, 'LastTitle' => Gdn_Format::text($Fields['Name']), 'LastUserID' => $Fields['InsertUserID'], 'LastDateInserted' => $Fields['DateInserted'], 'LastUrl' => DiscussionUrl($Fields)); CategoryModel::setCache($Fields['CategoryID'], $CategoryCache); // Clear the cache if necessary. if (val('Announce', $Fields)) { Gdn::cache()->remove($this->getAnnouncementCacheKey(val('CategoryID', $Fields))); if (val('Announce', $Fields) == 1) { Gdn::cache()->remove($this->getAnnouncementCacheKey()); } } } // Update the user's discussion count. $InsertUser = Gdn::userModel()->getID($Fields['InsertUserID']); $this->updateUserDiscussionCount($Fields['InsertUserID'], val('CountDiscussions', $InsertUser, 0) > 100); // Mark the user as participated. $this->SQL->replace('UserDiscussion', array('Participated' => 1), array('DiscussionID' => $DiscussionID, 'UserID' => val('InsertUserID', $Fields))); // Assign the new DiscussionID to the comment before saving. $FormPostValues['IsNewDiscussion'] = true; $FormPostValues['DiscussionID'] = $DiscussionID; // Do data prep. $DiscussionName = val('Name', $Fields, ''); $Story = val('Body', $Fields, ''); $NotifiedUsers = array(); $UserModel = Gdn::userModel(); $ActivityModel = new ActivityModel(); if (val('Type', $FormPostValues)) { $Code = 'HeadlineFormat.Discussion.' . $FormPostValues['Type']; } else { $Code = 'HeadlineFormat.Discussion'; } $HeadlineFormat = t($Code, '{ActivityUserID,user} started a new discussion: <a href="{Url,html}">{Data.Name,text}</a>'); $Category = CategoryModel::categories(val('CategoryID', $Fields)); $Activity = array('ActivityType' => 'Discussion', 'ActivityUserID' => $Fields['InsertUserID'], 'HeadlineFormat' => $HeadlineFormat, 'RecordType' => 'Discussion', 'RecordID' => $DiscussionID, 'Route' => DiscussionUrl($Fields), 'Data' => array('Name' => $DiscussionName, 'Category' => val('Name', $Category))); // Allow simple fulltext notifications if (c('Vanilla.Activity.ShowDiscussionBody', false)) { $Activity['Story'] = $Story; } // Notify all of the users that were mentioned in the discussion. $Usernames = getMentions($DiscussionName . ' ' . $Story); // Use our generic Activity for events, not mentions $this->EventArguments['Activity'] = $Activity; // Notify everyone that has advanced notifications. if (!c('Vanilla.QueueNotifications')) { try { $Fields['DiscussionID'] = $DiscussionID; $this->notifyNewDiscussion($Fields, $ActivityModel, $Activity); } catch (Exception $Ex) { throw $Ex; } } // Notifications for mentions foreach ($Usernames as $Username) { $User = $UserModel->getByUsername($Username); if (!$User) { continue; } // Check user can still see the discussion. if (!$this->canView($Fields, $User->UserID)) { continue; } $Activity['HeadlineFormat'] = t('HeadlineFormat.Mention', '{ActivityUserID,user} mentioned you in <a href="{Url,html}">{Data.Name,text}</a>'); $Activity['NotifyUserID'] = val('UserID', $User); $ActivityModel->queue($Activity, 'Mention'); } // Throw an event for users to add their own events. $this->EventArguments['Discussion'] = $Fields; $this->EventArguments['NotifiedUsers'] = $NotifiedUsers; $this->EventArguments['MentionedUsers'] = $Usernames; $this->EventArguments['ActivityModel'] = $ActivityModel; $this->fireEvent('BeforeNotification'); // Send all notifications. $ActivityModel->saveQueue(); } // Get CategoryID of this discussion $Discussion = $this->getID($DiscussionID, DATASET_TYPE_OBJECT); $CategoryID = val('CategoryID', $Discussion, false); // Update discussion counter for affected categories. if ($Insert || $StoredCategoryID) { $this->incrementNewDiscussion($Discussion); } if ($StoredCategoryID) { $this->updateDiscussionCount($StoredCategoryID); } // Fire an event that the discussion was saved. $this->EventArguments['FormPostValues'] = $FormPostValues; $this->EventArguments['Fields'] = $Fields; $this->EventArguments['DiscussionID'] = $DiscussionID; $this->fireEvent('AfterSaveDiscussion'); } } return $DiscussionID; }
/** * * * @param array $User * @return bool|string * @since 2.1 */ public function validateSpamRegistration($User) { $DiscoveryText = val('DiscoveryText', $User); $Log = validateRequired($DiscoveryText); $Spam = SpamModel::isSpam('Registration', $User, ['Log' => $Log]); if ($Spam) { if ($Log) { // The user entered discovery text. return self::REDIRECT_APPROVE; } else { $this->Validation->addValidationResult('DiscoveryText', 'Tell us why you want to join!'); return false; } } return true; }
/** * Insert or update core data about the comment. * * Events: BeforeSaveComment, AfterValidateComment, AfterSaveComment. * * @param array $FormPostValues Data from the form model. * @param array $Settings Currently unused. * @return int $CommentID * @since 2.0.0 */ public function save($FormPostValues, $Settings = false) { $Session = Gdn::session(); // Define the primary key in this model's table. $this->defineSchema(); // Add & apply any extra validation rules: $this->Validation->applyRule('Body', 'Required'); $this->Validation->addRule('MeAction', 'function:ValidateMeAction'); $this->Validation->applyRule('Body', 'MeAction'); $MaxCommentLength = Gdn::config('Vanilla.Comment.MaxLength'); if (is_numeric($MaxCommentLength) && $MaxCommentLength > 0) { $this->Validation->SetSchemaProperty('Body', 'Length', $MaxCommentLength); $this->Validation->applyRule('Body', 'Length'); } $MinCommentLength = c('Vanilla.Comment.MinLength'); if ($MinCommentLength && is_numeric($MinCommentLength)) { $this->Validation->SetSchemaProperty('Body', 'MinLength', $MinCommentLength); $this->Validation->addRule('MinTextLength', 'function:ValidateMinTextLength'); $this->Validation->applyRule('Body', 'MinTextLength'); } // Validate $CommentID and whether this is an insert $CommentID = val('CommentID', $FormPostValues); $CommentID = is_numeric($CommentID) && $CommentID > 0 ? $CommentID : false; $Insert = $CommentID === false; if ($Insert) { $this->AddInsertFields($FormPostValues); } else { $this->AddUpdateFields($FormPostValues); } // Prep and fire event $this->EventArguments['FormPostValues'] =& $FormPostValues; $this->EventArguments['CommentID'] = $CommentID; $this->fireEvent('BeforeSaveComment'); // Validate the form posted values if ($this->validate($FormPostValues, $Insert)) { // If the post is new and it validates, check for spam if (!$Insert || !$this->CheckForSpam('Comment')) { $Fields = $this->Validation->SchemaValidationFields(); unset($Fields[$this->PrimaryKey]); $CommentData = $CommentID ? array_merge($Fields, ['CommentID' => $CommentID]) : $Fields; // Check for spam $spam = SpamModel::isSpam('Comment', $CommentData); if ($spam) { return SPAM; } $isValid = true; $invalidReturnType = false; $this->EventArguments['CommentData'] = $CommentData; $this->EventArguments['IsValid'] =& $isValid; $this->EventArguments['InvalidReturnType'] =& $invalidReturnType; $this->fireEvent('AfterValidateComment'); if (!$isValid) { return $invalidReturnType; } if ($Insert === false) { // Log the save. LogModel::LogChange('Edit', 'Comment', array_merge($Fields, array('CommentID' => $CommentID))); // Save the new value. $this->SerializeRow($Fields); $this->SQL->put($this->Name, $Fields, array('CommentID' => $CommentID)); } else { // Make sure that the comments get formatted in the method defined by Garden. if (!val('Format', $Fields) || c('Garden.ForceInputFormatter')) { $Fields['Format'] = Gdn::config('Garden.InputFormatter', ''); } // Check for approval $ApprovalRequired = CheckRestriction('Vanilla.Approval.Require'); if ($ApprovalRequired && !val('Verified', Gdn::session()->User)) { $DiscussionModel = new DiscussionModel(); $Discussion = $DiscussionModel->getID(val('DiscussionID', $Fields)); $Fields['CategoryID'] = val('CategoryID', $Discussion); LogModel::insert('Pending', 'Comment', $Fields); return UNAPPROVED; } // Create comment. $this->SerializeRow($Fields); $CommentID = $this->SQL->insert($this->Name, $Fields); } if ($CommentID) { $this->EventArguments['CommentID'] = $CommentID; $this->EventArguments['Insert'] = $Insert; // IsNewDiscussion is passed when the first comment for new discussions are created. $this->EventArguments['IsNewDiscussion'] = val('IsNewDiscussion', $FormPostValues); $this->fireEvent('AfterSaveComment'); } } } // Update discussion's comment count $DiscussionID = val('DiscussionID', $FormPostValues); $this->UpdateCommentCount($DiscussionID, array('Slave' => false)); return $CommentID; }
/** * * * @param array $Data * @param bool $Preference * @param array $Options * @return array * @throws Exception */ public function save($Data, $Preference = false, $Options = array()) { trace('ActivityModel->save()'); $Activity = $Data; $this->_touch($Activity); if ($Activity['ActivityUserID'] == $Activity['NotifyUserID'] && !val('Force', $Options)) { trace('Skipping activity because it would notify the user of something they did.'); return; // don't notify users of something they did. } // Check the user's preference. if ($Preference) { list($Popup, $Email) = self::notificationPreference($Preference, $Activity['NotifyUserID'], 'both'); if ($Popup && !$Activity['Notified']) { $Activity['Notified'] = self::SENT_PENDING; } if ($Email && !$Activity['Emailed']) { $Activity['Emailed'] = self::SENT_PENDING; } if (!$Activity['Notified'] && !$Activity['Emailed'] && !val('Force', $Options)) { trace("Skipping activity because the user has no preference set."); return; } } $ActivityType = self::getActivityType($Activity['ActivityType']); $ActivityTypeID = val('ActivityTypeID', $ActivityType); if (!$ActivityTypeID) { trace("There is no {$ActivityType} activity type.", TRACE_WARNING); $ActivityType = self::getActivityType('Default'); $ActivityTypeID = val('ActivityTypeID', $ActivityType); } $Activity['ActivityTypeID'] = $ActivityTypeID; $NotificationInc = 0; if ($Activity['NotifyUserID'] > 0 && $Activity['Notified']) { $NotificationInc = 1; } // Check to see if we are sharing this activity with another one. if ($CommentActivityID = val('CommentActivityID', $Activity['Data'])) { $CommentActivity = $this->getID($CommentActivityID); $Activity['Data']['CommentNotifyUserID'] = $CommentActivity['NotifyUserID']; } // Make sure this activity isn't a duplicate. if (val('CheckRecord', $Options)) { // Check to see if this record already notified so we don't notify multiple times. $Where = arrayTranslate($Activity, array('NotifyUserID', 'RecordType', 'RecordID')); $Where['DateUpdated >'] = Gdn_Format::toDateTime(strtotime('-2 days')); // index hint $CheckActivity = $this->SQL->getWhere('Activity', $Where)->firstRow(); if ($CheckActivity) { return false; } } // Check to share the activity. if (val('Share', $Options)) { $this->Share($Activity); } // Group he activity. if ($GroupBy = val('GroupBy', $Options)) { $GroupBy = (array) $GroupBy; $Where = array(); foreach ($GroupBy as $ColumnName) { $Where[$ColumnName] = $Activity[$ColumnName]; } $Where['NotifyUserID'] = $Activity['NotifyUserID']; // Make sure to only group activities by day. $Where['DateInserted >'] = Gdn_Format::toDateTime(strtotime('-1 day')); // See if there is another activity to group these into. $GroupActivity = $this->SQL->getWhere('Activity', $Where)->firstRow(DATASET_TYPE_ARRAY); if ($GroupActivity) { $GroupActivity['Data'] = @unserialize($GroupActivity['Data']); $Activity = $this->mergeActivities($GroupActivity, $Activity); $NotificationInc = 0; } } $Delete = false; if ($Activity['Emailed'] == self::SENT_PENDING) { $this->email($Activity); $Delete = val('_Delete', $Activity); } $ActivityData = $Activity['Data']; if (isset($Activity['Data']) && is_array($Activity['Data'])) { $Activity['Data'] = serialize($Activity['Data']); } $this->defineSchema(); $Activity = $this->filterSchema($Activity); $ActivityID = val('ActivityID', $Activity); if (!$ActivityID) { if (!$Delete) { $this->addInsertFields($Activity); touchValue('DateUpdated', $Activity, $Activity['DateInserted']); $this->EventArguments['Activity'] =& $Activity; $this->EventArguments['ActivityID'] = null; $Handled = false; $this->EventArguments['Handled'] =& $Handled; $this->fireEvent('BeforeSave'); if (count($this->validationResults()) > 0) { return false; } if ($Handled) { // A plugin handled this activity so don't save it. return $Activity; } if (val('CheckSpam', $Options)) { // Check for spam $Spam = SpamModel::isSpam('Activity', $Activity); if ($Spam) { return SPAM; } // Check for approval $ApprovalRequired = CheckRestriction('Vanilla.Approval.Require'); if ($ApprovalRequired && !val('Verified', Gdn::session()->User)) { LogModel::insert('Pending', 'Activity', $Activity); return UNAPPROVED; } } $ActivityID = $this->SQL->insert('Activity', $Activity); $Activity['ActivityID'] = $ActivityID; } } else { $Activity['DateUpdated'] = Gdn_Format::toDateTime(); unset($Activity['ActivityID']); $this->EventArguments['Activity'] =& $Activity; $this->EventArguments['ActivityID'] = $ActivityID; $this->fireEvent('BeforeSave'); if (count($this->validationResults()) > 0) { return false; } $this->SQL->put('Activity', $Activity, array('ActivityID' => $ActivityID)); $Activity['ActivityID'] = $ActivityID; } $Activity['Data'] = $ActivityData; if (isset($CommentActivity)) { $CommentActivity['Data']['SharedActivityID'] = $Activity['ActivityID']; $CommentActivity['Data']['SharedNotifyUserID'] = $Activity['NotifyUserID']; $this->setField($CommentActivity['ActivityID'], 'Data', $CommentActivity['Data']); } if ($NotificationInc > 0) { $CountNotifications = Gdn::userModel()->getID($Activity['NotifyUserID'])->CountNotifications + $NotificationInc; Gdn::userModel()->setField($Activity['NotifyUserID'], 'CountNotifications', $CountNotifications); } // If this is a wall post then we need to notify on that. if (val('Name', $ActivityType) == 'WallPost' && $Activity['NotifyUserID'] == self::NOTIFY_PUBLIC) { $this->notifyWallPost($Activity); } return $Activity; }