/** * Set fields that need additional manipulation after retrieval. * * @param $User * @throws Exception */ public function setCalculatedFields(&$User) { if ($v = val('Attributes', $User)) { if (is_string($v)) { setValue('Attributes', $User, @unserialize($v)); } } if ($v = val('Permissions', $User)) { if (is_string($v)) { setValue('Permissions', $User, @unserialize($v)); } } if ($v = val('Preferences', $User)) { if (is_string($v)) { setValue('Preferences', $User, @unserialize($v)); } } if ($v = val('Photo', $User)) { if (!isUrl($v)) { $PhotoUrl = Gdn_Upload::url(changeBasename($v, 'n%s')); } else { $PhotoUrl = $v; } setValue('PhotoUrl', $User, $PhotoUrl); } if ($v = val('AllIPAddresses', $User)) { if (is_string($v)) { $IPAddresses = explode(',', $v); foreach ($IPAddresses as $i => $IPAddress) { $IPAddresses[$i] = ForceIPv4($IPAddress); } setValue('AllIPAddresses', $User, $IPAddresses); } } setValue('_CssClass', $User, ''); if ($v = val('Banned', $User)) { setValue('_CssClass', $User, 'Banned'); } $this->EventArguments['User'] =& $User; $this->fireEvent('SetCalculatedFields'); }
/** * * * @param $Path * @param $Controller */ public function init($Path, $Controller) { $Smarty = $this->smarty(); // Get a friendly name for the controller. $ControllerName = get_class($Controller); if (StringEndsWith($ControllerName, 'Controller', true)) { $ControllerName = substr($ControllerName, 0, -10); } // Get an ID for the body. $BodyIdentifier = strtolower($Controller->ApplicationFolder . '_' . $ControllerName . '_' . Gdn_Format::alphaNumeric(strtolower($Controller->RequestMethod))); $Smarty->assign('BodyID', htmlspecialchars($BodyIdentifier)); //$Smarty->assign('Config', Gdn::Config()); // Assign some information about the user. $Session = Gdn::session(); if ($Session->isValid()) { $User = array('Name' => $Session->User->Name, 'Photo' => '', 'CountNotifications' => (int) val('CountNotifications', $Session->User, 0), 'CountUnreadConversations' => (int) val('CountUnreadConversations', $Session->User, 0), 'SignedIn' => true); $Photo = $Session->User->Photo; if ($Photo) { if (!IsUrl($Photo)) { $Photo = Gdn_Upload::url(changeBasename($Photo, 'n%s')); } } else { if (function_exists('UserPhotoDefaultUrl')) { $Photo = UserPhotoDefaultUrl($Session->User, 'ProfilePhoto'); } elseif ($ConfigPhoto = C('Garden.DefaultAvatar')) { $Photo = Gdn_Upload::url($ConfigPhoto); } else { $Photo = asset('/applications/dashboard/design/images/defaulticon.png', true); } } $User['Photo'] = $Photo; } else { $User = false; /*array( 'Name' => '', 'CountNotifications' => 0, 'SignedIn' => FALSE);*/ } $Smarty->assign('User', $User); // Make sure that any datasets use arrays instead of objects. foreach ($Controller->Data as $Key => $Value) { if ($Value instanceof Gdn_DataSet) { $Controller->Data[$Key] = $Value->resultArray(); } elseif ($Value instanceof stdClass) { $Controller->Data[$Key] = (array) $Value; } } $BodyClass = val('CssClass', $Controller->Data, '', true); $Sections = Gdn_Theme::section(null, 'get'); if (is_array($Sections)) { foreach ($Sections as $Section) { $BodyClass .= ' Section-' . $Section; } } $Controller->Data['BodyClass'] = $BodyClass; // Set the current locale for themes to take advantage of. $Locale = Gdn::locale()->Locale; $CurrentLocale = array('Key' => $Locale, 'Lang' => str_replace('_', '-', $Locale)); if (class_exists('Locale')) { $CurrentLocale['Language'] = Locale::getPrimaryLanguage($Locale); $CurrentLocale['Region'] = Locale::getRegion($Locale); $CurrentLocale['DisplayName'] = Locale::getDisplayName($Locale, $Locale); $CurrentLocale['DisplayLanguage'] = Locale::getDisplayLanguage($Locale, $Locale); $CurrentLocale['DisplayRegion'] = Locale::getDisplayRegion($Locale, $Locale); } $Smarty->assign('CurrentLocale', $CurrentLocale); $Smarty->assign('Assets', (array) $Controller->Assets); $Smarty->assign('Path', Gdn::request()->path()); // Assign the controller data last so the controllers override any default data. $Smarty->assign($Controller->Data); $Smarty->Controller = $Controller; // for smarty plugins $Smarty->security = true; $Smarty->security_settings['IF_FUNCS'] = array_merge($Smarty->security_settings['IF_FUNCS'], array('Category', 'CheckPermission', 'InSection', 'InCategory', 'MultiCheckPermission', 'GetValue', 'SetValue', 'Url')); $Smarty->security_settings['MODIFIER_FUNCS'] = array_merge($Smarty->security_settings['MODIFIER_FUNCS'], array('sprintf')); $Smarty->secure_dir = array($Path); }
/** * Deletes uploaded default avatars. * * @param string $avatar The avatar to delete. */ private function deleteDefaultAvatars($avatar = '') { if ($avatar && $this->isUploadedDefaultAvatar($avatar)) { $upload = new Gdn_Upload(); $upload->delete(self::DEFAULT_AVATAR_FOLDER . '/' . basename($avatar)); $upload->delete(self::DEFAULT_AVATAR_FOLDER . '/' . basename(changeBasename($avatar, 'p%s'))); $upload->delete(self::DEFAULT_AVATAR_FOLDER . '/' . basename(changeBasename($avatar, 'n%s'))); } }
// Define the current profile picture $Picture = ''; if ($this->User->Photo != '') { if (IsUrl($this->User->Photo)) { $Picture = img($this->User->Photo, array('class' => 'ProfilePhotoLarge')); } else { $Picture = img(Gdn_Upload::url(changeBasename($this->User->Photo, 'p%s')), array('class' => 'ProfilePhotoLarge')); } } // Define the current thumbnail icon $Thumbnail = $this->User->Photo; if (!$Thumbnail && function_exists('UserPhotoDefaultUrl')) { $Thumbnail = UserPhotoDefaultUrl($this->User); } if ($Thumbnail && !isUrl($Thumbnail)) { $Thumbnail = Gdn_Upload::url(changeBasename($Thumbnail, 'n%s')); } $Thumbnail = img($Thumbnail, array('alt' => t('Thumbnail'))); ?> <div class="SmallPopup FormTitleWrapper"> <h1 class="H"><?php echo $this->data('Title'); ?> </h1> <?php echo $this->Form->open(array('enctype' => 'multipart/form-data')); echo $this->Form->errors(); ?> <ul> <?php if ($Picture != '') {
/** * 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 $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; }
$BannedPhoto = c('Garden.BannedPhoto', 'http://cdn.vanillaforums.com/images/banned_large.png'); if ($BannedPhoto) { $Photo = Gdn_Upload::url($BannedPhoto); } } if ($Photo) { ?> <div class="Photo PhotoWrap PhotoWrapLarge <?php echo val('_CssClass', $User); ?> "> <?php if (IsUrl($Photo)) { $Img = img($Photo, array('class' => 'ProfilePhotoLarge')); } else { $Img = img(Gdn_Upload::url(changeBasename($Photo, 'p%s')), array('class' => 'ProfilePhotoLarge')); } if (!$User->Banned && c('Garden.Profile.EditPhotos', true) && (Gdn::session()->UserID == $User->UserID || Gdn::session()->checkPermission('Garden.Users.Edit'))) { echo anchor(Wrap(t('Change Picture')), '/profile/picture?userid=' . $User->UserID, 'ChangePicture'); } echo $Img; ?> </div> <?php } else { if ($User->UserID == Gdn::session()->UserID || Gdn::session()->checkPermission('Garden.Users.Edit')) { ?> <div class="Photo"><?php echo anchor(t('Add a Profile Picture'), '/profile/picture?userid=' . $User->UserID, 'AddPicture BigButton'); ?>
<?php if (!defined('APPLICATION')) { exit; } $User = val('User', Gdn::controller()); if (!$User && Gdn::session()->isValid()) { $User = Gdn::session()->User; } if (!$User) { return; } $Photo = $User->Photo; if ($Photo) { $Photo = isUrl($Photo) ? $Photo : Gdn_Upload::url(changeBasename($Photo, 'p%s')); $PhotoAlt = t('Avatar'); } else { $Photo = UserModel::getDefaultAvatarUrl($User, 'profile'); $PhotoAlt = t('Default Avatar'); } if ($User->Banned) { $BannedPhoto = c('Garden.BannedPhoto', 'https://images.v-cdn.net/banned_large.png'); if ($BannedPhoto) { $Photo = Gdn_Upload::url($BannedPhoto); } } if ($Photo) { ?> <div class="Photo PhotoWrap PhotoWrapLarge <?php echo val('_CssClass', $User); ?>
/** * Takes a user object, and writes out an anchor of the user's icon to the user's profile. * * @param object|array $User A user object or array. * @param array $Options */ function userPhoto($User, $Options = array()) { if (is_string($Options)) { $Options = array('LinkClass' => $Options); } if ($Px = val('Px', $Options)) { $User = userBuilder($User, $Px); } else { $User = (object) $User; } $LinkClass = concatSep(' ', val('LinkClass', $Options, ''), 'PhotoWrap'); $ImgClass = val('ImageClass', $Options, 'ProfilePhoto'); $Size = val('Size', $Options); if ($Size) { $LinkClass .= " PhotoWrap{$Size}"; $ImgClass .= " {$ImgClass}{$Size}"; } else { $ImgClass .= " {$ImgClass}Medium"; // backwards compat } $FullUser = Gdn::userModel()->getID(val('UserID', $User), DATASET_TYPE_ARRAY); $UserCssClass = val('_CssClass', $FullUser); if ($UserCssClass) { $LinkClass .= ' ' . $UserCssClass; } $LinkClass = $LinkClass == '' ? '' : ' class="' . $LinkClass . '"'; $Photo = val('Photo', $User, val('PhotoUrl', $User)); $Name = val('Name', $User); $Title = htmlspecialchars(val('Title', $Options, $Name)); if ($FullUser && $FullUser['Banned']) { $Photo = c('Garden.BannedPhoto', 'http://cdn.vanillaforums.com/images/banned_large.png'); $Title .= ' (' . t('Banned') . ')'; } if (!$Photo && function_exists('UserPhotoDefaultUrl')) { $Photo = userPhotoDefaultUrl($User, $ImgClass); } if ($Photo) { if (!isUrl($Photo)) { $PhotoUrl = Gdn_Upload::url(changeBasename($Photo, 'n%s')); } else { $PhotoUrl = $Photo; } $Href = url(userUrl($User)); return '<a title="' . $Title . '" href="' . $Href . '"' . $LinkClass . '>' . img($PhotoUrl, array('alt' => $Name, 'class' => $ImgClass)) . '</a>'; } else { return ''; } }
/** * * * @param $ThemeType * @param $Basename * @param $ETag * @param null $NotFound * @return array * @throws Exception */ public function getCssFiles($ThemeType, $Basename, $ETag, &$NotFound = null) { $NotFound = array(); // Gather all of the css paths. switch ($Basename) { case 'Style': $this->_CssFiles = array(array('style.css', 'dashboard', array('Sort' => -10))); break; case 'Admin': $this->_CssFiles = array(array('admin.css', 'dashboard', array('Sort' => -10))); break; default: $this->_CssFiles = array(); } // Throw an event so that plugins can add their css too. $this->EventArguments['ETag'] = $ETag; $this->EventArguments['ThemeType'] = $ThemeType; $this->fireEvent($Basename . 'Css'); // Include theme customizations last so that they override everything else. switch ($Basename) { case 'Style': $this->addCssFile('custom.css', false, array('Sort' => 10)); if (Gdn::controller()->Theme && Gdn::controller()->ThemeOptions) { $Filenames = valr('Styles.Value', Gdn::controller()->ThemeOptions); if (is_string($Filenames) && $Filenames != '%s') { $this->addCssFile(changeBasename('custom.css', $Filenames), false, array('Sort' => 11)); } } break; case 'Admin': $this->addCssFile('customadmin.css', false, array('Sort' => 10)); break; } $this->fireEvent('AfterGetCssFiles'); // Hunt the css files down. $Paths = array(); foreach ($this->_CssFiles as $Info) { $Filename = $Info[0]; $Folder = val(1, $Info); $Options = val(2, $Info); $Css = val('Css', $Options); if ($Css) { // Add some literal Css. $Paths[] = array(false, $Folder, $Options); } else { list($Path, $UrlPath) = self::CssPath($Filename, $Folder, $ThemeType); if ($Path) { $Paths[] = array($Path, $UrlPath, $Options); } else { $NotFound[] = array($Filename, $Folder, $Options); } } } // Sort the paths. usort($Paths, array('AssetModel', '_ComparePath')); return $Paths; }
/** * Delete a screenshot from an addon. * * @param string $AddonPictureID Picture id to remove. * @throws Gdn_UserException No permission to delete this picture. */ public function deletePicture($AddonPictureID = '') { $AddonPictureModel = new Gdn_Model('AddonPicture'); $Picture = $AddonPictureModel->getWhere(array('AddonPictureID' => $AddonPictureID))->firstRow(); $AddonModel = new AddonModel(); $Addon = $AddonModel->getID($Picture->AddonID); $Session = Gdn::session(); if ($Session->UserID != $Addon['InsertUserID'] && !$Session->checkPermission('Addons.Addon.Manage')) { throw permissionException(); } if ($this->Form->authenticatedPostBack() && $this->Form->getFormValue('Yes')) { if ($Picture) { $Upload = new Gdn_Upload(); $Upload->delete(changeBasename($Picture->File, 'ao%s')); $Upload->delete(changeBasename($Picture->File, 'at%s')); $AddonPictureModel->delete(array('AddonPictureID' => $AddonPictureID)); } $this->RedirectUrl = url('/addon/' . $Picture->AddonID); } $this->render('deletepicture'); }
/** * Render basic data about user. * * @since 2.0.? * @access public * @param int $UserID Unique ID. */ public function get($UserID = false) { if (!$UserID) { $UserID = Gdn::session()->UserID; } if (($UserID != Gdn::session()->UserID || !Gdn::session()->UserID) && !checkPermission('Garden.Users.Edit')) { throw new Exception(t('You do not have permission to view other profiles.'), 401); } $UserModel = new UserModel(); // Get the user. $User = $UserModel->getID($UserID, DATASET_TYPE_ARRAY); if (!$User) { throw notFoundException('User'); } $PhotoUrl = $User['Photo']; if ($PhotoUrl && strpos($PhotoUrl, '//') == false) { $PhotoUrl = url('/uploads/' . changeBasename($PhotoUrl, 'n%s'), true); } $User['Photo'] = $PhotoUrl; // Remove unwanted fields. $this->Data = arrayTranslate($User, array('UserID', 'Name', 'Email', 'Photo')); $this->render(); }
/** * * * @param string $Path * @param Gdn_Controller $Controller */ public function init($Path, $Controller) { $Smarty = $this->smarty(); // Get a friendly name for the controller. $ControllerName = get_class($Controller); if (StringEndsWith($ControllerName, 'Controller', true)) { $ControllerName = substr($ControllerName, 0, -10); } // Get an ID for the body. $BodyIdentifier = strtolower($Controller->ApplicationFolder . '_' . $ControllerName . '_' . Gdn_Format::alphaNumeric(strtolower($Controller->RequestMethod))); $Smarty->assign('BodyID', htmlspecialchars($BodyIdentifier)); //$Smarty->assign('Config', Gdn::Config()); // Assign some information about the user. $Session = Gdn::session(); if ($Session->isValid()) { $User = array('Name' => htmlspecialchars($Session->User->Name), 'Photo' => '', 'CountNotifications' => (int) val('CountNotifications', $Session->User, 0), 'CountUnreadConversations' => (int) val('CountUnreadConversations', $Session->User, 0), 'SignedIn' => true); $Photo = $Session->User->Photo; if ($Photo) { if (!isUrl($Photo)) { $Photo = Gdn_Upload::url(changeBasename($Photo, 'n%s')); } } else { $Photo = UserModel::getDefaultAvatarUrl($Session->User); } $User['Photo'] = $Photo; } else { $User = false; /*array( 'Name' => '', 'CountNotifications' => 0, 'SignedIn' => FALSE);*/ } $Smarty->assign('User', $User); // Make sure that any datasets use arrays instead of objects. foreach ($Controller->Data as $Key => $Value) { if ($Value instanceof Gdn_DataSet) { $Controller->Data[$Key] = $Value->resultArray(); } elseif ($Value instanceof stdClass) { $Controller->Data[$Key] = (array) $Value; } } $BodyClass = val('CssClass', $Controller->Data, '', true); $Sections = Gdn_Theme::section(null, 'get'); if (is_array($Sections)) { foreach ($Sections as $Section) { $BodyClass .= ' Section-' . $Section; } } $Controller->Data['BodyClass'] = $BodyClass; // Set the current locale for themes to take advantage of. $Locale = Gdn::locale()->Locale; $CurrentLocale = array('Key' => $Locale, 'Lang' => str_replace('_', '-', Gdn::locale()->language(true))); if (class_exists('Locale')) { $CurrentLocale['Language'] = Locale::getPrimaryLanguage($Locale); $CurrentLocale['Region'] = Locale::getRegion($Locale); $CurrentLocale['DisplayName'] = Locale::getDisplayName($Locale, $Locale); $CurrentLocale['DisplayLanguage'] = Locale::getDisplayLanguage($Locale, $Locale); $CurrentLocale['DisplayRegion'] = Locale::getDisplayRegion($Locale, $Locale); } $Smarty->assign('CurrentLocale', $CurrentLocale); $Smarty->assign('Assets', (array) $Controller->Assets); // 2016-07-07 Linc: Request used to return blank for homepage. // Now it returns defaultcontroller. This restores BC behavior. $isHomepage = val('isHomepage', $Controller->Data); $Path = $isHomepage ? "" : Gdn::request()->path(); $Smarty->assign('Path', $Path); $Smarty->assign('Homepage', $isHomepage); // true/false // Assign the controller data last so the controllers override any default data. $Smarty->assign($Controller->Data); $security = new SmartySecurityVanilla($Smarty); $security->php_handling = Smarty::PHP_REMOVE; $security->allow_constants = false; $security->allow_super_globals = false; $security->streams = null; $security->setPhpFunctions(array_merge($security->php_functions, ['array', 'category', 'checkPermission', 'inSection', 'inCategory', 'ismobile', 'multiCheckPermission', 'getValue', 'setValue', 'url', 'useragenttype'])); $security->php_modifiers = array_merge($security->php_functions, array('sprintf')); $Smarty->enableSecurity($security); }
/** * * * @param $Row */ public function calculateRow(&$Row) { $ActivityType = self::GetActivityType($Row['ActivityTypeID']); $Row['ActivityType'] = val('Name', $ActivityType); if (is_string($Row['Data'])) { $Row['Data'] = @unserialize($Row['Data']); } $Row['PhotoUrl'] = url($Row['Route'], true); if (!$Row['Photo']) { if (isset($Row['ActivityPhoto'])) { $Row['Photo'] = $Row['ActivityPhoto']; $Row['PhotoUrl'] = userUrl($Row, 'Activity'); } else { $User = Gdn::userModel()->getID($Row['ActivityUserID'], DATASET_TYPE_ARRAY); if ($User) { $Photo = $User['Photo']; $Row['PhotoUrl'] = userUrl($User); if (!$Photo || stringBeginsWith($Photo, 'http')) { $Row['Photo'] = $Photo; } else { $Row['Photo'] = Gdn_Upload::url(changeBasename($Photo, 'n%s')); } } } } $Data = $Row['Data']; if (isset($Data['ActivityUserIDs'])) { $Row['ActivityUserID'] = array_merge(array($Row['ActivityUserID']), $Data['ActivityUserIDs']); $Row['ActivityUserID_Count'] = val('ActivityUserID_Count', $Data); } if (isset($Data['RegardingUserIDs'])) { $Row['RegardingUserID'] = array_merge(array($Row['RegardingUserID']), $Data['RegardingUserIDs']); $Row['RegardingUserID_Count'] = val('RegardingUserID_Count', $Data); } $Row['Url'] = ExternalUrl($Row['Route']); if ($Row['HeadlineFormat']) { $Row['Headline'] = formatString($Row['HeadlineFormat'], $Row); } else { $Row['Headline'] = Gdn_Format::activityHeadline($Row); } }
</td> <td><?php echo t('Thumbnail'); ?> </td> </tr> </thead> <tbody> <tr> <td> <?php echo img(Gdn_Upload::url(changeBasename($this->User->Photo, 'p%s')), array('id' => 'cropbox')); ?> </td> <td> <div style="<?php echo 'width:' . $this->ThumbSize . 'px;height:' . $this->ThumbSize . 'px;'; ?> overflow:hidden;"> <?php echo img(Gdn_Upload::url(changeBasename($this->User->Photo, 'p%s')), array('id' => 'preview')); ?> </div> </td> </tr> </tbody> </table> <?php echo $this->Form->close('Save', '', array('class' => 'Button Primary'));
/** * * * @param $themeType * @param $basename * @param $eTag * @param null $notFound * @return array * @throws Exception */ public function getCssFiles($themeType, $basename, $eTag, &$notFound = null) { $notFound = []; $basename = strtolower($basename); // Gather all of the css paths. switch ($basename) { case 'style': $this->_CssFiles = [['style.css', 'dashboard', ['Sort' => -10]]]; break; case 'admin': $this->_CssFiles = [['admin.css', 'dashboard', ['Sort' => -10]]]; break; default: $this->_CssFiles = []; } // Throw an event so that plugins can add their css too. $this->EventArguments['ETag'] = $eTag; $this->EventArguments['ThemeType'] = $themeType; $this->fireEvent("{$basename}Css"); // Include theme customizations last so that they override everything else. switch ($basename) { case 'style': $this->addCssFile('custom.css', false, ['Sort' => 10]); if (Gdn::controller()->Theme && Gdn::controller()->ThemeOptions) { $filenames = valr('Styles.Value', Gdn::controller()->ThemeOptions); if (is_string($filenames) && $filenames != '%s') { $this->addCssFile(changeBasename('custom.css', $filenames), false, ['Sort' => 11]); } } break; case 'admin': $this->addCssFile('customadmin.css', false, ['Sort' => 10]); break; } $this->fireEvent('AfterGetCssFiles'); // Hunt the css files down. $paths = []; foreach ($this->_CssFiles as $info) { $filename = $info[0]; $folder = val(1, $info); $options = val(2, $info); $css = val('Css', $options); if ($css) { // Add some literal Css. $paths[] = [false, $folder, $options]; } else { list($path, $urlPath) = self::cssPath($filename, $folder, $themeType); if ($path) { $paths[] = [$path, $urlPath, $options]; } else { $notFound[] = [$filename, $folder, $options]; } } } // Sort the paths. usort($paths, ['AssetModel', '_comparePath']); return $paths; }
/** * Set fields that need additional manipulation after retrieval. * * @param array|object &$User * @throws Exception */ public function setCalculatedFields(&$User) { if ($v = val('Attributes', $User)) { if (is_string($v)) { setValue('Attributes', $User, dbdecode($v)); } } if ($v = val('Permissions', $User)) { if (is_string($v)) { setValue('Permissions', $User, dbdecode($v)); } } if ($v = val('Preferences', $User)) { if (is_string($v)) { setValue('Preferences', $User, dbdecode($v)); } } if ($v = val('Photo', $User)) { if (!isUrl($v)) { $PhotoUrl = Gdn_Upload::url(changeBasename($v, 'n%s')); } else { $PhotoUrl = $v; } setValue('PhotoUrl', $User, $PhotoUrl); } // We store IPs in the UserIP table. To avoid unnecessary queries, the full list is not built here. Shim for BC. setValue('AllIPAddresses', $User, [val('InsertIPAddress', $User), val('LastIPAddress', $User)]); setValue('_CssClass', $User, ''); if (val('Banned', $User)) { setValue('_CssClass', $User, 'Banned'); } $this->EventArguments['User'] =& $User; $this->fireEvent('SetCalculatedFields'); }
<?php if (!defined('APPLICATION')) { exit; } $User = val('User', Gdn::controller()); if (!$User && Gdn::session()->isValid()) { $User = Gdn::session()->User; } if (!$User) { return; } $Photo = $User->Photo; if ($Photo) { if (!IsUrl($Photo)) { $Photo = Gdn_Upload::url(changeBasename($Photo, 'p%s')); } } else { $Photo = UserModel::getDefaultAvatarUrl($User, 'profile'); } if ($User->Banned) { $BannedPhoto = c('Garden.BannedPhoto', 'https://c3409409.ssl.cf0.rackcdn.com/images/banned_large.png'); if ($BannedPhoto) { $Photo = Gdn_Upload::url($BannedPhoto); } } if ($Photo) { ?> <div class="Photo PhotoWrap PhotoWrapLarge <?php echo val('_CssClass', $User); ?>
/** * Take a user object an return the URL to their photo. * * @param object|array $User */ function userPhotoUrl($User) { $FullUser = Gdn::userModel()->getID(val('UserID', $User), DATASET_TYPE_ARRAY); $Photo = val('Photo', $User); if ($FullUser && $FullUser['Banned']) { $Photo = 'https://c3409409.ssl.cf0.rackcdn.com/images/banned_100.png'; } if ($Photo) { if (!isUrl($Photo)) { $PhotoUrl = Gdn_Upload::url(changeBasename($Photo, 'n%s')); } else { $PhotoUrl = $Photo; } return $PhotoUrl; } return UserModel::getDefaultAvatarUrl($User); }
$Session = Gdn::session(); ?> <h4 class="H discussions-label">About</h4> <div class="User" itemscope itemtype="http://schema.org/Person"> <?php // $Photo = userPhoto($Row, array('LinkClass' => 'Img')); // if ($Photo) { // echo $Photo; // } // Define the current profile picture $Picture = ''; if ($this->User->Photo != '') { if (IsUrl($this->User->Photo)) { $Picture = img($this->User->Photo, array('class' => 'ProfilePhoto')); } else { $Picture = img(Gdn_Upload::url(changeBasename($this->User->Photo, 'p%s')), array('class' => 'ProfilePhoto')); } echo $Picture; } else { $Photo = userPhoto($Row, array('LinkClass' => 'Img')); if ($Photo) { echo $Photo; } } ?> <div class="profile-info"> <h4 class="H profile-username"><?php echo htmlspecialchars($this->User->Name); // echo '<span class="Gloss">'; // Gdn_Theme::BulletRow(); // if ($this->User->Title) {