Is it possible to undefine a variable in ColdFusion?
For example, something like this:
<cfset myVar = "lsajflksd" />
<cfoutput>
<p>myVar is Defined? #IsDefined("myVar")#</p> <!--- Prints YES --->
</cfoutput>
<cfset Undefine(myVar) /> <!--- Doesn't exist... --->
<cfoutput>
<p>myVar is Defined? #IsDefined("myVar")#</p> <!--- I want it to print NO --->
</cfoutput>
<cfset StructDelete(Variables, "myVar") />
Variables is the default scope for most variables in most contexts.
In modern versions, you can also use the struct.delete() member function.
myVar = "lsajflksd";
variables.delete('myVar');
https://docs.lucee.org/reference/objects/struct/delete.html
FYI...
<cffunction name="voidFunc" returntype="void">
</cffunction>
<cfset myVar = voidFunc()>
<cfoutput>#IsDefined("myVar")#</cfoutput> <!--- will show NO --->
I found out from this blog entry: cfinvoke destroys returnVariable for methods that return void
Related
We're having this issue where we are unable to decrypt a string we encrypted. It's a bit more complex then that, but that is what needs solving.
<cfset URLString = "https://someurl/report/somereport?date=12/27/2017&areaid=25&districtid=111®ion=southwest&city=Tampa&localekey=X999&localename=Ybor&informed=true">
<cfset FindTheQ = Find('?', URLString)>
<cfset MinusTheQ = Val(FindTheQ + 1)>
<cfset BaseURL = Left(URLString, FindTheQ)>
<cfset URLStringLength = Len(URLString)>
<cfset TheVariables = Mid(URLString, MinusTheQ, URLStringLength)>
<cfset SecretKey=GenerateSecretKey("AES")>
<cfset Encrypted = encrypt(TheVariables, SecretKey, "AES", "HEX") />
<cfset Decrypted = decrypt(Encrypted, SecretKey, "AES", "HEX") />
<cfset CompleteURL = BaseURL & Decrypted>
<cfoutput>
URLString: #URLString#<BR><BR>
BaseURL: #BaseURL#<BR><BR>
Variables: #TheVariables#<BR><BR>
Encrypted: #Encrypted#<BR><BR>
Decrypted: #Decrypted#<BR><BR>
CompleteURL: #CompleteURL#<BR><BR>
</cfoutput>
So above is a nice little test cfm page that works great. We get a string that is a URL. We extract it into two parts, the BaseURL and TheVariables. The idea is to encrypt TheVariables but not the BaseURL. Then we decrypt TheVariables, and concatenate with the BaseURL for the CompleteURL. I output them at the bottom to display how it works. Yay!
However, in the "real world" it's not one simple cfm page. In the real code, it's a component within the controller of a cfwheels framework with a separate encrypt and decrypt function being called with Javascript and Ajax. Sheesh! So assume all the related code exists to the two functions below (I included the minimum for readability.
<cfcomponent extends="Controller">
<cffunction name="EncryptLink">
<cfset SecretKey = "WTq8zYcZfaWVvMncigHqwQ==">
<cfset FindTheQ = Find('?', URL.URLString)>
<cfset MinusTheQ = Val(FindTheQ + 1)>
<cfset BaseURL = Left(URL.URLString, FindTheQ)>
<cfset URLStringLength = Len(URL.URLString)>
<cfset TheVariables = Mid(URL.URLString, MinusTheQ, URLStringLength)>
<cfset Encrypted = encrypt(TheVariables, SecretKey, "AES", "Base64") />
<cfset CompleteURL = BaseURL & Encrypted>
<cfreturn renderText(CompleteURL)>
</cffunction>
<cffunction name="DecryptLink">
<cfset SecretKey = "WTq8zYcZfaWVvMncigHqwQ==">
<cfset FindTheQ = Find('?', URL.URLString)>
<cfset MinusTheQ = Val(FindTheQ + 1)>
<cfset BaseURL = Left(URL.URLString, FindTheQ)>
<cfset URLStringLength = Len(URL.URLString)>
<cfset TheVariables = Mid(URL.URLString, MinusTheQ, URLStringLength)>
<cfset Decrypted = decrypt(TheVariables, SecretKey, "AES", "Base64")>
<cfset CompleteURL = BaseURL & Decrypted>
<cfreturn renderText(CompleteURL)>
</cffunction>
The result from the encrypt function is exactly what we want. It works! Yay again! However, when the decrypt function runs it brings back the first part, the BaseURL correctly, but the Decrypted part comes back random characters and those black diamonds with the question marks in them. Which means it's not being interpreted.
Additional:
The GenerateSecretKey function won't work for us since it will generate a new key when link is clicked to come in to be decrypted.
We've tried AES, DES, & Blowfish encryptions.
We've tried it with base64, UU, and hex encodings.
The renderText() function is for cfwheels and just returns the text you
specify. It is useful as a response to AJAX requests.
(Answer based on the original code)
The result from the encrypt function is exactly what we want.
Actually, it's not.
The string logic is slightly off, so BaseURL includes one too many characters. The first char of the query string ("d" in this case) gets added to the variables string. That changes the encrypted value, which is why it won't decrypt.
BaseURL: https://someurl/report/somereport?d
Variables: ?date=12/27/2017&...
Encrypted: 771D3386211040E83B0FD64F25...
CompleteURL: https://someurl/report/somereport?d771D3386211040E83B0FD64F25...
I'm assuming that isn't the real Decrypt() code, since the arguments don't match those used for Encrypt(). The params should be:
decrypt(TheVariables, SecretKey, "AES", "HEX")
A few other suggestions:
Using list functions (with delimiter ?) would simplify the code a LOT
Local scope all function variables
Use the arguments scope for parameters, instead of using URL directly
<cfoutput>
<cfloop from="1" to="10" index="i">
#i#<br />
<cfif i EQ 3>
<cfset i -= 3 />
blarg #i# <br >
</cfif>
</cfloop>
</cfoutput>
How can i decrease, one decrement value with forloop
and my expected output should be
1
2
3
blarg 0
3
4
5
6
7
8
9
10
The value of i in your example is controlled by the inner workings of <cfloop>, and its value simply exposed to the code within the loop block. You can change it within that block, but <cfloop> will simply expose the next value in the iteration each time.
To do what you want to do, either use a for(;;) loop, or a <cfloop> equivalent of same:
<cfset i=1>
<cfloop condition="i LTE 10">
<!--- your logic here--->
<cfset i++>
</cfloop>
I have a jQuery Mobile app that has several dynamic forms. What I am trying to do is parse the form submissions and get the value of the form field. When the data is received from the processing script, it looks as follows.
str:app_EAD17776-155D-D714-FB34E569AA814746_ser=a&app_EAD17776-155D-D714-FB34E569AA814746_app=A1234Washer%2C+ABC&app_EAD17776-155D-D714-FB34E569AA814746_chk=&app_EAD17776-155D-D714-FB34E569AA814747_ser=a&app_EAD17776-155D-D714-FB34E569AA814747_app=A1234Dryer%2C+ABC&app_EAD17776-155D-D714-FB34E569AA814747_chk=
What I need to do from there is get the values of each of these an put them in a 2 dimensional array.
I need to further explain the formfields in order for you to seem my problem. I will break down the formfield.
This formfield is an appliance serial number:
app_EAD17776-155D-D714-FB34E569AA814746_ser=a
The applianceType is: EAD17776-155D-D714-FB34E569AA814746
EAD17776-155D-D714-FB34E569AA814746 = Washer
Serial Number = a
This formfield is the Brand and Name of Appliance:
app_EAD17776-155D-D714-FB34E569AA814746_app=A1234Washer%2C+ABC
The appliance type is: EAD17776-155D-D714-FB34E569AA814746
Washer Brand and Name: A1234Washer, ABC
This formfield is an active checkbox:
app_EAD17776-155D-D714-FB34E569AA814746_chk=
The applianceType is: EAD17776-155D-D714-FB34E569AA814746
So what I am trying to do with this information is loop over this data and insert the form values along with the appropriate applianceType. First I query the active appliance types.
<cfquery name="qApplianceTypes">
SELECT *
FROM ApplianceTypes
WHERE Active = <cfqueryparam value="1" cfsqltype="cf_sql_bit">
</cfquery>
Then I loop over the results and match up the formfields applianceTypes in order to get the values and apply it to the array.
<cfset applianceTypeArray = ArrayNew(2)>
<cfset count = 1>
<cfloop query="qqApplianceTypes">
<!--- [1] appliance id ---->
<cfset applianceTypeArray[count][1] = qApplianceType.ApplianceTypeID>
<!--- loop over formfields to find relative id --->
<cfloop index="i" list="#Form.str#" delimiters=",">
<cfif right(i, 4) IS "_IDd" and qApplianceType.ApplianceTypeID is "#GetToken(ListFirst(i,' '),2,'_')#" >
<!--- [4] appliance ---->
<cfset applianceTypeArray[count][2] = #FORM[i]#>
</cfif>
</cfloop>
<!--- loop over formfields to find relative serialnumbers --->
<cfloop index="i" list="#Form.str#" delimiters=",">
<cfif right(i, 4) IS "_ser" and qApplianceType.ApplianceTypeID is "#GetToken(ListFirst(i,' '),2,'_')#" >
<!--- [3] serial number ---->
<cfset applianceTypeArray[count][3] = #FORM[i]#>
</cfif>
</cfloop>
<!--- loop over formfields to find relative appliance --->
<cfloop index="i" list="#Form.str#" delimiters=",">
<cfif right(i, 4) IS "_app" and qApplianceType.ApplianceTypeID is "#GetToken(ListFirst(i,' '),2,'_')#" >
<!--- [4] appliance ---->
<cfset applianceTypeArray[count][4] = #FORM[i]#>
</cfif>
</cfloop>
<cfset applianceTypeArray[count][5] = qApplianceType.ApplianceTypeID>
<!--- loop over formfields to find relative active or innactive --->
<cfloop index="i" list="#Form.str#" delimiters=",">
<cfif right(i, 4) IS "_chk" and qApplianceType.ApplianceTypeID is "#GetToken(ListFirst(i,' '),2,'_')#" >
<!--- [2] active ---->
<cfset applianceTypeArray[count][6] = #FORM[i]#>
</cfif>
</cfloop>
<cfset qloopcount2 = #count#>
<cfset count = count+1>
</cfloop>
Once I get the applianceTypeArray populated, I simply loop over the array to populate the database.
The problem is that I am not getting the values. It works is a form post but I have to use a get. When I dump the array, I get the following data.
0: "EAD17776-155D-D714-FB34E569AA814746"
1: null
2: null
3: null
4: "EAD17776-155D-D714-FB34E569AA814746"
0: "EAD17776-155D-D714-FB34E569AA814747"
1: null
2: null
3: null
4: "EAD17776-155D-D714-FB34E569AA814747"
First issue is that the elements start with 0 instead of 1 and I am not getting a elements 6. Second is that elements 0 - 4 are not the values but rather the appliance type ids.
Any suggestions?
I've heard that if you do not specify output="false" on a ColdFusion function that unnecessary buffering would occur which could hinder performance. So I wanted to run a test to see if I could prove this. My test is below. I saw no difference at all between output="true" or output="false".
So my question is: if I have functions used within large loops should I not have to worry about this setting? Or am I not testing this correctly?
My test was to call the same function 1,000,000 times. I ran it 3 times with output="false" and 3 times with output="true". All 6 tests finished at exactly 20-21 seconds.
The Test Code:
<cffunction name="good" output="false" returntype="Numeric" access="private">
<cfargument name="numIn" type="numeric" required="true">
<cfset var x = 0>
<cfset x = arguments.numIn + 1>
<cfreturn x>
</cffunction>
<cffunction name="bad" output="true" returntype="Numeric" access="private">
<cfargument name="numIn" type="numeric" required="true">
<cfset var x = 0>
<cfset x = arguments.numIn + 1>
<cfreturn x>
</cffunction>
<cfset loopNum = 1000000>
<cfset x = 0>
<cfoutput>
x = #x#<br>
Running bad function #loopNum# times...<br>
</cfoutput>
<cfset tBegin = GetTickCount()>
<cfloop from="1" to="#loopNum#" index="i">
<cfset x = bad(i)>
</cfloop>
<cfset tEnd = GetTickCount()>
<cfset scriptTime = (tEnd - tBegin)>
<cfoutput>
x = #x#<br>
Time to complete: #scriptTime#<br>
</cfoutput>
<!---
<cfset x = 0>
<cfoutput>
x = #x#<br>
Running good function #loopNum# times...<br>
</cfoutput>
<cfset tBegin = GetTickCount()>
<cfloop from="1" to="#loopNum#" index="i">
<cfset x = good(i)>
</cfloop>
<cfset tEnd = GetTickCount()>
<cfset scriptTime = (tEnd - tBegin)>
<cfoutput>
x = #x#<br>
Time to complete: #scriptTime#<br>
</cfoutput>
--->
I agree that it is best practice to always include output="false" in your functions unless you have a very specific and very good reason not to (it's okay in some of the Application.cfc methods like onRequest() for example). But I've never heard anyone say that the reason for this had anything to do with performance, either processor utilization or memory consumption. The primary reason I maintain it is so that you don't have sudden unexpected errors at runtime (caused by that same unnecessary buffering).
Here's an easy example:
<cffunction name="getFlag" output="true">
<cfreturn true />
</cffunction>
<cfif getFlag()><p>Hello World!</p></cfif>
<cfxml variable="doc"><cfoutput><root flag="#getFlag()#" /></cfoutput></cfxml>
<cfif doc.root.xmlAttributes.flag><p>Hello world!</p></cfif>
You might write something like this, thinking everything should be fine, since the first <cfif> test works fine and you can dump out the xml document and it looks okay from the output of a <cfdump>. And then later you would get the error message this example creates:
cannot convert the value " true" to a boolean
Because the white-space from the function bled into the XML attribute and while CF will allow you to treat the string "true" as a boolean, it won't convert the string " true". And then like many of us have before, you might spend a long time banging your head, trying to figure out why there's extra white-space in your XML document when there is no extra space in the return value from your function. It happened to me once when custom functions were introduced in CF5. ;)
I'm still trying to get my grips on Coldfusion...
I need to create a directy of files (say there are 10 files) and output 5 random files. Getting and outputting files is ok, but I'm not sure where to fit in the randrange. Here is my code:
<cfdirectory action="list" directory="#expandpath("img/")#" filter="some*.*" name="dir">
<!--- imgID --->
<CFSET imgID= #RandRange(1, #dir.allRecords#)#>
<!--- this only grabs the first 5 files --->
<cfoutput query="dir" maxrows="5">
<cfif FileExists("#expandpath("img/#name#")#")>
<cfimage source="#expandpath("img/#name#")#" name="myImage"> <cfif IsImage(myImage) is true>
<cfset ImageSetAntialiasing(myImage,"on")>
<cfset ImageScaleToFit(myImage,"highestQuality")>
<!--- append to a list --->
<li><cfimage source="#myImage#" action="writeToBrowser"></li>
</cfif>
</cfif>
</cfoutput>
This works ok in displaying the first 5 images. However, I would like to have 5 random images.
Thanks for some insights!
EDIT:
This is how I ended up doing it - ONE QUESTION UNSOLVED -
<!-- get the directy, listinfo="name" because I only need filenames --->
<cfdirectory action="list" LISTINFO="name" directory="#expandpath(" logos/")#" filter="marke*.*" name="dir">
<cfset images=[ ]>
<!-- since dir is not indexable, like dir[pos], I need another array!-->
<cfset dirArr=[ ]>
<cfset blocker="false">
<cfset maxLogos=5>
<!-- fill new dirArr(ay) -->
<cfoutput query="dir">
<cfset #ArrayAppend(dirArr, #expandpath( "logos/#name#")#)#>
</cfoutput>
<!-- loop -->
<cfloop condition="blocker eq false">
<-- random position -->
<cfset pos=R andRange(1, #dir.recordcount#)>
<cfif #dir.recordcount# eq 0 OR #ArrayLen(images)# gte #maxLogos#>
<-- STOP loop -->
<cfset blocker="true">
</cfif>
<cfset ArrayAppend(images, #dirArr[pos]#)>
<!-- BROKEN unknown ARRAYDELETE -->
<!--- <cfset ArrayDelete(dirArr, #dirArr[pos]#)> --->
<!-- IMG -->
<cfimage source="#dirArr[pos]#" name="myImage">
<cfif IsImage(myImage) is true>
<cfoutput>
<li data-icon="false">
<cfimage source="#myImage#" action="writeToBrowser">
</li>
</cfoutput>
</cfif>
</cfloop>
The problem is the ArrayDelete does not work variable ARRAYDELETE is undefined, Coldfusion(8) tells me. Any idea what I'm doing wrong?
A simple alternative is to shuffle the array once and then take the first five items:
<cfset MaxLogos = 5 />
<cfset Images = [] />
<cfset Files = DirectoryList( expandPath("logos") , false, "name" , "marke*.jpg" ) />
<cfset createObject( "java", "java.util.Collections" ).shuffle( Files ) />
<cfloop index="i" from="1" to=#Min(MaxLogos,ArrayLen(Files))# >
<cfset ArrayAppend( Images , Files[i] ) />
</cfloop>
<cfdump var=#Images# />
I'm not sure if your code will actually work as there appears to be several syntactical errors in it. Also you're doing a directory list on img but then pulling images from logos and you've not made it clear what the relationship is between these directories
those issues aside, here is how i would handle this.
<cfscript>
// this code is untested, but should get you going
// get list of image file names as an array
dir = directoryList(expandPath("imgs"), false, "name", "*.jpg");
images = [];
while(true) {
// if out directory list is now empty or we have 5 results, we're done
if(!arrayLen(dir) or arrayLen(images) gte 5) break;
// get an image from a random point in the list
pos = randrange(1, arrayLen(dir));
// append it to our images array
arrayAppend(images, dir[pos]);
// delete form the source array, this avoids duplicates in further iterations
arrayDeleteAt(dir, pos);
}
</cfscript>
This gives you an array of images, with between 0 and 5 elements, which you can then output as a list.
As a side note, its not advisable to use <cfimage> and related functions repeatedly. If you need to resize or manipulate an image you should then cache it back to disk rather than repeating the manipulation every request.