diff --git a/resources/js/app/app.js b/resources/js/app/app.js
index 746b02120..dfc296d7a 100644
--- a/resources/js/app/app.js
+++ b/resources/js/app/app.js
@@ -103,7 +103,9 @@ const _vueInstance = new Vue({
FieldGeoCoordinates: () => import(/* webpackChunkName: "field-geo-coordinates" */'app/components/form/field-geo-coordinates'),
FieldInteger: () => import(/* webpackChunkName: "field-integer" */'app/components/form/field-integer'),
FieldJson: () => import(/* webpackChunkName: "field-json" */'app/components/form/field-json'),
+ FieldMultipleCheckboxes: () => import(/* webpackChunkName: "field-multiple-checkboxes" */'app/components/form/field-multiple-checkboxes'),
FieldNumber: () => import(/* webpackChunkName: "field-number" */'app/components/form/field-number'),
+ FieldPassword: () => import(/* webpackChunkName: "field-password" */'app/components/form/field-password'),
FieldPlaintext: () => import(/* webpackChunkName: "field-plaintext" */'app/components/form/field-plaintext'),
FieldRadio: () => import(/* webpackChunkName: "field-radio" */'app/components/form/field-radio'),
FieldSelect: () => import(/* webpackChunkName: "field-select" */'app/components/form/field-select'),
@@ -632,7 +634,9 @@ Vue.component('FieldGeoCoordinates', _vueInstance.$options.components.FieldGeoCo
Vue.component('FieldDate', _vueInstance.$options.components.FieldDate);
Vue.component('FieldInteger', _vueInstance.$options.components.FieldInteger);
Vue.component('FieldJson', _vueInstance.$options.components.FieldJson);
+Vue.component('FieldMultipleCheckboxes', _vueInstance.$options.components.FieldMultipleCheckboxes);
Vue.component('FieldNumber', _vueInstance.$options.components.FieldNumber);
+Vue.component('FieldPassword', _vueInstance.$options.components.FieldPassword);
Vue.component('FieldPlaintext', _vueInstance.$options.components.FieldPlaintext);
Vue.component('FieldRadio', _vueInstance.$options.components.FieldRadio);
Vue.component('FieldSelect', _vueInstance.$options.components.FieldSelect);
diff --git a/resources/js/app/components/fast-create/form-field.vue b/resources/js/app/components/fast-create/form-field.vue
index f2e109370..20b16785b 100644
--- a/resources/js/app/components/fast-create/form-field.vue
+++ b/resources/js/app/components/fast-create/form-field.vue
@@ -114,6 +114,15 @@
@change="update"
v-if="fieldType === 'geo-coordinates'"
/>
+
+
+
+
+
+
@@ -228,6 +247,9 @@ export default {
if (this.field === 'date_ranges') {
return 'calendar';
}
+ if (this.jsonSchema?.type === 'array' && this.jsonSchema?.items?.type === 'string' && this.jsonSchema?.items?.enum?.length > 0) {
+ return 'multiple-checkboxes';
+ }
if (this.field === 'extra' || ['array','object'].includes(this.jsonSchema?.type) || this.jsonSchema?.oneOf?.filter(one => ['array','object'].includes(one?.type))?.length > 0) {
return 'json';
}
diff --git a/resources/js/app/components/form/field-multiple-checkboxes.vue b/resources/js/app/components/form/field-multiple-checkboxes.vue
new file mode 100644
index 000000000..625272692
--- /dev/null
+++ b/resources/js/app/components/form/field-multiple-checkboxes.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
diff --git a/resources/js/app/components/form/field-password.vue b/resources/js/app/components/form/field-password.vue
new file mode 100644
index 000000000..7eb88baaf
--- /dev/null
+++ b/resources/js/app/components/form/field-password.vue
@@ -0,0 +1,91 @@
+
+
+
+
+
diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php
index 8bc8f9fc9..347252cd7 100644
--- a/src/Controller/AppController.php
+++ b/src/Controller/AppController.php
@@ -14,6 +14,7 @@
use App\Form\Form;
use App\Utility\DateRangesTools;
+use App\Utility\PermissionsTrait;
use Authentication\Identity;
use BEdita\WebTools\ApiClientProvider;
use Cake\Controller\Controller;
@@ -36,6 +37,8 @@
*/
class AppController extends Controller
{
+ use PermissionsTrait;
+
/**
* BEdita4 API client
*
@@ -234,7 +237,7 @@ protected function specialAttributes(array &$data): void
}
$this->decodeJsonAttributes($data);
-
+ $this->prepareRoles($data);
$this->prepareDateRanges($data);
// prepare categories
@@ -299,6 +302,25 @@ protected function prepareDateRanges(array &$data): void
$data['date_ranges'] = DateRangesTools::prepare(Hash::get($data, 'date_ranges'));
}
+ /**
+ * Transform roles data into relations format.
+ *
+ * @param array $data The data to prepare
+ * @return void
+ */
+ protected function prepareRoles(array &$data): void
+ {
+ $roles = (string)Hash::get($data, 'roles');
+ $roles = empty($roles) ? [] : (array)json_decode($roles, true);
+ $data = array_filter($data, fn($key) => $key !== 'roles', ARRAY_FILTER_USE_KEY);
+ if (!empty($roles)) {
+ $data['relations']['roles']['replaceRelated'] = array_map(
+ fn($id) => ['id' => $id, 'type' => 'roles'],
+ array_keys($this->rolesByNames($roles))
+ );
+ }
+ }
+
/**
* Prepare request relation data.
*
@@ -311,7 +333,7 @@ protected function prepareRelations(array &$data): void
if (!empty($data['relations'])) {
$api = [];
foreach ($data['relations'] as $relation => $relationData) {
- $id = $data['id'];
+ $id = Hash::get($data, 'id', null);
foreach ($relationData as $method => $ids) {
$relatedIds = $this->relatedIds($ids);
if ($method === 'replaceRelated' || !empty($relatedIds)) {
@@ -321,7 +343,7 @@ protected function prepareRelations(array &$data): void
}
$data['_api'] = $api;
}
- unset($data['relations']);
+ $data = array_filter($data, fn($key) => $key !== 'relations', ARRAY_FILTER_USE_KEY);
}
/**
diff --git a/src/Controller/Component/ModulesComponent.php b/src/Controller/Component/ModulesComponent.php
index c81918cb5..776fa94f1 100644
--- a/src/Controller/Component/ModulesComponent.php
+++ b/src/Controller/Component/ModulesComponent.php
@@ -523,7 +523,7 @@ public function skipSaveRelated(string $id, array &$relatedData): bool
return true;
}
$methods = (array)Hash::extract($relatedData, '{n}.method');
- if (in_array('addRelated', $methods) || in_array('removeRelated', $methods)) {
+ if (in_array('addRelated', $methods) || in_array('removeRelated', $methods) || empty($id)) {
return false;
}
// check replaceRelated
diff --git a/src/Controller/Component/SchemaComponent.php b/src/Controller/Component/SchemaComponent.php
index 8ac5bfe35..ec0c57a3d 100644
--- a/src/Controller/Component/SchemaComponent.php
+++ b/src/Controller/Component/SchemaComponent.php
@@ -145,8 +145,12 @@ protected function fetchSchema(string $type)
// add special property `roles` to `users`
if ($type === 'users') {
$schema['properties']['roles'] = [
- 'type' => 'string',
- 'enum' => $this->fetchRoles(),
+ 'type' => 'array',
+ 'items' => [
+ 'type' => 'string',
+ 'enum' => $this->fetchRoles(),
+ ],
+ 'uniqueItems' => true,
];
}
$categories = $this->fetchCategories($type);
diff --git a/src/Controller/ModulesController.php b/src/Controller/ModulesController.php
index bc7da310f..f3ea2c756 100644
--- a/src/Controller/ModulesController.php
+++ b/src/Controller/ModulesController.php
@@ -15,7 +15,6 @@
use App\Utility\ApiConfigTrait;
use App\Utility\CacheTools;
use App\Utility\Message;
-use App\Utility\PermissionsTrait;
use BEdita\SDK\BEditaClientException;
use BEdita\WebTools\Utility\ApiTools;
use Cake\Core\Configure;
@@ -45,7 +44,6 @@
class ModulesController extends AppController
{
use ApiConfigTrait;
- use PermissionsTrait;
/**
* Object type currently used
diff --git a/tests/TestCase/Controller/AppControllerTest.php b/tests/TestCase/Controller/AppControllerTest.php
index 3dc064a37..e1b9b76ac 100644
--- a/tests/TestCase/Controller/AppControllerTest.php
+++ b/tests/TestCase/Controller/AppControllerTest.php
@@ -495,6 +495,31 @@ public function prepareRequestProvider(): array
]),
],
],
+ 'roles' => [
+ 'users', // object_type
+ [ // expected
+ 'id' => '5',
+ 'username' => 'mario',
+ '_api' => [
+ [
+ 'method' => 'replaceRelated',
+ 'id' => '5',
+ 'relation' => 'roles',
+ 'relatedIds' => [
+ [
+ 'id' => '1',
+ 'type' => 'roles',
+ ],
+ ],
+ ],
+ ],
+ ],
+ [ // data provided
+ 'id' => '5',
+ 'username' => 'mario',
+ 'roles' => json_encode(['admin']),
+ ],
+ ],
'relations' => [
'documents', // object_type
[
@@ -704,6 +729,7 @@ public function prepareRequestProvider(): array
* @covers ::specialAttributes()
* @covers ::decodeJsonAttributes()
* @covers ::prepareDateRanges()
+ * @covers ::prepareRoles()
* @covers ::prepareRelations()
* @covers ::setupParentsRelation()
* @covers ::changedAttributes()
diff --git a/tests/TestCase/Controller/Component/SchemaComponentTest.php b/tests/TestCase/Controller/Component/SchemaComponentTest.php
index 8d53135cc..65eb4f7fb 100644
--- a/tests/TestCase/Controller/Component/SchemaComponentTest.php
+++ b/tests/TestCase/Controller/Component/SchemaComponentTest.php
@@ -481,8 +481,12 @@ public function testFetchRoles(): void
static::assertNotEmpty($result['properties']['roles']);
$expected = [
- 'type' => 'string',
- 'enum' => ['admin', 'manager'],
+ 'type' => 'array',
+ 'items' => [
+ 'type' => 'string',
+ 'enum' => ['admin', 'manager'],
+ ],
+ 'uniqueItems' => true,
];
static::assertEquals($expected, $result['properties']['roles']);
}