I have been trying to submit a my own form to WFFM. The form I created is identical to the one created with WFFM, that way all fields map correctly.
I began following the following steps: https://jermdavis.wordpress.com/2015/05/18/programmatic-wffm-submissions/
I had to make minor changes to the code in order to get the SubmitActionManager to work
The members of Sitecore.Form.Core.Submit.SubmitActionManager class
have been moved to the IActionExecutor interface.To obtain the
instance of this interface use the
(IActionExecutor)Factory.CreateObject ("wffm/wffmActionExecutor",
false) call.
Below is the code I have so far:
public void SubmitData(ContactUsFormModel data)
{
var results = new List<ControlResult>();
results.Add(makeControlResult(Models.Constants._cufFirstNameID, "First Name", data.FirstName));
results.Add(makeControlResult(Models.Constants._cufLastNameID, "Last Name", data.LastName));
results.Add(makeControlResult(Models.Constants._cufEmailID, "Email", data.Email));
results.Add(makeControlResult(Models.Constants._cufCompanyID, "Company", data.Company));
results.Add(makeControlResult(Models.Constants._cufSubjectID, "Subject", data.Subject));
results.Add(makeControlResult(Models.Constants._cufMessageID, "Message", data.Message));
var formItem = Sitecore.Context.Database.GetItem(Models.Constants._contactUsFormID);
var simpleForm = new SitecoreSimpleForm(formItem);
var saveActionXml = simpleForm.FormItem.SaveActions;
var actionList = Sitecore.Form.Core.ContentEditor.Data.ListDefinition.Parse(saveActionXml);
var actionDefinitions = new List<ActionDefinition>();
actionDefinitions.AddRange(actionList.Groups.SelectMany(x => x.ListItems).Select(li => new ActionDefinition(li.ItemID, li.Parameters) { UniqueKey = li.Unicid }));
var SubmitActionManager = (IActionExecutor)Factory.CreateObject("wffm/wffmActionExecutor", false);
Sitecore.Form.Core.WffmActionEvent sessionID = new Sitecore.Form.Core.WffmActionEvent();// SessionIDGuid
var result = SubmitActionManager.ExecuteSaving(ID.Parse(Models.Constants._contactUsFormID), results.ToArray(), actionDefinitions.ToArray(), true, ID.Parse( sessionID.SessionIDGuid ));
}
private ControlResult makeControlResult(string fieldID, string fieldName, string fieldValue)
{
return new ControlResult(fieldName, fieldValue, string.Empty)
{
FieldID = fieldID,
FieldName = fieldName,
Value = fieldValue,
Parameters = string.Empty
};
}
I wasnt sure where to get Sitecore.Form.Core.Analytics.AnalyticsTracker.SessionId from to use it inside ExecuteSaving, so I used WffmActionEvent.
Also the guide I followed uses Execute, which is now deprecated, so I had to go with ExecureSaving (my best guess).
This however doesn't seem to be posting the submitted data into the databse. I am unable to see any of my submissions inside WFFM Form Reports or inside mongoDB.
The logs however state that the form is being saved to the database, not sure what the other warnings mean.
24688 17:20:39 WARN [WFFM] Tracker.Current is not initialized
24688 17:20:39 INFO AUDIT (sitecore\admin): [WFFM] Form {978DBF4C-0F56-45A8-A9AC-52EF8D995DDF} is saving to db
24688 17:20:39 WARN [WFFM] Tracker.Current.Contact is not initialized
24688 17:20:39 WARN [WFFM] Tracker.Current.Interaction is not initialized
24688 17:20:39 WARN [WFFM] CurrentSession is not initialized
As you are using Sitecore 8 the form submission is stored in MongoDB. Sitecore's implementation of MongoDB, xDB mostly relies on tracking users, calling them Contacts.
Most data stored in xDB is linked to a Contact via a ContactId. The error messages you are finding in the log are stating that tracking is currently not enabled, therefore no Contact is present and there is no Interaction between the site and user.
Therefore you need to start Sitecore.Tracker I recommend using the following code
if (!Tracker.IsActive)
Tracker.StartTracking();
if (!Tracker.IsActive || Tracker.Current.Contact == null)
{
// handle no tracker and contact
}
Now that you have tracking working you need to use the correct ID for your sessionID variable.
The blog you are following is based on Sitecore 7, in Sitecore 8 Sitecore.Form.Core.Analytics.AnalyticsTracker.SessionId is removed. I decompiled the Sitecore 7 code and Sitecore.Form.Core.Analytics.AnalyticsTracker.SessionId ultimately uses Tracker.CurrentVisit.VisitId.
However this namespace has changed in Sitecore 8, Visits are now called interactions so instead of your variable SessionID you will want to use
Tracker.Current.Interaction.InteractionId;
That should resolve the issue you are currently having but you may need a bit more development to finish it off.
Related
The IBM Watson iOS SDK using the Alchemy News service on Bluemix returns a string result which requires parsing to pull out the fields like url and cleaned title. ref: https://github.com/watson-developer-cloud/swift-sdk
I pull the string into an array and parse it in swift3 using some string methods but this is pretty ordinary and can produce unpredictable results
Is there a more elegant approach where I can access specific fields, like the url and cleaned title which I am passing to a UITableViewCell to select and segue to the url link.
sample code:
let alchemyDataNews = AlchemyDataNews(apiKey: apiKey)
let failure = { (error: Error) in print(error) }
let start = "now-14d" // 7 day ago
let end = "now" // today
let query = ["count": "15",
"dedup": "true",
"q.enriched.url.title": "[IBM]",
"return": "enriched.url.url,enriched.url.title" "enriched.url.title,enriched.url.entities.entity.text,enriched.url.entities.entity.type"]
Also I have noticed the search string [IBM] has a prefix of 0, i.e. 0[IBM] and have also seen an "A". What do these prefixes mean and where are they documented
Here is one way you can access the fields from a returned payload.
alchemyDataNews.getNews(from: "now-4d", to: "now", query: queryDict, failure: failWithError) { news in
for doc in (news.result?.docs)! {
var cleanedTitle = doc.source?.enriched?.url?.cleanedTitle
var author = doc.source?.enriched?.url?.author
var title = doc.source?.enriched?.url?.title
}}
Also, here is a nice API reference link for alchemy data which contains all of the request parameters and filters.
https://www.ibm.com/watson/developercloud/alchemydata-news/api/v1/
I've been trying to update our API call to the CIM interface for Authorize.net to hide the Billing Address fields on the hosted profile page.
The documentation states that when call the token creation function, passing in a setting "hostedProfileBillingAddressOptions" with a value of "showNone" will hide the billing address part of the form, however when I pass in this setting I am still getting the billing address showing.
I've verified that I'm passing the setting correctly (added the same way as the "hostedProfileIFrameCommunicatorUrl" and "hostedProfilePageBorderVisible" settings) and if I pass an invalid value for the "hostedProfileBillingAddressOptions" option, the Token creation function will return an error
Is there something else that this option is dependent on, such as an account setting or another settings parameter?
For reference, I'm testing this in the Sandbox system and I'm using the dotNet SDK, my test code for calling the API function is as follows
Public Shared Function CreateHostFormToken(apiId As String, apiKey As String, branchId As Int64, nUser As Contact, iframeComURL As String) As String
Dim nCustProfile = GetCustomerProfile(apiId, apiKey, branchId, nUser)
Dim nHost = New AuthorizeNet.Api.Contracts.V1.getHostedProfilePageRequest()
nHost.customerProfileId = nCustProfile
' Set Auth
Dim nAuth = New Api.Contracts.V1.merchantAuthenticationType()
nAuth.ItemElementName = Api.Contracts.V1.ItemChoiceType.transactionKey
nAuth.name = apiId
nAuth.Item = apiKey
nHost.merchantAuthentication = nAuth
' Set Params
Dim settingList As New List(Of Api.Contracts.V1.settingType)
Dim nParam As New Api.Contracts.V1.settingType With {.settingName = "hostedProfileIFrameCommunicatorUrl",
.settingValue = iframeComURL}
settingList.Add(nParam)
nParam = New Api.Contracts.V1.settingType With {.settingName = "hostedProfilePageBorderVisible",
.settingValue = "false"}
settingList.Add(nParam)
nParam = New Api.Contracts.V1.settingType With {.settingName = "hostedProfileBillingAddressOptions",
.settingValue = "showNone"}
settingList.Add(nParam)
nHost.hostedProfileSettings = settingList.ToArray
Dim nX = New AuthorizeNet.Api.Controllers.getHostedProfilePageController(nHost)
Dim nRes = nX.ExecuteWithApiResponse(GetEnvironment())
Return nRes.token
End Function
I've looked through the SDK code as well, and I don't see anything there that would be preventing the setting from being passed through.
Has anyone come across this issue, or successfully set the card entry form to hide the billing address?
There turned out to be two parts to the solution to this problem:
In order to use the "hostedProfileBillingAddressOptions" option, you need to use a newer version of the capture page than I was using. I was using "https://secure2.authorize.net/profile/", while the new version is "https://secure2.authorize.net/customer/". Added bonus, the new URL provides a much nicer and modern looking form.
However, once this was working, I then had the problem that on entering the card, a validation message told me that "address and Zip code are required", despite not being visible. I did also make sure that I had the option "hostedProfileBillingAddressRequired" set to false (which is it's default value anyway)
The response from Authorize.net support is that in order to capture card without an address, the option "hostedProfileValidationMode" must be set to "testMode".
This is not mentioned in the documentation (at least as far as I could see), so may not be something that other people are aware of since it is a little counter-intuitive to use 'testMode' on a live environment.
It's not ideal since validating the card for a customer account will send a transaction email to the merchant, but it seems there is not another way around this just now.
In summary, to allow the customer to add a credit card to their profile without having to provide an address, you need to specify the following options:
Form URL for capture - https://secure2.authorize.net/customer/
getHostedProfilePageRequest -
hostedProfileIFrameCommunicatorUrl: *your URL*
hostedProfilePageBorderVisible: false //assuming you are using an iFrame
hostedProfileValidationMode: testMode
hostedProfileBillingAddressOptions: showNone
I'm retrieving account entities using code like this:-
var connection = CrmConnection.Parse(ConnectionString);
using (var orgService = new OrganizationService(connection))
{
var context = new MyOrganizationServiceContext(orgService);
var accounts = context.AccountSet.Where(...);
}
None of the returned accounts' relationship properties are populated (understandable, as this could result in lots of data being retrieved). Is there any way of requesting that certain relationships be populated, either as part of the LINQ query, or afterwards (e.g. on an entity-by-entity basis)?
you have a couple of options. Here the relevant MSDN article:
Access entity relationships
I know that it is possible to enroll users in an engagement plan from with Sitecore by adding them to a specific state in the plan when they visit a campaign URL, adding them when they submit a Web Forms for Marketers Form, and manually adding them in the Supervisor interface.
Additionally, I know that you can use the API to add a user as described here:
http://briancaos.wordpress.com/2013/06/03/programming-for-sitecore-dms-engagement-plans/
However, that method requires a username.
I would like to enroll anonymous users in an engagement plan when they visit any page represented by a particular template in Sitecore (ie, page from the Product template). Is this possible using the API?
To expand on my above comment, and to supplement your own answer, here's a processor that you could add to the after the ItemResolver in the httpRequestBegin pipeline that would achieve the desired result. It is a very basic version that you could embellish as you see fit
class CampaignRedirect
{
public void Process(HttpRequestArgs args)
{
var request = HttpContext.Current.Request;
// must not already have the querystring in the URL
if(request.QueryString["sc_camp"] != null &&
request.QueryString["sc_camp"] != "XXXXXXXX")
return;
// must have a context item
if(Sitecore.Context.Item == null)
return;
var item = Sitecore.Context.Item;
// must be the right template
if(item.TemplateID.ToString() != "{XXXXXXXXX-XXXX-XXXXXX}")
return;
var basicUrl = LinkManager.GetItemUrl(item);
var response = HttpContext.Current.Response;
response.Redirect(basicUrl + "?sc_camp=XXXXXXX");
}
}
If you're not familiar with adding processors, take a look here.
Per Sitecore support, this is not currently possible. However, I was able to achieve what I wanted by adding a jQuery AJAX call to the campaign URL to the sublayout used by the page type in question. Naturally this only works for clients with JS enabled, but for my purposes, that is not an issue.
<script type="text/javascript">$(function() { $.get('/?sc_camp=[campaignid]'); });</script>
Edited 2014-05-19
I found a way to do this via the Sitecore API. This is rough and needs to check for null values, exceptions, etc., but it does work:
string cookieVal = Request.Cookies["SC_ANALYTICS_GLOBAL_COOKIE"].Value;
List<Guid> guids = new List<Guid>() {
new Guid(cookieVal)
};
Guid automationStateId = new Guid("{24963AE9-1C8C-4E18-8EEE-01BC249D1F1B}");
Guid automationId = Sitecore.Context.Database.GetItem(new Sitecore.Data.ID(automationStateId)).ParentID.ToGuid();
Sitecore.Analytics.Automation.Data.AutomationManager.Provider.CreateAutomationStatesFromBulk(guids, automationId, automationStateId);
I'm trying to pull a list of connected users in Firebase to simply populate a select dropdown. My problem is that I can't seem to access the child objects properly.
Using connectedUsers.userName (see below code) works but only for my own user data, it doesn't pull anything else.
It seemed to me like changing "myUserRef.on" to "userListRef.on" and using something like "snapshot.child('userName').val()" should work but it just throws undefined. The same goes for "connectedUsers.child.userName", I'm sure I'm missing something simple here.
In the below code by changing to "userListRef.on('child_added', function(snapshot)" I can successfully add and remove user data from Firebase, and log all of the objects to the console and all data looks fine when I drill down the objects. I just need a way to access that data so I can put all connected users into a select dropdown or remove them from it when they disconnect.
var userListRef = new Firebase('https://myaccount.firebaseIO.com/users/');
var myUserRef = userListRef.push();
// ADD USER DATA TO FIREBASE
var userId = $('#myIdInput').val();
var userName = $('#nameInput').val();
myUserRef.push({userId: userId, userName: userName});
// READ USER OBJECTS AND FIRE ADDUSER FUNCTION
myUserRef.on('child_added', function(snapshot) {
var connectedUsers = snapshot.val();
console.log(addUser);
//addUser(connectedUsers.userId, connectedUsers.userName);
});
// ADD USER TO SELECT DROPDOWN
function addUser(userId, userName) {
var modSelect = $('#tsmmodsendto');
modSelect.append($('<option></option>').attr("value", userId).text(userName));
}
// READ USER OBJECTS AND FIRE REMOVEUSER FUNCTION
myUserRef.on('child_removed', function(snapshot) {
var connectedUsers = snapshot.val();
console.log(removeUser);
//removeUser(connectedUsers.userId, connectedUsers.userName);
});
// REMOVE USER TO SELECT DROPDOWN
function removeUser(userId, userName) {
var modSelect = $('#tsmmodsendto');
modSelect.append($('<option></option>').removeAttr("value", userId).text(userName));
}
// ON DISCONNECT REMOVE USER DATA FROM FIREBASE
myUserRef.onDisconnect().remove();
The code you've written won't work properly because each user is writing to:
/users/PUSH_ID/PUSH_ID
And is then listening at:
/users/PUSH_ID
So every client is going to be listening only to its own data and will never see anyone else's data. In order to view other people's data, you need to all be listening / writing to the same path.
In your question you mention you see "undefined" if you change to listening at /users. Could you simplify your code, and use that approach, and perhaps then I can provide a more helpful answer?
Or if I'm not understanding correctly, please simplify and clarify your question.