Custom buildURL() method for FW/1
UPDATED: The functionality has now been rolled into the framework as of version 1.2, so the hack is unnecessary. The methodology for overriding a framework method still applies, but let me reiterate: be very careful what you change, and look out for any code changes to the overridden method in future releases!
Original post continues below…
My (first) FW/1 application consists of two subsystems: public and admin. The public subsystem is the default, so if I invoke a URL action without a subsystem specified explicitly, it will use ‘public’.
Of course, if you use the buildURL() method to create your links (which you should be doing!) it will always prepend your action with public: – which I find a little untidy, especially when using SES URLs.
It’s very easy to customise FW/1 to “fix” this. Simply import a customisation file at the top of your Application.cfc file – alongside all the application settings – and put the following code in that file:
<cffunction name="buildURL_custom" access="public" output="false">
<cfargument name="action" type="string" />
<cfargument name="path" type="string" default="#variables.framework.baseURL#" />
<cfargument name="queryString" type="string" default="" />
<cfset var initialDelim = '?' />
<cfset var varDelim = '&' />
<cfset var equalDelim = '=' />
<cfset var basePath = '' />
<cfset var extraArgs = '' />
<cfset var queryPart = '' />
<cfset var anchor = '' />
<cfset var ses = false />
<cfset var omitIndex = false />
<cfif arguments.path eq "useCgiScriptName">
<cfset arguments.path = CGI.SCRIPT_NAME />
<cfif variables.framework.SESOmitIndex>
<cfset arguments.path = getDirectoryFromPath( arguments.path ) />
<cfset omitIndex = true />
</cfif>
</cfif>
<cfif find( '?', arguments.action ) and arguments.queryString is ''>
<!--- shorthand for action/queryString pairing --->
<cfset arguments.queryString = REReplace( arguments.action, '[^\?]*\?', '') />
<cfset arguments.action = REReplace( arguments.action, '([^\?\##]*).*', '\1') />
</cfif>
<cfif find( '?', arguments.path ) gt 0>
<cfif right( arguments.path, 1 ) eq '?' or right( arguments.path, 1 ) eq '&'>
<cfset initialDelim = '' />
<cfelse>
<cfset initialDelim = '&' />
</cfif>
<cfelseif structKeyExists( request, 'generateSES' ) and request.generateSES>
<cfif omitIndex>
<cfset initialDelim = '' />
<cfelse>
<cfset initialDelim = '/' />
</cfif>
<cfset varDelim = '/' />
<cfset equalDelim = '/' />
<cfset ses = true />
</cfif>
<!--- Start customisation. Don't display subsystem if in default subsystem --->
<cfif ses>
<cfif getSubsystem( arguments.action ) eq getConfig().defaultsubsystem>
<cfset basePath = arguments.path & initialDelim & replace( getSectionAndItem( arguments.action ), '.', '/' ) />
<cfelse>
<cfset basePath = arguments.path & initialDelim & replace( getFullyQualifiedAction( arguments.action ), '.', '/' ) />
</cfif>
<cfelse>
<cfif getSubsystem( arguments.action ) eq getConfig().defaultsubsystem>
<cfset basePath = arguments.path & initialDelim & variables.framework.action & equalDelim & getSectionAndItem(arguments.action) />
<cfelse>
<cfset basePath = arguments.path & initialDelim & variables.framework.action & equalDelim & getFullyQualifiedAction(arguments.action) />
</cfif>
</cfif>
<!--- End customisation --->
<cfif len( arguments.queryString )>
<cfset extraArgs = REReplace( arguments.queryString, '([^\?\##]*).*', '\1') />
<cfif find( '?', arguments.queryString )>
<cfset queryPart = REReplace( arguments.queryString, '[^\?]*\?([^\##]*).*', '\1') />
</cfif>
<cfif find( '##', arguments.queryString )>
<cfset anchor = REReplace( arguments.queryString, '[^\##]*\##(.*)', '\1' ) />
</cfif>
<cfif ses>
<cfset extraArgs = listChangeDelims( extraArgs, '/', '&=' ) />
</cfif>
<cfif extraArgs is not ''>
<cfset basePath = basePath & varDelim & extraArgs />
</cfif>
<cfif queryPart is not ''>
<cfif ses>
<cfset basePath = basePath & '?' & queryPart />
<cfelse>
<cfset basePath = basePath & '&' & queryPart />
</cfif>
</cfif>
<cfif anchor is not ''>
<cfset basePath = basePath & '##' & anchor />
</cfif>
</cfif>
<cfreturn basePath />
</cffunction>
<cfset variables.buildURL = variables.buildURL_custom />
The code is just a copy-and-paste of the buildURL() method from the org.corfield.framework cfc. The changes I’ve made are in the commented area in the middle, and just tell it that if the requested action is in the default subsystem, then only return the section and item in the URL; otherwise use the fully qualified action.
Note that I’ve renamed the method to buildURL_custom, and then used a cfset after the end of the function to replace the pre-defined method, as you are not allowed to define the same method twice.
You can use this method to override other FW/1 methods, but I’d advise a touch of caution: your changes might have knock-on effects! Also, keep an eye on any future updates of the framework to see if you need to apply code changes to your custom methods.
> keep an eye on any future updates of the framework to see
> if you need to apply code changes to your custom methods.
Yeah, especially since I'm about to go create a patch for FW/1 to correct/improve some of the regexes in the original function. :/
The buildURL() function you copied is already out of date and does not include a recent enhancement / bug fix so, as Peter mentioned, you need to be very careful about wholesale overriding of functions.
There's an issue open for this and it will get addressed.
Perhaps a better workaround would be to call super.buildURL(args...) and then strip the :public from the result if found.
@Pete - yes, that probably would be a better method. But as I note, the point is moot now anyway as the functionality is now part of the framework. But a useful thought for anything else that involves over-riding built-in methods.
I agree with Pete - overriding the function and calling super.whatever() is a safer way to modify functionality and protects you when the framework is upgraded.