|
1
|
|
|
import type { RawLocation, Location } from 'vue-router'; |
|
2
|
|
|
import type { AppModulesService, AppModuleDefinition } from 'src/core/service/api/app-modules.service'; |
|
3
|
|
|
import type StoreApiService from 'src/core/service/api/store.api.service'; |
|
4
|
|
|
import type { ShopwareDiscountCampaignService } from 'src/app/service/discount-campaign.service'; |
|
5
|
|
|
import type { |
|
6
|
|
|
ExtensionStoreActionService, |
|
7
|
|
|
Extension, |
|
8
|
|
|
ExtensionVariant, |
|
9
|
|
|
ExtensionVariantType, |
|
10
|
|
|
ExtensionType, |
|
11
|
|
|
} from './extension-store-action.service'; |
|
12
|
|
|
|
|
13
|
|
|
type EXTENSION_VARIANT_TYPES = { |
|
14
|
|
|
[Property in Uppercase<ExtensionVariantType>]: Lowercase<Property> |
|
15
|
|
|
} |
|
16
|
|
|
|
|
17
|
|
|
type EXTENSION_TYPES = { |
|
18
|
|
|
[Property in Uppercase<ExtensionType>]: Lowercase<Property> |
|
19
|
|
|
} |
|
20
|
|
|
|
|
21
|
|
|
interface LabeledLocation extends Location { |
|
22
|
|
|
label: string|null |
|
23
|
|
|
} |
|
24
|
|
|
|
|
25
|
|
|
/** |
|
26
|
|
|
* @package merchant-services |
|
27
|
|
|
* @private |
|
28
|
|
|
*/ |
|
29
|
|
|
export default class ShopwareExtensionService { |
|
30
|
|
|
public readonly EXTENSION_VARIANT_TYPES: Readonly<EXTENSION_VARIANT_TYPES>; |
|
31
|
|
|
|
|
32
|
|
|
private readonly EXTENSION_TYPES: Readonly<EXTENSION_TYPES>; |
|
33
|
|
|
|
|
34
|
|
|
constructor( |
|
35
|
|
|
private readonly appModulesService: AppModulesService, |
|
36
|
|
|
private readonly extensionStoreActionService: ExtensionStoreActionService, |
|
37
|
|
|
private readonly discountCampaignService: ShopwareDiscountCampaignService, |
|
38
|
|
|
private readonly storeApiService: StoreApiService, |
|
39
|
|
|
) { |
|
40
|
|
|
this.EXTENSION_VARIANT_TYPES = Object.freeze({ |
|
41
|
|
|
RENT: 'rent', |
|
42
|
|
|
BUY: 'buy', |
|
43
|
|
|
FREE: 'free', |
|
44
|
|
|
}); |
|
45
|
|
|
|
|
46
|
|
|
this.EXTENSION_TYPES = Object.freeze({ |
|
47
|
|
|
APP: 'app', |
|
48
|
|
|
PLUGIN: 'plugin', |
|
49
|
|
|
}); |
|
50
|
|
|
} |
|
51
|
|
|
|
|
52
|
|
|
public async installExtension(extensionName: string, type: ExtensionType): Promise<void> { |
|
53
|
|
|
await this.extensionStoreActionService.installExtension(extensionName, type); |
|
54
|
|
|
|
|
55
|
|
|
await this.updateExtensionData(); |
|
56
|
|
|
} |
|
57
|
|
|
|
|
58
|
|
|
public async updateExtension(extensionName: string, type: ExtensionType, allowNewPrivileges = false): Promise<void> { |
|
59
|
|
|
await this.extensionStoreActionService.updateExtension(extensionName, type, allowNewPrivileges); |
|
60
|
|
|
|
|
61
|
|
|
await this.updateExtensionData(); |
|
62
|
|
|
} |
|
63
|
|
|
|
|
64
|
|
|
public async uninstallExtension(extensionName: string, type: ExtensionType, removeData: boolean): Promise<void> { |
|
65
|
|
|
await this.extensionStoreActionService.uninstallExtension(extensionName, type, removeData); |
|
66
|
|
|
|
|
67
|
|
|
await this.updateExtensionData(); |
|
68
|
|
|
} |
|
69
|
|
|
|
|
70
|
|
|
public async removeExtension(extensionName: string, type: ExtensionType): Promise<void> { |
|
71
|
|
|
await this.extensionStoreActionService.removeExtension(extensionName, type); |
|
72
|
|
|
|
|
73
|
|
|
await this.updateExtensionData(); |
|
74
|
|
|
} |
|
75
|
|
|
|
|
76
|
|
|
public async cancelLicense(licenseId: number): Promise<void> { |
|
77
|
|
|
await this.extensionStoreActionService.cancelLicense(licenseId); |
|
78
|
|
|
} |
|
79
|
|
|
|
|
80
|
|
|
public async activateExtension(extensionId: string, type: ExtensionType): Promise<void> { |
|
81
|
|
|
await this.extensionStoreActionService.activateExtension(extensionId, type); |
|
82
|
|
|
|
|
83
|
|
|
await this.updateModules(); |
|
84
|
|
|
} |
|
85
|
|
|
|
|
86
|
|
|
public async deactivateExtension(extensionId: string, type: ExtensionType): Promise<void> { |
|
87
|
|
|
await this.extensionStoreActionService.deactivateExtension(extensionId, type); |
|
88
|
|
|
|
|
89
|
|
|
await this.updateModules(); |
|
90
|
|
|
} |
|
91
|
|
|
|
|
92
|
|
|
public async updateExtensionData(): Promise<void> { |
|
93
|
|
|
Shopware.State.commit('shopwareExtensions/loadMyExtensions'); |
|
94
|
|
|
|
|
95
|
|
|
try { |
|
96
|
|
|
await this.extensionStoreActionService.refresh(); |
|
97
|
|
|
|
|
98
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment |
|
99
|
|
|
const myExtensions = await this.extensionStoreActionService.getMyExtensions(); |
|
100
|
|
|
|
|
101
|
|
|
Shopware.State.commit('shopwareExtensions/myExtensions', myExtensions); |
|
102
|
|
|
|
|
103
|
|
|
await this.updateModules(); |
|
104
|
|
|
} finally { |
|
105
|
|
|
Shopware.State.commit('shopwareExtensions/setLoading', false); |
|
106
|
|
|
} |
|
107
|
|
|
} |
|
108
|
|
|
|
|
109
|
|
|
public async checkLogin(): Promise<void> { |
|
110
|
|
|
try { |
|
111
|
|
|
const { userInfo } = await this.storeApiService.checkLogin(); |
|
112
|
|
|
Shopware.State.commit('shopwareExtensions/setUserInfo', userInfo); |
|
113
|
|
|
} catch { |
|
114
|
|
|
Shopware.State.commit('shopwareExtensions/setUserInfo', null); |
|
115
|
|
|
} |
|
116
|
|
|
} |
|
117
|
|
|
|
|
118
|
|
|
public orderVariantsByRecommendation(variants: ExtensionVariant[]): ExtensionVariant[] { |
|
119
|
|
|
const discounted = variants.filter((variant) => this.isVariantDiscounted(variant)); |
|
120
|
|
|
const notDiscounted = variants.filter((variant) => !this.isVariantDiscounted(variant)); |
|
121
|
|
|
|
|
122
|
|
|
return [ |
|
123
|
|
|
...this.orderByType(discounted), |
|
124
|
|
|
...this.orderByType(notDiscounted), |
|
125
|
|
|
]; |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
public isVariantDiscounted(variant: ExtensionVariant): boolean { |
|
129
|
|
|
if (!variant || !variant.discountCampaign |
|
130
|
|
|
|| typeof variant.discountCampaign.discountedPrice !== 'number' |
|
131
|
|
|
|| variant.discountCampaign.discountedPrice === variant.netPrice |
|
132
|
|
|
) { |
|
133
|
|
|
return false; |
|
134
|
|
|
} |
|
135
|
|
|
|
|
136
|
|
|
return this.discountCampaignService.isDiscountCampaignActive(variant.discountCampaign); |
|
137
|
|
|
} |
|
138
|
|
|
|
|
139
|
|
|
public getPriceFromVariant(variant: ExtensionVariant) { |
|
140
|
|
|
if (this.isVariantDiscounted(variant)) { |
|
141
|
|
|
// null assertion is fine here because we do all checks in isVariantDiscounted |
|
142
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
|
143
|
|
|
return variant.discountCampaign!.discountedPrice!; |
|
144
|
|
|
} |
|
145
|
|
|
|
|
146
|
|
|
return variant.netPrice; |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
public mapVariantToRecommendation(variant: ExtensionVariant) { |
|
150
|
|
|
switch (variant.type) { |
|
151
|
|
|
case this.EXTENSION_VARIANT_TYPES.FREE: |
|
152
|
|
|
return 0; |
|
153
|
|
|
case this.EXTENSION_VARIANT_TYPES.RENT: |
|
154
|
|
|
return 1; |
|
155
|
|
|
case this.EXTENSION_VARIANT_TYPES.BUY: |
|
156
|
|
|
return 2; |
|
157
|
|
|
default: |
|
158
|
|
|
return 3; |
|
159
|
|
|
} |
|
160
|
|
|
} |
|
161
|
|
|
|
|
162
|
|
|
public async getOpenLink(extension: Extension): Promise<null|RawLocation> { |
|
163
|
|
|
if (extension.isTheme) { |
|
164
|
|
|
return this.getLinkToTheme(extension); |
|
165
|
|
|
} |
|
166
|
|
|
|
|
167
|
|
|
if (extension.type === this.EXTENSION_TYPES.APP) { |
|
168
|
|
|
return this.getLinkToApp(extension); |
|
169
|
|
|
} |
|
170
|
|
|
|
|
171
|
|
|
// Only show open link when extension is active. The route is maybe not available |
|
172
|
|
|
if (!extension.active) { |
|
173
|
|
|
return null; |
|
174
|
|
|
} |
|
175
|
|
|
|
|
176
|
|
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment */ |
|
177
|
|
|
const entryRoutes = Shopware.State.get('extensionEntryRoutes').routes; |
|
178
|
|
|
|
|
179
|
|
|
if (entryRoutes[extension.name] !== undefined) { |
|
180
|
|
|
return { |
|
181
|
|
|
name: entryRoutes[extension.name].route, |
|
182
|
|
|
label: entryRoutes[extension.name].label ?? null, |
|
183
|
|
|
} as LabeledLocation; |
|
184
|
|
|
} |
|
185
|
|
|
/* eslint-enable */ |
|
186
|
|
|
|
|
187
|
|
|
return null; |
|
188
|
|
|
} |
|
189
|
|
|
|
|
190
|
|
|
private async updateModules() { |
|
191
|
|
|
const modules = await this.appModulesService.fetchAppModules(); |
|
192
|
|
|
|
|
193
|
|
|
Shopware.State.commit('shopwareApps/setApps', modules); |
|
194
|
|
|
} |
|
195
|
|
|
|
|
196
|
|
|
private async getLinkToTheme(extension: Extension) { |
|
197
|
|
|
const { Criteria } = Shopware.Data; |
|
198
|
|
|
const themeRepository = Shopware.Service('repositoryFactory').create('theme'); |
|
199
|
|
|
|
|
200
|
|
|
const criteria = new Criteria(1, 1); |
|
201
|
|
|
criteria.addFilter(Criteria.equals('technicalName', extension.name)); |
|
202
|
|
|
|
|
203
|
|
|
const { data: ids } = await themeRepository.searchIds(criteria); |
|
204
|
|
|
|
|
205
|
|
|
if (ids.length === 0) { |
|
206
|
|
|
return null; |
|
207
|
|
|
} |
|
208
|
|
|
|
|
209
|
|
|
return { |
|
210
|
|
|
name: 'sw.theme.manager.detail', |
|
211
|
|
|
params: { |
|
212
|
|
|
id: ids[0], |
|
213
|
|
|
}, |
|
214
|
|
|
}; |
|
215
|
|
|
} |
|
216
|
|
|
|
|
217
|
|
|
private getLinkToApp(extension: Extension) { |
|
218
|
|
|
const app = this.getAppFromStore(extension.name); |
|
219
|
|
|
|
|
220
|
|
|
if (!app) { |
|
221
|
|
|
return null; |
|
222
|
|
|
} |
|
223
|
|
|
|
|
224
|
|
|
if (this.appHasMainModule(app)) { |
|
225
|
|
|
return this.createLinkToModule(app.name); |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
|
|
return null; |
|
229
|
|
|
} |
|
230
|
|
|
|
|
231
|
|
|
private getAppFromStore(extensionName: string) { |
|
232
|
|
|
return Shopware.State.get('shopwareApps').apps.find((innerApp) => { |
|
233
|
|
|
return innerApp.name === extensionName; |
|
234
|
|
|
}); |
|
235
|
|
|
} |
|
236
|
|
|
|
|
237
|
|
|
private appHasMainModule(app: AppModuleDefinition) { |
|
238
|
|
|
return !!app.mainModule?.source; |
|
239
|
|
|
} |
|
240
|
|
|
|
|
241
|
|
|
private createLinkToModule(appName: string) { |
|
242
|
|
|
return { |
|
243
|
|
|
name: 'sw.extension.module', |
|
244
|
|
|
params: { |
|
245
|
|
|
appName, |
|
246
|
|
|
}, |
|
247
|
|
|
}; |
|
248
|
|
|
} |
|
249
|
|
|
|
|
250
|
|
|
private orderByType(variants: ExtensionVariant[]) { |
|
251
|
|
|
const valueTypes = variants.map((variant, index) => { |
|
252
|
|
|
return { value: this.mapVariantToRecommendation(variant), index }; |
|
253
|
|
|
}); |
|
254
|
|
|
|
|
255
|
|
|
valueTypes.sort((first, second) => { |
|
256
|
|
|
return first.value - second.value; |
|
257
|
|
|
}); |
|
258
|
|
|
|
|
259
|
|
|
return valueTypes.map((type) => { |
|
260
|
|
|
return variants[type.index]; |
|
261
|
|
|
}); |
|
262
|
|
|
} |
|
263
|
|
|
} |
|
264
|
|
|
|