new: [inbox] First version of Inbox system and requestProcessors - WiP
parent
010d0896a9
commit
77fe4e6505
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
use Cake\Filesystem\File;
|
||||
use Cake\Utility\Inflector;
|
||||
use Cake\Validation\Validator;
|
||||
|
||||
interface GenericProcessorActionI
|
||||
{
|
||||
public function create($requestData);
|
||||
public function process($requestID, $serverRequest);
|
||||
public function discard($requestID);
|
||||
public function setViewVariables($controller, $request);
|
||||
}
|
||||
|
||||
class GenericRequestProcessor
|
||||
{
|
||||
public $Inbox;
|
||||
protected $registeredActions = [];
|
||||
protected $validator;
|
||||
private $processingTemplate = '/genericTemplates/confirm';
|
||||
private $processingTemplatesDirectory = ROOT . '/templates/RequestProcessors';
|
||||
|
||||
public function __construct($registerActions=false) {
|
||||
$this->Inbox = TableRegistry::getTableLocator()->get('Inbox');
|
||||
if ($registerActions) {
|
||||
$this->registerActionInProcessor();
|
||||
}
|
||||
$processingTemplatePath = $this->getProcessingTemplatePath();
|
||||
$file = new File($this->processingTemplatesDirectory . DS . $processingTemplatePath);
|
||||
if ($file->exists()) {
|
||||
$this->processingTemplate = $processingTemplatePath;
|
||||
}
|
||||
$file->close();
|
||||
}
|
||||
|
||||
private function getProcessingTemplatePath()
|
||||
{
|
||||
$class = str_replace('RequestProcessor', '', get_parent_class($this));
|
||||
$action = strtolower(str_replace('Processor', '', get_class($this)));
|
||||
return sprintf('%s/%s.php',
|
||||
$class,
|
||||
$action
|
||||
);
|
||||
}
|
||||
|
||||
public function getProcessingTemplate()
|
||||
{
|
||||
if ($this->processingTemplate == '/genericTemplates/confirm') {
|
||||
return '/genericTemplates/confirm';
|
||||
}
|
||||
return DS . 'RequestProcessors' . DS . str_replace('.php', '', $this->processingTemplate);
|
||||
}
|
||||
|
||||
protected function generateRequest($requestData)
|
||||
{
|
||||
$request = $this->Inbox->newEmptyEntity();
|
||||
$request = $this->Inbox->patchEntity($request, $requestData);
|
||||
if ($request->getErrors()) {
|
||||
throw new MethodNotAllowed(__('Could not create request.{0}Reason: {1}', PHP_EOL, json_encode($request->getErrors())), 1);
|
||||
}
|
||||
return $request;
|
||||
}
|
||||
|
||||
protected function validateRequestData($requestData)
|
||||
{
|
||||
$errors = [];
|
||||
if (!isset($requestData['data'])) {
|
||||
$errors[] = __('No request data provided');
|
||||
}
|
||||
$validator = new Validator();
|
||||
if (method_exists($this, 'addValidatorRules')) {
|
||||
$validator = $this->addValidatorRules($validator);
|
||||
$errors = $validator->validate($requestData['data']);
|
||||
}
|
||||
if (!empty($errors)) {
|
||||
throw new Exception('Error while validating request data. ' . json_encode($errors), 1);
|
||||
}
|
||||
}
|
||||
|
||||
protected function registerActionInProcessor()
|
||||
{
|
||||
foreach ($this->registeredActions as $i => $action) {
|
||||
$className = "{$action}Processor";
|
||||
$reflection = new ReflectionClass($className);
|
||||
if ($reflection->isAbstract() || $reflection->isInterface()) {
|
||||
throw new Exception(__('Cannot create instance of %s, as it is abstract or is an interface'));
|
||||
}
|
||||
$this->{$action} = $reflection->newInstance();
|
||||
}
|
||||
}
|
||||
|
||||
public function checkLoading()
|
||||
{
|
||||
return 'Assimilation successful!';
|
||||
}
|
||||
|
||||
protected function setViewVariablesConfirmModal($controller, $id, $title='', $question='', $actionName='')
|
||||
{
|
||||
$controller->set('title', !empty($title) ? $title : __('Process request {0}', $id));
|
||||
$controller->set('question', !empty($question) ? $question : __('Confirm request {0}', $id));
|
||||
$controller->set('actionName', !empty($actionName) ? $actionName : __('Confirm'));
|
||||
$controller->set('path', ['controller' => 'inbox', 'action' => 'process', $id]);
|
||||
}
|
||||
|
||||
public function create($requestData)
|
||||
{
|
||||
$request = $this->generateRequest($requestData);
|
||||
$savedRequest = $this->Inbox->save($request);
|
||||
if ($savedRequest !== false) {
|
||||
// log here
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'RequestProcessors' . DS . 'GenericRequestProcessor.php');
|
||||
|
||||
class UserRequestProcessor extends GenericRequestProcessor
|
||||
{
|
||||
protected $scope = 'User';
|
||||
protected $action = 'overridden-in-processor-action';
|
||||
protected $description = 'overridden-in-processor-action';
|
||||
protected $registeredActions = [
|
||||
'Registration'
|
||||
];
|
||||
|
||||
public function __construct($loadFromAction=false) {
|
||||
parent::__construct($loadFromAction);
|
||||
}
|
||||
|
||||
public function create($requestData)
|
||||
{
|
||||
$requestData['scope'] = $this->scope;
|
||||
$requestData['action'] = $this->action;
|
||||
$requestData['description'] = $this->description;
|
||||
parent::create($requestData);
|
||||
}
|
||||
}
|
||||
|
||||
class RegistrationProcessor extends UserRequestProcessor implements GenericProcessorActionI {
|
||||
protected $action = 'Registration';
|
||||
protected $description;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->description = __('Handle user account for this cerebrate instance');
|
||||
$this->Users = TableRegistry::getTableLocator()->get('Users');
|
||||
}
|
||||
|
||||
protected function addValidatorRules($validator)
|
||||
{
|
||||
return $validator
|
||||
->requirePresence('username')
|
||||
->notEmpty('name', 'A username must be provided.')
|
||||
->requirePresence('email')
|
||||
->add('email', 'validFormat', [
|
||||
'rule' => 'email',
|
||||
'message' => 'E-mail must be valid'
|
||||
])
|
||||
->requirePresence('first_name')
|
||||
->notEmpty('name', 'A first name must be provided.')
|
||||
->requirePresence('last_name')
|
||||
->notEmpty('name', 'A last name must be provided.');
|
||||
}
|
||||
|
||||
public function create($requestData) {
|
||||
$this->validateRequestData($requestData);
|
||||
$requestData['title'] = __('User account creation requested for {0}', $requestData['data']['email']);
|
||||
parent::create($requestData);
|
||||
}
|
||||
|
||||
public function setViewVariables($controller, $request)
|
||||
{
|
||||
$dropdownData = [
|
||||
'role' => $this->Users->Roles->find('list', [
|
||||
'sort' => ['name' => 'asc']
|
||||
]),
|
||||
'individual' => [-1 => __('-- New individual --')] + $this->Users->Individuals->find('list', [
|
||||
'sort' => ['email' => 'asc']
|
||||
])->toArray()
|
||||
];
|
||||
$individualEntity = $this->Users->Individuals->newEntity([
|
||||
'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'] : '',
|
||||
]);
|
||||
$userEntity = $this->Users->newEntity([
|
||||
'individual_id' => -1,
|
||||
'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'] : '',
|
||||
]);
|
||||
$controller->set('individualEntity', $individualEntity);
|
||||
$controller->set('userEntity', $userEntity);
|
||||
$controller->set(compact('dropdownData'));
|
||||
}
|
||||
|
||||
public function process($id, $serverRequest)
|
||||
{
|
||||
$data = $serverRequest->getData();
|
||||
if ($data['individual_id'] == -1) {
|
||||
$individual = $this->Users->Individuals->newEntity([
|
||||
'uuid' => $data['uuid'],
|
||||
'email' => $data['email'],
|
||||
'first_name' => $data['first_name'],
|
||||
'last_name' => $data['last_name'],
|
||||
'position' => $data['position'],
|
||||
]);
|
||||
$individual = $this->Users->Individuals->save($individual);
|
||||
} else {
|
||||
$individual = $this->Users->Individuals->get($data['individual_id']);
|
||||
}
|
||||
$user = $this->Users->newEntity([
|
||||
'individual_id' => $individual->id,
|
||||
'username' => $data['username'],
|
||||
'password' => '~PASSWORD_TO_BE_REPLACED~',
|
||||
'role_id' => $data['role_id'],
|
||||
'disabled' => $data['disabled'],
|
||||
]);
|
||||
$user = $this->Users->save($user);
|
||||
return [
|
||||
'data' => $user,
|
||||
'success' => $user !== false,
|
||||
'message' => $user !== false ? __('User `{0}` created', $user->username) : __('Could not create user `{0}`.', $user->username),
|
||||
'errors' => $user->getErrors()
|
||||
];
|
||||
}
|
||||
|
||||
public function discard($id)
|
||||
{
|
||||
parent::discard($id);
|
||||
}
|
||||
}
|
|
@ -107,6 +107,13 @@ class AppController extends Controller
|
|||
} else if ($this->ParamHandler->isRest()) {
|
||||
throw new MethodNotAllowedException(__('Invalid user credentials.'));
|
||||
}
|
||||
|
||||
// if ($this->request->getParam('action') === 'index') {
|
||||
// $this->Security->setConfig('validatePost', false);
|
||||
// }
|
||||
$this->Security->setConfig('unlockedActions', ['index']);
|
||||
$this->Security->setConfig('validatePost', false);
|
||||
|
||||
$this->ACL->checkAccess();
|
||||
$this->set('menu', $this->ACL->getMenu());
|
||||
$this->set('ajax', $this->request->is('ajax'));
|
||||
|
|
|
@ -642,6 +642,29 @@ class ACLComponent extends Component
|
|||
]
|
||||
]
|
||||
],
|
||||
'Inbox' => [
|
||||
'label' => __('Inbox'),
|
||||
'url' => '/inbox/index',
|
||||
'children' => [
|
||||
'index' => [
|
||||
'url' => '/inbox/index',
|
||||
'label' => __('Inbox')
|
||||
],
|
||||
'view' => [
|
||||
'url' => '/inbox/view/{{id}}',
|
||||
'label' => __('View Meta Template'),
|
||||
'actions' => ['delete', 'edit', 'view'],
|
||||
'skipTopMenu' => 1
|
||||
],
|
||||
'delete' => [
|
||||
'url' => '/inbox/delete/{{id}}',
|
||||
'label' => __('Delete Meta Template'),
|
||||
'actions' => ['delete', 'edit', 'view'],
|
||||
'skipTopMenu' => 1,
|
||||
'popup' => 1
|
||||
]
|
||||
]
|
||||
],
|
||||
'MetaTemplates' => [
|
||||
'label' => __('Meta Field Templates'),
|
||||
'url' => '/metaTemplates/index',
|
||||
|
|
|
@ -145,7 +145,7 @@ class CRUDComponent extends Component
|
|||
$message = __(
|
||||
'{0} could not be added.{1}',
|
||||
$this->ObjectAlias,
|
||||
empty($validationMessage) ? '' : ' ' . __('Reason:{0}', $validationMessage)
|
||||
empty($validationMessage) ? '' : PHP_EOL . __('Reason:{0}', $validationMessage)
|
||||
);
|
||||
if ($this->Controller->ParamHandler->isRest()) {
|
||||
} else if ($this->Controller->ParamHandler->isAjax()) {
|
||||
|
@ -485,7 +485,7 @@ class CRUDComponent extends Component
|
|||
if (is_bool($contextFromField)) {
|
||||
$contextFromFieldText = sprintf('%s: %s', $field, $contextFromField ? 'true' : 'false');
|
||||
} else {
|
||||
$contextFromFieldText = $contextFromField;
|
||||
$contextFromFieldText = sprintf('%s: %s', $field, $contextFromField);
|
||||
}
|
||||
$filteringContexts[] = [
|
||||
'label' => Inflector::humanize($contextFromFieldText),
|
||||
|
|
|
@ -423,11 +423,12 @@ class RestResponseComponent extends Component
|
|||
public function ajaxSuccessResponse($ObjectAlias, $action, $entity, $message, $additionalData=[])
|
||||
{
|
||||
$action = $this->__dissectAdminRouting($action);
|
||||
$entity = is_array($entity) ? $entity : $entity->toArray();
|
||||
$response = [
|
||||
'success' => true,
|
||||
'message' => $message,
|
||||
'data' => $entity->toArray(),
|
||||
'url' => $this->__generateURL($action, $ObjectAlias, $entity->id)
|
||||
'data' => $entity,
|
||||
'url' => !empty($entity['id']) ? $this->__generateURL($action, $ObjectAlias, $entity['id']) : ''
|
||||
];
|
||||
if (!empty($additionalData)) {
|
||||
$response['additionalData'] = $additionalData;
|
||||
|
|
|
@ -18,7 +18,7 @@ class EncryptionKeysController extends AppController
|
|||
{
|
||||
$this->CRUD->index([
|
||||
'quickFilters' => ['encryption_key'],
|
||||
'filters' => ['owner_type', 'organisation_id', 'individual_id', 'encryption_key'],
|
||||
'filters' => ['owner_model', 'organisation_id', 'individual_id', 'encryption_key'],
|
||||
'contextFilters' => [
|
||||
'fields' => [
|
||||
'type'
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Controller\AppController;
|
||||
use Cake\Utility\Hash;
|
||||
use Cake\Utility\Text;
|
||||
use Cake\Database\Expression\QueryExpression;
|
||||
use Cake\Event\EventInterface;
|
||||
use Cake\Http\Exception\NotFoundException;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use Cake\Http\Exception\ForbiddenException;
|
||||
|
||||
|
||||
class InboxController extends AppController
|
||||
{
|
||||
public $filters = ['scope', 'action', 'title', 'origin', 'comment'];
|
||||
|
||||
public function beforeFilter(EventInterface $event)
|
||||
{
|
||||
parent::beforeFilter($event);
|
||||
$this->set('metaGroup', 'Administration');
|
||||
}
|
||||
|
||||
|
||||
public function index()
|
||||
{
|
||||
$this->CRUD->index([
|
||||
'filters' => $this->filters,
|
||||
'quickFilters' => ['scope', 'action', ['title' => true], ['comment' => true]],
|
||||
'contextFilters' => [
|
||||
'fields' => [
|
||||
'scope',
|
||||
'action',
|
||||
]
|
||||
],
|
||||
'contain' => ['Users']
|
||||
]);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
}
|
||||
|
||||
public function filtering()
|
||||
{
|
||||
$this->CRUD->filtering();
|
||||
}
|
||||
|
||||
// public function add()
|
||||
// {
|
||||
// $this->CRUD->add();
|
||||
// $responsePayload = $this->CRUD->getResponsePayload();
|
||||
// if (!empty($responsePayload)) {
|
||||
// return $responsePayload;
|
||||
// }
|
||||
// }
|
||||
|
||||
public function view($id)
|
||||
{
|
||||
$this->CRUD->view($id);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
$this->CRUD->delete($id);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
}
|
||||
|
||||
public function process($id)
|
||||
{
|
||||
$request = $this->Inbox->get($id);
|
||||
$scope = $request->scope;
|
||||
$action = $request->action;
|
||||
$processor = $this->Inbox->getRequestProcessor($scope, $action);
|
||||
if ($this->request->is('post')) {
|
||||
$processResult = $processor->process($id, $this->request);
|
||||
if ($processResult['success']) {
|
||||
$message = !empty($processResult['message']) ? $processResult['message'] : __('Request {0} processed.', $id);
|
||||
$response = $this->RestResponse->ajaxSuccessResponse('RequestProcessor', "{$scope}.{$action}", $processResult['data'], $message);
|
||||
} else {
|
||||
$message = !empty($processResult['message']) ? $processResult['message'] : __('Request {0} could not be processed.', $id);
|
||||
$response = $this->RestResponse->ajaxFailResponse('RequestProcessor', "{$scope}.{$action}", $processResult['data'], $message, $processResult['errors']);
|
||||
}
|
||||
return $response;
|
||||
} else {
|
||||
$processor->setViewVariables($this, $request);
|
||||
$processingTemplate = $processor->getProcessingTemplate();
|
||||
$this->set('request', $request);
|
||||
$this->viewBuilder()->setLayout('ajax');
|
||||
$this->render($processingTemplate);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ namespace App\Controller;
|
|||
use App\Controller\AppController;
|
||||
use Cake\Utility\Hash;
|
||||
use Cake\Utility\Text;
|
||||
use Cake\ORM\TableRegistry;
|
||||
use \Cake\Database\Expression\QueryExpression;
|
||||
|
||||
class UsersController extends AppController
|
||||
|
@ -134,4 +135,23 @@ class UsersController extends AppController
|
|||
return $this->redirect(['controller' => 'Users', 'action' => 'login']);
|
||||
}
|
||||
}
|
||||
|
||||
public function register()
|
||||
{
|
||||
$this->Inbox = TableRegistry::getTableLocator()->get('Inbox');
|
||||
$processor = $this->Inbox->getRequestProcessor('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',
|
||||
],
|
||||
];
|
||||
$processor->create($data);
|
||||
$this->Flash->success(__('Entry created'));
|
||||
return $this->redirect(['controller' => 'Inbox', 'action' => 'index']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Model\Entity;
|
||||
|
||||
use App\Model\Entity\AppModel;
|
||||
use Cake\ORM\Entity;
|
||||
|
||||
class Inbox extends AppModel
|
||||
{
|
||||
|
||||
}
|
|
@ -18,14 +18,14 @@ class EncryptionKeysTable extends AppTable
|
|||
'Individuals',
|
||||
[
|
||||
'foreignKey' => 'owner_id',
|
||||
'conditions' => ['owner_type' => 'individual']
|
||||
'conditions' => ['owner_model' => 'individual']
|
||||
]
|
||||
);
|
||||
$this->belongsTo(
|
||||
'Organisations',
|
||||
[
|
||||
'foreignKey' => 'owner_id',
|
||||
'conditions' => ['owner_type' => 'organisation']
|
||||
'conditions' => ['owner_model' => 'organisation']
|
||||
]
|
||||
);
|
||||
$this->setDisplayField('encryption_key');
|
||||
|
@ -34,13 +34,13 @@ class EncryptionKeysTable extends AppTable
|
|||
public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)
|
||||
{
|
||||
if (empty($data['owner_id'])) {
|
||||
if (empty($data['owner_type'])) {
|
||||
if (empty($data['owner_model'])) {
|
||||
return false;
|
||||
}
|
||||
if (empty($data[$data['owner_type'] . '_id'])) {
|
||||
if (empty($data[$data['owner_model'] . '_id'])) {
|
||||
return false;
|
||||
}
|
||||
$data['owner_id'] = $data[$data['owner_type'] . '_id'];
|
||||
$data['owner_id'] = $data[$data['owner_model'] . '_id'];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,8 +50,8 @@ class EncryptionKeysTable extends AppTable
|
|||
->notEmptyString('type')
|
||||
->notEmptyString('encryption_key')
|
||||
->notEmptyString('owner_id')
|
||||
->notEmptyString('owner_type')
|
||||
->requirePresence(['type', 'encryption_key', 'owner_id', 'owner_type'], 'create');
|
||||
->notEmptyString('owner_model')
|
||||
->requirePresence(['type', 'encryption_key', 'owner_id', 'owner_model'], 'create');
|
||||
return $validator;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
namespace App\Model\Table;
|
||||
|
||||
use App\Model\Table\AppTable;
|
||||
use Cake\Database\Schema\TableSchemaInterface;
|
||||
use Cake\Database\Type;
|
||||
use Cake\Filesystem\Folder;
|
||||
use Cake\ORM\Table;
|
||||
use Cake\ORM\RulesChecker;
|
||||
use Cake\Validation\Validator;
|
||||
|
||||
Type::map('json', 'Cake\Database\Type\JsonType');
|
||||
|
||||
class InboxTable extends AppTable
|
||||
{
|
||||
private $processorsDirectory = ROOT . '/libraries/RequestProcessors';
|
||||
private $requestProcessors;
|
||||
|
||||
public function initialize(array $config): void
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->addBehavior('UUID');
|
||||
$this->addBehavior('Timestamp', [
|
||||
'events' => [
|
||||
'Model.beforeSave' => [
|
||||
'created' => 'new'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$this->belongsTo('Users');
|
||||
$this->setDisplayField('title');
|
||||
}
|
||||
|
||||
protected function _initializeSchema(TableSchemaInterface $schema): TableSchemaInterface
|
||||
{
|
||||
$schema->setColumnType('data', 'json');
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
public function validationDefault(Validator $validator): Validator
|
||||
{
|
||||
$validator
|
||||
->notEmptyString('scope')
|
||||
->notEmptyString('action')
|
||||
->notEmptyString('title')
|
||||
->notEmptyString('origin')
|
||||
->datetime('created')
|
||||
|
||||
->requirePresence([
|
||||
'scope' => ['message' => __('The field `scope` is required')],
|
||||
'action' => ['message' => __('The field `action` is required')],
|
||||
'title' => ['message' => __('The field `title` is required')],
|
||||
'origin' => ['message' => __('The field `origin` is required')],
|
||||
], 'create');
|
||||
return $validator;
|
||||
}
|
||||
|
||||
public function buildRules(RulesChecker $rules): RulesChecker
|
||||
{
|
||||
$rules->add($rules->existsIn('user_id', 'Users'), [
|
||||
'message' => 'The provided `user_id` does not exist'
|
||||
]);
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function getRequestProcessor($name, $action=null)
|
||||
{
|
||||
if (!isset($this->requestProcessors)) {
|
||||
$this->loadRequestProcessors();
|
||||
}
|
||||
if (isset($this->requestProcessors[$name])) {
|
||||
if (is_null($action)) {
|
||||
return $this->requestProcessors[$name];
|
||||
} else if (!empty($this->requestProcessors[$name]->{$action})) {
|
||||
return $this->requestProcessors[$name]->{$action};
|
||||
} else {
|
||||
throw new \Exception(__('Processor {0}.{1} not found', $name, $action));
|
||||
}
|
||||
}
|
||||
throw new \Exception(__('Processor not found'), 1);
|
||||
}
|
||||
|
||||
private function loadRequestProcessors()
|
||||
{
|
||||
$processorDir = new Folder($this->processorsDirectory);
|
||||
$processorFiles = $processorDir->find('.*RequestProcessor\.php', true);
|
||||
foreach ($processorFiles as $processorFile) {
|
||||
if ($processorFile == 'GenericRequestProcessor.php') {
|
||||
continue;
|
||||
}
|
||||
$processorMainClassName = str_replace('.php', '', $processorFile);
|
||||
$processorMainClassNameShort = str_replace('RequestProcessor.php', '', $processorFile);
|
||||
$processorMainClass = $this->getProcessorClass($processorDir->pwd() . DS . $processorFile, $processorMainClassName);
|
||||
if ($processorMainClass !== false) {
|
||||
$this->requestProcessors[$processorMainClassNameShort] = $processorMainClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getProcessorClass($filePath, $processorMainClassName)
|
||||
{
|
||||
require_once($filePath);
|
||||
$reflection = new \ReflectionClass($processorMainClassName);
|
||||
$processorMainClass = $reflection->newInstance(true);
|
||||
if ($processorMainClass->checkLoading() === 'Assimilation successful!') {
|
||||
return $processorMainClass;
|
||||
}
|
||||
try {
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ class IndividualsTable extends AppTable
|
|||
'EncryptionKeys',
|
||||
[
|
||||
'foreignKey' => 'owner_id',
|
||||
'conditions' => ['owner_type' => 'individual']
|
||||
'conditions' => ['owner_model' => 'individual']
|
||||
]
|
||||
);
|
||||
$this->hasOne(
|
||||
|
|
|
@ -30,7 +30,7 @@ class OrganisationsTable extends AppTable
|
|||
[
|
||||
'dependent' => true,
|
||||
'foreignKey' => 'owner_id',
|
||||
'conditions' => ['owner_type' => 'organisation']
|
||||
'conditions' => ['owner_model' => 'organisation']
|
||||
]
|
||||
);
|
||||
$this->hasMany(
|
||||
|
|
|
@ -822,7 +822,7 @@ class BoostrapModal extends BootstrapGeneric {
|
|||
'variant' => 'primary',
|
||||
'text' => __('Ok'),
|
||||
'params' => [
|
||||
'data-dismiss' => 'modal',
|
||||
'data-dismiss' => $this->options['confirmFunction'] ? '' : 'modal',
|
||||
'onclick' => $this->options['confirmFunction']
|
||||
]
|
||||
]))->button();
|
||||
|
@ -848,8 +848,9 @@ class BoostrapModal extends BootstrapGeneric {
|
|||
'variant' => $variant,
|
||||
'text' => h($this->options['confirmText']),
|
||||
'params' => [
|
||||
'data-dismiss' => 'modal',
|
||||
'onclick' => $this->options['confirmFunction']
|
||||
'data-dismiss' => $this->options['confirmFunction'] ? '' : 'modal',
|
||||
// 'onclick' => sprintf('(function(clicked) { %s.finally( () => { $(clicked).closest(\'.modal\').data(\'modalObject\').hide() }) }(this))', $this->options['confirmFunction'])
|
||||
'onclick' => sprintf('closeModalOnFunctionCompletion(this, function(clicked) { return %s })', $this->options['confirmFunction'])
|
||||
]
|
||||
]))->button();
|
||||
return $buttonCancel . $buttonConfirm;
|
||||
|
|
|
@ -6,7 +6,7 @@ echo $this->element('genericElements/Form/genericForm', [
|
|||
'model' => 'Organisations',
|
||||
'fields' => [
|
||||
[
|
||||
'field' => 'owner_type',
|
||||
'field' => 'owner_model',
|
||||
'label' => __('Owner type'),
|
||||
'options' => array_combine(array_keys($dropdownData), array_keys($dropdownData)),
|
||||
'type' => 'dropdown'
|
||||
|
@ -17,7 +17,7 @@ echo $this->element('genericElements/Form/genericForm', [
|
|||
'options' => $dropdownData['organisation'] ?? [],
|
||||
'type' => 'dropdown',
|
||||
'stateDependence' => [
|
||||
'source' => '#owner_type-field',
|
||||
'source' => '#owner_model-field',
|
||||
'option' => 'organisation'
|
||||
]
|
||||
],
|
||||
|
@ -27,7 +27,7 @@ echo $this->element('genericElements/Form/genericForm', [
|
|||
'options' => $dropdownData['individual'] ?? [],
|
||||
'type' => 'dropdown',
|
||||
'stateDependence' => [
|
||||
'source' => '#owner_type-field',
|
||||
'source' => '#owner_model-field',
|
||||
'option' => 'individual'
|
||||
]
|
||||
],
|
||||
|
|
|
@ -45,7 +45,7 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
[
|
||||
'name' => __('Owner'),
|
||||
'data_path' => 'owner_id',
|
||||
'owner_type_path' => 'owner_type',
|
||||
'owner_model_path' => 'owner_model',
|
||||
'element' => 'owner'
|
||||
],
|
||||
[
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
echo $this->Html->scriptBlock(sprintf(
|
||||
'var csrfToken = %s;',
|
||||
json_encode($this->request->getAttribute('csrfToken'))
|
||||
));
|
||||
echo $this->element('genericElements/IndexTable/index_table', [
|
||||
'data' => [
|
||||
'data' => $data,
|
||||
'top_bar' => [
|
||||
'children' => [
|
||||
[
|
||||
'type' => 'context_filters',
|
||||
'context_filters' => !empty($filteringContexts) ? $filteringContexts : []
|
||||
],
|
||||
[
|
||||
'type' => 'search',
|
||||
'button' => __('Filter'),
|
||||
'placeholder' => __('Enter value to search'),
|
||||
'data' => '',
|
||||
'searchKey' => 'value',
|
||||
'allowFilering' => true
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => [
|
||||
[
|
||||
'name' => '#',
|
||||
'sort' => 'id',
|
||||
'data_path' => 'id',
|
||||
],
|
||||
[
|
||||
'name' => 'created',
|
||||
'sort' => 'created',
|
||||
'data_path' => 'created',
|
||||
'element' => 'datetime'
|
||||
],
|
||||
[
|
||||
'name' => 'scope',
|
||||
'sort' => 'scope',
|
||||
'data_path' => 'scope',
|
||||
],
|
||||
[
|
||||
'name' => 'action',
|
||||
'sort' => 'action',
|
||||
'data_path' => 'action',
|
||||
],
|
||||
[
|
||||
'name' => 'title',
|
||||
'sort' => 'title',
|
||||
'data_path' => 'title',
|
||||
],
|
||||
[
|
||||
'name' => 'origin',
|
||||
'sort' => 'origin',
|
||||
'data_path' => 'origin',
|
||||
],
|
||||
[
|
||||
'name' => 'user',
|
||||
'sort' => 'user_id',
|
||||
'data_path' => 'user',
|
||||
'element' => 'user'
|
||||
],
|
||||
[
|
||||
'name' => 'description',
|
||||
'sort' => 'description',
|
||||
'data_path' => 'description',
|
||||
],
|
||||
[
|
||||
'name' => 'comment',
|
||||
'sort' => 'comment',
|
||||
'data_path' => 'comment',
|
||||
],
|
||||
],
|
||||
'title' => __('Inbox'),
|
||||
'description' => __('A list of requests to be manually processed'),
|
||||
'actions' => [
|
||||
[
|
||||
'open_modal' => '/inbox/process/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
'icon' => 'eye'
|
||||
],
|
||||
[
|
||||
'open_modal' => '/individuals/delete/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
'icon' => 'trash'
|
||||
],
|
||||
]
|
||||
]
|
||||
]);
|
||||
echo '</div>';
|
||||
?>
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
echo $this->element(
|
||||
'/genericElements/SingleViews/single_view',
|
||||
[
|
||||
'data' => $entity,
|
||||
'fields' => [
|
||||
[
|
||||
'key' => __('ID'),
|
||||
'path' => 'id'
|
||||
],
|
||||
[
|
||||
'key' => 'created',
|
||||
'path' => 'created',
|
||||
],
|
||||
[
|
||||
'key' => 'scope',
|
||||
'path' => 'scope',
|
||||
],
|
||||
[
|
||||
'key' => 'action',
|
||||
'path' => 'action',
|
||||
],
|
||||
[
|
||||
'key' => 'title',
|
||||
'path' => 'title',
|
||||
],
|
||||
[
|
||||
'key' => 'origin',
|
||||
'path' => 'origin',
|
||||
],
|
||||
[
|
||||
'key' => 'user_id',
|
||||
'path' => 'user_id',
|
||||
],
|
||||
[
|
||||
'key' => 'description',
|
||||
'path' => 'description',
|
||||
],
|
||||
[
|
||||
'key' => 'comment',
|
||||
'path' => 'comment',
|
||||
],
|
||||
[
|
||||
'key' => 'data',
|
||||
'path' => 'data',
|
||||
],
|
||||
],
|
||||
'children' => []
|
||||
]
|
||||
);
|
|
@ -51,7 +51,7 @@ function runAllUpdate() {
|
|||
type: 'confirm-success',
|
||||
confirmText: '<?= __n('Run update', 'Run all updates', count($updateAvailables)) ?>',
|
||||
APIConfirm: (tmpApi) => {
|
||||
tmpApi.fetchAndPostForm(url, {}).then(() => {
|
||||
return tmpApi.fetchAndPostForm(url, {}).then(() => {
|
||||
location.reload()
|
||||
})
|
||||
},
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
$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'
|
||||
],
|
||||
],
|
||||
'submit' => [
|
||||
'action' => $this->request->getParam('action')
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$formUser = $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'],
|
||||
],
|
||||
[
|
||||
'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('<div class="individual-container">%s</div><div class="user-container">%s</div>',
|
||||
$formIndividual,
|
||||
$formUser
|
||||
),
|
||||
'confirmText' => __('Submit'),
|
||||
'confirmFunction' => 'submitRegistration(clicked)'
|
||||
]);
|
||||
?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function submitRegistration(clicked) {
|
||||
const tmpApi = new AJAXApi({
|
||||
statusNode: clicked
|
||||
})
|
||||
const $forms = $(clicked).closest('.modal').find('form')
|
||||
const url = $forms[0].action
|
||||
const data1 = getFormData($forms[0])
|
||||
const data2 = getFormData($forms[1])
|
||||
const data = {...data1, ...data2}
|
||||
return tmpApi.postData(url, data)
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('div.user-container #individual_id-field').change(function() {
|
||||
if ($(this).val() == -1) {
|
||||
$('div.individual-container').show()
|
||||
} else {
|
||||
$('div.individual-container').hide()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function getFormData(form) {
|
||||
return Object.values(form).reduce((obj,field) => {
|
||||
if (field.type === 'checkbox') {
|
||||
obj[field.name] = field.checked;
|
||||
} else {
|
||||
obj[field.name] = field.value;
|
||||
}
|
||||
return obj
|
||||
}, {})
|
||||
}
|
||||
</script>
|
|
@ -115,6 +115,24 @@
|
|||
'actionButton' => $this->element('genericElements/Form/submitButton', $submitButtonData),
|
||||
'class' => 'modal-lg'
|
||||
]);
|
||||
} else if (!empty($raw)) {
|
||||
echo sprintf(
|
||||
'%s%s%s%s%s%s',
|
||||
empty($data['description']) ? '' : sprintf(
|
||||
'<div class="pb-2">%s</div>',
|
||||
$data['description']
|
||||
),
|
||||
$ajaxFlashMessage,
|
||||
$formCreate,
|
||||
$fieldsString,
|
||||
empty($metaTemplateString) ? '' : $this->element(
|
||||
'genericElements/accordion_scaffold', [
|
||||
'body' => $metaTemplateString,
|
||||
'title' => 'Meta fields'
|
||||
]
|
||||
),
|
||||
$formEnd
|
||||
);
|
||||
} else {
|
||||
echo sprintf(
|
||||
'%s<h2>%s</h2>%s%s%s%s%s%s%s%s%s',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
$type = $this->Hash->extract($row, $field['owner_type_path'])[0];
|
||||
$type = $this->Hash->extract($row, $field['owner_model_path'])[0];
|
||||
$owner = $row[$type];
|
||||
$types = [
|
||||
'individual' => [
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
if (!empty($row['user'])) {
|
||||
$userId = $this->Hash->extract($row, 'user.id')[0];
|
||||
$userName = $this->Hash->extract($row, 'user.username')[0];
|
||||
echo $this->Html->link(
|
||||
h($userName),
|
||||
['controller' => 'users', 'action' => 'view', $userId]
|
||||
);
|
||||
}
|
||||
|
||||
?>
|
|
@ -12,6 +12,7 @@
|
|||
* - id: element ID for the input field - defaults to quickFilterField
|
||||
*/
|
||||
if (!isset($data['requirement']) || $data['requirement']) {
|
||||
$filterEffective = !empty($quickFilter); // No filters will be picked up, thus rendering the filtering useless
|
||||
$filteringButton = '';
|
||||
if (!empty($data['allowFilering'])) {
|
||||
$activeFilters = !empty($activeFilters) ? $activeFilters : [];
|
||||
|
@ -32,9 +33,10 @@
|
|||
$filteringButton = $this->Bootstrap->button($buttonConfig);
|
||||
}
|
||||
$button = empty($data['button']) && empty($data['fa-icon']) ? '' : sprintf(
|
||||
'<div class="input-group-append"><button class="btn btn-primary" %s id="quickFilterButton-%s">%s%s</button>%s</div>',
|
||||
'<div class="input-group-append"><button class="btn btn-primary" %s id="quickFilterButton-%s" %s>%s%s</button>%s</div>',
|
||||
empty($data['data']) ? '' : h($data['data']),
|
||||
h($tableRandomValue),
|
||||
$filterEffective ? '' : 'disabled="disabled"',
|
||||
empty($data['fa-icon']) ? '' : sprintf('<i class="fa fa-%s"></i>', h($data['fa-icon'])),
|
||||
empty($data['button']) ? '' : h($data['button']),
|
||||
$filteringButton
|
||||
|
@ -43,13 +45,14 @@
|
|||
$button .= $this->element('/genericElements/ListTopBar/element_simple', array('data' => $data['cancel']));
|
||||
}
|
||||
$input = sprintf(
|
||||
'<input id="quickFilterField-%s" type="text" class="form-control" placeholder="%s" aria-label="%s" style="padding: 2px 6px;" id="%s" data-searchkey="%s" value="%s">',
|
||||
'<input id="quickFilterField-%s" type="text" class="form-control" placeholder="%s" aria-label="%s" style="padding: 2px 6px;" id="%s" data-searchkey="%s" value="%s" %s>',
|
||||
h($tableRandomValue),
|
||||
empty($data['placeholder']) ? '' : h($data['placeholder']),
|
||||
empty($data['placeholder']) ? '' : h($data['placeholder']),
|
||||
empty($data['id']) ? 'quickFilterField' : h($data['id']),
|
||||
empty($data['searchKey']) ? 'searchall' : h($data['searchKey']),
|
||||
empty($data['value']) ? (!empty($quickFilterValue) ? h($quickFilterValue) : '') : h($data['value'])
|
||||
empty($data['value']) ? (!empty($quickFilterValue) ? h($quickFilterValue) : '') : h($data['value']),
|
||||
$filterEffective ? '' : 'disabled="disabled"'
|
||||
);
|
||||
echo sprintf(
|
||||
'<div class="input-group" data-table-random-value="%s" style="margin-left: auto;">%s%s</div>',
|
||||
|
|
|
@ -35,7 +35,7 @@ if (isset($menu[$metaGroup])) {
|
|||
}
|
||||
$active = ($scope === $this->request->getParam('controller') && $action === $this->request->getParam('action'));
|
||||
if (!empty($data['popup'])) {
|
||||
$link_template = '<a href="#" onClick="UI.submissionModalForSinglePage(\'%s\')" class="list-group-item list-group-item-action %s %s pl-3 border-0 %s">%s</a>';
|
||||
$link_template = '<a href="#" onClick="UI.submissionModalAutoGuess(\'%s\')" class="list-group-item list-group-item-action %s %s pl-3 border-0 %s">%s</a>';
|
||||
} else {
|
||||
$link_template = '<a href="%s" class="list-group-item list-group-item-action %s %s pl-3 border-0 %s">%s</a>';
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<?= $this->Form->postLink(
|
||||
'Delete',
|
||||
(empty($postLinkParameters) ? ['action' => 'delete', $id] : $postLinkParameters),
|
||||
['class' => 'btn btn-primary button-execute', 'id' => 'submitButton']
|
||||
['class' => 'btn btn-danger button-execute', 'id' => 'submitButton']
|
||||
)
|
||||
?>
|
||||
<button type="button" class="btn btn-secondary cancel-button" data-dismiss="modal"><?= __('Cancel') ?></button>
|
||||
|
|
|
@ -30,7 +30,7 @@ echo $this->Bootstrap->modal([
|
|||
'type' => 'confirm',
|
||||
'bodyHtml' => $filteringForm,
|
||||
'confirmText' => __('Filter'),
|
||||
'confirmFunction' => 'filterIndex(this)'
|
||||
'confirmFunction' => 'filterIndex(clicked)'
|
||||
]);
|
||||
?>
|
||||
|
||||
|
|
|
@ -286,6 +286,7 @@ class AJAXApi {
|
|||
let formData = new FormData()
|
||||
formData = AJAXApi.mergeFormData(formData, dataToPost)
|
||||
let requestConfig = AJAXApi.genericRequestConfigPOST
|
||||
requestConfig.headers.append('AUTHORIZATION', '~HACKY-HACK~')
|
||||
let options = {
|
||||
...requestConfig,
|
||||
body: formData,
|
||||
|
@ -307,8 +308,6 @@ class AJAXApi {
|
|||
title: 'There has been a problem with the operation',
|
||||
body: data.message
|
||||
}, true, skipFeedback);
|
||||
feedbackShown = true
|
||||
this.injectFormValidationFeedback(form, data.errors)
|
||||
toReturn = Promise.reject(data.errors);
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
@ -131,6 +131,35 @@ class UIFactory {
|
|||
return UI.submissionReloaderModal(url, reloadUrl, $reloadedElement, $statusNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and displays a modal where the modal's content is fetched from the provided URL. Reloads the index table after a successful operation.
|
||||
* Supports `displayOnSuccess` option to show another modal after the submission
|
||||
* @param {string} url - The URL from which the modal's content should be fetched
|
||||
* @param {(boolean|string)} [reloadUrl=false] - The URL from which the data should be fetched after confirming
|
||||
* @param {(jQuery|string)} [$table=false] - The table ID which should be reloaded on success
|
||||
* @return {Promise<Object>} Promise object resolving to the ModalFactory object
|
||||
*/
|
||||
submissionModalAutoGuess(url, reloadUrl=false, $table=false) {
|
||||
let currentAction = location.pathname.split('/')[2]
|
||||
currentAction += 'cdsc'
|
||||
if (currentAction !== undefined) {
|
||||
if (currentAction === 'index') {
|
||||
return UI.submissionModalForIndex(url, reloadUrl, $table)
|
||||
} else if (currentAction === 'view') {
|
||||
return UI.submissionModalForSinglePage(url, reloadUrl, $table)
|
||||
}
|
||||
}
|
||||
const successCallback = () => {
|
||||
UI.toast({
|
||||
variant: 'danger',
|
||||
title: 'Could not reload the page',
|
||||
body: 'Reloading the page manually is advised.'
|
||||
})
|
||||
}
|
||||
return UI.submissionModal(url, successCallback)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates and displays a modal where the modal's content is fetched from the provided URL. Reloads the provided element after a successful operation.
|
||||
* Supports `displayOnSuccess` option to show another modal after the submission
|
||||
|
@ -322,7 +351,7 @@ class Toaster {
|
|||
if (options.bodyHtml !== false) {
|
||||
$toastBody = $('<div class="toast-body"/>').html(options.bodyHtml)
|
||||
} else {
|
||||
$toastBody = $('<div class="toast-body"/>').text(options.body)
|
||||
$toastBody = $('<div class="toast-body"/>').append($('<div style="white-space: break-spaces;"/>').text(options.body))
|
||||
}
|
||||
$toast.append($toastBody)
|
||||
}
|
||||
|
|
|
@ -66,6 +66,17 @@ function attachTestConnectionResultHtml(result, $container) {
|
|||
return $testResultDiv
|
||||
}
|
||||
|
||||
function closeModalOnFunctionCompletion(clicked, fun) {
|
||||
const result = fun(clicked)
|
||||
if (result === undefined) {
|
||||
$(clicked).closest('.modal').data('modalObject').hide()
|
||||
} else {
|
||||
result.finally( () => {
|
||||
$(clicked).closest('.modal').data('modalObject').hide()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var UI
|
||||
$(document).ready(() => {
|
||||
if (typeof UIFactory !== "undefined") {
|
||||
|
|
Loading…
Reference in New Issue