diff --git a/libraries/default/InboxProcessors/UserInboxProcessor.php b/libraries/default/InboxProcessors/UserInboxProcessor.php
index 074ce13..53312d5 100644
--- a/libraries/default/InboxProcessors/UserInboxProcessor.php
+++ b/libraries/default/InboxProcessors/UserInboxProcessor.php
@@ -1,5 +1,6 @@
'E-mail must be valid'
])
->notEmpty('first_name', 'A first name must be provided')
- ->notEmpty('last_name', 'A last name must be provided');
+ ->notEmpty('last_name', 'A last name must be provided')
+ ->add('password', 'password_complexity', [
+ 'rule' => function($value, $context) {
+ if (!preg_match('/^((?=.*\d)|(?=.*\W+))(?![\n])(?=.*[A-Z])(?=.*[a-z]).*$|.{16,}/s', $value) || strlen($value) < 12) {
+ return false;
+ }
+ return true;
+ },
+ 'message' => __('Invalid password. Passwords have to be either 16 character long or 12 character long with 3/4 special groups.')
+ ]);
}
public function create($requestData) {
$this->validateRequestData($requestData);
+ $requestData['data']['password'] = (new DefaultPasswordHasher())->hash($requestData['data']['password']);
$requestData['title'] = __('User account creation requested for {0}', $requestData['data']['email']);
- return parent::create($requestData);
+ $creationResponse = parent::create($requestData);
+ $creationResponse['message'] = __('User account creation requested. Please wait for an admin to approve your account.');
+ return $creationResponse;
}
public function getViewVariables($request)
@@ -72,6 +85,11 @@ class RegistrationProcessor extends UserInboxProcessor implements GenericInboxPr
'username' => !empty($request['data']['username']) ? $request['data']['username'] : '',
'role_id' => !empty($request['data']['role_id']) ? $request['data']['role_id'] : '',
'disabled' => !empty($request['data']['disabled']) ? $request['data']['disabled'] : '',
+
+ 'email' => !empty($request['data']['email']) ? $request['data']['email'] : '',
+ 'first_name' => !empty($request['data']['first_name']) ? $request['data']['first_name'] : '',
+ 'last_name' => !empty($request['data']['last_name']) ? $request['data']['last_name'] : '',
+ 'position' => !empty($request['data']['position']) ? $request['data']['position'] : '',
]);
return [
'dropdownData' => $dropdownData,
@@ -82,6 +100,7 @@ class RegistrationProcessor extends UserInboxProcessor implements GenericInboxPr
public function process($id, $requestData, $inboxRequest)
{
+ $hashedPassword = $inboxRequest['data']['password'];
if ($requestData['individual_id'] == -1) {
$individual = $this->Users->Individuals->newEntity([
'uuid' => $requestData['uuid'],
@@ -101,6 +120,7 @@ class RegistrationProcessor extends UserInboxProcessor implements GenericInboxPr
'role_id' => $requestData['role_id'],
'disabled' => $requestData['disabled'],
]);
+ $user->set('password', $hashedPassword, ['setter' => false]); // ignore default password hashing as it has already been hashed
$user = $this->Users->save($user);
if ($user !== false) {
diff --git a/libraries/default/InboxProcessors/templates/User/Registration.php b/libraries/default/InboxProcessors/templates/User/Registration.php
index ebcc819..2eaffb0 100644
--- a/libraries/default/InboxProcessors/templates/User/Registration.php
+++ b/libraries/default/InboxProcessors/templates/User/Registration.php
@@ -1,99 +1,88 @@
element('genericElements/Form/genericForm', [
- 'entity' => $userEntity,
- 'ajax' => false,
- 'raw' => true,
- 'data' => [
- 'description' => __('Create user account'),
- 'model' => 'User',
- 'fields' => [
- [
- 'field' => 'individual_id',
- 'type' => 'dropdown',
- 'label' => __('Associated individual'),
- 'options' => $dropdownData['individual'],
- ],
- [
- 'field' => 'username',
- 'autocomplete' => 'off',
- ],
- [
- 'field' => 'role_id',
- 'type' => 'dropdown',
- 'label' => __('Role'),
- 'options' => $dropdownData['role']
- ],
- [
- 'field' => 'disabled',
- 'type' => 'checkbox',
- 'label' => 'Disable'
- ]
+$combinedForm = $this->element('genericElements/Form/genericForm', [
+ 'entity' => $userEntity,
+ 'ajax' => false,
+ 'raw' => true,
+ 'data' => [
+ 'description' => __('Create user account'),
+ 'model' => 'User',
+ 'fields' => [
+ [
+ 'field' => 'individual_id',
+ 'type' => 'dropdown',
+ 'label' => __('Associated individual'),
+ 'options' => $dropdownData['individual'],
],
- 'submit' => [
- 'action' => $this->request->getParam('action')
- ]
- ]
- ]);
-
- $formIndividual = $this->element('genericElements/Form/genericForm', [
- 'entity' => $individualEntity,
- 'ajax' => false,
- 'raw' => true,
- 'data' => [
- 'description' => __('Create individual'),
- 'model' => 'Individual',
- 'fields' => [
- [
- 'field' => 'email',
- 'autocomplete' => 'off'
- ],
- [
- 'field' => 'uuid',
- 'label' => 'UUID',
- 'type' => 'uuid',
- 'autocomplete' => 'off'
- ],
- [
- 'field' => 'first_name',
- 'autocomplete' => 'off'
- ],
- [
- 'field' => 'last_name',
- 'autocomplete' => 'off'
- ],
- [
- 'field' => 'position',
- 'autocomplete' => 'off'
- ],
+ [
+ 'field' => 'username',
+ 'autocomplete' => 'off',
+ ],
+ [
+ 'field' => 'role_id',
+ 'type' => 'dropdown',
+ 'label' => __('Role'),
+ 'options' => $dropdownData['role']
+ ],
+ [
+ 'field' => 'disabled',
+ 'type' => 'checkbox',
+ 'label' => 'Disable'
],
- 'submit' => [
- 'action' => $this->request->getParam('action')
- ]
- ]
- ]);
- echo $this->Bootstrap->modal([
- 'title' => __('Register user'),
- 'size' => 'lg',
- 'type' => 'confirm',
- 'bodyHtml' => sprintf('
%s
%s
',
- $formUser,
- $formIndividual
- ),
- 'confirmText' => __('Create user'),
- 'confirmFunction' => 'submitRegistration'
- ]);
+ sprintf('%s
', __('Create individual')),
+ [
+ 'field' => 'email',
+ 'autocomplete' => 'off'
+ ],
+ [
+ 'field' => 'uuid',
+ 'label' => 'UUID',
+ 'type' => 'uuid',
+ 'autocomplete' => 'off'
+ ],
+ [
+ 'field' => 'first_name',
+ 'autocomplete' => 'off'
+ ],
+ [
+ 'field' => 'last_name',
+ 'autocomplete' => 'off'
+ ],
+ [
+ 'field' => 'position',
+ 'autocomplete' => 'off'
+ ],
+ ],
+ 'submit' => [
+ 'action' => $this->request->getParam('action')
+ ]
+ ]
+]);
+
+
+echo $this->Bootstrap->modal([
+ 'title' => __('Register user'),
+ 'size' => 'lg',
+ 'type' => 'confirm',
+ 'bodyHtml' => sprintf(
+ '%s
',
+ $combinedForm
+ ),
+ 'confirmText' => __('Create user'),
+ 'confirmFunction' => 'submitRegistration'
+]);
?>
\ No newline at end of file
diff --git a/plugins/Tags/webroot/js/tagging.js b/plugins/Tags/webroot/js/tagging.js
index 606ad21..bcd9c3d 100644
--- a/plugins/Tags/webroot/js/tagging.js
+++ b/plugins/Tags/webroot/js/tagging.js
@@ -119,8 +119,10 @@ function initSelect2Picker($select) {
}
return buildTag(state)
}
+ const $modal = $select.closest('.modal')
$select.select2({
+ dropdownParent: $modal.length != 0 ? $modal.find('.modal-body') : $(document.body),
placeholder: 'Pick a tag',
tags: true,
width: '100%',
diff --git a/src/Controller/Component/ACLComponent.php b/src/Controller/Component/ACLComponent.php
index 1376d96..5feae97 100644
--- a/src/Controller/Component/ACLComponent.php
+++ b/src/Controller/Component/ACLComponent.php
@@ -254,7 +254,7 @@ class ACLComponent extends Component
*/
public function setPublicInterfaces(): void
{
- $this->Authentication->allowUnauthenticated(['login']);
+ $this->Authentication->allowUnauthenticated(['login', 'register']);
}
private function checkAccessInternal($controller, $action, $soft): bool
diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php
index 585eeba..64b5777 100644
--- a/src/Controller/Component/CRUDComponent.php
+++ b/src/Controller/Component/CRUDComponent.php
@@ -90,7 +90,7 @@ class CRUDComponent extends Component
$this->Controller->set('taggingEnabled', true);
$this->setAllTags();
}
- $filters = !empty($this->Controller->filters) ? $this->Controller->filters : [];
+ $filters = !empty($this->Controller->filterFields) ? $this->Controller->filterFields : [];
$this->Controller->set('filters', $filters);
$this->Controller->viewBuilder()->setLayout('ajax');
$this->Controller->render('/genericTemplates/filters');
diff --git a/src/Controller/Component/Navigation/sidemenu.php b/src/Controller/Component/Navigation/sidemenu.php
index e56ac7f..16dd350 100644
--- a/src/Controller/Component/Navigation/sidemenu.php
+++ b/src/Controller/Component/Navigation/sidemenu.php
@@ -63,6 +63,11 @@ class Sidemenu {
'icon' => $this->iconTable['UserSettings'],
'url' => '/user-settings/index',
],
+ 'LocalTools.index' => [
+ 'label' => __('Local Tools'),
+ 'icon' => $this->iconTable['LocalTools'],
+ 'url' => '/localTools/index',
+ ],
'Messages' => [
'label' => __('Messages'),
'icon' => $this->iconTable['Inbox'],
@@ -87,11 +92,6 @@ class Sidemenu {
'icon' => $this->iconTable['MetaTemplates'],
'url' => '/metaTemplates/index',
],
- 'LocalTools.index' => [
- 'label' => __('Local Tools'),
- 'icon' => $this->iconTable['LocalTools'],
- 'url' => '/localTools/index',
- ],
'Tags.index' => [
'label' => __('Tags'),
'icon' => $this->iconTable['Tags'],
diff --git a/src/Controller/UsersController.php b/src/Controller/UsersController.php
index 2205f08..d2235a6 100644
--- a/src/Controller/UsersController.php
+++ b/src/Controller/UsersController.php
@@ -6,6 +6,8 @@ use Cake\Utility\Hash;
use Cake\Utility\Text;
use Cake\ORM\TableRegistry;
use \Cake\Database\Expression\QueryExpression;
+use Cake\Http\Exception\UnauthorizedException;
+use Cake\Core\Configure;
class UsersController extends AppController
{
@@ -160,19 +162,27 @@ class UsersController extends AppController
public function register()
{
- $this->InboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
- $processor = $this->InboxProcessors->getProcessor('User', 'Registration');
- $data = [
- 'origin' => '127.0.0.1',
- 'comment' => 'Hi there!, please create an account',
- 'data' => [
- 'username' => 'foobar',
- 'email' => 'foobar@admin.test',
- 'first_name' => 'foo',
- 'last_name' => 'bar',
- ],
- ];
- $processorResult = $processor->create($data);
- return $processor->genHTTPReply($this, $processorResult, ['controller' => 'Inbox', 'action' => 'index']);
+ if (empty(Configure::read('Cerebrate')['security.registration.self-registration'])) {
+ throw new UnauthorizedException(__('User self-registration is not open.'));
+ }
+ if ($this->request->is('post')) {
+ $data = $this->request->getData();
+ $this->InboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
+ $processor = $this->InboxProcessors->getProcessor('User', 'Registration');
+ $data = [
+ 'origin' => $this->request->clientIp(),
+ 'comment' => '-no comment-',
+ 'data' => [
+ 'username' => $data['username'],
+ 'email' => $data['email'],
+ 'first_name' => $data['first_name'],
+ 'last_name' => $data['last_name'],
+ 'password' => $data['password'],
+ ],
+ ];
+ $processorResult = $processor->create($data);
+ return $processor->genHTTPReply($this, $processorResult, ['controller' => 'Inbox', 'action' => 'index']);
+ }
+ $this->viewBuilder()->setLayout('login');
}
}
diff --git a/src/Model/Table/SettingProviders/CerebrateSettingsProvider.php b/src/Model/Table/SettingProviders/CerebrateSettingsProvider.php
index 97641d5..25365de 100644
--- a/src/Model/Table/SettingProviders/CerebrateSettingsProvider.php
+++ b/src/Model/Table/SettingProviders/CerebrateSettingsProvider.php
@@ -263,6 +263,16 @@ class CerebrateSettingsProvider extends BaseSettingsProvider
]
],
'Security' => [
+ 'Registration' => [
+ 'Registration' => [
+ 'security.registration.self-registration' => [
+ 'name' => __('Allow self-registration'),
+ 'type' => 'boolean',
+ 'description' => __('Enable the self-registration feature where user can request account creation. Admin can view the request and accept it in the application inbox.'),
+ 'default' => false,
+ ],
+ ]
+ ],
'Development' => [
'Debugging' => [
'debug' => [
diff --git a/templates/Users/login.php b/templates/Users/login.php
index 7cc35d0..d5cab0e 100644
--- a/templates/Users/login.php
+++ b/templates/Users/login.php
@@ -1,53 +1,58 @@
';
- echo sprintf(
- '%s
',
- $this->Html->image('logo-purple.png', [
- 'alt' => __('Cerebrate logo'),
- 'width' => 100, 'height' => 100,
- 'style' => ['filter: drop-shadow(4px 4px 4px #924da666);']
- ])
- );
- echo sprintf('%s
', __('Sign in'));
- $template = [
- 'inputContainer' => '{{content}}
',
- 'formGroup' => '{{input}}{{label}}',
- 'submitContainer' => '{{content}}
',
- ];
- $this->Form->setTemplates($template);
- echo $this->Form->create(null, ['url' => ['controller' => 'users', 'action' => 'login']]);
- echo $this->Form->control('username', ['label' => 'Username', 'class' => 'form-control mb-2', 'placeholder' => __('Username')]);
- echo $this->Form->control('password', ['type' => 'password', 'label' => 'Password', 'class' => 'form-control mb-3', 'placeholder' => __('Password')]);
- echo $this->Form->control(__('Submit'), ['type' => 'submit', 'class' => 'btn btn-primary']);
- echo $this->Form->end();
-
- if (!empty(Configure::read('keycloak'))) {
- echo sprintf('
%s
', __('Or'));
- echo $this->Form->create(null, [
- 'url' => Cake\Routing\Router::url([
- 'prefix' => false,
- 'plugin' => 'ADmad/SocialAuth',
- 'controller' => 'Auth',
- 'action' => 'login',
- 'provider' => 'keycloak',
- '?' => ['redirect' => $this->request->getQuery('redirect')]
- ]),
- ]);
- echo $this->Bootstrap->button([
- 'type' => 'submit',
- 'text' => __('Login with Keycloak'),
- 'variant' => 'secondary',
- 'class' => ['d-block', 'w-100'],
- 'image' => [
- 'path' => '/img/keycloak_logo.png',
- 'alt' => 'Keycloak'
- ]
- ]);
- echo $this->Form->end();
- }
- echo '';
-
+use Cake\Core\Configure;
?>
-
+
+
+ %s
',
+ $this->Html->image('logo-purple.png', [
+ 'alt' => __('Cerebrate logo'),
+ 'width' => 100, 'height' => 100,
+ 'style' => ['filter: drop-shadow(4px 4px 4px #924da666);']
+ ])
+ );
+ echo sprintf('%s
', __('Sign In'));
+ $template = [
+ 'inputContainer' => '{{content}}
',
+ 'formGroup' => '{{input}}{{label}}',
+ 'submitContainer' => '{{content}}
',
+ ];
+ $this->Form->setTemplates($template);
+ echo $this->Form->create(null, ['url' => ['controller' => 'users', 'action' => 'login']]);
+ echo $this->Form->control('username', ['label' => 'Username', 'class' => 'form-control mb-2', 'placeholder' => __('Username')]);
+ echo $this->Form->control('password', ['type' => 'password', 'label' => 'Password', 'class' => 'form-control mb-3', 'placeholder' => __('Password')]);
+ echo $this->Form->control(__('Login'), ['type' => 'submit', 'class' => 'btn btn-primary']);
+ echo $this->Form->end();
+ if (!empty(Configure::read('Cerebrate')['security.registration.self-registration'])) {
+ echo '';
+ echo sprintf('
%s %s', __('Doesn\'t have an account?'), __('Sign up'));
+ echo '
';
+ }
+
+ if (!empty(Configure::read('keycloak'))) {
+ echo sprintf('
%s
', __('Or'));
+ echo $this->Form->create(null, [
+ 'url' => Cake\Routing\Router::url([
+ 'prefix' => false,
+ 'plugin' => 'ADmad/SocialAuth',
+ 'controller' => 'Auth',
+ 'action' => 'login',
+ 'provider' => 'keycloak',
+ '?' => ['redirect' => $this->request->getQuery('redirect')]
+ ]),
+ ]);
+ echo $this->Bootstrap->button([
+ 'type' => 'submit',
+ 'text' => __('Login with Keycloak'),
+ 'variant' => 'secondary',
+ 'class' => ['d-block', 'w-100'],
+ 'image' => [
+ 'path' => '/img/keycloak_logo.png',
+ 'alt' => 'Keycloak'
+ ]
+ ]);
+ echo $this->Form->end();
+ }
+ ?>
+
\ No newline at end of file
diff --git a/templates/Users/register.php b/templates/Users/register.php
new file mode 100644
index 0000000..dd1cba4
--- /dev/null
+++ b/templates/Users/register.php
@@ -0,0 +1,45 @@
+
+
+
+ %s
',
+ $this->Html->image('logo-purple.png', [
+ 'alt' => __('Cerebrate logo'),
+ 'width' => 100, 'height' => 100,
+ 'style' => ['filter: drop-shadow(4px 4px 4px #924da666);']
+ ])
+ );
+ echo sprintf('%s
', __('Sign up'));
+ $template = [
+ 'inputContainer' => '{{content}}
',
+ 'formGroup' => '{{label}}{{input}}',
+ 'submitContainer' => '{{content}}
',
+ 'label' => ''
+ ];
+ $this->Form->setTemplates($template);
+ echo $this->Form->create(null, ['url' => ['controller' => 'users', 'action' => 'register']]);
+
+ echo $this->Form->control('username', ['label' => __('Username'), 'class' => 'form-control mb-2']);
+ echo $this->Form->control('email', ['label' => __('E-mail Address'), 'class' => 'form-control mb-3']);
+
+ echo '';
+ echo '
';
+ echo $this->Form->control('first_name', ['label' => __('First Name'), 'class' => 'form-control']);
+ echo '
';
+ echo '
';
+ echo $this->Form->control('last_name', ['label' => __('Last Name'), 'class' => 'form-control mb-2']);
+ echo '
';
+ echo '
';
+
+ echo $this->Form->control('password', ['type' => 'password', 'label' => __('Password'), 'class' => 'form-control mb-4']);
+
+ echo $this->Form->control(__('Sign up'), ['type' => 'submit', 'class' => 'btn btn-primary']);
+ echo '';
+ echo sprintf('
%s %s', __('Have an account?'), __('Sign in'));
+ echo '
';
+ echo $this->Form->end();
+ ?>
+
\ No newline at end of file
diff --git a/templates/element/genericElements/IndexTable/headers.php b/templates/element/genericElements/IndexTable/headers.php
index 8366449..b949d12 100644
--- a/templates/element/genericElements/IndexTable/headers.php
+++ b/templates/element/genericElements/IndexTable/headers.php
@@ -21,9 +21,14 @@
}
}
+ if (!empty($header['element']) && $header['element'] === 'selector') {
+ $columnName = 'row-selector';
+ } else {
+ $columnName = h(\Cake\Utility\Inflector::variable(!empty($header['name']) ? $header['name'] : \Cake\Utility\Inflector::humanize($header['data_path'])));
+ }
$headersHtml .= sprintf(
'%s | ',
- h(\Cake\Utility\Inflector::variable(!empty($header['name']) ? $header['name'] : \Cake\Utility\Inflector::humanize($header['data_path']))),
+ $columnName,
$header_data
);
}
diff --git a/templates/element/genericElements/ListTopBar/group_table_action/hiddenColumns.php b/templates/element/genericElements/ListTopBar/group_table_action/hiddenColumns.php
index 5d0ce68..9714be4 100644
--- a/templates/element/genericElements/ListTopBar/group_table_action/hiddenColumns.php
+++ b/templates/element/genericElements/ListTopBar/group_table_action/hiddenColumns.php
@@ -4,6 +4,9 @@ $tableSettings['hidden_column'] = $tableSettings['hidden_column'] ?? [];
$availableColumnsHtml = '';
$availableColumns = [];
foreach ($table_data['fields'] as $field) {
+ if (!empty($field['element']) && $field['element'] === 'selector') {
+ continue;
+ }
$fieldName = !empty($field['name']) ? $field['name'] : \Cake\Utility\Inflector::humanize($field['data_path']);
$isVisible = !in_array(h(\Cake\Utility\Inflector::variable($fieldName)), $tableSettings['hidden_column']);
$availableColumns[] = $fieldName;
@@ -62,6 +65,7 @@ echo $availableColumnsHtml;
}
$(document).ready(function() {
+ addSupportOfNestedDropdown();
const $form = $('form.visible-column-form')
const $checkboxes = $form.find('input').not(':checked')
const $dropdownMenu = $form.closest('.dropdown')
diff --git a/templates/genericTemplates/filters.php b/templates/genericTemplates/filters.php
index d51a0b2..bc9698d 100644
--- a/templates/genericTemplates/filters.php
+++ b/templates/genericTemplates/filters.php
@@ -1,6 +1,13 @@
$fieldName,
+ ];
+}, $filters);
+
$filteringForm = $this->Bootstrap->table(
[
'small' => true,
@@ -9,19 +16,36 @@ $filteringForm = $this->Bootstrap->table(
'tableClass' => ['indexFilteringTable'],
],
[
- 'fields' => [
- __('Field'),
- __('Operator'),
- [
- 'labelHtml' => sprintf('%s %s',
- __('Value'),
- sprintf('', __('Supports strict matches and LIKE matches with the `%` character.
Example: `%.com`'))
- )
+ 'fields' => [
+ [
+ 'key' => 'fieldname', 'label' => __('Field'), 'formatter' => function ($field, $row) {
+ return sprintf('%s', h($field), h($field));
+ }
+ ],
+ [
+ 'key' => 'operator', 'label' => __('Operator'), 'formatter' => function ($field, $row) {
+ $options = [
+ sprintf('', '=', '='),
+ sprintf('', '!=', '!='),
+ ];
+ return sprintf('', implode('', $options));
+ }
+ ],
+ [
+ 'key' => 'value',
+ 'labelHtml' => sprintf(
+ '%s %s',
+ __('Value'),
+ sprintf('', __('Supports strict matches and LIKE matches with the `%` character.
Example: `%.com`'))
+ ),
+ 'formatter' => function ($field, $row) {
+ return sprintf('');
+ }
+ ],
],
- __('Action')
- ],
- 'items' => []
-]);
+ 'items' => $tableItems
+ ]
+);
if ($taggingEnabled) {
$helpText = $this->Bootstrap->genNode('sup', [
@@ -40,7 +64,6 @@ if ($taggingEnabled) {
}
$modalBody = sprintf('%s%s', $filteringForm, $filteringTags);
-
echo $this->Bootstrap->modal([
'title' => __('Filtering options for {0}', Inflector::singularize($this->request->getParam('controller'))),
'size' => 'lg',
@@ -69,7 +92,9 @@ echo $this->Bootstrap->modal([
if (rowData['operator'] == '!=') {
fullFilter += ' !='
}
- activeFilters[fullFilter] = rowData['value']
+ if (rowData['value'].length > 0) {
+ activeFilters[fullFilter] = rowData['value']
+ }
})
$select = modalObject.$modal.find('select.tag-input')
activeFilters['filteringTags'] = $select.select2('data').map(tag => tag.text)
@@ -85,8 +110,6 @@ echo $this->Bootstrap->modal([
function initFilteringTable($filteringTable) {
const $controlRow = $filteringTable.find('#controlRow')
- $filteringTable.find('tbody').empty()
- addControlRow($filteringTable)
const randomValue = getRandomValue()
const activeFilters = Object.assign({}, $(`#toggleFilterButton-${randomValue}`).data('activeFilters'))
const tags = activeFilters['filteringTags'] !== undefined ? Object.assign({}, activeFilters)['filteringTags'] : []
@@ -100,7 +123,7 @@ echo $this->Bootstrap->modal([
} else if (fieldParts.length > 2) {
console.error('Field contains multiple spaces. ' + field)
}
- addFilteringRow($filteringTable, field, value, operator)
+ setFilteringValues($filteringTable, field, value, operator)
}
$select = $filteringTable.closest('.modal-body').find('select.tag-input')
let passedTags = []
@@ -118,97 +141,17 @@ echo $this->Bootstrap->modal([
.trigger('change')
}
- function addControlRow($filteringTable) {
- const availableFilters = = json_encode($filters) ?>;
- const $selectField = $('').addClass('fieldSelect form-select form-select-sm')
- availableFilters.forEach(filter => {
- $selectField.append($('').text(filter))
- });
- const $selectOperator = $('').addClass('fieldOperator form-select form-select-sm')
- .append([
- $('').text('=').val('='),
- $('').text('!=').val('!='),
- ])
- const $row = $('
').attr('id', 'controlRow')
- .append(
- $(' | ').append($selectField),
- $(' | ').append($selectOperator),
- $(' | ').append(
- $('').attr('type', 'text').addClass('fieldValue form-control form-control-sm')
- ),
- $(' | ').append(
- $('').attr('type', 'button').addClass('btn btn-sm btn-primary')
- .append($('').addClass('fa fa-plus'))
- .click(addFiltering)
- )
- )
- $filteringTable.append($row)
- }
-
- function addFilteringRow($filteringTable, field, value, operator) {
- const $selectOperator = $('').addClass('fieldOperator form-select form-select-sm')
- .append([
- $('').text('=').val('='),
- $('').text('!=').val('!='),
- ]).val(operator)
- const $row = $('
')
- .append(
- $(' | ').text(field).addClass('fieldName').data('fieldName', field),
- $(' | ').append($selectOperator),
- $(' | ').append(
- $('').attr('type', 'text').addClass('fieldValue form-control form-control-sm').val(value)
- ),
- $(' | ').append(
- $('').attr('type', 'button').addClass('btn btn-sm btn-danger')
- .append($('').addClass('fa fa-trash'))
- .click(removeSelf)
- )
- )
- $filteringTable.append($row)
- const $controlRow = $filteringTable.find('#controlRow')
- disableOptionFromSelect($controlRow, field)
- }
-
- function addFiltering() {
- const $table = $(this).closest('table.indexFilteringTable')
- const $controlRow = $table.find('#controlRow')
- const field = $controlRow.find('select.fieldSelect').val()
- const value = $controlRow.find('input.fieldValue').val()
- const operator = $controlRow.find('input.fieldOperator').val()
- addFilteringRow($table, field, value, operator)
- $controlRow.find('input.fieldValue').val('')
- $controlRow.find('select.fieldSelect').val('')
- }
-
- function removeSelf() {
- const $row = $(this).closest('tr')
- const $controlRow = $row.closest('table.indexFilteringTable').find('#controlRow')
- const field = $row.data('fieldName')
- $row.remove()
- enableOptionFromSelect($controlRow, field)
- }
-
- function disableOptionFromSelect($controlRow, optionName) {
- $controlRow.find('select.fieldSelect option').each(function() {
- const $option = $(this)
- if ($option.text() == optionName) {
- $option.prop('disabled', true)
- }
- });
- }
-
- function enableOptionFromSelect($controlRow, optionName) {
- $controlRow.find('select.fieldSelect option').each(function() {
- const $option = $(this)
- if ($option.text() == optionName) {
- $option.prop('disabled', false)
- }
- });
+ function setFilteringValues($filteringTable, field, value, operator) {
+ $row = $filteringTable.find('td > span.fieldName').filter(function() {
+ return $(this).data('fieldname') == field
+ }).closest('tr')
+ $row.find('.fieldOperator').val(operator)
+ $row.find('.fieldValue').val(value)
}
function getDataFromRow($row) {
const rowData = {};
- rowData['name'] = $row.find('td.fieldName').data('fieldName')
+ rowData['name'] = $row.find('td > span.fieldName').data('fieldname')
rowData['operator'] = $row.find('select.fieldOperator').val()
rowData['value'] = $row.find('input.fieldValue').val()
return rowData
diff --git a/webroot/css/main.css b/webroot/css/main.css
index 8003ccc..def9e0f 100644
--- a/webroot/css/main.css
+++ b/webroot/css/main.css
@@ -94,7 +94,7 @@ input[type="checkbox"]:disabled.change-cursor {
}
.select2-container {
- z-index: 1056;
+ z-index: 900;
}
.select2-container--bootstrap-5 {
@@ -167,4 +167,12 @@ input[type="checkbox"]:disabled.change-cursor {
-webkit-mask-size: contain;
-webkit-mask-repeat: no-repeat;
-webkit-mask-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8' standalone='no'%3F%3E%3Csvg xmlns='http://www.w3.org/2000/svg' height='223.995' width='383.98752' viewBox='0 0 383.98752 223.995' role='img'%3E%3Cpath d='m 367.9975,0 h -118.06 c -21.38,0 -32.09,25.85 -16.97,40.97 l 32.4,32.4 -73.37,73.38 -73.37,-73.37 c -12.5,-12.5 -32.76,-12.5 -45.25,0 l -68.69,68.69 c -6.25,6.25 -6.25,16.38 0,22.63 l 22.62,22.62 c 6.25,6.25 16.38,6.25 22.63,0 l 46.06,-46.07 73.37,73.37 c 12.5,12.5 32.76,12.5 45.25,0 l 96,-96 32.4,32.4 c 15.12,15.12 40.97,4.41 40.97,-16.97 V 16 c 0.01,-8.84 -7.15,-16 -15.99,-16 z' /%3E%3C/svg%3E%0A");
+}
+
+.fs-7 {
+ font-size: .875rem !important;
+}
+
+.fs-8 {
+ font-size: .7rem !important;
}
\ No newline at end of file
diff --git a/webroot/js/main.js b/webroot/js/main.js
index 5b3727b..caf5d24 100644
--- a/webroot/js/main.js
+++ b/webroot/js/main.js
@@ -243,8 +243,13 @@ function overloadBSDropdown() {
return _orginal.call(this);
}
}($bs.Dropdown.prototype.toggle);
+ })(bootstrap);
+}
- document.querySelectorAll('.dropdown').forEach(function (dd) {
+function addSupportOfNestedDropdown() {
+ const CLASS_NAME_HAS_CHILD = 'has-child-dropdown-show';
+ document.querySelectorAll('.dropdown').forEach(function (dd) {
+ if (dd.getAttribute('data-listener-registered') === null) { // Only add listener once
dd.addEventListener('hide.bs.dropdown', function (e) {
if (this.classList.contains(CLASS_NAME_HAS_CHILD)) {
this.classList.remove(CLASS_NAME_HAS_CHILD);
@@ -261,8 +266,9 @@ function overloadBSDropdown() {
}
e.stopPropagation(); // do not need pop in multi level mode
});
- });
- })(bootstrap);
+ dd.setAttribute('data-listener-registered', 1)
+ }
+ });
}
var UI
@@ -271,6 +277,7 @@ $(document).ready(() => {
UI = new UIFactory()
}
overloadBSDropdown();
+ addSupportOfNestedDropdown();
const debouncedGlobalSearch = debounce(performGlobalSearch, 400)
$('#globalSearch')