Merge branch 'main' of github.com:cerebrate-project/cerebrate into main
commit
88752c8b16
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'RequestProcessors' . DS . 'GenericRequestProcessor.php');
|
||||
|
||||
class BroodRequestProcessor extends GenericRequestProcessor
|
||||
{
|
||||
protected $scope = 'Brood';
|
||||
protected $action = 'not-specified'; //overriden when extending
|
||||
protected $description = ''; // overriden when extending
|
||||
protected $registeredActions = [
|
||||
'ToolInterconnection',
|
||||
'OneWaySynchronization',
|
||||
];
|
||||
|
||||
public function __construct($loadFromAction=false) {
|
||||
parent::__construct($loadFromAction);
|
||||
}
|
||||
|
||||
public function create($requestData)
|
||||
{
|
||||
return parent::create($requestData);
|
||||
}
|
||||
}
|
||||
|
||||
class ToolInterconnectionProcessor extends BroodRequestProcessor implements GenericProcessorActionI {
|
||||
public $action = 'ToolInterconnection';
|
||||
protected $description;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->description = __('Handle tool interconnection request from other cerebrate instance');
|
||||
$this->Broods = TableRegistry::getTableLocator()->get('Broods');
|
||||
}
|
||||
|
||||
protected function addValidatorRules($validator)
|
||||
{
|
||||
return $validator;
|
||||
}
|
||||
|
||||
public function create($requestData) {
|
||||
$this->validateRequestData($requestData);
|
||||
$requestData['title'] = __('Cerebrate instance {0} requested interconnection for tool {1}', 'Insert brood name', 'Insert tool name');
|
||||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
public function process($id, $requestData)
|
||||
{
|
||||
$connectionSuccessfull = false;
|
||||
$interConnectionResult = [];
|
||||
if ($connectionSuccessfull) {
|
||||
$this->discard($id, $requestData);
|
||||
}
|
||||
return $this->genActionResult(
|
||||
$interConnectionResult,
|
||||
$connectionSuccessfull,
|
||||
$connectionSuccessfull ? __('Interconnection for `{0}` created', 'Insert tool name') : __('Could interconnect tool `{0}`.', 'Insert tool name'),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
public function discard($id, $requestData)
|
||||
{
|
||||
return parent::discard($id, $requestData);
|
||||
}
|
||||
}
|
||||
|
||||
class OneWaySynchronizationProcessor extends BroodRequestProcessor implements GenericProcessorActionI {
|
||||
public $action = 'OneWaySynchronization';
|
||||
protected $description;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->description = __('Handle cerebrate connection request for another cerebrate instance');
|
||||
$this->Broods = TableRegistry::getTableLocator()->get('Broods');
|
||||
}
|
||||
|
||||
protected function addValidatorRules($validator)
|
||||
{
|
||||
return $validator;
|
||||
}
|
||||
|
||||
public function create($requestData) {
|
||||
$this->validateRequestData($requestData);
|
||||
$requestData['title'] = __('Cerebrate instance {0} requested interconnection', 'Insert cerebrate name');
|
||||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
public function process($id, $requestData)
|
||||
{
|
||||
$connectionSuccessfull = false;
|
||||
$interConnectionResult = [];
|
||||
if ($connectionSuccessfull) {
|
||||
$this->discard($id, $requestData);
|
||||
}
|
||||
return $this->genActionResult(
|
||||
$interConnectionResult,
|
||||
$connectionSuccessfull,
|
||||
$connectionSuccessfull ? __('Interconnection with `{0}` created', 'Insert cerebrate name') : __('Could interconnect with `{0}`.', 'Insert cerebrate name'),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
public function discard($id, $requestData)
|
||||
{
|
||||
return parent::discard($id, $requestData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
use Cake\Filesystem\File;
|
||||
use Cake\Utility\Inflector;
|
||||
use Cake\Validation\Validator;
|
||||
use Cake\View\ViewBuilder;
|
||||
|
||||
interface GenericProcessorActionI
|
||||
{
|
||||
public function create($requestData);
|
||||
public function process($requestID, $serverRequest);
|
||||
public function discard($requestID ,$requestData);
|
||||
}
|
||||
|
||||
class GenericRequestProcessor
|
||||
{
|
||||
protected $Inbox;
|
||||
protected $registeredActions = [];
|
||||
protected $validator;
|
||||
private $processingTemplate = '/genericTemplates/confirm';
|
||||
private $processingTemplatesDirectory = ROOT . '/libraries/default/RequestProcessors/templates';
|
||||
|
||||
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 = str_replace('.php', '', $processingTemplatePath);
|
||||
}
|
||||
$file->close();
|
||||
}
|
||||
|
||||
public function getRegisteredActions()
|
||||
{
|
||||
return $this->registeredActions;
|
||||
}
|
||||
public function getScope()
|
||||
{
|
||||
return $this->scope;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
return $this->processingTemplate;
|
||||
}
|
||||
|
||||
public function render($request=[])
|
||||
{
|
||||
$processingTemplate = $this->getProcessingTemplate();
|
||||
$viewVariables = $this->getViewVariables($request);
|
||||
$builder = new ViewBuilder();
|
||||
$builder->disableAutoLayout()
|
||||
->setClassName('Monad')
|
||||
->setTemplate($processingTemplate);
|
||||
$view = $builder->build($viewVariables);
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
protected function generateRequest($requestData)
|
||||
{
|
||||
$request = $this->Inbox->newEmptyEntity();
|
||||
$request = $this->Inbox->patchEntity($request, $requestData);
|
||||
if ($request->getErrors()) {
|
||||
throw new Exception(__('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();
|
||||
}
|
||||
}
|
||||
|
||||
protected function getViewVariablesConfirmModal($id, $title='', $question='', $actionName='')
|
||||
{
|
||||
return [
|
||||
'title' => !empty($title) ? $title : __('Process request {0}', $id),
|
||||
'question' => !empty($question) ? $question : __('Confirm request {0}', $id),
|
||||
'actionName' => !empty($actionName) ? $actionName : __('Confirm'),
|
||||
'path' => ['controller' => 'inbox', 'action' => 'process', $id]
|
||||
];
|
||||
}
|
||||
|
||||
public function getViewVariables($request)
|
||||
{
|
||||
return $this->getViewVariablesConfirmModal($request->id, '', '', '');
|
||||
}
|
||||
|
||||
protected function genActionResult($data, $success, $message, $errors=[])
|
||||
{
|
||||
return [
|
||||
'data' => $data,
|
||||
'success' => $success,
|
||||
'message' => $message,
|
||||
'errors' => $errors,
|
||||
];
|
||||
}
|
||||
|
||||
public function genHTTPReply($controller, $processResult, $redirect=null)
|
||||
{
|
||||
$scope = $this->scope;
|
||||
$action = $this->action;
|
||||
if ($processResult['success']) {
|
||||
$message = !empty($processResult['message']) ? $processResult['message'] : __('Request {0} successfully processed.', $id);
|
||||
if ($controller->ParamHandler->isRest()) {
|
||||
$response = $controller->RestResponse->viewData($processResult, 'json');
|
||||
} else if ($controller->ParamHandler->isAjax()) {
|
||||
$response = $controller->RestResponse->ajaxSuccessResponse('RequestProcessor', "{$scope}.{$action}", $processResult['data'], $message);
|
||||
} else {
|
||||
$controller->Flash->success($message);
|
||||
if (!is_null($redirect)) {
|
||||
$response = $controller->redirect($redirect);
|
||||
} else {
|
||||
$response = $controller->redirect(['action' => 'index']);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$message = !empty($processResult['message']) ? $processResult['message'] : __('Request {0} could not be processed.', $id);
|
||||
if ($controller->ParamHandler->isRest()) {
|
||||
$response = $controller->RestResponse->viewData($processResult, 'json');
|
||||
} else if ($controller->ParamHandler->isAjax()) {
|
||||
$response = $controller->RestResponse->ajaxFailResponse('RequestProcessor', "{$scope}.{$action}", $processResult['data'], $message, $processResult['errors']);
|
||||
} else {
|
||||
$controller->Flash->error($message);
|
||||
if (!is_null($redirect)) {
|
||||
$response = $controller->redirect($redirect);
|
||||
} else {
|
||||
$response = $controller->redirect(['action' => 'index']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function checkLoading()
|
||||
{
|
||||
return 'Assimilation successful!';
|
||||
}
|
||||
|
||||
public function create($requestData)
|
||||
{
|
||||
$requestData['scope'] = $this->scope;
|
||||
$requestData['action'] = $this->action;
|
||||
$requestData['description'] = $this->description;
|
||||
$request = $this->generateRequest($requestData);
|
||||
$savedRequest = $this->Inbox->save($request);
|
||||
return $this->genActionResult(
|
||||
$savedRequest,
|
||||
$savedRequest !== false,
|
||||
__('{0} request for {1} created', $this->scope, $this->action),
|
||||
$request->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
public function discard($id, $requestData)
|
||||
{
|
||||
$request = $this->Inbox->get($id);
|
||||
$this->Inbox->delete($request);
|
||||
return $this->genActionResult(
|
||||
[],
|
||||
true,
|
||||
__('{0}.{1} request #{2} discarded', $this->scope, $this->action, $id)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'RequestProcessors' . DS . 'GenericRequestProcessor.php');
|
||||
|
||||
class ProposalRequestProcessor extends GenericRequestProcessor
|
||||
{
|
||||
protected $scope = 'Proposal';
|
||||
protected $action = 'not-specified'; //overriden when extending
|
||||
protected $description = ''; // overriden when extending
|
||||
protected $registeredActions = [
|
||||
'ProposalEdit'
|
||||
];
|
||||
|
||||
public function __construct($loadFromAction=false) {
|
||||
parent::__construct($loadFromAction);
|
||||
}
|
||||
|
||||
public function create($requestData)
|
||||
{
|
||||
return parent::create($requestData);
|
||||
}
|
||||
}
|
||||
|
||||
class ProposalEditProcessor extends ProposalRequestProcessor implements GenericProcessorActionI {
|
||||
public $action = 'ProposalEdit';
|
||||
protected $description;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->description = __('Handle proposal from users for this cerebrate instance');
|
||||
$this->Users = TableRegistry::getTableLocator()->get('Users');
|
||||
}
|
||||
|
||||
protected function addValidatorRules($validator)
|
||||
{
|
||||
return $validator;
|
||||
}
|
||||
|
||||
public function create($requestData) {
|
||||
$this->validateRequestData($requestData);
|
||||
$requestData['title'] = __('User `{0}` would like to modify record `{0}`', 'username', 'recordname');
|
||||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
public function process($id, $requestData)
|
||||
{
|
||||
$proposalAccepted = false;
|
||||
$saveResult = [];
|
||||
if ($proposalAccepted) {
|
||||
$this->discard($id, $requestData);
|
||||
}
|
||||
return $this->genActionResult(
|
||||
$saveResult,
|
||||
$proposalAccepted,
|
||||
$proposalAccepted ? __('Record `{0}` modify', 'recordname') : __('Could modify record `{0}`.', 'recordname'),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
public function discard($id, $requestData)
|
||||
{
|
||||
return parent::discard($id, $requestData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'RequestProcessors' . DS . 'GenericRequestProcessor.php');
|
||||
|
||||
class SynchronisationRequestProcessor extends GenericRequestProcessor
|
||||
{
|
||||
protected $scope = 'Synchronisation';
|
||||
protected $action = 'not-specified'; //overriden when extending
|
||||
protected $description = ''; // overriden when extending
|
||||
protected $registeredActions = [
|
||||
'DataExchange'
|
||||
];
|
||||
|
||||
public function __construct($loadFromAction=false) {
|
||||
parent::__construct($loadFromAction);
|
||||
}
|
||||
|
||||
public function create($requestData)
|
||||
{
|
||||
return parent::create($requestData);
|
||||
}
|
||||
}
|
||||
|
||||
class DataExchangeProcessor extends SynchronisationRequestProcessor implements GenericProcessorActionI {
|
||||
public $action = 'DataExchange';
|
||||
protected $description;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->description = __('Handle exchange of data between two cerebrate instances');
|
||||
$this->Users = TableRegistry::getTableLocator()->get('Users');
|
||||
}
|
||||
|
||||
protected function addValidatorRules($validator)
|
||||
{
|
||||
return $validator;
|
||||
}
|
||||
|
||||
public function create($requestData) {
|
||||
$this->validateRequestData($requestData);
|
||||
$requestData['title'] = __('Data exchange requested for record `{0}`', 'recordname');
|
||||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
public function process($id, $requestData)
|
||||
{
|
||||
$dataExchangeAccepted = false;
|
||||
$saveResult = [];
|
||||
if ($dataExchangeAccepted) {
|
||||
$this->discard($id, $requestData);
|
||||
}
|
||||
return $this->genActionResult(
|
||||
$saveResult,
|
||||
$dataExchangeAccepted,
|
||||
$dataExchangeAccepted ? __('Record `{0}` exchanged', 'recordname') : __('Could not exchange record `{0}`.', 'recordname'),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
public function discard($id, $requestData)
|
||||
{
|
||||
return parent::discard($id, $requestData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'RequestProcessors' . DS . 'GenericRequestProcessor.php');
|
||||
|
||||
class SCOPE_RequestProcessor extends GenericRequestProcessor
|
||||
{
|
||||
protected $scope = '~to-be-defined~';
|
||||
protected $action = 'not-specified'; //overriden when extending
|
||||
protected $description = ''; // overriden when extending
|
||||
protected $registeredActions = [
|
||||
'ACTION'
|
||||
];
|
||||
|
||||
public function __construct($loadFromAction=false) {
|
||||
parent::__construct($loadFromAction);
|
||||
}
|
||||
|
||||
public function create($requestData)
|
||||
{
|
||||
return parent::create($requestData);
|
||||
}
|
||||
}
|
||||
|
||||
class SCOPE_ACTION_Processor extends ProposalRequestProcessor implements GenericProcessorActionI {
|
||||
public $action = 'ACTION';
|
||||
protected $description;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->description = __('~to-be-defined~');
|
||||
$this->Users = TableRegistry::getTableLocator()->get('Users');
|
||||
}
|
||||
|
||||
protected function addValidatorRules($validator)
|
||||
{
|
||||
return $validator;
|
||||
}
|
||||
|
||||
public function create($requestData) {
|
||||
$this->validateRequestData($requestData);
|
||||
$requestData['title'] = __('~to-be-defined~');
|
||||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
public function process($id, $requestData)
|
||||
{
|
||||
$proposalAccepted = false;
|
||||
$saveResult = [];
|
||||
if ($proposalAccepted) {
|
||||
$this->discard($id, $requestData);
|
||||
}
|
||||
return $this->genActionResult(
|
||||
$saveResult,
|
||||
$proposalAccepted,
|
||||
$proposalAccepted ? __('success') : __('fail'),
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
public function discard($id, $requestData)
|
||||
{
|
||||
return parent::discard($id, $requestData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'RequestProcessors' . DS . 'GenericRequestProcessor.php');
|
||||
|
||||
class UserRequestProcessor extends GenericRequestProcessor
|
||||
{
|
||||
protected $scope = 'User';
|
||||
protected $action = 'not-specified'; //overriden when extending
|
||||
protected $description = ''; // overriden when extending
|
||||
protected $registeredActions = [
|
||||
'Registration'
|
||||
];
|
||||
protected $Users;
|
||||
|
||||
public function __construct($loadFromAction=false) {
|
||||
parent::__construct($loadFromAction);
|
||||
$this->Users = TableRegistry::getTableLocator()->get('Users');
|
||||
}
|
||||
|
||||
public function create($requestData)
|
||||
{
|
||||
return parent::create($requestData);
|
||||
}
|
||||
}
|
||||
|
||||
class RegistrationProcessor extends UserRequestProcessor implements GenericProcessorActionI {
|
||||
public $action = 'Registration';
|
||||
protected $description;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->description = __('Handle user account for this cerebrate instance');
|
||||
}
|
||||
|
||||
protected function addValidatorRules($validator)
|
||||
{
|
||||
return $validator
|
||||
->notEmpty('username', 'A username must be provided.')
|
||||
->add('email', 'validFormat', [
|
||||
'rule' => 'email',
|
||||
'message' => 'E-mail must be valid'
|
||||
])
|
||||
->notEmpty('first_name', 'A first name must be provided')
|
||||
->notEmpty('last_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']);
|
||||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
public function getViewVariables($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'] : '',
|
||||
]);
|
||||
return [
|
||||
'dropdownData' => $dropdownData,
|
||||
'userEntity' => $userEntity,
|
||||
'individualEntity' => $individualEntity
|
||||
];
|
||||
}
|
||||
|
||||
public function process($id, $requestData)
|
||||
{
|
||||
if ($requestData['individual_id'] == -1) {
|
||||
$individual = $this->Users->Individuals->newEntity([
|
||||
'uuid' => $requestData['uuid'],
|
||||
'email' => $requestData['email'],
|
||||
'first_name' => $requestData['first_name'],
|
||||
'last_name' => $requestData['last_name'],
|
||||
'position' => $requestData['position'],
|
||||
]);
|
||||
$individual = $this->Users->Individuals->save($individual);
|
||||
} else {
|
||||
$individual = $this->Users->Individuals->get($requestData['individual_id']);
|
||||
}
|
||||
$user = $this->Users->newEntity([
|
||||
'individual_id' => $individual->id,
|
||||
'username' => $requestData['username'],
|
||||
'password' => '~PASSWORD_TO_BE_REPLACED~',
|
||||
'role_id' => $requestData['role_id'],
|
||||
'disabled' => $requestData['disabled'],
|
||||
]);
|
||||
$user = $this->Users->save($user);
|
||||
|
||||
if ($user !== false) {
|
||||
$this->discard($id, $requestData);
|
||||
}
|
||||
return $this->genActionResult(
|
||||
$user,
|
||||
$user !== false,
|
||||
$user !== false ? __('User `{0}` created', $user->username) : __('Could not create user `{0}`.', $user->username),
|
||||
$user->getErrors()
|
||||
);
|
||||
}
|
||||
|
||||
public function discard($id, $requestData)
|
||||
{
|
||||
return parent::discard($id, $requestData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
$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')
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$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')
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
echo $this->Bootstrap->modal([
|
||||
'title' => __('Register user'),
|
||||
'size' => 'lg',
|
||||
'type' => 'confirm',
|
||||
'bodyHtml' => sprintf('<div class="user-container">%s</div><div class="individual-container">%s</div>',
|
||||
$formUser,
|
||||
$formIndividual
|
||||
),
|
||||
'confirmText' => __('Create user'),
|
||||
'confirmFunction' => 'submitRegistration'
|
||||
]);
|
||||
?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function submitRegistration(modalObject, tmpApi) {
|
||||
const $forms = modalObject.$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>
|
||||
|
||||
<style>
|
||||
div.individual-container > div, div.user-container > div {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
</style>
|
|
@ -107,6 +107,12 @@ 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->ACL->checkAccess();
|
||||
$this->set('menu', $this->ACL->getMenu());
|
||||
$this->set('ajax', $this->request->is('ajax'));
|
||||
|
|
|
@ -6,6 +6,7 @@ use App\Controller\AppController;
|
|||
use Cake\Utility\Hash;
|
||||
use Cake\Utility\Text;
|
||||
use \Cake\Database\Expression\QueryExpression;
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
class BroodsController extends AppController
|
||||
{
|
||||
|
@ -154,4 +155,22 @@ class BroodsController extends AppController
|
|||
$this->redirect($this->referer());
|
||||
}
|
||||
}
|
||||
|
||||
public function interconnectTools()
|
||||
{
|
||||
$this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
|
||||
$processor = $this->requestProcessor->getProcessor('Brood', 'ToolInterconnection');
|
||||
$data = [
|
||||
'origin' => '127.0.0.1',
|
||||
'comment' => 'Test comment',
|
||||
'user_id' => $this->ACL->getUser()->id,
|
||||
'data' => [
|
||||
'foo' => 'foo',
|
||||
'bar' => 'bar',
|
||||
'baz' => 'baz',
|
||||
],
|
||||
];
|
||||
$processorResult = $processor->create($data);
|
||||
return $processor->genHTTPReply($this, $processorResult, ['controller' => 'Broods', 'action' => 'index']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -165,7 +165,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()) {
|
||||
|
@ -508,7 +508,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;
|
||||
|
@ -438,11 +439,12 @@ class RestResponseComponent extends Component
|
|||
public function ajaxFailResponse($ObjectAlias, $action, $entity, $message, $errors = [])
|
||||
{
|
||||
$action = $this->__dissectAdminRouting($action);
|
||||
$entity = is_array($entity) ? $entity : $entity->toArray();
|
||||
$response = [
|
||||
'success' => false,
|
||||
'message' => $message,
|
||||
'errors' => $errors,
|
||||
'url' => $this->__generateURL($action, $ObjectAlias, $entity->id)
|
||||
'url' => !empty($entity['id']) ? $this->__generateURL($action, $ObjectAlias, $entity['id']) : ''
|
||||
];
|
||||
return $this->viewData($response);
|
||||
}
|
||||
|
|
|
@ -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,131 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Controller\AppController;
|
||||
use Cake\Database\Expression\QueryExpression;
|
||||
use Cake\Event\EventInterface;
|
||||
use Cake\ORM\TableRegistry;
|
||||
use Cake\Utility\Hash;
|
||||
use Cake\Utility\Text;
|
||||
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 view($id)
|
||||
{
|
||||
$this->CRUD->view($id);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
if ($this->request->is('post')) {
|
||||
$request = $this->Inbox->get($id);
|
||||
$this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
|
||||
$processor = $this->requestProcessor->getProcessor($request->scope, $request->action);
|
||||
$discardResult = $processor->discard($id, $request);
|
||||
return $processor->genHTTPReply($this, $discardResult);
|
||||
}
|
||||
$this->set('deletionTitle', __('Discard request'));
|
||||
$this->set('deletionText', __('Are you sure you want to discard request #{0}?', $id));
|
||||
$this->set('deletionConfirm', __('Discard'));
|
||||
$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;
|
||||
$this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
|
||||
$processor = $this->requestProcessor->getProcessor($request->scope, $request->action);
|
||||
if ($this->request->is('post')) {
|
||||
$processResult = $processor->process($id, $this->request->getData());
|
||||
return $processor->genHTTPReply($this, $processResult);
|
||||
} else {
|
||||
$renderedView = $processor->render($request);
|
||||
return $this->response->withStringBody($renderedView);
|
||||
}
|
||||
}
|
||||
|
||||
public function listProcessors()
|
||||
{
|
||||
$this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
|
||||
$requestProcessors = $this->requestProcessor->listProcessors();
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
return $this->RestResponse->viewData($requestProcessors, 'json');
|
||||
}
|
||||
$data = [];
|
||||
foreach ($requestProcessors as $scope => $processors) {
|
||||
foreach ($processors as $processor) {
|
||||
$data[] = [
|
||||
'enabled' => $processor->enabled,
|
||||
'scope' => $scope,
|
||||
'action' => $processor->action
|
||||
];
|
||||
}
|
||||
}
|
||||
$this->set('title', 'Available request processors');
|
||||
$this->set('fields', [
|
||||
[
|
||||
'name' => 'Enabled',
|
||||
'data_path' => 'enabled',
|
||||
'element' => 'boolean'
|
||||
],
|
||||
[
|
||||
'name' => 'Processor scope',
|
||||
'data_path' => 'scope',
|
||||
],
|
||||
[
|
||||
'name' => 'Processor action',
|
||||
'data_path' => 'action',
|
||||
]
|
||||
]);
|
||||
$this->set('data', $data);
|
||||
$this->render('/genericTemplates/index_simple');
|
||||
}
|
||||
}
|
|
@ -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,22 @@ class UsersController extends AppController
|
|||
return $this->redirect(\Cake\Routing\Router::url('/users/login'));
|
||||
}
|
||||
}
|
||||
|
||||
public function register()
|
||||
{
|
||||
$this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
|
||||
$processor = $this->requestProcessor->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']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,65 @@
|
|||
<?php
|
||||
namespace App\Model\Table;
|
||||
|
||||
use App\Model\Table\AppTable;
|
||||
use Cake\Database\Schema\TableSchemaInterface;
|
||||
use Cake\Database\Type;
|
||||
use Cake\ORM\Table;
|
||||
use Cake\ORM\RulesChecker;
|
||||
use Cake\Validation\Validator;
|
||||
|
||||
Type::map('json', 'Cake\Database\Type\JsonType');
|
||||
|
||||
class InboxTable extends AppTable
|
||||
{
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
namespace App\Model\Table;
|
||||
|
||||
use App\Model\Table\AppTable;
|
||||
use Cake\Filesystem\Folder;
|
||||
|
||||
class RequestProcessorTable extends AppTable
|
||||
{
|
||||
private $processorsDirectory = ROOT . '/libraries/default/RequestProcessors';
|
||||
private $requestProcessors;
|
||||
private $enabledProcessors = [ // to be defined in config
|
||||
'Brood' => [
|
||||
'ToolInterconnection' => false,
|
||||
'OneWaySynchronization' => false,
|
||||
],
|
||||
'Proposal' => [
|
||||
'ProposalEdit' => false,
|
||||
],
|
||||
'Synchronisation' => [
|
||||
'DataExchange' => false,
|
||||
],
|
||||
'User' => [
|
||||
'Registration' => true,
|
||||
],
|
||||
];
|
||||
|
||||
public function initialize(array $config): void
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->loadProcessors();
|
||||
}
|
||||
|
||||
public function getProcessor($scope, $action=null)
|
||||
{
|
||||
if (isset($this->requestProcessors[$scope])) {
|
||||
if (is_null($action)) {
|
||||
return $this->requestProcessors[$scope];
|
||||
} else if (!empty($this->requestProcessors[$scope]->{$action})) {
|
||||
return $this->requestProcessors[$scope]->{$action};
|
||||
} else {
|
||||
throw new \Exception(__('Processor {0}.{1} not found', $scope, $action));
|
||||
}
|
||||
}
|
||||
throw new \Exception(__('Processor not found'), 1);
|
||||
}
|
||||
|
||||
public function listProcessors($scope=null)
|
||||
{
|
||||
if (is_null($scope)) {
|
||||
return $this->requestProcessors;
|
||||
} else {
|
||||
if (isset($this->requestProcessors[$scope])) {
|
||||
return $this->requestProcessors[$scope];
|
||||
} else {
|
||||
throw new \Exception(__('Processors for {0} not found', $scope));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function loadProcessors()
|
||||
{
|
||||
$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;
|
||||
foreach ($this->requestProcessors[$processorMainClassNameShort]->getRegisteredActions() as $registeredAction) {
|
||||
$scope = $this->requestProcessors[$processorMainClassNameShort]->getScope();
|
||||
if (!empty($this->enabledProcessors[$scope][$registeredAction])) {
|
||||
$this->requestProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = true;
|
||||
} else {
|
||||
$this->requestProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getProcessorClass($filePath, $processorMainClassName)
|
||||
{
|
||||
require_once($filePath);
|
||||
$reflection = new \ReflectionClass($processorMainClassName);
|
||||
$processorMainClass = $reflection->newInstance(true);
|
||||
if ($processorMainClass->checkLoading() === 'Assimilation successful!') {
|
||||
return $processorMainClass;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -721,7 +721,7 @@ class BoostrapModal extends BootstrapGeneric {
|
|||
'footerClass' => [''],
|
||||
'type' => 'ok-only',
|
||||
'variant' => '',
|
||||
'confirmFunction' => '',
|
||||
'confirmFunction' => '', // Will be called with the following arguments confirmFunction(modalObject, tmpApi)
|
||||
'cancelFunction' => ''
|
||||
];
|
||||
|
||||
|
@ -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();
|
||||
|
@ -847,9 +847,10 @@ class BoostrapModal extends BootstrapGeneric {
|
|||
$buttonConfirm = (new BoostrapButton([
|
||||
'variant' => $variant,
|
||||
'text' => h($this->options['confirmText']),
|
||||
'class' => 'modal-confirm-button',
|
||||
'params' => [
|
||||
'data-dismiss' => 'modal',
|
||||
'onclick' => $this->options['confirmFunction']
|
||||
'data-dismiss' => $this->options['confirmFunction'] ? '' : 'modal',
|
||||
'data-confirmFunction' => sprintf('%s', $this->options['confirmFunction'])
|
||||
]
|
||||
]))->button();
|
||||
return $buttonCancel . $buttonConfirm;
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.0.4
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace App\View;
|
||||
|
||||
/**
|
||||
* A view class that supports rendering view file belonging to directories outside of the main application template folder.
|
||||
*/
|
||||
class MonadView extends AppView
|
||||
{
|
||||
private $additionalTemplatePaths = [
|
||||
ROOT . '/libraries/default/RequestProcessors/templates/',
|
||||
];
|
||||
|
||||
protected function _paths(?string $plugin = null, bool $cached = true): array
|
||||
{
|
||||
$paths = parent::_paths($plugin, $cached);
|
||||
$paths = array_merge($paths, $this->additionalTemplatePaths);
|
||||
return $paths;
|
||||
}
|
||||
}
|
|
@ -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,99 @@
|
|||
<?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' => [
|
||||
[
|
||||
'url' => '/inbox/view',
|
||||
'url_params_data_paths' => ['id'],
|
||||
'icon' => 'eye',
|
||||
'title' => __('View request')
|
||||
],
|
||||
[
|
||||
'open_modal' => '/inbox/process/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
'icon' => 'cogs',
|
||||
'title' => __('Process request')
|
||||
],
|
||||
[
|
||||
'open_modal' => '/inbox/delete/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
'icon' => 'trash',
|
||||
'title' => __('Discard request')
|
||||
],
|
||||
]
|
||||
]
|
||||
]);
|
||||
echo '</div>';
|
||||
?>
|
|
@ -0,0 +1,51 @@
|
|||
<?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',
|
||||
'type' => 'json'
|
||||
],
|
||||
],
|
||||
'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()
|
||||
})
|
||||
},
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
echo sprintf(
|
||||
'%s',
|
||||
sprintf(
|
||||
'<button id="submitButton" class="btn btn-primary" data-form-id="%s" autofocus>%s</button>',
|
||||
'<button id="submitButton" class="btn btn-primary" data-form-id="%s" type="button" autofocus>%s</button>',
|
||||
'#form-' . h($formRandomValue),
|
||||
__('Submit')
|
||||
)
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
);
|
||||
}
|
||||
$reload_url = !empty($action['reload_url']) ? $action['reload_url'] : $this->Url->build(['action' => 'index']);
|
||||
$action['onclick'] = sprintf('UI.openModalFromURL(\'%s\', \'%s\', \'%s\')', $modal_url, $reload_url, $tableRandomValue);
|
||||
$action['onclick'] = sprintf('UI.submissionModalForIndex(\'%s\', \'%s\', \'%s\')', $modal_url, $reload_url, $tableRandomValue);
|
||||
}
|
||||
echo sprintf(
|
||||
'<a href="%s" title="%s" aria-label="%s" %s %s class="link-unstyled"><i class="%s"></i></a> ',
|
||||
|
|
|
@ -14,7 +14,7 @@ if ($field['scope'] === 'individuals') {
|
|||
h($alignment['organisation']['name'])
|
||||
),
|
||||
!$canRemove ? '' : sprintf(
|
||||
"UI.openModalFromURL(%s);",
|
||||
"UI.submissionModalForIndex(%s);",
|
||||
sprintf(
|
||||
"'/alignments/delete/%s'",
|
||||
h($alignment['id'])
|
||||
|
@ -34,7 +34,7 @@ if ($field['scope'] === 'individuals') {
|
|||
h($alignment['individual']['email'])
|
||||
),
|
||||
!$canRemove ? '' : sprintf(
|
||||
"UI.openModalFromURL(%s);",
|
||||
"UI.submissionModalForIndex(%s);",
|
||||
sprintf(
|
||||
"'/alignments/delete/%s'",
|
||||
h($alignment['id'])
|
||||
|
|
|
@ -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]
|
||||
);
|
||||
}
|
||||
|
||||
?>
|
|
@ -71,12 +71,8 @@
|
|||
|
||||
<script>
|
||||
function openModalForButton(clicked, url, reloadUrl='') {
|
||||
const loadingOverlay = new OverlayFactory(clicked);
|
||||
const fallbackReloadUrl = '<?= $this->Url->build(['action' => 'index']); ?>'
|
||||
reloadUrl = reloadUrl != '' ? reloadUrl : fallbackReloadUrl
|
||||
loadingOverlay.show()
|
||||
UI.openModalFromURL(url, reloadUrl, '<?= $tableRandomValue ?>').finally(() => {
|
||||
loadingOverlay.hide()
|
||||
})
|
||||
UI.overlayUntilResolve(clicked, UI.submissionModalForIndex(url, reloadUrl, '<?= $tableRandomValue ?>'))
|
||||
}
|
||||
</script>
|
|
@ -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>',
|
||||
|
@ -127,7 +130,8 @@
|
|||
}
|
||||
$searchType = $('<span/>')
|
||||
.text(searchContain ? '<?= __('Contain') ?>' : '<?= __('Exact match') ?>')
|
||||
.attr('title', searchContain ? '<?= __('The search value will be used as a substring') ?>' : '<?= __('The search value must strictly match') ?>')
|
||||
.attr('title', searchContain ? '<?= __('The search value can be used as a substring with the wildcard operator `%`') ?>' : '<?= __('The search value must strictly match') ?>')
|
||||
.attr('style', 'cursor: help;')
|
||||
tableData.push([fieldName, $searchType])
|
||||
});
|
||||
tableData.sort((a, b) => a[0] < b[0] ? -1 : 1)
|
||||
|
@ -144,11 +148,7 @@
|
|||
}
|
||||
|
||||
function openFilteringModal(clicked, url, reloadUrl, tableId) {
|
||||
const loadingOverlay = new OverlayFactory(clicked);
|
||||
loadingOverlay.show()
|
||||
UI.openModalFromURL(url, reloadUrl, tableId).finally(() => {
|
||||
loadingOverlay.hide()
|
||||
})
|
||||
UI.overlayUntilResolve(clicked, UI.submissionModalForIndex(url, reloadUrl, tableId))
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
$elements .= $this->element('/genericElements/ListTopBar/element_' . (empty($element['type']) ? 'simple' : h($element['type'])), array('data' => $element, 'tableRandomValue' => $tableRandomValue));
|
||||
}
|
||||
echo sprintf(
|
||||
'<div %s class="btn-group btn-group-sm mr-2" role="group" aria-label="button-group">%s</div>',
|
||||
'<div %s class="btn-group btn-group-sm mr-2 flex-wrap" role="group" aria-label="button-group">%s</div>',
|
||||
(!empty($data['id'])) ? 'id="' . h($data['id']) . '"' : '',
|
||||
$elements
|
||||
);
|
||||
|
|
|
@ -20,7 +20,7 @@ if ($field['scope'] === 'individuals') {
|
|||
h($alignment['organisation']['name'])
|
||||
),
|
||||
sprintf(
|
||||
"UI.openModalFromURL(%s);",
|
||||
"UI.submissionModalForSinglePage(%s);",
|
||||
sprintf(
|
||||
"'/alignments/delete/%s'",
|
||||
$alignment['id']
|
||||
|
@ -40,7 +40,7 @@ if ($field['scope'] === 'individuals') {
|
|||
h($alignment['individual']['email'])
|
||||
),
|
||||
sprintf(
|
||||
"UI.openModalFromURL(%s);",
|
||||
"UI.submissionModalForSinglePage(%s);",
|
||||
sprintf(
|
||||
"'/alignments/delete/%s'",
|
||||
$alignment['id']
|
||||
|
@ -53,7 +53,7 @@ echo sprintf(
|
|||
'<div class="alignments-list">%s</div><div class="alignments-add-container"><button class="alignments-add-button btn btn-primary btn-sm" onclick="%s">%s</button></div>',
|
||||
$alignments,
|
||||
sprintf(
|
||||
"UI.openModalFromURL('/alignments/add/%s/%s');",
|
||||
"UI.submissionModalForSinglePage('/alignments/add/%s/%s');",
|
||||
h($field['scope']),
|
||||
h($extracted['id'])
|
||||
),
|
||||
|
|
|
@ -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.openModalFromURL(\'%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>';
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<?= $this->Form->postLink(
|
||||
h($actionName),
|
||||
$path,
|
||||
['class' => 'btn btn-primary button-execute']
|
||||
['class' => 'btn btn-primary button-execute', 'id' => 'submitButton']
|
||||
)
|
||||
?>
|
||||
<button type="button" class="btn btn-secondary cancel-button" data-dismiss="modal"><?= __('Cancel') ?></button>
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title"><?= __('Delete {0}', h(Cake\Utility\Inflector::singularize(Cake\Utility\Inflector::humanize($this->request->getParam('controller'))))) ?></h5>
|
||||
<h5 class="modal-title">
|
||||
<?php if (empty($deletionTitle)): ?>
|
||||
<p><?= __('Delete {0}', h(Cake\Utility\Inflector::singularize(Cake\Utility\Inflector::humanize($this->request->getParam('controller'))))) ?></p>
|
||||
<?php else: ?>
|
||||
<p><?= h($deletionTitle) ?></p>
|
||||
<?php endif; ?>
|
||||
</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
|
@ -15,9 +21,9 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<?= $this->Form->postLink(
|
||||
'Delete',
|
||||
!empty($deletionConfirm) ? h($deletionConfirm) : __('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>
|
||||
|
|
|
@ -15,7 +15,7 @@ $filteringForm = $this->Bootstrap->table(
|
|||
[
|
||||
'labelHtml' => sprintf('%s %s',
|
||||
__('Value'),
|
||||
sprintf('<span class="fa fa-info ml-1" title="%s"><span>', __('Supports strict match and LIKE match with the `%` character. Example: `%.com`'))
|
||||
sprintf('<sup class="fa fa-info" title="%s"><sup>', __('Supports strict match and LIKE match with the `%` character. Example: `%.com`'))
|
||||
)
|
||||
],
|
||||
__('Action')
|
||||
|
@ -30,7 +30,7 @@ echo $this->Bootstrap->modal([
|
|||
'type' => 'confirm',
|
||||
'bodyHtml' => $filteringForm,
|
||||
'confirmText' => __('Filter'),
|
||||
'confirmFunction' => 'filterIndex(this)'
|
||||
'confirmFunction' => 'filterIndex'
|
||||
]);
|
||||
?>
|
||||
|
||||
|
@ -40,11 +40,10 @@ echo $this->Bootstrap->modal([
|
|||
initFilteringTable($filteringTable)
|
||||
})
|
||||
|
||||
function filterIndex(clicked) {
|
||||
function filterIndex(modalObject, tmpApi) {
|
||||
const controller = '<?= $this->request->getParam('controller') ?>';
|
||||
const action = 'index';
|
||||
const $clicked = $(clicked)
|
||||
const $tbody = $clicked.closest('div.modal-content').find('table.indexFilteringTable tbody')
|
||||
const $tbody = modalObject.$modal.find('table.indexFilteringTable tbody')
|
||||
const $rows = $tbody.find('tr:not(#controlRow)')
|
||||
const activeFilters = {}
|
||||
$rows.each(function() {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
echo $this->element('genericElements/IndexTable/index_table', [
|
||||
'data' => [
|
||||
'skip_pagination' => true,
|
||||
'data' => !empty($data) ? $data : [],
|
||||
'top_bar' => [],
|
||||
'fields' => !empty($fields) ? $fields : [],
|
||||
'title' => !empty($title) ? h($title) : __('Index'),
|
||||
'description' => !empty($description) ? h($description) : '',
|
||||
'actions' => !empty($actions) ? $actions : []
|
||||
],
|
||||
]);
|
|
@ -134,6 +134,18 @@ class AJAXApi {
|
|||
return tmpApi.postForm(form, dataToMerge, constAlteredOptions.skipRequestHooks)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url - The URL to on which to execute the POST
|
||||
* @param {Object} [data={}] - The data to be posted
|
||||
* @param {Object} [options={}] - The options supported by AJAXApi#defaultOptions
|
||||
* @return {Promise<Object>} Promise object resolving to the result of the POST operation
|
||||
*/
|
||||
static async quickPostData(url, data={}, options={}) {
|
||||
const constAlteredOptions = Object.assign({}, {}, options)
|
||||
const tmpApi = new AJAXApi(constAlteredOptions)
|
||||
return tmpApi.postData(url, data, constAlteredOptions.skipRequestHooks)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url - The URL from which to fetch the form
|
||||
* @param {Object} [dataToMerge={}] - Additional data to be integrated or modified in the form
|
||||
|
@ -258,6 +270,61 @@ class AJAXApi {
|
|||
return toReturn
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} url - The URL to fetch
|
||||
* @param {Object} dataToPost - data to be posted
|
||||
* @param {boolean} [skipRequestHooks=false] - If true, default request hooks will be skipped
|
||||
* @param {boolean} [skipFeedback=false] - Pass this value to the AJAXApi.provideFeedback function
|
||||
* @return {Promise<string>} Promise object resolving to the result of the POST
|
||||
*/
|
||||
async postData(url, dataToPost, skipRequestHooks=false, skipFeedback=false) {
|
||||
if (!skipRequestHooks) {
|
||||
this.beforeRequest()
|
||||
}
|
||||
let toReturn
|
||||
try {
|
||||
let formData = new FormData()
|
||||
formData = AJAXApi.mergeFormData(formData, dataToPost)
|
||||
let requestConfig = AJAXApi.genericRequestConfigPOST
|
||||
requestConfig.headers.append('AUTHORIZATION', '~HACKY-HACK~')
|
||||
let options = {
|
||||
...requestConfig,
|
||||
body: formData,
|
||||
};
|
||||
const response = await fetch(url, options);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Network response was not ok. \`${response.statusText}\``)
|
||||
}
|
||||
const data = await response.json()
|
||||
if (data.success) {
|
||||
this.provideFeedback({
|
||||
variant: 'success',
|
||||
body: data.message
|
||||
}, false, skipFeedback);
|
||||
toReturn = data;
|
||||
} else {
|
||||
this.provideFeedback({
|
||||
variant: 'danger',
|
||||
title: 'There has been a problem with the operation',
|
||||
body: data.message
|
||||
}, true, skipFeedback);
|
||||
toReturn = Promise.reject(data.errors);
|
||||
}
|
||||
} catch (error) {
|
||||
this.provideFeedback({
|
||||
variant: 'danger',
|
||||
title: 'There has been a problem with the operation',
|
||||
body: error.message
|
||||
}, true, skipFeedback);
|
||||
toReturn = Promise.reject(error);
|
||||
} finally {
|
||||
if (!skipRequestHooks) {
|
||||
this.afterRequest()
|
||||
}
|
||||
}
|
||||
return toReturn
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLFormElement} form - The form to be posted
|
||||
* @param {Object} [dataToMerge={}] - Additional data to be integrated or modified in the form
|
||||
|
|
|
@ -25,20 +25,20 @@ class UIFactory {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create and display a modal where the modal's content is fetched from the provided URL.
|
||||
* Create and display a modal where the modal's content is fetched from the provided URL. Link an AJAXApi to the submission button
|
||||
* @param {string} url - The URL from which the modal's content should be fetched
|
||||
* @param {ModalFactory~POSTSuccessCallback} POSTSuccessCallback - The callback that handles successful form submission
|
||||
* @param {ModalFactory~POSTFailCallback} POSTFailCallback - The callback that handles form submissions errors and validation errors.
|
||||
* @return {Promise<Object>} Promise object resolving to the ModalFactory object
|
||||
*/
|
||||
modalFromURL(url, POSTSuccessCallback, POSTFailCallback) {
|
||||
submissionModal(url, POSTSuccessCallback, POSTFailCallback) {
|
||||
return AJAXApi.quickFetchURL(url).then((modalHTML) => {
|
||||
const theModal = new ModalFactory({
|
||||
rawHtml: modalHTML,
|
||||
POSTSuccessCallback: POSTSuccessCallback !== undefined ? POSTSuccessCallback : () => {},
|
||||
POSTFailCallback: POSTFailCallback !== undefined ? POSTFailCallback : (errorMessage) => {},
|
||||
});
|
||||
theModal.makeModal(modalHTML)
|
||||
theModal.makeModal()
|
||||
theModal.show()
|
||||
theModal.$modal.data('modalObject', theModal)
|
||||
return theModal
|
||||
|
@ -46,45 +46,139 @@ class UIFactory {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates and displays a modal where the modal's content is fetched from the provided URL. Reloads the table after a successful operation and handles displayOnSuccess option
|
||||
* Creates and displays a modal where the modal's content is fetched from the provided URL. Reloads the single page view 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 {string} reloadUrl - The URL from which the data should be fetched after confirming
|
||||
* @param {string} tableId - The table ID which should be reloaded on success
|
||||
* @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
|
||||
*/
|
||||
openModalFromURL(url, reloadUrl=false, tableId=false) {
|
||||
return UI.modalFromURL(url, (data) => {
|
||||
let reloaded = false
|
||||
if (reloadUrl === false || tableId === false) { // Try to get information from the DOM
|
||||
let $elligibleTable = $('table.table')
|
||||
let currentModel = location.pathname.split('/')[1]
|
||||
if ($elligibleTable.length == 1 && currentModel.length > 0) {
|
||||
let $container = $elligibleTable.closest('div[id^="table-container-"]')
|
||||
if ($container.length == 1) {
|
||||
UI.reload(`/${currentModel}/index`, $container, $elligibleTable)
|
||||
reloaded = true
|
||||
} else {
|
||||
$container = $elligibleTable.closest('div[id^="single-view-table-container-"]')
|
||||
if ($container.length == 1) {
|
||||
UI.reload(location.pathname, $container, $elligibleTable)
|
||||
reloaded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
submissionModalForSinglePage(url, reloadUrl=false, $table=false) {
|
||||
let $statusNode, $reloadedElement
|
||||
if (reloadUrl === false) {
|
||||
reloadUrl = location.pathname
|
||||
}
|
||||
if ($table === false) { // Try to get information from the DOM
|
||||
const $elligibleTable = $('table[id^="single-view-table-"]')
|
||||
const $container = $elligibleTable.closest('div[id^="single-view-table-container-"]')
|
||||
$reloadedElement = $container
|
||||
$statusNode = $elligibleTable
|
||||
} else {
|
||||
if ($table instanceof jQuery) {
|
||||
$reloadedElement = $table
|
||||
$statusNode = $table.find('table[id^="single-view-table-"]')
|
||||
} else {
|
||||
UI.reload(reloadUrl, $(`#table-container-${tableId}`), $(`#table-container-${tableId} table.table`))
|
||||
reloaded = true
|
||||
$reloadedElement = $(`single-view-table-container-${$table}`)
|
||||
$statusNode = $(`single-view-table-${$table}`)
|
||||
}
|
||||
}
|
||||
if ($reloadedElement.length == 0) {
|
||||
UI.toast({
|
||||
variant: 'danger',
|
||||
title: 'Could not find element to be reloaded',
|
||||
body: 'The content of this page may have changed and has not been reflected. Reloading the page is advised.'
|
||||
})
|
||||
return
|
||||
}
|
||||
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
|
||||
*/
|
||||
submissionModalForIndex(url, reloadUrl=false, $table=false) {
|
||||
let $statusNode, $reloadedElement
|
||||
if (reloadUrl === false) {
|
||||
const currentModel = location.pathname.split('/')[1]
|
||||
if (currentModel.length > 0) {
|
||||
reloadUrl = `/${currentModel}/index`
|
||||
} else {
|
||||
UI.toast({
|
||||
variant: 'danger',
|
||||
title: 'Could not find URL for the reload',
|
||||
body: 'The content of this page may have changed and has not been reflected. Reloading the page is advised.'
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
if ($table === false) { // Try to get information from the DOM
|
||||
const $elligibleTable = $('table.table')
|
||||
const $container = $elligibleTable.closest('div[id^="table-container-"]')
|
||||
$reloadedElement = $container
|
||||
$statusNode = $elligibleTable
|
||||
} else {
|
||||
if ($table instanceof jQuery) {
|
||||
$reloadedElement = $table
|
||||
$statusNode = $table.find('table.table')
|
||||
} else {
|
||||
$reloadedElement = $(`#table-container-${$table}`)
|
||||
$statusNode = $(`#table-container-${$table} table.table`)
|
||||
}
|
||||
}
|
||||
if ($reloadedElement.length == 0) {
|
||||
UI.toast({
|
||||
variant: 'danger',
|
||||
title: 'Could not find element to be reloaded',
|
||||
body: 'The content of this page may have changed and has not been reflected. Reloading the page is advised.'
|
||||
})
|
||||
return
|
||||
}
|
||||
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
|
||||
* @param {string} url - The URL from which the modal's content should be fetched
|
||||
* @param {string} reloadUrl - The URL from which the data should be fetched after confirming
|
||||
* @param {(jQuery|string)} $reloadedElement - The element which should be reloaded on success
|
||||
* @param {(jQuery|string)} [$statusNode=null] - A reference to a HTML node on which the loading animation should be displayed. If not provided, $container will be used
|
||||
* @return {Promise<Object>} Promise object resolving to the ModalFactory object
|
||||
*/
|
||||
submissionReloaderModal(url, reloadUrl, $reloadedElement, $statusNode=null) {
|
||||
const successCallback = function (data) {
|
||||
UI.reload(reloadUrl, $reloadedElement, $statusNode)
|
||||
if (data.additionalData !== undefined && data.additionalData.displayOnSuccess !== undefined) {
|
||||
UI.modal({
|
||||
rawHtml: data.additionalData.displayOnSuccess
|
||||
})
|
||||
} else {
|
||||
if (!reloaded) {
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return UI.submissionModal(url, successCallback)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -255,9 +349,9 @@ class Toaster {
|
|||
if (options.body !== false || options.bodyHtml !== false) {
|
||||
var $toastBody
|
||||
if (options.bodyHtml !== false) {
|
||||
$toastBody = $('<div class="toast-body"/>').html(options.mutedHtml)
|
||||
$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)
|
||||
}
|
||||
|
@ -273,8 +367,15 @@ class ModalFactory {
|
|||
*/
|
||||
constructor(options) {
|
||||
this.options = Object.assign({}, ModalFactory.defaultOptions, options)
|
||||
if (this.options.rawHtml && options.POSTSuccessCallback !== undefined) {
|
||||
this.attachSubmitButtonListener = true
|
||||
if (options.POSTSuccessCallback !== undefined) {
|
||||
if (this.options.rawHtml) {
|
||||
this.attachSubmitButtonListener = true
|
||||
} else {
|
||||
UI.toast({
|
||||
variant: 'danger',
|
||||
bodyHtml: '<b>POSTSuccessCallback</b> can only be used in conjuction with the <i>rawHtml</i> option. Instead, use the promise instead returned by the API call in <b>APIConfirm</b>.'
|
||||
})
|
||||
}
|
||||
}
|
||||
if (options.type === undefined && options.cancel !== undefined) {
|
||||
this.options.type = 'confirm'
|
||||
|
@ -349,14 +450,14 @@ class ModalFactory {
|
|||
* @property {string} confirmText - The text to be placed in the confirm button
|
||||
* @property {string} cancelText - The text to be placed in the cancel button
|
||||
* @property {boolean} closeManually - If true, the modal will be closed automatically whenever a footer's button is pressed
|
||||
* @property {boolean} closeOnSuccess - If true, the modal will be closed if the $FILL_ME operation is successful
|
||||
* @property {boolean} closeOnSuccess - If true, the modal will be closed if the operation is successful
|
||||
* @property {ModalFactory~confirm} confirm - The callback that should be called if the user confirm the modal
|
||||
* @property {ModalFactory~cancel} cancel - The callback that should be called if the user cancel the modal
|
||||
* @property {ModalFactory~APIConfirm} APIConfirm - The callback that should be called if the user confirm the modal. Behaves like the confirm option but provides an AJAXApi object that can be used to issue requests
|
||||
* @property {ModalFactory~APIError} APIError - The callback called if the APIConfirm callback fails.
|
||||
* @property {ModalFactory~shownCallback} shownCallback - The callback that should be called whenever the modal is shown
|
||||
* @property {ModalFactory~hiddenCallback} hiddenCallback - The callback that should be called whenever the modal is hiddenAPIConfirm
|
||||
* @property {ModalFactory~POSTSuccessCallback} POSTSuccessCallback - The callback that should be called if the POST operation has been a success
|
||||
* @property {ModalFactory~POSTSuccessCallback} POSTSuccessCallback - The callback that should be called if the POST operation has been a success. Works in confunction with the `rawHtml`
|
||||
* @property {ModalFactory~POSTFailCallback} POSTFailCallback - The callback that should be called if the POST operation has been a failure (Either the request failed or the form validation did not pass)
|
||||
*/
|
||||
static defaultOptions = {
|
||||
|
@ -587,8 +688,11 @@ class ModalFactory {
|
|||
}
|
||||
|
||||
/** Attach the submission click listener for modals that have been generated by raw HTML */
|
||||
findSubmitButtonAndAddListener(clearOnclick=true) {
|
||||
const $submitButton = this.$modal.find('.modal-footer #submitButton')
|
||||
findSubmitButtonAndAddListener() {
|
||||
let $submitButton = this.$modal.find('.modal-footer #submitButton')
|
||||
if (!$submitButton[0]) {
|
||||
$submitButton = this.$modal.find('.modal-footer .modal-confirm-button')
|
||||
}
|
||||
if ($submitButton[0]) {
|
||||
const formID = $submitButton.data('form-id')
|
||||
let $form
|
||||
|
@ -597,26 +701,46 @@ class ModalFactory {
|
|||
} else {
|
||||
$form = this.$modal.find('form')
|
||||
}
|
||||
if (clearOnclick) {
|
||||
if ($submitButton.data('confirmfunction') !== undefined && $submitButton.data('confirmfunction') !== '') {
|
||||
const clickHandler = window[$submitButton.data('confirmfunction')]
|
||||
this.options.APIConfirm = (tmpApi) => {
|
||||
let clickResult = clickHandler(this, tmpApi)
|
||||
if (clickResult !== undefined) {
|
||||
return clickResult
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
this.options.POSTSuccessCallback(data)
|
||||
} else { // Validation error
|
||||
this.injectFormValidationFeedback(form, data.errors)
|
||||
return Promise.reject('Validation error');
|
||||
}
|
||||
})
|
||||
.catch((errorMessage) => {
|
||||
this.options.POSTFailCallback(errorMessage)
|
||||
return Promise.reject(errorMessage);
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$submitButton[0].removeAttribute('onclick')
|
||||
}
|
||||
|
||||
this.options.APIConfirm = (tmpApi) => {
|
||||
return tmpApi.postForm($form[0])
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
this.options.POSTSuccessCallback(data)
|
||||
} else { // Validation error
|
||||
this.injectFormValidationFeedback(form, data.errors)
|
||||
return Promise.reject('Validation error');
|
||||
}
|
||||
})
|
||||
.catch((errorMessage) => {
|
||||
this.options.POSTFailCallback(errorMessage)
|
||||
return Promise.reject(errorMessage);
|
||||
})
|
||||
this.options.APIConfirm = (tmpApi) => {
|
||||
return tmpApi.postForm($form[0])
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
this.options.POSTSuccessCallback(data)
|
||||
} else { // Validation error
|
||||
this.injectFormValidationFeedback(form, data.errors)
|
||||
return Promise.reject('Validation error');
|
||||
}
|
||||
})
|
||||
.catch((errorMessage) => {
|
||||
this.options.POSTFailCallback(errorMessage)
|
||||
return Promise.reject(errorMessage);
|
||||
})
|
||||
}
|
||||
}
|
||||
$submitButton.click(this.getConfirmationHandlerFunction())
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue