import { ISettingsProvider } from '../core/ProviderContracts'; export interface IExpiryIndicatorSettings { baseField: string; expiryField: string; default: { lifeTime: { value: number; unit: 'days' | 'months' | 'years' }; columnRule: any[]; // tslint:disable-line:no-any }; rules: any[]; // tslint:disable-line:no-any nullText: string; confirmExtension: boolean; [key: string]: any; // tslint:disable-line:no-any } export const ExpiryIndicatorSettingsProvider: ISettingsProvider = { key: 'expiryindicator', displayName: 'ExpiryIndicator', description: 'Zentrale Standards, Vererbung und Bindungen für Listen und Bibliotheken.', iconName: 'DateTime2', schemaVersion: 2, minimumPortalSettingsVersion: '3.0.0', componentIds: [ 'a555f4fc-d6a6-4421-8189-457449d9bbde', '95691218-bcb1-4fad-adda-02bb12014d15', 'cd58f5d9-ffc8-4df7-a910-3054842d7abf' ], storage: { kind: 'rootPropertyBag', propertyKey: 'PortalSettings.ExpiryIndicator.Defaults' }, sections: [ { key: 'scope', title: 'Gültigkeitsbereich', layout: 'singleColumn', iconName: 'Globe', elements: [ { type: 'notice', appearance: 'info', title: 'Site-Collection-Standard', text: 'Diese Werte gelten für alle vererbenden Bindungen im Root Web und in Unterwebs.', columnSpan: 12 }, { type: 'bindingInventory', key: 'bindings', title: 'Registrierte Bindungen', columnSpan: 12 } ] }, { key: 'dates', title: 'Datumsberechnung', layout: 'twoColumns', iconName: 'Calendar', elements: [ { type: 'textField', key: 'baseField', path: 'baseField', label: 'Basis-Datumsfeld', description: 'Interner Feldname, normalerweise Created.', required: true, columnSpan: 6 }, { type: 'textField', key: 'expiryField', path: 'expiryField', label: 'Ablaufdatumsfeld', description: 'Standardwert für neue Bindungen; vorhandene Bindungen behalten ihren Feldnamen.', required: true, columnSpan: 6 }, { type: 'number', key: 'lifeTimeValue', path: 'default.lifeTime.value', label: 'Standardlaufzeit', minimum: 1, maximum: 10000, columnSpan: 6 }, { type: 'choice', key: 'lifeTimeUnit', path: 'default.lifeTime.unit', label: 'Einheit', columnSpan: 6, options: [ { key: 'days', text: 'Tage' }, { key: 'months', text: 'Monate' }, { key: 'years', text: 'Jahre' } ] }, { type: 'textField', key: 'nullText', path: 'nullText', label: 'Text ohne Ablaufdatum', columnSpan: 6 }, { type: 'boolean', key: 'confirmExtension', path: 'confirmExtension', label: 'Verlängerung bestätigen', description: 'Zeigt vor der Verlängerung eine Sicherheitsabfrage.', columnSpan: 6 } ] }, { key: 'rules', title: 'Farben und fachliche Regeln', layout: 'twoColumns', iconName: 'BranchFork', elements: [ { type: 'json', key: 'columnRule', path: 'default.columnRule', label: 'Standard-Farbregeln', description: 'Die erste passende Schwelle gewinnt.', columnSpan: 6 }, { type: 'json', key: 'rules', path: 'rules', label: 'Feldabhängige Regeln', description: 'Unterstützt Legacy-Regeln und verschachtelte AND-/OR-Bedingungen.', columnSpan: 6 } ] }, { key: 'preview', title: 'Zusammenfassung', layout: 'singleColumn', elements: [ { type: 'propertySummary', title: 'Aktuelle Werte', columnSpan: 12 }, { type: 'jsonPreview', title: 'Zentraler Standard', collapsedByDefault: true, columnSpan: 12 } ] } ], createDefault: (): IExpiryIndicatorSettings => ({ baseField: 'Created', expiryField: 'ExpiryDate', default: { lifeTime: { value: 2, unit: 'years' }, columnRule: [ { daysUntilExpiry: 0, operator: 'lessOrEqual', backgroundColor: '#a4262c', textColor: '#ffffff', label: 'Abgelaufen' }, { daysUntilExpiry: 30, operator: 'lessOrEqual', backgroundColor: '#ffaa44', textColor: '#000000', label: 'Läuft bald ab' }, { daysUntilExpiry: 90, operator: 'lessOrEqual', backgroundColor: '#fff4ce', textColor: '#000000', label: 'Beobachten' } ] }, rules: [], nullText: 'Kein Ablaufdatum', confirmExtension: true }), normalize: (value: any): IExpiryIndicatorSettings => { // tslint:disable-line:no-any const defaults: IExpiryIndicatorSettings = ExpiryIndicatorSettingsProvider.createDefault(); const source: any = value && typeof value === 'object' ? value : {}; // tslint:disable-line:no-any const result: any = {}; // tslint:disable-line:no-any Object.keys(source).forEach((key: string): void => { result[key] = source[key]; }); result.baseField = safeName(source.baseField) ? source.baseField : defaults.baseField; result.expiryField = safeName(source.expiryField) ? source.expiryField : defaults.expiryField; result.default = source.default && typeof source.default === 'object' ? source.default : defaults.default; result.default.lifeTime = result.default.lifeTime || defaults.default.lifeTime; result.default.columnRule = Array.isArray(result.default.columnRule) ? result.default.columnRule : defaults.default.columnRule; result.rules = Array.isArray(source.rules) ? source.rules : []; result.nullText = typeof source.nullText === 'string' ? source.nullText : defaults.nullText; result.confirmExtension = source.confirmExtension !== false; return result as IExpiryIndicatorSettings; }, validate: (value: IExpiryIndicatorSettings): string[] => { const errors: string[] = []; if (!safeName(value.baseField)) { errors.push('Das Basis-Datumsfeld ist ungültig.'); } if (!safeName(value.expiryField)) { errors.push('Das Ablaufdatumsfeld ist ungültig.'); } if (!value.default || !value.default.lifeTime || value.default.lifeTime.value < 1 || ['days', 'months', 'years'].indexOf(value.default.lifeTime.unit) < 0) { errors.push('Die Standardlaufzeit ist ungültig.'); } if (!value.default || !Array.isArray(value.default.columnRule)) { errors.push('Die Standard-Farbregeln sind ungültig.'); } if (!Array.isArray(value.rules)) { errors.push('Die fachlichen Regeln sind ungültig.'); } if (value.default) { validateColorRules(value.default.columnRule, 'Standard-Farbregeln', errors); } for (let i: number = 0; i < value.rules.length; i++) { const rule: any = value.rules[i]; // tslint:disable-line:no-any if (!rule || (!rule.condition && (!safeName(rule.columnName) || rule.columnValue === undefined))) { errors.push('Regel ' + (i + 1) + ' benötigt eine Bedingung oder columnName/columnValue.'); } if (rule && rule.condition) { validateCondition(rule.condition, 'Regel ' + (i + 1), 1, errors); } if (!rule || !rule.lifeTime || Number(rule.lifeTime.value) < 1 || ['days', 'months', 'years'].indexOf(rule.lifeTime.unit) < 0) { errors.push('Regel ' + (i + 1) + ' enthält eine ungültige Laufzeit.'); } if (rule) { validateColorRules(rule.columnRule, 'Regel ' + (i + 1), errors); } } return errors; } }; function safeName(value: string): boolean { return typeof value === 'string' && /^[A-Za-z_][A-Za-z0-9_]*$/.test(value); } function validateColorRules(rules: any[], path: string, errors: string[]): void { // tslint:disable-line:no-any if (!Array.isArray(rules)) { return; } const operators: string[] = ['lessThan', 'lessOrEqual', 'equal', 'greaterOrEqual', 'greaterThan']; for (let i: number = 0; i < rules.length; i++) { const rule: any = rules[i]; // tslint:disable-line:no-any if (!rule || typeof rule.daysUntilExpiry !== 'number' || operators.indexOf(rule.operator) < 0 || typeof rule.backgroundColor !== 'string' || typeof rule.textColor !== 'string') { errors.push(path + '[' + i + '] ist ungültig.'); } } } function validateCondition(condition: any, path: string, depth: number, errors: string[]): void { // tslint:disable-line:no-any if (depth > 10) { errors.push(path + ' überschreitet die maximale Bedingungstiefe.'); return; } if (!condition || (condition.operator !== 'and' && condition.operator !== 'or') || !Array.isArray(condition.conditions) || !condition.conditions.length) { errors.push(path + ' enthält eine ungültige AND-/OR-Bedingungsgruppe.'); return; } for (let i: number = 0; i < condition.conditions.length; i++) { const child: any = condition.conditions[i]; // tslint:disable-line:no-any if (child && child.operator) { validateCondition(child, path + '.' + i, depth + 1, errors); } else if (!child || !safeName(child.columnName) || child.columnValue === undefined) { errors.push(path + '.' + i + ' ist keine gültige Feldbedingung.'); } } }