Adding appointments to your calendar through email notifications - coldfusion

I need an example for adding events to my calendars through emails. For example when I send an email to my clients regarding their appointment, i want them to have an option in the email that will allow them to add the event on their calendar by just clicking on a button or something.
Is there anything out there that someone can direct me to it?
I need this mainly to use it with coldfusion 9.
Thanks!

I'd recommend the iCalUs UDF from http://www.cflib.org/udf/icalus
WebDH provides a great example here.
Here's an example using CF9 that I quickly put together but haven't tested yet.
<cfscript>
eventStr = {};
eventStr.organizerName = "John Doe"; //Organizer Name
eventStr.organizerEmail = "john.doe#email.com"; //Organizer Email
eventStr.startTime = ParseDateTime("12/30/2011 11:00"); //format: m/d/yyyy HH:mm OR h:mm TT -- this is Eastern time
eventStr.subject = "Demo Example";
eventStr.location = "StackOverflow.com";
eventStr.description = "Example iCalendar using CF9";
// Display in browser
//pc = getpagecontext().getresponse();
//pc.getresponse().setcontenttype('text/calendar');
//pc.setHeader("Content-Disposition","inline;filename=newAppointment.ics");
//writeOutput(iCalUS(eventStr));
//Email
m = new mail();
m.setSubject( "Event" );
m.setTo( "user#email.com" );
m.setFrom( "me#email.com" );
m.setServer( "localhost" );
//m.addParam( file="#ACCOUNT_TXT_FILE#" );
m.addPart( type="text", charset="utf-8", wraptext="72", body="Attached is a calendar event..." );
m.addPart( type="text/calendar" body="#iCalUS(eventStr)#");
m.send();
</cfscript>
Here's another example reference that shows how to email the calendar event.

Here is a very basic implementation of the ICS format. This is designed to be accessed through a browser, but it'd be fairly academic to change to create a text file with ICS extension and send it via e-mail.
<cfheader name="Content-Disposition" value="attachment; filename=event.ics" />
<cfcontent reset="true" type="text/calendar" />
<cfscript>
// handle all-day events
if (NOT isDate(starttime) OR NOT isDate(endtime)) {
dtstart=';VALUE=DATE:#dateFormat(eventdate,"yyyymmdd")#';
dtend=';VALUE=DATE:#dateFormat(dateAdd("d",1,eventdate),"yyyymmdd")#';
} else {
dtstart=';TZID="Eastern Standard Time":#dateFormat(eventdate,"yyyymmdd")#T#timeFormat(starttime,"HHmmss")#';
dtend=';TZID="Eastern Standard Time":#dateFormat(eventdate,"yyyymmdd")#T#timeFormat(endtime,"HHmmss")#';
}
</cfscript>
<cfoutput>
BEGIN:VCALENDAR
PRODID:-//Company//Source//EN
VERSION:2.0
METHOD:PUBLISH
BEGIN:VTIMEZONE
TZID:Eastern Standard Time
BEGIN:STANDARD
DTSTART:16011104T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:16010311T020000
RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
END:DAYLIGHT
END:VTIMEZONE
BEGIN:VEVENT
CLASS:PUBLIC
CREATED:#dateFormat(dateAdded,"yyyymmdd")#T#timeFormat(dateAdded,"HHmmss")#Z
DESCRIPTION:#desc#
DTEND#dtend#
DTSTAMP:#dateFormat(dateAdded,"yyyymmdd")#T#timeFormat(dateAdded,"HHmmss")#Z
DTSTART#dtstart#
LAST-MODIFIED:#dateFormat(dateApproved,"yyyymmdd")#T#timeFormat(dateApproved,"HHmmss")#Z
LOCATION:#location#
PRIORITY:5
SEQUENCE:0
SUMMARY;LANGUAGE=en-us:#title#
TRANSP:OPAQUE
UID:#dateFormat(now(),"yyyymmdd")#T#timeFormat(now(),"HHmmss")#Z##uniqueID#
BEGIN:VALARM
TRIGGER:-PT15M
ACTION:DISPLAY
DESCRIPTION:Reminder
END:VALARM
END:VEVENT
END:VCALENDAR
</cfoutput>

Related

ColdFusion 2021 - How to handle SAML/SSO with multiple applications on same server

We have a server with about a dozen small applications each in their own subfolder of the server (//URL/app1, //URL/app2, etc).
I've got the basic SSO authentication round trip working. I set up my account with my IDP and have the response set to go to a common landing page (ACS URL). Since the landing page is currently shared with all the apps, it is in a separate folder distinct from the apps (//URL/sso/acsLandingPage.cfm)
I'm now working on my first app. I can detect the user is not logged in so I do a initSAMLAuthRequest(idp, sp, relayState: "CALLING_PAGE_URL") and that goes out, authenticates, then returns to the landing page.
But how do I redirect back to my target application and tell it the user is authenticated?
If I just do a <cflocation url="CALLING_PAGE_URL" /> the original app doesn't know about the SAML request.
Is there a function that I can call in the original app that will tell if the current browser/user has an open session?
Do I need to set up separate SP for each application so rather than one common landing page each app would have its own landing page so it can set session variables to pass back to the main application? (the IDP treats our apps as "one server", I can get separate keys if that is the best way to deal with this).
My current working idea for the ACS landing page is to parse the relayState URL to find out which application started the init request and then do something like this:
ACSLandingPage.cfm
<cfset response = processSAMLResponse(idp, sp) />
<cfif find(response.relaystate, 'app1')>
<cfapplication name="app1" sessionmanagement="true" />
<cfelseif find(response.relaystate, 'app2')>
<cfapplication name="app2" sessionmanagement="true" />
</cfif>
<cfset session.authenticated_username = response.nameid />
<cflocation url="#response.relaystate#" />
Not terribly ideal, but I think it might work.
I was hoping I was just overlooking something simple and really appreciate any help I can get.
Edit:
My above idea of using <cfapplication in the ACSLandingPage is not working because the <cfapplication keeps trying to assign it to a new session so that when I redirect back to the original app, it thinks it is in a different session so does not have access to the original session.authenticated-username.
Ok, here's how I ended up solving this problem. Probably not the "correct" solution, but it works for me.
The full code solution would be way too long and complicated and rely on too many local calls that would not make sense, so I'm trying to get this down to just some code snippets that will make sense to show how my solution works.
In each application, the Application.cfc looks a bit like this. Each app has a name set to the path of the Application.cfc. We do this because we often will run "training instances" of the codebase on the same server that point to an alternate DB schema so users can play around without corrupting production data.
component {
this.name = hash(getCurrentTemplatePath());
...
In the application's onRequestStart function it has something a bit like this:
cfparam(session.is_authenticated, false);
cfparam(session.auth_username, '');
cfparam(application._auth_struct, {}); // will be important later
// part 1
// there will be code in this block later in the description
// part 2
if (NOT session.is_authenticated OR session.auth_username EQ '') {
var returnURL = '#getPageContext().getRequest().getScheme()#://#cgi.server_name#/#cgi.http_url#'; // points back to this calling page
// start the call
InitSAMLAuthRequest({
'idp' : 'IDP_NAME',
'sp' : 'SP_NAME',
'relayState': returnURL
});
}
// log them in
if (session.is_authenticated AND session.auth_username NEQ '' AND NOT isUserLoggedIn()) {
... do cflogin stuff here ...
}
// throw problems if we are not logged in by this point
if (NOT isUserLoggedIn()) {
... if we don't have a logged in user by this point do error handling and redirect them somewhere safe ...
}
This initiates the SAML connection to our ID Provider. The provider does its stuff and returns the user to the file 'https://myserver/sso/ProcessSAMLResponse.cfm'.
processSAMLResponse uses the returnURL set in relayState to determine which application initiated the request so it can get a path to the app's Application.cfc.
<cfset response = ProcessSAMLResponse(idpname:"IDP_NAME", spname:"SP_NAME") />
<cfset returnURL = response.RELAYSTATE />
<cfif findNoCase("/app1", returnURL)>
<cfset appPath = "PHYSICAL_PATH_TO_APP1s_APPLICATION.CFC" />
<cfelseif findNoCase("/app2", returnURL)>
<cfset appPath = "PHYSICAL_PATH_TO_APP2s_APPLICATION.CFC" />
<cfelseif findNoCase("/app3", returnURL)>
<cfset appPath = "PHYSICAL_PATH_TO_APP3s_APPLICATION.CFC" />
...
</cfif>
<!--- initiate application --->
<cfapplication name="#hash(appPath)#" sessionmanagement="true"></cfapplication>
<!--- create a token (little more than a random string and a bit prettier than a UUID) --->
<cfset auth_token = hash(response.NAMEID & dateTimeFormat(now(), 'YYYYmmddHHnnssL'))/>
<cfset application._auth_struct[auth_token] = {
"nameid": lcase(response.NAMEID),
"expires": dateAdd('n', 5, now())
} />
<!--- append token (can also be done with a ?: if you are inclined) --->
<cfif NOT find("?", returnURL)>
<cfset returnURL &= "?auth_token=" & encodeForURL(auth_token) />
<cfelse>
<cfset returnURL &= "&auth_token=" & encodeForURL(auth_token) />
</cfif>
<!--- return to the calling page --->
<cflocation url="#returnURL#" addToken="No"/>
This throws it back to the application. So we go back into the application's onRequestStart to fill in that part 1 block from above:
cfparam(session.is_authenticated, false);
cfparam(session.auth_username, '');
// part 1
// look for an auth token
if (NOT session.is_authenticated AND session.auth_username EQ '' AND structKeyExists(URL, 'auth_token')) {
var auth_token = URL.auth_token;
// see if it exists in our auth struct (and has all fields)
if ( structKeyExists(application, "_auth_struct")
AND structKeyExists(application._auth_struct, auth_token)
AND isStruct(application._auth_struct[auth_token])
AND structKeyExists(application._auth_struct[auth_token], 'nameid')
AND structKeyExists(application._auth_struct[auth_token], 'expires')) {
// only load if not expired
if (application._auth_struct[auth_token].expires GT now()) {
session.is_authenticated = true;
session.auth_username = application._auth_struct[auth_token].nameid;
}
// remove token from struct to prevent replays
structDelete(application._auth_struct, auth_token);
} // token in auth struct?
// remove expired tokens
application._auth_struct = structFilter(application._auth_struct, function(key, value) {
return value.expires GT now();
});
} // auth_token?
// part 2
// .... from earlier
So that's how I solved the problem of multiple apps trying to use a single IDP/SP combination.
Important caveats:
This is all done on an intranet server, so my security is much more lax than it would be on a public facing server. (in particular, using an application variable to store the auth-tokens could be vulnerable to a massive DDOS type attack that would flood new sessions and fill available memory).
A subset of 1 - these apps get a few hundred users a day across all apps, if you have a site that gets thousands of hits a day, storing the tokens in application like I do may not be memory efficient enough for you.
My IDP is very constrained. It would be much nicer if I could just create distinct SP settings for each app and have the return calls go directly back to the calling app.
I skipped a few checks and error handling to keep the sample simple. You should do lots more tests on the values, especially to make sure the nameID is a valid user before the actual cflogin call.
Before calling initSAMLAuthRequest, you may want to add a session counter to prevent an infinite loop of authentication calls if something goes wrong (learned that the hard way).

queryEach or .each() not Working at CF 11, Why?

I was seaching in google better form to interate over "query" in coldfusion, since im new in the company im working, and im trying to get more from CF
Here my attempts:
My models:
<cffunction hint="Foo" name="Foo" access="public" returntype="query">
<!--- Argumentos --->
<cfargument hint="Something" name="ArgComBus" type="string" required="no" default="">
<cfargument hint="Other thing" name="ArgPar" type="string" required="no" default="">
<cfscript>
queryService = new Query();
queryService.setSql("
SELECT
column1,
column2,
FROM
tab_bar
WHERE
1=1
#arguments.ArgComBus#
");
queryService.setDataSource(session.Dsn);
if(Len(Trim(arguments.ArgPar))){
Evaluate(arguments.ArgPar);
}
queryResult = queryService.execute();
qBus = queryResult.getResult();
</cfscript>
<cfreturn qBus>
</cffunction>
My script
<cfscript>
arrFoo = arrayNew(1);
qFoo = this.Foo(
ArgComBus = " AND column1 = #variables.bar# ");
// First Attempt - The each method was not found.
qFoo.each(function (foo) {
arrFoo.append(foo);
});
// Second Attempt - Variable QUERYEACH is undefined.
queryEach(qFoo, function (foo) {
arrFoo.append(foo);
});
writeDump(arrFoo);
</cfscript>
My Server Dump
InstallKit Native Windows
appserver Tomcat
productlevel Developer
productname ColdFusion Server
productversion 11,0,05,293506
rootdir C:\CFusion11\cfusion
I even used getMetaData() on my query variable qFoo and that return that is array... so when i tried use something like that (trying to convert array in query?)
cfQuery = createObject("java", "coldfusion.sql.QueryTable").init(qFoo);
.each() and queryEach() same answer... i even tried use arrayEach() but return the object is coldfusion.sql.QueryTable and not array
You are running ColdFusion 11.
The queryEach() function was not added until ColdFusion 2016:
reference 1 (from cfdocs)
reference 2 (from Adobe docs)
Originally I had posted that the each() member function was not available in Adobe ColdFusion 11. Aquitaine pointed out in the comments that it actually is. I incorrectly referenced the Each() function for Lucee that works with collections. The Each() function related to this question is actually the script version of the ArrayEach() tag function. Which is available in ColdFusion 11 (it was actually added in ColdFusion 10). Sorry for the confusion.
The documentation may be wrong. I could not get the function to work as Each() except under ColdFusion 2018. For ColdFusion 11 I could only get it to work as ArrayEach().
reference 3 (from cfdocs)
reference 4 (from Adobe docs)
Here are a couple of examples on how to loop over a query in ColdFusion 11 (borrowed from cfdocs):
// Define our query
platform = ["Adobe ColdFusion", "Railo", "Lucee"];
myQuery = queryNew(" ");
queryAddColumn(myQuery, "platform", "CF_SQL_VARCHAR", platform);
// By row index
for (i = 1; i <= myQuery.recordCount; i++) {
writeOutput("<li>#myQuery["platform"][i]#</li>");
}
// By query
for (row in myQuery) {
writeOutput("<li>#row.platform#</li>");
}
// By arrayeach
writeOutput("<h3>By arrayeach:</h3>");
function printArray(vendor, index)
{
writeOutput("<li>#vendor#</li>");
}
arrayEach(platform,printArray);
I created a gist for you on TryCF.com so you can see this code in action and play around with it if you like. Just click here to run the code.

Mailgun & CfMailGun component. How to implement it

I'm trying to use the component [https://github.com/DominicWatson/cfmailgun][1] to send email (using SendMessage ()) through mailgun.
I am not able to use it. I have read the documentation at this address: http://dominicwatson.github.io/cfmailgun/
But I can not implement it. The author has not included a complete example.
He added only this:
mailGunClient = new MailGunClient( apiKey = myPrivateApiKey );
Can anyone help me with a more complete example?
Thank you.
Edit:
After some tests, I've tried with:
<cfscript>
myPrivateApiKey = "key-123456789";
mailGunClient = new MailGunClient( apiKey = myPrivateApiKey );
mailGunClient.sendMessage(domain = 'domain.com', from='info#domain.com', to='to#gmail.com', subject='Test', text='hello world', html='<b>hello world</b>');
</cfscript>
But I get the error:
MailGun request failure. ['from' parameter is missing]
Edit 2
I solved it (thanks to the help of the author of the script). It was a problem in the component.
Anywhere you see <cfhttpparam ... name="#key#" ..., changing that to <cfhttpparam ... name="#LCase( key )#" ...

CFSpreadsheet loses SpreadSheetAddFreezePane when updating

I am trying to add spreadsheets to a workbook with a freeze pane. The freeze pane will work if the action is write but not if I add another sheet using update.
<cfscript>
theSheet = SpreadsheetNew(SheetName);
SpreadsheetAddRows(theSheet,TheQuery);
format2=StructNew();
format2.font="Arial";
format2.fontsize="10";
format2.color="Black;";
format2.italic="False";
format2.bold="true";
format2.alignment="left";
format2.textwrap="true";
format2.fgcolor="tan";
format2.bottomborder="thick";
format2.bottombordercolor="Black";
format2.leftborder="thick";
format2.leftbordercolor="Black";
format2.rightborder="thick";
format2.rightbordercolor="Black";
SpreadsheetFormatRows(theSheet,format2,"1-2");
SpreadsheetFormatColumns(theSheet,format2,"1-3");
SpreadSheetAddFreezePane(theSheet,3,1);
</cfscript>
<cfspreadsheet filename="#theFile#" name="theSheet" sheet="#SheetCount#" action="update" sheetname="#SheetName#">
Sounds like it could be a bug. Unless there is a specific reason for using action=update, I would just use action=write instead. Read in the workbook. Add a new sheet. Make it active. Then write it back to disk.
<cfscript>
theSheet = SpreadSheetRead( theFile );
SpreadsheetCreateSheet( theSheet, sheetName );
SpreadSheetSetActiveSheet( theSheet, sheetName );
// ... code to add data
SpreadSheetAddFreezePane( theSheet, 3, 1 );
SpreadSheetWrite( theSheet, theFile, true );
</cfscript>
As Adam mentioned in the comments, you may want to file a bug report (and post the bug number here so others can vote on it).

ColdFusion: Trying to query database in CFScript

My boss wants me to use cfscript instead of tags for database interaction. Does anybody know of any good tutorials? I bought the Adobe ColdFusion application development book, vol 2. But it does not have much on scripting. I did google and found this site, but it did not explain much.
Does any body know of any good tutorials on accessing the data base in CFScript?
Basically I have to convert the following to using CFScript:
<cfquery name="drafts" datasource="ICEchat">
SELECT * from Messages where IsTemp=1 and LinkA=#FORM.LinkA# and LinkB=#FORM.LinkA#
</cfquery>
<cfif drafts.recordcount GT '0'>
<cfquery name="Attachments" datasource="ICEchat">
SELECT * FROM Attachments where id=2
</cfquery>
{ Message:"<cfoutput query="drafts">#Message#</cfoutput>", Attachments:[<cfoutput query="attachments">
"#url#"<cfif attachments.currentRow LT attachments.recordcount>,</cfif>
</cfoutput>]}
<cfelse>
<cfquery name="addrecord" datasource="ICEchat">
INSERT INTO Messages
VALUES(1,1,' ',1)
</cfquery>
{ Message:"NA", Attachments:[]}
</cfif>
From the 4th link on google for "cfscript query tutorial":
<CFSCRIPT>
myQry = new Query(); // new query object
myQry.setSQL("select bookid, title, genre from app.books where bookid = :bookid"); //set query
myQry.addParam(name="bookid",value="5",CFSQLTYPE="CF_SQL_INTEGER"); // add query param
qryRes = myQry.execute(); // execute query
writedump(qryRes.getResult().recordcount, true); // get resultcount
writedump(qryRes.getResult(), false); // dump result
writeoutput('<BR>');
</CFSCRIPT>
That ought to tell you everything you need to know.
Also, you really should not be creating JSON manually, no matter how simple it is. Use serializeJson().
Didn't test this, but this should do it.
<cfscript>
local.drafts = new Query();
local.drafts.setDatasource("ICEchat");
local.drafts.addParam(name="linkA", value="#form.linkA#", cfsqltype="CF_SQL_VARCHAR");
local.drafts.addParam(name="linkB", value="#form.linkB#", cfsqltype="CF_SQL_VARCHAR");
local.drafts.setSQL("SELECT * from Messages where IsTemp=1 and LinkA = :linkA and LinkB = :linkA");
local.drafts.execute().getResult();
if (local.drafts.recordcount GT 0) {
local.attachments = new Query();
local.attachments.setDatasource("ICEchat");
local.attachments.setSQL("SELECT * FROM Attachments where id=2");
local.attachments.execute().getResult();
WriteOutput("{ Message: ");
for (i=1; i LTE local.drafts.recordcount; i=i+1) {
WriteOutput(local.drafts.message[i]);
}
WriteOutput(", Attachments: ");
for (i=1; i LTE local.attachments.recordcount; i=i+1) {
WriteOutput(local.drafts.url[i]);
if (i LT local.attachments.recordcount) {
WriteOutput(", ");
}
}
WriteOutput("}");
} else {
local.q = new Query();
local.q.setDatasource("ICEchat");
local.q.setSQL("INSERT INTO Messages VALUES(1,1,' ',1)");
local.q.execute();
WriteOutput("{ Message:"NA", Attachments:[]}");
}
</cfscript>
I have been searching for a solution for the same error. I get not defined errors or construct errors. Have been chatting with Ray Camden for the past day, but everything he has suggested is not doing what I need either. I have been working on a website conversion from the standard CF tags to cfscript.
Ray suggested that the .execute(); is what pulls the recordcount and suggested this is all was needed: x=queryExecute(); would fetch the recordcount, with (x) being the query. He suggested not to use getPrefix(); but I read that the .getPrefix(); is what pulls the recordcount. He is experienced I am sure you know Ray Camden, but I keep getting the same error no matter what I try doing inside of my code.