/** * Check whether or not the record is spam. * @param string $RecordType By default, this should be one of the following: * - Comment: A comment. * - Discussion: A discussion. * - User: A user registration. * @param array $Data The record data. * @param array $Options Options for fine-tuning this method call. * - Log: Log the record if it is found to be spam. */ public static function isSpam($RecordType, $Data, $Options = array()) { if (self::$Disabled) { return false; } // Set some information about the user in the data. if ($RecordType == 'Registration') { touchValue('Username', $Data, $Data['Name']); } else { touchValue('InsertUserID', $Data, Gdn::session()->UserID); $User = Gdn::userModel()->getID(val('InsertUserID', $Data), DATASET_TYPE_ARRAY); if ($User) { if (val('Verified', $User)) { // The user has been verified and isn't a spammer. return false; } touchValue('Username', $Data, $User['Name']); touchValue('Email', $Data, $User['Email']); touchValue('IPAddress', $Data, $User['LastIPAddress']); } } if (!isset($Data['Body']) && isset($Data['Story'])) { $Data['Body'] = $Data['Story']; } touchValue('IPAddress', $Data, Gdn::request()->ipAddress()); $Sp = self::_Instance(); $Sp->EventArguments['RecordType'] = $RecordType; $Sp->EventArguments['Data'] =& $Data; $Sp->EventArguments['Options'] =& $Options; $Sp->EventArguments['IsSpam'] = false; $Sp->fireEvent('CheckSpam'); $Spam = $Sp->EventArguments['IsSpam']; // Log the spam entry. if ($Spam && val('Log', $Options, true)) { $LogOptions = array(); switch ($RecordType) { case 'Registration': $LogOptions['GroupBy'] = array('RecordIPAddress'); break; case 'Comment': case 'Discussion': case 'Activity': case 'ActivityComment': $LogOptions['GroupBy'] = array('RecordID'); break; } LogModel::insert('Spam', $RecordType, $Data, $LogOptions); } return $Spam; }
/** * Add an item to the items array. * * @param string $type The type of the item: link, group or divider. * @param array $item The item to add to the array. * @throws Exception */ protected function addItem($type, $item) { $this->touchKey($item); if (!is_array(val('key', $item))) { $item['key'] = explode('.', val('key', $item)); } else { $item['key'] = array_values(val('key', $item)); } $item = (array) $item; // Make sure the link has its type. $item['type'] = $type; // Walk into the items list to set the item. $items =& $this->items; foreach (val('key', $item) as $i => $key_part) { if ($i === count(val('key', $item)) - 1) { // Add the item here. if (array_key_exists($key_part, $items)) { // The item is already here so merge this one on top of it. if ($items[$key_part]['type'] !== $type) { throw new \Exception(val('key', $item) . " of type {$type} does not match existing type {$items[$key_part]['type']}.", 500); } $items[$key_part] = array_merge($items[$key_part], $item); } else { // The item is new so just add it here. touchValue('_sort', $item, count($items)); $items[$key_part] = $item; } } else { // This is a group. if (!array_key_exists($key_part, $items)) { // The group doesn't exist so lazy-create it. $items[$key_part] = array('type' => 'group', 'text' => '', 'items' => [], '_sort' => count($items)); } elseif ($items[$key_part]['type'] !== 'group') { throw new \Exception("{$key_part} is not a group", 500); } elseif (!array_key_exists('items', $items[$key_part])) { // Lazy create the items array. $items[$key_part]['items'] = []; } $items =& $items[$key_part]['items']; } } }
/** * Create or update a discussion. * * @since 2.0.0 * @access public * * @param int $CategoryID Unique ID of the category to add the discussion to. */ public function discussion($CategoryUrlCode = '') { // Override CategoryID if categories are disabled $UseCategories = $this->ShowCategorySelector = (bool) c('Vanilla.Categories.Use'); if (!$UseCategories) { $CategoryUrlCode = ''; } // Setup head $this->addJsFile('jquery.autosize.min.js'); $this->addJsFile('autosave.js'); $this->addJsFile('post.js'); $Session = Gdn::session(); Gdn_Theme::section('PostDiscussion'); // Set discussion, draft, and category data $DiscussionID = isset($this->Discussion) ? $this->Discussion->DiscussionID : ''; $DraftID = isset($this->Draft) ? $this->Draft->DraftID : 0; $Category = false; $CategoryModel = new CategoryModel(); if (isset($this->Discussion)) { $this->CategoryID = $this->Discussion->CategoryID; $Category = CategoryModel::categories($this->CategoryID); } elseif ($CategoryUrlCode != '') { $Category = CategoryModel::categories($CategoryUrlCode); if ($Category) { $this->CategoryID = val('CategoryID', $Category); } } if ($Category) { $this->Category = (object) $Category; $this->setData('Category', $Category); $this->Form->addHidden('CategoryID', $this->Category->CategoryID); if (val('DisplayAs', $this->Category) == 'Discussions' && !$DraftID) { $this->ShowCategorySelector = false; } else { // Get all our subcategories to add to the category if we are in a Header or Categories category. $this->Context = CategoryModel::getSubtree($this->CategoryID); } } else { $this->CategoryID = 0; $this->Category = null; } $CategoryData = $this->ShowCategorySelector ? CategoryModel::categories() : false; // Check permission if (isset($this->Discussion)) { // Make sure that content can (still) be edited. $CanEdit = DiscussionModel::canEdit($this->Discussion); if (!$CanEdit) { throw permissionException('Vanilla.Discussions.Edit'); } // Make sure only moderators can edit closed things if ($this->Discussion->Closed) { $this->permission('Vanilla.Discussions.Edit', true, 'Category', $this->Category->PermissionCategoryID); } $this->Form->setFormValue('DiscussionID', $this->Discussion->DiscussionID); $this->title(t('Edit Discussion')); if ($this->Discussion->Type) { $this->setData('Type', $this->Discussion->Type); } else { $this->setData('Type', 'Discussion'); } } else { // Permission to add. if ($this->Category) { $this->permission('Vanilla.Discussions.Add', true, 'Category', $this->Category->PermissionCategoryID); } else { $this->permission('Vanilla.Discussions.Add'); } $this->title(t('New Discussion')); } touchValue('Type', $this->Data, 'Discussion'); // See if we should hide the category dropdown. if ($this->ShowCategorySelector) { $AllowedCategories = CategoryModel::getByPermission('Discussions.Add', $this->Form->getValue('CategoryID', $this->CategoryID), ['Archived' => 0, 'AllowDiscussions' => 1], ['AllowedDiscussionTypes' => $this->Data['Type']]); if (count($AllowedCategories) == 1) { $AllowedCategory = array_pop($AllowedCategories); $this->ShowCategorySelector = false; $this->Form->addHidden('CategoryID', $AllowedCategory['CategoryID']); if ($this->Form->isPostBack() && !$this->Form->getFormValue('CategoryID')) { $this->Form->setFormValue('CategoryID', $AllowedCategory['CategoryID']); } } } // Set the model on the form $this->Form->setModel($this->DiscussionModel); if (!$this->Form->isPostBack()) { // Prep form with current data for editing if (isset($this->Discussion)) { $this->Form->setData($this->Discussion); } elseif (isset($this->Draft)) { $this->Form->setData($this->Draft); } else { if ($this->Category !== null) { $this->Form->setData(array('CategoryID' => $this->Category->CategoryID)); } $this->populateForm($this->Form); } } elseif ($this->Form->authenticatedPostBack()) { // Form was submitted // Save as a draft? $FormValues = $this->Form->formValues(); $FormValues = $this->DiscussionModel->filterForm($FormValues); $this->deliveryType(Gdn::request()->getValue('DeliveryType', $this->_DeliveryType)); if ($DraftID == 0) { $DraftID = $this->Form->getFormValue('DraftID', 0); } $Draft = $this->Form->buttonExists('Save_Draft') ? true : false; $Preview = $this->Form->buttonExists('Preview') ? true : false; if (!$Preview) { if (!is_object($this->Category) && is_array($CategoryData) && isset($FormValues['CategoryID'])) { $this->Category = val($FormValues['CategoryID'], $CategoryData); } if (is_object($this->Category)) { // Check category permissions. if ($this->Form->getFormValue('Announce', '') && !$Session->checkPermission('Vanilla.Discussions.Announce', true, 'Category', $this->Category->PermissionCategoryID)) { $this->Form->addError('You do not have permission to announce in this category', 'Announce'); } if ($this->Form->getFormValue('Close', '') && !$Session->checkPermission('Vanilla.Discussions.Close', true, 'Category', $this->Category->PermissionCategoryID)) { $this->Form->addError('You do not have permission to close in this category', 'Close'); } if ($this->Form->getFormValue('Sink', '') && !$Session->checkPermission('Vanilla.Discussions.Sink', true, 'Category', $this->Category->PermissionCategoryID)) { $this->Form->addError('You do not have permission to sink in this category', 'Sink'); } if (!isset($this->Discussion) && (!$Session->checkPermission('Vanilla.Discussions.Add', true, 'Category', $this->Category->PermissionCategoryID) || !$this->Category->AllowDiscussions)) { $this->Form->addError('You do not have permission to start discussions in this category', 'CategoryID'); } } $isTitleValid = true; $Name = trim($this->Form->getFormValue('Name', '')); if (!$Draft) { // Let's be super aggressive and disallow titles with no word characters in them! $hasWordCharacter = preg_match('/\\w/u', $Name) === 1; if (!$hasWordCharacter || $Name != '' && Gdn_Format::text($Name) == '') { $this->Form->addError(t('You have entered an invalid discussion title'), 'Name'); $isTitleValid = false; } } if ($isTitleValid) { // Trim the name. $FormValues['Name'] = $Name; $this->Form->setFormValue('Name', $Name); } if ($this->Form->errorCount() == 0) { if ($Draft) { $DraftID = $this->DraftModel->save($FormValues); $this->Form->setValidationResults($this->DraftModel->validationResults()); } else { $DiscussionID = $this->DiscussionModel->save($FormValues); $this->Form->setValidationResults($this->DiscussionModel->validationResults()); if ($DiscussionID > 0) { if ($DraftID > 0) { $this->DraftModel->delete($DraftID); } } if ($DiscussionID == SPAM || $DiscussionID == UNAPPROVED) { $this->StatusMessage = t('DiscussionRequiresApprovalStatus', 'Your discussion will appear after it is approved.'); // Clear out the form so that a draft won't save. $this->Form->formValues(array()); $this->render('Spam'); return; } } } } else { // If this was a preview click, create a discussion/comment shell with the values for this comment $this->Discussion = new stdClass(); $this->Discussion->Name = $this->Form->getValue('Name', ''); $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->EventArguments['Discussion'] =& $this->Discussion; $this->EventArguments['Comment'] =& $this->Comment; $this->fireEvent('BeforeDiscussionPreview'); if ($this->_DeliveryType == DELIVERY_TYPE_ALL) { $this->addAsset('Content', $this->fetchView('preview')); } else { $this->View = 'preview'; } } if ($this->Form->errorCount() > 0) { // Return the form errors $this->errorMessage($this->Form->errors()); } elseif ($DiscussionID > 0 || $DraftID > 0) { // Make sure that the ajax request form knows about the newly created discussion or draft id $this->setJson('DiscussionID', $DiscussionID); $this->setJson('DraftID', $DraftID); if (!$Preview) { // If the discussion was not a draft if (!$Draft) { // Redirect to the new discussion $Discussion = $this->DiscussionModel->getID($DiscussionID, DATASET_TYPE_OBJECT, array('Slave' => false)); $this->setData('Discussion', $Discussion); $this->EventArguments['Discussion'] = $Discussion; $this->fireEvent('AfterDiscussionSave'); if ($this->_DeliveryType == DELIVERY_TYPE_ALL) { redirect(discussionUrl($Discussion, 1)) . '?new=1'; } else { $this->RedirectUrl = discussionUrl($Discussion, 1, true) . '?new=1'; } } else { // If this was a draft save, notify the user about the save $this->informMessage(sprintf(t('Draft saved at %s'), Gdn_Format::date())); } } } } // Add hidden fields for editing $this->Form->addHidden('DiscussionID', $DiscussionID); $this->Form->addHidden('DraftID', $DraftID, true); $this->fireEvent('BeforeDiscussionRender'); if ($this->CategoryID) { $Breadcrumbs = CategoryModel::getAncestors($this->CategoryID); } else { $Breadcrumbs = array(); } $Breadcrumbs[] = array('Name' => $this->data('Title'), 'Url' => val('AddUrl', val($this->data('Type'), DiscussionModel::discussionTypes()), '/post/discussion')); $this->setData('Breadcrumbs', $Breadcrumbs); $this->setData('_AnnounceOptions', $this->announceOptions()); // Render view (posts/discussion.php or post/preview.php) $this->render(); }
function renderAdventureNav($items) { foreach ($items as $item) { if (val('type', $item) == 'group') { $heading = val('text', $item); if (!$heading) { touchValue('cssClass', $item, ''); $item['cssClass'] .= ' nav-group-noheading'; } ?> <div type="group" class="nav-group <?php echo val('cssClass', $item); ?> "> <?php if ($heading) { echo '<h3>' . val('text', $item) . '</h3>'; } if (val('items', $item)) { renderAdventureNav(val('items', $item)); } echo '</div>'; } if (val('type', $item) == 'link') { ?> <div class="nav-item"> <a role="menuitem" class="nav-link <?php echo val('cssClass', $item); ?> " tabindex="-1" href="<?php echo url(val('url', $item)); ?> "> <div class="nav-item-icon"><?php echo icon(val('icon', $item)); ?> </div> <div class="nav-item-content"> <div class="nav-item-title"><?php echo val('text', $item); ?> </div> <div class="nav-item-description"> <?php echo val('description', $item); ?> </div> </div> <div class="nav-item-arrow"><?php echo dashboardSymbol('chevron-right'); ?> </div> </a> </div> <?php } if (val('type', $item) == 'divider') { echo '<hr/>'; } } }
/** * * * @param $Data * @param null $CategoryID * @return bool */ public static function joinRecentPosts(&$Data, $CategoryID = null) { $DiscussionIDs = array(); $CommentIDs = array(); $Joined = false; foreach ($Data as &$Row) { if (!is_null($CategoryID) && $Row['CategoryID'] != $CategoryID) { continue; } if (isset($Row['LastTitle']) && $Row['LastTitle']) { continue; } if ($Row['LastDiscussionID']) { $DiscussionIDs[] = $Row['LastDiscussionID']; } if ($Row['LastCommentID']) { $CommentIDs[] = $Row['LastCommentID']; } $Joined = true; } // Create a fresh copy of the Sql object so as not to pollute. $Sql = clone Gdn::sql(); $Sql->reset(); $Discussions = null; // Grab the discussions. if (count($DiscussionIDs) > 0) { $Discussions = $Sql->whereIn('DiscussionID', $DiscussionIDs)->get('Discussion')->resultArray(); $Discussions = Gdn_DataSet::Index($Discussions, array('DiscussionID')); } if (count($CommentIDs) > 0) { $Comments = $Sql->whereIn('CommentID', $CommentIDs)->get('Comment')->resultArray(); $Comments = Gdn_DataSet::Index($Comments, array('CommentID')); } foreach ($Data as &$Row) { if (!is_null($CategoryID) && $Row['CategoryID'] != $CategoryID) { continue; } $Discussion = val($Row['LastDiscussionID'], $Discussions); $NameUrl = 'x'; if ($Discussion) { $Row['LastTitle'] = Gdn_Format::text($Discussion['Name']); $Row['LastUserID'] = $Discussion['InsertUserID']; $Row['LastDiscussionUserID'] = $Discussion['InsertUserID']; $Row['LastDateInserted'] = $Discussion['DateInserted']; $NameUrl = Gdn_Format::text($Discussion['Name'], true); $Row['LastUrl'] = DiscussionUrl($Discussion, false, '/') . '#latest'; } if (!empty($Comments) && ($Comment = val($Row['LastCommentID'], $Comments))) { $Row['LastUserID'] = $Comment['InsertUserID']; $Row['LastDateInserted'] = $Comment['DateInserted']; $Row['DateLastComment'] = $Comment['DateInserted']; } else { $Row['NoComment'] = true; } touchValue('LastTitle', $Row, ''); touchValue('LastUserID', $Row, null); touchValue('LastDiscussionUserID', $Row, null); touchValue('LastDateInserted', $Row, null); touchValue('LastUrl', $Row, null); } return $Joined; }
/** * Save message from form submission. * * @since 2.0.0 * @access public * * @param array $FormPostValues Values submitted via form. * @return int Unique ID of message created or updated. */ public function save($FormPostValues, $Conversation = null, $Options = array()) { $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->addInsertFields($FormPostValues); $this->EventArguments['FormPostValues'] = $FormPostValues; $this->fireEvent('BeforeSaveValidation'); // Determine if spam check should be skipped. $SkipSpamCheck = !empty($Options['NewConversation']); // Validate the form posted values $MessageID = false; if ($this->validate($FormPostValues) && !$this->checkForSpam('ConversationMessage', $SkipSpamCheck)) { $Fields = $this->Validation->schemaValidationFields(); // All fields on the form that relate to the schema touchValue('Format', $Fields, c('Garden.InputFormatter', 'Html')); $this->EventArguments['Fields'] = $Fields; $this->fireEvent('BeforeSave'); $MessageID = $this->SQL->insert($this->Name, $Fields); $this->LastMessageID = $MessageID; $ConversationID = val('ConversationID', $Fields, 0); if (!$Conversation) { $Conversation = $this->SQL->getWhere('Conversation', array('ConversationID' => $ConversationID))->firstRow(DATASET_TYPE_ARRAY); } $Message = $this->getID($MessageID); $this->EventArguments['Conversation'] = $Conversation; $this->EventArguments['Message'] = $Message; $this->fireEvent('AfterSave'); // Get the new message count for the conversation. $SQLR = $this->SQL->select('MessageID', 'count', 'CountMessages')->select('MessageID', 'max', 'LastMessageID')->from('ConversationMessage')->where('ConversationID', $ConversationID)->get()->firstRow(DATASET_TYPE_ARRAY); if (sizeof($SQLR)) { list($CountMessages, $LastMessageID) = array_values($SQLR); } else { return; } // Update the conversation's DateUpdated field. $DateUpdated = Gdn_Format::toDateTime(); $this->SQL->update('Conversation c')->set('CountMessages', $CountMessages)->set('LastMessageID', $LastMessageID)->set('UpdateUserID', Gdn::session()->UserID)->set('DateUpdated', $DateUpdated)->where('ConversationID', $ConversationID)->put(); // Update the last message of the users that were previously up-to-date on their read messages. $this->SQL->update('UserConversation uc')->set('uc.LastMessageID', $MessageID)->set('uc.DateConversationUpdated', $DateUpdated)->where('uc.ConversationID', $ConversationID)->where('uc.Deleted', '0')->where('uc.CountReadMessages', $CountMessages - 1)->where('uc.UserID <>', $Session->UserID)->put(); // Update the date updated of the users that were not up-to-date. $this->SQL->update('UserConversation uc')->set('uc.DateConversationUpdated', $DateUpdated)->where('uc.ConversationID', $ConversationID)->where('uc.Deleted', '0')->where('uc.CountReadMessages <>', $CountMessages - 1)->where('uc.UserID <>', $Session->UserID)->put(); // Update the sending user. $this->SQL->update('UserConversation uc')->set('uc.CountReadMessages', $CountMessages)->set('Deleted', 0)->set('uc.DateConversationUpdated', $DateUpdated)->where('ConversationID', $ConversationID)->where('UserID', $Session->UserID)->put(); // Find users involved in this conversation $UserData = $this->SQL->select('UserID')->select('LastMessageID')->select('Deleted')->from('UserConversation')->where('ConversationID', $ConversationID)->get()->result(DATASET_TYPE_ARRAY); $UpdateCountUserIDs = array(); $NotifyUserIDs = array(); // Collapse for call to UpdateUserCache and ActivityModel. $InsertUserFound = false; foreach ($UserData as $UpdateUser) { $LastMessageID = val('LastMessageID', $UpdateUser); $UserID = val('UserID', $UpdateUser); $Deleted = val('Deleted', $UpdateUser); if ($UserID == val('InsertUserID', $Fields)) { $InsertUserFound = true; if ($Deleted) { $this->SQL->put('UserConversation', array('Deleted' => 0, 'DateConversationUpdated' => $DateUpdated), array('ConversationID' => $ConversationID, 'UserID' => $UserID)); } } // Update unread for users that were up to date if ($LastMessageID == $MessageID) { $UpdateCountUserIDs[] = $UserID; } // Send activities to users that have not deleted the conversation if (!$Deleted) { $NotifyUserIDs[] = $UserID; } } if (!$InsertUserFound) { $UserConversation = array('UserID' => val('InsertUserID', $Fields), 'ConversationID' => $ConversationID, 'LastMessageID' => $LastMessageID, 'CountReadMessages' => $CountMessages, 'DateConversationUpdated' => $DateUpdated); $this->SQL->insert('UserConversation', $UserConversation); } if (sizeof($UpdateCountUserIDs)) { $ConversationModel = new ConversationModel(); $ConversationModel->updateUserUnreadCount($UpdateCountUserIDs, true); } $this->fireEvent('AfterAdd'); $activityModel = new ActivityModel(); foreach ($NotifyUserIDs as $notifyUserID) { if ($Session->UserID == $notifyUserID) { continue; // don't notify self. } // Notify the users of the new message. $activity = array('ActivityType' => 'ConversationMessage', 'ActivityUserID' => val('InsertUserID', $Fields), 'NotifyUserID' => $notifyUserID, 'HeadlineFormat' => t('HeadlineFormat.ConversationMessage', '{ActivityUserID,user} sent you a <a href="{Url,html}">message</a>'), 'RecordType' => 'Conversation', 'RecordID' => $ConversationID, 'Story' => val('Body', $Fields, ''), 'Format' => val('Format', $Fields, c('Garden.InputFormatter')), 'Route' => "/messages/{$ConversationID}#{$MessageID}"); if (c('Conversations.Subjects.Visible') && val('Subject', $Conversation, '')) { $activity['HeadlineFormat'] = val('Subject', $Conversation, ''); } $activityModel->queue($activity, 'ConversationMessage'); } $activityModel->saveQueue(); } return $MessageID; }
/** * Parent override. * * @param array &$Fields */ public function addInsertFields(&$Fields) { $this->defineSchema(); // Set the hour offset based on the client's clock. $ClientHour = val('ClientHour', $Fields, ''); if (is_numeric($ClientHour) && $ClientHour >= 0 && $ClientHour < 24) { $HourOffset = $ClientHour - date('G', time()); $Fields['HourOffset'] = $HourOffset; } // Set some required dates. $Now = Gdn_Format::toDateTime(); $Fields[$this->DateInserted] = $Now; touchValue('DateFirstVisit', $Fields, $Now); $Fields['DateLastActive'] = $Now; $Fields['InsertIPAddress'] = ipEncode(Gdn::request()->ipAddress()); $Fields['LastIPAddress'] = ipEncode(Gdn::request()->ipAddress()); }
/** * Resolve relative static resources into full paths * * This method is used to translate CSS, Js and Template relative file lists * into absolute paths. * * Element values should conform to the following format: * * [] => array( * 'FileName' => // filename (relative, absolute, or URL) * 'AppFolder' => // optional application folder to target (default controller app) * ); * * @param array $resourceList * @param string $stub * @param array $options Optional. List of check options. * - 'GlobalLibrary' // Check $Stub/library in global section * - 'StripRoot' // Strip PATH_ROOT from final results * - 'CDNS' // List of external CDN replacements * @param array $checkLocations Optional. List of locations to check. * - 'themes' * - 'plugins' * - 'applications' * - 'global' */ public static function resolveStaticResources($resourceList, $stub, $options = null, $checkLocations = null) { // All locations by default if (!is_array($checkLocations)) { $checkLocations = array('themes', 'plugins', 'applications', 'global'); } // Default options $defaultOptions = array('GlobalLibrary' => true, 'StripRoot' => true, 'CDNS' => array(), 'AutoVersion' => true); if (!is_array($options)) { $options = array(); } $options = array_merge($defaultOptions, $options); // Parse options $checkGlobalLibrary = val('GlobalLibrary', $options); $stripRoot = val('StripRoot', $options); $autoDetectVersion = val('AutoVersion', $options); // See if we're allowing any CDN replacements $CDNs = val('CDNS', $options, array()); // Pre-get controller info $controllerAppFolder = false; $controllerTheme = false; if (Gdn::Controller() instanceof Gdn_Controller) { $controllerAppFolder = Gdn::controller()->ApplicationFolder; $controllerTheme = Gdn::controller()->Theme; } $fileList = array(); foreach ($resourceList as $index => $resourceInfo) { $resourceFile = $resourceInfo['FileName']; $resourceFolder = val('AppFolder', $resourceInfo); $resourceOptions = (array) val('Options', $resourceInfo, false); if ($resourceFile === false) { if (!$resourceOptions) { continue; } $rawCSS = val('Css', $resourceOptions, false); if (!$rawCSS) { continue; } $cssHash = md5($rawCSS); $fileList[$resourceFolder] = array('options' => $resourceOptions); continue; } $skipFileCheck = false; // Resolve CDN resources if (array_key_exists($resourceFile, $CDNs)) { $resourceFile = $CDNs[$resourceFile]; } if (strpos($resourceFile, '//') !== false) { // This is a link to an external file. $skipFileCheck = true; $testPaths = array($resourceFile); } elseif (strpos($resourceFile, '/') === 0) { // A direct path to the file was given. $testPaths = array(paths(PATH_ROOT, $resourceFile)); } elseif (strpos($resourceFile, '~') === 0) { $skipFileCheck = true; $resourceFile = substr($resourceFile, 1); $testPaths = array(paths(PATH_ROOT, $resourceFile)); } else { // Relative path $appFolder = val('AppFolder', $resourceInfo, false); if ($appFolder == '') { $appFolder = $controllerAppFolder; } if ($appFolder == 'false') { $appFolder = false; } // Resources can come from: // - a theme // - an application // - a plugin // - global garden resource-specific folder // - global garden resource-specific library folder $testPaths = array(); // Theme if (in_array('themes', $checkLocations) && $controllerTheme) { // Application-specific theme override if ($appFolder) { $testPaths[] = paths(PATH_THEMES, $controllerTheme, $appFolder, $stub, $resourceFile); } // Garden-wide theme override $testPaths[] = paths(PATH_THEMES, $controllerTheme, $stub, $resourceFile); } // Application or plugin $isPluginFolder = stringBeginsWith(trim($appFolder, '/'), 'plugins/', true, false); if ($isPluginFolder) { $pluginFolder = stringBeginsWith(trim($appFolder, '/'), 'plugins/', true, true); } if (in_array('plugins', $checkLocations) && $isPluginFolder) { // Plugin $testPaths[] = paths(PATH_PLUGINS, $pluginFolder, $stub, $resourceFile); $testPaths[] = paths(PATH_PLUGINS, $pluginFolder, $resourceFile); } if (in_array('applications', $checkLocations) && !$isPluginFolder) { // Application if ($appFolder) { $testPaths[] = paths(PATH_APPLICATIONS, $appFolder, $stub, $resourceFile); } // Dashboard app is added by default if ($appFolder != 'dashboard') { $testPaths[] = paths(PATH_APPLICATIONS, 'dashboard', $stub, $resourceFile); } } if (in_array('global', $checkLocations)) { // Global folder. eg. root/js/ $testPaths[] = paths(PATH_ROOT, $stub, $resourceFile); if ($checkGlobalLibrary) { // Global library folder. eg. root/js/library/ $testPaths[] = paths(PATH_ROOT, $stub, 'library', $resourceFile); } } } // Find the first file that matches the path. $resourcePath = false; if (!$skipFileCheck) { foreach ($testPaths as $glob) { $paths = safeGlob($glob); if (is_array($paths) && count($paths) > 0) { $resourcePath = $paths[0]; break; } } // Get version $version = val('Version', $resourceInfo, false); // If a path was matched, make sure it has a version if ($resourcePath && !$version && $autoDetectVersion) { // Theme file if (!$version && preg_match('`themes/([^/]+)/`i', $resourcePath, $matches)) { $themeName = $matches[1]; $themeInfo = Gdn::themeManager()->getThemeInfo($themeName); $version = val('Version', $themeInfo); $versionSource = "theme {$themeName}"; } // Plugin file if (!$version && preg_match('`plugins/([^/]+)/`i', $resourcePath, $matches)) { $pluginName = $matches[1]; $pluginInfo = Gdn::pluginManager()->getPluginInfo($pluginName, Gdn_PluginManager::ACCESS_PLUGINNAME); $version = val('Version', $pluginInfo); $versionSource = "plugin {$pluginName}"; } // Application file if (!$version && preg_match('`applications/([^/]+)/`i', $resourcePath, $matches)) { $applicationName = $matches[1]; $applicationInfo = Gdn::applicationManager()->getApplicationInfo($applicationName); $version = val('Version', $applicationInfo); $versionSource = "app {$applicationName}"; } } } else { $version = null; } // Global file if (!$version) { $version = APPLICATION_VERSION; } // If a path was succesfully matched if ($resourcePath !== false || $skipFileCheck) { // We enact SkipFileCheck for virtual paths, targeting controllers // perhaps, or full URLs from the CDN resolver. if ($skipFileCheck) { $resourcePath = array_pop($testPaths); } // Strip PATH_ROOT from absolute path $resourceResolved = $resourcePath; if ($stripRoot) { $resourceResolved = str_replace(array(PATH_ROOT, DS), array('', '/'), $resourcePath); } // Bring options into response structure $resource = array('path' => $resourcePath); $resourceOptions = (array) val('Options', $resourceInfo, array()); touchValue('version', $resource, $version); if ($resourceOptions) { touchValue('options', $resource, $resourceOptions); } $fileList[$resourceResolved] = $resource; } } return $fileList; }
/** * @param SettingsController $sender * @param array $Args */ protected function settings_addEdit($sender, $Args) { $client_id = $sender->Request->Get('client_id'); Gdn::Locale()->SetTranslation('AuthenticationKey', 'Client ID'); Gdn::Locale()->SetTranslation('AssociationSecret', 'Secret'); Gdn::Locale()->SetTranslation('AuthenticateUrl', 'Authentication Url'); /* @var Gdn_Form $form */ $form = $sender->Form; $model = new Gdn_AuthenticationProviderModel(); $form->setModel($model); if ($form->authenticatedPostBack()) { if ($form->getFormValue('Generate') || $sender->Request->post('Generate')) { $form->setFormValue('AuthenticationKey', mt_rand()); $form->setFormValue('AssociationSecret', md5(mt_rand())); $sender->setFormSaved(FALSE); } else { $form->validateRule('AuthenticationKey', 'ValidateRequired'); $form->validateRule('AuthenticationKey', 'regex:`^[a-z0-9_-]+$`i', T('The client id must contain only letters, numbers and dashes.')); $form->validateRule('AssociationSecret', 'ValidateRequired'); $form->validateRule('AuthenticateUrl', 'ValidateRequired'); $form->setFormValue('AuthenticationSchemeAlias', 'jsconnect'); if ($form->save(['ID' => $client_id])) { $sender->RedirectUrl = url('/settings/jsconnect'); } } } else { if ($client_id) { $provider = self::getProvider($client_id); touchValue('Trusted', $provider, 1); } else { $provider = array(); } $form->setData($provider); } // Set up the form controls for editing the connection. $hashTypes = hash_algos(); $hashTypes = array_combine($hashTypes, $hashTypes); $controls = ['AuthenticationKey' => ['LabelCode' => 'Client ID', 'Description' => T('The client ID uniquely identifies the site.', 'The client ID uniquely identifies the site. You can generate a new ID with the button at the bottom of this page.')], 'AssociationSecret' => ['LabelCode' => 'Secret', 'Description' => T('The secret secures the sign in process.', 'The secret secures the sign in process. Do <b>NOT</b> give the secret out to anyone.')], 'Name' => ['LabelCode' => 'Site Name', 'Description' => T('Enter a short name for the site.', 'Enter a short name for the site. This is displayed on the signin buttons.')], 'AuthenticateUrl' => ['LabelCode' => 'Authentication URL', 'Description' => T('The location of the JSONP formatted authentication data.'), 'Options' => ['class' => 'InputBox BigInput']], 'SignInUrl' => ['LabelCode' => 'Sign In URL', 'Description' => T('The url that users use to sign in.') . ' ' . T('Use {target} to specify a redirect.'), 'Options' => ['class' => 'InputBox BigInput']], 'RegisterUrl' => ['LabelCode' => 'Registration URL', 'Description' => T('The url that users use to register for a new account.'), 'Options' => ['class' => 'InputBox BigInput']], 'SignOutUrl' => ['LabelCode' => 'Sign Out URL', 'Description' => T('The url that users use to sign out of your site.'), 'Options' => ['class' => 'InputBox BigInput']], 'Trusted' => ['Control' => 'checkbox', 'LabelCode' => 'This is trusted connection and can sync roles & permissions.'], 'IsDefault' => ['Control' => 'checkbox', 'LabelCode' => 'Make this connection your default signin method.'], 'Advanced' => ['Control' => 'callback', 'Callback' => function ($form) { return '<h2>' . T('Advanced') . '</h2>'; }], 'HashType' => ['Control' => 'dropdown', 'LabelCode' => 'Hash Algorithm', 'Items' => $hashTypes, 'Description' => T('Choose md5 if you\'re not sure what to choose.', "You can select a custom hash algorithm to sign your requests. The hash algorithm must also be used in your client library. Choose md5 if you're not sure what to choose."), 'Options' => ['Default' => 'md5']], 'TestMode' => ['Control' => 'checkbox', 'LabelCode' => 'This connection is in test-mode.']]; $sender->setData('_Controls', $controls); $sender->setData('Title', sprintf(T($client_id ? 'Edit %s' : 'Add %s'), T('Connection'))); // Throw a render event as this plugin so that handlers can call our methods. Gdn::pluginManager()->callEventHandlers($this, __CLASS__, 'addedit', 'render'); $sender->render('Settings_AddEdit', '', 'plugins/jsconnect'); }
/** * Check whether or not the record is spam. * @param string $RecordType By default, this should be one of the following: * - Comment: A comment. * - Discussion: A discussion. * - User: A user registration. * @param array $Data The record data. * @param array $Options Options for fine-tuning this method call. * - Log: Log the record if it is found to be spam. */ public static function isSpam($RecordType, $Data, $Options = array()) { if (self::$Disabled) { return false; } // Set some information about the user in the data. if ($RecordType == 'Registration') { touchValue('Username', $Data, $Data['Name']); } else { touchValue('InsertUserID', $Data, Gdn::session()->UserID); $User = Gdn::userModel()->getID(val('InsertUserID', $Data), DATASET_TYPE_ARRAY); if ($User) { if (val('Verified', $User)) { // The user has been verified and isn't a spammer. return false; } touchValue('Username', $Data, $User['Name']); touchValue('Email', $Data, $User['Email']); touchValue('IPAddress', $Data, $User['LastIPAddress']); } } if (!isset($Data['Body']) && isset($Data['Story'])) { $Data['Body'] = $Data['Story']; } // Make sure all IP addresses are unpacked. $Data = ipDecodeRecursive($Data); touchValue('IPAddress', $Data, Gdn::request()->ipAddress()); $Sp = self::_Instance(); $Sp->EventArguments['RecordType'] = $RecordType; $Sp->EventArguments['Data'] =& $Data; $Sp->EventArguments['Options'] =& $Options; $Sp->EventArguments['IsSpam'] = false; $Sp->fireEvent('CheckSpam'); $Spam = $Sp->EventArguments['IsSpam']; // Log the spam entry. if ($Spam && val('Log', $Options, true)) { $LogOptions = array(); switch ($RecordType) { case 'Registration': $LogOptions['GroupBy'] = array('RecordIPAddress'); break; case 'Comment': case 'Discussion': case 'Activity': case 'ActivityComment': $LogOptions['GroupBy'] = array('RecordID'); break; } // If this is a discussion or a comment, it needs some special handling. if ($RecordType == 'Comment' || $RecordType == 'Discussion') { // Grab the record ID, if available. $recordID = intval(val("{$RecordType}ID", $Data)); /** * If we have a valid record ID, run it through flagForReview. This will allow us to purge existing * discussions and comments that have been flagged as SPAM after being edited. If there's no valid ID, * just treat it with regular SPAM logging. */ if ($recordID) { self::flagForReview($RecordType, $recordID, $Data); } else { LogModel::insert('Spam', $RecordType, $Data, $LogOptions); } } else { LogModel::insert('Spam', $RecordType, $Data, $LogOptions); } } return $Spam; }
/** * Lists the connections to other sites. * * @param int|string $UserReference * @param string $Username * @since 2.1 */ public function connections($UserReference = '', $Username = '') { $this->permission('Garden.SignIn.Allow'); $this->getUserInfo($UserReference, $Username, '', true); $UserID = valr('User.UserID', $this); $this->_setBreadcrumbs(t('Social'), userUrl($this->User, '', 'connections')); $PModel = new Gdn_AuthenticationProviderModel(); $Providers = $PModel->getProviders(); $this->setData('_Providers', $Providers); $this->setData('Connections', array()); $this->EventArguments['User'] = $this->User; $this->fireEvent('GetConnections'); // Add some connection information. foreach ($this->Data['Connections'] as &$Row) { $Provider = val($Row['ProviderKey'], $Providers, array()); touchValue('Connected', $Row, !is_null(val('UniqueID', $Provider, null))); } $this->canonicalUrl(userUrl($this->User, '', 'connections')); $this->title(t('Social')); require_once $this->fetchViewLocation('connection_functions'); $this->render(); }
/** * */ public function renderMaster() { // Build the master view if necessary if (in_array($this->_DeliveryType, array(DELIVERY_TYPE_ALL))) { $this->MasterView = $this->masterView(); // Only get css & ui components if this is NOT a syndication request if ($this->SyndicationMethod == SYNDICATION_NONE && is_object($this->Head)) { // if (ArrayHasValue($this->_CssFiles, 'style.css')) { // $this->AddCssFile('custom.css'); // // // Add the theme option's css file. // if ($this->Theme && $this->ThemeOptions) { // $Filenames = GetValueR('Styles.Value', $this->ThemeOptions); // if (is_string($Filenames) && $Filenames != '%s') // $this->_CssFiles[] = array('FileName' => ChangeBasename('custom.css', $Filenames), 'AppFolder' => FALSE, 'Options' => FALSE); // } // } elseif (ArrayHasValue($this->_CssFiles, 'admin.css')) { // $this->AddCssFile('customadmin.css'); // } $this->EventArguments['CssFiles'] =& $this->_CssFiles; $this->fireEvent('BeforeAddCss'); $ETag = AssetModel::eTag(); $CombineAssets = c('Garden.CombineAssets'); $ThemeType = isMobile() ? 'mobile' : 'desktop'; // And now search for/add all css files. foreach ($this->_CssFiles as $CssInfo) { $CssFile = $CssInfo['FileName']; if (!is_array($CssInfo['Options'])) { $CssInfo['Options'] = array(); } $Options =& $CssInfo['Options']; // style.css and admin.css deserve some custom processing. if (in_array($CssFile, array('style.css', 'admin.css'))) { if (!$CombineAssets) { // Grab all of the css files from the asset model. $AssetModel = new AssetModel(); $CssFiles = $AssetModel->getCssFiles($ThemeType, ucfirst(substr($CssFile, 0, -4)), $ETag); foreach ($CssFiles as $Info) { $this->Head->addCss($Info[1], 'all', true, $CssInfo); } } else { $Basename = substr($CssFile, 0, -4); $this->Head->addCss(url("/utility/css/{$ThemeType}/{$Basename}-{$ETag}.css", '//'), 'all', false, $CssInfo['Options']); } continue; } $AppFolder = $CssInfo['AppFolder']; $LookupFolder = !empty($AppFolder) ? $AppFolder : $this->ApplicationFolder; $Search = AssetModel::CssPath($CssFile, $LookupFolder, $ThemeType); if (!$Search) { continue; } list($Path, $UrlPath) = $Search; if (isUrl($Path)) { $this->Head->AddCss($Path, 'all', val('AddVersion', $Options, true), $Options); continue; } else { // Check to see if there is a CSS cacher. $CssCacher = Gdn::factory('CssCacher'); if (!is_null($CssCacher)) { $Path = $CssCacher->get($Path, $AppFolder); } if ($Path !== false) { $Path = substr($Path, strlen(PATH_ROOT)); $Path = str_replace(DS, '/', $Path); $this->Head->addCss($Path, 'all', true, $Options); } } } // Add a custom js file. if (arrayHasValue($this->_CssFiles, 'style.css')) { $this->addJsFile('custom.js'); // only to non-admin pages. } // And now search for/add all JS files. $Cdns = array(); if (!c('Garden.Cdns.Disable', false)) { $Cdns = array('jquery.js' => "//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"); } $this->EventArguments['Cdns'] =& $Cdns; $this->fireEvent('AfterJsCdns'); $this->Head->addScript('', 'text/javascript', array('content' => $this->definitionList(false))); foreach ($this->_JsFiles as $Index => $JsInfo) { $JsFile = $JsInfo['FileName']; if (isset($Cdns[$JsFile])) { $JsFile = $Cdns[$JsFile]; } if (strpos($JsFile, '//') !== false) { // This is a link to an external file. $this->Head->addScript($JsFile, 'text/javascript', val('Options', $JsInfo, array())); continue; } elseif (strpos($JsFile, '/') !== false) { // A direct path to the file was given. $JsPaths = array(combinePaths(array(PATH_ROOT, str_replace('/', DS, $JsFile)), DS)); } else { $AppFolder = $JsInfo['AppFolder']; if ($AppFolder == '') { $AppFolder = $this->ApplicationFolder; } // JS can come from a theme, an any of the application folder, or it can come from the global js folder: $JsPaths = array(); if ($this->Theme) { // 1. Application-specific js. eg. root/themes/theme_name/app_name/design/ $JsPaths[] = PATH_THEMES . DS . $this->Theme . DS . $AppFolder . DS . 'js' . DS . $JsFile; // 2. Garden-wide theme view. eg. root/themes/theme_name/design/ $JsPaths[] = PATH_THEMES . DS . $this->Theme . DS . 'js' . DS . $JsFile; } // 3. The application or plugin folder. if (stringBeginsWith(trim($AppFolder, '/'), 'plugins/')) { $JsPaths[] = PATH_PLUGINS . strstr($AppFolder, '/') . "/js/{$JsFile}"; $JsPaths[] = PATH_PLUGINS . strstr($AppFolder, '/') . "/{$JsFile}"; } else { $JsPaths[] = PATH_APPLICATIONS . "/{$AppFolder}/js/{$JsFile}"; } // 4. Global JS folder. eg. root/js/ $JsPaths[] = PATH_ROOT . DS . 'js' . DS . $JsFile; // 5. Global JS library folder. eg. root/js/library/ $JsPaths[] = PATH_ROOT . DS . 'js' . DS . 'library' . DS . $JsFile; } // Find the first file that matches the path. $JsPath = false; foreach ($JsPaths as $Glob) { $Paths = safeGlob($Glob); if (is_array($Paths) && count($Paths) > 0) { $JsPath = $Paths[0]; break; } } if ($JsPath !== false) { $JsSrc = str_replace(array(PATH_ROOT, DS), array('', '/'), $JsPath); $Options = (array) $JsInfo['Options']; $Options['path'] = $JsPath; $Version = val('Version', $JsInfo); if ($Version) { touchValue('version', $Options, $Version); } $this->Head->addScript($JsSrc, 'text/javascript', $Options); } } } // Add the favicon. $Favicon = C('Garden.FavIcon'); if ($Favicon) { $this->Head->setFavIcon(Gdn_Upload::url($Favicon)); } // Make sure the head module gets passed into the assets collection. $this->addModule('Head'); } // Master views come from one of four places: $MasterViewPaths = array(); $MasterViewPath2 = viewLocation($this->masterView() . '.master', '', $this->ApplicationFolder); if (strpos($this->MasterView, '/') !== false) { $MasterViewPaths[] = combinePaths(array(PATH_ROOT, str_replace('/', DS, $this->MasterView) . '.master*')); } else { if ($this->Theme) { // 1. Application-specific theme view. eg. root/themes/theme_name/app_name/views/ $MasterViewPaths[] = combinePaths(array(PATH_THEMES, $this->Theme, $this->ApplicationFolder, 'views', $this->MasterView . '.master*')); // 2. Garden-wide theme view. eg. /path/to/application/themes/theme_name/views/ $MasterViewPaths[] = combinePaths(array(PATH_THEMES, $this->Theme, 'views', $this->MasterView . '.master*')); } // 3. Application default. eg. root/app_name/views/ $MasterViewPaths[] = combinePaths(array(PATH_APPLICATIONS, $this->ApplicationFolder, 'views', $this->MasterView . '.master*')); // 4. Garden default. eg. root/dashboard/views/ $MasterViewPaths[] = combinePaths(array(PATH_APPLICATIONS, 'dashboard', 'views', $this->MasterView . '.master*')); } // Find the first file that matches the path. $MasterViewPath = false; foreach ($MasterViewPaths as $Glob) { $Paths = safeGlob($Glob); if (is_array($Paths) && count($Paths) > 0) { $MasterViewPath = $Paths[0]; break; } } if ($MasterViewPath != $MasterViewPath2) { trace("Master views differ. Controller: {$MasterViewPath}, ViewLocation(): {$MasterViewPath2}", TRACE_WARNING); } $this->EventArguments['MasterViewPath'] =& $MasterViewPath; $this->fireEvent('BeforeFetchMaster'); if ($MasterViewPath === false) { trigger_error(errorMessage("Could not find master view: {$this->MasterView}.master*", $this->ClassName, '_FetchController'), E_USER_ERROR); } /// A unique identifier that can be used in the body tag of the master view if needed. $ControllerName = $this->ClassName; // Strip "Controller" from the body identifier. if (substr($ControllerName, -10) == 'Controller') { $ControllerName = substr($ControllerName, 0, -10); } // Strip "Gdn_" from the body identifier. if (substr($ControllerName, 0, 4) == 'Gdn_') { $ControllerName = substr($ControllerName, 4); } $this->setData('CssClass', $this->Application . ' ' . $ControllerName . ' ' . $this->RequestMethod . ' ' . $this->CssClass, true); // Check to see if there is a handler for this particular extension. $ViewHandler = Gdn::factory('ViewHandler' . strtolower(strrchr($MasterViewPath, '.'))); if (is_null($ViewHandler)) { $BodyIdentifier = strtolower($this->ApplicationFolder . '_' . $ControllerName . '_' . Gdn_Format::alphaNumeric(strtolower($this->RequestMethod))); include $MasterViewPath; } else { $ViewHandler->render($MasterViewPath, $this); } }
/** * Set the data definition to load/save from the config. * * @param array $Def A list of fields from the config that this form will use. */ public function schema($Def = null) { if ($Def !== null) { $Schema = array(); foreach ($Def as $Key => $Value) { $Row = array('Name' => '', 'Type' => 'string', 'Control' => 'TextBox', 'Options' => array()); if (is_numeric($Key)) { $Row['Name'] = $Value; } elseif (is_string($Value)) { $Row['Name'] = $Key; $Row['Type'] = $Value; } elseif (is_array($Value)) { $Row['Name'] = $Key; $Row = array_merge($Row, $Value); } else { $Row['Name'] = $Key; } touchValue('Config', $Row, $Row['Name']); $Schema[] = $Row; } $this->_Schema = $Schema; } return $this->_Schema; }
/** * * * @param $Data */ protected function _touch(&$Data) { touchValue('ActivityType', $Data, 'Default'); touchValue('ActivityUserID', $Data, Gdn::session()->UserID); touchValue('NotifyUserID', $Data, self::NOTIFY_PUBLIC); touchValue('Headline', $Data, null); touchValue('Story', $Data, null); touchValue('Notified', $Data, 0); touchValue('Emailed', $Data, 0); touchValue('Photo', $Data, null); touchValue('Route', $Data, null); if (!isset($Data['Data']) || !is_array($Data['Data'])) { $Data['Data'] = array(); } }
/** * Generates a multi-field form from a schema. * * @param array $Schema An array where each item of the array is a row that identifies a form field with the following information: * - Name: The name of the form field. * - Control: The type of control used for the field. This is one of the control methods on the Gdn_Form object. * - LabelCode: The translation code for the label. Optional. * - Description: An optional description for the field. * - Items: If the control is a list control then its items are specified here. * - Options: Additional options to be passed into the control. * @param type $Options Additional options to pass into the form. * - Wrap: A two item array specifying the text to wrap the form in. * - ItemWrap: A two item array specifying the text to wrap each form item in. */ public function simple($Schema, $Options = array()) { $Result = valr('Wrap.0', $Options, '<ul>'); $ItemWrap = val('ItemWrap', $Options, array("<li>\n ", "\n</li>\n")); foreach ($Schema as $Index => $Row) { if (is_string($Row)) { $Row = array('Name' => $Index, 'Control' => $Row); } if (!isset($Row['Name'])) { $Row['Name'] = $Index; } if (!isset($Row['Options'])) { $Row['Options'] = array(); } $Result .= $ItemWrap[0]; $LabelCode = self::labelCode($Row); $Description = val('Description', $Row, ''); if ($Description) { $Description = '<div class="Info">' . $Description . '</div>'; } touchValue('Control', $Row, 'TextBox'); switch (strtolower($Row['Control'])) { case 'categorydropdown': $Result .= $this->label($LabelCode, $Row['Name']) . $Description . $this->categoryDropDown($Row['Name'], $Row['Options']); break; case 'checkbox': $Result .= $Description . $this->checkBox($Row['Name'], $LabelCode); break; case 'dropdown': $Result .= $this->label($LabelCode, $Row['Name']) . $Description . $this->dropDown($Row['Name'], $Row['Items'], $Row['Options']); break; case 'radiolist': $Result .= $Description . $this->radioList($Row['Name'], $Row['Items'], $Row['Options']); break; case 'checkboxlist': $Result .= $this->label($LabelCode, $Row['Name']) . $Description . $this->checkBoxList($Row['Name'], $Row['Items'], null, $Row['Options']); break; case 'textbox': $Result .= $this->label($LabelCode, $Row['Name']) . $Description . $this->textBox($Row['Name'], $Row['Options']); break; case 'callback': $Row['DescriptionHtml'] = $Description; $Row['LabelCode'] = $LabelCode; $Result .= call_user_func($Row['Callback'], $this, $Row); break; default: $Result .= "Error a control type of {$Row['Control']} is not supported."; break; } $Result .= $ItemWrap[1]; } $Result .= valr('Wrap.1', $Options, '</ul>'); return $Result; }
/** * Generates a multi-field form from a schema. * * @param array $Schema An array where each item of the array is a row that identifies a form field with the following information: * - Name: The name of the form field. * - Control: The type of control used for the field. This is one of the control methods on the Gdn_Form object. * - LabelCode: The translation code for the label. Optional. * - Description: An optional description for the field. * - Items: If the control is a list control then its items are specified here. * - Options: Additional options to be passed into the control. * @param type $Options Additional options to pass into the form. * - Wrap: A two item array specifying the text to wrap the form in. * - ItemWrap: A two item array specifying the text to wrap each form item in. */ public function simple($Schema, $Options = array()) { $Result = valr('Wrap.0', $Options, '<ul>'); foreach ($Schema as $Index => $Row) { if (is_string($Row)) { $Row = array('Name' => $Index, 'Control' => $Row); } if (!isset($Row['Name'])) { $Row['Name'] = $Index; } if (!isset($Row['Options'])) { $Row['Options'] = array(); } if (strtolower($Row['Control']) == 'callback') { $ItemWrap = ''; } else { $ItemWrap = val('ItemWrap', $Options, array('<li class="' . $this->getStyle('form-group') . "\">\n", "\n</li>\n")); } $Result .= $ItemWrap[0]; $LabelCode = self::labelCode($Row); $image = ''; if (strtolower($Row['Control']) == 'imageupload') { $image = $this->currentImage($Row['Name'], $Row['Options']); $image = wrap($image, 'div', ['class' => 'image-wrap-label']); } $Description = val('Description', $Row, ''); if ($Description) { $Description = wrap($Description, 'div', ['class' => 'description info']); } $Description .= $image; if ($Description) { $labelWrap = wrap($this->label($LabelCode, $Row['Name']) . $Description, 'div', ['class' => 'label-wrap']); } else { $labelWrap = wrap($this->label($LabelCode, $Row['Name']), 'div', ['class' => 'label-wrap']); } touchValue('Control', $Row, 'TextBox'); switch (strtolower($Row['Control'])) { case 'categorydropdown': $Result .= $this->label($LabelCode, $Row['Name']) . $Description . $this->categoryDropDown($Row['Name'], $Row['Options']); break; case 'checkbox': $Result .= $labelWrap . wrap($this->checkBox($Row['Name'], $LabelCode, $Row['Options']), 'div', ['class' => 'input-wrap']); break; case 'toggle': $Result .= $Description . $this->toggle($Row['Name'], $LabelCode, $Row['Options']); break; case 'dropdown': $Row['Options']['Wrap'] = true; $Result .= $labelWrap . $this->dropDown($Row['Name'], $Row['Items'], $Row['Options']); break; case 'radiolist': $Result .= $labelWrap . wrap($this->radioList($Row['Name'], $Row['Items'], $Row['Options']), 'div', ['class' => 'input-wrap']); break; case 'checkboxlist': $Result .= $labelWrap . wrap($this->checkBoxList($Row['Name'], $Row['Items'], null, $Row['Options']), 'div', ['class' => 'input-wrap']); break; case 'imageupload': $Result .= $labelWrap . $this->imageUploadWrap($Row['Name'], $Row['Options']); break; case 'textbox': $Row['Options']['Wrap'] = true; $Result .= $labelWrap . $this->textBox($Row['Name'], $Row['Options']); break; case 'callback': $Row['DescriptionHtml'] = $Description; $Row['LabelCode'] = $LabelCode; $Result .= call_user_func($Row['Callback'], $this, $Row); break; default: $Result .= "Error a control type of {$Row['Control']} is not supported."; break; } $Result .= $ItemWrap[1]; } $Result .= valr('Wrap.1', $Options, '</ul>'); return $Result; }
/** * Gets the javascript definition list used to pass data to the client. * * @param bool $wrap Whether or not to wrap the result in a `script` tag. * @return string Returns a string containing the `<script>` tag of the definitions. . */ public function definitionList($wrap = true) { $Session = Gdn::session(); if (!array_key_exists('TransportError', $this->_Definitions)) { $this->_Definitions['TransportError'] = T('Transport error: %s', 'A fatal error occurred while processing the request.<br />The server returned the following response: %s'); } if (!array_key_exists('TransientKey', $this->_Definitions)) { $this->_Definitions['TransientKey'] = $Session->transientKey(); } if (!array_key_exists('WebRoot', $this->_Definitions)) { $this->_Definitions['WebRoot'] = combinePaths(array(Gdn::request()->domain(), Gdn::request()->webRoot()), '/'); } if (!array_key_exists('UrlFormat', $this->_Definitions)) { $this->_Definitions['UrlFormat'] = url('{Path}'); } if (!array_key_exists('Path', $this->_Definitions)) { $this->_Definitions['Path'] = Gdn::request()->path(); } if (!array_key_exists('Args', $this->_Definitions)) { $this->_Definitions['Args'] = http_build_query(Gdn::request()->get()); } if (!array_key_exists('ResolvedPath', $this->_Definitions)) { $this->_Definitions['ResolvedPath'] = $this->ResolvedPath; } if (!array_key_exists('ResolvedArgs', $this->_Definitions)) { if (sizeof($this->ReflectArgs) && (isset($this->ReflectArgs[0]) && $this->ReflectArgs[0] instanceof Gdn_Pluggable || isset($this->ReflectArgs['Sender']) && $this->ReflectArgs['Sender'] instanceof Gdn_Pluggable || isset($this->ReflectArgs['sender']) && $this->ReflectArgs['sender'] instanceof Gdn_Pluggable)) { $ReflectArgs = array_slice($this->ReflectArgs, 1); } else { $ReflectArgs = $this->ReflectArgs; } $this->_Definitions['ResolvedArgs'] = $ReflectArgs; } if (!array_key_exists('SignedIn', $this->_Definitions)) { if (Gdn::session()->checkPermission('Garden.Moderation.Manage')) { $SignedIn = 2; } else { $SignedIn = (int) Gdn::session()->isValid(); } $this->_Definitions['SignedIn'] = $SignedIn; } if (Gdn::session()->isValid()) { // Tell the client what our hour offset is so it can compare it to the user's real offset. touchValue('SetHourOffset', $this->_Definitions, Gdn::session()->User->HourOffset); } if (!array_key_exists('ConfirmHeading', $this->_Definitions)) { $this->_Definitions['ConfirmHeading'] = t('Confirm'); } if (!array_key_exists('ConfirmText', $this->_Definitions)) { $this->_Definitions['ConfirmText'] = t('Are you sure you want to do that?'); } if (!array_key_exists('Okay', $this->_Definitions)) { $this->_Definitions['Okay'] = t('Okay'); } if (!array_key_exists('Cancel', $this->_Definitions)) { $this->_Definitions['Cancel'] = t('Cancel'); } if (!array_key_exists('Search', $this->_Definitions)) { $this->_Definitions['Search'] = t('Search'); } // Output a JavaScript object with all the definitions. $result = 'gdn=window.gdn||{};gdn.meta=' . json_encode($this->_Definitions) . ';'; if ($wrap) { $result = "<script>{$result}</script>"; } return $result; }
/** * Deprecated. * * @param string $ThemeFile The path to the theme file. * @param string $VariableName The name of the theme info variable name. * @return null|array Returns the theme info. * @deprecated */ private function scanThemeFileOld($ThemeFile, $VariableName = '') { // Find the $PluginInfo array if (!file_exists($ThemeFile)) { return null; } $Lines = file($ThemeFile); $InfoBuffer = false; $ClassBuffer = false; $ClassName = ''; $ThemeInfoString = ''; if (!$VariableName) { $VariableName = 'ThemeInfo'; } $ParseVariableName = '$' . $VariableName; ${$VariableName} = array(); foreach ($Lines as $Line) { if ($InfoBuffer && substr(trim($Line), -2) == ');') { $ThemeInfoString .= $Line; $ClassBuffer = true; $InfoBuffer = false; } if (stringBeginsWith(trim($Line), $ParseVariableName)) { $InfoBuffer = true; } if ($InfoBuffer) { $ThemeInfoString .= $Line; } if ($ClassBuffer && strtolower(substr(trim($Line), 0, 6)) == 'class ') { $Parts = explode(' ', $Line); if (count($Parts) > 2) { $ClassName = $Parts[1]; } break; } } unset($Lines); if ($ThemeInfoString != '') { @eval($ThemeInfoString); } // Define the folder name and assign the class name for the newly added item. $var = ${$VariableName}; if (isset($var) && is_array($var)) { reset($var); $name = key($var); $var = current($var); $var['Index'] = $name; $var['AboutFile'] = $ThemeFile; $var['RealAboutFile'] = realpath($ThemeFile); $var['ThemeRoot'] = dirname($ThemeFile); touchValue('Name', $var, $name); touchValue('Folder', $var, basename(dirname($ThemeFile))); return $var; } elseif ($VariableName !== null) { if (isset($var)) { return $var; } } return null; }
/** * * * @param $Path * @param bool $Text * @param null $Format * @param array $Options * @return mixed|null|string */ public static function link($Path, $Text = false, $Format = null, $Options = array()) { $Session = Gdn::session(); $Class = val('class', $Options, ''); $WithDomain = val('WithDomain', $Options); $Target = val('Target', $Options, ''); if ($Target == 'current') { $Target = trim(url('', true), '/ '); } if (is_null($Format)) { $Format = '<a href="%url" class="%class">%text</a>'; } switch ($Path) { case 'activity': touchValue('Permissions', $Options, 'Garden.Activity.View'); break; case 'category': $Breadcrumbs = Gdn::controller()->data('Breadcrumbs'); if (is_array($Breadcrumbs) && count($Breadcrumbs) > 0) { $Last = array_pop($Breadcrumbs); $Path = val('Url', $Last); $DefaultText = val('Name', $Last, T('Back')); } else { $Path = '/'; $DefaultText = c('Garden.Title', T('Back')); } if (!$Text) { $Text = $DefaultText; } break; case 'dashboard': $Path = 'dashboard/settings'; touchValue('Permissions', $Options, array('Garden.Settings.Manage', 'Garden.Settings.View')); if (!$Text) { $Text = t('Dashboard'); } break; case 'home': $Path = '/'; if (!$Text) { $Text = t('Home'); } break; case 'inbox': $Path = 'messages/inbox'; touchValue('Permissions', $Options, 'Garden.SignIn.Allow'); if (!$Text) { $Text = t('Inbox'); } if ($Session->isValid() && $Session->User->CountUnreadConversations) { $Class = trim($Class . ' HasCount'); $Text .= ' <span class="Alert">' . $Session->User->CountUnreadConversations . '</span>'; } if (!$Session->isValid() || !Gdn::applicationManager()->checkApplication('Conversations')) { $Text = false; } break; case 'forumroot': $Route = Gdn::router()->getDestination('DefaultForumRoot'); if (is_null($Route)) { $Path = '/'; } else { $Path = combinePaths(array('/', $Route)); } break; case 'profile': touchValue('Permissions', $Options, 'Garden.SignIn.Allow'); if (!$Text && $Session->isValid()) { $Text = $Session->User->Name; } if ($Session->isValid() && $Session->User->CountNotifications) { $Class = trim($Class . ' HasCount'); $Text .= ' <span class="Alert">' . $Session->User->CountNotifications . '</span>'; } break; case 'user': $Path = 'profile'; touchValue('Permissions', $Options, 'Garden.SignIn.Allow'); if (!$Text && $Session->isValid()) { $Text = $Session->User->Name; } break; case 'photo': $Path = 'profile'; TouchValue('Permissions', $Options, 'Garden.SignIn.Allow'); if (!$Text && $Session->isValid()) { $IsFullPath = strtolower(substr($Session->User->Photo, 0, 7)) == 'http://' || strtolower(substr($Session->User->Photo, 0, 8)) == 'https://'; $PhotoUrl = $IsFullPath ? $Session->User->Photo : Gdn_Upload::url(changeBasename($Session->User->Photo, 'n%s')); $Text = img($PhotoUrl, array('alt' => $Session->User->Name)); } break; case 'drafts': TouchValue('Permissions', $Options, 'Garden.SignIn.Allow'); if (!$Text) { $Text = t('My Drafts'); } if ($Session->isValid() && $Session->User->CountDrafts) { $Class = trim($Class . ' HasCount'); $Text .= ' <span class="Alert">' . $Session->User->CountDrafts . '</span>'; } break; case 'discussions/bookmarked': TouchValue('Permissions', $Options, 'Garden.SignIn.Allow'); if (!$Text) { $Text = t('My Bookmarks'); } if ($Session->isValid() && $Session->User->CountBookmarks) { $Class = trim($Class . ' HasCount'); $Text .= ' <span class="Count">' . $Session->User->CountBookmarks . '</span>'; } break; case 'discussions/mine': TouchValue('Permissions', $Options, 'Garden.SignIn.Allow'); if (!$Text) { $Text = t('My Discussions'); } if ($Session->isValid() && $Session->User->CountDiscussions) { $Class = trim($Class . ' HasCount'); $Text .= ' <span class="Count">' . $Session->User->CountDiscussions . '</span>'; } break; case 'register': if (!$Text) { $Text = t('Register'); } $Path = registerUrl($Target); break; case 'signin': case 'signinout': // The destination is the signin/signout toggle link. if ($Session->isValid()) { if (!$Text) { $Text = T('Sign Out'); } $Path = signOutUrl($Target); $Class = concatSep(' ', $Class, 'SignOut'); } else { if (!$Text) { $Text = t('Sign In'); } $Path = signInUrl($Target); if (signInPopup() && strpos(Gdn::Request()->Url(), 'entry') === false) { $Class = concatSep(' ', $Class, 'SignInPopup'); } } break; } if ($Text == false && strpos($Format, '%text') !== false) { return ''; } if (val('Permissions', $Options) && !$Session->checkPermission($Options['Permissions'], false)) { return ''; } $Url = Gdn::request()->url($Path, $WithDomain); if ($TK = val('TK', $Options)) { if (in_array($TK, array(1, 'true'))) { $TK = 'TransientKey'; } $Url .= (strpos($Url, '?') === false ? '?' : '&') . $TK . '=' . urlencode(Gdn::session()->transientKey()); } if (strcasecmp(trim($Path, '/'), Gdn::request()->path()) == 0) { $Class = concatSep(' ', $Class, 'Selected'); } // Build the final result. $Result = $Format; $Result = str_replace('%url', $Url, $Result); $Result = str_replace('%text', $Text, $Result); $Result = str_replace('%class', $Class, $Result); return $Result; }
/** * Build HTML for a social signin button. * * @param $Name * @param $Url * @param string $Type * @param array $Attributes * @return string HTML. */ function socialSignInButton($Name, $Url, $Type = 'button', $Attributes = []) { touchValue('title', $Attributes, sprintf(t('Sign In with %s'), $Name)); $Title = $Attributes['title']; $Class = val('class', $Attributes, ''); unset($Attributes['class']); switch ($Type) { case 'icon': $Result = anchor('<span class="Icon"></span>', $Url, 'SocialIcon SocialIcon-' . $Name . ' ' . $Class, $Attributes); break; case 'button': default: $Result = anchor('<span class="Icon"></span><span class="Text">' . $Title . '</span>', $Url, 'SocialIcon SocialIcon-' . $Name . ' HasText ' . $Class, $Attributes); break; } return $Result; }