diff --git a/src/Controller/Component/ACLComponent.php b/src/Controller/Component/ACLComponent.php index ead5a48..a1f06ea 100644 --- a/src/Controller/Component/ACLComponent.php +++ b/src/Controller/Component/ACLComponent.php @@ -98,7 +98,7 @@ class ACLComponent extends Component 'connectLocal' => ['perm_admin'], 'delete' => ['perm_admin'], 'edit' => ['perm_admin'], - 'exposedTools' => ['perm_admin'], + 'exposedTools' => ['OR' => ['perm_admin', 'perm_sync']], 'index' => ['perm_admin'], 'connectorIndex' => ['perm_admin'], 'view' => ['perm_admin'], diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index e2e8fbe..2c4704d 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -165,7 +165,8 @@ class CRUDComponent extends Component } } else { $this->Controller->isFailResponse = true; - $validationMessage = $this->prepareValidationError($data); + $validationErrors = $data->getErrors(); + $validationMessage = $this->prepareValidationMessage($validationErrors); $message = __( '{0} could not be added.{1}', $this->ObjectAlias, @@ -174,7 +175,7 @@ class CRUDComponent extends Component if ($this->Controller->ParamHandler->isRest()) { $this->Controller->restResponsePayload = $this->RestResponse->viewData($message, 'json'); } else if ($this->Controller->ParamHandler->isAjax()) { - $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxFailResponse($this->ObjectAlias, 'add', $data, $message, $validationMessage); + $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxFailResponse($this->ObjectAlias, 'add', $data, $message, $validationErrors); } else { $this->Controller->Flash->error($message); } @@ -183,6 +184,21 @@ class CRUDComponent extends Component $this->Controller->set('entity', $data); } + private function prepareValidationMessage($errors) + { + $validationMessage = ''; + if (!empty($errors)) { + if (count($errors) == 1) { + $field = array_keys($errors)[0]; + $fieldError = implode(', ', array_values($errors[$field])); + $validationMessage = __('{0}: {1}', $field, $fieldError); + } else { + $validationMessage = __('There has been validation issues with multiple fields'); + } + } + return $validationMessage; + } + private function prepareValidationError($data) { $validationMessage = ''; @@ -192,7 +208,7 @@ class CRUDComponent extends Component foreach ($errorData as $key => $value) { $errorMessages[] = $value; } - $validationMessage .= __(' {1}', $field, implode(',', $errorMessages)); + $validationMessage .= __('{0}: {1}', $field, implode(',', $errorMessages)); } } return $validationMessage; @@ -261,14 +277,15 @@ class CRUDComponent extends Component } } } else { - $validationMessage = $this->prepareValidationError($data); + $validationErrors = $data->getErrors(); + $validationMessage = $this->prepareValidationMessage($validationErrors); $message = __( __('{0} could not be modified.'), $this->ObjectAlias ); if ($this->Controller->ParamHandler->isRest()) { } else if ($this->Controller->ParamHandler->isAjax()) { - $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxFailResponse($this->ObjectAlias, 'edit', $data, $message, $data->getErrors()); + $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxFailResponse($this->ObjectAlias, 'edit', $data, $message, $validationErrors); } else { $this->Controller->Flash->error($message); } @@ -699,7 +716,8 @@ class CRUDComponent extends Component } } } else { - $validationMessage = $this->prepareValidationError($data); + $validationErrors = $data->getErrors(); + $validationMessage = $this->prepareValidationMessage($validationErrors); $message = __( '{0} could not be modified.{1}', $this->ObjectAlias, @@ -707,7 +725,7 @@ class CRUDComponent extends Component ); if ($this->Controller->ParamHandler->isRest()) { } else if ($this->Controller->ParamHandler->isAjax()) { - $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxFailResponse($this->ObjectAlias, 'toggle', $message, $validationMessage); + $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxFailResponse($this->ObjectAlias, 'toggle', $message, $validationErrors); } else { $this->Controller->Flash->error($message); if (empty($params['redirect'])) { diff --git a/src/Lib/default/local_tool_connectors/SkeletonConnectorExample.php b/src/Lib/default/local_tool_connectors/SkeletonConnectorExample.php new file mode 100644 index 0000000..ce9b62d --- /dev/null +++ b/src/Lib/default/local_tool_connectors/SkeletonConnectorExample.php @@ -0,0 +1,171 @@ + [ + 'type' => 'index', + 'scope' => 'child', + 'params' => [ + 'quickFilter', + 'sort', + 'direction', + 'page', + 'limit' + ] + ], + 'myFormAction' => [ + 'type' => 'formAction', + 'scope' => 'childAction', + 'params' => [ + 'setting', + 'value' + ], + 'redirect' => 'serverSettingsAction' + ] + ]; + + public function health(Object $connection): array + { + /* + returns an array with 2 keys: + [ + status: the numeric response code (0: UNKNOWN, 1: OK, 2: ISSUES, 3: ERROR), + message: status message shown + ] + */ + return $health; + } + + /* + * + * ====================================== Exposed custom functions ====================================== + * + */ + + public function myIndexAction(array $params): array + { + // $data = get data from local tool + + //if we want to filter it via the quicksearch + if (!empty($params['quickFilter'])) { + // filter $data + } + + // return the data embedded in a generic index parameter array + + return [ + 'type' => 'index', + 'title' => false, + 'description' => false, + 'data' => [ + 'data' => $data, + 'skip_pagination' => 1, + 'top_bar' => [ + 'children' => [ + [ + 'type' => 'search', + 'button' => __('Filter'), + 'placeholder' => __('Enter value to search'), + 'data' => '', + 'searchKey' => 'value', + 'additionalUrlParams' => $urlParams + ] + ] + ], + 'fields' => [ + [ + 'name' => 'field1_name', + 'sort' => 'field1.path', + 'data_path' => 'field1.path', + ] + ], + 'pull' => 'right', + 'actions' => [ + [ + 'open_modal' => '/localTools/action/' . h($params['connection']['id']) . '/myForm?myKey={{0}}', + 'modal_params_data_path' => ['myKey'], + 'icon' => 'font_awesome_icon_name', + 'reload_url' => '/localTools/action/' . h($params['connection']['id']) . '/myIndex' + ] + ] + ] + ]; + } + + + public function myFormAction(array $params): array + { + if ($params['request']->is(['get'])) { + return [ + 'data' => [ + 'title' => __('My Form Title'), + 'description' => __('My form description'), + 'submit' => [ + 'action' => $params['request']->getParam('action') + ], + 'url' => ['controller' => 'localTools', 'action' => 'action', h($params['connection']['id']), 'myFormAction'] + ] + ]; + } elseif ($params['request']->is(['post'])) { + // handle posted data + if ($success) { + return ['success' => 1, 'message' => __('Action successful.')]; + } else { + return ['success' => 0, 'message' => __('Action failed spectacularly.')]; + } + } + throw new MethodNotAllowedException(__('Invalid http request type for the given action.')); + } + + /* + * + * ====================================== Inter connection functions ====================================== + * + */ + + public function initiateConnection(array $params): array + { + // encode initial connection in local tool + // build and return initiation payload + return $payload; + } + + public function acceptConnection(array $params): array + { + // encode acceptance of the connection request in local tool, based on the payload from the initiation + // return payload for remote to encode the connection + return $payload; + } + + public function finaliseConnection(array $params): bool + { + // based on the payload from the acceptance, finalise the connection + // return true on success + return $success; + } +} + + ?> diff --git a/src/Model/Table/BroodsTable.php b/src/Model/Table/BroodsTable.php index 4502a76..ce783f4 100644 --- a/src/Model/Table/BroodsTable.php +++ b/src/Model/Table/BroodsTable.php @@ -26,7 +26,12 @@ class BroodsTable extends AppTable public function validationDefault(Validator $validator): Validator { - return $validator; + return $validator + ->requirePresence(['name', 'url', 'organisation_id'], 'create') + ->notEmptyString('name') + ->notEmptyString('url') + ->url('url', __('The provided value is not a valid URL')) + ->naturalNumber('organisation_id', false); } public function genHTTPClient(Object $brood, array $options=[]): Object diff --git a/templates/Broods/add.php b/templates/Broods/add.php index ffd525f..6069870 100644 --- a/templates/Broods/add.php +++ b/templates/Broods/add.php @@ -26,7 +26,8 @@ 'field' => 'organisation_id', 'label' => __('Owner organisation'), 'options' => $dropdownData['organisation'], - 'type' => 'dropdown' + 'type' => 'dropdown', + 'empty' => __('-- pick one --') ), array( 'field' => 'trusted', diff --git a/templates/element/genericElements/Form/Fields/dropdownField.php b/templates/element/genericElements/Form/Fields/dropdownField.php index 84ea7b8..eb4ea14 100644 --- a/templates/element/genericElements/Form/Fields/dropdownField.php +++ b/templates/element/genericElements/Form/Fields/dropdownField.php @@ -1,6 +1,7 @@ $fieldData['options'], + 'empty' => $fieldData['empty'] ?? false, 'class' => ($fieldData['class'] ?? '') . ' formDropdown custom-select' ]; echo $this->FormFieldMassage->prepareFormElement($this->Form, $controlParams, $fieldData); diff --git a/templates/element/genericElements/ListTopBar/group_search.php b/templates/element/genericElements/ListTopBar/group_search.php index 814ffcc..b7b711e 100644 --- a/templates/element/genericElements/ListTopBar/group_search.php +++ b/templates/element/genericElements/ListTopBar/group_search.php @@ -107,7 +107,7 @@ .click(function() { const url = `/${controller}/filtering` const reloadUrl = `/${controller}/index${additionalUrlParams}` - openFilteringModal(this, url, reloadUrl, $(`#table-container-${randomValue}`)); + openFilteringModal(this, url, reloadUrl, $(`#index-table-${randomValue}`)); }) function doFilter($button) { @@ -151,7 +151,8 @@ } function openFilteringModal(clicked, url, reloadUrl, tableId) { - UI.overlayUntilResolve(clicked, UI.submissionModalForIndex(url, reloadUrl, tableId)) + const modalPromise = UI.submissionModalForIndex(url, reloadUrl, tableId) + UI.overlayUntilResolve(clicked, modalPromise) } }); diff --git a/webroot/js/bootstrap-helper.js b/webroot/js/bootstrap-helper.js index 09e5719..6c4176b 100644 --- a/webroot/js/bootstrap-helper.js +++ b/webroot/js/bootstrap-helper.js @@ -996,21 +996,22 @@ class FormValidationHelper { injectValidationErrorInForm(fieldName, errors) { const inputField = Array.from(this.form).find(node => { return node.name == fieldName }) if (inputField !== undefined) { - const $messageNode = this.buildValidationMessageNode(errors) + const $messageNode = this.buildValidationMessageNode(fieldName, errors) const $inputField = $(inputField) $inputField.addClass('is-invalid') $messageNode.insertAfter($inputField) } else { - const $messageNode = this.buildValidationMessageNode(errors, true) + const $messageNode = this.buildValidationMessageNode(fieldName, errors, true) const $flashContainer = $(this.form).parent().find('#flashContainer') $messageNode.insertAfter($flashContainer) } } - buildValidationMessageNode(errors, isAlert=false) { + buildValidationMessageNode(fieldName, errors, isAlert=false) { const $messageNode = $('
') if (isAlert) { $messageNode.addClass('alert alert-danger').attr('role', 'alert') + $messageNode.append($('').text(`${fieldName}: `)) } else { $messageNode.addClass('invalid-feedback') } @@ -1020,7 +1021,7 @@ class FormValidationHelper { if (hasMultipleErrors) { $messageNode.append($('
  • ').text(error)) } else { - $messageNode.text(error) + $messageNode.append($('').text(error)) } } } else {