how can I use cfdirectory to make a list of documents downloadable using ColdFusion/Lucee? - coldfusion

In my Application.cfc, I setup a mapping
this.mappings["/downloads"]="J:\Downloads\documents";
In my template, I have
<cfdirectory action="list" directory="#expandpath("/downloads")#" filter="*.zip|*.docx" name="downloads" recurse="yes">
<!--- <cfdump var="#expandpath("/software")#"> --->
<cfdump var="#downloads#">
<ul>
<cfoutput query="#downloads#">
<li>#downloads.name#</li>
</cfoutput>
</ul>
I'm trying to make the documents downloadable but when the link is clicked, nothing is happening which makes me think my links are not correct however when I mouse over the link, I see the full path which is correct.
What am I missing to make the list of documents clickable?
Here is the URL displayed when mouseover the 3rd document for example.

Since the files are outside of your webroot you will need to have ColdFusion read the file and send it back to the browser.
You will need to create a page, like download.cfm, that can accept a URL parameter to know which file to access. Once you have selected the file you can use something like the following to stream the file.
<cfheader name="Content-disposition" value="attachment;filename=#datafile#">
<cfcontent file="#datafile#" type="application/pdf">
The above code was pulled from https://www.raymondcamden.com/2006/03/10/Ask-a-Jedi-Using-ColdFusion-to-serve-files
WARNING:
Reading URL parameters in this way and giving people access to the filesystem is extremely unsafe. Safer alternatives should be considered before moving something like this into a production environment.

All I needed to do for this exercise is to setup a mapping in my Application.cfc. As others have stated, there is zero security here but for the purpose of this exercise of understanding virtual directories (IIS) and aliases (CommandBox), this is sufficient.
this.mappings["/guides"]="J:\guides";
Then I can use cfdirectory to build my query object
<cfdirectory action="list" directory="j:\guides" recurse="false" name="nameofqry" type="file" sort="datelastmodified desc" filter="*.docx">
Next, perform a cfoutput using my alias as the a href link
<cfoutput query="nameofqry" maxrows="40">
<li>#nameofqry.name#</li>
</cfoutput>

Related

Why do CFM templates being included with cfinclude need their own <cfoutput> wrappers?

I am using <cfinclude> to include various pages within a master page. In my master page all the body content is wrapped in <cfoutput> tags. However I noticed that after testing, the included .cfm page don't see the <cfoutput> tags at all and hence don't display the dynamic data.
<body>
<cfoutput>
<cfinclude template="page1.cfm" />
<cfinclude template="page2.cfm" />
<cfinclude template="page3.cfm" />
</cfoutput>
</body>
In the above example, the included templates that have dynamic data in them will not display properly. To solve this I have to add <cfoutput> tags within each of the CFM files. How come they can't use the <cfoutput> tags that are already there within the body?
Because each CFML file is compiled separately, and whether or not to output something is determined at compile time, not runtime.
Set aside how ColdFusion works, you want to do this. The point of using something like CFINCLUDE is that you're able to write a chunk of code once and use it in multiple situations.
Think of it as a poor mans' encapsulation. Someone should be able to use that included template without getting bogged down in the mundane details of the template that is being included.

Export to Excel - Hidden Field not generating results

I've a simple CFC file that contains the different functions for different queries & a separate function that displays the reports dynamically based on the queries.
All the queries work except one which returns approx. 50k rows. Its just a blank screen & I get no error. When I dump the query results, they do get dumped on the screen but while displaying it in a tabular report it gives nothing.
I've a another CFM file that returns 100k rows & works fine.
Below is CFC code that is not working.
<cfcomponent>
<cfparam name="qry1" default="">
<cffunction name="showqry1" access="remote">
<cfquery name="qry1" dataSource="myds" cachedwithin="#CreateTimeSpan(0, 2, 0, 0)#">
<!--- myquery --->
</cfquery>
<cfset Display()>
</cffunction>
<cffunction name="showqry2" access="remote">
<cfquery name="qry1" dataSource="myds" cachedwithin="#CreateTimeSpan(0, 2, 0, 0)#">
<!--- myquery --->
</cfquery>
<cfset Display()>
</cffunction>
<cffunction name="Display" access="private">
<cfdump var="#rptQry#" top="20">
<cfsavecontent variable="myrpt">
<table>
<!--- make a tabular report here using cfloop over the query--->
</table>
</cfsavecontent>
<cfform action="test.cfm" method="post" name="ExcelData">
<cfoutput>#myrpt#</cfoutput>
<cfinput type="hidden" name="excel_data" value="#myrpt#"/><!---This is giving the error. --->
<cfinput type="submit" name="test" value="Export" />
</cfform>
</cffunction>
</cfcomponent>
Any idea why CFM works fine but CFC doesn't? I need my CFC to work & dont want it to convert it to CFM...
UPDATE:
I've added a comment("This is giving the error") in the above code that is cause of the error. Irrespective of CFC/CFM this doesn't work.
I use the hidden field to pass data to another file which exports data to excel. Any alternate suggestions??
Any help is highly appreciated.
Thanks
You still need to read that doc I put in the comment about how to ask questions clearly.
However you are putting your recordset into a variable qry1, but trying to dump a variable rptQry. But that would just error, unless there's some code you're not showing us that populates rptQry.
Also, from a coding practice POV, you shouldn't really be outputting stuff in your functions: that's best done in a CFM page. Get your data with a CFC method; display it with a CFM.
I also recommend you read up on how to do OO with CFML (or in general). Perhaps get Matt Gifford's book "Object-Oriented Programming in ColdFusion"
Your Display function has cfsavecontent with tabular data and you are putting it into a cfform inside a cfc. I don't know why you are doing that. Insted, simply do an ajax call which return that cfsavecontent and then show it in the cfm.
Else, I guess you may have to output the cfform in the Display function. I may be wrong, but I don't think you can simply place a cfform inside a cfc and expect it to show up on the browser. CFC is not for browser rendering, it should be in a cfm.
Regarding the comment, "This is because I need to export to excel on click of a button for which I'd need cfform. Can you suggest some alternate to this functionality?", I will give you some things to think about.
First, you talk about recordsets containing several thousand rows and you have code where you attempt to display that in a browser. Quite simply, that will take an enormous amount of time to render. So, it's a bad idea.
Next, your code has functions for various queries but just one display function. Unless that's a cleverly written function that figures out the column names, it will only work if all the queries have the same columns. If that's the case, maybe you only need one query and some variables.
My suggestion is to start with a form where the user sends the appropriate information which determines what sql gets written. This form should also include a way for them to choose whether they want the results rendered in excel or html. If they choose html, do something to ensure that the data being returned does not overwhelm their browser.
By the way, re-useable code for displaying query results is a good idea. However, a custom tag might be a more conventional way to do it.

Cfdocument being served by server despite being in cfsavecontent

It seems that when I use the <cfsavecontent> tag, the output of that is being served by the server (without the variable being outputted), which, to me, kind of defeats the purpose of <cfsavecontent>.
If this is important: my application uses ColdSpring, ModelGlue and Transfer ORM.
Here's my example code in a function:
<cfsavecontent variable="testvar">
<cfinclude template="test.cfm" />
</cfsavecontent>
<cfreturn testvar>
And the template:
<cfdocument format="PDF" pagetype="A4" orientation="portrait" unit="cm">
<cfoutput>
<!--- PDF content here --->
</cfoutput>
</cfdocument>
The PDF content is being parsed by my browser (Google Chrome), while the view hasn't even been loaded in. How can I best prevent this from happening?
Just to clarify: I am not outputting the #testvar# variable yet in this code, though it seems it loads the template in the browser anyways.
To achieve what you're trying to do, should you not simply be using the name attribute of <cfdocument> to put the PDF data into a variable, instead of trying to <cfsavecontent> it?
Disclosure: I've never used <cfdocument> for anything other than proof-of-concept code and testing, but that's what I'm inferring from the docs.
As I also needed to make multiple PDF documents merge, I ended up doing the following. Many thanks to Adam Cameron for providing the solution to my initial issue.
In the template file, I use the <cfdocument> tag with the name attribute to save the PDF in a variable (thanks to Adam Cameron for this)
Then, I store all the PDF documents in an array in their binary format
In my view, I merge the PDF documents together by using <cfpdf>'s merge action, and using a cfloop, to loop over the array, inside it.
Finally, I display the content by using <cfcontent> and using the variable attribute with toBinary(myPdf)
This got me to where I am.
cfinclude will process the test.cfm page, and the way you configured cfdocument will cause "opening" of pdf document in your browser.
You can prevent openning of this file by saving file on the disc:
<cfdocument format="PDF" pagetype="A4" orientation="portrait" unit="cm" filename ="test.pdf" overwrite ="yes">
But this will not prevent execution of cfinclude in the cfcontent tag, it will just prevent opening in the browser.
You can observe cfinclude as request to the server, it will always be executed.
The solution would be to invoke request on test.cfm file which contains cfdocument in the moment that you actually want to generate pdf.
Example: Use javascript on client to invoke report service which will generate and pop out the screen with pdf report.
Hope this helps.

ColdFusion include

I'm currently learning ColdFusion. I have a background in PHP and I am a bit confused by this.
I have a select menu and I want the options to be saved in a different file. (For example options.cfm) When I call the file I want to include the options inside the select menu.
Now I realize I could probably do it with something like this:
<select>
<cfinclude template="options.cfm">
</select>
Although what I really want to do is a bit more complicated. I want to have the cfinclude saved inside a variable. I realize this won't work but it is basically what I want to accomplish:
<cfset options=<cfinclude template="options.cfm">>
Is there anyway to do that? Or at least a better way to accomplish what I am doing.
Take a look at the cfsavecontent tag, It allows you to capture what would otherwise have been output to the response :
<cfsavecontent variable="options">
<cfinclude template="options.cfm">
</cfsavecontent>
UPDATE: Instead of using cfsavecontent every time you need those options saved to a variable, you could instead do it once inside of the options.cfm file. Then, anytime you include the file, it will create the variable.
<!--- Inside options.cfm --->
<cfsavecontent variable="options">
<option value="val1">Value 1</option>
<option value="val2">Value 2</option>
<option value="val3">Value 3</option>
</cfsavecontent>
Then where ever you needed that variable to exist you would simply need to cfinclude that file.
<cfinclude template="options.cfm">
i know this is a bit late but one issue i see is if this is site wide or just per client.
if site wide then great but if it is different on each client it could cause some issues.
my solution as i don't use cookies or sessions is to create a temp table and write the variables to it. each page that loads and needs that data queries and/or writes to the table.
a client id variable is created when the client visits the site and the table is named it.
just a thought.

How do I force evaluation of a cfif stored in a string?

I am trying to store coldfusion code in a database to be used for the subject of a cfmail. The code stored is as follows:
"RE: <cfif myData.general.legalName NEQ """"> {{dotlegalname}}<cfelse>{{docketLegalName}}</cfif>,
DOT## {{dot}}, Docket ##(s) {{docketString}}"
When I retrieve string from the database, I use cfsavecontent to attempt to evaluate it.
<cfsavecontent variable="subject">
<cfoutput>#myData.email.subject#</cfoutput>
</cfsavecontent>
I also tried
<cfsavecontent variable="subject">
<cfoutput>#evaluate(myData.email.subject)#</cfoutput>
</cfsavecontent>
And then I replace all the {{ }} with the appropriate values.
However, the subject of the email is stubbornly refusing to contain an evaluated cfif, and is instead showing the cfif as if it were a string.
Any ideas?
The only way to dynamically evaluate code that you are creating at runtime is via writing it out to a file, and then executing it.
The easiest way would be to write it a .cfm page in the Virtual File System (probably name the file after a UUID, so it's unique), and then it where you need to run the contents.
I wouldn't normally advocate generating code at runtime like this, but it can be the most elegant solution in some cases.
As an alternative, instead of storing the CFML code in the database, you have a set of CFML email template files that get stored in a directory on your server, and in your database you simply record which template needs to be included either via cfinclude or cfmodule.
You can't dynamically evaluate CFML stored in a database without first writing it to file and then using <cfinclude> to include it.
Further to Mark's answer here is some psuedo code:
<cfset fileName = createUUID() & ".cfm">
<cfset fileWrite( fileName, [CODE_FROM_DB]>
<cfinclude template="#fileName#">
<cfset fileDelete( fileName )>
I have used code like this before with no problems. Anything in the Virtual File System flies as it is all run in RAM. For best practice do remember to delete the files created ;)
If you absolutely have to do this, look at the evaluate() function. This, essentially, fires up a new CF thread, compiles the string passed to it, runs it, and returns the result.
If at all possible, I would try to find a way to move your logic to the actual file being run, not the string from the database. I assume you are pulling the data based on some string you've already built, so you might consider appending something to it, so you are looking up subjectDotLegal and subjectDocketLegal or something similar.
Remember, evaluate() is slow, ugly, and can be dangerous (it will run anything passed to it!). If there's a way around it, I suggest you use it.
why not just use something like mustache?
http://mustache.github.com/
https://github.com/pmcelhaney/Mustache.cfc
it has the ability to not only do some of the logic that you want in your script dynamically. i really would suggest you check out the project and maybe even improve and contribute on it.
OH and just for the chance to be on a soapbox: I've been emailing Adobe for years saying that we need the ability to dynamically parse and render CFML. Sadly my cries have only gotten ignored. maybe if more people complained that this feature needs to be added, it would get the attention it deserves.
To give an example: Assume code.txt is a text file that contains the following (just to facilitate simulating CFML stored in a db): <cfoutput>#now()#</cfoutput>
The following code would work:
<cfset q = queryNew("code") />
<cfset queryAddRow(q,1) />
<cfset querySetCell(q, "code", fileRead(expandPath('code.txt')), 1) />
<cfdump var="#q#">
<cfset newCodeFile = expandPath('dynamic.cfm') />
<cfset fileWrite(newCodeFile, q.code[1]) />
<cfinclude template="dynamic.cfm" />
In OpenBlueDragon there is the render function, which can do this.
You can mimic this function in Railo by creating a custom built-in function that saves the file into RAM then cfincludes it, using the following code:
<cffunction name="render" output="Yes" returntype="string"><!---
---><cfargument name="Code" required="Yes" type="string"><!---
---><cfset local.mapping = {'/render_ram_resource':'ram://'}><!---
---><cfapplication action="update" mappings="#local.mapping#"><!---
---><cfset local.fileName = "/render_ram_resource/_render_" &
createUUID() & ".cfm"><!---
---><cffile action="WRITE" file="#fileName#"
output="#arguments.Code#"><!---
---><cfinclude template="#fileName#"><!---
---><cffile action="DELETE" file="#fileName#"><!---
---></cffunction>
(This looks unusual because it needs to allow output, but prevent extra whitespace, hence why all the comments. Unfortunately SO's syntax highlighting seems to be confused by them.)
If you need an ACF-compatible solution, you'll need to use the regular filesystem and a pre-created mapping. (Well, in ACF9 and above you can use the RAM virtual filesystem, but afaik you can't create mappings on the fly like this.)
There's a better way, namely using in memory files. This way you don't have any I/O on the disk and therefore much faster:
For tags that take logical path, define mapping in Administrator. Execute in-memory CFM pages using the cfinclude tag:
Create a mapping for ram:/// so that it can be used in the tags. In this example, /inmemory is the mapping that points to ram:///.
For tags that take absolute path, specify the syntax as provided in the following example:
You can also delete the file from the ram usinf cffile and action delete.
Here's how I stored my header and footers for all pages in a record. This code can go at the top of each page. But I have it in the APPLICATION.cfm and it seems to be working great.
The key here is not use #pound# signs on your expressions. User [square braces]. The code will pick them and evaluate them and return the result back to the template.
It will substitute the number 0 if it can not evaluate an expression as a means of error handling.
<CFSET FooterID=1234> <!-- ID of the record you want to use -->
<CFQUERY NAME="StoredHeader" Datasource="DS1">
Select Body from templates where id=#FooterID#
</CFQUERY>
<CFSET Parse=StoredHeader.Body>
<CFLOOP CONDITION="FindNoCase('[',Parse,1) GT 0">
<CFSET STB=FindNoCase('[',Parse,1)>
<CFSET ENB=FindNoCase(']',Parse,1)>
<CFIF ENB-STB GT 0>
<CFSET BracketExp=Mid(Parse,STB+1,ENB-1-STB)>
<CFTRY>
<CFSET BracketValue=Evaluate(BracketExp)>
<CFSET Parse=ReplaceNoCase(Parse,'['&BracketExp&']',Evaluate(#BracketExp#))>
<cfcatch type="any">
<div>'Using ZERO 0 for missing <cfoutput>#BracketExp#' </cfoutput> </div>
<CFSET Parse=ReplaceNoCase(Parse,'['&BracketExp&']','0')>
</cfcatch>
</CFTRY>
</CFIF>
</CFLOOP>
<CFSET Footer=Parse>
<cfoutput>FOOTER</cfoutput>
I would try the built-in QuoteName function.