PageRenderTime 240ms CodeModel.GetById 71ms app.highlight 7ms RepoModel.GetById 153ms app.codeStats 1ms

/wheels/global/public.cfm

http://cfwheels.googlecode.com/
ColdFusion | 632 lines | 583 code | 38 blank | 11 comment | 131 complexity | 1ce8163e1d197d615ddaecc640c4a9cd MD5 | raw file
  1<!--- PUBLIC CONFIGURATION FUNCTIONS --->
  2
  3<cffunction name="addFormat" returntype="void" access="public" output="false" hint="Adds a new MIME format to your Wheels application for use with responding to multiple formats."
  4	examples='
  5		<!--- Add the `js` format --->
  6		<cfset addFormat(extension="js", mimeType="text/javascript")>
  7
  8		<!--- Add the `ppt` and `pptx` formats --->
  9		<cfset addFormat(extension="ppt", mimeType="application/vnd.ms-powerpoint")>
 10		<cfset addFormat(extension="pptx", mimeType="application/vnd.ms-powerpoint")>
 11	'
 12	categories="configuration" chapters="responding-with-multiple-formats" functions="provides,renderWith">
 13	<cfargument name="extension" type="string" required="true" hint="File extension to add." />
 14	<cfargument name="mimeType" type="string" required="true" hint="Matching MIME type to associate with the file extension." />
 15	<cfset application.wheels.formats[arguments.extension] = arguments.mimeType />
 16</cffunction>
 17
 18<cffunction name="addRoute" returntype="void" access="public" output="false" hint="Adds a new route to your application."
 19	examples=
 20	'
 21		<!--- Example 1: Adds a route which will invoke the `profile` action on the `user` controller with `params.userName` set when the URL matches the `pattern` argument --->
 22		<cfset addRoute(name="userProfile", pattern="user/[username]", controller="user", action="profile")>
 23
 24		<!--- Example 2: Category/product URLs. Note the order of precedence is such that the more specific route should be defined first so Wheels will fall back to the less-specific version if it''s not found --->
 25		<cfset addRoute(name="product", pattern="products/[categorySlug]/[productSlug]", controller="products", action="product")>
 26		<cfset addRoute(name="productCategory", pattern="products/[categorySlug]", controller="products", action="category")>
 27		<cfset addRoute(name="products", pattern="products", controller="products", action="index")>
 28
 29		<!--- Example 3: Change the `home` route. This should be listed last because it is least specific --->
 30		<cfset addRoute(name="home", pattern="", controller="main", action="index")>
 31	'
 32	categories="configuration" chapters="using-routes" functions="">
 33	<cfargument name="name" type="string" required="false" default="" hint="Name for the route. This is referenced as the `name` argument in functions based on @URLFor like @linkTo, @startFormTag, etc.">
 34	<cfargument name="pattern" type="string" required="true" hint="The URL pattern that the route will match.">
 35	<cfargument name="controller" type="string" required="false" default="" hint="Controller to call when route matches (unless the controller name exists in the pattern).">
 36	<cfargument name="action" type="string" required="false" default="" hint="Action to call when route matches (unless the action name exists in the pattern).">
 37	<cfscript>
 38		var loc = {};
 39
 40		// throw errors when controller or action is not passed in as arguments and not included in the pattern
 41		if (!Len(arguments.controller) && arguments.pattern Does Not Contain "[controller]")
 42			$throw(type="Wheels.IncorrectArguments", message="The `controller` argument is not passed in or included in the pattern.", extendedInfo="Either pass in the `controller` argument to specifically tell Wheels which controller to call or include it in the pattern to tell Wheels to determine it dynamically on each request based on the incoming URL.");
 43		if (!Len(arguments.action) && arguments.pattern Does Not Contain "[action]")
 44			$throw(type="Wheels.IncorrectArguments", message="The `action` argument is not passed in or included in the pattern.", extendedInfo="Either pass in the `action` argument to specifically tell Wheels which action to call or include it in the pattern to tell Wheels to determine it dynamically on each request based on the incoming URL.");
 45
 46		loc.thisRoute = Duplicate(arguments);
 47		loc.thisRoute.variables = "";
 48		if (Find(".", loc.thisRoute.pattern))
 49		{
 50			loc.thisRoute.format = ListLast(loc.thisRoute.pattern, ".");
 51			loc.thisRoute.formatVariable = ReplaceList(loc.thisRoute.format, "[,]", "");
 52			loc.thisRoute.pattern = ListFirst(loc.thisRoute.pattern, ".");
 53		}
 54		loc.iEnd = ListLen(loc.thisRoute.pattern, "/");
 55		for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
 56		{
 57			loc.item = ListGetAt(loc.thisRoute.pattern, loc.i, "/");
 58
 59			if (REFind("^\[", loc.item))
 60				loc.thisRoute.variables = ListAppend(loc.thisRoute.variables, ReplaceList(loc.item, "[,]", ""));
 61		}
 62		ArrayAppend(application.wheels.routes, loc.thisRoute);
 63	</cfscript>
 64</cffunction>
 65
 66<cffunction name="addDefaultRoutes" returntype="void" access="public" output="false" hint="Adds the default Wheels routes (for example, `[controller]/[action]/[key]`, etc.) to your application. Only use this method if you have set `loadDefaultRoutes` to `false` and want to control exactly where in the route order you want to place the default routes."
 67	examples=
 68	'
 69		<!--- Adds the default routes to your application (done in `config/routes.cfm`) --->
 70		<cfset addDefaultRoutes()>
 71	'
 72	categories="configuration" chapters="using-routes" functions="">
 73	<cfscript>
 74		addRoute(pattern="[controller]/[action]/[key]");
 75		addRoute(pattern="[controller]/[action]");
 76		addRoute(pattern="[controller]", action="index");
 77	</cfscript>
 78</cffunction>
 79
 80<cffunction name="set" returntype="void" access="public" output="false" hint="Use to configure a global setting or set a default for a function."
 81	examples=
 82	'
 83		<!--- Example 1: Set the `URLRewriting` setting to `Partial` --->
 84		<cfset set(URLRewriting="Partial")>
 85
 86		<!--- Example 2: Set default values for the arguments in the `buttonTo` view helper. This works for the majority of Wheels functions/arguments. --->
 87		<cfset set(functionName="buttonTo", onlyPath=true, host="", protocol="", port=0, text="", confirm="", image="", disable="")>
 88
 89		<!--- Example 3: Set the default values for a form helper to get the form marked up to your preferences --->
 90		<cfset set(functionName="textField", labelPlacement="before", prependToLabel="<div>", append="</div>", appendToLabel="<br />")>
 91	'
 92	categories="configuration" chapters="configuration-and-defaults" functions="get">
 93	<cfscript>
 94		var loc = {};
 95		if (ArrayLen(arguments) > 1)
 96		{
 97			for (loc.key in arguments)
 98			{
 99				if (loc.key != "functionName")
100					for (loc.i = 1; loc.i lte listlen(arguments.functionName); loc.i = loc.i + 1) {
101						application.wheels.functions[Trim(ListGetAt(arguments.functionName, loc.i))][loc.key] = arguments[loc.key];
102					}
103			}
104		}
105		else
106		{
107			application.wheels[StructKeyList(arguments)] = arguments[1];
108		}
109	</cfscript>
110</cffunction>
111
112<!--- PUBLIC GLOBAL FUNCTIONS --->
113
114<!--- miscellaneous --->
115
116<cffunction name="controller" returntype="any" access="public" output="false" hint="Creates and returns a controller object with your own custom `name` and `params`. Used primarily for testing purposes."
117	examples='
118		<cfset testController = controller("users", params)>
119	'
120	categories="global,miscellaneous" chapters="" functions="">
121	<cfargument name="name" type="string" required="true" hint="Name of the controller to create.">
122	<cfargument name="params" type="struct" required="false" default="#StructNew()#" hint="The params struct (combination of `form` and `URL` variables).">
123	<cfscript>
124		var loc = {};
125		loc.args = {};
126		loc.args.name = arguments.name;
127		loc.returnValue = $doubleCheckedLock(name="controllerLock", condition="$cachedControllerClassExists", execute="$createControllerClass", conditionArgs=loc.args, executeArgs=loc.args);
128		if (!StructIsEmpty(arguments.params))
129			loc.returnValue = loc.returnValue.$createControllerObject(arguments.params);
130		return loc.returnValue;
131	</cfscript>
132</cffunction>
133
134<cffunction name="deobfuscateParam" returntype="string" access="public" output="false" hint="Deobfuscates a value."
135	examples=
136	'
137		<!--- Get the original value from an obfuscated one --->
138		<cfset originalValue = deobfuscateParam("b7ab9a50")>
139	'
140	categories="global,miscellaneous" chapters="obfuscating-urls" functions="obfuscateParam">
141	<cfargument name="param" type="string" required="true" hint="Value to deobfuscate.">
142	<cfscript>
143		var loc = {};
144		if (Val(SpanIncluding(arguments.param, "0,1,2,3,4,5,6,7,8,9")) != arguments.param)
145		{
146			try
147			{
148				loc.checksum = Left(arguments.param, 2);
149				loc.returnValue = Right(arguments.param, (Len(arguments.param)-2));
150				loc.z = BitXor(InputBasen(loc.returnValue,16),461);
151				loc.returnValue = "";
152				loc.iEnd = Len(loc.z)-1;
153				for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
154					loc.returnValue = loc.returnValue & Left(Right(loc.z, loc.i),1);
155				loc.checksumtest = "0";
156				loc.iEnd = Len(loc.returnValue);
157				for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
158					loc.checksumtest = (loc.checksumtest + Left(Right(loc.returnValue, loc.i),1));
159				loc.c1 = ToString(FormatBaseN((loc.checksumtest+154),10));
160				loc.c2 = InputBasen(loc.checksum, 16);
161				if (loc.c1 != loc.c2)
162					loc.returnValue = arguments.param;
163			}
164			catch(Any e)
165			{
166		    	loc.returnValue = arguments.param;
167			}
168		}
169		else
170		{
171	    	loc.returnValue = arguments.param;
172		}
173	</cfscript>
174	<cfreturn loc.returnValue>
175</cffunction>
176
177<cffunction name="get" returntype="any" access="public" output="false" hint="Returns the current setting for the supplied Wheels setting or the current default for the supplied Wheels function argument."
178	examples=
179	'
180		<!--- Get the current value for the `tableNamePrefix` Wheels setting --->
181		<cfset setting = get("tableNamePrefix")>
182
183		<!--- Get the default for the `message` argument of the `validatesConfirmationOf` method  --->
184		<cfset setting = get(functionName="validatesConfirmationOf", name="message")>
185	'
186	categories="global,miscellaneous" chapters="configuration-and-defaults" functions="set">
187	<cfargument name="name" type="string" required="true" hint="Variable name to get setting for.">
188	<cfargument name="functionName" type="string" required="false" default="" hint="Function name to get setting for.">
189	<cfscript>
190		var loc = {};
191		if (Len(arguments.functionName))
192			loc.returnValue = application.wheels.functions[arguments.functionName][arguments.name];
193		else
194			loc.returnValue = application.wheels[arguments.name];
195	</cfscript>
196	<cfreturn loc.returnValue>
197</cffunction>
198
199<cffunction name="model" returntype="any" access="public" output="false" hint="Returns a reference to the requested model so that class level methods can be called on it."
200	examples=
201	'
202		<!--- The `model("author")` part of the code below gets a reference to the model from the application scope, and then the `findByKey` class level method is called on it --->
203		<cfset authorObject = model("author").findByKey(1)>
204	'
205	categories="global,miscellaneous" chapters="object-relational-mapping" functions="">
206	<cfargument name="name" type="string" required="true" hint="Name of the model to get a reference to.">
207	<!--- we need an instance of the model to be able to build queries without adding the query data to the application scope model --->
208	<cfreturn $doubleCheckedLock(name="modelLock", condition="$cachedModelClassExists", execute="$createModelClass", conditionArgs=arguments, executeArgs=arguments)>
209</cffunction>
210
211<cffunction name="obfuscateParam" returntype="string" access="public" output="false" hint="Obfuscates a value. Typically used for hiding primary key values when passed along in the URL."
212	examples=
213	'
214		<!--- Obfuscate the primary key value `99` --->
215		<cfset newValue = obfuscateParam(99)>
216	'
217	categories="global,miscellaneous" chapters="obfuscating-urls" functions="deobfuscateParam">
218	<cfargument name="param" type="any" required="true" hint="Value to obfuscate.">
219	<cfscript>
220		var loc = {};
221		if (IsValid("integer", arguments.param) && IsNumeric(arguments.param) && arguments.param > 0)
222		{
223			// railo strips leading zeros from integers so do this for both engines
224			arguments.param = Val(SpanIncluding(arguments.param, "0,1,2,3,4,5,6,7,8,9"));
225			loc.iEnd = Len(arguments.param);
226			loc.a = (10^loc.iEnd) + Reverse(arguments.param);
227			loc.b = "0";
228			for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
229				loc.b = (loc.b + Left(Right(arguments.param, loc.i), 1));
230			loc.returnValue = FormatBaseN((loc.b+154),16) & FormatBaseN(BitXor(loc.a,461),16);
231		}
232		else
233		{
234			loc.returnValue = arguments.param;
235		}
236	</cfscript>
237	<cfreturn loc.returnValue>
238</cffunction>
239
240<cffunction name="pluginNames" returntype="string" access="public" output="false" hint="Returns a list of all installed plugins' names."
241	examples=
242	'
243		<!--- Check if the Scaffold plugin is installed --->
244		<cfif ListFindNoCase("scaffold", pluginNames())>
245			<!--- do something cool --->
246		</cfif>
247	'
248	categories="global,miscellaneous" chapters="using-and-creating-plugins" functions="">
249	<cfreturn StructKeyList(application.wheels.plugins)>
250</cffunction>
251
252<cffunction name="URLFor" returntype="string" access="public" output="false" hint="Creates an internal URL based on supplied arguments."
253	examples=
254	'
255		<!--- Create the URL for the `logOut` action on the `account` controller, typically resulting in `/account/log-out` --->
256		##URLFor(controller="account", action="logOut")##
257
258		<!--- Create a URL with an anchor set on it --->
259		##URLFor(action="comments", anchor="comment10")##
260
261		<!--- Create a URL based on a route called `products`, which expects params for `categorySlug` and `productSlug` --->
262		##URLFor(route="product", categorySlug="accessories", productSlug="battery-charger")##
263	'
264	categories="global,miscellaneous" chapters="request-handling,linking-pages" functions="redirectTo,linkTo,startFormTag">
265	<cfargument name="route" type="string" required="false" default="" hint="Name of a route that you have configured in `config/routes.cfm`.">
266	<cfargument name="controller" type="string" required="false" default="" hint="Name of the controller to include in the URL.">
267	<cfargument name="action" type="string" required="false" default="" hint="Name of the action to include in the URL.">
268	<cfargument name="key" type="any" required="false" default="" hint="Key(s) to include in the URL.">
269	<cfargument name="params" type="string" required="false" default="" hint="Any additional params to be set in the query string.">
270	<cfargument name="anchor" type="string" required="false" default="" hint="Sets an anchor name to be appended to the path.">
271	<cfargument name="onlyPath" type="boolean" required="false" hint="If `true`, returns only the relative URL (no protocol, host name or port).">
272	<cfargument name="host" type="string" required="false" hint="Set this to override the current host.">
273	<cfargument name="protocol" type="string" required="false" hint="Set this to override the current protocol.">
274	<cfargument name="port" type="numeric" required="false" hint="Set this to override the current port number.">
275	<cfargument name="$URLRewriting" type="string" required="false" default="#application.wheels.URLRewriting#">
276	<cfscript>
277		var loc = {};
278		$args(name="URLFor", args=arguments);
279		loc.params = {};
280		if (StructKeyExists(variables, "params"))
281			StructAppend(loc.params, variables.params, true);
282		if (application.wheels.showErrorInformation)
283		{
284			if (arguments.onlyPath && (Len(arguments.host) || Len(arguments.protocol)))
285				$throw(type="Wheels.IncorrectArguments", message="Can't use the `host` or `protocol` arguments when `onlyPath` is `true`.", extendedInfo="Set `onlyPath` to `false` so that `linkTo` will create absolute URLs and thus allowing you to set the `host` and `protocol` on the link.");
286		}
287
288		// get primary key values if an object was passed in
289		if (IsObject(arguments.key))
290		{
291			arguments.key = arguments.key.key();
292		}
293
294		// build the link
295		loc.returnValue = application.wheels.webPath & ListLast(request.cgi.script_name, "/");
296		if (Len(arguments.route))
297		{
298			// link for a named route
299			loc.route = $findRoute(argumentCollection=arguments);
300			if (arguments.$URLRewriting == "Off")
301			{
302				loc.returnValue = loc.returnValue & "?controller=";
303				if (Len(arguments.controller))
304					loc.returnValue = loc.returnValue & hyphenize(arguments.controller);
305				else
306					loc.returnValue = loc.returnValue & hyphenize(loc.route.controller);
307				loc.returnValue = loc.returnValue & "&action=";
308				if (Len(arguments.action))
309					loc.returnValue = loc.returnValue & hyphenize(arguments.action);
310				else
311					loc.returnValue = loc.returnValue & hyphenize(loc.route.action);
312				// add it the format if it exists
313				if (StructKeyExists(loc.route, "formatVariable") && StructKeyExists(arguments, loc.route.formatVariable))
314					loc.returnValue = loc.returnValue & "&#loc.route.formatVariable#=#arguments[loc.route.formatVariable]#";
315				loc.iEnd = ListLen(loc.route.variables);
316				for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
317				{
318					loc.property = ListGetAt(loc.route.variables, loc.i);
319					if (loc.property != "controller" && loc.property != "action")
320						loc.returnValue = loc.returnValue & "&" & loc.property & "=" & $URLEncode(arguments[loc.property]);
321				}
322			}
323			else
324			{
325				loc.iEnd = ListLen(loc.route.pattern, "/");
326				for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
327				{
328					loc.property = ListGetAt(loc.route.pattern, loc.i, "/");
329					if (loc.property Contains "[")
330					{
331						loc.property = Mid(loc.property, 2, Len(loc.property)-2);
332						if (application.wheels.showErrorInformation && !StructKeyExists(arguments, loc.property))
333							$throw(type="Wheels", message="Incorrect Arguments", extendedInfo="The route chosen by Wheels `#loc.route.name#` requires the argument `#loc.property#`. Pass the argument `#loc.property#` or change your routes to reflect the proper variables needed.");
334						loc.param = $URLEncode(arguments[loc.property]);
335						if (loc.property == "controller" || loc.property == "action")
336							loc.param = hyphenize(loc.param);
337						else if (application.wheels.obfuscateUrls)
338							loc.param = obfuscateParam(loc.param);
339						loc.returnValue = loc.returnValue & "/" & loc.param; // get param from arguments
340					}
341					else
342					{
343						loc.returnValue = loc.returnValue & "/" & loc.property; // add hard coded param from route
344					}
345				}
346				// add it the format if it exists
347				if (StructKeyExists(loc.route, "formatVariable") && StructKeyExists(arguments, loc.route.formatVariable))
348					loc.returnValue = loc.returnValue & ".#arguments[loc.route.formatVariable]#";
349			}
350		}
351		else // link based on controller/action/key
352		{
353			// when no controller or action was passed in we link to the current page (controller/action only, not query string etc) by default
354			if (!Len(arguments.controller) && !Len(arguments.action) && StructKeyExists(loc.params, "action"))
355				arguments.action = loc.params.action;
356			if (!Len(arguments.controller) && StructKeyExists(loc.params, "controller"))
357				arguments.controller = loc.params.controller;
358			if (Len(arguments.key) && !Len(arguments.action) && StructKeyExists(loc.params, "action"))
359				arguments.action = loc.params.action;
360			loc.returnValue = loc.returnValue & "?controller=" & hyphenize(arguments.controller);
361			if (Len(arguments.action))
362				loc.returnValue = loc.returnValue & "&action=" & hyphenize(arguments.action);
363			if (Len(arguments.key))
364			{
365				loc.param = $URLEncode(arguments.key);
366				if (application.wheels.obfuscateUrls)
367					loc.param = obfuscateParam(loc.param);
368				loc.returnValue = loc.returnValue & "&key=" & loc.param;
369			}
370		}
371
372		if (arguments.$URLRewriting != "Off")
373		{
374			loc.returnValue = Replace(loc.returnValue, "?controller=", "/");
375			loc.returnValue = Replace(loc.returnValue, "&action=", "/");
376			loc.returnValue = Replace(loc.returnValue, "&key=", "/");
377		}
378		if (arguments.$URLRewriting == "On")
379		{
380			loc.returnValue = Replace(loc.returnValue, application.wheels.rewriteFile, "");
381			loc.returnValue = Replace(loc.returnValue, "//", "/");
382		}
383
384		if (Len(arguments.params))
385			loc.returnValue = loc.returnValue & $constructParams(params=arguments.params, $URLRewriting=arguments.$URLRewriting);
386		if (Len(arguments.anchor))
387			loc.returnValue = loc.returnValue & "##" & arguments.anchor;
388
389		if (!arguments.onlyPath)
390		{
391			if (arguments.port != 0)
392				loc.returnValue = ":" & arguments.port & loc.returnValue; // use the port that was passed in by the developer
393			else if (request.cgi.server_port != 80 && request.cgi.server_port != 443)
394				loc.returnValue = ":" & request.cgi.server_port & loc.returnValue; // if the port currently in use is not 80 or 443 we set it explicitly in the URL
395			if (Len(arguments.host))
396				loc.returnValue = arguments.host & loc.returnValue;
397			else
398				loc.returnValue = request.cgi.server_name & loc.returnValue;
399			if (Len(arguments.protocol))
400				loc.returnValue = arguments.protocol & "://" & loc.returnValue;
401			else
402				loc.returnValue = SpanExcluding(LCase(request.cgi.server_protocol), "/") & "://" & loc.returnValue;
403		}
404	</cfscript>
405	<cfreturn loc.returnValue>
406</cffunction>
407
408<!--- string helpers --->
409
410<cffunction name="capitalize" returntype="string" access="public" output="false" hint="Returns the text with the first character converted to uppercase."
411	examples=
412	'
413		<!--- Capitalize a sentence, will result in "Wheels is a framework" --->
414		##capitalize("wheels is a framework")##
415	'
416	categories="global,string" chapters="miscellaneous-helpers" functions="humanize,pluralize,singularize">
417	<cfargument name="text" type="string" required="true" hint="Text to capitalize.">
418	<cfif !Len(arguments.text)>
419		<cfreturn arguments.text />
420	</cfif>
421	<cfreturn UCase(Left(arguments.text, 1)) & Mid(arguments.text, 2, Len(arguments.text)-1)>
422</cffunction>
423
424<cffunction name="humanize" returntype="string" access="public" output="false" hint="Returns readable text by capitalizing and converting camel casing to multiple words."
425	examples=
426	'
427		<!--- Humanize a string, will result in "Wheels Is A Framework" --->
428		##humanize("wheelsIsAFramework")##
429
430		<!--- Humanize a string, force wheels to replace "Cfml" with "CFML" --->
431		##humanize("wheelsIsACFMLFramework", "CFML")##
432	'
433	categories="global,string" chapters="miscellaneous-helpers" functions="capitalize,pluralize,singularize">
434	<cfargument name="text" type="string" required="true" hint="Text to humanize.">
435	<cfargument name="except" type="string" required="false" default="" hint="a list of strings (space separated) to replace within the output.">
436	<cfscript>
437		var loc = {};
438		loc.returnValue = REReplace(arguments.text, "([[:upper:]])", " \1", "all"); // adds a space before every capitalized word
439		loc.returnValue = REReplace(loc.returnValue, "([[:upper:]]) ([[:upper:]])(?:\s|\b)", "\1\2", "all"); // fixes abbreviations so they form a word again (example: aURLVariable)
440		if (Len(arguments.except))
441		{
442			loc.iEnd = ListLen(arguments.except, " ");
443			for (loc.i = 1; loc.i lte loc.iEnd; loc.i++)
444			{
445				loc.a = ListGetAt(arguments.except, loc.i);
446				loc.returnValue = ReReplaceNoCase(loc.returnValue, "#loc.a#(?:\b)", "#loc.a#", "all");
447			}
448		}
449		loc.returnValue = Trim(capitalize(loc.returnValue)); // capitalize the first letter and trim final result (which removes the leading space that happens if the string starts with an upper case character)
450	</cfscript>
451	<cfreturn loc.returnValue>
452</cffunction>
453
454<cffunction name="pluralize" returntype="string" access="public" output="false" hint="Returns the plural form of the passed in word. Can also pluralize a word based on a value passed to the `count` argument."
455	examples=
456	'
457		<!--- Pluralize a word, will result in "people" --->
458		##pluralize("person")##
459
460		<!--- Pluralize based on the count passed in --->
461		Your search returned ##pluralize(word="person", count=users.RecordCount)##
462	'
463	categories="global,string" chapters="miscellaneous-helpers" functions="capitalize,humanize,singularize">
464	<cfargument name="word" type="string" required="true" hint="The word to pluralize.">
465	<cfargument name="count" type="numeric" required="false" default="-1" hint="Pluralization will occur when this value is not `1`.">
466	<cfargument name="returnCount" type="boolean" required="false" default="true" hint="Will return `count` prepended to the pluralization when `true` and `count` is not `-1`.">
467	<cfreturn $singularizeOrPluralizeWithCount(text=arguments.word, which="pluralize", count=arguments.count, returnCount=arguments.returnCount)>
468</cffunction>
469
470<cffunction name="singularize" returntype="string" access="public" output="false" hint="Returns the singular form of the passed in word."
471	examples=
472	'
473		<!--- Singularize a word, will result in "language" --->
474		##singularize("languages")##
475	'
476	categories="global,string" chapters="miscellaneous-helpers" functions="capitalize,humanize,pluralize">
477	<cfargument name="word" type="string" required="true" hint="String to singularize.">
478	<cfreturn $singularizeOrPluralizeWithCount(text=arguments.word, which="singularize")>
479</cffunction>
480
481<cffunction name="toXHTML" returntype="string" access="public" output="false" hint="Returns an XHTML-compliant string."
482	examples=
483	'
484		<!--- Outputs `productId=5&amp;categoryId=12&amp;returningCustomer=1` --->
485		<cfoutput>
486			##toXHTML("productId=5&categoryId=12&returningCustomer=1")##
487		</cfoutput>
488	'
489	categories="global,string" chapters="" functions="">
490	<cfargument name="text" type="string" required="true" hint="String to make XHTML-compliant.">
491	<cfset arguments.text = Replace(arguments.text, "&", "&amp;", "all")>
492	<cfreturn arguments.text>
493</cffunction>
494
495<cffunction name="mimeTypes" returntype="string" access="public" output="false" hint="Returns an associated MIME type based on a file extension."
496	examples=
497	'
498		<!--- Get the internally-stored MIME type for `xls` --->
499		<cfset mimeType = mimeTypes("xls")>
500
501		<!--- Get the internally-stored MIME type for a dynamic value. Fall back to a MIME type of `text/plain` if it''s not found --->
502		<cfset mimeType = mimeTypes(extension=params.type, fallback="text/plain")>
503	'
504	categories="global,miscellaneous" chapters="" functions="">
505	<cfargument name="extension" required="true" type="string" hint="The extension to get the MIME type for.">
506	<cfargument name="fallback" required="false" type="string" default="application/octet-stream" hint="the fallback MIME type to return.">
507	<cfif StructKeyExists(application.wheels.mimetypes, arguments.extension)>
508		<cfset arguments.fallback = application.wheels.mimetypes[arguments.extension]>
509	</cfif>
510	<cfreturn arguments.fallback>
511</cffunction>
512
513<cffunction name="hyphenize" returntype="string" access="public" output="false" hint="Converts camelCase strings to lowercase strings with hyphens as word delimiters instead. Example: `myVariable` becomes `my-variable`."
514	examples=
515	'
516		<!--- Outputs "my-blog-post" --->
517		<cfoutput>
518			##hyphenize("myBlogPost")##
519		</cfoutput>
520	'
521	categories="global,string" chapters="" functions="">
522	<cfargument name="string" type="string" required="true" hint="The string to hyphenize.">
523	<cfset arguments.string = REReplace(arguments.string, "([A-Z][a-z])", "-\l\1", "all")>
524	<cfset arguments.string = REReplace(arguments.string, "([a-z])([A-Z])", "\1-\l\2", "all")>
525	<cfset arguments.string = REReplace(arguments.string, "^-", "", "one")>
526	<cfreturn LCase(arguments.string)>
527</cffunction>
528
529<!--- PRIVATE FUNCTIONS --->
530
531<cffunction name="$singularizeOrPluralizeWithCount" returntype="string" access="public" output="false" hint="Decides if we need to convert the word based on the count value passed in and then adds the count to the string.">
532	<cfargument name="text" type="string" required="true" hint="See documentation for @pluralize.">
533	<cfargument name="count" type="numeric" required="false" default="-1" hint="See documentation for @pluralize.">
534	<cfargument name="returnCount" type="boolean" required="false" default="true" hint="See documentation for @pluralize.">
535	<cfargument name="which" type="string" required="true" hint="Should be either `singularize` or `pluralize`.">
536	<cfscript>
537		var loc = {};
538		loc.returnValue = $args(name="$singularizeOrPluralizeWithCount", cachable=true, args=arguments);
539		if (!StructKeyExists(loc, "returnValue"))
540		{
541			// run conversion unless count is passed in and its value means conversion is unnecessary
542			if (arguments.which == "pluralize" && arguments.count == 1)
543				loc.returnValue = arguments.text;
544			else
545				loc.returnValue = $singularizeOrPluralize(text=arguments.text, which=arguments.which);
546	
547			// return the count number in the string (e.g. "5 sites" instead of just "sites")
548			if (arguments.returnCount && arguments.count != -1)
549				loc.returnValue = LSNumberFormat(arguments.count) & " " & loc.returnValue;
550		}
551		return loc.returnValue;
552	</cfscript>
553</cffunction>
554
555<cffunction name="$singularizeOrPluralize" returntype="string" access="public" output="false" hint="Converts a word to singular or plural form.">
556	<cfargument name="text" type="string" required="true" hint="See documentation for @pluralize.">
557	<cfargument name="which" type="string" required="true" hint="See documentation for @$singularizeOrPluralizeWithCount.">
558	<cfscript>
559		var loc = {};
560		loc.returnValue = $args(name="$singularizeOrPluralize", cachable=true, args=arguments);
561		if (!StructKeyExists(loc, "returnValue"))
562		{
563			// default to returning the same string when nothing can be converted
564			loc.returnValue = arguments.text;
565			
566			// only pluralize/singularize the last part of a camelCased variable (e.g. in "websiteStatusUpdate" we only change the "update" part)
567			// also set a variable with the unchanged part of the string (to be prepended before returning final result)
568			if (REFind("[A-Z]", arguments.text))
569			{
570				loc.upperCasePos = REFind("[A-Z]", Reverse(arguments.text));
571				loc.prepend = Mid(arguments.text, 1, Len(arguments.text)-loc.upperCasePos);
572				arguments.text = Reverse(Mid(Reverse(arguments.text), 1, loc.upperCasePos));
573			}
574
575			loc.uncountables = "advice,air,blood,deer,equipment,fish,food,furniture,garbage,graffiti,grass,homework,housework,information,knowledge,luggage,mathematics,meat,milk,money,music,pollution,research,rice,sand,series,sheep,soap,software,species,sugar,traffic,transportation,travel,trash,water,feedback";
576			loc.irregulars = "child,children,foot,feet,man,men,move,moves,person,people,sex,sexes,tooth,teeth,woman,women";
577			if (ListFindNoCase(loc.uncountables, arguments.text))
578			{
579				// this word is the same in both plural and singular so it can just be returned as is
580				loc.returnValue = arguments.text;
581			}
582			else if (ListFindNoCase(loc.irregulars, arguments.text))
583			{
584				// this word cannot be converted in a standard way so we return a preset value as specifed in the list above
585				loc.pos = ListFindNoCase(loc.irregulars, arguments.text);
586				if (arguments.which == "singularize" && loc.pos MOD 2 == 0)
587					loc.returnValue = ListGetAt(loc.irregulars, loc.pos-1);
588				else if (arguments.which == "pluralize" && loc.pos MOD 2 != 0)
589					loc.returnValue = ListGetAt(loc.irregulars, loc.pos+1);
590				else
591					loc.returnValue = arguments.text;
592			}
593			else
594			{
595				// this word can probably be converted to plural/singular using standard rules so we'll do that
596				// we'll start by setting the rules and create an array from them
597				if (arguments.which == "pluralize")
598					loc.ruleList = "(quiz)$,\1zes,^(ox)$,\1en,([m|l])ouse$,\1ice,(matr|vert|ind)ix|ex$,\1ices,(x|ch|ss|sh)$,\1es,([^aeiouy]|qu)y$,\1ies,(hive)$,\1s,(?:([^f])fe|([lr])f)$,\1\2ves,sis$,ses,([ti])um$,\1a,(buffal|tomat|potat|volcan|her)o$,\1oes,(bu)s$,\1ses,(alias|status)$,\1es,(octop|vir)us$,\1i,(ax|test)is$,\1es,s$,s,$,s";
599				else if (arguments.which == "singularize")
600					loc.ruleList = "(quiz)zes$,\1,(matr)ices$,\1ix,(vert|ind)ices$,\1ex,^(ox)en,\1,(alias|status)es$,\1,([octop|vir])i$,\1us,(cris|ax|test)es$,\1is,(shoe)s$,\1,(o)es$,\1,(bus)es$,\1,([m|l])ice$,\1ouse,(x|ch|ss|sh)es$,\1,(m)ovies$,\1ovie,(s)eries$,\1eries,([^aeiouy]|qu)ies$,\1y,([lr])ves$,\1f,(tive)s$,\1,(hive)s$,\1,([^f])ves$,\1fe,(^analy)ses$,\1sis,((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$,\1\2sis,([ti])a$,\1um,(n)ews$,\1ews,(.*)?ss$,\1ss,s$,#Chr(7)#";
601				loc.rules = ArrayNew(2);
602				loc.count = 1;
603				loc.iEnd = ListLen(loc.ruleList);
604				for (loc.i=1; loc.i <= loc.iEnd; loc.i=loc.i+2)
605				{
606					loc.rules[loc.count][1] = ListGetAt(loc.ruleList, loc.i);
607					loc.rules[loc.count][2] = ListGetAt(loc.ruleList, loc.i+1);
608					loc.count = loc.count + 1;
609				}
610				
611				// loop through the rules looking for a match and perform the regex replace when we find one
612				loc.iEnd = ArrayLen(loc.rules);
613				for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
614				{
615					if (REFindNoCase(loc.rules[loc.i][1], arguments.text))
616					{
617						loc.returnValue = REReplaceNoCase(arguments.text, loc.rules[loc.i][1], loc.rules[loc.i][2]);
618						break;
619					}
620				}
621
622				// set back to blank string since we worked around the fact that we can't have blank values in lists above by using Chr(7) instead
623				loc.returnValue = Replace(loc.returnValue, Chr(7), "", "all");
624			}
625	
626			// if this is a camel cased string we need to prepend the unchanged part to the result
627			if (StructKeyExists(loc, "prepend"))
628				loc.returnValue = loc.prepend & loc.returnValue;
629		}
630		return loc.returnValue;
631	</cfscript>
632</cffunction>