Completed
Push — master ( 1d4b89...0f5db1 )
by
unknown
21:32 queued 15s
created
lib/private/Settings/DeclarativeManager.php 2 patches
Indentation   +444 added lines, -444 removed lines patch added patch discarded remove patch
@@ -35,448 +35,448 @@
 block discarded – undo
35 35
  */
36 36
 class DeclarativeManager implements IDeclarativeManager {
37 37
 
38
-	/** @var array<string, list<IDeclarativeSettingsForm>> */
39
-	private array $declarativeForms = [];
40
-
41
-	/**
42
-	 * @var array<string, list<DeclarativeSettingsFormSchemaWithoutValues>>
43
-	 */
44
-	private array $appSchemas = [];
45
-
46
-	public function __construct(
47
-		private IEventDispatcher $eventDispatcher,
48
-		private IGroupManager $groupManager,
49
-		private Coordinator $coordinator,
50
-		private IConfig $config,
51
-		private IAppConfig $appConfig,
52
-		private LoggerInterface $logger,
53
-		private ICrypto $crypto,
54
-	) {
55
-	}
56
-
57
-	/**
58
-	 * @inheritdoc
59
-	 */
60
-	public function registerSchema(string $app, array $schema): void {
61
-		$this->appSchemas[$app] ??= [];
62
-
63
-		if (!$this->validateSchema($app, $schema)) {
64
-			throw new Exception('Invalid schema. Please check the logs for more details.');
65
-		}
66
-
67
-		foreach ($this->appSchemas[$app] as $otherSchema) {
68
-			if ($otherSchema['id'] === $schema['id']) {
69
-				throw new Exception('Duplicate form IDs detected: ' . $schema['id']);
70
-			}
71
-		}
72
-
73
-		$fieldIDs = array_map(fn ($field) => $field['id'], $schema['fields']);
74
-		$otherFieldIDs = array_merge(...array_map(fn ($schema) => array_map(fn ($field) => $field['id'], $schema['fields']), $this->appSchemas[$app]));
75
-		$intersectionFieldIDs = array_intersect($fieldIDs, $otherFieldIDs);
76
-		if (count($intersectionFieldIDs) > 0) {
77
-			throw new Exception('Non unique field IDs detected: ' . join(', ', $intersectionFieldIDs));
78
-		}
79
-
80
-		$this->appSchemas[$app][] = $schema;
81
-	}
82
-
83
-	/**
84
-	 * @inheritdoc
85
-	 */
86
-	public function loadSchemas(): void {
87
-		if (empty($this->declarativeForms)) {
88
-			$declarativeSettings = $this->coordinator->getRegistrationContext()->getDeclarativeSettings();
89
-			foreach ($declarativeSettings as $declarativeSetting) {
90
-				$app = $declarativeSetting->getAppId();
91
-				/** @var IDeclarativeSettingsForm $declarativeForm */
92
-				$declarativeForm = Server::get($declarativeSetting->getService());
93
-				$this->registerSchema($app, $declarativeForm->getSchema());
94
-				$this->declarativeForms[$app][] = $declarativeForm;
95
-			}
96
-		}
97
-
98
-		$this->eventDispatcher->dispatchTyped(new DeclarativeSettingsRegisterFormEvent($this));
99
-	}
100
-
101
-	/**
102
-	 * @inheritdoc
103
-	 */
104
-	public function getFormIDs(IUser $user, string $type, string $section): array {
105
-		$isAdmin = $this->groupManager->isAdmin($user->getUID());
106
-		/** @var array<string, list<string>> $formIds */
107
-		$formIds = [];
108
-
109
-		foreach ($this->appSchemas as $app => $schemas) {
110
-			$ids = [];
111
-			usort($schemas, [$this, 'sortSchemasByPriorityCallback']);
112
-			foreach ($schemas as $schema) {
113
-				if ($schema['section_type'] === DeclarativeSettingsTypes::SECTION_TYPE_ADMIN && !$isAdmin) {
114
-					continue;
115
-				}
116
-				if ($schema['section_type'] === $type && $schema['section_id'] === $section) {
117
-					$ids[] = $schema['id'];
118
-				}
119
-			}
120
-
121
-			if (!empty($ids)) {
122
-				$formIds[$app] = array_merge($formIds[$app] ?? [], $ids);
123
-			}
124
-		}
125
-
126
-		return $formIds;
127
-	}
128
-
129
-	/**
130
-	 * @inheritdoc
131
-	 * @throws Exception
132
-	 */
133
-	public function getFormsWithValues(IUser $user, ?string $type, ?string $section): array {
134
-		$isAdmin = $this->groupManager->isAdmin($user->getUID());
135
-		$forms = [];
136
-
137
-		foreach ($this->appSchemas as $app => $schemas) {
138
-			foreach ($schemas as $schema) {
139
-				if ($type !== null && $schema['section_type'] !== $type) {
140
-					continue;
141
-				}
142
-				if ($section !== null && $schema['section_id'] !== $section) {
143
-					continue;
144
-				}
145
-				// If listing all fields skip the admin fields which a non-admin user has no access to
146
-				if ($type === null && $schema['section_type'] === 'admin' && !$isAdmin) {
147
-					continue;
148
-				}
149
-
150
-				$s = $schema;
151
-				$s['app'] = $app;
152
-
153
-				foreach ($s['fields'] as &$field) {
154
-					$field['value'] = $this->getValue($user, $app, $schema['id'], $field['id']);
155
-				}
156
-				unset($field);
157
-
158
-				/** @var DeclarativeSettingsFormSchemaWithValues $s */
159
-				$forms[] = $s;
160
-			}
161
-		}
162
-
163
-		usort($forms, [$this, 'sortSchemasByPriorityCallback']);
164
-
165
-		return $forms;
166
-	}
167
-
168
-	private function sortSchemasByPriorityCallback(mixed $a, mixed $b): int {
169
-		if ($a['priority'] === $b['priority']) {
170
-			return 0;
171
-		}
172
-		return $a['priority'] > $b['priority'] ? -1 : 1;
173
-	}
174
-
175
-	/**
176
-	 * @return DeclarativeSettingsStorageType
177
-	 */
178
-	private function getStorageType(string $app, string $fieldId): string {
179
-		if (array_key_exists($app, $this->appSchemas)) {
180
-			foreach ($this->appSchemas[$app] as $schema) {
181
-				foreach ($schema['fields'] as $field) {
182
-					if ($field['id'] == $fieldId) {
183
-						if (array_key_exists('storage_type', $field)) {
184
-							return $field['storage_type'];
185
-						}
186
-					}
187
-				}
188
-
189
-				if (array_key_exists('storage_type', $schema)) {
190
-					return $schema['storage_type'];
191
-				}
192
-			}
193
-		}
194
-
195
-		return DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL;
196
-	}
197
-
198
-	/**
199
-	 * @return DeclarativeSettingsSectionType
200
-	 * @throws Exception
201
-	 */
202
-	private function getSectionType(string $app, string $fieldId): string {
203
-		if (array_key_exists($app, $this->appSchemas)) {
204
-			foreach ($this->appSchemas[$app] as $schema) {
205
-				foreach ($schema['fields'] as $field) {
206
-					if ($field['id'] == $fieldId) {
207
-						return $schema['section_type'];
208
-					}
209
-				}
210
-			}
211
-		}
212
-
213
-		throw new Exception('Unknown fieldId "' . $fieldId . '"');
214
-	}
215
-
216
-	/**
217
-	 * @psalm-param DeclarativeSettingsSectionType $sectionType
218
-	 * @throws NotAdminException
219
-	 */
220
-	private function assertAuthorized(IUser $user, string $sectionType): void {
221
-		if ($sectionType === 'admin' && !$this->groupManager->isAdmin($user->getUID())) {
222
-			throw new NotAdminException('Logged in user does not have permission to access these settings.');
223
-		}
224
-	}
225
-
226
-	/**
227
-	 * @return DeclarativeSettingsValueTypes
228
-	 * @throws Exception
229
-	 * @throws NotAdminException
230
-	 */
231
-	private function getValue(IUser $user, string $app, string $formId, string $fieldId): mixed {
232
-		$sectionType = $this->getSectionType($app, $fieldId);
233
-		$this->assertAuthorized($user, $sectionType);
234
-
235
-		$storageType = $this->getStorageType($app, $fieldId);
236
-		switch ($storageType) {
237
-			case DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL:
238
-				$form = $this->getForm($app, $formId);
239
-				if ($form !== null && $form instanceof IDeclarativeSettingsFormWithHandlers) {
240
-					return $form->getValue($fieldId, $user);
241
-				}
242
-				$event = new DeclarativeSettingsGetValueEvent($user, $app, $formId, $fieldId);
243
-				$this->eventDispatcher->dispatchTyped($event);
244
-				return $event->getValue();
245
-			case DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL:
246
-				return $this->getInternalValue($user, $app, $formId, $fieldId);
247
-			default:
248
-				throw new Exception('Unknown storage type "' . $storageType . '"');
249
-		}
250
-	}
251
-
252
-	/**
253
-	 * @inheritdoc
254
-	 */
255
-	public function setValue(IUser $user, string $app, string $formId, string $fieldId, mixed $value): void {
256
-		$sectionType = $this->getSectionType($app, $fieldId);
257
-		$this->assertAuthorized($user, $sectionType);
258
-
259
-		$storageType = $this->getStorageType($app, $fieldId);
260
-		switch ($storageType) {
261
-			case DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL:
262
-				$form = $this->getForm($app, $formId);
263
-				if ($form !== null && $form instanceof IDeclarativeSettingsFormWithHandlers) {
264
-					$form->setValue($fieldId, $value, $user);
265
-					break;
266
-				}
267
-				// fall back to event handling
268
-				$this->eventDispatcher->dispatchTyped(new DeclarativeSettingsSetValueEvent($user, $app, $formId, $fieldId, $value));
269
-				break;
270
-			case DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL:
271
-				$this->saveInternalValue($user, $app, $formId, $fieldId, $value);
272
-				break;
273
-			default:
274
-				throw new Exception('Unknown storage type "' . $storageType . '"');
275
-		}
276
-	}
277
-
278
-	/**
279
-	 * If a declarative setting was registered as a form and not just a schema
280
-	 * then this will yield the registering form.
281
-	 */
282
-	private function getForm(string $app, string $formId): ?IDeclarativeSettingsForm {
283
-		$allForms = $this->declarativeForms[$app] ?? [];
284
-		foreach ($allForms as $form) {
285
-			if ($form->getSchema()['id'] === $formId) {
286
-				return $form;
287
-			}
288
-		}
289
-		return null;
290
-	}
291
-
292
-	private function getInternalValue(IUser $user, string $app, string $formId, string $fieldId): mixed {
293
-		$sectionType = $this->getSectionType($app, $fieldId);
294
-		$defaultValue = $this->getDefaultValue($app, $formId, $fieldId);
295
-
296
-		$field = $this->getSchemaField($app, $formId, $fieldId);
297
-		$isSensitive = $field !== null && isset($field['sensitive']) && $field['sensitive'] === true;
298
-
299
-		switch ($sectionType) {
300
-			case DeclarativeSettingsTypes::SECTION_TYPE_ADMIN:
301
-				$value = $this->config->getAppValue($app, $fieldId, $defaultValue);
302
-				break;
303
-			case DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL:
304
-				$value = $this->config->getUserValue($user->getUID(), $app, $fieldId, $defaultValue);
305
-				break;
306
-			default:
307
-				throw new Exception('Unknown section type "' . $sectionType . '"');
308
-		}
309
-		if ($isSensitive && $value !== '') {
310
-			try {
311
-				$value = $this->crypto->decrypt($value);
312
-			} catch (Exception $e) {
313
-				$this->logger->warning('Failed to decrypt sensitive value for field {field} in app {app}: {message}', [
314
-					'field' => $fieldId,
315
-					'app' => $app,
316
-					'message' => $e->getMessage(),
317
-				]);
318
-				$value = $defaultValue;
319
-			}
320
-		}
321
-		return $value;
322
-	}
323
-
324
-	private function saveInternalValue(IUser $user, string $app, string $formId, string $fieldId, mixed $value): void {
325
-		$sectionType = $this->getSectionType($app, $fieldId);
326
-
327
-		$field = $this->getSchemaField($app, $formId, $fieldId);
328
-		if ($field !== null && isset($field['sensitive']) && $field['sensitive'] === true && $value !== '' && $value !== 'dummySecret') {
329
-			try {
330
-				$value = $this->crypto->encrypt($value);
331
-			} catch (Exception $e) {
332
-				$this->logger->warning('Failed to decrypt sensitive value for field {field} in app {app}: {message}', [
333
-					'field' => $fieldId,
334
-					'app' => $app,
335
-					'message' => $e->getMessage()]
336
-				);
337
-				throw new Exception('Failed to encrypt sensitive value');
338
-			}
339
-		}
340
-
341
-		switch ($sectionType) {
342
-			case DeclarativeSettingsTypes::SECTION_TYPE_ADMIN:
343
-				$this->appConfig->setValueString($app, $fieldId, $value);
344
-				break;
345
-			case DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL:
346
-				$this->config->setUserValue($user->getUID(), $app, $fieldId, $value);
347
-				break;
348
-			default:
349
-				throw new Exception('Unknown section type "' . $sectionType . '"');
350
-		}
351
-	}
352
-
353
-	private function getSchemaField(string $app, string $formId, string $fieldId): ?array {
354
-		$form = $this->getForm($app, $formId);
355
-		if ($form !== null) {
356
-			foreach ($form->getSchema()['fields'] as $field) {
357
-				if ($field['id'] === $fieldId) {
358
-					return $field;
359
-				}
360
-			}
361
-		}
362
-		foreach ($this->appSchemas[$app] ?? [] as $schema) {
363
-			if ($schema['id'] === $formId) {
364
-				foreach ($schema['fields'] as $field) {
365
-					if ($field['id'] === $fieldId) {
366
-						return $field;
367
-					}
368
-				}
369
-			}
370
-		}
371
-		return null;
372
-	}
373
-
374
-	private function getDefaultValue(string $app, string $formId, string $fieldId): mixed {
375
-		foreach ($this->appSchemas[$app] as $schema) {
376
-			if ($schema['id'] === $formId) {
377
-				foreach ($schema['fields'] as $field) {
378
-					if ($field['id'] === $fieldId) {
379
-						if (isset($field['default'])) {
380
-							if (is_array($field['default'])) {
381
-								return json_encode($field['default']);
382
-							}
383
-							return $field['default'];
384
-						}
385
-					}
386
-				}
387
-			}
388
-		}
389
-		return null;
390
-	}
391
-
392
-	private function validateSchema(string $appId, array $schema): bool {
393
-		if (!isset($schema['id'])) {
394
-			$this->logger->warning('Attempt to register a declarative settings schema with no id', ['app' => $appId]);
395
-			return false;
396
-		}
397
-		$formId = $schema['id'];
398
-		if (!isset($schema['section_type'])) {
399
-			$this->logger->warning('Declarative settings: missing section_type', ['app' => $appId, 'form_id' => $formId]);
400
-			return false;
401
-		}
402
-		if (!in_array($schema['section_type'], [DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL])) {
403
-			$this->logger->warning('Declarative settings: invalid section_type', ['app' => $appId, 'form_id' => $formId, 'section_type' => $schema['section_type']]);
404
-			return false;
405
-		}
406
-		if (!isset($schema['section_id'])) {
407
-			$this->logger->warning('Declarative settings: missing section_id', ['app' => $appId, 'form_id' => $formId]);
408
-			return false;
409
-		}
410
-		if (!isset($schema['storage_type'])) {
411
-			$this->logger->warning('Declarative settings: missing storage_type', ['app' => $appId, 'form_id' => $formId]);
412
-			return false;
413
-		}
414
-		if (!in_array($schema['storage_type'], [DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL, DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL])) {
415
-			$this->logger->warning('Declarative settings: invalid storage_type', ['app' => $appId, 'form_id' => $formId, 'storage_type' => $schema['storage_type']]);
416
-			return false;
417
-		}
418
-		if (!isset($schema['title'])) {
419
-			$this->logger->warning('Declarative settings: missing title', ['app' => $appId, 'form_id' => $formId]);
420
-			return false;
421
-		}
422
-		if (!isset($schema['fields']) || !is_array($schema['fields'])) {
423
-			$this->logger->warning('Declarative settings: missing or invalid fields', ['app' => $appId, 'form_id' => $formId]);
424
-			return false;
425
-		}
426
-		foreach ($schema['fields'] as $field) {
427
-			if (!isset($field['id'])) {
428
-				$this->logger->warning('Declarative settings: missing field id', ['app' => $appId, 'form_id' => $formId, 'field' => $field]);
429
-				return false;
430
-			}
431
-			$fieldId = $field['id'];
432
-			if (!isset($field['title'])) {
433
-				$this->logger->warning('Declarative settings: missing field title', ['app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId]);
434
-				return false;
435
-			}
436
-			if (!isset($field['type'])) {
437
-				$this->logger->warning('Declarative settings: missing field type', ['app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId]);
438
-				return false;
439
-			}
440
-			if (!in_array($field['type'], [
441
-				DeclarativeSettingsTypes::MULTI_SELECT, DeclarativeSettingsTypes::MULTI_CHECKBOX, DeclarativeSettingsTypes::RADIO,
442
-				DeclarativeSettingsTypes::SELECT, DeclarativeSettingsTypes::CHECKBOX,
443
-				DeclarativeSettingsTypes::URL, DeclarativeSettingsTypes::EMAIL, DeclarativeSettingsTypes::NUMBER,
444
-				DeclarativeSettingsTypes::TEL, DeclarativeSettingsTypes::TEXT, DeclarativeSettingsTypes::PASSWORD,
445
-			])) {
446
-				$this->logger->warning('Declarative settings: invalid field type', [
447
-					'app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId, 'type' => $field['type'],
448
-				]);
449
-				return false;
450
-			}
451
-			if (isset($field['sensitive']) && $field['sensitive'] === true && !in_array($field['type'], [DeclarativeSettingsTypes::TEXT, DeclarativeSettingsTypes::PASSWORD])) {
452
-				$this->logger->warning('Declarative settings: sensitive field type is supported only for TEXT and PASSWORD types ({app}, {form_id}, {field_id})', [
453
-					'app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId,
454
-				]);
455
-				return false;
456
-			}
457
-			if (!$this->validateField($appId, $formId, $field)) {
458
-				return false;
459
-			}
460
-		}
461
-
462
-		return true;
463
-	}
464
-
465
-	private function validateField(string $appId, string $formId, array $field): bool {
466
-		$fieldId = $field['id'];
467
-		if (in_array($field['type'], [
468
-			DeclarativeSettingsTypes::MULTI_SELECT, DeclarativeSettingsTypes::MULTI_CHECKBOX, DeclarativeSettingsTypes::RADIO,
469
-			DeclarativeSettingsTypes::SELECT
470
-		])) {
471
-			if (!isset($field['options'])) {
472
-				$this->logger->warning('Declarative settings: missing field options', ['app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId]);
473
-				return false;
474
-			}
475
-			if (!is_array($field['options'])) {
476
-				$this->logger->warning('Declarative settings: field options should be an array', ['app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId]);
477
-				return false;
478
-			}
479
-		}
480
-		return true;
481
-	}
38
+    /** @var array<string, list<IDeclarativeSettingsForm>> */
39
+    private array $declarativeForms = [];
40
+
41
+    /**
42
+     * @var array<string, list<DeclarativeSettingsFormSchemaWithoutValues>>
43
+     */
44
+    private array $appSchemas = [];
45
+
46
+    public function __construct(
47
+        private IEventDispatcher $eventDispatcher,
48
+        private IGroupManager $groupManager,
49
+        private Coordinator $coordinator,
50
+        private IConfig $config,
51
+        private IAppConfig $appConfig,
52
+        private LoggerInterface $logger,
53
+        private ICrypto $crypto,
54
+    ) {
55
+    }
56
+
57
+    /**
58
+     * @inheritdoc
59
+     */
60
+    public function registerSchema(string $app, array $schema): void {
61
+        $this->appSchemas[$app] ??= [];
62
+
63
+        if (!$this->validateSchema($app, $schema)) {
64
+            throw new Exception('Invalid schema. Please check the logs for more details.');
65
+        }
66
+
67
+        foreach ($this->appSchemas[$app] as $otherSchema) {
68
+            if ($otherSchema['id'] === $schema['id']) {
69
+                throw new Exception('Duplicate form IDs detected: ' . $schema['id']);
70
+            }
71
+        }
72
+
73
+        $fieldIDs = array_map(fn ($field) => $field['id'], $schema['fields']);
74
+        $otherFieldIDs = array_merge(...array_map(fn ($schema) => array_map(fn ($field) => $field['id'], $schema['fields']), $this->appSchemas[$app]));
75
+        $intersectionFieldIDs = array_intersect($fieldIDs, $otherFieldIDs);
76
+        if (count($intersectionFieldIDs) > 0) {
77
+            throw new Exception('Non unique field IDs detected: ' . join(', ', $intersectionFieldIDs));
78
+        }
79
+
80
+        $this->appSchemas[$app][] = $schema;
81
+    }
82
+
83
+    /**
84
+     * @inheritdoc
85
+     */
86
+    public function loadSchemas(): void {
87
+        if (empty($this->declarativeForms)) {
88
+            $declarativeSettings = $this->coordinator->getRegistrationContext()->getDeclarativeSettings();
89
+            foreach ($declarativeSettings as $declarativeSetting) {
90
+                $app = $declarativeSetting->getAppId();
91
+                /** @var IDeclarativeSettingsForm $declarativeForm */
92
+                $declarativeForm = Server::get($declarativeSetting->getService());
93
+                $this->registerSchema($app, $declarativeForm->getSchema());
94
+                $this->declarativeForms[$app][] = $declarativeForm;
95
+            }
96
+        }
97
+
98
+        $this->eventDispatcher->dispatchTyped(new DeclarativeSettingsRegisterFormEvent($this));
99
+    }
100
+
101
+    /**
102
+     * @inheritdoc
103
+     */
104
+    public function getFormIDs(IUser $user, string $type, string $section): array {
105
+        $isAdmin = $this->groupManager->isAdmin($user->getUID());
106
+        /** @var array<string, list<string>> $formIds */
107
+        $formIds = [];
108
+
109
+        foreach ($this->appSchemas as $app => $schemas) {
110
+            $ids = [];
111
+            usort($schemas, [$this, 'sortSchemasByPriorityCallback']);
112
+            foreach ($schemas as $schema) {
113
+                if ($schema['section_type'] === DeclarativeSettingsTypes::SECTION_TYPE_ADMIN && !$isAdmin) {
114
+                    continue;
115
+                }
116
+                if ($schema['section_type'] === $type && $schema['section_id'] === $section) {
117
+                    $ids[] = $schema['id'];
118
+                }
119
+            }
120
+
121
+            if (!empty($ids)) {
122
+                $formIds[$app] = array_merge($formIds[$app] ?? [], $ids);
123
+            }
124
+        }
125
+
126
+        return $formIds;
127
+    }
128
+
129
+    /**
130
+     * @inheritdoc
131
+     * @throws Exception
132
+     */
133
+    public function getFormsWithValues(IUser $user, ?string $type, ?string $section): array {
134
+        $isAdmin = $this->groupManager->isAdmin($user->getUID());
135
+        $forms = [];
136
+
137
+        foreach ($this->appSchemas as $app => $schemas) {
138
+            foreach ($schemas as $schema) {
139
+                if ($type !== null && $schema['section_type'] !== $type) {
140
+                    continue;
141
+                }
142
+                if ($section !== null && $schema['section_id'] !== $section) {
143
+                    continue;
144
+                }
145
+                // If listing all fields skip the admin fields which a non-admin user has no access to
146
+                if ($type === null && $schema['section_type'] === 'admin' && !$isAdmin) {
147
+                    continue;
148
+                }
149
+
150
+                $s = $schema;
151
+                $s['app'] = $app;
152
+
153
+                foreach ($s['fields'] as &$field) {
154
+                    $field['value'] = $this->getValue($user, $app, $schema['id'], $field['id']);
155
+                }
156
+                unset($field);
157
+
158
+                /** @var DeclarativeSettingsFormSchemaWithValues $s */
159
+                $forms[] = $s;
160
+            }
161
+        }
162
+
163
+        usort($forms, [$this, 'sortSchemasByPriorityCallback']);
164
+
165
+        return $forms;
166
+    }
167
+
168
+    private function sortSchemasByPriorityCallback(mixed $a, mixed $b): int {
169
+        if ($a['priority'] === $b['priority']) {
170
+            return 0;
171
+        }
172
+        return $a['priority'] > $b['priority'] ? -1 : 1;
173
+    }
174
+
175
+    /**
176
+     * @return DeclarativeSettingsStorageType
177
+     */
178
+    private function getStorageType(string $app, string $fieldId): string {
179
+        if (array_key_exists($app, $this->appSchemas)) {
180
+            foreach ($this->appSchemas[$app] as $schema) {
181
+                foreach ($schema['fields'] as $field) {
182
+                    if ($field['id'] == $fieldId) {
183
+                        if (array_key_exists('storage_type', $field)) {
184
+                            return $field['storage_type'];
185
+                        }
186
+                    }
187
+                }
188
+
189
+                if (array_key_exists('storage_type', $schema)) {
190
+                    return $schema['storage_type'];
191
+                }
192
+            }
193
+        }
194
+
195
+        return DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL;
196
+    }
197
+
198
+    /**
199
+     * @return DeclarativeSettingsSectionType
200
+     * @throws Exception
201
+     */
202
+    private function getSectionType(string $app, string $fieldId): string {
203
+        if (array_key_exists($app, $this->appSchemas)) {
204
+            foreach ($this->appSchemas[$app] as $schema) {
205
+                foreach ($schema['fields'] as $field) {
206
+                    if ($field['id'] == $fieldId) {
207
+                        return $schema['section_type'];
208
+                    }
209
+                }
210
+            }
211
+        }
212
+
213
+        throw new Exception('Unknown fieldId "' . $fieldId . '"');
214
+    }
215
+
216
+    /**
217
+     * @psalm-param DeclarativeSettingsSectionType $sectionType
218
+     * @throws NotAdminException
219
+     */
220
+    private function assertAuthorized(IUser $user, string $sectionType): void {
221
+        if ($sectionType === 'admin' && !$this->groupManager->isAdmin($user->getUID())) {
222
+            throw new NotAdminException('Logged in user does not have permission to access these settings.');
223
+        }
224
+    }
225
+
226
+    /**
227
+     * @return DeclarativeSettingsValueTypes
228
+     * @throws Exception
229
+     * @throws NotAdminException
230
+     */
231
+    private function getValue(IUser $user, string $app, string $formId, string $fieldId): mixed {
232
+        $sectionType = $this->getSectionType($app, $fieldId);
233
+        $this->assertAuthorized($user, $sectionType);
234
+
235
+        $storageType = $this->getStorageType($app, $fieldId);
236
+        switch ($storageType) {
237
+            case DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL:
238
+                $form = $this->getForm($app, $formId);
239
+                if ($form !== null && $form instanceof IDeclarativeSettingsFormWithHandlers) {
240
+                    return $form->getValue($fieldId, $user);
241
+                }
242
+                $event = new DeclarativeSettingsGetValueEvent($user, $app, $formId, $fieldId);
243
+                $this->eventDispatcher->dispatchTyped($event);
244
+                return $event->getValue();
245
+            case DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL:
246
+                return $this->getInternalValue($user, $app, $formId, $fieldId);
247
+            default:
248
+                throw new Exception('Unknown storage type "' . $storageType . '"');
249
+        }
250
+    }
251
+
252
+    /**
253
+     * @inheritdoc
254
+     */
255
+    public function setValue(IUser $user, string $app, string $formId, string $fieldId, mixed $value): void {
256
+        $sectionType = $this->getSectionType($app, $fieldId);
257
+        $this->assertAuthorized($user, $sectionType);
258
+
259
+        $storageType = $this->getStorageType($app, $fieldId);
260
+        switch ($storageType) {
261
+            case DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL:
262
+                $form = $this->getForm($app, $formId);
263
+                if ($form !== null && $form instanceof IDeclarativeSettingsFormWithHandlers) {
264
+                    $form->setValue($fieldId, $value, $user);
265
+                    break;
266
+                }
267
+                // fall back to event handling
268
+                $this->eventDispatcher->dispatchTyped(new DeclarativeSettingsSetValueEvent($user, $app, $formId, $fieldId, $value));
269
+                break;
270
+            case DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL:
271
+                $this->saveInternalValue($user, $app, $formId, $fieldId, $value);
272
+                break;
273
+            default:
274
+                throw new Exception('Unknown storage type "' . $storageType . '"');
275
+        }
276
+    }
277
+
278
+    /**
279
+     * If a declarative setting was registered as a form and not just a schema
280
+     * then this will yield the registering form.
281
+     */
282
+    private function getForm(string $app, string $formId): ?IDeclarativeSettingsForm {
283
+        $allForms = $this->declarativeForms[$app] ?? [];
284
+        foreach ($allForms as $form) {
285
+            if ($form->getSchema()['id'] === $formId) {
286
+                return $form;
287
+            }
288
+        }
289
+        return null;
290
+    }
291
+
292
+    private function getInternalValue(IUser $user, string $app, string $formId, string $fieldId): mixed {
293
+        $sectionType = $this->getSectionType($app, $fieldId);
294
+        $defaultValue = $this->getDefaultValue($app, $formId, $fieldId);
295
+
296
+        $field = $this->getSchemaField($app, $formId, $fieldId);
297
+        $isSensitive = $field !== null && isset($field['sensitive']) && $field['sensitive'] === true;
298
+
299
+        switch ($sectionType) {
300
+            case DeclarativeSettingsTypes::SECTION_TYPE_ADMIN:
301
+                $value = $this->config->getAppValue($app, $fieldId, $defaultValue);
302
+                break;
303
+            case DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL:
304
+                $value = $this->config->getUserValue($user->getUID(), $app, $fieldId, $defaultValue);
305
+                break;
306
+            default:
307
+                throw new Exception('Unknown section type "' . $sectionType . '"');
308
+        }
309
+        if ($isSensitive && $value !== '') {
310
+            try {
311
+                $value = $this->crypto->decrypt($value);
312
+            } catch (Exception $e) {
313
+                $this->logger->warning('Failed to decrypt sensitive value for field {field} in app {app}: {message}', [
314
+                    'field' => $fieldId,
315
+                    'app' => $app,
316
+                    'message' => $e->getMessage(),
317
+                ]);
318
+                $value = $defaultValue;
319
+            }
320
+        }
321
+        return $value;
322
+    }
323
+
324
+    private function saveInternalValue(IUser $user, string $app, string $formId, string $fieldId, mixed $value): void {
325
+        $sectionType = $this->getSectionType($app, $fieldId);
326
+
327
+        $field = $this->getSchemaField($app, $formId, $fieldId);
328
+        if ($field !== null && isset($field['sensitive']) && $field['sensitive'] === true && $value !== '' && $value !== 'dummySecret') {
329
+            try {
330
+                $value = $this->crypto->encrypt($value);
331
+            } catch (Exception $e) {
332
+                $this->logger->warning('Failed to decrypt sensitive value for field {field} in app {app}: {message}', [
333
+                    'field' => $fieldId,
334
+                    'app' => $app,
335
+                    'message' => $e->getMessage()]
336
+                );
337
+                throw new Exception('Failed to encrypt sensitive value');
338
+            }
339
+        }
340
+
341
+        switch ($sectionType) {
342
+            case DeclarativeSettingsTypes::SECTION_TYPE_ADMIN:
343
+                $this->appConfig->setValueString($app, $fieldId, $value);
344
+                break;
345
+            case DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL:
346
+                $this->config->setUserValue($user->getUID(), $app, $fieldId, $value);
347
+                break;
348
+            default:
349
+                throw new Exception('Unknown section type "' . $sectionType . '"');
350
+        }
351
+    }
352
+
353
+    private function getSchemaField(string $app, string $formId, string $fieldId): ?array {
354
+        $form = $this->getForm($app, $formId);
355
+        if ($form !== null) {
356
+            foreach ($form->getSchema()['fields'] as $field) {
357
+                if ($field['id'] === $fieldId) {
358
+                    return $field;
359
+                }
360
+            }
361
+        }
362
+        foreach ($this->appSchemas[$app] ?? [] as $schema) {
363
+            if ($schema['id'] === $formId) {
364
+                foreach ($schema['fields'] as $field) {
365
+                    if ($field['id'] === $fieldId) {
366
+                        return $field;
367
+                    }
368
+                }
369
+            }
370
+        }
371
+        return null;
372
+    }
373
+
374
+    private function getDefaultValue(string $app, string $formId, string $fieldId): mixed {
375
+        foreach ($this->appSchemas[$app] as $schema) {
376
+            if ($schema['id'] === $formId) {
377
+                foreach ($schema['fields'] as $field) {
378
+                    if ($field['id'] === $fieldId) {
379
+                        if (isset($field['default'])) {
380
+                            if (is_array($field['default'])) {
381
+                                return json_encode($field['default']);
382
+                            }
383
+                            return $field['default'];
384
+                        }
385
+                    }
386
+                }
387
+            }
388
+        }
389
+        return null;
390
+    }
391
+
392
+    private function validateSchema(string $appId, array $schema): bool {
393
+        if (!isset($schema['id'])) {
394
+            $this->logger->warning('Attempt to register a declarative settings schema with no id', ['app' => $appId]);
395
+            return false;
396
+        }
397
+        $formId = $schema['id'];
398
+        if (!isset($schema['section_type'])) {
399
+            $this->logger->warning('Declarative settings: missing section_type', ['app' => $appId, 'form_id' => $formId]);
400
+            return false;
401
+        }
402
+        if (!in_array($schema['section_type'], [DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL])) {
403
+            $this->logger->warning('Declarative settings: invalid section_type', ['app' => $appId, 'form_id' => $formId, 'section_type' => $schema['section_type']]);
404
+            return false;
405
+        }
406
+        if (!isset($schema['section_id'])) {
407
+            $this->logger->warning('Declarative settings: missing section_id', ['app' => $appId, 'form_id' => $formId]);
408
+            return false;
409
+        }
410
+        if (!isset($schema['storage_type'])) {
411
+            $this->logger->warning('Declarative settings: missing storage_type', ['app' => $appId, 'form_id' => $formId]);
412
+            return false;
413
+        }
414
+        if (!in_array($schema['storage_type'], [DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL, DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL])) {
415
+            $this->logger->warning('Declarative settings: invalid storage_type', ['app' => $appId, 'form_id' => $formId, 'storage_type' => $schema['storage_type']]);
416
+            return false;
417
+        }
418
+        if (!isset($schema['title'])) {
419
+            $this->logger->warning('Declarative settings: missing title', ['app' => $appId, 'form_id' => $formId]);
420
+            return false;
421
+        }
422
+        if (!isset($schema['fields']) || !is_array($schema['fields'])) {
423
+            $this->logger->warning('Declarative settings: missing or invalid fields', ['app' => $appId, 'form_id' => $formId]);
424
+            return false;
425
+        }
426
+        foreach ($schema['fields'] as $field) {
427
+            if (!isset($field['id'])) {
428
+                $this->logger->warning('Declarative settings: missing field id', ['app' => $appId, 'form_id' => $formId, 'field' => $field]);
429
+                return false;
430
+            }
431
+            $fieldId = $field['id'];
432
+            if (!isset($field['title'])) {
433
+                $this->logger->warning('Declarative settings: missing field title', ['app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId]);
434
+                return false;
435
+            }
436
+            if (!isset($field['type'])) {
437
+                $this->logger->warning('Declarative settings: missing field type', ['app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId]);
438
+                return false;
439
+            }
440
+            if (!in_array($field['type'], [
441
+                DeclarativeSettingsTypes::MULTI_SELECT, DeclarativeSettingsTypes::MULTI_CHECKBOX, DeclarativeSettingsTypes::RADIO,
442
+                DeclarativeSettingsTypes::SELECT, DeclarativeSettingsTypes::CHECKBOX,
443
+                DeclarativeSettingsTypes::URL, DeclarativeSettingsTypes::EMAIL, DeclarativeSettingsTypes::NUMBER,
444
+                DeclarativeSettingsTypes::TEL, DeclarativeSettingsTypes::TEXT, DeclarativeSettingsTypes::PASSWORD,
445
+            ])) {
446
+                $this->logger->warning('Declarative settings: invalid field type', [
447
+                    'app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId, 'type' => $field['type'],
448
+                ]);
449
+                return false;
450
+            }
451
+            if (isset($field['sensitive']) && $field['sensitive'] === true && !in_array($field['type'], [DeclarativeSettingsTypes::TEXT, DeclarativeSettingsTypes::PASSWORD])) {
452
+                $this->logger->warning('Declarative settings: sensitive field type is supported only for TEXT and PASSWORD types ({app}, {form_id}, {field_id})', [
453
+                    'app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId,
454
+                ]);
455
+                return false;
456
+            }
457
+            if (!$this->validateField($appId, $formId, $field)) {
458
+                return false;
459
+            }
460
+        }
461
+
462
+        return true;
463
+    }
464
+
465
+    private function validateField(string $appId, string $formId, array $field): bool {
466
+        $fieldId = $field['id'];
467
+        if (in_array($field['type'], [
468
+            DeclarativeSettingsTypes::MULTI_SELECT, DeclarativeSettingsTypes::MULTI_CHECKBOX, DeclarativeSettingsTypes::RADIO,
469
+            DeclarativeSettingsTypes::SELECT
470
+        ])) {
471
+            if (!isset($field['options'])) {
472
+                $this->logger->warning('Declarative settings: missing field options', ['app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId]);
473
+                return false;
474
+            }
475
+            if (!is_array($field['options'])) {
476
+                $this->logger->warning('Declarative settings: field options should be an array', ['app' => $appId, 'form_id' => $formId, 'field_id' => $fieldId]);
477
+                return false;
478
+            }
479
+        }
480
+        return true;
481
+    }
482 482
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -66,7 +66,7 @@  discard block
 block discarded – undo
66 66
 
67 67
 		foreach ($this->appSchemas[$app] as $otherSchema) {
68 68
 			if ($otherSchema['id'] === $schema['id']) {
69
-				throw new Exception('Duplicate form IDs detected: ' . $schema['id']);
69
+				throw new Exception('Duplicate form IDs detected: '.$schema['id']);
70 70
 			}
71 71
 		}
72 72
 
@@ -74,7 +74,7 @@  discard block
 block discarded – undo
74 74
 		$otherFieldIDs = array_merge(...array_map(fn ($schema) => array_map(fn ($field) => $field['id'], $schema['fields']), $this->appSchemas[$app]));
75 75
 		$intersectionFieldIDs = array_intersect($fieldIDs, $otherFieldIDs);
76 76
 		if (count($intersectionFieldIDs) > 0) {
77
-			throw new Exception('Non unique field IDs detected: ' . join(', ', $intersectionFieldIDs));
77
+			throw new Exception('Non unique field IDs detected: '.join(', ', $intersectionFieldIDs));
78 78
 		}
79 79
 
80 80
 		$this->appSchemas[$app][] = $schema;
@@ -210,7 +210,7 @@  discard block
 block discarded – undo
210 210
 			}
211 211
 		}
212 212
 
213
-		throw new Exception('Unknown fieldId "' . $fieldId . '"');
213
+		throw new Exception('Unknown fieldId "'.$fieldId.'"');
214 214
 	}
215 215
 
216 216
 	/**
@@ -245,7 +245,7 @@  discard block
 block discarded – undo
245 245
 			case DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL:
246 246
 				return $this->getInternalValue($user, $app, $formId, $fieldId);
247 247
 			default:
248
-				throw new Exception('Unknown storage type "' . $storageType . '"');
248
+				throw new Exception('Unknown storage type "'.$storageType.'"');
249 249
 		}
250 250
 	}
251 251
 
@@ -271,7 +271,7 @@  discard block
 block discarded – undo
271 271
 				$this->saveInternalValue($user, $app, $formId, $fieldId, $value);
272 272
 				break;
273 273
 			default:
274
-				throw new Exception('Unknown storage type "' . $storageType . '"');
274
+				throw new Exception('Unknown storage type "'.$storageType.'"');
275 275
 		}
276 276
 	}
277 277
 
@@ -304,7 +304,7 @@  discard block
 block discarded – undo
304 304
 				$value = $this->config->getUserValue($user->getUID(), $app, $fieldId, $defaultValue);
305 305
 				break;
306 306
 			default:
307
-				throw new Exception('Unknown section type "' . $sectionType . '"');
307
+				throw new Exception('Unknown section type "'.$sectionType.'"');
308 308
 		}
309 309
 		if ($isSensitive && $value !== '') {
310 310
 			try {
@@ -346,7 +346,7 @@  discard block
 block discarded – undo
346 346
 				$this->config->setUserValue($user->getUID(), $app, $fieldId, $value);
347 347
 				break;
348 348
 			default:
349
-				throw new Exception('Unknown section type "' . $sectionType . '"');
349
+				throw new Exception('Unknown section type "'.$sectionType.'"');
350 350
 		}
351 351
 	}
352 352
 
Please login to merge, or discard this patch.
apps/settings/lib/Controller/CommonSettingsTrait.php 1 patch
Indentation   +159 added lines, -159 removed lines patch added patch discarded remove patch
@@ -28,163 +28,163 @@
 block discarded – undo
28 28
  */
29 29
 trait CommonSettingsTrait {
30 30
 
31
-	/** @var ISettingsManager */
32
-	private $settingsManager;
33
-
34
-	/** @var INavigationManager */
35
-	private $navigationManager;
36
-
37
-	/** @var IUserSession */
38
-	private $userSession;
39
-
40
-	/** @var IGroupManager */
41
-	private $groupManager;
42
-
43
-	/** @var ISubAdmin */
44
-	private $subAdmin;
45
-
46
-	private IDeclarativeManager $declarativeSettingsManager;
47
-
48
-	/** @var IInitialState */
49
-	private $initialState;
50
-
51
-	/**
52
-	 * @return array{forms: array{personal: array, admin: array}}
53
-	 */
54
-	private function getNavigationParameters(string $currentType, string $currentSection): array {
55
-		return [
56
-			'forms' => [
57
-				'personal' => $this->formatPersonalSections($currentType, $currentSection),
58
-				'admin' => $this->formatAdminSections($currentType, $currentSection),
59
-			],
60
-		];
61
-	}
62
-
63
-	/**
64
-	 * @param IIconSection[][] $sections
65
-	 * @psalm-param 'admin'|'personal' $type
66
-	 * @return list<array{anchor: string, section-name: string, active: bool, icon: string}>
67
-	 */
68
-	protected function formatSections(array $sections, string $currentSection, string $type, string $currentType): array {
69
-		$templateParameters = [];
70
-		foreach ($sections as $prioritizedSections) {
71
-			foreach ($prioritizedSections as $section) {
72
-				if ($type === 'admin') {
73
-					$settings = $this->settingsManager->getAllowedAdminSettings($section->getID(), $this->userSession->getUser());
74
-				} elseif ($type === 'personal') {
75
-					$settings = $this->settingsManager->getPersonalSettings($section->getID());
76
-				}
77
-
78
-				/** @psalm-suppress PossiblyNullArgument */
79
-				$declarativeFormIDs = $this->declarativeSettingsManager->getFormIDs($this->userSession->getUser(), $type, $section->getID());
80
-
81
-				if (empty($settings) && empty($declarativeFormIDs)) {
82
-					continue;
83
-				}
84
-
85
-				$icon = $section->getIcon();
86
-
87
-				$active = $section->getID() === $currentSection
88
-					&& $type === $currentType;
89
-
90
-				$templateParameters[] = [
91
-					'anchor' => $section->getID(),
92
-					'section-name' => $section->getName(),
93
-					'active' => $active,
94
-					'icon' => $icon,
95
-				];
96
-			}
97
-		}
98
-		return $templateParameters;
99
-	}
100
-
101
-	protected function formatPersonalSections(string $currentType, string $currentSection): array {
102
-		$sections = $this->settingsManager->getPersonalSections();
103
-		return $this->formatSections($sections, $currentSection, 'personal', $currentType);
104
-	}
105
-
106
-	protected function formatAdminSections(string $currentType, string $currentSection): array {
107
-		$sections = $this->settingsManager->getAdminSections();
108
-		return $this->formatSections($sections, $currentSection, 'admin', $currentType);
109
-	}
110
-
111
-	/**
112
-	 * @param list<ISettings> $settings
113
-	 * @param list<DeclarativeSettingsFormSchemaWithValues> $declarativeSettings
114
-	 * @return array{content: string}
115
-	 */
116
-	private function formatSettings(array $settings, array $declarativeSettings): array {
117
-		$settings = array_merge($settings, $declarativeSettings);
118
-
119
-		usort($settings, function ($first, $second) {
120
-			$priorityOne = $first instanceof ISettings ? $first->getPriority() : $first['priority'];
121
-			$priorityTwo = $second instanceof ISettings ? $second->getPriority() : $second['priority'];
122
-			return $priorityOne - $priorityTwo;
123
-		});
124
-
125
-		$html = '';
126
-		foreach ($settings as $setting) {
127
-			if ($setting instanceof ISettings) {
128
-				$form = $setting->getForm();
129
-				$html .= $form->renderAs('')->render();
130
-			} else {
131
-				$html .= '<div id="' . $setting['app'] . '_' . $setting['id'] . '"></div>';
132
-			}
133
-		}
134
-		return ['content' => $html];
135
-	}
136
-
137
-	/**
138
-	 * @psalm-param 'admin'|'personal' $type
139
-	 */
140
-	private function getIndexResponse(string $type, string $section): TemplateResponse {
141
-		$user = $this->userSession->getUser();
142
-		assert($user !== null, 'No user logged in for settings');
143
-
144
-		$this->declarativeSettingsManager->loadSchemas();
145
-		$declarativeSettings = $this->declarativeSettingsManager->getFormsWithValues($user, $type, $section);
146
-
147
-		foreach ($declarativeSettings as &$form) {
148
-			foreach ($form['fields'] as &$field) {
149
-				if (isset($field['sensitive']) && $field['sensitive'] === true && !empty($field['value'])) {
150
-					$field['value'] = 'dummySecret';
151
-				}
152
-			}
153
-		}
154
-
155
-		if ($type === 'personal') {
156
-			$settings = array_values($this->settingsManager->getPersonalSettings($section));
157
-			if ($section === 'theming') {
158
-				$this->navigationManager->setActiveEntry('accessibility_settings');
159
-			} else {
160
-				$this->navigationManager->setActiveEntry('settings');
161
-			}
162
-		} elseif ($type === 'admin') {
163
-			$settings = array_values($this->settingsManager->getAllowedAdminSettings($section, $user));
164
-			if (empty($settings) && empty($declarativeSettings)) {
165
-				throw new NotAdminException('Logged in user does not have permission to access these settings.');
166
-			}
167
-			$this->navigationManager->setActiveEntry('admin_settings');
168
-		} else {
169
-			throw new InvalidArgumentException('$type must be either "admin" or "personal"');
170
-		}
171
-
172
-		if (!empty($declarativeSettings)) {
173
-			Util::addScript(Application::APP_ID, 'declarative-settings-forms');
174
-			$this->initialState->provideInitialState('declarative-settings-forms', $declarativeSettings);
175
-		}
176
-
177
-		$settings = array_merge(...$settings);
178
-		$templateParams = $this->formatSettings($settings, $declarativeSettings);
179
-		$templateParams = array_merge($templateParams, $this->getNavigationParameters($type, $section));
180
-
181
-		$activeSection = $this->settingsManager->getSection($type, $section);
182
-		if ($activeSection) {
183
-			$templateParams['pageTitle'] = $activeSection->getName();
184
-			$templateParams['activeSectionId'] = $activeSection->getID();
185
-			$templateParams['activeSectionType'] = $type;
186
-		}
187
-
188
-		return new TemplateResponse('settings', 'settings/frame', $templateParams);
189
-	}
31
+    /** @var ISettingsManager */
32
+    private $settingsManager;
33
+
34
+    /** @var INavigationManager */
35
+    private $navigationManager;
36
+
37
+    /** @var IUserSession */
38
+    private $userSession;
39
+
40
+    /** @var IGroupManager */
41
+    private $groupManager;
42
+
43
+    /** @var ISubAdmin */
44
+    private $subAdmin;
45
+
46
+    private IDeclarativeManager $declarativeSettingsManager;
47
+
48
+    /** @var IInitialState */
49
+    private $initialState;
50
+
51
+    /**
52
+     * @return array{forms: array{personal: array, admin: array}}
53
+     */
54
+    private function getNavigationParameters(string $currentType, string $currentSection): array {
55
+        return [
56
+            'forms' => [
57
+                'personal' => $this->formatPersonalSections($currentType, $currentSection),
58
+                'admin' => $this->formatAdminSections($currentType, $currentSection),
59
+            ],
60
+        ];
61
+    }
62
+
63
+    /**
64
+     * @param IIconSection[][] $sections
65
+     * @psalm-param 'admin'|'personal' $type
66
+     * @return list<array{anchor: string, section-name: string, active: bool, icon: string}>
67
+     */
68
+    protected function formatSections(array $sections, string $currentSection, string $type, string $currentType): array {
69
+        $templateParameters = [];
70
+        foreach ($sections as $prioritizedSections) {
71
+            foreach ($prioritizedSections as $section) {
72
+                if ($type === 'admin') {
73
+                    $settings = $this->settingsManager->getAllowedAdminSettings($section->getID(), $this->userSession->getUser());
74
+                } elseif ($type === 'personal') {
75
+                    $settings = $this->settingsManager->getPersonalSettings($section->getID());
76
+                }
77
+
78
+                /** @psalm-suppress PossiblyNullArgument */
79
+                $declarativeFormIDs = $this->declarativeSettingsManager->getFormIDs($this->userSession->getUser(), $type, $section->getID());
80
+
81
+                if (empty($settings) && empty($declarativeFormIDs)) {
82
+                    continue;
83
+                }
84
+
85
+                $icon = $section->getIcon();
86
+
87
+                $active = $section->getID() === $currentSection
88
+                    && $type === $currentType;
89
+
90
+                $templateParameters[] = [
91
+                    'anchor' => $section->getID(),
92
+                    'section-name' => $section->getName(),
93
+                    'active' => $active,
94
+                    'icon' => $icon,
95
+                ];
96
+            }
97
+        }
98
+        return $templateParameters;
99
+    }
100
+
101
+    protected function formatPersonalSections(string $currentType, string $currentSection): array {
102
+        $sections = $this->settingsManager->getPersonalSections();
103
+        return $this->formatSections($sections, $currentSection, 'personal', $currentType);
104
+    }
105
+
106
+    protected function formatAdminSections(string $currentType, string $currentSection): array {
107
+        $sections = $this->settingsManager->getAdminSections();
108
+        return $this->formatSections($sections, $currentSection, 'admin', $currentType);
109
+    }
110
+
111
+    /**
112
+     * @param list<ISettings> $settings
113
+     * @param list<DeclarativeSettingsFormSchemaWithValues> $declarativeSettings
114
+     * @return array{content: string}
115
+     */
116
+    private function formatSettings(array $settings, array $declarativeSettings): array {
117
+        $settings = array_merge($settings, $declarativeSettings);
118
+
119
+        usort($settings, function ($first, $second) {
120
+            $priorityOne = $first instanceof ISettings ? $first->getPriority() : $first['priority'];
121
+            $priorityTwo = $second instanceof ISettings ? $second->getPriority() : $second['priority'];
122
+            return $priorityOne - $priorityTwo;
123
+        });
124
+
125
+        $html = '';
126
+        foreach ($settings as $setting) {
127
+            if ($setting instanceof ISettings) {
128
+                $form = $setting->getForm();
129
+                $html .= $form->renderAs('')->render();
130
+            } else {
131
+                $html .= '<div id="' . $setting['app'] . '_' . $setting['id'] . '"></div>';
132
+            }
133
+        }
134
+        return ['content' => $html];
135
+    }
136
+
137
+    /**
138
+     * @psalm-param 'admin'|'personal' $type
139
+     */
140
+    private function getIndexResponse(string $type, string $section): TemplateResponse {
141
+        $user = $this->userSession->getUser();
142
+        assert($user !== null, 'No user logged in for settings');
143
+
144
+        $this->declarativeSettingsManager->loadSchemas();
145
+        $declarativeSettings = $this->declarativeSettingsManager->getFormsWithValues($user, $type, $section);
146
+
147
+        foreach ($declarativeSettings as &$form) {
148
+            foreach ($form['fields'] as &$field) {
149
+                if (isset($field['sensitive']) && $field['sensitive'] === true && !empty($field['value'])) {
150
+                    $field['value'] = 'dummySecret';
151
+                }
152
+            }
153
+        }
154
+
155
+        if ($type === 'personal') {
156
+            $settings = array_values($this->settingsManager->getPersonalSettings($section));
157
+            if ($section === 'theming') {
158
+                $this->navigationManager->setActiveEntry('accessibility_settings');
159
+            } else {
160
+                $this->navigationManager->setActiveEntry('settings');
161
+            }
162
+        } elseif ($type === 'admin') {
163
+            $settings = array_values($this->settingsManager->getAllowedAdminSettings($section, $user));
164
+            if (empty($settings) && empty($declarativeSettings)) {
165
+                throw new NotAdminException('Logged in user does not have permission to access these settings.');
166
+            }
167
+            $this->navigationManager->setActiveEntry('admin_settings');
168
+        } else {
169
+            throw new InvalidArgumentException('$type must be either "admin" or "personal"');
170
+        }
171
+
172
+        if (!empty($declarativeSettings)) {
173
+            Util::addScript(Application::APP_ID, 'declarative-settings-forms');
174
+            $this->initialState->provideInitialState('declarative-settings-forms', $declarativeSettings);
175
+        }
176
+
177
+        $settings = array_merge(...$settings);
178
+        $templateParams = $this->formatSettings($settings, $declarativeSettings);
179
+        $templateParams = array_merge($templateParams, $this->getNavigationParameters($type, $section));
180
+
181
+        $activeSection = $this->settingsManager->getSection($type, $section);
182
+        if ($activeSection) {
183
+            $templateParams['pageTitle'] = $activeSection->getName();
184
+            $templateParams['activeSectionId'] = $activeSection->getID();
185
+            $templateParams['activeSectionType'] = $type;
186
+        }
187
+
188
+        return new TemplateResponse('settings', 'settings/frame', $templateParams);
189
+    }
190 190
 }
Please login to merge, or discard this patch.
apps/settings/lib/Controller/DeclarativeSettingsController.php 1 patch
Indentation   +95 added lines, -95 removed lines patch added patch discarded remove patch
@@ -28,104 +28,104 @@
 block discarded – undo
28 28
  * @psalm-import-type SettingsDeclarativeForm from ResponseDefinitions
29 29
  */
30 30
 class DeclarativeSettingsController extends OCSController {
31
-	public function __construct(
32
-		string $appName,
33
-		IRequest $request,
34
-		private IUserSession $userSession,
35
-		private IDeclarativeManager $declarativeManager,
36
-		private LoggerInterface $logger,
37
-	) {
38
-		parent::__construct($appName, $request);
39
-	}
31
+    public function __construct(
32
+        string $appName,
33
+        IRequest $request,
34
+        private IUserSession $userSession,
35
+        private IDeclarativeManager $declarativeManager,
36
+        private LoggerInterface $logger,
37
+    ) {
38
+        parent::__construct($appName, $request);
39
+    }
40 40
 
41
-	/**
42
-	 * Sets a declarative settings value
43
-	 *
44
-	 * @param string $app ID of the app
45
-	 * @param string $formId ID of the form
46
-	 * @param string $fieldId ID of the field
47
-	 * @param mixed $value Value to be saved
48
-	 * @return DataResponse<Http::STATUS_OK, null, array{}>
49
-	 * @throws NotLoggedInException Not logged in or not an admin user
50
-	 * @throws NotAdminException Not logged in or not an admin user
51
-	 * @throws OCSBadRequestException Invalid arguments to save value
52
-	 *
53
-	 * 200: Value set successfully
54
-	 */
55
-	#[NoAdminRequired]
56
-	public function setValue(string $app, string $formId, string $fieldId, mixed $value): DataResponse {
57
-		return $this->saveValue($app, $formId, $fieldId, $value);
58
-	}
41
+    /**
42
+     * Sets a declarative settings value
43
+     *
44
+     * @param string $app ID of the app
45
+     * @param string $formId ID of the form
46
+     * @param string $fieldId ID of the field
47
+     * @param mixed $value Value to be saved
48
+     * @return DataResponse<Http::STATUS_OK, null, array{}>
49
+     * @throws NotLoggedInException Not logged in or not an admin user
50
+     * @throws NotAdminException Not logged in or not an admin user
51
+     * @throws OCSBadRequestException Invalid arguments to save value
52
+     *
53
+     * 200: Value set successfully
54
+     */
55
+    #[NoAdminRequired]
56
+    public function setValue(string $app, string $formId, string $fieldId, mixed $value): DataResponse {
57
+        return $this->saveValue($app, $formId, $fieldId, $value);
58
+    }
59 59
 
60
-	/**
61
-	 * Sets a declarative settings value.
62
-	 * Password confirmation is required for sensitive values.
63
-	 *
64
-	 * @param string $app ID of the app
65
-	 * @param string $formId ID of the form
66
-	 * @param string $fieldId ID of the field
67
-	 * @param mixed $value Value to be saved
68
-	 * @return DataResponse<Http::STATUS_OK, null, array{}>
69
-	 * @throws NotLoggedInException Not logged in or not an admin user
70
-	 * @throws NotAdminException Not logged in or not an admin user
71
-	 * @throws OCSBadRequestException Invalid arguments to save value
72
-	 *
73
-	 * 200: Value set successfully
74
-	 */
75
-	#[NoAdminRequired]
76
-	#[PasswordConfirmationRequired]
77
-	public function setSensitiveValue(string $app, string $formId, string $fieldId, mixed $value): DataResponse {
78
-		return $this->saveValue($app, $formId, $fieldId, $value);
79
-	}
60
+    /**
61
+     * Sets a declarative settings value.
62
+     * Password confirmation is required for sensitive values.
63
+     *
64
+     * @param string $app ID of the app
65
+     * @param string $formId ID of the form
66
+     * @param string $fieldId ID of the field
67
+     * @param mixed $value Value to be saved
68
+     * @return DataResponse<Http::STATUS_OK, null, array{}>
69
+     * @throws NotLoggedInException Not logged in or not an admin user
70
+     * @throws NotAdminException Not logged in or not an admin user
71
+     * @throws OCSBadRequestException Invalid arguments to save value
72
+     *
73
+     * 200: Value set successfully
74
+     */
75
+    #[NoAdminRequired]
76
+    #[PasswordConfirmationRequired]
77
+    public function setSensitiveValue(string $app, string $formId, string $fieldId, mixed $value): DataResponse {
78
+        return $this->saveValue($app, $formId, $fieldId, $value);
79
+    }
80 80
 
81
-	/**
82
-	 * Sets a declarative settings value.
83
-	 *
84
-	 * @param string $app ID of the app
85
-	 * @param string $formId ID of the form
86
-	 * @param string $fieldId ID of the field
87
-	 * @param mixed $value Value to be saved
88
-	 * @return DataResponse<Http::STATUS_OK, null, array{}>
89
-	 * @throws NotLoggedInException Not logged in or not an admin user
90
-	 * @throws NotAdminException Not logged in or not an admin user
91
-	 * @throws OCSBadRequestException Invalid arguments to save value
92
-	 *
93
-	 * 200: Value set successfully
94
-	 */
95
-	private function saveValue(string $app, string $formId, string $fieldId, mixed $value): DataResponse {
96
-		$user = $this->userSession->getUser();
97
-		if ($user === null) {
98
-			throw new NotLoggedInException();
99
-		}
81
+    /**
82
+     * Sets a declarative settings value.
83
+     *
84
+     * @param string $app ID of the app
85
+     * @param string $formId ID of the form
86
+     * @param string $fieldId ID of the field
87
+     * @param mixed $value Value to be saved
88
+     * @return DataResponse<Http::STATUS_OK, null, array{}>
89
+     * @throws NotLoggedInException Not logged in or not an admin user
90
+     * @throws NotAdminException Not logged in or not an admin user
91
+     * @throws OCSBadRequestException Invalid arguments to save value
92
+     *
93
+     * 200: Value set successfully
94
+     */
95
+    private function saveValue(string $app, string $formId, string $fieldId, mixed $value): DataResponse {
96
+        $user = $this->userSession->getUser();
97
+        if ($user === null) {
98
+            throw new NotLoggedInException();
99
+        }
100 100
 
101
-		try {
102
-			$this->declarativeManager->loadSchemas();
103
-			$this->declarativeManager->setValue($user, $app, $formId, $fieldId, $value);
104
-			return new DataResponse(null);
105
-		} catch (NotAdminException $e) {
106
-			throw $e;
107
-		} catch (Exception $e) {
108
-			$this->logger->error('Failed to set declarative settings value: ' . $e->getMessage());
109
-			throw new OCSBadRequestException();
110
-		}
111
-	}
101
+        try {
102
+            $this->declarativeManager->loadSchemas();
103
+            $this->declarativeManager->setValue($user, $app, $formId, $fieldId, $value);
104
+            return new DataResponse(null);
105
+        } catch (NotAdminException $e) {
106
+            throw $e;
107
+        } catch (Exception $e) {
108
+            $this->logger->error('Failed to set declarative settings value: ' . $e->getMessage());
109
+            throw new OCSBadRequestException();
110
+        }
111
+    }
112 112
 
113
-	/**
114
-	 * Gets all declarative forms with the values prefilled.
115
-	 *
116
-	 * @return DataResponse<Http::STATUS_OK, list<SettingsDeclarativeForm>, array{}>
117
-	 * @throws NotLoggedInException
118
-	 * @NoSubAdminRequired
119
-	 *
120
-	 * 200: Forms returned
121
-	 */
122
-	#[NoAdminRequired]
123
-	public function getForms(): DataResponse {
124
-		$user = $this->userSession->getUser();
125
-		if ($user === null) {
126
-			throw new NotLoggedInException();
127
-		}
128
-		$this->declarativeManager->loadSchemas();
129
-		return new DataResponse($this->declarativeManager->getFormsWithValues($user, null, null));
130
-	}
113
+    /**
114
+     * Gets all declarative forms with the values prefilled.
115
+     *
116
+     * @return DataResponse<Http::STATUS_OK, list<SettingsDeclarativeForm>, array{}>
117
+     * @throws NotLoggedInException
118
+     * @NoSubAdminRequired
119
+     *
120
+     * 200: Forms returned
121
+     */
122
+    #[NoAdminRequired]
123
+    public function getForms(): DataResponse {
124
+        $user = $this->userSession->getUser();
125
+        if ($user === null) {
126
+            throw new NotLoggedInException();
127
+        }
128
+        $this->declarativeManager->loadSchemas();
129
+        return new DataResponse($this->declarativeManager->getFormsWithValues($user, null, null));
130
+    }
131 131
 }
Please login to merge, or discard this patch.
apps/settings/appinfo/routes.php 1 patch
Indentation   +55 added lines, -55 removed lines patch added patch discarded remove patch
@@ -8,66 +8,66 @@
 block discarded – undo
8 8
  * SPDX-License-Identifier: AGPL-3.0-only
9 9
  */
10 10
 return [
11
-	'resources' => [
12
-		'AuthSettings' => ['url' => '/settings/personal/authtokens' , 'root' => ''],
13
-	],
14
-	'routes' => [
15
-		['name' => 'AuthorizedGroup#saveSettings', 'url' => '/settings/authorizedgroups/saveSettings', 'verb' => 'POST'],
11
+    'resources' => [
12
+        'AuthSettings' => ['url' => '/settings/personal/authtokens' , 'root' => ''],
13
+    ],
14
+    'routes' => [
15
+        ['name' => 'AuthorizedGroup#saveSettings', 'url' => '/settings/authorizedgroups/saveSettings', 'verb' => 'POST'],
16 16
 
17
-		['name' => 'AuthSettings#wipe', 'url' => '/settings/personal/authtokens/wipe/{id}', 'verb' => 'POST' , 'root' => ''],
17
+        ['name' => 'AuthSettings#wipe', 'url' => '/settings/personal/authtokens/wipe/{id}', 'verb' => 'POST' , 'root' => ''],
18 18
 
19
-		['name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST' , 'root' => ''],
20
-		['name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST' , 'root' => ''],
21
-		['name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST' , 'root' => ''],
19
+        ['name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST' , 'root' => ''],
20
+        ['name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST' , 'root' => ''],
21
+        ['name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST' , 'root' => ''],
22 22
 
23
-		['name' => 'AppSettings#getAppDiscoverJSON', 'url' => '/settings/api/apps/discover', 'verb' => 'GET', 'root' => ''],
24
-		['name' => 'AppSettings#getAppDiscoverMedia', 'url' => '/settings/api/apps/media', 'verb' => 'GET', 'root' => ''],
25
-		['name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET' , 'root' => ''],
26
-		['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET' , 'root' => ''],
27
-		['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET' , 'root' => ''],
28
-		['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'GET' , 'root' => ''],
29
-		['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'POST' , 'root' => ''],
30
-		['name' => 'AppSettings#enableApps', 'url' => '/settings/apps/enable', 'verb' => 'POST' , 'root' => ''],
31
-		['name' => 'AppSettings#disableApp', 'url' => '/settings/apps/disable/{appId}', 'verb' => 'GET' , 'root' => ''],
32
-		['name' => 'AppSettings#disableApps', 'url' => '/settings/apps/disable', 'verb' => 'POST' , 'root' => ''],
33
-		['name' => 'AppSettings#updateApp', 'url' => '/settings/apps/update/{appId}', 'verb' => 'GET' , 'root' => ''],
34
-		['name' => 'AppSettings#uninstallApp', 'url' => '/settings/apps/uninstall/{appId}', 'verb' => 'GET' , 'root' => ''],
35
-		['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}', 'verb' => 'GET', 'defaults' => ['category' => ''] , 'root' => ''],
36
-		['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}/{id}', 'verb' => 'GET', 'defaults' => ['category' => '', 'id' => ''] , 'root' => ''],
37
-		['name' => 'AppSettings#force', 'url' => '/settings/apps/force', 'verb' => 'POST' , 'root' => ''],
23
+        ['name' => 'AppSettings#getAppDiscoverJSON', 'url' => '/settings/api/apps/discover', 'verb' => 'GET', 'root' => ''],
24
+        ['name' => 'AppSettings#getAppDiscoverMedia', 'url' => '/settings/api/apps/media', 'verb' => 'GET', 'root' => ''],
25
+        ['name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET' , 'root' => ''],
26
+        ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET' , 'root' => ''],
27
+        ['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET' , 'root' => ''],
28
+        ['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'GET' , 'root' => ''],
29
+        ['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'POST' , 'root' => ''],
30
+        ['name' => 'AppSettings#enableApps', 'url' => '/settings/apps/enable', 'verb' => 'POST' , 'root' => ''],
31
+        ['name' => 'AppSettings#disableApp', 'url' => '/settings/apps/disable/{appId}', 'verb' => 'GET' , 'root' => ''],
32
+        ['name' => 'AppSettings#disableApps', 'url' => '/settings/apps/disable', 'verb' => 'POST' , 'root' => ''],
33
+        ['name' => 'AppSettings#updateApp', 'url' => '/settings/apps/update/{appId}', 'verb' => 'GET' , 'root' => ''],
34
+        ['name' => 'AppSettings#uninstallApp', 'url' => '/settings/apps/uninstall/{appId}', 'verb' => 'GET' , 'root' => ''],
35
+        ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}', 'verb' => 'GET', 'defaults' => ['category' => ''] , 'root' => ''],
36
+        ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}/{id}', 'verb' => 'GET', 'defaults' => ['category' => '', 'id' => ''] , 'root' => ''],
37
+        ['name' => 'AppSettings#force', 'url' => '/settings/apps/force', 'verb' => 'POST' , 'root' => ''],
38 38
 
39
-		['name' => 'Users#setDisplayName', 'url' => '/settings/users/{username}/displayName', 'verb' => 'POST' , 'root' => ''],
40
-		['name' => 'Users#setEMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT' , 'root' => ''],
41
-		['name' => 'Users#setUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT' , 'root' => ''],
42
-		['name' => 'Users#getVerificationCode', 'url' => '/settings/users/{account}/verify', 'verb' => 'GET' , 'root' => ''],
43
-		['name' => 'Users#usersList', 'url' => '/settings/users', 'verb' => 'GET' , 'root' => ''],
44
-		['name' => 'Users#usersListByGroup', 'url' => '/settings/users/{group}', 'verb' => 'GET', 'requirements' => ['group' => '.+'] , 'root' => ''],
45
-		['name' => 'Users#setPreference', 'url' => '/settings/users/preferences/{key}', 'verb' => 'POST' , 'root' => ''],
46
-		['name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET' , 'root' => ''],
47
-		['name' => 'CheckSetup#setupCheckManager', 'url' => '/settings/setupcheck', 'verb' => 'GET' , 'root' => ''],
48
-		['name' => 'CheckSetup#check', 'url' => '/settings/ajax/checksetup', 'verb' => 'GET' , 'root' => ''],
49
-		['name' => 'CheckSetup#getFailedIntegrityCheckFiles', 'url' => '/settings/integrity/failed', 'verb' => 'GET' , 'root' => ''],
50
-		['name' => 'CheckSetup#rescanFailedIntegrityCheck', 'url' => '/settings/integrity/rescan', 'verb' => 'GET' , 'root' => ''],
51
-		['name' => 'PersonalSettings#index', 'url' => '/settings/user/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'personal-info'] , 'root' => ''],
52
-		['name' => 'AdminSettings#index', 'url' => '/settings/admin/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'server'] , 'root' => ''],
53
-		['name' => 'AdminSettings#form', 'url' => '/settings/admin/{section}', 'verb' => 'GET' , 'root' => ''],
54
-		['name' => 'ChangePassword#changePersonalPassword', 'url' => '/settings/personal/changepassword', 'verb' => 'POST' , 'root' => ''],
55
-		['name' => 'ChangePassword#changeUserPassword', 'url' => '/settings/users/changepassword', 'verb' => 'POST' , 'root' => ''],
56
-		['name' => 'TwoFactorSettings#index', 'url' => '/settings/api/admin/twofactorauth', 'verb' => 'GET' , 'root' => ''],
57
-		['name' => 'TwoFactorSettings#update', 'url' => '/settings/api/admin/twofactorauth', 'verb' => 'PUT' , 'root' => ''],
58
-		['name' => 'AISettings#update', 'url' => '/settings/api/admin/ai', 'verb' => 'PUT' , 'root' => ''],
39
+        ['name' => 'Users#setDisplayName', 'url' => '/settings/users/{username}/displayName', 'verb' => 'POST' , 'root' => ''],
40
+        ['name' => 'Users#setEMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT' , 'root' => ''],
41
+        ['name' => 'Users#setUserSettings', 'url' => '/settings/users/{username}/settings', 'verb' => 'PUT' , 'root' => ''],
42
+        ['name' => 'Users#getVerificationCode', 'url' => '/settings/users/{account}/verify', 'verb' => 'GET' , 'root' => ''],
43
+        ['name' => 'Users#usersList', 'url' => '/settings/users', 'verb' => 'GET' , 'root' => ''],
44
+        ['name' => 'Users#usersListByGroup', 'url' => '/settings/users/{group}', 'verb' => 'GET', 'requirements' => ['group' => '.+'] , 'root' => ''],
45
+        ['name' => 'Users#setPreference', 'url' => '/settings/users/preferences/{key}', 'verb' => 'POST' , 'root' => ''],
46
+        ['name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET' , 'root' => ''],
47
+        ['name' => 'CheckSetup#setupCheckManager', 'url' => '/settings/setupcheck', 'verb' => 'GET' , 'root' => ''],
48
+        ['name' => 'CheckSetup#check', 'url' => '/settings/ajax/checksetup', 'verb' => 'GET' , 'root' => ''],
49
+        ['name' => 'CheckSetup#getFailedIntegrityCheckFiles', 'url' => '/settings/integrity/failed', 'verb' => 'GET' , 'root' => ''],
50
+        ['name' => 'CheckSetup#rescanFailedIntegrityCheck', 'url' => '/settings/integrity/rescan', 'verb' => 'GET' , 'root' => ''],
51
+        ['name' => 'PersonalSettings#index', 'url' => '/settings/user/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'personal-info'] , 'root' => ''],
52
+        ['name' => 'AdminSettings#index', 'url' => '/settings/admin/{section}', 'verb' => 'GET', 'defaults' => ['section' => 'server'] , 'root' => ''],
53
+        ['name' => 'AdminSettings#form', 'url' => '/settings/admin/{section}', 'verb' => 'GET' , 'root' => ''],
54
+        ['name' => 'ChangePassword#changePersonalPassword', 'url' => '/settings/personal/changepassword', 'verb' => 'POST' , 'root' => ''],
55
+        ['name' => 'ChangePassword#changeUserPassword', 'url' => '/settings/users/changepassword', 'verb' => 'POST' , 'root' => ''],
56
+        ['name' => 'TwoFactorSettings#index', 'url' => '/settings/api/admin/twofactorauth', 'verb' => 'GET' , 'root' => ''],
57
+        ['name' => 'TwoFactorSettings#update', 'url' => '/settings/api/admin/twofactorauth', 'verb' => 'PUT' , 'root' => ''],
58
+        ['name' => 'AISettings#update', 'url' => '/settings/api/admin/ai', 'verb' => 'PUT' , 'root' => ''],
59 59
 
60
-		['name' => 'Help#help', 'url' => '/settings/help/{mode}', 'verb' => 'GET', 'defaults' => ['mode' => ''] , 'root' => ''],
60
+        ['name' => 'Help#help', 'url' => '/settings/help/{mode}', 'verb' => 'GET', 'defaults' => ['mode' => ''] , 'root' => ''],
61 61
 
62
-		['name' => 'WebAuthn#startRegistration', 'url' => '/settings/api/personal/webauthn/registration', 'verb' => 'GET' , 'root' => ''],
63
-		['name' => 'WebAuthn#finishRegistration', 'url' => '/settings/api/personal/webauthn/registration', 'verb' => 'POST' , 'root' => ''],
64
-		['name' => 'WebAuthn#deleteRegistration', 'url' => '/settings/api/personal/webauthn/registration/{id}', 'verb' => 'DELETE' , 'root' => ''],
62
+        ['name' => 'WebAuthn#startRegistration', 'url' => '/settings/api/personal/webauthn/registration', 'verb' => 'GET' , 'root' => ''],
63
+        ['name' => 'WebAuthn#finishRegistration', 'url' => '/settings/api/personal/webauthn/registration', 'verb' => 'POST' , 'root' => ''],
64
+        ['name' => 'WebAuthn#deleteRegistration', 'url' => '/settings/api/personal/webauthn/registration/{id}', 'verb' => 'DELETE' , 'root' => ''],
65 65
 
66
-		['name' => 'Reasons#getPdf', 'url' => '/settings/download/reasons', 'verb' => 'GET', 'root' => ''],
67
-	],
68
-	'ocs' => [
69
-		['name' => 'DeclarativeSettings#setValue', 'url' => '/settings/api/declarative/value', 'verb' => 'POST', 'root' => ''],
70
-		['name' => 'DeclarativeSettings#setSensitiveValue', 'url' => '/settings/api/declarative/value-sensitive', 'verb' => 'POST', 'root' => ''],
71
-		['name' => 'DeclarativeSettings#getForms', 'url' => '/settings/api/declarative/forms', 'verb' => 'GET', 'root' => ''],
72
-	],
66
+        ['name' => 'Reasons#getPdf', 'url' => '/settings/download/reasons', 'verb' => 'GET', 'root' => ''],
67
+    ],
68
+    'ocs' => [
69
+        ['name' => 'DeclarativeSettings#setValue', 'url' => '/settings/api/declarative/value', 'verb' => 'POST', 'root' => ''],
70
+        ['name' => 'DeclarativeSettings#setSensitiveValue', 'url' => '/settings/api/declarative/value-sensitive', 'verb' => 'POST', 'root' => ''],
71
+        ['name' => 'DeclarativeSettings#getForms', 'url' => '/settings/api/declarative/forms', 'verb' => 'GET', 'root' => ''],
72
+    ],
73 73
 ];
Please login to merge, or discard this patch.
apps/testing/lib/Settings/DeclarativeSettingsForm.php 1 patch
Indentation   +191 added lines, -191 removed lines patch added patch discarded remove patch
@@ -11,195 +11,195 @@
 block discarded – undo
11 11
 use OCP\Settings\IDeclarativeSettingsForm;
12 12
 
13 13
 class DeclarativeSettingsForm implements IDeclarativeSettingsForm {
14
-	public function getSchema(): array {
15
-		return [
16
-			'id' => 'test_declarative_form',
17
-			'priority' => 10,
18
-			'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, // admin, personal
19
-			'section_id' => 'additional',
20
-			'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL, // external, internal (handled by core to store in appconfig and preferences)
21
-			'title' => 'Test declarative settings class', // NcSettingsSection name
22
-			'description' => 'This form is registered with a DeclarativeSettingsForm class', // NcSettingsSection description
23
-			'doc_url' => '', // NcSettingsSection doc_url for documentation or help page, empty string if not needed
24
-			'fields' => [
25
-				[
26
-					'id' => 'test_ex_app_field_7', // configkey
27
-					'title' => 'Multi-selection', // name or label
28
-					'description' => 'Select some option setting', // hint
29
-					'type' => DeclarativeSettingsTypes::MULTI_SELECT, // select, radio, multi-select
30
-					'options' => ['foo', 'bar', 'baz'], // simple options for select, radio, multi-select
31
-					'placeholder' => 'Select some multiple options', // input placeholder
32
-					'default' => ['foo', 'bar'],
33
-				],
34
-				[
35
-					'id' => 'some_real_setting',
36
-					'title' => 'Choose init status check background job interval',
37
-					'description' => 'How often AppAPI should check for initialization status',
38
-					'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio)
39
-					'placeholder' => 'Choose init status check background job interval',
40
-					'default' => '40m',
41
-					'options' => [
42
-						[
43
-							'name' => 'Each 40 minutes', // NcCheckboxRadioSwitch display name
44
-							'value' => '40m' // NcCheckboxRadioSwitch value
45
-						],
46
-						[
47
-							'name' => 'Each 60 minutes',
48
-							'value' => '60m'
49
-						],
50
-						[
51
-							'name' => 'Each 120 minutes',
52
-							'value' => '120m'
53
-						],
54
-						[
55
-							'name' => 'Each day',
56
-							'value' => 60 * 24 . 'm'
57
-						],
58
-					],
59
-				],
60
-				[
61
-					'id' => 'test_ex_app_field_1', // configkey
62
-					'title' => 'Default text field', // label
63
-					'description' => 'Set some simple text setting', // hint
64
-					'type' => DeclarativeSettingsTypes::TEXT, // text, password, email, tel, url, number
65
-					'placeholder' => 'Enter text setting', // placeholder
66
-					'default' => 'foo',
67
-				],
68
-				[
69
-					'id' => 'test_ex_app_field_1_1',
70
-					'title' => 'Email field',
71
-					'description' => 'Set email config',
72
-					'type' => DeclarativeSettingsTypes::EMAIL,
73
-					'placeholder' => 'Enter email',
74
-					'default' => '',
75
-				],
76
-				[
77
-					'id' => 'test_ex_app_field_1_2',
78
-					'title' => 'Tel field',
79
-					'description' => 'Set tel config',
80
-					'type' => DeclarativeSettingsTypes::TEL,
81
-					'placeholder' => 'Enter your tel',
82
-					'default' => '',
83
-				],
84
-				[
85
-					'id' => 'test_ex_app_field_1_3',
86
-					'title' => 'Url (website) field',
87
-					'description' => 'Set url config',
88
-					'type' => 'url',
89
-					'placeholder' => 'Enter url',
90
-					'default' => '',
91
-				],
92
-				[
93
-					'id' => 'test_ex_app_field_1_4',
94
-					'title' => 'Number field',
95
-					'description' => 'Set number config',
96
-					'type' => DeclarativeSettingsTypes::NUMBER,
97
-					'placeholder' => 'Enter number value',
98
-					'default' => 0,
99
-				],
100
-				[
101
-					'id' => 'test_ex_app_field_2',
102
-					'title' => 'Password',
103
-					'description' => 'Set some secure value setting',
104
-					'type' => 'password',
105
-					'placeholder' => 'Set secure value',
106
-					'default' => '',
107
-				],
108
-				[
109
-					'id' => 'test_ex_app_field_3',
110
-					'title' => 'Selection',
111
-					'description' => 'Select some option setting',
112
-					'type' => DeclarativeSettingsTypes::SELECT, // select, radio, multi-select
113
-					'options' => ['foo', 'bar', 'baz'],
114
-					'placeholder' => 'Select some option setting',
115
-					'default' => 'foo',
116
-				],
117
-				[
118
-					'id' => 'test_ex_app_field_4',
119
-					'title' => 'Toggle something',
120
-					'description' => 'Select checkbox option setting',
121
-					'type' => DeclarativeSettingsTypes::CHECKBOX, // checkbox, multiple-checkbox
122
-					'label' => 'Verify something if enabled',
123
-					'default' => false,
124
-				],
125
-				[
126
-					'id' => 'test_ex_app_field_5',
127
-					'title' => 'Multiple checkbox toggles, describing one setting, checked options are saved as an JSON object {foo: true, bar: false}',
128
-					'description' => 'Select checkbox option setting',
129
-					'type' => DeclarativeSettingsTypes::MULTI_CHECKBOX, // checkbox, multi-checkbox
130
-					'default' => ['foo' => true, 'bar' => true, 'baz' => true],
131
-					'options' => [
132
-						[
133
-							'name' => 'Foo',
134
-							'value' => 'foo', // multiple-checkbox configkey
135
-						],
136
-						[
137
-							'name' => 'Bar',
138
-							'value' => 'bar',
139
-						],
140
-						[
141
-							'name' => 'Baz',
142
-							'value' => 'baz',
143
-						],
144
-						[
145
-							'name' => 'Qux',
146
-							'value' => 'qux',
147
-						],
148
-					],
149
-				],
150
-				[
151
-					'id' => 'test_ex_app_field_6',
152
-					'title' => 'Radio toggles, describing one setting like single select',
153
-					'description' => 'Select radio option setting',
154
-					'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio)
155
-					'label' => 'Select single toggle',
156
-					'default' => 'foo',
157
-					'options' => [
158
-						[
159
-							'name' => 'First radio', // NcCheckboxRadioSwitch display name
160
-							'value' => 'foo' // NcCheckboxRadioSwitch value
161
-						],
162
-						[
163
-							'name' => 'Second radio',
164
-							'value' => 'bar'
165
-						],
166
-						[
167
-							'name' => 'Third radio',
168
-							'value' => 'baz'
169
-						],
170
-					],
171
-				],
172
-				[
173
-					'id' => 'test_sensitive_field',
174
-					'title' => 'Sensitive text field',
175
-					'description' => 'Set some secure value setting that is stored encrypted',
176
-					'type' => DeclarativeSettingsTypes::TEXT,
177
-					'label' => 'Sensitive field',
178
-					'placeholder' => 'Set secure value',
179
-					'default' => '',
180
-					'sensitive' => true, // only for TEXT, PASSWORD types
181
-				],
182
-				[
183
-					'id' => 'test_sensitive_field_2',
184
-					'title' => 'Sensitive password field',
185
-					'description' => 'Set some password setting that is stored encrypted',
186
-					'type' => DeclarativeSettingsTypes::PASSWORD,
187
-					'label' => 'Sensitive field',
188
-					'placeholder' => 'Set secure value',
189
-					'default' => '',
190
-					'sensitive' => true, // only for TEXT, PASSWORD types
191
-				],
192
-				[
193
-					'id' => 'test_non_sensitive_field',
194
-					'title' => 'Password field',
195
-					'description' => 'Set some password setting',
196
-					'type' => DeclarativeSettingsTypes::PASSWORD,
197
-					'label' => 'Password field',
198
-					'placeholder' => 'Set secure value',
199
-					'default' => '',
200
-					'sensitive' => false,
201
-				],
202
-			],
203
-		];
204
-	}
14
+    public function getSchema(): array {
15
+        return [
16
+            'id' => 'test_declarative_form',
17
+            'priority' => 10,
18
+            'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, // admin, personal
19
+            'section_id' => 'additional',
20
+            'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL, // external, internal (handled by core to store in appconfig and preferences)
21
+            'title' => 'Test declarative settings class', // NcSettingsSection name
22
+            'description' => 'This form is registered with a DeclarativeSettingsForm class', // NcSettingsSection description
23
+            'doc_url' => '', // NcSettingsSection doc_url for documentation or help page, empty string if not needed
24
+            'fields' => [
25
+                [
26
+                    'id' => 'test_ex_app_field_7', // configkey
27
+                    'title' => 'Multi-selection', // name or label
28
+                    'description' => 'Select some option setting', // hint
29
+                    'type' => DeclarativeSettingsTypes::MULTI_SELECT, // select, radio, multi-select
30
+                    'options' => ['foo', 'bar', 'baz'], // simple options for select, radio, multi-select
31
+                    'placeholder' => 'Select some multiple options', // input placeholder
32
+                    'default' => ['foo', 'bar'],
33
+                ],
34
+                [
35
+                    'id' => 'some_real_setting',
36
+                    'title' => 'Choose init status check background job interval',
37
+                    'description' => 'How often AppAPI should check for initialization status',
38
+                    'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio)
39
+                    'placeholder' => 'Choose init status check background job interval',
40
+                    'default' => '40m',
41
+                    'options' => [
42
+                        [
43
+                            'name' => 'Each 40 minutes', // NcCheckboxRadioSwitch display name
44
+                            'value' => '40m' // NcCheckboxRadioSwitch value
45
+                        ],
46
+                        [
47
+                            'name' => 'Each 60 minutes',
48
+                            'value' => '60m'
49
+                        ],
50
+                        [
51
+                            'name' => 'Each 120 minutes',
52
+                            'value' => '120m'
53
+                        ],
54
+                        [
55
+                            'name' => 'Each day',
56
+                            'value' => 60 * 24 . 'm'
57
+                        ],
58
+                    ],
59
+                ],
60
+                [
61
+                    'id' => 'test_ex_app_field_1', // configkey
62
+                    'title' => 'Default text field', // label
63
+                    'description' => 'Set some simple text setting', // hint
64
+                    'type' => DeclarativeSettingsTypes::TEXT, // text, password, email, tel, url, number
65
+                    'placeholder' => 'Enter text setting', // placeholder
66
+                    'default' => 'foo',
67
+                ],
68
+                [
69
+                    'id' => 'test_ex_app_field_1_1',
70
+                    'title' => 'Email field',
71
+                    'description' => 'Set email config',
72
+                    'type' => DeclarativeSettingsTypes::EMAIL,
73
+                    'placeholder' => 'Enter email',
74
+                    'default' => '',
75
+                ],
76
+                [
77
+                    'id' => 'test_ex_app_field_1_2',
78
+                    'title' => 'Tel field',
79
+                    'description' => 'Set tel config',
80
+                    'type' => DeclarativeSettingsTypes::TEL,
81
+                    'placeholder' => 'Enter your tel',
82
+                    'default' => '',
83
+                ],
84
+                [
85
+                    'id' => 'test_ex_app_field_1_3',
86
+                    'title' => 'Url (website) field',
87
+                    'description' => 'Set url config',
88
+                    'type' => 'url',
89
+                    'placeholder' => 'Enter url',
90
+                    'default' => '',
91
+                ],
92
+                [
93
+                    'id' => 'test_ex_app_field_1_4',
94
+                    'title' => 'Number field',
95
+                    'description' => 'Set number config',
96
+                    'type' => DeclarativeSettingsTypes::NUMBER,
97
+                    'placeholder' => 'Enter number value',
98
+                    'default' => 0,
99
+                ],
100
+                [
101
+                    'id' => 'test_ex_app_field_2',
102
+                    'title' => 'Password',
103
+                    'description' => 'Set some secure value setting',
104
+                    'type' => 'password',
105
+                    'placeholder' => 'Set secure value',
106
+                    'default' => '',
107
+                ],
108
+                [
109
+                    'id' => 'test_ex_app_field_3',
110
+                    'title' => 'Selection',
111
+                    'description' => 'Select some option setting',
112
+                    'type' => DeclarativeSettingsTypes::SELECT, // select, radio, multi-select
113
+                    'options' => ['foo', 'bar', 'baz'],
114
+                    'placeholder' => 'Select some option setting',
115
+                    'default' => 'foo',
116
+                ],
117
+                [
118
+                    'id' => 'test_ex_app_field_4',
119
+                    'title' => 'Toggle something',
120
+                    'description' => 'Select checkbox option setting',
121
+                    'type' => DeclarativeSettingsTypes::CHECKBOX, // checkbox, multiple-checkbox
122
+                    'label' => 'Verify something if enabled',
123
+                    'default' => false,
124
+                ],
125
+                [
126
+                    'id' => 'test_ex_app_field_5',
127
+                    'title' => 'Multiple checkbox toggles, describing one setting, checked options are saved as an JSON object {foo: true, bar: false}',
128
+                    'description' => 'Select checkbox option setting',
129
+                    'type' => DeclarativeSettingsTypes::MULTI_CHECKBOX, // checkbox, multi-checkbox
130
+                    'default' => ['foo' => true, 'bar' => true, 'baz' => true],
131
+                    'options' => [
132
+                        [
133
+                            'name' => 'Foo',
134
+                            'value' => 'foo', // multiple-checkbox configkey
135
+                        ],
136
+                        [
137
+                            'name' => 'Bar',
138
+                            'value' => 'bar',
139
+                        ],
140
+                        [
141
+                            'name' => 'Baz',
142
+                            'value' => 'baz',
143
+                        ],
144
+                        [
145
+                            'name' => 'Qux',
146
+                            'value' => 'qux',
147
+                        ],
148
+                    ],
149
+                ],
150
+                [
151
+                    'id' => 'test_ex_app_field_6',
152
+                    'title' => 'Radio toggles, describing one setting like single select',
153
+                    'description' => 'Select radio option setting',
154
+                    'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio)
155
+                    'label' => 'Select single toggle',
156
+                    'default' => 'foo',
157
+                    'options' => [
158
+                        [
159
+                            'name' => 'First radio', // NcCheckboxRadioSwitch display name
160
+                            'value' => 'foo' // NcCheckboxRadioSwitch value
161
+                        ],
162
+                        [
163
+                            'name' => 'Second radio',
164
+                            'value' => 'bar'
165
+                        ],
166
+                        [
167
+                            'name' => 'Third radio',
168
+                            'value' => 'baz'
169
+                        ],
170
+                    ],
171
+                ],
172
+                [
173
+                    'id' => 'test_sensitive_field',
174
+                    'title' => 'Sensitive text field',
175
+                    'description' => 'Set some secure value setting that is stored encrypted',
176
+                    'type' => DeclarativeSettingsTypes::TEXT,
177
+                    'label' => 'Sensitive field',
178
+                    'placeholder' => 'Set secure value',
179
+                    'default' => '',
180
+                    'sensitive' => true, // only for TEXT, PASSWORD types
181
+                ],
182
+                [
183
+                    'id' => 'test_sensitive_field_2',
184
+                    'title' => 'Sensitive password field',
185
+                    'description' => 'Set some password setting that is stored encrypted',
186
+                    'type' => DeclarativeSettingsTypes::PASSWORD,
187
+                    'label' => 'Sensitive field',
188
+                    'placeholder' => 'Set secure value',
189
+                    'default' => '',
190
+                    'sensitive' => true, // only for TEXT, PASSWORD types
191
+                ],
192
+                [
193
+                    'id' => 'test_non_sensitive_field',
194
+                    'title' => 'Password field',
195
+                    'description' => 'Set some password setting',
196
+                    'type' => DeclarativeSettingsTypes::PASSWORD,
197
+                    'label' => 'Password field',
198
+                    'placeholder' => 'Set secure value',
199
+                    'default' => '',
200
+                    'sensitive' => false,
201
+                ],
202
+            ],
203
+        ];
204
+    }
205 205
 }
Please login to merge, or discard this patch.
tests/lib/Settings/DeclarativeManagerTest.php 1 patch
Indentation   +599 added lines, -599 removed lines patch added patch discarded remove patch
@@ -30,604 +30,604 @@
 block discarded – undo
30 30
 
31 31
 class DeclarativeManagerTest extends TestCase {
32 32
 
33
-	/** @var IDeclarativeManager|MockObject */
34
-	private $declarativeManager;
35
-
36
-	/** @var IEventDispatcher|MockObject */
37
-	private $eventDispatcher;
38
-
39
-	/** @var IGroupManager|MockObject */
40
-	private $groupManager;
41
-
42
-	/** @var Coordinator|MockObject */
43
-	private $coordinator;
44
-
45
-	/** @var IConfig|MockObject */
46
-	private $config;
47
-
48
-	/** @var IAppConfig|MockObject */
49
-	private $appConfig;
50
-
51
-	/** @var LoggerInterface|MockObject */
52
-	private $logger;
53
-
54
-	/** @var ICrypto|MockObject */
55
-	private $crypto;
56
-
57
-	/** @var IUser|MockObject */
58
-	private $user;
59
-
60
-	/** @var IUser|MockObject */
61
-	private $adminUser;
62
-
63
-	private IDeclarativeSettingsForm&MockObject $closureForm;
64
-
65
-	public const validSchemaAllFields = [
66
-		'id' => 'test_form_1',
67
-		'priority' => 10,
68
-		'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, // admin, personal
69
-		'section_id' => 'additional',
70
-		'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL, // external, internal (handled by core to store in appconfig and preferences)
71
-		'title' => 'Test declarative settings', // NcSettingsSection name
72
-		'description' => 'These fields are rendered dynamically from declarative schema', // NcSettingsSection description
73
-		'doc_url' => '', // NcSettingsSection doc_url for documentation or help page, empty string if not needed
74
-		'fields' => [
75
-			[
76
-				'id' => 'test_field_7', // configkey
77
-				'title' => 'Multi-selection', // name or label
78
-				'description' => 'Select some option setting', // hint
79
-				'type' => DeclarativeSettingsTypes::MULTI_SELECT,
80
-				'options' => ['foo', 'bar', 'baz'], // simple options for select, radio, multi-select
81
-				'placeholder' => 'Select some multiple options', // input placeholder
82
-				'default' => ['foo', 'bar'],
83
-			],
84
-			[
85
-				'id' => 'some_real_setting',
86
-				'title' => 'Select single option',
87
-				'description' => 'Single option radio buttons',
88
-				'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio)
89
-				'placeholder' => 'Select single option, test interval',
90
-				'default' => '40m',
91
-				'options' => [
92
-					[
93
-						'name' => 'Each 40 minutes', // NcCheckboxRadioSwitch display name
94
-						'value' => '40m' // NcCheckboxRadioSwitch value
95
-					],
96
-					[
97
-						'name' => 'Each 60 minutes',
98
-						'value' => '60m'
99
-					],
100
-					[
101
-						'name' => 'Each 120 minutes',
102
-						'value' => '120m'
103
-					],
104
-					[
105
-						'name' => 'Each day',
106
-						'value' => 60 * 24 . 'm'
107
-					],
108
-				],
109
-			],
110
-			[
111
-				'id' => 'test_field_1', // configkey
112
-				'title' => 'Default text field', // label
113
-				'description' => 'Set some simple text setting', // hint
114
-				'type' => DeclarativeSettingsTypes::TEXT,
115
-				'placeholder' => 'Enter text setting', // placeholder
116
-				'default' => 'foo',
117
-			],
118
-			[
119
-				'id' => 'test_field_1_1',
120
-				'title' => 'Email field',
121
-				'description' => 'Set email config',
122
-				'type' => DeclarativeSettingsTypes::EMAIL,
123
-				'placeholder' => 'Enter email',
124
-				'default' => '',
125
-			],
126
-			[
127
-				'id' => 'test_field_1_2',
128
-				'title' => 'Tel field',
129
-				'description' => 'Set tel config',
130
-				'type' => DeclarativeSettingsTypes::TEL,
131
-				'placeholder' => 'Enter your tel',
132
-				'default' => '',
133
-			],
134
-			[
135
-				'id' => 'test_field_1_3',
136
-				'title' => 'Url (website) field',
137
-				'description' => 'Set url config',
138
-				'type' => 'url',
139
-				'placeholder' => 'Enter url',
140
-				'default' => '',
141
-			],
142
-			[
143
-				'id' => 'test_field_1_4',
144
-				'title' => 'Number field',
145
-				'description' => 'Set number config',
146
-				'type' => DeclarativeSettingsTypes::NUMBER,
147
-				'placeholder' => 'Enter number value',
148
-				'default' => 0,
149
-			],
150
-			[
151
-				'id' => 'test_field_2',
152
-				'title' => 'Password',
153
-				'description' => 'Set some secure value setting',
154
-				'type' => 'password',
155
-				'placeholder' => 'Set secure value',
156
-				'default' => '',
157
-			],
158
-			[
159
-				'id' => 'test_field_3',
160
-				'title' => 'Selection',
161
-				'description' => 'Select some option setting',
162
-				'type' => DeclarativeSettingsTypes::SELECT,
163
-				'options' => ['foo', 'bar', 'baz'],
164
-				'placeholder' => 'Select some option setting',
165
-				'default' => 'foo',
166
-			],
167
-			[
168
-				'id' => 'test_field_4',
169
-				'title' => 'Toggle something',
170
-				'description' => 'Select checkbox option setting',
171
-				'type' => DeclarativeSettingsTypes::CHECKBOX,
172
-				'label' => 'Verify something if enabled',
173
-				'default' => false,
174
-			],
175
-			[
176
-				'id' => 'test_field_5',
177
-				'title' => 'Multiple checkbox toggles, describing one setting, checked options are saved as an JSON object {foo: true, bar: false}',
178
-				'description' => 'Select checkbox option setting',
179
-				'type' => DeclarativeSettingsTypes::MULTI_CHECKBOX,
180
-				'default' => ['foo' => true, 'bar' => true],
181
-				'options' => [
182
-					[
183
-						'name' => 'Foo',
184
-						'value' => 'foo', // multiple-checkbox configkey
185
-					],
186
-					[
187
-						'name' => 'Bar',
188
-						'value' => 'bar',
189
-					],
190
-					[
191
-						'name' => 'Baz',
192
-						'value' => 'baz',
193
-					],
194
-					[
195
-						'name' => 'Qux',
196
-						'value' => 'qux',
197
-					],
198
-				],
199
-			],
200
-			[
201
-				'id' => 'test_field_6',
202
-				'title' => 'Radio toggles, describing one setting like single select',
203
-				'description' => 'Select radio option setting',
204
-				'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio)
205
-				'label' => 'Select single toggle',
206
-				'default' => 'foo',
207
-				'options' => [
208
-					[
209
-						'name' => 'First radio', // NcCheckboxRadioSwitch display name
210
-						'value' => 'foo' // NcCheckboxRadioSwitch value
211
-					],
212
-					[
213
-						'name' => 'Second radio',
214
-						'value' => 'bar'
215
-					],
216
-					[
217
-						'name' => 'Second radio',
218
-						'value' => 'baz'
219
-					],
220
-				],
221
-			],
222
-			[
223
-				'id' => 'test_sensitive_field',
224
-				'title' => 'Sensitive text field',
225
-				'description' => 'Set some secure value setting that is stored encrypted',
226
-				'type' => DeclarativeSettingsTypes::TEXT,
227
-				'label' => 'Sensitive field',
228
-				'placeholder' => 'Set secure value',
229
-				'default' => '',
230
-				'sensitive' => true, // only for TEXT, PASSWORD types
231
-			],
232
-			[
233
-				'id' => 'test_sensitive_field_2',
234
-				'title' => 'Sensitive password field',
235
-				'description' => 'Set some password setting that is stored encrypted',
236
-				'type' => DeclarativeSettingsTypes::PASSWORD,
237
-				'label' => 'Sensitive field',
238
-				'placeholder' => 'Set secure value',
239
-				'default' => '',
240
-				'sensitive' => true, // only for TEXT, PASSWORD types
241
-			],
242
-			[
243
-				'id' => 'test_non_sensitive_field',
244
-				'title' => 'Password field',
245
-				'description' => 'Set some password setting',
246
-				'type' => DeclarativeSettingsTypes::PASSWORD,
247
-				'label' => 'Password field',
248
-				'placeholder' => 'Set secure value',
249
-				'default' => '',
250
-				'sensitive' => false,
251
-			],
252
-		],
253
-	];
254
-
255
-	public static bool $testSetInternalValueAfterChange = false;
256
-
257
-	protected function setUp(): void {
258
-		parent::setUp();
259
-
260
-		$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
261
-		$this->groupManager = $this->createMock(IGroupManager::class);
262
-		$this->coordinator = $this->createMock(Coordinator::class);
263
-		$this->config = $this->createMock(IConfig::class);
264
-		$this->appConfig = $this->createMock(IAppConfig::class);
265
-		$this->logger = $this->createMock(LoggerInterface::class);
266
-		$this->crypto = $this->createMock(ICrypto::class);
267
-
268
-		$this->declarativeManager = new DeclarativeManager(
269
-			$this->eventDispatcher,
270
-			$this->groupManager,
271
-			$this->coordinator,
272
-			$this->config,
273
-			$this->appConfig,
274
-			$this->logger,
275
-			$this->crypto,
276
-		);
277
-
278
-		$this->user = $this->createMock(IUser::class);
279
-		$this->user->expects($this->any())
280
-			->method('getUID')
281
-			->willReturn('test_user');
282
-
283
-		$this->adminUser = $this->createMock(IUser::class);
284
-		$this->adminUser->expects($this->any())
285
-			->method('getUID')
286
-			->willReturn('admin_test_user');
287
-
288
-		$this->groupManager->expects($this->any())
289
-			->method('isAdmin')
290
-			->willReturnCallback(function ($userId) {
291
-				return $userId === 'admin_test_user';
292
-			});
293
-	}
294
-
295
-	public function testRegisterSchema(): void {
296
-		$app = 'testing';
297
-		$schema = self::validSchemaAllFields;
298
-		$this->declarativeManager->registerSchema($app, $schema);
299
-		$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']);
300
-		$this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app]));
301
-	}
302
-
303
-	/**
304
-	 * Simple test to verify that exception is thrown when trying to register schema with duplicate id
305
-	 */
306
-	public function testRegisterDuplicateSchema(): void {
307
-		$this->declarativeManager->registerSchema('testing', self::validSchemaAllFields);
308
-		$this->expectException(\Exception::class);
309
-		$this->declarativeManager->registerSchema('testing', self::validSchemaAllFields);
310
-	}
311
-
312
-	/**
313
-	 * It's not allowed to register schema with duplicate fields ids for the same app
314
-	 */
315
-	public function testRegisterSchemaWithDuplicateFields(): void {
316
-		// Register first valid schema
317
-		$this->declarativeManager->registerSchema('testing', self::validSchemaAllFields);
318
-		// Register second schema with duplicate fields, but different schema id
319
-		$this->expectException(\Exception::class);
320
-		$schema = self::validSchemaAllFields;
321
-		$schema['id'] = 'test_form_2';
322
-		$this->declarativeManager->registerSchema('testing', $schema);
323
-	}
324
-
325
-	public function testRegisterMultipleSchemasAndDuplicate(): void {
326
-		$app = 'testing';
327
-		$schema = self::validSchemaAllFields;
328
-		$this->declarativeManager->registerSchema($app, $schema);
329
-		$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']);
330
-		// 1. Check that form is registered for the app
331
-		$this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app]));
332
-		$app = 'testing2';
333
-		$this->declarativeManager->registerSchema($app, $schema);
334
-		$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']);
335
-		// 2. Check that form is registered for the second app
336
-		$this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app]));
337
-		$app = 'testing';
338
-		$this->expectException(\Exception::class); // expecting duplicate form id and duplicate fields ids exception
339
-		$this->declarativeManager->registerSchema($app, $schema);
340
-		$schemaDuplicateFields = self::validSchemaAllFields;
341
-		$schemaDuplicateFields['id'] = 'test_form_2'; // change form id to test duplicate fields
342
-		$this->declarativeManager->registerSchema($app, $schemaDuplicateFields);
343
-		// 3. Check that not valid form with duplicate fields is not registered
344
-		$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schemaDuplicateFields['section_type'], $schemaDuplicateFields['section_id']);
345
-		$this->assertFalse(isset($formIds[$app]) && in_array($schemaDuplicateFields['id'], $formIds[$app]));
346
-	}
347
-
348
-	/**
349
-	 * @dataProvider dataValidateSchema
350
-	 */
351
-	public function testValidateSchema(bool $expected, bool $expectException, string $app, array $schema): void {
352
-		if ($expectException) {
353
-			$this->expectException(\Exception::class);
354
-		}
355
-		$this->declarativeManager->registerSchema($app, $schema);
356
-		$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']);
357
-		$this->assertEquals($expected, isset($formIds[$app]) && in_array($schema['id'], $formIds[$app]));
358
-	}
359
-
360
-	public static function dataValidateSchema(): array {
361
-		return [
362
-			'valid schema with all supported fields' => [
363
-				true,
364
-				false,
365
-				'testing',
366
-				self::validSchemaAllFields,
367
-			],
368
-			'invalid schema with missing id' => [
369
-				false,
370
-				true,
371
-				'testing',
372
-				[
373
-					'priority' => 10,
374
-					'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN,
375
-					'section_id' => 'additional',
376
-					'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL,
377
-					'title' => 'Test declarative settings',
378
-					'description' => 'These fields are rendered dynamically from declarative schema',
379
-					'doc_url' => '',
380
-					'fields' => [
381
-						[
382
-							'id' => 'test_field_7',
383
-							'title' => 'Multi-selection',
384
-							'description' => 'Select some option setting',
385
-							'type' => DeclarativeSettingsTypes::MULTI_SELECT,
386
-							'options' => ['foo', 'bar', 'baz'],
387
-							'placeholder' => 'Select some multiple options',
388
-							'default' => ['foo', 'bar'],
389
-						],
390
-					],
391
-				],
392
-			],
393
-			'invalid schema with invalid field' => [
394
-				false,
395
-				true,
396
-				'testing',
397
-				[
398
-					'id' => 'test_form_1',
399
-					'priority' => 10,
400
-					'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN,
401
-					'section_id' => 'additional',
402
-					'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL,
403
-					'title' => 'Test declarative settings',
404
-					'description' => 'These fields are rendered dynamically from declarative schema',
405
-					'doc_url' => '',
406
-					'fields' => [
407
-						[
408
-							'id' => 'test_invalid_field',
409
-							'title' => 'Invalid field',
410
-							'description' => 'Some invalid setting description',
411
-							'type' => 'some_invalid_type',
412
-							'placeholder' => 'Some invalid field placeholder',
413
-							'default' => null,
414
-						],
415
-					],
416
-				],
417
-			],
418
-		];
419
-	}
420
-
421
-	public function testGetFormIDs(): void {
422
-		$app = 'testing';
423
-		$schema = self::validSchemaAllFields;
424
-		$this->declarativeManager->registerSchema($app, $schema);
425
-		$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']);
426
-		$this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app]));
427
-		$app = 'testing2';
428
-		$this->declarativeManager->registerSchema($app, $schema);
429
-		$formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']);
430
-		$this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app]));
431
-	}
432
-
433
-	/**
434
-	 * Check that form with default values is returned with internal storage_type
435
-	 */
436
-	public function testGetFormsWithDefaultValues(): void {
437
-		$app = 'testing';
438
-		$schema = self::validSchemaAllFields;
439
-		$this->declarativeManager->registerSchema($app, $schema);
440
-
441
-		$this->config->expects($this->any())
442
-			->method('getAppValue')
443
-			->willReturnCallback(fn ($app, $configkey, $default) => $default);
444
-
445
-		$forms = $this->declarativeManager->getFormsWithValues($this->adminUser, $schema['section_type'], $schema['section_id']);
446
-		$this->assertNotEmpty($forms);
447
-		$this->assertTrue(array_search($schema['id'], array_column($forms, 'id')) !== false);
448
-		// Check some_real_setting field default value
449
-		$someRealSettingField = array_values(array_filter(array_filter($forms, fn ($form) => $form['id'] === $schema['id'])[0]['fields'], fn ($field) => $field['id'] === 'some_real_setting'))[0];
450
-		$schemaSomeRealSettingField = array_values(array_filter($schema['fields'], fn ($field) => $field['id'] === 'some_real_setting'))[0];
451
-		$this->assertEquals($schemaSomeRealSettingField['default'], $someRealSettingField['default']);
452
-	}
453
-
454
-	/**
455
-	 * Check values in json format to ensure that they are properly encoded
456
-	 */
457
-	public function testGetFormsWithDefaultValuesJson(): void {
458
-		$app = 'testing';
459
-		$schema = [
460
-			'id' => 'test_form_1',
461
-			'priority' => 10,
462
-			'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL,
463
-			'section_id' => 'additional',
464
-			'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL,
465
-			'title' => 'Test declarative settings',
466
-			'description' => 'These fields are rendered dynamically from declarative schema',
467
-			'doc_url' => '',
468
-			'fields' => [
469
-				[
470
-					'id' => 'test_field_json',
471
-					'title' => 'Multi-selection',
472
-					'description' => 'Select some option setting',
473
-					'type' => DeclarativeSettingsTypes::MULTI_SELECT,
474
-					'options' => ['foo', 'bar', 'baz'],
475
-					'placeholder' => 'Select some multiple options',
476
-					'default' => ['foo', 'bar'],
477
-				],
478
-			],
479
-		];
480
-		$this->declarativeManager->registerSchema($app, $schema);
481
-
482
-		// config->getUserValue() should be called with json encoded default value
483
-		$this->config->expects($this->once())
484
-			->method('getUserValue')
485
-			->with($this->adminUser->getUID(), $app, 'test_field_json', json_encode($schema['fields'][0]['default']))
486
-			->willReturn(json_encode($schema['fields'][0]['default']));
487
-
488
-		$forms = $this->declarativeManager->getFormsWithValues($this->adminUser, $schema['section_type'], $schema['section_id']);
489
-		$this->assertNotEmpty($forms);
490
-		$this->assertTrue(array_search($schema['id'], array_column($forms, 'id')) !== false);
491
-		$testFieldJson = array_values(array_filter(array_filter($forms, fn ($form) => $form['id'] === $schema['id'])[0]['fields'], fn ($field) => $field['id'] === 'test_field_json'))[0];
492
-		$this->assertEquals(json_encode($schema['fields'][0]['default']), $testFieldJson['value']);
493
-	}
494
-
495
-	/**
496
-	 * Check that saving value for field with internal storage_type is handled by core
497
-	 */
498
-	public function testSetInternalValue(): void {
499
-		$app = 'testing';
500
-		$schema = self::validSchemaAllFields;
501
-		$this->declarativeManager->registerSchema($app, $schema);
502
-		self::$testSetInternalValueAfterChange = false;
503
-
504
-		$this->config->expects($this->any())
505
-			->method('getAppValue')
506
-			->willReturnCallback(function ($app, $configkey, $default) {
507
-				if ($configkey === 'some_real_setting' && self::$testSetInternalValueAfterChange) {
508
-					return '120m';
509
-				}
510
-				return $default;
511
-			});
512
-
513
-		$this->appConfig->expects($this->once())
514
-			->method('setValueString')
515
-			->with($app, 'some_real_setting', '120m');
516
-
517
-		$forms = $this->declarativeManager->getFormsWithValues($this->adminUser, $schema['section_type'], $schema['section_id']);
518
-		$someRealSettingField = array_values(array_filter(array_filter($forms, fn ($form) => $form['id'] === $schema['id'])[0]['fields'], fn ($field) => $field['id'] === 'some_real_setting'))[0];
519
-		$this->assertEquals('40m', $someRealSettingField['value']); // first check that default value (40m) is returned
520
-
521
-		// Set new value for some_real_setting field
522
-		$this->declarativeManager->setValue($this->adminUser, $app, $schema['id'], 'some_real_setting', '120m');
523
-		self::$testSetInternalValueAfterChange = true;
524
-
525
-		$forms = $this->declarativeManager->getFormsWithValues($this->adminUser, $schema['section_type'], $schema['section_id']);
526
-		$this->assertNotEmpty($forms);
527
-		$this->assertTrue(array_search($schema['id'], array_column($forms, 'id')) !== false);
528
-		// Check some_real_setting field default value
529
-		$someRealSettingField = array_values(array_filter(array_filter($forms, fn ($form) => $form['id'] === $schema['id'])[0]['fields'], fn ($field) => $field['id'] === 'some_real_setting'))[0];
530
-		$this->assertEquals('120m', $someRealSettingField['value']);
531
-	}
532
-
533
-	public function testSetExternalValue(): void {
534
-		$app = 'testing';
535
-		$schema = self::validSchemaAllFields;
536
-		// Change storage_type to external and section_type to personal
537
-		$schema['storage_type'] = DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL;
538
-		$schema['section_type'] = DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL;
539
-		$this->declarativeManager->registerSchema($app, $schema);
540
-
541
-		$setDeclarativeSettingsValueEvent = new DeclarativeSettingsSetValueEvent(
542
-			$this->adminUser,
543
-			$app,
544
-			$schema['id'],
545
-			'some_real_setting',
546
-			'120m'
547
-		);
548
-
549
-		$this->eventDispatcher->expects($this->once())
550
-			->method('dispatchTyped')
551
-			->with($setDeclarativeSettingsValueEvent);
552
-		$this->declarativeManager->setValue($this->adminUser, $app, $schema['id'], 'some_real_setting', '120m');
553
-	}
554
-
555
-	public function testAdminFormUserUnauthorized(): void {
556
-		$app = 'testing';
557
-		$schema = self::validSchemaAllFields;
558
-		$this->declarativeManager->registerSchema($app, $schema);
559
-
560
-		$this->expectException(\Exception::class);
561
-		$this->declarativeManager->getFormsWithValues($this->user, $schema['section_type'], $schema['section_id']);
562
-	}
563
-
564
-	/**
565
-	 * Ensure that the `setValue` method is called if the form implements the handler interface.
566
-	 */
567
-	public function testSetValueWithHandler(): void {
568
-		$schema = self::validSchemaAllFields;
569
-		$schema['storage_type'] = DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL;
570
-
571
-		$form = $this->createMock(IDeclarativeSettingsFormWithHandlers::class);
572
-		$form->expects(self::atLeastOnce())
573
-			->method('getSchema')
574
-			->willReturn($schema);
575
-		// The setter should be called once!
576
-		$form->expects(self::once())
577
-			->method('setValue')
578
-			->with('test_field_2', 'some password', $this->adminUser);
579
-
580
-		\OC::$server->registerService('OCA\\Testing\\Settings\\DeclarativeForm', fn () => $form, false);
581
-
582
-		$context = $this->createMock(RegistrationContext::class);
583
-		$context->expects(self::atLeastOnce())
584
-			->method('getDeclarativeSettings')
585
-			->willReturn([new ServiceRegistration('testing', 'OCA\\Testing\\Settings\\DeclarativeForm')]);
586
-
587
-		$this->coordinator->expects(self::atLeastOnce())
588
-			->method('getRegistrationContext')
589
-			->willReturn($context);
590
-
591
-		$this->declarativeManager->loadSchemas();
592
-
593
-		$this->eventDispatcher->expects(self::never())
594
-			->method('dispatchTyped');
595
-
596
-		$this->declarativeManager->setValue($this->adminUser, 'testing', 'test_form_1', 'test_field_2', 'some password');
597
-	}
598
-
599
-	public function testGetValueWithHandler(): void {
600
-		$schema = self::validSchemaAllFields;
601
-		$schema['storage_type'] = DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL;
602
-
603
-		$form = $this->createMock(IDeclarativeSettingsFormWithHandlers::class);
604
-		$form->expects(self::atLeastOnce())
605
-			->method('getSchema')
606
-			->willReturn($schema);
607
-		// The setter should be called once!
608
-		$form->expects(self::once())
609
-			->method('getValue')
610
-			->with('test_field_2', $this->adminUser)
611
-			->willReturn('very secret password');
612
-
613
-		\OC::$server->registerService('OCA\\Testing\\Settings\\DeclarativeForm', fn () => $form, false);
614
-
615
-		$context = $this->createMock(RegistrationContext::class);
616
-		$context->expects(self::atLeastOnce())
617
-			->method('getDeclarativeSettings')
618
-			->willReturn([new ServiceRegistration('testing', 'OCA\\Testing\\Settings\\DeclarativeForm')]);
619
-
620
-		$this->coordinator->expects(self::atLeastOnce())
621
-			->method('getRegistrationContext')
622
-			->willReturn($context);
623
-
624
-		$this->declarativeManager->loadSchemas();
625
-
626
-		$this->eventDispatcher->expects(self::never())
627
-			->method('dispatchTyped');
628
-
629
-		$password = $this->invokePrivate($this->declarativeManager, 'getValue', [$this->adminUser, 'testing', 'test_form_1', 'test_field_2']);
630
-		self::assertEquals('very secret password', $password);
631
-	}
33
+    /** @var IDeclarativeManager|MockObject */
34
+    private $declarativeManager;
35
+
36
+    /** @var IEventDispatcher|MockObject */
37
+    private $eventDispatcher;
38
+
39
+    /** @var IGroupManager|MockObject */
40
+    private $groupManager;
41
+
42
+    /** @var Coordinator|MockObject */
43
+    private $coordinator;
44
+
45
+    /** @var IConfig|MockObject */
46
+    private $config;
47
+
48
+    /** @var IAppConfig|MockObject */
49
+    private $appConfig;
50
+
51
+    /** @var LoggerInterface|MockObject */
52
+    private $logger;
53
+
54
+    /** @var ICrypto|MockObject */
55
+    private $crypto;
56
+
57
+    /** @var IUser|MockObject */
58
+    private $user;
59
+
60
+    /** @var IUser|MockObject */
61
+    private $adminUser;
62
+
63
+    private IDeclarativeSettingsForm&MockObject $closureForm;
64
+
65
+    public const validSchemaAllFields = [
66
+        'id' => 'test_form_1',
67
+        'priority' => 10,
68
+        'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN, // admin, personal
69
+        'section_id' => 'additional',
70
+        'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL, // external, internal (handled by core to store in appconfig and preferences)
71
+        'title' => 'Test declarative settings', // NcSettingsSection name
72
+        'description' => 'These fields are rendered dynamically from declarative schema', // NcSettingsSection description
73
+        'doc_url' => '', // NcSettingsSection doc_url for documentation or help page, empty string if not needed
74
+        'fields' => [
75
+            [
76
+                'id' => 'test_field_7', // configkey
77
+                'title' => 'Multi-selection', // name or label
78
+                'description' => 'Select some option setting', // hint
79
+                'type' => DeclarativeSettingsTypes::MULTI_SELECT,
80
+                'options' => ['foo', 'bar', 'baz'], // simple options for select, radio, multi-select
81
+                'placeholder' => 'Select some multiple options', // input placeholder
82
+                'default' => ['foo', 'bar'],
83
+            ],
84
+            [
85
+                'id' => 'some_real_setting',
86
+                'title' => 'Select single option',
87
+                'description' => 'Single option radio buttons',
88
+                'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio)
89
+                'placeholder' => 'Select single option, test interval',
90
+                'default' => '40m',
91
+                'options' => [
92
+                    [
93
+                        'name' => 'Each 40 minutes', // NcCheckboxRadioSwitch display name
94
+                        'value' => '40m' // NcCheckboxRadioSwitch value
95
+                    ],
96
+                    [
97
+                        'name' => 'Each 60 minutes',
98
+                        'value' => '60m'
99
+                    ],
100
+                    [
101
+                        'name' => 'Each 120 minutes',
102
+                        'value' => '120m'
103
+                    ],
104
+                    [
105
+                        'name' => 'Each day',
106
+                        'value' => 60 * 24 . 'm'
107
+                    ],
108
+                ],
109
+            ],
110
+            [
111
+                'id' => 'test_field_1', // configkey
112
+                'title' => 'Default text field', // label
113
+                'description' => 'Set some simple text setting', // hint
114
+                'type' => DeclarativeSettingsTypes::TEXT,
115
+                'placeholder' => 'Enter text setting', // placeholder
116
+                'default' => 'foo',
117
+            ],
118
+            [
119
+                'id' => 'test_field_1_1',
120
+                'title' => 'Email field',
121
+                'description' => 'Set email config',
122
+                'type' => DeclarativeSettingsTypes::EMAIL,
123
+                'placeholder' => 'Enter email',
124
+                'default' => '',
125
+            ],
126
+            [
127
+                'id' => 'test_field_1_2',
128
+                'title' => 'Tel field',
129
+                'description' => 'Set tel config',
130
+                'type' => DeclarativeSettingsTypes::TEL,
131
+                'placeholder' => 'Enter your tel',
132
+                'default' => '',
133
+            ],
134
+            [
135
+                'id' => 'test_field_1_3',
136
+                'title' => 'Url (website) field',
137
+                'description' => 'Set url config',
138
+                'type' => 'url',
139
+                'placeholder' => 'Enter url',
140
+                'default' => '',
141
+            ],
142
+            [
143
+                'id' => 'test_field_1_4',
144
+                'title' => 'Number field',
145
+                'description' => 'Set number config',
146
+                'type' => DeclarativeSettingsTypes::NUMBER,
147
+                'placeholder' => 'Enter number value',
148
+                'default' => 0,
149
+            ],
150
+            [
151
+                'id' => 'test_field_2',
152
+                'title' => 'Password',
153
+                'description' => 'Set some secure value setting',
154
+                'type' => 'password',
155
+                'placeholder' => 'Set secure value',
156
+                'default' => '',
157
+            ],
158
+            [
159
+                'id' => 'test_field_3',
160
+                'title' => 'Selection',
161
+                'description' => 'Select some option setting',
162
+                'type' => DeclarativeSettingsTypes::SELECT,
163
+                'options' => ['foo', 'bar', 'baz'],
164
+                'placeholder' => 'Select some option setting',
165
+                'default' => 'foo',
166
+            ],
167
+            [
168
+                'id' => 'test_field_4',
169
+                'title' => 'Toggle something',
170
+                'description' => 'Select checkbox option setting',
171
+                'type' => DeclarativeSettingsTypes::CHECKBOX,
172
+                'label' => 'Verify something if enabled',
173
+                'default' => false,
174
+            ],
175
+            [
176
+                'id' => 'test_field_5',
177
+                'title' => 'Multiple checkbox toggles, describing one setting, checked options are saved as an JSON object {foo: true, bar: false}',
178
+                'description' => 'Select checkbox option setting',
179
+                'type' => DeclarativeSettingsTypes::MULTI_CHECKBOX,
180
+                'default' => ['foo' => true, 'bar' => true],
181
+                'options' => [
182
+                    [
183
+                        'name' => 'Foo',
184
+                        'value' => 'foo', // multiple-checkbox configkey
185
+                    ],
186
+                    [
187
+                        'name' => 'Bar',
188
+                        'value' => 'bar',
189
+                    ],
190
+                    [
191
+                        'name' => 'Baz',
192
+                        'value' => 'baz',
193
+                    ],
194
+                    [
195
+                        'name' => 'Qux',
196
+                        'value' => 'qux',
197
+                    ],
198
+                ],
199
+            ],
200
+            [
201
+                'id' => 'test_field_6',
202
+                'title' => 'Radio toggles, describing one setting like single select',
203
+                'description' => 'Select radio option setting',
204
+                'type' => DeclarativeSettingsTypes::RADIO, // radio (NcCheckboxRadioSwitch type radio)
205
+                'label' => 'Select single toggle',
206
+                'default' => 'foo',
207
+                'options' => [
208
+                    [
209
+                        'name' => 'First radio', // NcCheckboxRadioSwitch display name
210
+                        'value' => 'foo' // NcCheckboxRadioSwitch value
211
+                    ],
212
+                    [
213
+                        'name' => 'Second radio',
214
+                        'value' => 'bar'
215
+                    ],
216
+                    [
217
+                        'name' => 'Second radio',
218
+                        'value' => 'baz'
219
+                    ],
220
+                ],
221
+            ],
222
+            [
223
+                'id' => 'test_sensitive_field',
224
+                'title' => 'Sensitive text field',
225
+                'description' => 'Set some secure value setting that is stored encrypted',
226
+                'type' => DeclarativeSettingsTypes::TEXT,
227
+                'label' => 'Sensitive field',
228
+                'placeholder' => 'Set secure value',
229
+                'default' => '',
230
+                'sensitive' => true, // only for TEXT, PASSWORD types
231
+            ],
232
+            [
233
+                'id' => 'test_sensitive_field_2',
234
+                'title' => 'Sensitive password field',
235
+                'description' => 'Set some password setting that is stored encrypted',
236
+                'type' => DeclarativeSettingsTypes::PASSWORD,
237
+                'label' => 'Sensitive field',
238
+                'placeholder' => 'Set secure value',
239
+                'default' => '',
240
+                'sensitive' => true, // only for TEXT, PASSWORD types
241
+            ],
242
+            [
243
+                'id' => 'test_non_sensitive_field',
244
+                'title' => 'Password field',
245
+                'description' => 'Set some password setting',
246
+                'type' => DeclarativeSettingsTypes::PASSWORD,
247
+                'label' => 'Password field',
248
+                'placeholder' => 'Set secure value',
249
+                'default' => '',
250
+                'sensitive' => false,
251
+            ],
252
+        ],
253
+    ];
254
+
255
+    public static bool $testSetInternalValueAfterChange = false;
256
+
257
+    protected function setUp(): void {
258
+        parent::setUp();
259
+
260
+        $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
261
+        $this->groupManager = $this->createMock(IGroupManager::class);
262
+        $this->coordinator = $this->createMock(Coordinator::class);
263
+        $this->config = $this->createMock(IConfig::class);
264
+        $this->appConfig = $this->createMock(IAppConfig::class);
265
+        $this->logger = $this->createMock(LoggerInterface::class);
266
+        $this->crypto = $this->createMock(ICrypto::class);
267
+
268
+        $this->declarativeManager = new DeclarativeManager(
269
+            $this->eventDispatcher,
270
+            $this->groupManager,
271
+            $this->coordinator,
272
+            $this->config,
273
+            $this->appConfig,
274
+            $this->logger,
275
+            $this->crypto,
276
+        );
277
+
278
+        $this->user = $this->createMock(IUser::class);
279
+        $this->user->expects($this->any())
280
+            ->method('getUID')
281
+            ->willReturn('test_user');
282
+
283
+        $this->adminUser = $this->createMock(IUser::class);
284
+        $this->adminUser->expects($this->any())
285
+            ->method('getUID')
286
+            ->willReturn('admin_test_user');
287
+
288
+        $this->groupManager->expects($this->any())
289
+            ->method('isAdmin')
290
+            ->willReturnCallback(function ($userId) {
291
+                return $userId === 'admin_test_user';
292
+            });
293
+    }
294
+
295
+    public function testRegisterSchema(): void {
296
+        $app = 'testing';
297
+        $schema = self::validSchemaAllFields;
298
+        $this->declarativeManager->registerSchema($app, $schema);
299
+        $formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']);
300
+        $this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app]));
301
+    }
302
+
303
+    /**
304
+     * Simple test to verify that exception is thrown when trying to register schema with duplicate id
305
+     */
306
+    public function testRegisterDuplicateSchema(): void {
307
+        $this->declarativeManager->registerSchema('testing', self::validSchemaAllFields);
308
+        $this->expectException(\Exception::class);
309
+        $this->declarativeManager->registerSchema('testing', self::validSchemaAllFields);
310
+    }
311
+
312
+    /**
313
+     * It's not allowed to register schema with duplicate fields ids for the same app
314
+     */
315
+    public function testRegisterSchemaWithDuplicateFields(): void {
316
+        // Register first valid schema
317
+        $this->declarativeManager->registerSchema('testing', self::validSchemaAllFields);
318
+        // Register second schema with duplicate fields, but different schema id
319
+        $this->expectException(\Exception::class);
320
+        $schema = self::validSchemaAllFields;
321
+        $schema['id'] = 'test_form_2';
322
+        $this->declarativeManager->registerSchema('testing', $schema);
323
+    }
324
+
325
+    public function testRegisterMultipleSchemasAndDuplicate(): void {
326
+        $app = 'testing';
327
+        $schema = self::validSchemaAllFields;
328
+        $this->declarativeManager->registerSchema($app, $schema);
329
+        $formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']);
330
+        // 1. Check that form is registered for the app
331
+        $this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app]));
332
+        $app = 'testing2';
333
+        $this->declarativeManager->registerSchema($app, $schema);
334
+        $formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']);
335
+        // 2. Check that form is registered for the second app
336
+        $this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app]));
337
+        $app = 'testing';
338
+        $this->expectException(\Exception::class); // expecting duplicate form id and duplicate fields ids exception
339
+        $this->declarativeManager->registerSchema($app, $schema);
340
+        $schemaDuplicateFields = self::validSchemaAllFields;
341
+        $schemaDuplicateFields['id'] = 'test_form_2'; // change form id to test duplicate fields
342
+        $this->declarativeManager->registerSchema($app, $schemaDuplicateFields);
343
+        // 3. Check that not valid form with duplicate fields is not registered
344
+        $formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schemaDuplicateFields['section_type'], $schemaDuplicateFields['section_id']);
345
+        $this->assertFalse(isset($formIds[$app]) && in_array($schemaDuplicateFields['id'], $formIds[$app]));
346
+    }
347
+
348
+    /**
349
+     * @dataProvider dataValidateSchema
350
+     */
351
+    public function testValidateSchema(bool $expected, bool $expectException, string $app, array $schema): void {
352
+        if ($expectException) {
353
+            $this->expectException(\Exception::class);
354
+        }
355
+        $this->declarativeManager->registerSchema($app, $schema);
356
+        $formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']);
357
+        $this->assertEquals($expected, isset($formIds[$app]) && in_array($schema['id'], $formIds[$app]));
358
+    }
359
+
360
+    public static function dataValidateSchema(): array {
361
+        return [
362
+            'valid schema with all supported fields' => [
363
+                true,
364
+                false,
365
+                'testing',
366
+                self::validSchemaAllFields,
367
+            ],
368
+            'invalid schema with missing id' => [
369
+                false,
370
+                true,
371
+                'testing',
372
+                [
373
+                    'priority' => 10,
374
+                    'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN,
375
+                    'section_id' => 'additional',
376
+                    'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL,
377
+                    'title' => 'Test declarative settings',
378
+                    'description' => 'These fields are rendered dynamically from declarative schema',
379
+                    'doc_url' => '',
380
+                    'fields' => [
381
+                        [
382
+                            'id' => 'test_field_7',
383
+                            'title' => 'Multi-selection',
384
+                            'description' => 'Select some option setting',
385
+                            'type' => DeclarativeSettingsTypes::MULTI_SELECT,
386
+                            'options' => ['foo', 'bar', 'baz'],
387
+                            'placeholder' => 'Select some multiple options',
388
+                            'default' => ['foo', 'bar'],
389
+                        ],
390
+                    ],
391
+                ],
392
+            ],
393
+            'invalid schema with invalid field' => [
394
+                false,
395
+                true,
396
+                'testing',
397
+                [
398
+                    'id' => 'test_form_1',
399
+                    'priority' => 10,
400
+                    'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_ADMIN,
401
+                    'section_id' => 'additional',
402
+                    'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL,
403
+                    'title' => 'Test declarative settings',
404
+                    'description' => 'These fields are rendered dynamically from declarative schema',
405
+                    'doc_url' => '',
406
+                    'fields' => [
407
+                        [
408
+                            'id' => 'test_invalid_field',
409
+                            'title' => 'Invalid field',
410
+                            'description' => 'Some invalid setting description',
411
+                            'type' => 'some_invalid_type',
412
+                            'placeholder' => 'Some invalid field placeholder',
413
+                            'default' => null,
414
+                        ],
415
+                    ],
416
+                ],
417
+            ],
418
+        ];
419
+    }
420
+
421
+    public function testGetFormIDs(): void {
422
+        $app = 'testing';
423
+        $schema = self::validSchemaAllFields;
424
+        $this->declarativeManager->registerSchema($app, $schema);
425
+        $formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']);
426
+        $this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app]));
427
+        $app = 'testing2';
428
+        $this->declarativeManager->registerSchema($app, $schema);
429
+        $formIds = $this->declarativeManager->getFormIDs($this->adminUser, $schema['section_type'], $schema['section_id']);
430
+        $this->assertTrue(isset($formIds[$app]) && in_array($schema['id'], $formIds[$app]));
431
+    }
432
+
433
+    /**
434
+     * Check that form with default values is returned with internal storage_type
435
+     */
436
+    public function testGetFormsWithDefaultValues(): void {
437
+        $app = 'testing';
438
+        $schema = self::validSchemaAllFields;
439
+        $this->declarativeManager->registerSchema($app, $schema);
440
+
441
+        $this->config->expects($this->any())
442
+            ->method('getAppValue')
443
+            ->willReturnCallback(fn ($app, $configkey, $default) => $default);
444
+
445
+        $forms = $this->declarativeManager->getFormsWithValues($this->adminUser, $schema['section_type'], $schema['section_id']);
446
+        $this->assertNotEmpty($forms);
447
+        $this->assertTrue(array_search($schema['id'], array_column($forms, 'id')) !== false);
448
+        // Check some_real_setting field default value
449
+        $someRealSettingField = array_values(array_filter(array_filter($forms, fn ($form) => $form['id'] === $schema['id'])[0]['fields'], fn ($field) => $field['id'] === 'some_real_setting'))[0];
450
+        $schemaSomeRealSettingField = array_values(array_filter($schema['fields'], fn ($field) => $field['id'] === 'some_real_setting'))[0];
451
+        $this->assertEquals($schemaSomeRealSettingField['default'], $someRealSettingField['default']);
452
+    }
453
+
454
+    /**
455
+     * Check values in json format to ensure that they are properly encoded
456
+     */
457
+    public function testGetFormsWithDefaultValuesJson(): void {
458
+        $app = 'testing';
459
+        $schema = [
460
+            'id' => 'test_form_1',
461
+            'priority' => 10,
462
+            'section_type' => DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL,
463
+            'section_id' => 'additional',
464
+            'storage_type' => DeclarativeSettingsTypes::STORAGE_TYPE_INTERNAL,
465
+            'title' => 'Test declarative settings',
466
+            'description' => 'These fields are rendered dynamically from declarative schema',
467
+            'doc_url' => '',
468
+            'fields' => [
469
+                [
470
+                    'id' => 'test_field_json',
471
+                    'title' => 'Multi-selection',
472
+                    'description' => 'Select some option setting',
473
+                    'type' => DeclarativeSettingsTypes::MULTI_SELECT,
474
+                    'options' => ['foo', 'bar', 'baz'],
475
+                    'placeholder' => 'Select some multiple options',
476
+                    'default' => ['foo', 'bar'],
477
+                ],
478
+            ],
479
+        ];
480
+        $this->declarativeManager->registerSchema($app, $schema);
481
+
482
+        // config->getUserValue() should be called with json encoded default value
483
+        $this->config->expects($this->once())
484
+            ->method('getUserValue')
485
+            ->with($this->adminUser->getUID(), $app, 'test_field_json', json_encode($schema['fields'][0]['default']))
486
+            ->willReturn(json_encode($schema['fields'][0]['default']));
487
+
488
+        $forms = $this->declarativeManager->getFormsWithValues($this->adminUser, $schema['section_type'], $schema['section_id']);
489
+        $this->assertNotEmpty($forms);
490
+        $this->assertTrue(array_search($schema['id'], array_column($forms, 'id')) !== false);
491
+        $testFieldJson = array_values(array_filter(array_filter($forms, fn ($form) => $form['id'] === $schema['id'])[0]['fields'], fn ($field) => $field['id'] === 'test_field_json'))[0];
492
+        $this->assertEquals(json_encode($schema['fields'][0]['default']), $testFieldJson['value']);
493
+    }
494
+
495
+    /**
496
+     * Check that saving value for field with internal storage_type is handled by core
497
+     */
498
+    public function testSetInternalValue(): void {
499
+        $app = 'testing';
500
+        $schema = self::validSchemaAllFields;
501
+        $this->declarativeManager->registerSchema($app, $schema);
502
+        self::$testSetInternalValueAfterChange = false;
503
+
504
+        $this->config->expects($this->any())
505
+            ->method('getAppValue')
506
+            ->willReturnCallback(function ($app, $configkey, $default) {
507
+                if ($configkey === 'some_real_setting' && self::$testSetInternalValueAfterChange) {
508
+                    return '120m';
509
+                }
510
+                return $default;
511
+            });
512
+
513
+        $this->appConfig->expects($this->once())
514
+            ->method('setValueString')
515
+            ->with($app, 'some_real_setting', '120m');
516
+
517
+        $forms = $this->declarativeManager->getFormsWithValues($this->adminUser, $schema['section_type'], $schema['section_id']);
518
+        $someRealSettingField = array_values(array_filter(array_filter($forms, fn ($form) => $form['id'] === $schema['id'])[0]['fields'], fn ($field) => $field['id'] === 'some_real_setting'))[0];
519
+        $this->assertEquals('40m', $someRealSettingField['value']); // first check that default value (40m) is returned
520
+
521
+        // Set new value for some_real_setting field
522
+        $this->declarativeManager->setValue($this->adminUser, $app, $schema['id'], 'some_real_setting', '120m');
523
+        self::$testSetInternalValueAfterChange = true;
524
+
525
+        $forms = $this->declarativeManager->getFormsWithValues($this->adminUser, $schema['section_type'], $schema['section_id']);
526
+        $this->assertNotEmpty($forms);
527
+        $this->assertTrue(array_search($schema['id'], array_column($forms, 'id')) !== false);
528
+        // Check some_real_setting field default value
529
+        $someRealSettingField = array_values(array_filter(array_filter($forms, fn ($form) => $form['id'] === $schema['id'])[0]['fields'], fn ($field) => $field['id'] === 'some_real_setting'))[0];
530
+        $this->assertEquals('120m', $someRealSettingField['value']);
531
+    }
532
+
533
+    public function testSetExternalValue(): void {
534
+        $app = 'testing';
535
+        $schema = self::validSchemaAllFields;
536
+        // Change storage_type to external and section_type to personal
537
+        $schema['storage_type'] = DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL;
538
+        $schema['section_type'] = DeclarativeSettingsTypes::SECTION_TYPE_PERSONAL;
539
+        $this->declarativeManager->registerSchema($app, $schema);
540
+
541
+        $setDeclarativeSettingsValueEvent = new DeclarativeSettingsSetValueEvent(
542
+            $this->adminUser,
543
+            $app,
544
+            $schema['id'],
545
+            'some_real_setting',
546
+            '120m'
547
+        );
548
+
549
+        $this->eventDispatcher->expects($this->once())
550
+            ->method('dispatchTyped')
551
+            ->with($setDeclarativeSettingsValueEvent);
552
+        $this->declarativeManager->setValue($this->adminUser, $app, $schema['id'], 'some_real_setting', '120m');
553
+    }
554
+
555
+    public function testAdminFormUserUnauthorized(): void {
556
+        $app = 'testing';
557
+        $schema = self::validSchemaAllFields;
558
+        $this->declarativeManager->registerSchema($app, $schema);
559
+
560
+        $this->expectException(\Exception::class);
561
+        $this->declarativeManager->getFormsWithValues($this->user, $schema['section_type'], $schema['section_id']);
562
+    }
563
+
564
+    /**
565
+     * Ensure that the `setValue` method is called if the form implements the handler interface.
566
+     */
567
+    public function testSetValueWithHandler(): void {
568
+        $schema = self::validSchemaAllFields;
569
+        $schema['storage_type'] = DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL;
570
+
571
+        $form = $this->createMock(IDeclarativeSettingsFormWithHandlers::class);
572
+        $form->expects(self::atLeastOnce())
573
+            ->method('getSchema')
574
+            ->willReturn($schema);
575
+        // The setter should be called once!
576
+        $form->expects(self::once())
577
+            ->method('setValue')
578
+            ->with('test_field_2', 'some password', $this->adminUser);
579
+
580
+        \OC::$server->registerService('OCA\\Testing\\Settings\\DeclarativeForm', fn () => $form, false);
581
+
582
+        $context = $this->createMock(RegistrationContext::class);
583
+        $context->expects(self::atLeastOnce())
584
+            ->method('getDeclarativeSettings')
585
+            ->willReturn([new ServiceRegistration('testing', 'OCA\\Testing\\Settings\\DeclarativeForm')]);
586
+
587
+        $this->coordinator->expects(self::atLeastOnce())
588
+            ->method('getRegistrationContext')
589
+            ->willReturn($context);
590
+
591
+        $this->declarativeManager->loadSchemas();
592
+
593
+        $this->eventDispatcher->expects(self::never())
594
+            ->method('dispatchTyped');
595
+
596
+        $this->declarativeManager->setValue($this->adminUser, 'testing', 'test_form_1', 'test_field_2', 'some password');
597
+    }
598
+
599
+    public function testGetValueWithHandler(): void {
600
+        $schema = self::validSchemaAllFields;
601
+        $schema['storage_type'] = DeclarativeSettingsTypes::STORAGE_TYPE_EXTERNAL;
602
+
603
+        $form = $this->createMock(IDeclarativeSettingsFormWithHandlers::class);
604
+        $form->expects(self::atLeastOnce())
605
+            ->method('getSchema')
606
+            ->willReturn($schema);
607
+        // The setter should be called once!
608
+        $form->expects(self::once())
609
+            ->method('getValue')
610
+            ->with('test_field_2', $this->adminUser)
611
+            ->willReturn('very secret password');
612
+
613
+        \OC::$server->registerService('OCA\\Testing\\Settings\\DeclarativeForm', fn () => $form, false);
614
+
615
+        $context = $this->createMock(RegistrationContext::class);
616
+        $context->expects(self::atLeastOnce())
617
+            ->method('getDeclarativeSettings')
618
+            ->willReturn([new ServiceRegistration('testing', 'OCA\\Testing\\Settings\\DeclarativeForm')]);
619
+
620
+        $this->coordinator->expects(self::atLeastOnce())
621
+            ->method('getRegistrationContext')
622
+            ->willReturn($context);
623
+
624
+        $this->declarativeManager->loadSchemas();
625
+
626
+        $this->eventDispatcher->expects(self::never())
627
+            ->method('dispatchTyped');
628
+
629
+        $password = $this->invokePrivate($this->declarativeManager, 'getValue', [$this->adminUser, 'testing', 'test_form_1', 'test_field_2']);
630
+        self::assertEquals('very secret password', $password);
631
+    }
632 632
 
633 633
 }
Please login to merge, or discard this patch.