Skip to content

Commit 3ae93a7

Browse files
committed
Merge branch 'main' into feature/hubspot-forms-v9-multi-target
# Conflicts: # src/Umbraco.Cms.Integrations.sln V9 multi-target updates; remove region mandatory check; null check on form rendering.
2 parents 4d1341b + 354ebb1 commit 3ae93a7

File tree

68 files changed

+2656
-164
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+2656
-164
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ This repository houses open-source extensions, created for Umbraco CMS, that int
88

99
[CommerceTools](./src/Umbraco.Cms.Integrations.Commerce.CommerceTools/) - a product and category picker that can be added as a property editor for content, with a value converter providing a strongly typed model for rendering.
1010

11+
[Shopify](./src/Umbraco.Cms.Integrations.Commerce.Shopify/) - a products picker that can be added as a property editor for content, with a value converter providing a strongly typed model for rendering.
12+
1113
### SEO
1214

1315
[Semrush](./src/Umbraco.Cms.Integrations.SEO.Semrush/) - a search tool available as a content app, helping editors research and use appropriate keywords for their content, to help with website search engine optimisation.
1416

1517
### CRM
1618

17-
[HubSpot](./src/Umbraco.Cms.Integrations.Crm.HubSpot/) - a form picker and rendering component for Hubspot forms.
19+
[HubSpot](./src/Umbraco.Cms.Integrations.Crm.Hubspot/) - a form picker and rendering component for Hubspot forms.
Lines changed: 1 addition & 0 deletions
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
function ProductPickerController($scope, editorService, notificationsService, umbracoCmsIntegrationsCommerceShopifyService, umbracoCmsIntegrationsCommerceShopifyResource) {
2+
3+
const oauthName = "OAuth";
4+
5+
var vm = this;
6+
7+
vm.loading = false;
8+
vm.selectedProducts = [];
9+
10+
vm.config = {
11+
validationLimit: {
12+
min: $scope.model.config.validationLimit.min ?? 0,
13+
max: $scope.model.config.validationLimit.max
14+
}
15+
}
16+
17+
// step 1. check configuration
18+
checkConfiguration(function () {
19+
// step 2. get products
20+
getProducts();
21+
});
22+
23+
function getProducts() {
24+
vm.loading = true;
25+
26+
umbracoCmsIntegrationsCommerceShopifyResource.getProductsList().then(function (response) {
27+
if (response.isValid) {
28+
29+
vm.productsList = response.result.products;
30+
31+
if ($scope.model.value != undefined && $scope.model.value.length > 0) {
32+
loadProductsPreview();
33+
};
34+
}
35+
vm.loading = false;
36+
});
37+
}
38+
39+
// products table events
40+
vm.selectProduct = function (item) {
41+
42+
var isProductSelected =
43+
$scope.model.selectedProducts.filter(function(i) { return i.id === item.id }).length > 0;
44+
45+
// check if products count is in the validation limit interval
46+
var isProductsCountValid = validateProductsCount(isProductSelected);
47+
if (isProductsCountValid) {
48+
if (isProductSelected) {
49+
$scope.model.selectedProducts =
50+
$scope.model.selectedProducts.filter(function(i) { return i.id !== item.id; });
51+
} else {
52+
$scope.model.selectedProducts.push(item);
53+
}
54+
}
55+
}
56+
57+
vm.isSelected = function (item, products) {
58+
return products.filter(function (i) { return i.id === item.id }).length > 0;
59+
}
60+
61+
vm.openProductsPickerOverlay = function () {
62+
var options = {
63+
title: "Shopify products",
64+
description: "Select product(s)",
65+
selectedProducts: vm.selectedProducts,
66+
config: vm.config,
67+
view: "/App_Plugins/UmbracoCms.Integrations/Commerce/Shopify/views/productPickerOverlay.html",
68+
size: "large",
69+
submit: function (selectedProducts) {
70+
vm.submit(selectedProducts);
71+
72+
loadProductsPreview();
73+
74+
editorService.close();
75+
},
76+
close: function () {
77+
editorService.close();
78+
}
79+
};
80+
81+
editorService.open(options);
82+
}
83+
84+
vm.submit = function (products) {
85+
$scope.model.value = products.map(function (item) { return item.id }).join(',');
86+
}
87+
88+
vm.remove = function (node) {
89+
vm.submit(vm.selectedProducts.filter(el => el.id != node.alias));
90+
loadProductsPreview();
91+
}
92+
93+
function checkConfiguration(callback) {
94+
umbracoCmsIntegrationsCommerceShopifyResource.checkConfiguration().then(function (response) {
95+
96+
vm.status = {
97+
isValid: response.isValid === true,
98+
type: response.type,
99+
description: umbracoCmsIntegrationsCommerceShopifyService.configDescription[response.type.value],
100+
useOAuth: response.isValid === true && response.type.value === oauthName
101+
};
102+
103+
if (response.isValid === false) {
104+
vm.loading = false;
105+
vm.error = umbracoCmsIntegrationsCommerceShopifyService.configDescription.None;
106+
notificationsService.warning("Shopify API", umbracoCmsIntegrationsCommerceShopifyService.configDescription.None);
107+
} else {
108+
callback();
109+
}
110+
});
111+
}
112+
113+
function loadProductsPreview() {
114+
vm.previewNodes = [];
115+
116+
var ids = $scope.model.value.split(",");
117+
118+
var list = vm.productsList.filter(el => {
119+
var id = ids.find(e => e == el.id);
120+
return id !== undefined ? el : null;
121+
});
122+
vm.selectedProducts = list;
123+
124+
list.forEach(el => {
125+
vm.previewNodes.push({
126+
icon: "icon-shopping-basket",
127+
name: el.title,
128+
alias: el.id
129+
});
130+
});
131+
}
132+
133+
function validateProductsCount(isRemoved) {
134+
135+
var updatedCount = isRemoved
136+
? $scope.model.selectedProducts.length - 1
137+
: $scope.model.selectedProducts.length + 1;
138+
139+
if (vm.config.validationLimit.min != null && vm.config.validationLimit.max != null) {
140+
return vm.config.validationLimit.min <= updatedCount && vm.config.validationLimit.max >= updatedCount;
141+
}
142+
143+
if (vm.config.validationLimit.min != null)
144+
return vm.config.validationLimit.min <= updatedCount;
145+
146+
if (vm.config.validationLimit.max != null)
147+
return vm.config.validationLimit.max >= updatedCount;
148+
}
149+
}
150+
151+
angular.module("umbraco")
152+
.controller("Umbraco.Cms.Integrations.Commerce.Shopify.ProductPickerController", ProductPickerController);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
function productPickerSettingsController($scope, notificationsService, umbracoCmsIntegrationsCommerceShopifyService, umbracoCmsIntegrationsCommerceShopifyResource) {
2+
3+
var vm = this;
4+
5+
const oauthName = "OAuth";
6+
7+
vm.oauthSetup = {};
8+
vm.status = {};
9+
10+
umbracoCmsIntegrationsCommerceShopifyResource.checkConfiguration()
11+
.then(function (response) {
12+
13+
vm.status = {
14+
isValid: response.isValid === true,
15+
type: response.type,
16+
description: umbracoCmsIntegrationsCommerceShopifyService.configDescription[response.type.value],
17+
useOAuth: response.isValid === true && response.type.value === oauthName
18+
};
19+
20+
if (vm.status.useOAuth) {
21+
validateOAuthSetup();
22+
}
23+
24+
if (response.isValid !== true) {
25+
notificationsService.warning("Shopify Configuration",
26+
"Invalid setup. Please review the API/OAuth settings.");
27+
}
28+
});
29+
30+
vm.onConnectClick = function () {
31+
32+
umbracoCmsIntegrationsCommerceShopifyResource.getAuthorizationUrl().then(function (response) {
33+
vm.authWindow = window.open(response,
34+
"Authorize", "width=900,height=700,modal=yes,alwaysRaised=yes");
35+
36+
});
37+
}
38+
39+
vm.onRevokeToken = function () {
40+
umbracoCmsIntegrationsCommerceShopifyResource.revokeAccessToken().then(function (response) {
41+
vm.oauthSetup.isConnected = false;
42+
notificationsService.success("Shopify Configuration", "OAuth connection revoked.");
43+
});
44+
}
45+
46+
// authorization listener
47+
window.addEventListener("message", function (event) {
48+
if (event.data.type === "shopify:oauth:success") {
49+
umbracoCmsIntegrationsCommerceShopifyResource.getAccessToken(event.data.code).then(function (response) {
50+
if (response.startsWith("Error:")) {
51+
notificationsService.error("Shopify Configuration", response);
52+
} else {
53+
vm.oauthSetup.isConnected = true;
54+
vm.status.description = umbracoCmsIntegrationsCommerceShopifyService.configDescription.OAuthConnected;
55+
notificationsService.success("Shopify Configuration", "OAuth connected.");
56+
}
57+
});
58+
59+
}
60+
}, false);
61+
62+
63+
function validateOAuthSetup() {
64+
umbracoCmsIntegrationsCommerceShopifyResource.validateAccessToken().then(function (response) {
65+
66+
vm.oauthSetup = {
67+
isConnected: response.isValid,
68+
isAccessTokenExpired: response.isExpired,
69+
isAccessTokenValid: response.isValid
70+
}
71+
72+
if (vm.oauthSetup.isConnected === true && vm.oauthSetup.isAccessTokenValid === true) {
73+
vm.status.description = umbracoCmsIntegrationsCommerceShopifyService.configDescription.OAuthConnected;
74+
}
75+
76+
// refresh access token
77+
if (vm.oauthSetup.isAccessTokenExpired === true) {
78+
umbracoCmsIntegrationsCommerceShopifyService.refreshAccessToken().then(function (response) {});
79+
}
80+
81+
});
82+
}
83+
}
84+
85+
angular.module("umbraco")
86+
.controller("Umbraco.Cms.Integrations.Commerce.Shopify.ProductPickerSettingsController", productPickerSettingsController);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
angular.module("umbraco.resources")
2+
.factory("umbracoCmsIntegrationsCommerceShopifyResource",
3+
function($http, umbRequestHelper) {
4+
const apiEndpoint = "backoffice/UmbracoCmsIntegrationsCommerceShopify/Products";
5+
6+
return {
7+
checkConfiguration: function () {
8+
return umbRequestHelper.resourcePromise(
9+
$http.get(`${apiEndpoint}/CheckConfiguration`),
10+
"Failed to get resource");
11+
},
12+
getAuthorizationUrl: function () {
13+
return umbRequestHelper.resourcePromise(
14+
$http.get(`${apiEndpoint}/GetAuthorizationUrl`),
15+
"Failed");
16+
},
17+
getAccessToken: function (authorizationCode) {
18+
return umbRequestHelper.resourcePromise(
19+
$http.post(`${apiEndpoint}/GetAccessToken`, { code: authorizationCode }),
20+
"Failed");
21+
},
22+
revokeAccessToken: function () {
23+
return umbRequestHelper.resourcePromise(
24+
$http.post(`${apiEndpoint}/RevokeAccessToken`),
25+
"Failed");
26+
},
27+
validateAccessToken: function () {
28+
return umbRequestHelper.resourcePromise(
29+
$http.get(`${apiEndpoint}/ValidateAccessToken`),
30+
"Failed");
31+
},
32+
getProductsList: function() {
33+
return umbRequestHelper.resourcePromise(
34+
$http.get(`${apiEndpoint}/GetList`), "Failed to get resource");
35+
}
36+
};
37+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
function shopifyService() {
2+
return {
3+
configDescription: {
4+
API: "An access token is configured and will be used to connect to your Shopify account.",
5+
OAuth:
6+
"No access token is configured. To connect to your Shopify account using OAuth click 'Connect', select your account and agree to the permissions.",
7+
None: "No access token or OAuth configuration could be found. Please review your settings before continuing.",
8+
OAuthConnected:
9+
"OAuth is configured and an access token is available to connect to your Shopify account. To revoke, click 'Revoke'"
10+
}
11+
}
12+
}
13+
14+
angular.module("umbraco.services")
15+
.service("umbracoCmsIntegrationsCommerceShopifyService", shopifyService);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"javascript": [
3+
"~/App_Plugins/UmbracoCms.Integrations/Commerce/Shopify/js/productPicker.controller.js",
4+
"~/App_Plugins/UmbracoCms.Integrations/Commerce/Shopify/js/productPickerSettings.controller.js",
5+
"~/App_Plugins/UmbracoCms.Integrations/Commerce/Shopify/js/shopify.resource.js",
6+
"~/App_Plugins/UmbracoCms.Integrations/Commerce/Shopify/js/shopify.service.js"
7+
]
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<div ng-controller="Umbraco.Cms.Integrations.Commerce.Shopify.ProductPickerController as vm">
2+
3+
<div ui-sortable ng-model="vm.previewNodes" ng-if="vm.previewNodes">
4+
<umb-node-preview ng-repeat="node in vm.previewNodes"
5+
icon="node.icon"
6+
name="node.name"
7+
alias="node.alias"
8+
published="node.published"
9+
description="node.description"
10+
sortable="true"
11+
allow-remove="true"
12+
allow-open="false"
13+
on-remove="vm.remove(node)">
14+
</umb-node-preview>
15+
</div>
16+
17+
<button type="button"
18+
class="umb-node-preview-add"
19+
ng-click="vm.openProductsPickerOverlay()"
20+
id="{{model.alias}}"
21+
aria-label="{{model.label}}: {{labels.general_add}}">
22+
<localize key="general_add">Add</localize>
23+
<span class="sr-only">...</span>
24+
</button>
25+
26+
</div>

0 commit comments

Comments
 (0)