This is the source code of PluginHandler.cfc.
This file is part of the Mangoblog viewCount plugin!
The code is colored using the Coldfusion code coloring component by Paul Klinkenberg.
<cfcomponent name="viewCount" extends="BasePlugin"> <cfset variables.name = "viewCount" /> <cfset variables.package = "nl/coldfusiondeveloper/mango/plugins/viewCount" /> <!--- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ---> <cffunction name="init" access="public" output="false" returntype="any"> <cfargument name="mainManager" type="any" required="true" /> <cfargument name="preferences" type="any" required="true" /> <cfset var blogid = arguments.mainManager.getBlog().getId() /> <cfset var path = blogid & "/" & variables.package /> <cfset variables.preferencesManager = arguments.preferences /> <cfset variables.manager = arguments.mainManager /> <cfset initSettings(maxHours=2, excludeSearchEngines=1, showPublicly=1, appearance="View count: $viewcount$") /> <!--- get database related specs and handler ---> <cfset variables.objQryAdapter = variables.manager.getQueryInterface() /> <!--- get db-type, but remove any version nrs etc.---> <cfset variables.dbType = rereplaceNoCase(variables.objQryAdapter.getDBType(), ".*(m[ys]sql).*", "\1") /> <cfset variables.tablePrefix = variables.objQryAdapter.getTablePrefix() /> <cfreturn this/> </cffunction> <!--- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ---> <cffunction name="setup" hint="This is run when a plugin is activated" access="public" output="false" returntype="any"> <cfset super.setup(argumentCollection=arguments) /> <!--- check supported database types ---> <cfif not listFindNoCase("mysql,mssql", variables.dbType)> <cfthrow message="Only mySQL and MSSQL databases are currently supported for plugin ViewCount!" /> </cfif> <cfset _createViewCountsTable() /> <cfreturn "ViewCount plugin activated. <br />You can now <a href='generic_settings.cfm?event=showViewCountSettings&owner=ViewCount&selected=showViewCountSettings'>Configure it</a>" /> </cffunction> <!--- :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ---> <cffunction name="processEvent" hint="Synchronous event handling" access="public" output="false" returntype="any"> <cfargument name="event" type="any" required="true" /> <cfset var outputData = "" /> <cfset var context = "" /> <cfset var postID = "" /> <cfset var err_str = "" /> <cfset var sql_str = "" /> <cfset var viewCounts_qry = "" /> <cfset var key = "" /> <cfset var data = "" /> <cfset var link = "" /> <cfset var page = "" /> <cfset var eventName = arguments.event.getName() /> <cfset var javascript_str = "" /> <cfif eventName eq "showViewCount"> <!--- this is a template event, there should be a context and a request ---> <cfset outputData = arguments.event.getOutputData() /> <cfset context = arguments.event.getContextData() /> <cfif structkeyexists(context,"currentPost")> <cfset postID = context.currentPost.getID() /> <cfset arguments.event.setOutputData(outputData & getViewCountHTML(postID)) /> </cfif> <cfelseif eventName eq "updateViewCount"> <!--- this is a template event, there should be a context and a request ---> <cfset context = arguments.event.getContextData() /> <cfif structkeyexists(context,"currentPost")> <cfset postID = context.currentPost.getID() /> <cfset _updateViewCount(postID) /> </cfif> <cfelseif eventName eq "beforePostContentEnd"> <!--- this is a template event, there should be a context and a request ---> <cfset context = arguments.event.getContextData() /> <cfif structkeyexists(context,"currentPost")> <cfset postID = context.currentPost.getID() /> <cfset _updateViewCount(postID) /> <cfif getSetting('showPublicly') eq 1> <cfset outputData = arguments.event.getOutputData() /> <cfset arguments.event.setOutputData(outputData & " " & getViewCountHTML(postID)) /> </cfif> </cfif> <!--- admin nav event ---> <cfelseif eventName eq "settingsNav"> <cfset link = structnew() /> <cfset link.owner = "ViewCount"> <cfset link.page = "settings" /> <cfset link.title = "ViewCount" /> <cfset link.eventName = "showViewCountSettings" /> <cfset arguments.event.addLink(link) /> <!--- show the viewCounts in the admin posts overview ---> <cfelseif listFindNoCase("postsNav,customPostsNav", eventName)> <!--- get all viewcounts ---> <cfset viewCounts_qry = getViewCounts() /> <!--- create javascript to inject the counts into the existing table ---> <cfsavecontent variable="javascript_str"><cfoutput> <script type="text/javascript"> $(function(){ var counts = {0:0<cfloop query="viewCounts_qry">, '#id#':#viewCount#</cfloop>}; var $postsTRs = $('##content tr'); $postsTRs.each(function() { var $a = $('a:first', this); if ($a.length) { var id = $a.get(0).href.replace(/^.*?[\?;&]id=([0-9A-F-]+).*$/i, "$1"); var td = "<td style=\"text-align:right\">" + counts[id] + "</td>"; } else var td = "<th>Views</th>"; $(this).append($(td).addClass($a.parent().attr('class'))); }); }); </script> </cfoutput></cfsavecontent> <!--- add javascript to the page ---> <cfhtmlhead text="#javascript_str#" /> <!--- admin event ---> <cfelseif eventName eq "showViewCountSettings"> <cfif variables.manager.isCurrentUserLoggedIn()> <cfset data = arguments.event.getData() /> <cfif structkeyexists(data.externaldata,"apply")> <cfif not isNumeric(data.externaldata.maxHours) or data.externaldata.maxHours lt 0 or int(data.externaldata.maxHours) neq data.externaldata.maxHours> <cfset err_str = '<p class="error">The number of hours is invalid!</p>' /> <cfelse> <cfif not findNoCase("$viewcount$", data.externaldata.appearance)> <cfset data.externaldata.appearance = data.externaldata.appearance & " $viewcount$" /> </cfif> <cfset setSettings(maxHours=data.externaldata.maxHours , excludeSearchEngines=data.externaldata.excludeSearchEngines , showPublicly=data.externaldata.showPublicly , appearance=data.externaldata.appearance) /> <cfset persistSettings() /> <cfset data.message.setstatus("success") /> <cfset data.message.setType("settings") /> <cfset data.message.settext("ViewCount settings have been saved") /> </cfif> <cfelseif structKeyExists(data.externaldata, "saveViewCounts")> <cfloop collection="#data.externaldata#" item="key"> <cfif findNoCase('viewCount_', key) eq 1 and ( not isNumeric(data.externaldata[key]) or int(data.externaldata[key]) neq data.externaldata[key] or data.externaldata[key] lt 0 )> <cfset data.message.setstatus("error") /> <cfset data.message.setType("settings") /> <cfset data.message.settext("All viewCounts must be integers, 0 or higher (#data.externaldata[key]#)") /> <cfbreak /> </cfif> </cfloop> <cfif data.message.getstatus() neq "error"> <cfloop collection="#data.externaldata#" item="key"> <cfif findNoCase('viewCount_', key) eq 1> <cfset _setViewCount(postID=listgetat(key, 2, '_'), viewCount=data.externaldata[key]) /> </cfif> </cfloop> <!--- now re-init the viewcount-cache ---> <cfset _clearViewCountCache() /> <cfset data.message.setstatus("success") /> <cfset data.message.setType("settings") /> <cfset data.message.settext("All viewCounts are updated!") /> </cfif> </cfif> <cfsavecontent variable="page"> <cfinclude template="admin/settingsForm.cfm" /> </cfsavecontent> <!--- change message ---> <cfset data.message.setTitle("ViewCount settings") /> <cfset data.message.setData(page) /> </cfif> </cfif> <cfreturn arguments.event /> </cffunction> <cffunction name="getViewCountHTML" access="public" returntype="string" hint="Returns the html indicating the viewCount for a post"> <cfargument name="postID" type="string" required="yes" /> <cfreturn replaceNoCase(getSetting('appearance'), "$viewcount$", getViewCount(arguments.postID), "all") /> </cffunction> <cffunction name="getViewCount" access="public" hint="Retrieves the view count for a given post" returntype="numeric"> <cfargument name="postID" type="string" required="yes" /> <cfset var viewCountCache_struct = _getViewCountCache() /> <cfif structKeyExists(viewCountCache_struct, arguments.postID)> <cfreturn viewCountCache_struct[arguments.postID] /> </cfif> <cfreturn 0 /> </cffunction> <cffunction name="_updateViewCount" access="private" hint="Updates the view count for a given post, when certain criteria are met" returntype="void"> <cfargument name="postID" type="string" required="yes" /> <cfset var sql_str = "" /> <cfif (getSetting('maxHours') gt 0 and _viewAlreadyLogged(postID=arguments.postID)) or (getSetting('excludeSearchEngines') and _isSearchEngine())> <!--- do nothing; already counted for this IP in the last x hours / it is a searchengine ---> <cfelse> <!--- database driver for mysql on Railo (Adobe CF as well?) can be set to only accept one statement per call. So just to be sure, call it in 2 times. ---> <cfif variables.dbType eq "mySQL"> <cfset variables.objQryAdapter.makeQuery(query="INSERT IGNORE INTO #variables.tablePrefix#viewCounts (postID, viewCount) VALUES ('#arguments.postID#', 0)", returnResult=false) /> </cfif> <cfsavecontent variable="sql_str"><cfoutput> <cfif variables.dbType eq "MSSQL"> IF NOT EXISTS ( SELECT * FROM #variables.tablePrefix#viewCounts WHERE postID = '#arguments.postID#' ) BEGIN INSERT INTO #variables.tablePrefix#viewCounts (postID, viewCount) VALUES ('#arguments.postID#', 0) END; </cfif> UPDATE #variables.tablePrefix#viewCounts SET viewCount = viewCount+1 WHERE postID = '#arguments.postID#' </cfoutput></cfsavecontent> <cfset variables.objQryAdapter.makeQuery(query=sql_str, returnResult=false) /> <cfset _updateViewCountCache(postID=arguments.postID, increaseBy=1) /> </cfif> </cffunction> <cffunction name="_setViewCount" access="private" returntype="void" hint="Sets a given view count for a given post. Is used by the admin page, where one can change the viewCounts manually ('pimp my viewCount' ;-)"> <cfargument name="postID" type="string" required="yes" /> <cfargument name="viewCount" type="numeric" required="yes" /> <cfset variables.objQryAdapter.makeQuery(query="DELETE FROM #variables.tablePrefix#viewCounts WHERE postID = '#arguments.postID#'", returnResult=false) /> <cfset variables.objQryAdapter.makeQuery(query="INSERT INTO #variables.tablePrefix#viewCounts (postID, viewCount) VALUES ('#arguments.postID#', #arguments.viewCount#)", returnResult=false) /> </cffunction> <cffunction name="_createViewCountsTable" access="private" hint="Creates the database table where results will be stored" returntype="void"> <cfset var sql_str = "" /> <cfsavecontent variable="sql_str"><cfoutput> <cfif variables.dbType eq "MSSQL"> IF OBJECT_ID('#variables.tablePrefix#viewCounts', 'U') IS NULL </cfif>CREATE TABLE <cfif variables.dbType eq "mySQL">IF NOT EXISTS </cfif>#variables.tablePrefix#viewCounts( viewCount int NOT NULL, postID varchar(35) UNIQUE NOT NULL ) </cfoutput></cfsavecontent> <cfset variables.objQryAdapter.makeQuery(query=sql_str, returnResult=false) /> </cffunction> <cffunction name="_clearViewCountCache" access="private" hint="Erases the stored viewCount cache" returntype="void"> <cflock name="writeIntoViewCountCache" timeout="3" throwontimeout="yes"> <cfset structDelete(application, "_viewCountCache", false) /> </cflock> </cffunction> <cffunction name="_updateViewCountCache" access="private" returntype="void" hint="Updates the local cache of viewCount numbers (does not alter the database; that must be done with _setViewCountCache/_updateViewCountCache)"> <cfargument name="postID" type="string" required="yes" /> <cfargument name="increaseBy" type="numeric" required="yes" hint="Can be any number, usually '1' though." /> <cfset var viewCounts_struct = "" /> <cflock name="writeIntoViewCountCache" timeout="1" throwontimeout="no"> <cfset viewCounts_struct = _getViewCountCache() /> <cfif not structKeyExists(viewCounts_struct, arguments.postID)> <cftry> <cfset structInsert(viewCounts_struct, arguments.postID, 0, false) /> <cfcatch><!--- in 0.00000001 seconds, another thread just wrote the key for us. That's okay. ---></cfcatch> </cftry> </cfif> <cfset structUpdate(viewCounts_struct, arguments.postID, viewCounts_struct[arguments.postID]+arguments.increaseBy) /> </cflock> </cffunction> <cffunction name="_getViewCountCache" access="private" hint="Retrieves all current view counts in a structure" returntype="struct"> <cfset var sql_str = "" /> <cfset var sel_qry = "" /> <cfset var viewCounts_struct = structNew() /> <!--- if the cache does not exist (anymore), create it ---> <cfif not structKeyExists(variables, "_viewCountCache")> <cfsavecontent variable="sql_str"><cfoutput> SELECT postID, viewCount FROM #variables.tablePrefix#viewCounts </cfoutput></cfsavecontent> <cfset sel_qry = variables.objQryAdapter.makeQuery(query=sql_str, returnResult=true) /> <cfloop query="sel_qry"> <cfset structInsert(viewCounts_struct, sel_qry.postID, sel_qry.viewCount) /> </cfloop> <cfset structInsert(variables, "_viewCountCache", viewCounts_struct, true) /> </cfif> <cfreturn variables._viewCountCache /> </cffunction> <cffunction name="_isSearchEngine" access="private" returntype="boolean" hint="Checks the http_user_agent to see if the current visitor is a spider/bot/serach engine"> <cfif reFindNoCase("(googlebot|yahoo\.com|wise-guys|baidu\.com|gigabot|ia_archiver|linkwalker|Ask Jeeves|Indy Library|ilse\.nl|grub\.org|voila\.com|girafabot|msnbot|qweerybot|Scooter|Surveybot|Turnitinbot|botje\.nl|ZoekyBot|looksmart\.net|walhello)", cgi.http_user_agent)> <cfreturn true /> </cfif> <cfreturn false /> </cffunction> <cffunction name="_viewAlreadyLogged" access="private" returntype="boolean" hint="Checks to see if the current IP already viewed this page in the last x hours"> <cfargument name="postID" type="string" required="yes" /> <cfargument name="ip" type="string" required="yes" default="#cgi.remote_addr#" /> <cfset var lastView = "" /> <cfif not structKeyExists(application, "_viewCountIPs")> <cfset structInsert(application, "_viewCountIPs", structNew(), true) /> </cfif> <cfif not structKeyExists(application._viewCountIPs, arguments.postID)> <cfset structInsert(application._viewCountIPs, arguments.postID, structNew(), true) /> </cfif> <!--- if the IP was logged for this post ---> <cfif structKeyExists(application._viewCountIPs[arguments.postID], arguments.ip)> <!--- check if the last view was more then maxHours ago. (if so, the datetime will be overwritten further on) ---> <cfset lastView = application._viewCountIPs[arguments.postID][arguments.ip] /> <cfif dateDiff('n', lastView, now()) lt getSetting('maxHours')*60> <cfreturn true /> </cfif> </cfif> <!--- remember this view including the view time ---> <cfset structInsert(application._viewCountIPs[arguments.postID], arguments.ip, now(), true) /> <cfreturn false /> </cffunction> <cffunction name="getViewCounts" access="public" returntype="query" hint="I return a query with all viewcounts, optionally sorted"> <cfargument name="order" type="string" required="no" default="page" /> <cfargument name="dir" type="string" required="no" default="ASC" hint="ASC or DESC" /> <cfset var sql_str = "" /> <cfsavecontent variable="sql_str"><cfoutput> SELECT <cfif findNoCase('mssql', variables.dbType)>ISNULL<cfelse>IFNULL</cfif>(#variables.tablePrefix#viewCounts.viewCount,0) AS viewCount , #variables.tablePrefix#entry.id, #variables.tablePrefix#entry.title, #variables.tablePrefix#entry.name, #variables.tablePrefix#post.posted_on FROM #variables.tablePrefix#entry INNER JOIN #variables.tablePrefix#post ON #variables.tablePrefix#post.id = #variables.tablePrefix#entry.id LEFT OUTER JOIN #variables.tablePrefix#viewCounts ON #variables.tablePrefix#viewCounts.postID = #variables.tablePrefix#entry.id ORDER BY <cfif arguments.order eq 'page'>#variables.tablePrefix#entry.title<cfelseif arguments.order eq 'date'>#variables.tablePrefix#post.posted_on<cfelse>#variables.tablePrefix#viewCounts.viewCount</cfif> <cfif listFindNoCase('asc,desc', arguments.dir)>#arguments.dir#</cfif> </cfoutput></cfsavecontent> <cfreturn variables.objQryAdapter.makeQuery(query=sql_str, returnResult=true) /> </cffunction> </cfcomponent>