Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
4949380
feat(enum): introduce FieldEditPolicy enum
vitormattos Mar 19, 2026
b49552b
refactor(db): replace legacy flags with FieldEditPolicy and FieldExpo…
vitormattos Mar 19, 2026
19cf8a9
refactor(db): update mapper queries to filter by policy enums
vitormattos Mar 19, 2026
2e5b0f4
feat(migration): add edit_policy and exposure_policy columns
vitormattos Mar 19, 2026
aabd13e
refactor(api): declare policy enum types in response definitions
vitormattos Mar 19, 2026
e504e1a
refactor(service): evaluate field access via FieldEditPolicy and Fiel…
vitormattos Mar 19, 2026
d664d6a
refactor(service): validate FieldEditPolicy and FieldExposurePolicy o…
vitormattos Mar 19, 2026
b676616
refactor(service): apply policy enums in field definition service
vitormattos Mar 19, 2026
7daec53
refactor(service): enforce policy enums in field value service
vitormattos Mar 19, 2026
a407a42
refactor(service): use policy enums when importing field data
vitormattos Mar 19, 2026
b96174d
refactor(service): validate policy enum values in import payload
vitormattos Mar 19, 2026
af047f2
refactor(search): filter directory results by FieldExposurePolicy
vitormattos Mar 19, 2026
bb4c41a
refactor(controller): map policy enums in field definition API
vitormattos Mar 19, 2026
9d84442
refactor(controller): map policy enums in field value API
vitormattos Mar 19, 2026
372faf7
feat(openapi): expose FieldEditPolicy and FieldExposurePolicy in spec
vitormattos Mar 19, 2026
b01cf5a
feat(openapi): expose policy enums in administration spec
vitormattos Mar 19, 2026
a365a2e
feat(openapi): expose policy enums in full spec
vitormattos Mar 19, 2026
d0a9971
chore(build): add redocly config for openapi-typescript generation
vitormattos Mar 19, 2026
455c12f
feat(types): regenerate openapi.ts with policy enum types
vitormattos Mar 19, 2026
a6a87dc
feat(types): regenerate openapi-administration.ts with policy enum types
vitormattos Mar 19, 2026
6e75da1
feat(types): regenerate openapi-full.ts with policy enum types
vitormattos Mar 19, 2026
6bec60b
refactor(types): expose FieldEditPolicy and FieldExposurePolicy in index
vitormattos Mar 19, 2026
abd7f04
fix(types): extend generateOcsUrl and generateUrl signatures
vitormattos Mar 19, 2026
01d55af
chore(deps): fix typescript:generate script to use openapi-typescript -t
vitormattos Mar 19, 2026
ef4007a
feat(admin-ui): replace status text with NcChip and add NcActions menu
vitormattos Mar 19, 2026
ab3676e
refactor(personal-ui): adapt field rendering for policy enums
vitormattos Mar 19, 2026
5384ef8
refactor(admin-ui): update user fields dialog for policy enums
vitormattos Mar 19, 2026
cbb4f2b
test(utils): add options null to adminFieldValues fixtures
vitormattos Mar 19, 2026
428723c
test(utils): add options null to fieldOrder fixtures
vitormattos Mar 19, 2026
ccdfdfe
test(e2e): update support helpers for policy enum changes
vitormattos Mar 19, 2026
87b3de7
test(e2e): add layout regression test for admin field list ordering
vitormattos Mar 19, 2026
86be308
test(e2e): update workflow spec for policy enum changes
vitormattos Mar 19, 2026
4c137f7
test(screenshots): fix avatar re-login and handle-testid filter in ge…
vitormattos Mar 19, 2026
af09ab6
docs(screenshots): regenerate admin-catalog screenshot
vitormattos Mar 19, 2026
f72e2c3
docs(screenshots): regenerate admin-catalog thumbnail
vitormattos Mar 19, 2026
e4f5e4c
docs(screenshots): regenerate personal-settings screenshot
vitormattos Mar 19, 2026
fe68b26
docs(screenshots): regenerate personal-settings thumbnail
vitormattos Mar 19, 2026
34f00fc
docs(screenshots): regenerate user-management-dialog thumbnail
vitormattos Mar 19, 2026
3918f2d
docs(screenshots): regenerate workflow-notify-admins thumbnail
vitormattos Mar 19, 2026
9f68fe8
test(api): update base test case for policy enum fields
vitormattos Mar 19, 2026
ec7bd86
test(unit): update FieldDefinitionApiController test for policy enums
vitormattos Mar 19, 2026
dd9f588
test(unit): update FieldValueApiController test for policy enums
vitormattos Mar 19, 2026
7c2da2e
test(unit): update FieldValueAdminApiController test for policy enums
vitormattos Mar 19, 2026
aaa7459
test(unit): update FieldAccessService test for policy enums
vitormattos Mar 19, 2026
5a5e4c8
test(unit): update FieldDefinitionService test for policy enums
vitormattos Mar 19, 2026
37d4cc8
test(unit): update FieldDefinitionValidator test for policy enums
vitormattos Mar 19, 2026
99696f6
test(unit): update FieldValueService test for policy enums
vitormattos Mar 19, 2026
2576639
test(unit): update DataImportService test for policy enums
vitormattos Mar 19, 2026
0d18699
test(unit): update ImportPayloadValidator test for policy enums
vitormattos Mar 19, 2026
cf4bc5c
test(unit): update ProfileFieldDirectorySearchService test for policy…
vitormattos Mar 19, 2026
062bd8b
test(unit): update export command test for policy enums
vitormattos Mar 19, 2026
dc525eb
test(unit): update LogProfileFieldChangeOperation workflow test
vitormattos Mar 19, 2026
53f0ef1
test(unit): update EmailUserProfileFieldChangeOperation workflow test
vitormattos Mar 19, 2026
4790adf
test(unit): update NotifyAdminsOrGroupsProfileFieldChangeOperation test
vitormattos Mar 19, 2026
a3971f6
test(unit): update CreateTalkConversationProfileFieldChangeOperation …
vitormattos Mar 19, 2026
ea9f233
test(unit): update SendWebhookProfileFieldChangeOperation workflow test
vitormattos Mar 19, 2026
76f9528
test(unit): update UserProfileFieldCheck test for policy enums
vitormattos Mar 19, 2026
c5ef995
test(integration): update import command integration test for policy …
vitormattos Mar 19, 2026
3130810
test(integration): update FieldValueAdminApiController integration test
vitormattos Mar 19, 2026
59c1141
test(integration): update FieldValueApiController integration test
vitormattos Mar 19, 2026
dc74146
test(integration): update OrphanedFieldValueCleanupService integratio…
vitormattos Mar 19, 2026
a222c1b
test(integration): update LogProfileFieldChangeOperation integration …
vitormattos Mar 19, 2026
da2039c
test(integration): update UserProfileFieldCheck integration test
vitormattos Mar 19, 2026
6a5242a
feat(admin-ui): announce successful field operations for screen readers
vitormattos Mar 19, 2026
0b5a415
test(integration): migrate api feature scenarios to policy enums
vitormattos Mar 19, 2026
0d2615d
test(integration): migrate unified search feature scenarios to policy…
vitormattos Mar 19, 2026
a0489db
test(integration): migrate workflow feature scenarios to policy enums
vitormattos Mar 19, 2026
c832eb3
test(integration): align FieldDefinitionApiController integration tes…
vitormattos Mar 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
/// <reference types="vite/client" />

declare module '@nextcloud/router' {
export function generateOcsUrl(path: string): string
export function generateUrl(path: string): string
export function generateOcsUrl(path: string, params?: object, options?: object): string
export function generateUrl(path: string, params?: object, options?: object): string
}

interface SettingsUserListRow {
Expand Down
Binary file modified img/screenshots/admin-catalog-thumb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified img/screenshots/admin-catalog.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified img/screenshots/personal-settings-thumb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified img/screenshots/personal-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified img/screenshots/user-management-dialog-thumb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified img/screenshots/workflow-notify-admins-thumb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 12 additions & 24 deletions lib/Controller/FieldDefinitionApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,8 @@ public function index(): DataResponse {
* @param string $fieldKey Immutable unique key of the field
* @param string $label Human-readable label shown in the UI
* @param string $type Value type accepted by the field
* @param bool $adminOnly Whether only admins can edit values for this field
* @param bool $userEditable Whether the owner can edit the field value
* @param bool $userVisible Whether the owner can see the field in personal settings
* @param string $initialVisibility Initial visibility applied to new values
* @param string $editPolicy Whether values are managed by admins only or by users too
* @param string $exposurePolicy Whether the field is hidden or which default visibility new values receive
* @param int $sortOrder Display order used in admin and profile forms
* @param bool $active Whether the definition is currently active
* @param list<string> $options Allowed values for select fields (ignored for other types)
Expand All @@ -72,10 +70,8 @@ public function create(
string $fieldKey,
string $label,
string $type,
bool $adminOnly = false,
bool $userEditable = false,
bool $userVisible = true,
string $initialVisibility = 'private',
string $editPolicy = 'users',
string $exposurePolicy = 'private',
int $sortOrder = 0,
bool $active = true,
array $options = [],
Expand All @@ -85,10 +81,8 @@ public function create(
'field_key' => $fieldKey,
'label' => $label,
'type' => $type,
'admin_only' => $adminOnly,
'user_editable' => $userEditable,
'user_visible' => $userVisible,
'initial_visibility' => $initialVisibility,
'edit_policy' => $editPolicy,
'exposure_policy' => $exposurePolicy,
'sort_order' => $sortOrder,
'active' => $active,
];
Expand All @@ -111,10 +105,8 @@ public function create(
* @param int $id Identifier of the field definition
* @param string $label Human-readable label shown in the UI
* @param string $type Value type accepted by the field
* @param bool $adminOnly Whether only admins can edit values for this field
* @param bool $userEditable Whether the owner can edit the field value
* @param bool $userVisible Whether the owner can see the field in personal settings
* @param string $initialVisibility Initial visibility applied to new values
* @param string $editPolicy Whether values are managed by admins only or by users too
* @param string $exposurePolicy Whether the field is hidden or which default visibility new values receive
* @param int $sortOrder Display order used in admin and profile forms
* @param bool $active Whether the definition is currently active
* @param list<string> $options Allowed values for select fields (ignored for other types)
Expand All @@ -129,10 +121,8 @@ public function update(
int $id,
string $label,
string $type,
bool $adminOnly = false,
bool $userEditable = false,
bool $userVisible = true,
string $initialVisibility = 'private',
string $editPolicy = 'users',
string $exposurePolicy = 'private',
int $sortOrder = 0,
bool $active = true,
array $options = [],
Expand All @@ -146,10 +136,8 @@ public function update(
$payload = [
'label' => $label,
'type' => $type,
'admin_only' => $adminOnly,
'user_editable' => $userEditable,
'user_visible' => $userVisible,
'initial_visibility' => $initialVisibility,
'edit_policy' => $editPolicy,
'exposure_policy' => $exposurePolicy,
'sort_order' => $sortOrder,
'active' => $active,
];
Expand Down
3 changes: 2 additions & 1 deletion lib/Controller/FieldValueApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use InvalidArgumentException;
use OCA\ProfileFields\AppInfo\Application;
use OCA\ProfileFields\Enum\FieldExposurePolicy;
use OCA\ProfileFields\Service\FieldAccessService;
use OCA\ProfileFields\Service\FieldDefinitionService;
use OCA\ProfileFields\Service\FieldValueService;
Expand Down Expand Up @@ -58,7 +59,7 @@ public function index(): DataResponse {
$editableFields = [];

foreach ($definitions as $definition) {
if (!$definition->getUserVisible()) {
if (!FieldExposurePolicy::from($definition->getExposurePolicy())->isUserVisible()) {
continue;
}

Expand Down
33 changes: 10 additions & 23 deletions lib/Db/FieldDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,10 @@
* @method void setLabel(string $value)
* @method string getType()
* @method void setType(string $value)
* @method bool getAdminOnly()
* @method void setAdminOnly(bool $value)
* @method bool getUserEditable()
* @method void setUserEditable(bool $value)
* @method bool getUserVisible()
* @method void setUserVisible(bool $value)
* @method string getInitialVisibility()
* @method void setInitialVisibility(string $value)
* @method string getEditPolicy()
* @method void setEditPolicy(string $value)
* @method string getExposurePolicy()
* @method void setExposurePolicy(string $value)
* @method int getSortOrder()
* @method void setSortOrder(int $value)
* @method bool getActive()
Expand All @@ -43,10 +39,8 @@ class FieldDefinition extends Entity {
protected $fieldKey;
protected $label;
protected $type;
protected $adminOnly;
protected $userEditable;
protected $userVisible;
protected $initialVisibility;
protected $editPolicy;
protected $exposurePolicy;
protected $sortOrder;
protected $active;
protected $options;
Expand All @@ -55,9 +49,6 @@ class FieldDefinition extends Entity {

public function __construct() {
$this->addType('id', 'integer');
$this->addType('adminOnly', 'boolean');
$this->addType('userEditable', 'boolean');
$this->addType('userVisible', 'boolean');
$this->addType('sortOrder', 'integer');
$this->addType('active', 'boolean');
$this->addType('createdAt', 'datetime');
Expand All @@ -70,10 +61,8 @@ public function __construct() {
* field_key: string,
* label: string,
* type: string,
* admin_only: bool,
* user_editable: bool,
* user_visible: bool,
* initial_visibility: string,
* edit_policy: string,
* exposure_policy: string,
* sort_order: int,
* active: bool,
* options: list<string>|null,
Expand All @@ -89,10 +78,8 @@ public function jsonSerialize(): array {
'field_key' => $this->getFieldKey(),
'label' => $this->getLabel(),
'type' => $this->getType(),
'admin_only' => $this->getAdminOnly(),
'user_editable' => $this->getUserEditable(),
'user_visible' => $this->getUserVisible(),
'initial_visibility' => $this->getInitialVisibility(),
'edit_policy' => $this->getEditPolicy(),
'exposure_policy' => $this->getExposurePolicy(),
'sort_order' => $this->getSortOrder(),
'active' => $this->getActive(),
'options' => $rawOptions !== null ? (json_decode($rawOptions, true) ?? null) : null,
Expand Down
24 changes: 8 additions & 16 deletions lib/Db/FieldDefinitionMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,8 @@ public function findByFieldKey(string $fieldKey): ?FieldDefinition {
'field_key',
'label',
'type',
'admin_only',
'user_editable',
'user_visible',
'initial_visibility',
'edit_policy',
'exposure_policy',
'sort_order',
'active',
'options',
Expand All @@ -54,10 +52,8 @@ public function findById(int $id): ?FieldDefinition {
'field_key',
'label',
'type',
'admin_only',
'user_editable',
'user_visible',
'initial_visibility',
'edit_policy',
'exposure_policy',
'sort_order',
'active',
'options',
Expand All @@ -84,10 +80,8 @@ public function findAllOrdered(): array {
'field_key',
'label',
'type',
'admin_only',
'user_editable',
'user_visible',
'initial_visibility',
'edit_policy',
'exposure_policy',
'sort_order',
'active',
'options',
Expand All @@ -111,10 +105,8 @@ public function findActiveOrdered(): array {
'field_key',
'label',
'type',
'admin_only',
'user_editable',
'user_visible',
'initial_visibility',
'edit_policy',
'exposure_policy',
'sort_order',
'active',
'options',
Expand Down
33 changes: 33 additions & 0 deletions lib/Enum/FieldEditPolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/**
* SPDX-FileCopyrightText: 2026 LibreCode coop and LibreCode contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

declare(strict_types=1);

namespace OCA\ProfileFields\Enum;

enum FieldEditPolicy: string {
case ADMINS = 'admins';
case USERS = 'users';

/**
* @return list<string>
*/
public static function values(): array {
return [
self::ADMINS->value,
self::USERS->value,
];
}

public static function isValid(string $value): bool {
return self::tryFrom($value) !== null;
}

public function userCanEdit(): bool {
return $this === self::USERS;
}
}
45 changes: 45 additions & 0 deletions lib/Enum/FieldExposurePolicy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

/**
* SPDX-FileCopyrightText: 2026 LibreCode coop and LibreCode contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

declare(strict_types=1);

namespace OCA\ProfileFields\Enum;

enum FieldExposurePolicy: string {
case HIDDEN = 'hidden';
case PRIVATE = 'private';
case USERS = 'users';
case PUBLIC = 'public';

/**
* @return list<string>
*/
public static function values(): array {
return [
self::HIDDEN->value,
self::PRIVATE->value,
self::USERS->value,
self::PUBLIC->value,
];
}

public static function isValid(string $value): bool {
return self::tryFrom($value) !== null;
}

public function isUserVisible(): bool {
return $this !== self::HIDDEN;
}

public function initialVisibility(): FieldVisibility {
return match ($this) {
self::HIDDEN, self::PRIVATE => FieldVisibility::PRIVATE,
self::USERS => FieldVisibility::USERS,
self::PUBLIC => FieldVisibility::PUBLIC,
};
}
}
13 changes: 4 additions & 9 deletions lib/Migration/Version1000Date20260309120000.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,11 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt
$table->addColumn('type', Types::STRING, [
'length' => 32,
]);
$table->addColumn('admin_only', Types::BOOLEAN, [
'default' => false,
]);
$table->addColumn('user_editable', Types::BOOLEAN, [
'default' => false,
]);
$table->addColumn('user_visible', Types::BOOLEAN, [
'default' => true,
$table->addColumn('edit_policy', Types::STRING, [
'length' => 32,
'default' => 'users',
]);
$table->addColumn('initial_visibility', Types::STRING, [
$table->addColumn('exposure_policy', Types::STRING, [
'length' => 32,
'default' => 'private',
]);
Expand Down
14 changes: 6 additions & 8 deletions lib/ResponseDefinitions.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
/**
* @psalm-type ProfileFieldsType = 'text'|'number'|'select'
* @psalm-type ProfileFieldsVisibility = 'private'|'users'|'public'
* @psalm-type ProfileFieldsEditPolicy = 'admins'|'users'
* @psalm-type ProfileFieldsExposurePolicy = 'hidden'|'private'|'users'|'public'
* @psalm-type ProfileFieldsDefinitionInput = array{
* field_key?: string,
* label?: string,
* type?: string,
* admin_only?: bool,
* user_editable?: bool,
* user_visible?: bool,
* initial_visibility?: string,
* edit_policy?: ProfileFieldsEditPolicy,
* exposure_policy?: ProfileFieldsExposurePolicy,
* sort_order?: int,
* active?: bool,
* options?: list<string>,
Expand All @@ -29,10 +29,8 @@
* field_key: non-empty-string,
* label: non-empty-string,
* type: ProfileFieldsType,
* admin_only: bool,
* user_editable: bool,
* user_visible: bool,
* initial_visibility: ProfileFieldsVisibility,
* edit_policy: ProfileFieldsEditPolicy,
* exposure_policy: ProfileFieldsExposurePolicy,
* sort_order: int,
* active: bool,
* options: list<string>|null,
Expand Down
7 changes: 4 additions & 3 deletions lib/Search/ProfileFieldDirectorySearchService.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use InvalidArgumentException;
use OCA\ProfileFields\Db\FieldValue;
use OCA\ProfileFields\Db\FieldValueMapper;
use OCA\ProfileFields\Enum\FieldExposurePolicy;
use OCA\ProfileFields\Enum\FieldVisibility;
use OCA\ProfileFields\Service\FieldDefinitionService;
use OCP\IGroupManager;
Expand Down Expand Up @@ -72,7 +73,7 @@ public function search(?IUser $actor, string $term, int $limit, int $offset): ar
continue;
}

if (!$this->isSearchableForActor($definition->getUserVisible(), $value->getCurrentVisibility(), $actorIsAdmin, $actorUid !== null)) {
if (!$this->isSearchableForActor(FieldExposurePolicy::from($definition->getExposurePolicy()), $value->getCurrentVisibility(), $actorIsAdmin, $actorUid !== null)) {
continue;
}

Expand Down Expand Up @@ -123,12 +124,12 @@ private function extractScalarValue(FieldValue $value): ?string {
return trim((string)$scalar);
}

private function isSearchableForActor(bool $fieldIsUserVisible, string $currentVisibility, bool $actorIsAdmin, bool $actorIsAuthenticated): bool {
private function isSearchableForActor(FieldExposurePolicy $exposurePolicy, string $currentVisibility, bool $actorIsAdmin, bool $actorIsAuthenticated): bool {
if ($actorIsAdmin) {
return true;
}

if (!$fieldIsUserVisible) {
if (!$exposurePolicy->isUserVisible()) {
return false;
}

Expand Down
Loading
Loading