The profile manager in the Web store is probably the most tricky piece of code in this Web application as it deals with the ability to create profiles and reconnect to it via Cookie assignments. The Cookie munging is rather a messy piece of code and I'll try to explain the flow and cookie handling in this topic.

The Profile manager is fairly sophisticated. It's tied to the cCustomer object of the wwStore and provides the following features:

The base profile page looks like this:

The base profile management and handling was basically described in the Placing an Order topic, so we won't discuss it again here. Basically the oCustomer object is used to handle display and saving of the profile information.

A couple of things that are important in the Profile forms is how the Country and State drop downs work. These lists are hardcoded into the HTML with their appropriate state and country codes as key values. If they're static how do they get selected properly once a user comes back to his profile? A little JavaScript handles selection of the country in the page's Onload event:

<body  onload="SelectListItem(document.forms[0].Country,'<%= oCust.CountryId %>');
                           SelectListItem(document.forms[0].State,'<%= oCust.State %>');">

The list could have been generated on the fly since the coutry codes are stored in a table in wws_lookups. This is the way it originally worked, but building the country and state drop down took a while because these lists are fairly long containing a fair amount of data. Hence some script was added to provide an easy way to select the item:

<script language="JavaScript">
function SelectListItem(loList,lcValue) {
   loList.selectedIndex = 0;

	for (x=0;x<loList.length-1;x++) {
     if (loList.options[x].value == lcValue) {
	       loList.selectedIndex = x;
	       break;
	    }
	}
}
</script>

Other than that remember that all fields in the form are 'bound' to an oCust member and embedded into the input fields of the actual form. When the customer is saved the business object validates the customer and saves the data or - on an error - redisplays the data with an error message.

Re-attaching to a profile


Profiles are managed through the SessionId cookie that gets assigned when a user comes to the store for the first time. The Cookie gets written in the wwStoreProcess::Process() method with the InitSession method of the wwProcess class.

The customer is mapped from this session id with a SessionID field in the Customer table. The cookie is written as a permanent cookie. When the user returns that cookie is read again and he is automatically reattached to this profile remembering his customer info.

For this to work people must stick to the same browser on the same machine. So, the store supports reconnecting to their original user profile by entering their email address and a password which is saved with the profile:

This code is actually a bit tricky as it needs to reassign and re-write an existing cookie, then return to the page that the user originally went to. This code hits the LogonProfile method.

This method is another one of those that handles both the input form and its response as you can see by the case statement above. If a password is to be emailed the code does the following:

 IF !loCustomer.Find("trim(email) == [" + pcEmail + "]" )
         pcErrorMsg = "The specified email address does not exist. Please sign up for a brand new account profile."

         *** Go to the profile page and display an error message
         THIS.Profile(pcErrorMsg)
      ELSE  
         IF EMPTY(loCustomer.oData.Password)
            loCustomer.oData.Password = LOWER(SUBSTR(SYS(2015),2))
            loCustomer.Save()
         ENDIF
	     THIS.SendEmail(pcEmail,Config.cStoreName + " Profile Password.",;
	                    "Your logon info for your online account is: "  +CRLF + CRLF +;
	                    "Username: " + pcEmail + CRLF +;
	                    "Password: " + loCustomer.oData.Password + CRLF + CRLF +;
	                    "The West Wind Team" + CRLF,,,.T.)
	                    
	     pcErrorMsg = "Check your email and then re-enter your password on this form."               
      ENDIF 	                    
  
      *** Just redisplay this form
      Response.ExpandTemplate( Request.GetPhysicalPath())
ENDIF

where pcEmail is the captured form field. Then an email is sent.

If the user enters a password along with the email address the password is checked:

      IF !loCustomer.Find("trim(email) == [" + pcEmail + "] and password = padr([" + lcPassword + "],10)")
         *** Redisplay this form
         pcErrorMsg = "Invalid Password.<br>If you forgot your password check the <i>Forgot your Password?</i> checkbox."
         Response.ExpandTemplate( Config.cHTMLPagePath + "LogonProfile.wws") 
         RETURN
      ENDIF   
      
      *** Ok - logged in
     
      *** Now fix the current session by updating the CookieId with the User ID
      lcSessionId = loCustomer.oData.UserId
      IF EMPTY(loCustomer.oData.UserId)
        *** User never had an ID???
         lcSessionId = SYS(2015)
         loCustomer.oData.UserId = lcSessionID
         loCustomer.Save()
      ENDIF
      
      *** Kill any existing sessions with the same ID
      Session.DeleteSession(lcSessionId)
      
      *** Find the current session and update the current SessionID
      IF !Session.LocateSession(Session.cSessionID)
         Session.NewSession()
      ENDIF
       
      Session.SetSessionId(lcSessionID)
      
      *** Force a new Cookie to be written with that value when page is built
      Response.cAutoSessionCookieName = Config.cCookieName
      Response.cAutoSessionCookie = loCustomer.oData.UserID
      Response.lAutoSessionCookiePersist = .T.
      
      *** Make sure the cookie and Id match 
      Session.SetSessionVar("CustomerPK",TRANS(loCustomer.oData.Pk))

      *** Preset Foreign or US/Can status in session for shopping cart
      IF !INLIST(loCustomer.oData.CountryId,"US","CA")
         Session.SetSessionVar("Foreign","True")
      ENDIF         

      *** Write out our new Session Cookie then redirect to
      *** the page we need to go to:
      DO CASE 
       CASE pcNextMethod = "OrderProfile"
         THIS.StandardPage("Attaching to your profile",[<a href="orderprofile.wws">Click here</a> if your browser isn't navigating to the profile form automatically.],,1,"orderprofile.wws")
       CASE pcNextMethod = "Contest"  
         THIS.StandardPage("Attaching to your profile",[<a href="/webconnection/contest.wst">Click here</a> if your browser isn't navigating to the profile form automatically.],,1,"/webconnection/contest.wst")
       CASE pcNextMethod = "CodeSignup"  
         THIS.StandardPage("Attaching to your profile",[<a href="/webconnection/codesignup.wst">Click here</a> if your browser isn't navigating to the profile form automatically.],,1,"/webconnection/codesignup.wst")
       OTHERWISE
         THIS.StandardPage("Attaching to your profile",[<a href="orderprofile.wws">Click here</a> if your browser isn't navigating to the profile form automatically.],,1,"profile.wws")
      ENDCASE

The code starts by validating the user. If the wrong password was passed in the user gets the login form again to try again. If the email and password match the code now goes on to delete the current session and then creates a new Session. In addition the name and value of the Cookie is explicitly reset using the cAutoSession properties. These properties get automatically written into the HTTP header by the Response object when the HTTP header gets written to output. This guarantees that the cookie is matched to the customer.

It's important that the Cookie gets actually written to the user before we move on since the profile pages rely on the Session to figure out which customer to load. If the cookie isn't written correctly these follow up pages can't retrieve the right data! In order to do so, this code writes out an HTML page with StandardPage that has a Meta Refresh (the 1,"orderprofile.wws" parameter for example) to immediately navigate to the next page. This is an indirect redirect which uses an immediate page and a Meta Refresh tag or a link to click on for ancient browsers that don't support META headers. Plain Redirects do not support HTTP headers so a Cookie can't be written with them. Check wwProcess::StandardPage for more info on auto-refresh operation in Web Connection.

The cookie gets written, and then the actual result page is displaying the input forms with the filled data from the customer object. Note the CASE statement on the bottom of the page which determines where to go next. The cNextMethod property holds the page name. This value comes from the querystring or from an explicit knowledge of the URL that started off the profile operation. I use this same profile code to handle the main profile, the order profile, as well as several contests we run on the West Wind site. Each of those is going to a custom template page that looks slightly different, but has the same behavior as this profile page here. The CASE statement routes these things to the appropriate link.

As I said this code is very messy. Looking at it it appears this is overcomplicated for what should be a simple thing. However, the only way I got this to work reliably is by using this code. Some simpler approaches wouldn't work 100% of the time...


Profiles are important to Web sites


Profile management is very powerful. I use this profile manager for everything on the West Wind site from logins to the message board to the contest signups to the customer profile. This kind of behavior is very useful for any Web site that keeps in contact with its customers. There's no reason to come up with several different profile managers when one can be reused and tied to a single cookie on your Web site! Make good use of it.


Last Updated: 4/29/2000 | Send topic feedback