These are great questions. I want to track how many people are actually logged into my site. I did this by just storing a 1 in a bit column in my database for the user who logs in. So when a user logs in, a query updates this column to a 1. When they log out, another query sets it to 0. When anyone loads the home page a number shows how many folks are online at that moment. This number comes from that SQL you showed me. <cfquery name="".......> SELECT COUNT(ONLINE) AS NumberOnline FROM USER_LOGIN WHERE ONLINE = '1' </cfquery> Code (markup): On probelm is: that when people dont log out then the database never updates and the nuber online never truly refelcts people online at that moment. It also shows a little online Icon next to every person who is online. A second probelm: If a user doesn’t log out then the Session times out. Well when the session times out the onSessionend doesn’t update the database because I can’t figure out how to tell the query in the onsessionend method which users session has timed out. Reason being that there is no session variable such as session.userid because the session has timed out. And as you said CLIENT variables dont work because onsessionend is not a request. Third problem: I don’t want the number logged in to reflect every session that starts. I don’t want to count people who are just browsing the site. I only want to count people who are logged in. Counting only the ones logged in is the easy part. Its decrementing the onlinenow variable to only refelct the ones who forgot to log themselfs out and every time a session ends when someone stops requesting pages from the server.. See what I'm getting at. Also I'm sorry if I have been a little rude lately.. You have been a big help and I do appreciate everything you have shown me. Take care,
Yes, that makes total sense. Yes, but you can get the values that are about to expire by using the session scope passed into the OnSessionEnd function. ie. #arguments.sessionScope.userID# I am assuming that only users that are logged in have a session.UserID value, right? So you should be able to use #arguments.sessionScope.userID# to to catch only the sessions of logged in users and record their logout and update the counter. Let me try a quick test and see if it works the way I'm thinking it should. > Also I'm sorry if I have been a little rude lately.. Don't even worry about it. I think we've both just been busy lately, and a bit frustrated with the CF tasks we're on
Yes, that works. I simulated a login like this <form method="post" action="LoginForm.cfm"> Test UserID <input type="text" name="userID"> <input type="submit"> </form> <!--- simulating a login here ----> <cfif IsDefined('form.userID')> <!--- assign user id to session variable ---> <cfset session.UserID = form.userID> <!--- record login in database ---> <cfquery datasource="MyDatabase" name="last_login"> INSERT INTO User_LOGIN (UserID, Last_Login_Date, OnLine) VALUES ('#session.UserID#', #createODBCDateTime(now())#, '1' ) </cfquery> <!--- increment online count ---> <cflock name="OnLineNowLock" type="exclusive" timeout="10"> <cfset application.onlinenow = application.onlinenow + 1> </cflock> </cfif> Code (markup): And here is the Application.cfc with a very short timeout. I used cflog to record whether a "loggedin" user was detected or not. You can get rid of the debugging code later. <cfcomponent output="true"> <cfset this.name="userid"> <cfset this.clientStorage = "cookie"> <cfset this.setClientCookies = "yes"> <cfset this.sessionmanagement="yes"> <cfset this.sessiontimeout=CreateTimeSpan(0,0,0,10)> <cffunction name="onApplicationStart"> Calling OnApplicationStart<br> <cfset application.onlinenow = 0> </cffunction> <cffunction name="OnSessionStart" returntype="void"> Calling OnSessionStart<br> </cffunction> <cffunction name="OnRequestStart"> <cfargument type="String" name="targetPage" required="true"/> Calling OnRequestStart<br> <cfset REQUEST.sitename = "Bla"> <cfset REQUEST.companyname = "Bla"> <cfset REQUEST.dataSource = "Bla"> <cfset dbdsn = "Bla"> <cfset dbuser = "Bla"> <cfset dbpw = "Bla"> </cffunction> <cffunction name="onSessionEnd"> <cfargument name="sessionScope" type="struct" required="true"> <cfargument name="appScope" type="struct" required="false"> <cfset var last_login = ""> <!--- if this is a logged in user, record their log out ---> <cfif structKeyExists(arguments.sessionScope, "userID")> <cftry> <cfquery datasource="MyDatabase" name="last_login"> UPDATE USER_LOGIN SET LAST_LOGIN_DATE = #CreateODBCDateTime(NOW())#, ONLINE = '0' WHERE USERID = '#arguments.SessionScope.USERID#' AND ONLINE = '1' </cfquery> <cflock type="exclusive" name="OnLineNowLock" timeout="10"> <cfset arguments.appScope.onlinenow = arguments.appScope.onlinenow - 1> </cflock> <cflog file="OnSessionEndLog" text="SUCCESS Logged out userID #arguments.SessionScope.USERID# at #now()#"> <cfcatch> <cflog file="OnSessionEndLog" text="ERROR logging out userID #arguments.SessionScope.USERID# at #now()#"> </cfcatch> </cftry> <cfelse> <cflog file="OnSessionEndLog" text="Not a logged in user at #now()#"> </cfif> </cffunction> </cfcomponent> Code (markup):
Wonderful, last night I was really thinking that I was going to have to totally rewrite the whole site and brake it up into two different parts with separate application pages and all.. You have really brought me closer to getting this one done. Although... When I execute this application on my shared server, it doesn't appear to work exactly. I'm not sure what is going on because I don't think it is creating an error, I dont think the cfif statement is working completely. I'm going to try to change the <cflog> to <cfmail> for the shared server. When I execute it on my own server I can see the log file and what is happening is the cfif statement isn't minding the "strucykeyExist" So if a user is not logged in you get SUCCESS Logged out userID at {ts '2007-12-10 21:03:44'} If they are you do get SUCCESS Logged out userID 8A7AAED8-C988-CA4E-FBE89B8087436B79 at {ts '2007-12-10 20:57:54'} You can see the #userid# var is not in the log when the user is not logged in. Meaning that the cfif statement ran anyway instead of running the <cfelse> <cfif structKeyExists(arguments.sessionScope, "userID")> Code (markup): I tried to change this to <cfif ISDefined("arguments.sessionScope.userID")> Code (markup): And this caused the <cfelse> statement to run whether a user was logged in or not. One thing that may be throwing me off is that I have cf8 for development on my PC and my shared hosting is CFMX7.. Do you think this makes a difference? Have a good night
I was able to get a few emails from the shared server and it looks like the cfif / cfelse statement is working the same way as I described it above.
That suggests to me that USERID is defined in the session scope. Are you setting a default for it somewhere? My example is predicated on session.userID only being defined for users that are logged in. If you're setting a default value for it then the current code won't work. You'll need to use something like <!--- if session.userID exists and the key is _not_ the default value assume this is a logged in user ---> <cfif structKeyExists(arguments.sessionScope, "userID") AND arguments.sessionScope.userID NEQ "the default value"> .... </cfif> Also, be careful testing it with the same browser session. ie don't get thrown off by the wrong results. That happened to me at first when I tested it. The session should be dead anyway, but you could always add a StructClear to the end of function if you want. <cfset StructClear(arguments.sessionScope)>
Your totally right. I had a little peice of code in a header that looked like this <cfif not Isdefined("session.userid")> <cfset session.userid = ""> </cfif> Code (markup): So everything with the onsession end is working, And on my cf8 server were i can see the logs I know it is working for sure. On my CFMX7 shared server, the application dosnt seem to want to timeout. Could this be because I have other application.cfc files in other folders that have a longer timeout? You may ask "Why would you have a different timeout in the other app...?" We shortened the timeout in the main application file for testing. When I build an application for a new folder, how much of the code form the original application should I include. I dont think I need to set applcation variables again do I?
Alright, the onsessionend works great. I had to work out a couple other probelms in some other pages but Its working now. I just had to get rid of every <cfset session.userid = ""> and I had to add a <cfset structdelete(session,"userid", true)> to the user_logout.cfm page so when a user does log out, it doesnt leave an empty userid string to timeout. Now I'm trying to secure all the pages I dont want people to see without logging in. Before I used: <CFSET x = Trim(#client.userid#)> <cfif len(#x#) LTE 0> <CFLOCATION URL="../../index.cfm"> </CFif> Code (markup): now I'm trying: <cfif structKeyExists(session, "userID") AND session.userID NEQ ""> <cfelse> <CFLOCATION URL="../index.cfm"> </cfif> Code (markup): Is there a better more reliable way to do this? Or do think this is fine?
Yes, either that or just checking that the value is NEQ "" would work. When you do the logout, don't forget to run the db update and decrement the application.online counter too. Because it won't happen in OnSessionEnd since session.userid key was deleted. Yes, that's fine.