diff --git a/README.md b/README.md index 3ced42f..9e4580a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ PortalSettings V3 ist ein providerbasiertes SPFx-WebPart für SharePoint Server | Eigenschaft | Wert | |---|---| -| Version | 3.2.6 | +| Version | 3.2.7 | | SharePoint Framework | 1.4.1 | | Build-Node | 8.17.0 | | npm | 6.x | @@ -171,7 +171,7 @@ sharepoint/solution/portal-settings.sppkg ## Installation -1. `portal-settings.sppkg` im App Catalog durch Version `3.2.6.0` ersetzen. +1. `portal-settings.sppkg` im App Catalog durch Version `3.2.7.0` ersetzen. 2. Die App im Root Web der gewünschten Site Collection installieren oder aktualisieren. 3. Die Seite mit dem PnP-PowerShell-Skript erzeugen beziehungsweise reparieren: diff --git a/ToDo.md b/ToDo.md index 85a4213..7068804 100644 --- a/ToDo.md +++ b/ToDo.md @@ -2,7 +2,7 @@ Stand: 21.07.2026 Branch: `dev-3.0` -Zielversion: 3.2.6 +Zielversion: 3.2.7 ## Architektur @@ -79,7 +79,7 @@ Zielversion: 3.2.6 - [ ] Per PnP-Skript provisionierte `PortalSettings.aspx` und enthaltenes WebPart prüfen. - [ ] MegaMenu-Termsets laden und speichern. - [ ] CustomBranding laden und schemaerhaltend speichern. -- [x] ExpiryIndicator-Standard über SharePoint-SE-kompatibles POST ohne MERGE/PATCH speichern. +- [x] ExpiryIndicator-Standard über SharePoint-SE-kompatibles CSOM `ProcessQuery` speichern. - [ ] Expiry-Bindung hinzufügen, lokal konfigurieren und wieder auf Vererbung stellen. - [ ] Bedienung mit Tastatur, 200-Prozent-Zoom und Windows-Hochkontrast prüfen. - [ ] Alte V2-Seite nach erfolgreicher Parallelabnahme archivieren oder entfernen. diff --git a/config/package-solution.json b/config/package-solution.json index f664317..1d01bc3 100644 --- a/config/package-solution.json +++ b/config/package-solution.json @@ -3,7 +3,7 @@ "solution": { "name": "portal-settings-client-side-solution", "id": "1c98e32d-bb7b-43b7-9625-074d6e4ea286", - "version": "3.2.6.0", + "version": "3.2.7.0", "includeClientSideAssets": true, "skipFeatureDeployment": false }, diff --git a/package-lock.json b/package-lock.json index 8e32c35..3202d9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "portal-settings", - "version": "3.2.6", + "version": "3.2.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index adb3f04..ba7b201 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "portal-settings", - "version": "3.2.6", + "version": "3.2.7", "private": true, "main": "lib/index.js", "engines": { diff --git a/src/services/PortalSettingsDataService.ts b/src/services/PortalSettingsDataService.ts index d1b6ee7..3608efd 100644 --- a/src/services/PortalSettingsDataService.ts +++ b/src/services/PortalSettingsDataService.ts @@ -299,13 +299,35 @@ export class PortalSettingsDataService { private saveRootProperty(key: string, value: string): Promise { if (!key) { return Promise.reject(new Error('Der Property-Bag-Schlüssel fehlt.')); } - const body: any = { '__metadata': { 'type': 'SP.PropertyValues' } }; - body[this.encodePropertyName(key)] = value; - return this.context.spHttpClient.post(this.siteUrl + '/_api/web/AllProperties', SPHttpClient.configurations.v1, { - // SP.PropertyValues accepts POST, but SharePoint Server SE rejects the - // MERGE override because it is handled as an unsupported HTTP PATCH. - headers: this.writeHeaders(), body: JSON.stringify(body) - }).then((response: SPHttpClientResponse): Promise => this.ensureOk(response, 'Der Site-Collection-Standard konnte nicht gespeichert werden.')); + const requestXml: string = '' + + '' + + '' + + '' + this.escapeXml(key) + '' + + '' + this.escapeXml(value) + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + return this.context.spHttpClient.post(this.siteUrl + '/_vti_bin/client.svc/ProcessQuery', SPHttpClient.configurations.v1, { + headers: { 'Accept': 'application/json', 'Content-Type': 'text/xml;charset="UTF-8"' }, body: requestXml + }).then((response: SPHttpClientResponse): Promise => { + if (!response.ok) { + return this.responseError(response, 'Der Site-Collection-Standard konnte nicht gespeichert werden.') + .then((error: Error): Promise => Promise.reject(error)); + } + return response.text(); + }).then((text: string): void => { + const payload: any[] = text ? JSON.parse(text) : []; + if (payload.length && payload[0] && payload[0].ErrorInfo) { + throw new Error('Der Site-Collection-Standard konnte nicht gespeichert werden. ' + + (payload[0].ErrorInfo.ErrorMessage || 'SharePoint hat den CSOM-Aufruf abgelehnt.')); + } + }); } private findAction(provider: ISettingsProvider, actions: IUserCustomActionInfo[]): IUserCustomActionInfo | undefined { @@ -381,7 +403,10 @@ export class PortalSettingsDataService { return url; } private normalizePropertyName(value: string): string { return String(value || '').replace(/_x002e_/gi, '.').toLowerCase(); } - private encodePropertyName(value: string): string { return value.replace(/\./g, '_x002e_'); } + private escapeXml(value: string): string { + return String(value || '').replace(/&/g, '&').replace(//g, '>') + .replace(/"/g, '"').replace(/'/g, '''); + } private readHeaders(): any { return { 'Accept': 'application/json;odata=verbose', 'OData-Version': '3.0' }; } private writeHeaders(method?: string): any { diff --git a/tests/validate-static-assets.js b/tests/validate-static-assets.js index c42ba25..88878c3 100644 --- a/tests/validate-static-assets.js +++ b/tests/validate-static-assets.js @@ -26,8 +26,8 @@ var rootPropertyWriter = dataService.substring( dataService.indexOf('private findAction') ); -assert(packageJson.version === '3.2.6', 'Package-Version ist nicht 3.2.6.'); -assert(solution.version === '3.2.6.0', 'Solution-Version ist nicht 3.2.6.0.'); +assert(packageJson.version === '3.2.7', 'Package-Version ist nicht 3.2.7.'); +assert(solution.version === '3.2.7.0', 'Solution-Version ist nicht 3.2.7.0.'); assert(!solution.features, 'Das inkompatible Seiten-Provisioning-Feature darf nicht paketiert werden.'); assert(!!config.bundles['portal-settings-web-part'], 'WebPart-Bundle fehlt.'); assert(copyAssets.deployCdnPath === 'temp/deploy', 'Ship-Assets werden nicht nach temp/deploy geschrieben.'); @@ -40,9 +40,10 @@ assert(dataService.indexOf('/_api/site/rootweb/UserCustomActions') >= 0 && 'Root-Web-Actions werden nicht für die Provider-Erkennung berücksichtigt.'); assert(dataService.indexOf('body.Sequence = provider.storage.action.sequence') >= 0, 'Bestehende Actions werden nicht auf die Provider-Sequence aktualisiert.'); -assert(rootPropertyWriter.indexOf('headers: this.writeHeaders(), body: JSON.stringify(body)') >= 0 && - rootPropertyWriter.indexOf("writeHeaders('MERGE')") < 0, - 'Root-Web-Properties müssen mit POST ohne MERGE/PATCH gespeichert werden.'); +assert(rootPropertyWriter.indexOf('/_vti_bin/client.svc/ProcessQuery') >= 0 && + rootPropertyWriter.indexOf('SetFieldValue') >= 0 && rootPropertyWriter.indexOf('Name="Update"') >= 0 && + rootPropertyWriter.indexOf('/_api/web/AllProperties') < 0, + 'Root-Web-Properties müssen über den SharePoint-CSOM-Endpunkt gespeichert werden.'); ['themePrimary', 'neutralPrimary', 'neutralLight', 'white'].forEach(function (slot) { assert(styles.indexOf('[theme: ' + slot + ', default:') >= 0, 'Themeslot fehlt: ' + slot); });