/** * Return a formatted string * @link http://www.php.net/manual/en/function.sprintf.php * @param format string <p> * The format string is composed of zero or more directives: * ordinary characters (excluding %) that are * copied directly to the result, and conversion * specifications, each of which results in fetching its * own parameter. This applies to both sprintf * and printf. * </p> * <p> * Each conversion specification consists of a percent sign * (%), followed by one or more of these * elements, in order: * An optional sign specifier that forces a sign * (- or +) to be used on a number. By default, only the - sign is used * on a number if it's negative. This specifier forces positive numbers * to have the + sign attached as well, and was added in PHP 4.3.0. * @param args mixed[optional] <p> * </p> * @param _ mixed[optional] * @return string a string produced according to the formatting string * format. */ function dbsprintf($format, $args = null, $_ = null) { $varArray = func_get_args(); $count = count($varArray); if ($count > 1) { $v = $varArray[$count - 1]; if ($v === true) { $varArray = array_slice($varArray, 0, $count - 1); return call_user_func_array('sprintf', $varArray); } else { if ($v === false) { $varArray = array_slice($varArray, 0, $count - 1); $count--; } } for ($index = 1; $index < $count; $index++) { $patterns = array('/\\r/', '/\\n/', '/\\x00/', '/\\x1a/'); $var = preg_replace($patterns, '', $varArray[$index]); if ($var === false) { $var = 0; } $varArray[$index] = dbencode($var); } return call_user_func_array('sprintf', $varArray); } else { return $format; } }
/** * Set a property on a category. * * @param int $ID * @param array|string $Property * @param bool|false $Value * @return array|string */ public function setField($ID, $Property, $Value = false) { if (!is_array($Property)) { $Property = array($Property => $Value); } if (isset($Property['AllowedDiscussionTypes']) && is_array($Property['AllowedDiscussionTypes'])) { $Property['AllowedDiscussionTypes'] = dbencode($Property['AllowedDiscussionTypes']); } $this->SQL->put($this->Name, $Property, array('CategoryID' => $ID)); // Set the cache. self::setCache($ID, $Property); return $Property; }
/** * * * @param array $Data * @param bool $Settings * @return bool */ public function save($Data, $Settings = false) { // Grab the current record. $Row = false; if ($id = val('ID', $Settings)) { $Row = $this->getWhere(array($this->PrimaryKey => $id))->firstRow(DATASET_TYPE_ARRAY); } elseif (isset($Data[$this->PrimaryKey])) { $Row = $this->getWhere(array($this->PrimaryKey => $Data[$this->PrimaryKey]))->firstRow(DATASET_TYPE_ARRAY); } elseif ($PK = val('PK', $Settings)) { $Row = $this->getWhere(array($PK => $Data[$PK]))->firstRow(DATASET_TYPE_ARRAY); } // Get the columns and put the extended data in the attributes. $this->defineSchema(); $Columns = $this->Schema->fields(); $Remove = array('TransientKey' => 1, 'hpt' => 1, 'Save' => 1, 'Checkboxes' => 1); $Data = array_diff_key($Data, $Remove); $Attributes = array_diff_key($Data, $Columns); if (!empty($Attributes)) { $Data = array_diff_key($Data, $Attributes); $Data['Attributes'] = dbencode($Attributes); } $Insert = !$Row; if ($Insert) { $this->addInsertFields($Data); } else { $this->addUpdateFields($Data); } // Validate the form posted values if ($this->validate($Data, $Insert) === true) { // Clear the default from other authentication providers. $Default = val('IsDefault', $Data); if ($Default) { $this->SQL->put($this->Name, array('IsDefault' => 0), array('AuthenticationKey <>' => val('AuthenticationKey', $Data))); } $Fields = $this->Validation->validationFields(); if ($Insert === false) { $PrimaryKeyVal = $Row[$this->PrimaryKey]; $this->update($Fields, array($this->PrimaryKey => $PrimaryKeyVal)); } else { $PrimaryKeyVal = $this->insert($Fields); } } else { $PrimaryKeyVal = false; } return $PrimaryKeyVal; }
/** * Check an addon's file to extract the addon information out of it. * * @param string $Path The path to the file. * @param bool $ThrowError Whether or not to throw an exception if there is a problem analyzing the addon. * @return array An array of addon information. */ public static function analyzeAddon($Path, $ThrowError = true) { if (!file_exists($Path)) { if ($ThrowError) { throw new Exception("{$Path} not found.", 404); } return false; } $Addon = []; $Result = []; $InfoPaths = array('/settings/about.php', '/default.php', '/class.*.plugin.php', '/about.php', '/definitions.php', '/index.php', 'vanilla2export.php'); // Get the list of potential files to analyze. if (is_dir($Path)) { $Entries = self::getInfoFiles($Path, $InfoPaths); $DeleteEntries = false; } else { $Entries = self::getInfoZip($Path, $InfoPaths, false, $ThrowError); $DeleteEntries = true; } foreach ($Entries as $Entry) { if ($Entry['Name'] == '/index.php') { // This could be the core vanilla package. $Version = self::parseCoreVersion($Entry['Path']); if (!$Version) { continue; } // The application was confirmed. $Addon = array('AddonKey' => 'vanilla', 'AddonTypeID' => ADDON_TYPE_CORE, 'Name' => 'Vanilla', 'Description' => 'Vanilla is an open-source, standards-compliant, multi-lingual, fully extensible discussion forum for the web. Anyone who has web-space that meets the requirements can download and use Vanilla for free!', 'Version' => $Version, 'License' => 'GPLv2', 'Path' => $Entry['Path']); break; } elseif ($Entry['Name'] == 'vanilla2export.php') { // This could be the vanilla porter. $Version = self::parseCoreVersion($Entry['Path']); if (!$Version) { continue; } $Addon = array('AddonKey' => 'porter', 'AddonTypeID' => ADDON_TYPE_CORE, 'Name' => 'Vanilla Porter', 'Description' => 'Drop this script in your existing site and navigate to it in your web browser to export your existing forum data to the Vanilla 2 import format.', 'Version' => $Version, 'License' => 'GPLv2', 'Path' => $Entry['Path']); break; } else { // This could be an addon. $Info = self::parseInfoArray($Entry['Path']); if (!is_array($Info) && count($Info)) { continue; } $Key = key($Info); $Variable = $Info['Variable']; $Info = $Info[$Key]; // Validate the addon. $Name = $Entry['Name']; $Valid = true; if (!val('Name', $Info)) { $Info['Name'] = $Key; } // Validate basic fields. $checkResult = self::checkRequiredFields($Info); if (count($checkResult)) { $Result = array_merge($Result, $checkResult); $Valid = false; } // Validate folder name matches key. if (isset($Entry['Base']) && strcasecmp($Entry['Base'], $Key) != 0 && $Variable != 'ThemeInfo') { $Result[] = "{$Name}: The addon's key is not the same as its folder name."; $Valid = false; } if (!$Valid) { continue; } // The addon is valid. $Addon = array_merge(array('AddonKey' => $Key, 'AddonTypeID' => ''), $Info); switch ($Variable) { case 'ApplicationInfo': $Addon['AddonTypeID'] = ADDON_TYPE_APPLICATION; break; case 'LocaleInfo': $Addon['AddonTypeID'] = ADDON_TYPE_LOCALE; break; case 'PluginInfo': $Addon['AddonTypeID'] = ADDON_TYPE_PLUGIN; break; case 'ThemeInfo': $Addon['AddonTypeID'] = ADDON_TYPE_THEME; break; } } } if ($DeleteEntries) { $FolderPath = substr($Path, 0, -4); Gdn_FileSystem::removeFolder($FolderPath); } // Add the addon requirements. if (!empty($Addon)) { $Requirements = arrayTranslate($Addon, ['RequiredApplications' => 'Applications', 'RequiredPlugins' => 'Plugins', 'RequiredThemes' => 'Themes']); foreach ($Requirements as $Type => $Items) { if (!is_array($Items)) { unset($Requirements[$Type]); } } $Addon['Requirements'] = dbencode($Requirements); $Addon['Checked'] = true; $Addon['Path'] = $Path; $UploadsPath = PATH_UPLOADS . '/'; if (stringBeginsWith($Addon['Path'], $UploadsPath)) { $Addon['File'] = substr($Addon['Path'], strlen($UploadsPath)); } if (is_file($Path)) { $Addon['MD5'] = md5_file($Path); $Addon['FileSize'] = filesize($Path); } } elseif ($ThrowError) { $Msg = implode("\n", $Result); throw new Gdn_UserException($Msg, 400); } else { return false; } return $Addon; }
/** * * * @param string $Column * @param int $RowID * @param string $Name * @param string $Value * @return bool|Gdn_DataSet|object|string * @throws Exception */ public function saveToSerializedColumn($Column, $RowID, $Name, $Value = '') { if (!isset($this->Schema)) { $this->defineSchema(); } // TODO: need to be sure that $this->PrimaryKey is only one primary key $FieldName = $this->PrimaryKey; // Load the existing values $Row = $this->SQL->select($Column)->from($this->Name)->where($FieldName, $RowID)->get()->firstRow(); if (!$Row) { throw new Exception(T('ErrorRecordNotFound')); } $Values = dbdecode($Row->{$Column}); if (is_string($Values) && $Values != '') { throw new Exception(T('Serialized column failed to be unserialized.')); } if (!is_array($Values)) { $Values = array(); } if (!is_array($Name)) { // Assign the new value(s) $Name = array($Name => $Value); } $Values = dbencode(array_merge($Values, $Name)); // Save the values back to the db return $this->SQL->from($this->Name)->where($FieldName, $RowID)->set($Column, $Values)->put(); }
/** * @param PostController $Sender * @param array $Args * @return mixed */ public function PostController_Comment_Create($Sender, $Args = array()) { if ($Sender->Form->AuthenticatedPostBack()) { $Sender->Form->SetModel($Sender->CommentModel); // Grab the discussion for use later. $DiscussionID = $Sender->Form->GetFormValue('DiscussionID'); $DiscussionModel = new DiscussionModel(); $Discussion = $DiscussionModel->GetID($DiscussionID); // Check to see if the discussion is supposed to be in private... $WhisperConversationID = GetValueR('Attributes.WhisperConversationID', $Discussion); if ($WhisperConversationID === TRUE) { // There isn't a conversation so we want to create one. $Sender->Form->SetFormValue('Whisper', TRUE); $WhisperUserIDs = GetValueR('Attributes.WhisperUserIDs', $Discussion); $Sender->Form->SetFormValue('RecipientUserID', $WhisperUserIDs); } elseif ($WhisperConversationID) { // There is already a conversation. $Sender->Form->SetFormValue('Whisper', TRUE); $Sender->Form->SetFormValue('ConversationID', $WhisperConversationID); } $Whisper = $Sender->Form->GetFormValue('Whisper') && GetIncomingValue('Type') != 'Draft'; $WhisperTo = trim($Sender->Form->GetFormValue('To')); $ConversationID = $Sender->Form->GetFormValue('ConversationID'); // If this isn't a whisper then post as normal. if (!$Whisper) { return call_user_func_array(array($Sender, 'Comment'), $Args); } $ConversationModel = new ConversationModel(); $ConversationMessageModel = new ConversationMessageModel(); if ($ConversationID > 0) { $Sender->Form->SetModel($ConversationMessageModel); } else { // We have to remove the blank conversation ID or else the model won't validate. $FormValues = $Sender->Form->FormValues(); unset($FormValues['ConversationID']); $FormValues['Subject'] = GetValue('Name', $Discussion); $Sender->Form->FormValues($FormValues); $Sender->Form->SetModel($ConversationModel); $ConversationModel->Validation->ApplyRule('DiscussionID', 'Required'); } $ID = $Sender->Form->Save($ConversationMessageModel); if ($Sender->Form->ErrorCount() > 0) { $Sender->ErrorMessage($Sender->Form->Errors()); } else { if ($WhisperConversationID === TRUE) { $Discussion->Attributes['WhisperConversationID'] = $ID; $DiscussionModel->SetProperty($DiscussionID, 'Attributes', dbencode($Discussion->Attributes)); } $LastCommentID = GetValue('LastCommentID', $Discussion); $MessageID = GetValue('LastMessageID', $ConversationMessageModel, FALSE); // Randomize the querystring to force the browser to refresh. $Rand = mt_rand(10000, 99999); if ($LastCommentID) { // Link to the last comment. $HashID = $MessageID ? 'w' . $MessageID : $LastCommentID; $Sender->RedirectUrl = Url("discussion/comment/{$LastCommentID}?rand={$Rand}#Comment_{$HashID}", TRUE); } else { // Link to the discussion. $Hash = $MessageID ? "Comment_w{$MessageID}" : 'Item_1'; $Name = rawurlencode(GetValue('Name', $Discussion, 'x')); $Sender->RedirectUrl = Url("discussion/{$DiscussionID}/{$Name}?rand={$Rand}#{$Hash}", TRUE); } } $Sender->Render(); } else { return call_user_func_array(array($Sender, 'Comment'), $Args); } }
/** * Create secure handshake with remote authenticator. * * @access public * @since 2.0.? * @author Tim Gunter * * @param string $AuthenticationSchemeAlias (default: 'default') */ public function handshake($AuthenticationSchemeAlias = 'default') { try { // Don't show anything if handshaking not turned on by an authenticator if (!Gdn::authenticator()->canHandshake()) { throw new Exception(); } // Try to load the authenticator $Authenticator = Gdn::authenticator()->authenticateWith($AuthenticationSchemeAlias); // Try to grab the authenticator data $Payload = $Authenticator->getHandshake(); if ($Payload === false) { Gdn::request()->withURI('dashboard/entry/auth/password'); return Gdn::dispatcher()->dispatch(); } } catch (Exception $e) { Gdn::request()->WithURI('/entry/signin'); return Gdn::dispatcher()->dispatch(); } $UserInfo = array('UserKey' => $Authenticator->GetUserKeyFromHandshake($Payload), 'ConsumerKey' => $Authenticator->GetProviderKeyFromHandshake($Payload), 'TokenKey' => $Authenticator->GetTokenKeyFromHandshake($Payload), 'UserName' => $Authenticator->GetUserNameFromHandshake($Payload), 'UserEmail' => $Authenticator->GetUserEmailFromHandshake($Payload)); if (method_exists($Authenticator, 'GetRolesFromHandshake')) { $RemoteRoles = $Authenticator->GetRolesFromHandshake($Payload); if (!empty($RemoteRoles)) { $UserInfo['Roles'] = $RemoteRoles; } } // Manual user sync is disabled. No hand holding will occur for users. $SyncScreen = c('Garden.Authenticator.SyncScreen', 'on'); switch ($SyncScreen) { case 'on': // Authenticator events fired inside $this->syncScreen($Authenticator, $UserInfo, $Payload); break; case 'off': case 'smart': $UserID = $this->UserModel->synchronize($UserInfo['UserKey'], array('Name' => $UserInfo['UserName'], 'Email' => $UserInfo['UserEmail'], 'Roles' => val('Roles', $UserInfo))); if ($UserID > 0) { // Account created successfully. // Finalize the link between the forum user and the foreign userkey $Authenticator->finalize($UserInfo['UserKey'], $UserID, $UserInfo['ConsumerKey'], $UserInfo['TokenKey'], $Payload); $UserEventData = array_merge(array('UserID' => $UserID, 'Payload' => $Payload), $UserInfo); Gdn::authenticator()->trigger(Gdn_Authenticator::AUTH_CREATED, $UserEventData); /// ... and redirect them appropriately $Route = $this->redirectTo(); if ($Route !== false) { redirect($Route); } else { redirect('/'); } } else { // Account not created. if ($SyncScreen == 'smart') { $this->informMessage(t('There is already an account in this forum using your email address. Please create a new account, or enter the credentials for the existing account.')); $this->syncScreen($Authenticator, $UserInfo, $Payload); } else { // Set the memory cookie to allow signinloopback to shortcircuit remote query. $CookiePayload = array('Sync' => 'Failed'); $encodedCookiePayload = dbencode($CookiePayload); $Authenticator->remember($UserInfo['ConsumerKey'], $encodedCookiePayload); // This resets vanilla's internal "where am I" to the homepage. Needed. Gdn::request()->withRoute('DefaultController'); $this->SelfUrl = url(''); //Gdn::request()->Path(); $this->View = 'syncfailed'; $this->ProviderSite = $Authenticator->getProviderUrl(); $this->render(); } } break; } }
/** * Grab second forum's data and merge with current forum. * * Merge Users on email address. Keeps this forum's username/password. * Merge Roles, Tags, and Categories on precise name matches. * * @todo Compare column names between forums and use intersection */ public function MergeForums($OldDatabase, $OldPrefix, $LegacySlug) { $NewPrefix = C('Database.DatabasePrefix'); $this->OldDatabase = $OldDatabase; $this->OldPrefix = $OldPrefix; $DoLegacy = !empty($LegacySlug); // USERS // if ($this->OldTableExists('User')) { $UserColumns = $this->GetColumns('User', $OldDatabase, $OldPrefix); // Merge IDs of duplicate users Gdn::SQL()->Query('update ' . $NewPrefix . 'User u set u.OldID = (select u2.UserID from `' . $OldDatabase . '`.' . $OldPrefix . 'User u2 where u2.Email = u.Email limit 1)'); // Copy non-duplicate users Gdn::SQL()->Query('insert into ' . $NewPrefix . 'User (' . $UserColumns . ', OldID) select ' . $UserColumns . ', UserID from `' . $OldDatabase . '`.' . $OldPrefix . 'User where Email not in (select Email from ' . $NewPrefix . 'User)'); // UserMeta if ($this->OldTableExists('UserMeta')) { Gdn::SQL()->Query('insert ignore into ' . $NewPrefix . 'UserMeta (UserID, Name, Value) select u.UserID, um.Name, um.Value from ' . $NewPrefix . 'User u, `' . $OldDatabase . '`.' . $OldPrefix . 'UserMeta um where u.OldID = um.UserID'); } } // ROLES // if ($this->OldTableExists('Role')) { $RoleColumns = $this->GetColumns('Role', $OldDatabase, $OldPrefix); // Merge IDs of duplicate roles Gdn::SQL()->Query('update ' . $NewPrefix . 'Role r set r.OldID = (select r2.RoleID from `' . $OldDatabase . '`.' . $OldPrefix . 'Role r2 where r2.Name = r.Name)'); // Copy non-duplicate roles Gdn::SQL()->Query('insert into ' . $NewPrefix . 'Role (' . $RoleColumns . ', OldID) select ' . $RoleColumns . ', RoleID from `' . $OldDatabase . '`.' . $OldPrefix . 'Role where Name not in (select Name from ' . $NewPrefix . 'Role)'); // UserRole if ($this->OldTableExists('UserRole')) { Gdn::SQL()->Query('insert ignore into ' . $NewPrefix . 'UserRole (RoleID, UserID) select r.RoleID, u.UserID from ' . $NewPrefix . 'User u, ' . $NewPrefix . 'Role r, `' . $OldDatabase . '`.' . $OldPrefix . 'UserRole ur where u.OldID = (ur.UserID) and r.OldID = (ur.RoleID)'); } } // CATEGORIES // if ($this->OldTableExists('Category')) { $CategoryColumnOptions = array('Legacy' => $DoLegacy); $CategoryColumns = $this->GetColumns('Category', $OldDatabase, $OldPrefix, $CategoryColumnOptions); /*if ($this->MergeCategories) { // Merge IDs of duplicate category names Gdn::SQL()->Query('update '.$NewPrefix.'Category c set c.OldID = (select c2.CategoryID from `'.$OldDatabase.'`.'.$OldPrefix.'Category c2 where c2.Name = c.Name)'); // Copy non-duplicate categories Gdn::SQL()->Query('insert into '.$NewPrefix.'Category ('.$CategoryColumns.', OldID) select '.$CategoryColumns.', CategoryID from `'.$OldDatabase.'`.'.$OldPrefix.'Category where Name not in (select Name from '.$NewPrefix.'Category)'); } else {*/ // Import categories if ($DoLegacy) { Gdn::SQL()->Query('insert into ' . $NewPrefix . 'Category (' . $CategoryColumns . ', OldID, ForeignID) select ' . $CategoryColumns . ', CategoryID, concat(\'' . $LegacySlug . '-\', CategoryID) from `' . $OldDatabase . '`.' . $OldPrefix . 'Category where Name <> "Root"'); } else { Gdn::SQL()->Query('insert into ' . $NewPrefix . 'Category (' . $CategoryColumns . ', OldID) select ' . $CategoryColumns . ', CategoryID from `' . $OldDatabase . '`.' . $OldPrefix . 'Category where Name <> "Root"'); } // Remap hierarchy in the ugliest way possible $CategoryMap = array(); $Categories = Gdn::SQL()->Select('CategoryID')->Select('ParentCategoryID')->Select('OldID')->From('Category')->Where(array('OldID >' => 0))->Get()->Result(DATASET_TYPE_ARRAY); foreach ($Categories as $Category) { $CategoryMap[$Category['OldID']] = $Category['CategoryID']; } foreach ($Categories as $Category) { if ($Category['ParentCategoryID'] > 0 && !empty($CategoryMap[$Category['ParentCategoryID']])) { $ParentID = $CategoryMap[$Category['ParentCategoryID']]; Gdn::SQL()->Update('Category')->Set(array('ParentCategoryID' => $ParentID))->Where(array('CategoryID' => $Category['CategoryID']))->Put(); } } $CategoryModel = new CategoryModel(); $CategoryModel->RebuildTree(); //} // UserCategory } // DISCUSSIONS // if ($this->OldTableExists('Discussion')) { $DiscussionColumnOptions = array('Legacy' => $DoLegacy); $DiscussionColumns = $this->GetColumns('Discussion', $OldDatabase, $OldPrefix, $DiscussionColumnOptions); // Copy over all discussions if ($DoLegacy) { Gdn::SQL()->Query('insert into ' . $NewPrefix . 'Discussion (' . $DiscussionColumns . ', OldID, ForeignID) select ' . $DiscussionColumns . ', DiscussionID, concat(\'' . $LegacySlug . '-\', DiscussionID) from `' . $OldDatabase . '`.' . $OldPrefix . 'Discussion'); } else { Gdn::SQL()->Query('insert into ' . $NewPrefix . 'Discussion (' . $DiscussionColumns . ', OldID) select ' . $DiscussionColumns . ', DiscussionID from `' . $OldDatabase . '`.' . $OldPrefix . 'Discussion'); } // Convert imported discussions to use new UserIDs Gdn::SQL()->Query('update ' . $NewPrefix . 'Discussion d set d.InsertUserID = (SELECT u.UserID from ' . $NewPrefix . 'User u where u.OldID = d.InsertUserID) where d.OldID > 0'); Gdn::SQL()->Query('update ' . $NewPrefix . 'Discussion d set d.UpdateUserID = (SELECT u.UserID from ' . $NewPrefix . 'User u where u.OldID = d.UpdateUserID) where d.OldID > 0 and d.UpdateUserID is not null'); Gdn::SQL()->Query('update ' . $NewPrefix . 'Discussion d set d.CategoryID = (SELECT c.CategoryID from ' . $NewPrefix . 'Category c where c.OldID = d.CategoryID) where d.OldID > 0'); // UserDiscussion if ($this->OldTableExists('UserDiscussion')) { Gdn::SQL()->Query('insert ignore into ' . $NewPrefix . 'UserDiscussion (DiscussionID, UserID, Score, CountComments, DateLastViewed, Dismissed, Bookmarked) select d.DiscussionID, u.UserID, ud.Score, ud.CountComments, ud.DateLastViewed, ud.Dismissed, ud.Bookmarked from ' . $NewPrefix . 'User u, ' . $NewPrefix . 'Discussion d, `' . $OldDatabase . '`.' . $OldPrefix . 'UserDiscussion ud where u.OldID = (ud.UserID) and d.OldID = (ud.DiscussionID)'); } } // COMMENTS // if ($this->OldTableExists('Comment')) { $CommentColumnOptions = array('Legacy' => $DoLegacy); $CommentColumns = $this->GetColumns('Comment', $OldDatabase, $OldPrefix, $CommentColumnOptions); // Copy over all comments if ($DoLegacy) { Gdn::SQL()->Query('insert into ' . $NewPrefix . 'Comment (' . $CommentColumns . ', OldID, ForeignID) select ' . $CommentColumns . ', CommentID, concat(\'' . $LegacySlug . '-\', CommentID) from `' . $OldDatabase . '`.' . $OldPrefix . 'Comment'); } else { Gdn::SQL()->Query('insert into ' . $NewPrefix . 'Comment (' . $CommentColumns . ', OldID) select ' . $CommentColumns . ', CommentID from `' . $OldDatabase . '`.' . $OldPrefix . 'Comment'); } // Convert imported comments to use new UserIDs Gdn::SQL()->Query('update ' . $NewPrefix . 'Comment c set c.InsertUserID = (SELECT u.UserID from ' . $NewPrefix . 'User u where u.OldID = c.InsertUserID) where c.OldID > 0'); Gdn::SQL()->Query('update ' . $NewPrefix . 'Comment c set c.UpdateUserID = (SELECT u.UserID from ' . $NewPrefix . 'User u where u.OldID = c.UpdateUserID) where c.OldID > 0 and c.UpdateUserID is not null'); // Convert imported comments to use new DiscussionIDs Gdn::SQL()->Query('update ' . $NewPrefix . 'Comment c set c.DiscussionID = (SELECT d.DiscussionID from ' . $NewPrefix . 'Discussion d where d.OldID = c.DiscussionID) where c.OldID > 0'); } // MEDIA // if ($this->OldTableExists('Media')) { $MediaColumns = $this->GetColumns('Media', $OldDatabase, $OldPrefix); // Copy over all media Gdn::SQL()->Query('insert into ' . $NewPrefix . 'Media (' . $MediaColumns . ', OldID) select ' . $MediaColumns . ', MediaID from `' . $OldDatabase . '`.' . $OldPrefix . 'Media'); // InsertUserID Gdn::SQL()->Query('update ' . $NewPrefix . 'Media m set m.InsertUserID = (SELECT u.UserID from ' . $NewPrefix . 'User u where u.OldID = m.InsertUserID) where m.OldID > 0'); // ForeignID / ForeignTable //Gdn::SQL()->Query('update '.$NewPrefix.'Media m // set m.ForeignID = (SELECT c.CommentID from '.$NewPrefix.'Comment c where c.OldID = m.ForeignID) // where m.OldID > 0 and m.ForeignTable = \'comment\''); Gdn::SQL()->Query('update ' . $NewPrefix . 'Media m set m.ForeignID = (SELECT d.DiscussionID from ' . $NewPrefix . 'Discussion d where d.OldID = m.ForeignID) where m.OldID > 0 and m.ForeignTable = \'discussion\''); } // CONVERSATION // if ($this->OldTableExists('Conversation')) { $ConversationColumns = $this->GetColumns('Conversation', $OldDatabase, $OldPrefix); // Copy over all Conversations Gdn::SQL()->Query('insert into ' . $NewPrefix . 'Conversation (' . $ConversationColumns . ', OldID) select ' . $ConversationColumns . ', ConversationID from `' . $OldDatabase . '`.' . $OldPrefix . 'Conversation'); // InsertUserID Gdn::SQL()->Query('update ' . $NewPrefix . 'Conversation c set c.InsertUserID = (SELECT u.UserID from ' . $NewPrefix . 'User u where u.OldID = c.InsertUserID) where c.OldID > 0'); // UpdateUserID Gdn::SQL()->Query('update ' . $NewPrefix . 'Conversation c set c.UpdateUserID = (SELECT u.UserID from ' . $NewPrefix . 'User u where u.OldID = c.UpdateUserID) where c.OldID > 0'); // Contributors // a. Build userid lookup $Users = Gdn::SQL()->Query('select UserID, OldID from ' . $NewPrefix . 'User'); $UserIDLookup = array(); foreach ($Users->Result() as $User) { $OldID = GetValue('OldID', $User); $UserIDLookup[$OldID] = GetValue('UserID', $User); } // b. Translate contributor userids $Conversations = Gdn::SQL()->Query('select ConversationID, Contributors from ' . $NewPrefix . 'Conversation where Contributors <> ""'); foreach ($Conversations->Result() as $Conversation) { $Contributors = dbdecode(GetValue('Contributors', $Conversation)); if (!is_array($Contributors)) { continue; } $UpdatedContributors = array(); foreach ($Contributors as $UserID) { if (isset($UserIDLookup[$UserID])) { $UpdatedContributors[] = $UserIDLookup[$UserID]; } } // c. Update each conversation $ConversationID = GetValue('ConversationID', $Conversation); Gdn::SQL()->Query('update ' . $NewPrefix . 'Conversation set Contributors = "' . mysql_real_escape_string(dbencode($UpdatedContributors)) . '" where ConversationID = ' . $ConversationID); } // ConversationMessage // Copy over all ConversationMessages Gdn::SQL()->Query('insert into ' . $NewPrefix . 'ConversationMessage (ConversationID,Body,Format, InsertUserID,DateInserted,InsertIPAddress,OldID) select ConversationID,Body,Format,InsertUserID,DateInserted,InsertIPAddress,MessageID from `' . $OldDatabase . '`.' . $OldPrefix . 'ConversationMessage'); // ConversationID Gdn::SQL()->Query('update ' . $NewPrefix . 'ConversationMessage cm set cm.ConversationID = (SELECT c.ConversationID from ' . $NewPrefix . 'Conversation c where c.OldID = cm.ConversationID) where cm.OldID > 0'); // InsertUserID Gdn::SQL()->Query('update ' . $NewPrefix . 'ConversationMessage c set c.InsertUserID = (SELECT u.UserID from ' . $NewPrefix . 'User u where u.OldID = c.InsertUserID) where c.OldID > 0'); // Conversation FirstMessageID Gdn::SQL()->Query('update ' . $NewPrefix . 'Conversation c set c.FirstMessageID = (SELECT cm.MessageID from ' . $NewPrefix . 'ConversationMessage cm where cm.OldID = c.FirstMessageID) where c.OldID > 0'); // Conversation LastMessageID Gdn::SQL()->Query('update ' . $NewPrefix . 'Conversation c set c.LastMessageID = (SELECT cm.MessageID from ' . $NewPrefix . 'ConversationMessage cm where cm.OldID = c.LastMessageID) where c.OldID > 0'); // UserConversation Gdn::SQL()->Query('insert ignore into ' . $NewPrefix . 'UserConversation (ConversationID, UserID, CountReadMessages, DateLastViewed, DateCleared, Bookmarked, Deleted, DateConversationUpdated) select c.ConversationID, u.UserID, uc.CountReadMessages, uc.DateLastViewed, uc.DateCleared, uc.Bookmarked, uc.Deleted, uc.DateConversationUpdated from ' . $NewPrefix . 'User u, ' . $NewPrefix . 'Conversation c, `' . $OldDatabase . '`.' . $OldPrefix . 'UserConversation uc where u.OldID = (uc.UserID) and c.OldID = (uc.ConversationID)'); } // POLLS // if ($this->OldTableExists('Poll')) { $PollColumns = $this->GetColumns('Poll', $OldDatabase, $OldPrefix); $PollOptionColumns = $this->GetColumns('PollOption', $OldDatabase, $OldPrefix); // Copy over all polls & options Gdn::SQL()->Query('insert into ' . $NewPrefix . 'Poll (' . $PollColumns . ', OldID) select ' . $PollColumns . ', PollID from `' . $OldDatabase . '`.' . $OldPrefix . 'Poll'); Gdn::SQL()->Query('insert into ' . $NewPrefix . 'PollOption (' . $PollOptionColumns . ', OldID) select ' . $PollOptionColumns . ', PollOptionID from `' . $OldDatabase . '`.' . $OldPrefix . 'PollOption'); // Convert imported options to use new PollIDs Gdn::SQL()->Query('update ' . $NewPrefix . 'PollOption o set o.PollID = (SELECT p.DiscussionID from ' . $NewPrefix . 'Poll p where p.OldID = o.PollID) where o.OldID > 0'); // Convert imported polls & options to use new UserIDs Gdn::SQL()->Query('update ' . $NewPrefix . 'Poll p set p.InsertUserID = (SELECT u.UserID from ' . $NewPrefix . 'User u where u.OldID = p.InsertUserID) where p.OldID > 0'); Gdn::SQL()->Query('update ' . $NewPrefix . 'PollOption o set o.InsertUserID = (SELECT u.UserID from ' . $NewPrefix . 'User u where u.OldID = o.InsertUserID) where o.OldID > 0'); } // TAGS // if ($this->OldTableExists('Tag')) { $TagColumns = $this->GetColumns('Tag', $OldDatabase, $OldPrefix); $TagDiscussionColumns = $this->GetColumns('TagDiscussion', $OldDatabase, $OldPrefix); // Record reference of source forum tag ID Gdn::SQL()->Query('update ' . $NewPrefix . 'Tag t set t.OldID = (select t2.TagID from `' . $OldDatabase . '`.' . $OldPrefix . 'Tag t2 where t2.Name = t.Name limit 1)'); // Import tags not present in destination forum Gdn::SQL()->Query('insert into ' . $NewPrefix . 'Tag (' . $TagColumns . ', OldID) select ' . $TagColumns . ', TagID from `' . $OldDatabase . '`.' . $OldPrefix . 'Tag where Name not in (select Name from ' . $NewPrefix . 'Tag)'); // TagDiscussion if ($this->OldTableExists('TagDiscussion')) { // Insert source tag:discussion mapping Gdn::SQL()->Query('insert ignore into ' . $NewPrefix . 'TagDiscussion (TagID, DiscussionID, OldCategoryID) select t.TagID, d.DiscussionID, td.CategoryID from ' . $NewPrefix . 'Tag t, ' . $NewPrefix . 'Discussion d, `' . $OldDatabase . '`.' . $OldPrefix . 'TagDiscussion td where t.OldID = (td.TagID) and d.OldID = (td.DiscussionID)'); /** * Incoming tags may or may not have CategoryIDs associated with them, so we'll need to update them with a * current CategoryID, if applicable, based on the original category ID (OldCategoryID) from the source */ Gdn::SQL()->Query('update ' . $NewPrefix . 'TagDiscussion td set CategoryID = (select c.CategoryID from ' . $NewPrefix . 'Category c where c.OldID = td.OldCategoryID limit 1) where OldCategoryID > 0'); } } //// // Draft - new UserIDs // Activity - wallpost, activitycomment // Tag - new UserID, merge on name // TagDiscussion - new DiscussionID, TagID // Update counters // LastCommentID }
/** * * * @param array $Data * @param bool $Preference * @param array $Options * @return array|bool|string|null * @throws Exception */ public function save($Data, $Preference = false, $Options = []) { 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 null; // 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 null; } } $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, ['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 = []; 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'] = dbdecode($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'] = dbencode($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; $this->prune(); } } 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, ['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; }
<?php if (!defined('APPLICATION')) { exit; } /** * Conversations stub content for a new site. * * Called by ConversationsHooks::Setup() to insert stub content upon enabling app. * * @copyright 2009-2016 Vanilla Forums Inc. * @license http://www.opensource.org/licenses/gpl-2.0.php GNU GPL v2 * @package Conversations * @since 2.2 */ // Only do this once, ever. if (!$Drop) { return; } $SQL = Gdn::database()->sql(); // Prep default content $ConversationBody = "Pssst. Hey. A conversation is a private chat between two or more members. No one can see it except the members added. You can delete this one since I’m just a bot and know better than to talk back."; $SystemUserID = Gdn::userModel()->getSystemUserID(); $TargetUserID = Gdn::session()->UserID; $Now = Gdn_Format::toDateTime(); $Contributors = dbencode(array($SystemUserID, $TargetUserID)); // Insert stub conversation $ConversationID = $SQL->insert('Conversation', array('InsertUserID' => $SystemUserID, 'DateInserted' => $Now, 'Contributors' => $Contributors, 'CountMessages' => 1)); $MessageID = $SQL->insert('ConversationMessage', array('ConversationID' => $ConversationID, 'Body' => t('StubConversationBody', $ConversationBody), 'Format' => 'Html', 'InsertUserID' => $SystemUserID, 'DateInserted' => $Now)); $SQL->update('Conversation')->set('LastMessageID', $MessageID)->where('ConversationID', $ConversationID)->put(); $SQL->insert('UserConversation', array('ConversationID' => $ConversationID, 'UserID' => $TargetUserID, 'CountReadMessages' => 0, 'LastMessageID' => $MessageID, 'DateConversationUpdated' => $Now));
/** * Restores a single entry from the log. * * @param array $Log The log entry. * @param bool $DeleteLog Whether or not to delete the log entry after the restore. * @throws Exception Throws an exception if restoring the record causes a validation error. */ private function restoreOne($Log, $DeleteLog = true) { // Throw an event to see if the restore is being overridden. $Handled = false; $this->EventArguments['Handled'] =& $Handled; $this->EventArguments['Log'] =& $Log; $this->fireEvent('BeforeRestore'); if ($Handled) { return; // a plugin handled the restore. } if ($Log['RecordType'] == 'Configuration') { throw new Gdn_UserException('Restoring configuration edits is currently not supported.'); } if ($Log['RecordType'] == 'Registration') { $TableName = 'User'; } else { $TableName = $Log['RecordType']; } $Data = $Log['Data']; if (isset($Data['Attributes'])) { $Attr = 'Attributes'; } elseif (isset($Data['Data'])) { $Attr = 'Data'; } else { $Attr = ''; } if ($Attr) { if (is_string($Data[$Attr])) { $Data[$Attr] = dbdecode($Data[$Attr]); } // Record a bit of information about the restoration. if (!is_array($Data[$Attr])) { $Data[$Attr] = []; } $Data[$Attr]['RestoreUserID'] = Gdn::session()->UserID; $Data[$Attr]['DateRestored'] = Gdn_Format::toDateTime(); } if (!isset($Columns[$TableName])) { $Columns[$TableName] = Gdn::sql()->fetchColumns($TableName); } $Set = array_flip($Columns[$TableName]); // Set the sets from the data. foreach ($Set as $Key => $Value) { if (isset($Data[$Key])) { $Value = $Data[$Key]; if (is_array($Value)) { $Value = dbencode($Value); } $Set[$Key] = $Value; } else { unset($Set[$Key]); } } switch ($Log['Operation']) { case 'Edit': // We are restoring an edit so just update the record. $IDColumn = $Log['RecordType'] . 'ID'; $Where = [$IDColumn => $Log['RecordID']]; unset($Set[$IDColumn]); Gdn::sql()->put($TableName, $Set, $Where); break; case 'Delete': case 'Spam': case 'Moderate': case 'Pending': case 'Ban': if (!$Log['RecordID']) { // This log entry was never in the table. if (isset($Set['DateInserted'])) { $Set['DateInserted'] = Gdn_Format::toDateTime(); } } // Insert the record back into the db. if ($Log['Operation'] == 'Spam' && $Log['RecordType'] == 'Registration') { saveToConfig(['Garden.Registration.NameUnique' => false, 'Garden.Registration.EmailUnique' => false], '', false); if (isset($Data['Username'])) { $Set['Name'] = $Data['Username']; } $ID = Gdn::userModel()->insertForBasic($Set, false, ['ValidateSpam' => false]); if (!$ID) { throw new Exception(Gdn::userModel()->Validation->resultsText()); } else { Gdn::userModel()->sendWelcomeEmail($ID, '', 'Register'); } } else { $ID = Gdn::sql()->options('Replace', true)->insert($TableName, $Set); if (!$ID && isset($Log['RecordID'])) { $ID = $Log['RecordID']; } // Unban a user. if ($Log['RecordType'] == 'User' && $Log['Operation'] == 'Ban') { Gdn::userModel()->setField($ID, 'Banned', 0); } // Keep track of a discussion ID so that its count can be recalculated. if ($Log['Operation'] != 'Edit') { switch ($Log['RecordType']) { case 'Discussion': $this->recalcIDs['Discussion'][$ID] = true; break; case 'Comment': $this->recalcIDs['Discussion'][$Log['ParentRecordID']] = true; break; } } if ($Log['Operation'] == 'Pending') { switch ($Log['RecordType']) { case 'Discussion': if (val('UserDiscussion', $this->recalcIDs) && val($Log['RecordUserID'], $this->recalcIDs['UserDiscussion'])) { $this->recalcIDs['UserDiscussion'][$Log['RecordUserID']]++; } else { $this->recalcIDs['UserDiscussion'][$Log['RecordUserID']] = 1; } break; case 'Comment': if (val('UserComment', $this->recalcIDs) && val($Log['RecordUserID'], $this->recalcIDs['UserComment'])) { $this->recalcIDs['UserComment'][$Log['RecordUserID']]++; } else { $this->recalcIDs['UserComment'][$Log['RecordUserID']] = 1; } break; } } } break; } // Fire 'after' event if (isset($ID)) { $this->EventArguments['InsertID'] = $ID; } $this->fireEvent('AfterRestore'); if ($DeleteLog) { Gdn::sql()->delete('Log', ['LogID' => $Log['LogID']]); } }
/** * Saves a name/value to the user's specified $Column. * * This method throws exceptions when errors are encountered. Use try catch blocks to capture these exceptions. * * @param string $Column The name of the serialized column to save to. At the time of this writing there are three serialized columns on the user table: Permissions, Preferences, and Attributes. * @param int $UserID The UserID to save. * @param mixed $Name The name of the value being saved, or an associative array of name => value pairs to be saved. If this is an associative array, the $Value argument will be ignored. * @param mixed $Value The value being saved. */ public function saveToSerializedColumn($Column, $UserID, $Name, $Value = '') { // Load the existing values $UserData = $this->getID($UserID, DATASET_TYPE_OBJECT); if (!$UserData) { throw new Exception(sprintf('User %s not found.', $UserID)); } $Values = val($Column, $UserData); if (!is_array($Values) && !is_object($Values)) { $Values = dbdecode($UserData->{$Column}); } // Throw an exception if the field was not empty but is also not an object or array if (is_string($Values) && $Values != '') { throw new Exception(sprintf(t('Serialized column "%s" failed to be unserialized.'), $Column)); } if (!is_array($Values)) { $Values = []; } // Hook for plugins $this->EventArguments['CurrentValues'] =& $Values; $this->EventArguments['Column'] =& $Column; $this->EventArguments['UserID'] =& $UserID; $this->EventArguments['Name'] =& $Name; $this->EventArguments['Value'] =& $Value; $this->fireEvent('BeforeSaveSerialized'); // Assign the new value(s) if (!is_array($Name)) { $Name = [$Name => $Value]; } $RawValues = array_merge($Values, $Name); $Values = []; foreach ($RawValues as $Key => $RawValue) { if (!is_null($RawValue)) { $Values[$Key] = $RawValue; } } $Values = dbencode($Values); // Save the values back to the db $SaveResult = $this->SQL->put('User', [$Column => $Values], ['UserID' => $UserID]); $this->clearCache($UserID, ['user']); return $SaveResult; }
/** * Place a name/value pair into the user's session stash. * * @param string $name The key of the stash value. * @param mixed $value The value of the stash to set. Pass null to retrieve the key. * @param bool $unsetOnRetrieve Whether or not to unset the key from stash. * * @return mixed Returns the value of the stash or null on failure. */ public function stash($name = '', $value = '', $unsetOnRetrieve = true) { if ($name == '') { return; } // Create a fresh copy of the Sql object to avoid pollution. $sql = clone Gdn::sql(); $sql->reset(); // Grab the user's session. $session = $this->getStashSession($sql, $value); if (!$session) { return; } // Stash or unstash the value depending on inputs. if ($value != '') { $session->Attributes[$name] = $value; } else { $value = val($name, $session->Attributes); if ($unsetOnRetrieve) { unset($session->Attributes[$name]); } } // Update the attributes. $sql->put('Session', ['DateUpdated' => Gdn_Format::toDateTime(), 'Attributes' => dbencode($session->Attributes)], ['SessionID' => $session->SessionID]); return $value; }
/** * Create or update a comment. * * @since 2.0.0 * @access public * * @param int $DiscussionID Unique ID to add the comment to. If blank, this method will throw an error. */ public function comment($DiscussionID = '') { // Get $DiscussionID from RequestArgs if valid if ($DiscussionID == '' && count($this->RequestArgs)) { if (is_numeric($this->RequestArgs[0])) { $DiscussionID = $this->RequestArgs[0]; } } // If invalid $DiscussionID, get from form. $this->Form->setModel($this->CommentModel); $DiscussionID = is_numeric($DiscussionID) ? $DiscussionID : $this->Form->getFormValue('DiscussionID', 0); // Set discussion data $this->DiscussionID = $DiscussionID; $this->Discussion = $Discussion = $this->DiscussionModel->getID($DiscussionID); // Is this an embedded comment being posted to a discussion that doesn't exist yet? $vanilla_type = $this->Form->getFormValue('vanilla_type', ''); $vanilla_url = $this->Form->getFormValue('vanilla_url', ''); $vanilla_category_id = $this->Form->getFormValue('vanilla_category_id', ''); $Attributes = array('ForeignUrl' => $vanilla_url); $vanilla_identifier = $this->Form->getFormValue('vanilla_identifier', ''); $isEmbeddedComments = $vanilla_url != '' && $vanilla_identifier != ''; // Only allow vanilla identifiers of 32 chars or less - md5 if larger if (strlen($vanilla_identifier) > 32) { $Attributes['vanilla_identifier'] = $vanilla_identifier; $vanilla_identifier = md5($vanilla_identifier); } if (!$Discussion && $isEmbeddedComments) { $Discussion = $Discussion = $this->DiscussionModel->getForeignID($vanilla_identifier, $vanilla_type); if ($Discussion) { $this->DiscussionID = $DiscussionID = $Discussion->DiscussionID; $this->Form->setValue('DiscussionID', $DiscussionID); } } // If so, create it! if (!$Discussion && $isEmbeddedComments) { // Add these values back to the form if they exist! $this->Form->addHidden('vanilla_identifier', $vanilla_identifier); $this->Form->addHidden('vanilla_type', $vanilla_type); $this->Form->addHidden('vanilla_url', $vanilla_url); $this->Form->addHidden('vanilla_category_id', $vanilla_category_id); $PageInfo = fetchPageInfo($vanilla_url); if (!($Title = $this->Form->getFormValue('Name'))) { $Title = val('Title', $PageInfo, ''); if ($Title == '') { $Title = t('Undefined discussion subject.'); if (!empty($PageInfo['Exception']) && $PageInfo['Exception'] === "Couldn't connect to host.") { $Title .= ' ' . t('Page timed out.'); } } } $Description = val('Description', $PageInfo, ''); $Images = val('Images', $PageInfo, array()); $LinkText = t('EmbededDiscussionLinkText', 'Read the full story here'); if (!$Description && count($Images) == 0) { $Body = formatString('<p><a href="{Url}">{LinkText}</a></p>', array('Url' => $vanilla_url, 'LinkText' => $LinkText)); } else { $Body = formatString(' <div class="EmbeddedContent">{Image}<strong>{Title}</strong> <p>{Excerpt}</p> <p><a href="{Url}">{LinkText}</a></p> <div class="ClearFix"></div> </div>', array('Title' => $Title, 'Excerpt' => $Description, 'Image' => count($Images) > 0 ? img(val(0, $Images), array('class' => 'LeftAlign')) : '', 'Url' => $vanilla_url, 'LinkText' => $LinkText)); } if ($Body == '') { $Body = $vanilla_url; } if ($Body == '') { $Body = t('Undefined discussion body.'); } // Validate the CategoryID for inserting. $Category = CategoryModel::categories($vanilla_category_id); if (!$Category) { $vanilla_category_id = c('Vanilla.Embed.DefaultCategoryID', 0); if ($vanilla_category_id <= 0) { // No default category defined, so grab the first non-root category and use that. $vanilla_category_id = $this->DiscussionModel->SQL->select('CategoryID')->from('Category')->where('CategoryID >', 0)->get()->firstRow()->CategoryID; // No categories in the db? default to 0 if (!$vanilla_category_id) { $vanilla_category_id = 0; } } } else { $vanilla_category_id = $Category['CategoryID']; } $EmbedUserID = c('Garden.Embed.UserID'); if ($EmbedUserID) { $EmbedUser = Gdn::userModel()->getID($EmbedUserID); } if (!$EmbedUserID || !$EmbedUser) { $EmbedUserID = Gdn::userModel()->getSystemUserID(); } $EmbeddedDiscussionData = array('InsertUserID' => $EmbedUserID, 'DateInserted' => Gdn_Format::toDateTime(), 'DateUpdated' => Gdn_Format::toDateTime(), 'CategoryID' => $vanilla_category_id, 'ForeignID' => $vanilla_identifier, 'Type' => $vanilla_type, 'Name' => $Title, 'Body' => $Body, 'Format' => 'Html', 'Attributes' => dbencode($Attributes)); $this->EventArguments['Discussion'] =& $EmbeddedDiscussionData; $this->fireEvent('BeforeEmbedDiscussion'); $DiscussionID = $this->DiscussionModel->SQL->insert('Discussion', $EmbeddedDiscussionData); $ValidationResults = $this->DiscussionModel->validationResults(); if (count($ValidationResults) == 0 && $DiscussionID > 0) { $this->Form->addHidden('DiscussionID', $DiscussionID); // Put this in the form so reposts won't cause new discussions. $this->Form->setFormValue('DiscussionID', $DiscussionID); // Put this in the form values so it is used when saving comments. $this->setJson('DiscussionID', $DiscussionID); $this->Discussion = $Discussion = $this->DiscussionModel->getID($DiscussionID, DATASET_TYPE_OBJECT, array('Slave' => false)); // Update the category discussion count if ($vanilla_category_id > 0) { $this->DiscussionModel->updateDiscussionCount($vanilla_category_id, $DiscussionID); } } } // If no discussion was found, error out if (!$Discussion) { $this->Form->addError(t('Failed to find discussion for commenting.')); } /** * Special care is taken for embedded comments. Since we don't currently use an advanced editor for these * comments, we may need to apply certain filters and fixes to the data to maintain its intended display * with the input format (e.g. maintaining newlines). */ if ($isEmbeddedComments) { $inputFormatter = $this->Form->getFormValue('Format', c('Garden.InputFormatter')); switch ($inputFormatter) { case 'Wysiwyg': $this->Form->setFormValue('Body', nl2br($this->Form->getFormValue('Body'))); break; } } $PermissionCategoryID = val('PermissionCategoryID', $Discussion); // Setup head $this->addJsFile('jquery.autosize.min.js'); $this->addJsFile('autosave.js'); $this->addJsFile('post.js'); // Setup comment model, $CommentID, $DraftID $Session = Gdn::session(); $CommentID = isset($this->Comment) && property_exists($this->Comment, 'CommentID') ? $this->Comment->CommentID : ''; $DraftID = isset($this->Comment) && property_exists($this->Comment, 'DraftID') ? $this->Comment->DraftID : ''; $this->EventArguments['CommentID'] = $CommentID; $this->EventArguments['DraftID'] = $DraftID; // Determine whether we are editing $Editing = $CommentID > 0 || $DraftID > 0; $this->EventArguments['Editing'] = $Editing; // If closed, cancel & go to discussion if ($Discussion && $Discussion->Closed == 1 && !$Editing && !$Session->checkPermission('Vanilla.Discussions.Close', true, 'Category', $PermissionCategoryID)) { redirect(DiscussionUrl($Discussion)); } // Add hidden IDs to form $this->Form->addHidden('DiscussionID', $DiscussionID); $this->Form->addHidden('CommentID', $CommentID); $this->Form->addHidden('DraftID', $DraftID, true); // Check permissions if ($Discussion && $Editing) { // Permission to edit if ($this->Comment->InsertUserID != $Session->UserID) { $this->permission('Vanilla.Comments.Edit', true, 'Category', $Discussion->PermissionCategoryID); } // Make sure that content can (still) be edited. $EditContentTimeout = c('Garden.EditContentTimeout', -1); $CanEdit = $EditContentTimeout == -1 || strtotime($this->Comment->DateInserted) + $EditContentTimeout > time(); if (!$CanEdit) { $this->permission('Vanilla.Comments.Edit', true, 'Category', $Discussion->PermissionCategoryID); } // Make sure only moderators can edit closed things if ($Discussion->Closed) { $this->permission('Vanilla.Comments.Edit', true, 'Category', $Discussion->PermissionCategoryID); } $this->Form->setFormValue('CommentID', $CommentID); } elseif ($Discussion) { // Permission to add $this->permission('Vanilla.Comments.Add', true, 'Category', $Discussion->PermissionCategoryID); } if ($this->Form->authenticatedPostBack()) { // Save as a draft? $FormValues = $this->Form->formValues(); $FormValues = $this->CommentModel->filterForm($FormValues); if (!$Editing) { unset($FormValues['CommentID']); } if ($DraftID == 0) { $DraftID = $this->Form->getFormValue('DraftID', 0); } $Type = GetIncomingValue('Type'); $Draft = $Type == 'Draft'; $this->EventArguments['Draft'] = $Draft; $Preview = $Type == 'Preview'; if ($Draft) { $DraftID = $this->DraftModel->save($FormValues); $this->Form->addHidden('DraftID', $DraftID, true); $this->Form->setValidationResults($this->DraftModel->validationResults()); } elseif (!$Preview) { // Fix an undefined title if we can. if ($this->Form->getFormValue('Name') && val('Name', $Discussion) == t('Undefined discussion subject.')) { $Set = array('Name' => $this->Form->getFormValue('Name')); if (isset($vanilla_url) && $vanilla_url && strpos(val('Body', $Discussion), t('Undefined discussion subject.')) !== false) { $LinkText = t('EmbededDiscussionLinkText', 'Read the full story here'); $Set['Body'] = formatString('<p><a href="{Url}">{LinkText}</a></p>', array('Url' => $vanilla_url, 'LinkText' => $LinkText)); } $this->DiscussionModel->setField(val('DiscussionID', $Discussion), $Set); } $Inserted = !$CommentID; $CommentID = $this->CommentModel->save($FormValues); // The comment is now half-saved. if (is_numeric($CommentID) && $CommentID > 0) { if (in_array($this->deliveryType(), array(DELIVERY_TYPE_ALL, DELIVERY_TYPE_DATA))) { $this->CommentModel->save2($CommentID, $Inserted, true, true); } else { $this->jsonTarget('', url("/post/comment2.json?commentid={$CommentID}&inserted={$Inserted}"), 'Ajax'); } // $Discussion = $this->DiscussionModel->getID($DiscussionID); $Comment = $this->CommentModel->getID($CommentID, DATASET_TYPE_OBJECT, array('Slave' => false)); $this->EventArguments['Discussion'] = $Discussion; $this->EventArguments['Comment'] = $Comment; $this->fireEvent('AfterCommentSave'); } elseif ($CommentID === SPAM || $CommentID === UNAPPROVED) { $this->StatusMessage = t('CommentRequiresApprovalStatus', 'Your comment will appear after it is approved.'); } $this->Form->setValidationResults($this->CommentModel->validationResults()); if ($CommentID > 0 && $DraftID > 0) { $this->DraftModel->delete($DraftID); } } // Handle non-ajax requests first: if ($this->_DeliveryType == DELIVERY_TYPE_ALL) { if ($this->Form->errorCount() == 0) { // Make sure that this form knows what comment we are editing. if ($CommentID > 0) { $this->Form->addHidden('CommentID', $CommentID); } // If the comment was not a draft if (!$Draft) { // Redirect to the new comment. if ($CommentID > 0) { redirect("discussion/comment/{$CommentID}/#Comment_{$CommentID}"); } elseif ($CommentID == SPAM) { $this->setData('DiscussionUrl', DiscussionUrl($Discussion)); $this->View = 'Spam'; } } elseif ($Preview) { // If this was a preview click, create a comment shell with the values for this comment $this->Comment = new stdClass(); $this->Comment->InsertUserID = $Session->User->UserID; $this->Comment->InsertName = $Session->User->Name; $this->Comment->InsertPhoto = $Session->User->Photo; $this->Comment->DateInserted = Gdn_Format::date(); $this->Comment->Body = val('Body', $FormValues, ''); $this->Comment->Format = val('Format', $FormValues, c('Garden.InputFormatter')); $this->addAsset('Content', $this->fetchView('preview')); } else { // If this was a draft save, notify the user about the save $this->informMessage(sprintf(t('Draft saved at %s'), Gdn_Format::date())); } } } else { // Handle ajax-based requests if ($this->Form->errorCount() > 0) { // Return the form errors $this->errorMessage($this->Form->errors()); } else { // Make sure that the ajax request form knows about the newly created comment or draft id $this->setJson('CommentID', $CommentID); $this->setJson('DraftID', $DraftID); if ($Preview) { // If this was a preview click, create a comment shell with the values for this comment $this->Comment = new stdClass(); $this->Comment->InsertUserID = $Session->User->UserID; $this->Comment->InsertName = $Session->User->Name; $this->Comment->InsertPhoto = $Session->User->Photo; $this->Comment->DateInserted = Gdn_Format::date(); $this->Comment->Body = val('Body', $FormValues, ''); $this->Comment->Format = val('Format', $FormValues, c('Garden.InputFormatter')); $this->View = 'preview'; } elseif (!$Draft) { // If the comment was not a draft // If Editing a comment if ($Editing) { // Just reload the comment in question $this->Offset = 1; $Comments = $this->CommentModel->getIDData($CommentID, array('Slave' => false)); $this->setData('Comments', $Comments); $this->setData('Discussion', $Discussion); // Load the discussion $this->ControllerName = 'discussion'; $this->View = 'comments'; // Also define the discussion url in case this request came from the post screen and needs to be redirected to the discussion $this->setJson('DiscussionUrl', DiscussionUrl($this->Discussion) . '#Comment_' . $CommentID); } else { // If the comment model isn't sorted by DateInserted or CommentID then we can't do any fancy loading of comments. $OrderBy = valr('0.0', $this->CommentModel->orderBy()); // $Redirect = !in_array($OrderBy, array('c.DateInserted', 'c.CommentID')); // $DisplayNewCommentOnly = $this->Form->getFormValue('DisplayNewCommentOnly'); // if (!$Redirect) { // // Otherwise load all new comments that the user hasn't seen yet // $LastCommentID = $this->Form->getFormValue('LastCommentID'); // if (!is_numeric($LastCommentID)) // $LastCommentID = $CommentID - 1; // Failsafe back to this new comment if the lastcommentid was not defined properly // // // Don't reload the first comment if this new comment is the first one. // $this->Offset = $LastCommentID == 0 ? 1 : $this->CommentModel->GetOffset($LastCommentID); // // Do not load more than a single page of data... // $Limit = c('Vanilla.Comments.PerPage', 30); // // // Redirect if the new new comment isn't on the same page. // $Redirect |= !$DisplayNewCommentOnly && PageNumber($this->Offset, $Limit) != PageNumber($Discussion->CountComments - 1, $Limit); // } // if ($Redirect) { // // The user posted a comment on a page other than the last one, so just redirect to the last page. // $this->RedirectUrl = Gdn::request()->Url("discussion/comment/$CommentID/#Comment_$CommentID", true); // } else { // // Make sure to load all new comments since the page was last loaded by this user // if ($DisplayNewCommentOnly) $this->Offset = $this->CommentModel->GetOffset($CommentID); $Comments = $this->CommentModel->GetIDData($CommentID, array('Slave' => false)); $this->setData('Comments', $Comments); $this->setData('NewComments', true); $this->ClassName = 'DiscussionController'; $this->ControllerName = 'discussion'; $this->View = 'comments'; // } // Make sure to set the user's discussion watch records $CountComments = $this->CommentModel->getCount($DiscussionID); $Limit = is_object($this->data('Comments')) ? $this->data('Comments')->numRows() : $Discussion->CountComments; $Offset = $CountComments - $Limit; $this->CommentModel->SetWatch($this->Discussion, $Limit, $Offset, $CountComments); } } else { // If this was a draft save, notify the user about the save $this->informMessage(sprintf(t('Draft saved at %s'), Gdn_Format::date())); } // And update the draft count $UserModel = Gdn::userModel(); $CountDrafts = $UserModel->getAttribute($Session->UserID, 'CountDrafts', 0); $this->setJson('MyDrafts', t('My Drafts')); $this->setJson('CountDrafts', $CountDrafts); } } } elseif ($this->Request->isPostBack()) { throw new Gdn_UserException(t('Invalid CSRF token.', 'Invalid CSRF token. Please try again.'), 401); } else { // Load form if (isset($this->Comment)) { $this->Form->setData((array) $this->Comment); } } // Include data for FireEvent if (property_exists($this, 'Discussion')) { $this->EventArguments['Discussion'] = $this->Discussion; } if (property_exists($this, 'Comment')) { $this->EventArguments['Comment'] = $this->Comment; } $this->fireEvent('BeforeCommentRender'); if ($this->deliveryType() == DELIVERY_TYPE_DATA) { if ($this->data('Comments') instanceof Gdn_DataSet) { $Comment = $this->data('Comments')->firstRow(DATASET_TYPE_ARRAY); if ($Comment) { $Photo = $Comment['InsertPhoto']; if (strpos($Photo, '//') === false) { $Photo = Gdn_Upload::url(changeBasename($Photo, 'n%s')); } $Comment['InsertPhoto'] = $Photo; } $this->Data = array('Comment' => $Comment); } $this->RenderData($this->Data); } else { require_once $this->fetchViewLocation('helper_functions', 'Discussion'); // Render default view. $this->render(); } }
/** * * * @param $Table * @param $Key * @param int $Limit * @param bool $Max * @return array|mixed */ public function getBatch($Table, $Key, $Limit = 10000, $Max = false) { $Key = "DBA.Range.{$Key}"; // See if there is already a range. $Current = dbdecode(Gdn::get($Key, '')); if (!is_array($Current) || !isset($Current['Min']) || !isset($Current['Max'])) { list($Current['Min'], $Current['Max']) = $this->primaryKeyRange($Table); if ($Max && $Current['Max'] > $Max) { $Current['Max'] = $Max; } } if (!isset($Current['To'])) { $Current['To'] = $Current['Max']; } else { $Current['To'] -= $Limit - 1; } $Current['From'] = $Current['To'] - $Limit; Gdn::set($Key, dbencode($Current)); $Current['Complete'] = $Current['To'] < $Current['Min']; $Total = $Current['Max'] - $Current['Min']; if ($Total > 0) { $Complete = $Current['Max'] - $Current['From']; $Percent = 100 * $Complete / $Total; if ($Percent > 100) { $Percent = 100; } $Current['Percent'] = round($Percent) . '%'; } return $Current; }
/** * Get a user's permissions. * * @param int $userID Unique ID of the user. * @return Vanilla\Permissions */ public function getPermissions($userID) { $permissions = new Vanilla\Permissions(); $permissionsKey = ''; if (Gdn::cache()->activeEnabled()) { $permissionsIncrement = $this->getPermissionsIncrement(); $permissionsKey = formatString(self::USERPERMISSIONS_KEY, ['UserID' => $userID, 'PermissionsIncrement' => $permissionsIncrement]); $cachedPermissions = Gdn::cache()->get($permissionsKey); if ($cachedPermissions !== Gdn_Cache::CACHEOP_FAILURE) { $permissions->setPermissions($cachedPermissions); return $permissions; } } $data = Gdn::permissionModel()->cachePermissions($userID); $permissions->compileAndLoad($data); $this->EventArguments['UserID'] = $userID; $this->EventArguments['Permissions'] = $permissions; $this->fireEvent('loadPermissions'); if (Gdn::cache()->activeEnabled()) { Gdn::cache()->store($permissionsKey, $permissions->getPermissions()); } else { // Save the permissions to the user table if ($userID > 0) { $this->SQL->put('User', ['Permissions' => dbencode($permissions->getPermissions())], ['UserID' => $userID]); } } return $permissions; }
/** * Place a name/value pair into the user's session stash. */ public function stash($Name = '', $Value = '', $UnsetOnRetrieve = true) { if ($Name == '') { return; } // Grab the user's session $Session = $this->_getStashSession($Value); if (!$Session) { return; } // Stash or unstash the value depending on inputs if ($Name != '' && $Value != '') { $Session->Attributes[$Name] = $Value; } elseif ($Name != '') { $Value = val($Name, $Session->Attributes); if ($UnsetOnRetrieve) { unset($Session->Attributes[$Name]); } } // Update the attributes if ($Name != '') { Gdn::SQL()->put('Session', array('DateUpdated' => Gdn_Format::toDateTime(), 'Attributes' => dbencode($Session->Attributes)), array('SessionID' => $Session->SessionID)); } return $Value; }