Skip to content

Commit 935b48c

Browse files
committed
ActiveCampaign - Contacts Workflow
1 parent 0211c5e commit 935b48c

23 files changed

+638
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
using Umbraco.Cms.Core.Composing;
4+
using Umbraco.Cms.Core.DependencyInjection;
5+
using Umbraco.Forms.Core.Providers;
6+
using Umbraco.Forms.Integrations.Crm.ActiveCampaign.Configuration;
7+
using Umbraco.Forms.Integrations.Crm.ActiveCampaign.Services;
8+
9+
namespace Umbraco.Forms.Integrations.Crm.ActiveCampaign
10+
{
11+
public class ActiveCampaignComposer : IComposer
12+
{
13+
public void Compose(IUmbracoBuilder builder)
14+
{
15+
var options = builder.Services
16+
.AddOptions<ActiveCampaignSettings>()
17+
.Bind(builder.Config.GetSection(Constants.SettingsPath));
18+
19+
builder.WithCollectionBuilder<WorkflowCollectionBuilder>()
20+
.Add<ActiveCampaignContactsWorkflow>();
21+
22+
builder.Services
23+
.AddHttpClient(Constants.ContactsHttpClient, client =>
24+
{
25+
client.BaseAddress = new Uri(
26+
$"{builder.Config.GetSection(Constants.SettingsPath)[nameof(ActiveCampaignSettings.BaseUrl)]}/api/3/contacts");
27+
client.DefaultRequestHeaders
28+
.Add("Api-Token", builder.Config.GetSection(Constants.SettingsPath)[nameof(ActiveCampaignSettings.ApiKey)]);
29+
});
30+
31+
builder.Services.AddSingleton<IContactService, ContactService>();
32+
}
33+
}
34+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
using Microsoft.Extensions.Options;
2+
using Polly;
3+
using System.Text.Json;
4+
using Umbraco.Forms.Core;
5+
using Umbraco.Forms.Core.Enums;
6+
using Umbraco.Forms.Core.Persistence.Dtos;
7+
using Umbraco.Forms.Integrations.Crm.ActiveCampaign.Configuration;
8+
using Umbraco.Forms.Integrations.Crm.ActiveCampaign.Models.Dtos;
9+
using Umbraco.Forms.Integrations.Crm.ActiveCampaign.Services;
10+
11+
namespace Umbraco.Forms.Integrations.Crm.ActiveCampaign
12+
{
13+
public class ActiveCampaignContactsWorkflow : WorkflowType
14+
{
15+
private readonly ActiveCampaignSettings _settings;
16+
17+
private readonly IContactService _contactService;
18+
19+
[Core.Attributes.Setting("Contact Mappings",
20+
Description = "Map contact details with form fields",
21+
View = "~/App_Plugins/UmbracoForms.Integrations/Crm/ActiveCampaign/contact-mapper.html")]
22+
public string ContactMappings { get; set; }
23+
24+
public ActiveCampaignContactsWorkflow(IOptions<ActiveCampaignSettings> options, IContactService contactService)
25+
{
26+
Id = new Guid(Constants.WorkflowId);
27+
Name = "ActiveCampaign Contacts Workflow";
28+
Description = "Submit form data to ActiveCampaign Contacts";
29+
Icon = "icon-users";
30+
31+
_settings = options.Value;
32+
33+
_contactService = contactService;
34+
}
35+
36+
public override WorkflowExecutionStatus Execute(WorkflowExecutionContext context)
37+
{
38+
var mappings = JsonSerializer.Deserialize<List<ContactMappingDto>>(ContactMappings);
39+
40+
var email = context.Record.RecordFields[Guid.Parse(mappings.First(p => p.ContactField == "email").FormField.Id)]
41+
.ValuesAsString();
42+
43+
var contacts = _contactService.Get(email).Result;
44+
45+
var result = _contactService.CreateOrUpdate(new ContactRequestDto
46+
{
47+
Contact = contacts.Contacts.Count > 0
48+
? contacts.Contacts.First() : Build(context.Record)
49+
},
50+
contacts.Contacts.Count > 0).Result;
51+
52+
if (!result) return WorkflowExecutionStatus.Failed;
53+
54+
return WorkflowExecutionStatus.Completed;
55+
}
56+
57+
public override List<Exception> ValidateSettings()
58+
{
59+
var list = new List<Exception>();
60+
61+
if (string.IsNullOrEmpty(ContactMappings))
62+
list.Add(new Exception("Contact mappings are required."));
63+
64+
var mappings = JsonSerializer.Deserialize<List<ContactMappingDto>>(ContactMappings);
65+
bool validMappings = _settings.ContactFields
66+
.Where(p => p.Required)
67+
.Any(p => mappings.Select(q => q.ContactField).Contains(p.Name));
68+
if (!validMappings)
69+
list.Add(new Exception("Invalid contact mappings. Please make sure the mandatory fields are mapped."));
70+
71+
return list;
72+
}
73+
74+
/// <summary>
75+
/// Create Contact instance using the mapped details and record fields
76+
/// </summary>
77+
/// <param name="record"></param>
78+
/// <returns></returns>
79+
private ContactDto Build(Record record) => new ContactDto
80+
{
81+
Email = ReadMapping(record, "email"),
82+
FirstName = ReadMapping(record, "firstName"),
83+
LastName = ReadMapping(record, "lastName"),
84+
Phone = ReadMapping(record, "phone")
85+
};
86+
87+
private string ReadMapping(Record record, string name)
88+
{
89+
var mappings = JsonSerializer.Deserialize<List<ContactMappingDto>>(ContactMappings);
90+
91+
var mappingItem = mappings.FirstOrDefault(p => p.ContactField == name);
92+
93+
return mappingItem != null
94+
? record.RecordFields[Guid.Parse(mappingItem.FormField.Id)].ValuesAsString()
95+
: string.Empty;
96+
}
97+
}
98+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
function activeCampaignResource($http, umbRequestHelper) {
2+
3+
const apiEndpoint = "backoffice/UmbracoFormsIntegrationsCrmActiveCampaign/Contacts";
4+
5+
return {
6+
checkApiAccess: function () {
7+
return umbRequestHelper.resourcePromise(
8+
$http.get(`${apiEndpoint}/CheckApiAccess`),
9+
"Failed to get resource");
10+
},
11+
getContactFields: function () {
12+
return umbRequestHelper.resourcePromise(
13+
$http.get(`${apiEndpoint}/GetContactFields`),
14+
"Failed to get resource");
15+
},
16+
getCurrencies: function () {
17+
return umbRequestHelper.resourcePromise(
18+
$http.get(`${apiEndpoint}/GetCurrencies`),
19+
"Failed to get resource");
20+
},
21+
getAvailableMappingFields: function () {
22+
return umbRequestHelper.resourcePromise(
23+
$http.get(`${apiEndpoint}/GetAvailableMappingFields`),
24+
"Failed to get resource");
25+
},
26+
getRequiredMappingFields: function () {
27+
return umbRequestHelper.resourcePromise(
28+
$http.get(`${apiEndpoint}/GetRequiredMappingFields`),
29+
"Failed to get resource");
30+
}
31+
};
32+
}
33+
34+
angular.module('umbraco.resources').factory('umbracoFormsIntegrationsCrmActiveCampaignResource', activeCampaignResource);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
function activeCampaignService($routeParams, pickerResource) {
2+
return {
3+
getFormFields: function (callback) {
4+
var formId = $routeParams.id;
5+
6+
if (formId !== -1) {
7+
pickerResource.getAllFields(formId).then(function (response) {
8+
callback(response.data);
9+
});
10+
} else callback([]);
11+
}
12+
};
13+
}
14+
15+
angular.module("umbraco.services")
16+
.factory("activeCampaignService", activeCampaignService)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
function contactMapperController($scope, notificationsService, activeCampaignService, umbracoFormsIntegrationsCrmActiveCampaignResource) {
2+
3+
var vm = this;
4+
5+
init();
6+
7+
vm.addMapping = function () {
8+
9+
if (vm.contactMappings.find(p => p.contactField === vm.selectedMapping.contactField)) {
10+
notificationsService.warning("Contact field is already mapped.");
11+
return;
12+
}
13+
14+
vm.contactMappings.push({
15+
contactField: vm.selectedMapping.contactField,
16+
formField: vm.formFields.find(p => p.id === vm.selectedMapping.formField)
17+
});
18+
19+
$scope.setting.value = JSON.stringify(vm.contactMappings);
20+
21+
vm.selectedMapping.clear();
22+
}
23+
24+
vm.deleteMapping = function (index) {
25+
26+
vm.contactMappings.splice(index, 1);
27+
28+
$scope.setting.value = JSON.stringify(vm.mappings);
29+
}
30+
31+
32+
function init() {
33+
34+
vm.selectedMapping = {
35+
customerField: "",
36+
formField: "",
37+
clear: function () {
38+
this.customerField = "";
39+
this.formField = "";
40+
}
41+
};
42+
43+
vm.contactMappings = $scope.setting.value
44+
? JSON.parse($scope.setting.value)
45+
: [];
46+
47+
umbracoFormsIntegrationsCrmActiveCampaignResource.checkApiAccess().then(function (response) {
48+
if (!response.isApiConfigurationValid)
49+
notificationsService.error("ActiveCampaign API", "Invalid API Configuration");
50+
});
51+
52+
activeCampaignService.getFormFields(function (response) {
53+
vm.formFields = response;
54+
});
55+
56+
umbracoFormsIntegrationsCrmActiveCampaignResource.getContactFields().then(function (response) {
57+
vm.contactFields = response;
58+
59+
vm.requiredContactFields = vm.contactFields.filter(x => x.required).map(x => x.displayName).join(',');
60+
});
61+
}
62+
}
63+
64+
angular.module("umbraco")
65+
.controller("UmbracoForms.Integrations.Crm.ActiveCampaign.ContactMapperController", contactMapperController);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<div ng-controller="UmbracoForms.Integrations.Crm.ActiveCampaign.ContactMapperController as vm">
2+
<div>
3+
<select ng-model="vm.selectedMapping.contactField">
4+
<option value="">Select contact field</option>
5+
<option ng-repeat="field in vm.contactFields" value="{{ field.name }}">{{ field.name }}</option>
6+
</select>
7+
<select ng-model="vm.selectedMapping.formField">
8+
<option value="">Select form field</option>
9+
<option ng-repeat="field in vm.formFields" value="{{ field.id }}">{{ field.value }}</option>
10+
</select>
11+
</div>
12+
13+
<div ng-if="vm.contactFields.length > 0" class="alert alert-info mt2 mr3" role="alert">
14+
<span>Mandatory fields: {{ vm.requiredContactFields }}</span>
15+
</div>
16+
17+
<div class="mt2">
18+
<umb-button type="button" class="mt2"
19+
action="vm.addMapping()"
20+
label="Add mapping"
21+
disabled="vm.selectedMapping.contactField.length === 0 || vm.selectedMapping.formField.length === 0">
22+
</umb-button>
23+
</div>
24+
25+
<div class="umb-forms-mappings mt2" ng-if="vm.contactMappings.length > 0">
26+
27+
<div class="umb-forms-mapping-header">
28+
<div class="umb-forms-mapping-field -no-margin-left">Contact Field</div>
29+
<div class="umb-forms-mapping-field">Form Field</div>
30+
<div class="umb-forms-mapping-remove -no-margin-right"></div>
31+
</div>
32+
33+
<div class="umb-forms-mapping" ng-repeat="mapping in vm.contactMappings">
34+
<div class="umb-forms-mapping-field -no-margin-left">{{ mapping.contactField }}</div>
35+
<div class="umb-forms-mapping-field">{{ mapping.formField.value }}</div>
36+
<div class="umb-forms-mapping-remove -no-margin-right">
37+
<a href="" ng-click="vm.deleteMapping($index)">
38+
<i class="icon-trash"></i>
39+
</a>
40+
</div>
41+
</div>
42+
</div>
43+
44+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"javascript": [
3+
"~/App_Plugins/UmbracoForms.Integrations/Crm/ActiveCampaign/contact-mapper.controller.js",
4+
"~/App_Plugins/UmbracoForms.Integrations/Crm/ActiveCampaign/activecampaign.resource.js",
5+
"~/App_Plugins/UmbracoForms.Integrations/Crm/ActiveCampaign/activecampaign.service.js"
6+
],
7+
"css": [ "" ]
8+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+

2+
namespace Umbraco.Forms.Integrations.Crm.ActiveCampaign.Configuration
3+
{
4+
public class ActiveCampaignSettings
5+
{
6+
public ActiveCampaignSettings()
7+
{
8+
ContactFields = new List<ContactFieldSettings>();
9+
}
10+
11+
public string BaseUrl { get; set; }
12+
13+
public string ApiKey { get; set; }
14+
15+
public List<ContactFieldSettings> ContactFields { get; set; }
16+
}
17+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+

2+
namespace Umbraco.Forms.Integrations.Crm.ActiveCampaign.Configuration
3+
{
4+
public class ContactFieldSettings
5+
{
6+
public string Name { get; set; }
7+
8+
public string DisplayName { get; set; }
9+
10+
public bool Required { get; set; }
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+

2+
namespace Umbraco.Forms.Integrations.Crm.ActiveCampaign
3+
{
4+
public class Constants
5+
{
6+
public const string WorkflowId = "5e8b110b-715f-4ba5-a384-86dba53a4d1b";
7+
8+
public const string ContactsHttpClient = "ContactsClient";
9+
10+
public const string SettingsPath = "Umbraco:Forms:Integrations:Crm:ActiveCampaign:Settings";
11+
}
12+
}

0 commit comments

Comments
 (0)