Extending Liferay DXP - User Registration (Part 2)

This is the second part of the "Extending Liferay DXP - User Registration" blog. In this blog we explore the ways of implementing registration process for a portal with multiple sites

Portal Sites Configuration

Let’s presume we have a portal with the following sites configured:

  • "Liferay", default site, will not be listed
  • "Site 1", site with open membership
  • "Site 2", site with restricted membership
  • "Site 3", site with restricted membership
  • "Site 4", private site, will not be listed

Each of the sites has its own description which we want to display to the user:

 

User Registration Process Flow

The main steps of the user registration process that we are going to implement here are:

  1. Check if a user already has an account 
  2. If user already has an account but is not signed in, ask to sign in
  3. If user is already signed in, show current read-only details of a user
  4. Show the sites which is the user is not a member of with the description of the site when the site is selected
  5. Collect additional information from the user if the user has selected a 'restricted' site
  6. User reviews the request, with the ability to save request as PDF file, and submits the form
  7. On form submission:
    1. Automatically create user account if user does not exist
    2. If the site selected is 'open', user is automatically enrolled into this site
    3. If the site selected is 'restricted', site membership request is created for this user
    4. If the site selected is 'restricted', notification email is sent to site owners with user details attached as PDF

 

For the implementation of this process we use SmartForms.

Here we show only essential screenshots of the User Registration Form, the entire form definition can be downloaded (see link at the end of this blog). Once it is imported you can use Visual Designer to see how the business rules are implemented.

 

User Flow

1. User is asked to enter an email, SmartForms connects to portal webservices to check if such email is already registered (the source code for all webservices is at the end of the blog)

2. If user already has an account, then 'must log in' message is displayed

3. If user is already signed in, the form is automatically populated with user details (user data is automatically brought by SmartForms from Liferay environment).

4. On the next page the user is asked to select a site from the site list obtained via webservice. When user selects a site the description of the site is displayed (webservice again). You can put your own definition of Terms & Conditions together with 'I agree' checkbox.

.

5. If the site the user selected is of 'restricted' type, the user is asked to provide additional information.

SmartForms will automatically handle this rule once you add the following state condition to 'Company/Organisation Details' page. Visual Designer screenshot:

6. The membership request summary is displayed on a final page, allowing the user to save membership request details as PDF file and Submit the form when ready.

7. Processing the submitted form. There are two form submission handlers :

  1. Emailer, which sends a 'membership site request' email to Site Owner(s) if the selected site is 'restricted', attaching PDF that contains all data submitted by the user (implemented using SmartForms workflow, but of course you can use your own).
  2. Webhook, that creates user account if the user is not registered yet and submits membership request of this user to a selected site. (the source code for this webhook  is at the end of the blog).

That's it. That is the user flow, which is probably longer than the implementation notes listed below.

Implementation Notes

Data Flow

Below is the generic data flow where the Form Controller could be implemented inside or outside of the portal.

In our implementation we are using SmartForms to build and run User Registration Form. SmartForms Cloud is acting as Form Controller.

 

User Registration Handler Code

User registration handler implements:

  1. SOAP webservices to query portal data and populate form fields
  2. JSON REST endpoint to executes all relevant actions on form submission, creation of portal users and site membership requests

Here we provide source code for essential functions, link to get the full source code is at the end of the blog.

 

Checking if User is Registered

@WebMethod (action=namespace +  "getMemberStatusByEmail" )

public String getMemberStatusByEmail(

         @WebParam (name= "fieldName" )

         String fieldname,

         @WebParam (name= "fields" )

         Field[] fieldList) {

     try {

     

         Map<String, Field> fieldMap = fieldArrayToMap(fieldList);

         Field emailAddressVO = fieldMap.get(FIELDNAME_USER_EMAIL);

         if (emailAddressVO ==  null ) {

             logger.warn( "Call to getMemberStatusByEmail() is misconfigured, cannot find field '" + FIELDNAME_USER_EMAIL +  "'" );

             return "false" ;

         }

         String emailAddress = emailAddressVO.getValue();

         

         if (emailAddress.trim().length() ==  0 ) {

             return "false" ;

         }

             

         try {

             UserLocalServiceUtil.getUserByEmailAddress( this .getCompanyId(fieldMap), emailAddress);

             // no exception, user exists

             return "true" ;

         catch (Exception e) {}

         // user is not registered

         return "false" ;

     catch (Throwable e) {

         logger.error( "System error " , e);

         return "false" ;

     }

}

 

Getting List of Open and Protected Sites

@WebMethod (action=namespace +  "getSites" )

public Option[] getSites(

         @WebParam (name= "fieldName" )

         String fieldname,

         @WebParam (name= "fields" )

         Field[] fieldList) {

     Option[] blankOptions =  new Option[ 0 ];

     try {      

         Map<String, Field> fieldMap = fieldArrayToMap(fieldList);        

         long companyId =  this .getCompanyId(fieldMap);          

         User user =  null ;

         Field userScreennameField = fieldMap.get(FIELDNAME_USER_SCREENNAME);

         if (userScreennameField !=  null ) {

             if (userScreennameField.getValue().trim().length() >  0 ) {

                 try {

                     user = UserLocalServiceUtil.getUserByScreenName(companyId, userScreennameField.getValue());

                 catch (Exception e) {}

             }

         }

         

         List<Option> validGroups =  new ArrayList<Option>();

         LinkedHashMap<String, Object> params =  new LinkedHashMap<String, Object>();

         // limit selection by sites only

         params.put( "site" new Boolean( true ));

         // limit selection by active groups only

         params.put( "active" new Boolean( true ));

         List<Group> allGroupsList = GroupLocalServiceUtil.search(companyId, params , QueryUtil.ALL_POS, QueryUtil.ALL_POS);

         Iterator<Group> allActiveGroups = allGroupsList.iterator();

         while (allActiveGroups.hasNext()) {

             Group group = allActiveGroups.next();

             boolean isAlreadyAMember =  false ;

             // check if user is already a member of it

             if (user !=  null ) {

                 if (group.isGuest()) {

                     // is a member anyway

                     isAlreadyAMember =  true ;

                 else {

                     isAlreadyAMember = UserLocalServiceUtil.hasGroupUser(group.getGroupId(), user.getUserId());

                 }

             }

             // add the site to the selection list if this is a regular community site and the user is not already a member of it

             if (group.isRegularSite() && !group.isUser() && !isAlreadyAMember && !group.isGuest()) {

                 // include Open and Restricted sites only

                 if (group.getType() ==  1 || group.getType() ==  2 ) {

                     validGroups.add(  new Option( group.getName(group.getDefaultLanguageId()), String.valueOf(group.getGroupId()) ) );

                 }

             }

         }

         return validGroups.toArray( new Option[validGroups.size()]);

     catch (Throwable e) {

         logger.error( "System error " , e);

         return blankOptions;

     }

}

 

Getting Email Addresses of Owners of a Site

@WebMethod (action=namespace +  "getSiteOwnerEmails" )

public String getSiteOwnerEmails(

         @WebParam (name= "fieldName" )

         String fieldname,

         @WebParam (name= "fields" )

         Field[] fieldList) {

     Map<String, Field> fieldMap = fieldArrayToMap(fieldList);

     Group group =  this .getSelectedGroup(fieldMap);

     if (group ==  null ) {

         // no group selected yet

         return "" ;

     else if (group.getType() !=  2 ) {

         // this is not a restricted site

         return "" ;

     else {

         // check if Terms and Conditions acknowledge is checked, otherwise no point of fetching email addresses

         Field termsAndConditionsField = fieldMap.get(FIELDNAME_TnC_CHECKBOX);

         if (termsAndConditionsField ==  null ) {

             logger.warn( "Call to getSiteOwnerEmails() is misconfigured, cannot find field '" + FIELDNAME_TnC_CHECKBOX +  "'" );

             return "" ;

         }

         if (termsAndConditionsField.getValue().length() ==  0 ) {

             // not checked

             return "" ;

         }

         // make a list of email addresses of site owners for a restricted site

         // this will be used to send 'site membership request' email

         StringBuilder response =  new StringBuilder();

         Role siteOwnerRole;

         try {

             siteOwnerRole = RoleLocalServiceUtil.getRole(group.getCompanyId(), RoleConstants.SITE_OWNER);

         catch (PortalException e) {

             logger.error( "Unexpected error" , e);

             return "" ;

         }

         List<User> groupUsers = UserLocalServiceUtil.getGroupUsers(group.getGroupId());

         for ( int i =  0 ; i < groupUsers.size(); i++) {

             User user = groupUsers.get(i);

             if (UserGroupRoleLocalServiceUtil.hasUserGroupRole(user.getUserId(), group.getGroupId(), siteOwnerRole.getRoleId())) {

                 if (response.length() >  0 ) {

                     response.append( ';' );

                 }

                 response.append(user.getEmailAddress());

             }

         }

         logger.info( "compiled site admin emails " + response.toString());

         return response.toString();

     }

}

 

Creating User Account and Site Membership Request

// method to process JSON webhook call

@POST

@Path ( "/user-registration" )

public void newFormSubmittedAsJsonFormat(String input) {

     logger.info( "In /webhook/user-registration" );

     

     /* check authorization */

     String handShakeKey = request.getHeader( "X-SmartForms-Handshake-Key" );

     if (handShakeKey ==  null || !handShakeKey.equals(SMARTFORMS_HADSHAKE_KEY) ) {

         throw new WebApplicationException(Response.Status.UNAUTHORIZED);           

     }

     

     JSONObject data;

     try {

         data = JSONFactoryUtil.createJSONObject(input);

         

         Map<String, String> fields =  this .jsonFormDataToFields(data);

         logger.info( "Have received fields " + fields.size());

         User user =  null ;

         

         ServiceContext serviceContext =  new ServiceContext();

         long groupId = Long.parseLong(fields.get(FIELD_SITE_ID));

         long companyId = Long.parseLong(fields.get(FIELD_COMPANY_ID));

         if (fields.get(FIELD_USER_ID).length() >  0 ) {

             logger.info( "User is already registered" );

             try {

                 user = UserLocalServiceUtil.getUser(Long.parseLong(fields.get(FIELD_USER_ID)));

             catch (Exception e) {

                 logger.error( "Unable to fetch user" , e);

                 throw new WebApplicationException(Response.Status.NOT_FOUND);

             }

         else {

             // create user

             String firstName = fields.get(FIELD_USER_FIRST_NAME);

             String lastName = fields.get(FIELD_USER_LAST_NAME);

             String email = fields.get(FIELD_USER_EMAIL);

             logger.info( "Creating user " + firstName +  " " + lastName +  " " + email);

             try {

                 // the following data could come from the form, but we just provide some hard-coded value

                 long groups[] =  new long [ 0 ];

                 if (!fields.get(FIELD_SITE_TYPE).equals( "restricted" )) {

                     // this is an open group, add it to the list

                     groups =  new long [ 1 ];

                     groups[ 0 ] = groupId;

                 }

                 

                 long blanks[] =  new long [ 0 ];

                 boolean sendEmail =  false ;

                 Locale locale = PortalUtil.getSiteDefaultLocale(groupId);

                 boolean male =  true ;

                 String jobTitle =  "" ;

                 long suffixId =  0 ;

                 long prefixId =  0 ;

                 String openId =  null ;

                 long facebookId =  0 ;

                 String screenName =  null ;

                 boolean autoScreenName =  true ;

                 boolean autoPassword =  true ;

                 long creatorUserId =  0 ;

                 user = UserLocalServiceUtil.addUser(

                         creatorUserId, companyId,

                         autoPassword,  null null ,

                         autoScreenName, screenName , email,

                         facebookId, openId, locale,

                         firstName,  "" , lastName,

                         prefixId, suffixId, male,

                         1 1 2000 ,

                         jobTitle,

                         groups, blanks, blanks, blanks,

                         sendEmail ,

                         serviceContext);

             catch (Exception e) {

                 logger.error( "Unable to create user" , e);

                 throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);

             }

         }

         

         if (fields.get(FIELD_SITE_TYPE).equals( "restricted" )) {

             try {

                 MembershipRequestLocalServiceUtil.addMembershipRequest(user.getUserId(), groupId,

                         "User has requested membership via User Registration Form, you should have received an email" , serviceContext);

             catch (PortalException e) {

                 logger.error( "Unable ot add membership request" );;

             }

         }      

     catch (JSONException e) {

         logger.error( "Unable to create json object from data" );

         throw new WebApplicationException(Response.Status.BAD_REQUEST);

     }

}  

 

private Map<String, String> jsonFormDataToFields(JSONObject data) {

     Map<String, String> map =  new HashMap<String, String>();

     JSONArray fields = data.getJSONArray( "fields" );

     for ( int i =  0 ; i < fields.length(); i++) {

         JSONObject field = fields.getJSONObject(i);

         logger.info(field.toJSONString());

         map.put(field.getString( "name" ), field.getString( "value" ));

     }

     return map;

}

 

Full source code for Form Handler project can be downloaded from here:

http://extras.repo.smartfor.ms/blogs/Extending-Liferay-User-Registration/userRegistration-source.zip

 

To make it work on your Liferay installation using SmartForms you will need to:

One more thing, in SmartForm webhook configuration please change the localhost to URL of your portal:

Feel free to ask questions if you run into problems ...