Регистрация  |  Вход

Salesforce community. Практика 2. Все до кучи.

Привет всем! Вот собрался с силами и попробовал применить все свои наработки из темы порталов на salesforce communities. Посмотрим что из этого получилось, а я расскажу как я это сделал.

Я создал community, которое состоит из 6 страниц: Home page, Page 1 и Page 2, Login page, Registration page, Template (шаблон, в который встраиваются остальные страницы).



Тестовое community позволяет выполнять основные необходимые задачи - регистрация, авторизация пользователей и контроль доступа к "закрытым" страницам. Страницы Home, Login и Registraion являются открытыми для незарегистрированных пользователей - выполняют соответствующие своим названиям функции. Страницы Page 1 и Page 2 доступны только зарегистрированным пользователям.



При попытке доступа к ним незарегистрированным пользователям предлагается выполнить вход или зарегистрироваться.



salesforce-community-1



salesforce-community-2



salesforce-community-3



Вот как выглядят страницы для зарегистрированного пользователя.



salesforce-community-4



salesforce-community-5



Начнем с создания страницы Home и Template. Названия могут быть любыми. Я рекомендую давать названия страницам, контроллерам и любым другим компонентам с определенным префиксом. Потом будет легче в них ориентироваться, особенно если у заказчика есть еще другой кастомный функционал. Вот исходный код моих страниц и контроллеров:



c_CommunityTemplate.page




<apex:page showHeader="false" standardStylesheets="false" docType="html-5.0" controller="c_CommunityTemplateCtrl">
<html>
<head>
<apex:stylesheet value="{!URLFOR($Resource.bootstrap, 'bootstrap/css/bootstrap.css')}"/>

<style>
html, body {height:100%;}
.c-wrapper-box {min-height:100%; position:relative;}
.c-header {min-height: 150px; background-color: #f0f0f0;}
.c-header h1 {margin: 40px 0 20px 0; text-transform: uppercase;}
.c-menu {background-color: #007FFF;}
.c-menu .navbar {margin-bottom: 0;};
.c-body {min-height: 50px; padding-bottom:60px;}
.c-footer {height: 60px; background-color: #f0f0f0; position:absolute; bottom:0; width: 100%;}
.c-footer .c-footerNote {margin-top: 20px;}
</style>

</head>
<body>
<div class="c-wrapper-box">
<div class="c-header">
<div class="container">
<div class="row-fluid">
<div class="span12 text-center">
<h1>SF Community Example</h1>
<div class="text-right" style="margin-top: 40px;">
<p>
<apex:outputPanel layout="none" rendered="{!isGuest}">
Welcome guest! <a href="{!$Page.c_CommunityLogin}">Log in</a> | <a href="{!$Page.c_CommunityRegister}">Register</a>
</apex:outputPanel>
<apex:outputPanel layout="none" rendered="{!NOT(isGuest)}">
Welcome {!$User.FirstName} {!$User.LastName}! <a href="/secur/logout.jsp">Log out</a>
</apex:outputPanel>
</p>
</div>
</div>
</div>
</div>
</div>
<div class="c-menu">
<div class="container">
<div class="navbar navbar-inverse">
<div class="navbar-inner">
<ul class="nav">
<li id="pageHomeLink"><a class="brand" href="{!$Page.c_CommunityHomePage}">Home</a></li>
<li id="page1link"><a href="{!$Page.c_CommunityPage1}">Page 1</a></li>
<li id="page2link"><a href="{!$Page.c_CommunityPage2}">Page 2</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="c-body">
<apex:insert name="body"/>
</div>
<div class="c-footer">
<div class="container">
<div class="row-fluid">
<div class="span12 text-center">
<p class="c-footerNote">SF Community Example</p>
</div>
</div>
</div>
</div>
</div>

<apex:includeScript value="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" />
<apex:includeScript value="{!URLFOR($Resource.bootstrap, 'bootstrap/js/bootstrap.js')}"/>

</body>
</html>
</apex:page>


c_CommunityTemplateCtrl.cls




public with sharing class c_CommunityTemplateCtrl {

public Boolean isGuest { get{
return System.UserInfo.getUserType() == 'Guest';
//Guest, Standard, PowerCustomerSuccess, ...
} }

}


Это шаблон, который содержит: header, footer, главное меню, а также логику по выводу приветствия или приглашения на вход или регистрацию. Этот шаблон будет подключаться к каждой странице на community. Теперь страница Home. Тут ничего сложного - указываем в качестве шаблона предыдущую страницу и выводим контент. Также подсвечиваем пункт в главном меню.




<apex:page showHeader="false" standardStylesheets="false" controller="c_CommunityHomePageCtrl">
<apex:composition template="c_CommunityTemplate">
<apex:define name="body">

<script>
jQuery.noConflict();
jQuery(function(){
jQuery('#pageHomeLink').addClass('active');
})
</script>

<div class="container">
<div class="row-fluid">
<div class="span12">
<h2>SF Community Public Home Page</h2>
<div class="text-center" style="margin-top: 50px;">
<img src="http://www.boomi.com/files/application_graphic_salesfo.png" />
</div>
</div>
</div>
</div>
</apex:define>
</apex:composition>
</apex:page>


Теперь приступим к этапу настройки community. Чтобы наша сделать страницу Home страницей community по умолчанию необходимо в настройках (Communities -> Manage Communities -> Force.com (напротив имени выбранного community)) указать ее в пункте "Active Site Home Page".



salesforce-community-active-site-home-page  



Теперь если вы перейдете по ссылке указанной в секции Custom URL, то должны увидеть вашу кастомную страницу Home.   Если вы хотите открыть  еще другие страницы для незарегистрированных пользователей, то необходимо указать ее в списке доступных для community. Communities -> Manage Communities -> Force.com (напротив имени выбранного community) -> Site Visualforce Pages -> Edit. Перемещаем в секцию Enabled Visualforce Pages те страницы, к которым вы хотите открыть доступ незарегистрированным пользователям.



Далее создадим страницы Login и Register для нашего community. Вернее создавать особо ничего не надо. При включении commnunity на орге появляются все необходимые страницы для осуществления регистрации и авторизации пользователей. Вам остается взять исходный код и изменить под ваши требования. Первое что я делаю - это прикручиваю шаблон нашего community к страницам Login и Register, затем добавляю дополнительную логику, если она необходима. Вот минимальный исходный код обеих страниц (с контроллерами):  



c_CommunityLogin.page




<apex:page id="loginPage" showHeader="false" title="Community Login" standardStylesheets="false" controller="c_CommunityLoginCtrl" action="{!init}">
<apex:composition template="c_CommunityTemplate">
<apex:define name="body">
<div class="container">
<div style="width: 350px; margin: 100px auto;">

<apex:form id="loginForm" forceSSL="true">
<apex:outputPanel layout="block">

<apex:pageMessages id="error"/>

<apex:panelGrid columns="2" style="margin-top:1em;">
<apex:outputLabel value="{!$Label.site.username}" for="username"/>
<apex:inputText id="username" value="{!username}"/>
<apex:outputLabel value="{!$Label.site.password}" for="password"/>
<apex:inputSecret id="password" value="{!password}"/>
<apex:outputText value=""/>
<apex:commandButton action="{!login}" value="{!$Label.site.login_button}" id="loginButton"/>
<apex:outputText value=""/>
<apex:outputText value=""/>
<apex:outputText value=""/>
<apex:panelGroup id="theLinks">
<!--
<apex:outputLink value="{!$Page.ForgotPassword}"> {!$Label.site.forgot_your_password_q}</apex:outputLink>
<apex:outputText value=" | " rendered="{!$Site.RegistrationEnabled}" /> -->
<apex:outputLink value="{!$Page.c_CommunityRegister}" rendered="{!$Site.RegistrationEnabled}">{!$Label.site.new_user_q}</apex:outputLink>
</apex:panelGroup>
</apex:panelGrid>
</apex:outputPanel>
</apex:form>

</div>
</div>
</apex:define>
</apex:composition>
</apex:page>


c_CommunityLoginCtrl.cls




public class c_CommunityLoginCtrl {

public String username {get; set;}
public String password {get; set;}

public Boolean isGuest { get{
return System.UserInfo.getUserType() == 'Guest';
} set; }

public pageReference init() {
if (!isGuest) {
return Page.c_CommunityHomePage;
}
return null;
}

public PageReference login() {
String startUrl = System.currentPageReference().getParameters().get('startURL');
return Site.login(username, password, startUrl);
}

}


c_CommunityRegister.cls




<apex:page id="loginPage" showHeader="false" title="Community Login" standardStylesheets="false" controller="c_CommunityRegisterCtrl" action="{!init}">
<apex:composition template="c_CommunityTemplate">
<apex:define name="body">
<div class="container">
<div style="width: 350px; margin: 100px auto;">

<apex:form id="theForm" forceSSL="true">
<apex:pageMessages id="error"/>
<apex:panelGrid columns="2" style="margin-top:1em;">
<apex:outputLabel value="First Name" for="firstName"/>
<apex:inputText required="true" id="firstName" value="{!firstName}"/>
<apex:outputLabel value="Last Name" for="lastName"/>
<apex:inputText required="true" id="lastName" value="{!lastName}"/>
<apex:outputLabel value="{!$Label.site.community_nickname}" for="communityNickname"/>
<apex:inputText required="true" id="communityNickname" value="{!communityNickname}"/>
<apex:outputLabel value="{!$Label.site.email}" for="email"/>
<apex:inputText required="true" id="email" value="{!email}"/>
<apex:outputLabel value="{!$Label.site.password}" for="password"/>
<apex:inputSecret id="password" value="{!password}"/>
<apex:outputLabel value="{!$Label.site.confirm_password}" for="confirmPassword"/>
<apex:inputSecret id="confirmPassword" value="{!confirmPassword}"/>
<apex:outputText value=""/>
<apex:commandButton action="{!registerUser}" value="{!$Label.site.submit}" id="submit"/>
</apex:panelGrid>
<br/>
</apex:form>

</div>
</div>
</apex:define>
</apex:composition>
</apex:page>


c_CommunityRegisterCtrl.cls




public with sharing class c_CommunityRegisterCtrl {

public Boolean isGuest { get{
return System.UserInfo.getUserType() == 'Guest';
} set; }

public pageReference init() {
if (!isGuest) {
return Page.c_CommunityHomePage;
}
return null;
}

public String firstName {get; set;}
public String lastName {get; set;}
public String email {get; set;}
public String password {get; set {password = value == null ? value : value.trim(); } }
public String confirmPassword {get; set { confirmPassword = value == null ? value : value.trim(); } }
public String communityNickname {get; set { communityNickname = value == null ? value : value.trim(); } }

private boolean isValidPassword() {
return password == confirmPassword;
}

public PageReference registerUser() {

// it's okay if password is null - we'll send the user a random password in that case
if (!isValidPassword()) {
ApexPages.Message msg = new ApexPages.Message(ApexPages.Severity.ERROR, Label.site.passwords_dont_match);
ApexPages.addMessage(msg);
return null;
}

String profileId = '00e90000000jatp'; // To be filled in by customer.
//String roleEnum = 'c_role'; // To be filled in by customer.
String accountId = '0019000000PHgpz'; // To be filled in by customer.

String userName = email;

User u = new User();
u.Username = userName;
u.Email = email;
u.FirstName = firstName;
u.LastName = lastName;
u.CommunityNickname = communityNickname;
u.ProfileId = profileId;

String userId = Site.createPortalUser(u, accountId, password);

if (userId != null) {
if (password != null && password.length() > 1) {
return Site.login(userName, password, null);
}
else {
PageReference page = System.Page.CommunitiesSelfRegConfirm;
page.setRedirect(true);
return page;
}
}
return null;
}

}


Данные страницы необходимо добавить с список доступных для community (как я писал выше) чтобы их могли видеть незарегистрированные пользователи. Так же в контроллер обеих страниц я добавил автоматический редирект для зарегистрированных пользователей на домашнюю страницу. С одной стороны такое поведение понятно для пользователей, с другой стороны позволяет избежать ошибок в salesforce вызванных повторной попыткой зарегистрированных пользователей пройти повторную регистрацию или выполнить вход.



!ВАЖНО: Небольшой отступление, но то что я напишу важно понять. Незарегистрированному пользователю (до момента входа) назначается профиль Site Guest User и права доступа настраиваются на самом community как я показывал выше. Как только пользователь выполнит вход, его права сразу меняются на права в соответствии с профилем указанным при регистрации. Поэтому все страницы что вы указывали в Community, а также закрытые страницы для зарегистрированных пользователей необходимо указать в настройках всех профилей, которые подключены к данному community. Это также касается GRUD и FLS для объектов.



salesforce-community-assigned-profiles  



Создаем закрытые страницы для зарегистрированных пользователей. Никакой особой логики  в страницу я не вкладывал. Вот пример минимальной страницы:



c_CommunityPage1.page




<apex:page showHeader="false" standardStylesheets="false">
<apex:composition template="c_CommunityTemplate">
<apex:define name="body">

<script>
jQuery.noConflict();
jQuery(function(){
jQuery('#page1link').addClass('active');
})
</script>

<div class="container">
<div class="row-fluid">
<div class="span12">

<h2>Private community Page 1</h2>

<div class="text-center" style="margin-top: 50px;">
<img src="http://cdn-static.zdnet.com/i/story/60/01/078985/crm082911a.png" width="800" />
</div>

</div>
</div>
</div>
</apex:define>
</apex:composition>
</apex:page>


Особенности настройки для данных страниц в том, что их необходимо разрешать только в настройках профилей, подключенных к community. В настройках самого community ничего указывать не надо. Вот теперь community почти готово. Осталось рассмотреть пару ключевых настроек для выполнения правильных редиректов в различных ситуациях. Во-первых необходимо указать вашу кастомную страницы Login в качестве страницы по-умолчанию для Authorization Required Page (401)



salesforce-community-autorization-required



Кстати остальные страницы из этого списке тоже не мешает поменять под использование вашего шаблона. Потому что, например, если вы введете название страницы которой нет на орге, то откроется стандартная страница Page Not Found Page (404), а она не очень похожа на то что мы так упорно разрабатывали. Так же я заметил что при выходи их community происходит редирект на страницу /login. Как я уже упоминал раньше, данная страницы является внутренней (служебной) страницей salesforce, к которая мы не имеем никакого доступа (и которая кстати так и не заработала правильно на моем орге). Поэтому я сделал небольшой фокус и указал в настройках community выполнять автоматический редирект со страницы /login на мою кастомную страницу входа.



salesforce-community-login-redirect  



!ВАЖНО Еще одно замечание!!! Все ссылки должны работать через https!!! Если вы ранее выполнили вход, а потом перешли на community по ссылке http, то salesforce определит вас как анонимного пользователя. А попытка выполнить вход выдаст ошибку. Я применял уже такой ход на реальных проектах - на всех страницах выполнять автоматический редирект на https, если страница в адресной строке имела протокол http. На моем тестовом портале я пока не добавил данную проверку, так что вы можете пощупать данный баг если замените протокол в ссылке ниже и перейдете по ней на community, где был уже выполнен вход.



Вот наверное собственно и все. Посмотреть на рабочий вариант можно здесь (https://dmntestcommunities-developer-edition.ap1.force.com/). Единственное, я отключил автоматическую регистрацию.



Данные для тестового входа:



portal1@portal1.com / portal55555