new: [outbox] Added outbox and linked it with failed outgoing messages
parent
063575d8b3
commit
1da74b283a
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Migrations\AbstractMigration;
|
||||
use Phinx\Db\Adapter\MysqlAdapter;
|
||||
|
||||
class OutboxSystem extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* Change Method.
|
||||
*
|
||||
* More information on this method is available here:
|
||||
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||
* @return void
|
||||
*/
|
||||
|
||||
public $autoId = false; // turn off automatic `id` column create. We want it to be `int(10) unsigned`
|
||||
|
||||
|
||||
public function change()
|
||||
{
|
||||
$table = $this->table('outbox', [
|
||||
'signed' => false,
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
]);
|
||||
$table
|
||||
->addColumn('id', 'integer', [
|
||||
'autoIncrement' => true,
|
||||
'limit' => 10,
|
||||
'signed' => false,
|
||||
])
|
||||
->addPrimaryKey('id')
|
||||
->addColumn('uuid', 'uuid', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
])
|
||||
->addColumn('scope', 'string', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
'limit' => 191,
|
||||
'comment' => 'The to model linked with the message',
|
||||
])
|
||||
->addColumn('action', 'string', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
'limit' => 191,
|
||||
'comment' => 'The action linked with the message',
|
||||
])
|
||||
->addColumn('title', 'string', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
'limit' => 191,
|
||||
])
|
||||
->addColumn('user_id', 'integer', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
'signed' => false,
|
||||
'length' => 10,
|
||||
])
|
||||
->addColumn('comment', 'text', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
])
|
||||
->addColumn('description', 'text', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
])
|
||||
->addColumn('data', 'text', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
'limit' => MysqlAdapter::TEXT_LONG
|
||||
])
|
||||
->addColumn('created', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
|
||||
$table->addForeignKey('user_id', 'users', 'id', ['delete'=> 'CASCADE', 'update'=> 'CASCADE']);
|
||||
|
||||
$table->addIndex(['uuid'], ['unique' => true])
|
||||
->addIndex('scope')
|
||||
->addIndex('action')
|
||||
->addIndex('title')
|
||||
->addIndex('created')
|
||||
->addIndex('user_id');
|
||||
|
||||
$table->create();
|
||||
}
|
||||
}
|
||||
|
|
@ -193,7 +193,7 @@ class GenericInboxProcessor
|
|||
$requestData['action'] = $this->action;
|
||||
$requestData['description'] = $this->description;
|
||||
$request = $this->generateRequest($requestData);
|
||||
$savedRequest = $this->Inbox->save($request);
|
||||
$savedRequest = $this->Inbox->createEntry($request);
|
||||
return $this->genActionResult(
|
||||
$savedRequest,
|
||||
$savedRequest !== false,
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'OutboxProcessors' . DS . 'GenericOutboxProcessor.php');
|
||||
|
||||
class BroodsOutboxProcessor extends GenericOutboxProcessor
|
||||
{
|
||||
protected $scope = 'Broods';
|
||||
protected $action = 'not-specified'; //overriden when extending
|
||||
protected $description = ''; // overriden when extending
|
||||
protected $registeredActions = [
|
||||
'ResendFailedMessage',
|
||||
];
|
||||
|
||||
public function __construct($loadFromAction=false) {
|
||||
parent::__construct($loadFromAction);
|
||||
}
|
||||
|
||||
public function create($requestData)
|
||||
{
|
||||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
protected function getIssuerBrood($broodId)
|
||||
{
|
||||
$brood = $this->Broods->find()
|
||||
->where(['id' => $broodId])
|
||||
->first();
|
||||
return $brood;
|
||||
}
|
||||
|
||||
protected function getLocalTool($toolId)
|
||||
{
|
||||
$tool = $this->LocalTools->find()
|
||||
->where(['id' => $toolId])
|
||||
->first();
|
||||
return $tool;
|
||||
}
|
||||
}
|
||||
|
||||
class ResendFailedMessageProcessor extends BroodsOutboxProcessor implements GenericProcessorActionI {
|
||||
public $action = 'ResendFailedMessage';
|
||||
protected $description;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->description = __('Handle re-sending messages that failed to be received from other cerebrate instances.');
|
||||
$this->Broods = TableRegistry::getTableLocator()->get('Broods');
|
||||
$this->LocalTools = \Cake\ORM\TableRegistry::getTableLocator()->get('LocalTools');
|
||||
}
|
||||
|
||||
protected function addValidatorRules($validator)
|
||||
{
|
||||
return $validator;
|
||||
}
|
||||
|
||||
public function getViewVariables($request)
|
||||
{
|
||||
$request->brood = $this->getIssuerBrood($request['data']['brood_id']);
|
||||
$request->individual = $request->user->individual;
|
||||
$request->localTool = $this->getLocalTool($request['data']['local_tool_id']);
|
||||
$request->remoteTool = $request['data']['remote_tool'];
|
||||
return [
|
||||
'request' => $request,
|
||||
];
|
||||
}
|
||||
|
||||
public function create($requestData) {
|
||||
$this->validateRequestData($requestData);
|
||||
$brood = $requestData['brood'];
|
||||
$requestData['title'] = __('Issue while sending message to Cerebrate instance `{0}` using `{1}`', $brood->name, sprintf('%s.%s', $requestData['model'], $requestData['action']));
|
||||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
public function process($id, $requestData, $outboxRequest)
|
||||
{
|
||||
if (!empty($requestData['is_delete'])) { // -> declined
|
||||
$success = true;
|
||||
$messageSucess = __('Message successfully deleted');
|
||||
$messageFail = '';
|
||||
} else {
|
||||
$brood = $this->getIssuerBrood((int) $outboxRequest->data['brood_id']);
|
||||
$url = $outboxRequest->data['url'];
|
||||
$dataSent = $outboxRequest->data['sent'];
|
||||
$dataSent['connectorName'] = 'MispConnector';
|
||||
$response = $this->Broods->sendRequest($brood, $url, true, $dataSent);
|
||||
$jsonReply = $response->getJson();
|
||||
$success = !empty($jsonReply['success']);
|
||||
$messageSuccess = __('Message successfully sent to `{0}`', $brood->name);
|
||||
$messageFail = __('Could not send message to `{0}`.', $brood->name);
|
||||
}
|
||||
if ($success) {
|
||||
$this->discard($id, $requestData);
|
||||
}
|
||||
return $this->genActionResult(
|
||||
[],
|
||||
$success,
|
||||
$success ? $messageSuccess : $messageFail,
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
public function discard($id, $requestData)
|
||||
{
|
||||
return parent::discard($id, $requestData);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
use Cake\Filesystem\File;
|
||||
use Cake\Utility\Inflector;
|
||||
use Cake\Validation\Validator;
|
||||
use Cake\View\ViewBuilder;
|
||||
use Cake\Routing\Router;
|
||||
|
||||
interface GenericProcessorActionI
|
||||
{
|
||||
public function create($requestData);
|
||||
public function process($requestID, $serverRequest, $outboxRequest);
|
||||
public function discard($requestID ,$requestData);
|
||||
}
|
||||
|
||||
class GenericOutboxProcessor
|
||||
{
|
||||
protected $Outbox;
|
||||
protected $registeredActions = [];
|
||||
protected $validator;
|
||||
protected $processingTemplate = '/genericTemplates/confirm';
|
||||
protected $processingTemplatesDirectory = ROOT . '/libraries/default/OutboxProcessors/templates';
|
||||
|
||||
public function __construct($registerActions=false) {
|
||||
$this->Outbox = TableRegistry::getTableLocator()->get('Outbox');
|
||||
if ($registerActions) {
|
||||
$this->registerActionInProcessor();
|
||||
}
|
||||
$this->assignProcessingTemplate();
|
||||
}
|
||||
|
||||
private function assignProcessingTemplate()
|
||||
{
|
||||
$processingTemplatePath = $this->getProcessingTemplatePath();
|
||||
$file = new File($this->processingTemplatesDirectory . DS . $processingTemplatePath);
|
||||
if ($file->exists()) {
|
||||
$this->processingTemplate = str_replace('.php', '', $processingTemplatePath);
|
||||
}
|
||||
$file->close();
|
||||
}
|
||||
|
||||
protected function updateProcessingTemplate($request)
|
||||
{
|
||||
}
|
||||
|
||||
public function getRegisteredActions()
|
||||
{
|
||||
return $this->registeredActions;
|
||||
}
|
||||
public function getScope()
|
||||
{
|
||||
return $this->scope;
|
||||
}
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description ?? '';
|
||||
}
|
||||
|
||||
protected function getProcessingTemplatePath()
|
||||
{
|
||||
return sprintf('%s/%s.php',
|
||||
$this->scope,
|
||||
$this->action
|
||||
);
|
||||
}
|
||||
|
||||
public function getProcessingTemplate()
|
||||
{
|
||||
return $this->processingTemplate;
|
||||
}
|
||||
|
||||
public function render($request=[], Cake\Http\ServerRequest $serverRequest)
|
||||
{
|
||||
$viewVariables = $this->getViewVariables($request);
|
||||
$this->updateProcessingTemplate($request);
|
||||
$processingTemplate = $this->getProcessingTemplate();
|
||||
$builder = new ViewBuilder();
|
||||
$builder->disableAutoLayout()
|
||||
->setClassName('Monad')
|
||||
->setTemplate($processingTemplate);
|
||||
$view = $builder->build($viewVariables);
|
||||
$view->setRequest($serverRequest);
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
protected function generateRequest($requestData)
|
||||
{
|
||||
$request = $this->Outbox->newEmptyEntity();
|
||||
$request = $this->Outbox->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' => 'outbox', '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('OutboxProcessor', "{$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('OutboxProcessor', "{$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)
|
||||
{
|
||||
$user_id = Router::getRequest()->getSession()->read('Auth.id');
|
||||
$requestData['scope'] = $this->scope;
|
||||
$requestData['action'] = $this->action;
|
||||
$requestData['description'] = $this->description;
|
||||
$requestData['user_id'] = $user_id;
|
||||
$request = $this->generateRequest($requestData);
|
||||
$savedRequest = $this->Outbox->createEntry($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->Outbox->get($id);
|
||||
$this->Outbox->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 . 'InboxProcessors' . DS . 'GenericOutboxProcessor.php');
|
||||
|
||||
class SCOPE_OutboxProcessor extends GenericOutboxProcessor
|
||||
{
|
||||
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 ProposalOutboxProcessor 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, $inboxRequest)
|
||||
{
|
||||
$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,138 @@
|
|||
<?php
|
||||
$footerButtons = [
|
||||
[
|
||||
'clickFunction' => 'cancel',
|
||||
'variant' => 'secondary',
|
||||
'text' => __('Cancel'),
|
||||
],
|
||||
[
|
||||
'clickFunction' => 'deleteEntry',
|
||||
'variant' => 'danger',
|
||||
'text' => __('Delete Message'),
|
||||
],
|
||||
[
|
||||
'clickFunction' => 'resendMessage',
|
||||
'text' => __('Re-Send Message'),
|
||||
]
|
||||
];
|
||||
|
||||
$tools = sprintf(
|
||||
'<div class="mx-auto mb-3 mw-75 d-flex align-items-center">
|
||||
<span class="flex-grow-1 text-right" style="font-size: large;">%s</span>
|
||||
<span class="mx-3">%s</span>
|
||||
<span class="flex-grow-1 text-left" style="font-size: large;">%s</span>
|
||||
</div>',
|
||||
sprintf('<span class="mr-2 d-inline-flex flex-column"><a href="%s" target="_blank" title="%s">%s</a><i style="font-size: medium;" class="text-center">%s</i></span>',
|
||||
sprintf('/localTools/view/%s', h($request['localTool']->id)),
|
||||
h($request['localTool']->description),
|
||||
h($request['localTool']->name),
|
||||
__('(local tool)')
|
||||
),
|
||||
sprintf('<i class="%s fa-lg"></i>', $this->FontAwesome->getClass('long-arrow-alt-right')),
|
||||
sprintf('<span class="ml-2 d-inline-flex flex-column"><a href="%s" target="_blank" title="%s">%s</a><i style="font-size: medium;" class="text-center">%s</i></span>',
|
||||
sprintf('/localTools/broodTools/%s', h($request['data']['remote_tool']['id'])),
|
||||
h($request['data']['remote_tool']['description']),
|
||||
h($request['data']['remote_tool']['name']),
|
||||
__('(remote tool)')
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
$table = $this->Bootstrap->table(['small' => true, 'bordered' => false, 'striped' => false, 'hover' => false], [
|
||||
'fields' => [
|
||||
['key' => 'created', 'label' => __('Date'), 'formatter' => function($value, $row) {
|
||||
return $value->i18nFormat('yyyy-MM-dd HH:mm:ss');
|
||||
}],
|
||||
['key' => 'brood', 'label' => __('Brood'), 'formatter' => function($brood, $row) {
|
||||
return sprintf('<a href="%s" target="_blank">%s</a>',
|
||||
$this->Url->build(['controller' => 'broods', 'action' => 'view', $brood['id']]),
|
||||
h($brood['name'])
|
||||
);
|
||||
}],
|
||||
['key' => 'individual', 'label' => __('Individual'), 'formatter' => function($individual, $row) {
|
||||
return sprintf('<a href="%s" target="_blank">%s</a>',
|
||||
$this->Url->build(['controller' => 'users', 'action' => 'view', $individual['id']]),
|
||||
h($individual['email'])
|
||||
);
|
||||
}],
|
||||
['key' => 'individual.alignments', 'label' => __('Alignment'), 'formatter' => function($alignments, $row) {
|
||||
$html = '';
|
||||
foreach ($alignments as $alignment) {
|
||||
$html .= sprintf('<div class="text-nowrap"><b>%s</b> @ <a href="%s" target="_blank">%s</a></div>',
|
||||
h($alignment['type']),
|
||||
$this->Url->build(['controller' => 'users', 'action' => 'view', $alignment['organisation']['id']]),
|
||||
h($alignment['organisation']['name'])
|
||||
);
|
||||
}
|
||||
return $html;
|
||||
}],
|
||||
],
|
||||
'items' => [$request->toArray()],
|
||||
]);
|
||||
|
||||
$requestData = $this->Bootstrap->collapse([
|
||||
'title' => __('Message data'),
|
||||
'open' => true,
|
||||
],
|
||||
sprintf('<pre class="p-2 rounded mb-0" style="background: #eeeeee55;"><code>%s</code></pre>', json_encode($request['data']['sent'], JSON_PRETTY_PRINT))
|
||||
);
|
||||
|
||||
$rows = sprintf('<tr><td class="font-weight-bold">%s</td><td>%s</td></tr>', __('URL'), h($request['data']['url']));
|
||||
$rows .= sprintf('<tr><td class="font-weight-bold">%s</td><td>%s</td></tr>', __('Reason'), h($request['data']['reason']['message']) ?? '');
|
||||
$rows .= sprintf('<tr><td class="font-weight-bold">%s</td><td>%s</td></tr>', __('Errors'), h(json_encode($request['data']['reason']['errors'])) ?? '');
|
||||
$table2 = sprintf('<table class="table table-sm table-striped"><tbody>%s</tbody></table>', $rows);
|
||||
|
||||
$form = $this->element('genericElements/Form/genericForm', [
|
||||
'entity' => null,
|
||||
'ajax' => false,
|
||||
'raw' => true,
|
||||
'data' => [
|
||||
'model' => 'Inbox',
|
||||
'fields' => [
|
||||
[
|
||||
'field' => 'is_delete',
|
||||
'type' => 'checkbox',
|
||||
'default' => false
|
||||
]
|
||||
],
|
||||
'submit' => [
|
||||
'action' => $this->request->getParam('action')
|
||||
]
|
||||
]
|
||||
]);
|
||||
$form = sprintf('<div class="d-none">%s</div>', $form);
|
||||
|
||||
$bodyHtml = sprintf('<div><div>%s</div><div>%s</div><div>%s</div>%s</div>%s',
|
||||
$tools,
|
||||
$table,
|
||||
$table2,
|
||||
$requestData,
|
||||
$form
|
||||
);
|
||||
|
||||
echo $this->Bootstrap->modal([
|
||||
'title' => $request['title'],
|
||||
'size' => 'xl',
|
||||
'type' => 'custom',
|
||||
'bodyHtml' => sprintf('<div class="p-3">%s</div>',
|
||||
$bodyHtml
|
||||
),
|
||||
'footerButtons' => $footerButtons
|
||||
]);
|
||||
|
||||
?>
|
||||
|
||||
<script>
|
||||
function resendMessage(modalObject, tmpApi) {
|
||||
const $form = modalObject.$modal.find('form')
|
||||
return tmpApi.postForm($form[0])
|
||||
}
|
||||
function deleteEntry(modalObject, tmpApi) {
|
||||
const $form = modalObject.$modal.find('form')
|
||||
const $discardField = $form.find('input#is_delete-field')
|
||||
$discardField.prop('checked', true)
|
||||
return tmpApi.postForm($form[0])
|
||||
}
|
||||
function cancel(modalObject, tmpApi) {
|
||||
}
|
||||
</script>
|
|
@ -650,22 +650,55 @@ class ACLComponent extends Component
|
|||
'url' => '/inbox/index',
|
||||
'label' => __('Inbox')
|
||||
],
|
||||
'outbox' => [
|
||||
'url' => '/outbox/index',
|
||||
'label' => __('Outbox')
|
||||
],
|
||||
'view' => [
|
||||
'url' => '/inbox/view/{{id}}',
|
||||
'label' => __('View Meta Template'),
|
||||
'label' => __('View Message'),
|
||||
'actions' => ['delete', 'edit', 'view'],
|
||||
'skipTopMenu' => 1
|
||||
],
|
||||
'delete' => [
|
||||
'url' => '/inbox/delete/{{id}}',
|
||||
'label' => __('Delete Meta Template'),
|
||||
'label' => __('Delete Message'),
|
||||
'actions' => ['delete', 'edit', 'view'],
|
||||
'skipTopMenu' => 1,
|
||||
'popup' => 1
|
||||
],
|
||||
'listProcessors' => [
|
||||
'url' => '/inbox/listProcessors',
|
||||
'label' => __('List Request Processors'),
|
||||
'label' => __('List Inbox Processors'),
|
||||
'skipTopMenu' => 1
|
||||
]
|
||||
]
|
||||
],
|
||||
'Outbox' => [
|
||||
'label' => __('Outbox'),
|
||||
'url' => '/outbox/index',
|
||||
'children' => [
|
||||
'index' => [
|
||||
'url' => '/outbox/index',
|
||||
'label' => __('Outbox'),
|
||||
'skipTopMenu' => 1
|
||||
],
|
||||
'view' => [
|
||||
'url' => '/outbox/view/{{id}}',
|
||||
'label' => __('View Message'),
|
||||
'actions' => ['delete', 'edit', 'view'],
|
||||
'skipTopMenu' => 1
|
||||
],
|
||||
'delete' => [
|
||||
'url' => '/outbox/delete/{{id}}',
|
||||
'label' => __('Delete Message'),
|
||||
'actions' => ['delete', 'edit', 'view'],
|
||||
'skipTopMenu' => 1,
|
||||
'popup' => 1
|
||||
],
|
||||
'listProcessors' => [
|
||||
'url' => '/outbox/listProcessors',
|
||||
'label' => __('List Outbox Processors'),
|
||||
'skipTopMenu' => 1
|
||||
]
|
||||
]
|
||||
|
|
|
@ -119,7 +119,7 @@ class InboxController extends AppController
|
|||
$this->set('data', $data);
|
||||
}
|
||||
|
||||
public function createInboxEntry($scope, $action)
|
||||
public function createEntry($scope, $action)
|
||||
{
|
||||
if (!$this->request->is('post')) {
|
||||
throw new MethodNotAllowedException(__('Only POST method is accepted'));
|
||||
|
@ -137,7 +137,7 @@ class InboxController extends AppController
|
|||
$errors = $this->Inbox->checkUserBelongsToBroodOwnerOrg($this->ACL->getUser(), $entryData);
|
||||
if (!empty($errors)) {
|
||||
$message = __('Could not create inbox message');
|
||||
return $this->RestResponse->ajaxFailResponse(Inflector::singularize($this->Inbox->getAlias()), 'createInboxEntry', [], $message, $errors);
|
||||
return $this->RestResponse->ajaxFailResponse(Inflector::singularize($this->Inbox->getAlias()), 'createEntry', [], $message, $errors);
|
||||
}
|
||||
} else {
|
||||
$processor = $this->inboxProcessors->getProcessor($scope, $action);
|
||||
|
|
|
@ -237,7 +237,7 @@ class LocalToolsController extends AppController
|
|||
$response = $this->RestResponse->ajaxSuccessResponse('LocalTool', 'connectionRequest', [], $inboxResult['message']);
|
||||
} else {
|
||||
$this->Flash->success($inboxResult['message']);
|
||||
$this->redirect(['action' => 'broodTools', $cerebrate_id]);
|
||||
$response = $this->redirect(['action' => 'broodTools', $cerebrate_id]);
|
||||
}
|
||||
} else {
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
|
@ -246,7 +246,7 @@ class LocalToolsController extends AppController
|
|||
$response = $this->RestResponse->ajaxFailResponse('LocalTool', 'connectionRequest', [], $inboxResult['message'], $inboxResult['errors']);
|
||||
} else {
|
||||
$this->Flash->error($inboxResult['message']);
|
||||
$this->redirect($this->referer());
|
||||
$response = $this->redirect($this->referer());
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Controller\AppController;
|
||||
use Cake\Database\Expression\QueryExpression;
|
||||
use Cake\Event\EventInterface;
|
||||
use Cake\ORM\TableRegistry;
|
||||
use Cake\Utility\Inflector;
|
||||
use Cake\Utility\Hash;
|
||||
use Cake\Utility\Text;
|
||||
use Cake\Http\Exception\NotFoundException;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use Cake\Http\Exception\ForbiddenException;
|
||||
|
||||
|
||||
class OutboxController extends AppController
|
||||
{
|
||||
public $filters = ['scope', 'action', 'title', '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',
|
||||
]
|
||||
],
|
||||
'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)
|
||||
{
|
||||
$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->Outbox->get($id, ['contain' => ['Users' => ['Individuals' => ['Alignments' => 'Organisations']]]]);
|
||||
$scope = $request->scope;
|
||||
$action = $request->action;
|
||||
$this->outboxProcessors = TableRegistry::getTableLocator()->get('OutboxProcessors');
|
||||
$processor = $this->outboxProcessors->getProcessor($scope, $action);
|
||||
if ($this->request->is('post')) {
|
||||
$processResult = $processor->process($id, $this->request->getData(), $request);
|
||||
return $processor->genHTTPReply($this, $processResult);
|
||||
} else {
|
||||
$renderedView = $processor->render($request, $this->request);
|
||||
return $this->response->withStringBody($renderedView);
|
||||
}
|
||||
}
|
||||
|
||||
public function listProcessors()
|
||||
{
|
||||
$this->OutboxProcessors = TableRegistry::getTableLocator()->get('OutboxProcessors');
|
||||
$processors = $this->OutboxProcessors->listProcessors();
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
return $this->RestResponse->viewData($processors, 'json');
|
||||
}
|
||||
$data = [];
|
||||
foreach ($processors as $scope => $scopedProcessors) {
|
||||
foreach ($scopedProcessors as $processor) {
|
||||
$data[] = [
|
||||
'enabled' => $processor->enabled,
|
||||
'scope' => $scope,
|
||||
'action' => $processor->action,
|
||||
'description' => isset($processor->getDescription) ? $processor->getDescription() : null,
|
||||
'notice' => $processor->notice ?? null,
|
||||
'error' => $processor->error ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
$this->set('data', $data);
|
||||
}
|
||||
|
||||
public function createEntry($scope, $action)
|
||||
{
|
||||
if (!$this->request->is('post')) {
|
||||
throw new MethodNotAllowedException(__('Only POST method is accepted'));
|
||||
}
|
||||
$entryData = [
|
||||
'user_id' => $this->ACL->getUser()['id'],
|
||||
];
|
||||
$entryData['data'] = $this->request->getData() ?? [];
|
||||
$this->OutboxProcessors = TableRegistry::getTableLocator()->get('OutboxProcessors');
|
||||
$processor = $this->OutboxProcessors->getProcessor($scope, $action);
|
||||
$creationResult = $this->OutboxProcessors->createOutboxEntry($processor, $entryData);
|
||||
return $processor->genHTTPReply($this, $creationResult);
|
||||
}
|
||||
}
|
|
@ -212,11 +212,7 @@ class BroodsTable extends AppTable
|
|||
} else {
|
||||
$response = $http->get($brood->url, $data, $config);
|
||||
}
|
||||
if ($response->isOk()) {
|
||||
return $response;
|
||||
} else {
|
||||
throw new NotFoundException(__('Could not send to the requested resource.'));
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function injectRequiredData($params, $data): Array
|
||||
|
@ -230,30 +226,30 @@ class BroodsTable extends AppTable
|
|||
|
||||
public function sendLocalToolConnectionRequest($params, $data): array
|
||||
{
|
||||
$url = '/inbox/createInboxEntry/LocalTool/IncomingConnectionRequest';
|
||||
$url = '/inbox/createEntry/LocalTool/IncomingConnectionRequest';
|
||||
$data = $this->injectRequiredData($params, $data);
|
||||
$response = $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
|
||||
try {
|
||||
$response = $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
|
||||
$jsonReply = $response->getJson();
|
||||
if (empty($jsonReply['success'])) {
|
||||
$this->handleMessageNotCreated($response);
|
||||
$jsonReply = $this->handleMessageNotCreated($params['remote_cerebrate'], $url, $data, 'LocalTool', 'IncomingConnectionRequest', $response, $params);
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
$jsonReply = $this->handleSendingFailed($response);
|
||||
$jsonReply = $this->handleSendingFailed($params['remote_cerebrate'], $url, $data, 'LocalTool', 'IncomingConnectionRequest', $e, $params);
|
||||
}
|
||||
return $jsonReply;
|
||||
}
|
||||
|
||||
public function sendLocalToolAcceptedRequest($params, $data): Response
|
||||
{
|
||||
$url = '/inbox/createInboxEntry/LocalTool/AcceptedRequest';
|
||||
$url = '/inbox/createEntry/LocalTool/AcceptedRequest';
|
||||
$data = $this->injectRequiredData($params, $data);
|
||||
return $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
|
||||
}
|
||||
|
||||
public function sendLocalToolDeclinedRequest($params, $data): Response
|
||||
{
|
||||
$url = '/inbox/createInboxEntry/LocalTool/DeclinedRequest';
|
||||
$url = '/inbox/createEntry/LocalTool/DeclinedRequest';
|
||||
$data = $this->injectRequiredData($params, $data);
|
||||
return $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
|
||||
}
|
||||
|
@ -264,10 +260,19 @@ class BroodsTable extends AppTable
|
|||
* @param Object $response
|
||||
* @return array
|
||||
*/
|
||||
private function handleSendingFailed(Object $response): array
|
||||
private function handleSendingFailed($brood, $url, $data, $model, $action, $e, $params): array
|
||||
{
|
||||
// debug('sending failed. Modify state and add entry in outbox');
|
||||
throw new NotFoundException(__('sending failed. Modify state and add entry in outbox'));
|
||||
$reason = [
|
||||
'message' => __('Failed to send message to remote cerebrate. It has been placed in the outbox.'),
|
||||
'errors' => [$e->getMessage()],
|
||||
];
|
||||
$this->saveErrorInOutbox($brood, $url, $data, $reasonMessage, $params);
|
||||
$creationResult = [
|
||||
'success' => false,
|
||||
'message' => $reason['message'],
|
||||
'errors' => $reason['errors'],
|
||||
];
|
||||
return $creationResult;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -276,8 +281,43 @@ class BroodsTable extends AppTable
|
|||
* @param Object $response
|
||||
* @return array
|
||||
*/
|
||||
private function handleMessageNotCreated(Object $response): array
|
||||
private function handleMessageNotCreated($brood, $url, $data, $model, $action, $response, $params): array
|
||||
{
|
||||
// debug('Saving message failed. Modify state and add entry in outbox');
|
||||
$responseErrors = $response->getStringBody();
|
||||
if (!is_null($response->getJson())) {
|
||||
$responseErrors = $response->getJson()['errors'] ?? $response->getJson()['message'];
|
||||
}
|
||||
$reason = [
|
||||
'message' => __('Message rejected by the remote cerebrate. It has been placed in the outbox.'),
|
||||
'errors' => [$responseErrors],
|
||||
];
|
||||
$this->saveErrorInOutbox($brood, $url, $data, $reason, $model, $action, $params);
|
||||
$creationResult = [
|
||||
'success' => false,
|
||||
'message' => $reason['message'],
|
||||
'errors' => $reason['errors'],
|
||||
];
|
||||
return $creationResult;
|
||||
}
|
||||
|
||||
private function saveErrorInOutbox($brood, $url, $data, $reason, $model, $action, $params): array
|
||||
{
|
||||
$this->OutboxProcessors = TableRegistry::getTableLocator()->get('OutboxProcessors');
|
||||
$processor = $this->OutboxProcessors->getProcessor('Broods', 'ResendFailedMessage');
|
||||
$entryData = [
|
||||
'data' => [
|
||||
'sent' => $data,
|
||||
'url' => $url,
|
||||
'brood_id' => $brood->id,
|
||||
'reason' => $reason,
|
||||
'local_tool_id' => $params['connection']['id'],
|
||||
'remote_tool' => $params['remote_tool'],
|
||||
],
|
||||
'brood' => $brood,
|
||||
'model' => $model,
|
||||
'action' => $action,
|
||||
];
|
||||
$creationResult = $processor->create($entryData);
|
||||
return $creationResult;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,4 +86,10 @@ class InboxTable extends AppTable
|
|||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function createEntry($entryData)
|
||||
{
|
||||
$savedEntry = $this->save($entryData);
|
||||
return $savedEntry;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
namespace App\Model\Table;
|
||||
|
||||
use App\Model\Table\AppTable;
|
||||
use Cake\Filesystem\Folder;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use Cake\Core\Exception\Exception;
|
||||
|
||||
class MissingOutboxProcessorException extends Exception
|
||||
{
|
||||
protected $_defaultCode = 404;
|
||||
}
|
||||
|
||||
class OutboxProcessorsTable extends AppTable
|
||||
{
|
||||
private $processorsDirectory = ROOT . '/libraries/default/OutboxProcessors';
|
||||
private $outboxProcessors;
|
||||
private $enabledProcessors = [ // to be defined in config
|
||||
'Brood' => [
|
||||
'ResendFailedMessageProcessor' => true,
|
||||
],
|
||||
];
|
||||
|
||||
public function initialize(array $config): void
|
||||
{
|
||||
parent::initialize($config);
|
||||
$this->loadProcessors();
|
||||
}
|
||||
|
||||
public function getProcessor($scope, $action=null)
|
||||
{
|
||||
if (isset($this->outboxProcessors[$scope])) {
|
||||
if (is_null($action)) {
|
||||
return $this->outboxProcessors[$scope];
|
||||
} else if (!empty($this->outboxProcessors[$scope]->{$action})) {
|
||||
return $this->outboxProcessors[$scope]->{$action};
|
||||
} else {
|
||||
throw new \Exception(__('Processor {0}.{1} not found', $scope, $action));
|
||||
}
|
||||
}
|
||||
throw new MissingOutboxProcessorException(__('Processor not found'));
|
||||
}
|
||||
|
||||
public function listProcessors($scope=null)
|
||||
{
|
||||
if (is_null($scope)) {
|
||||
return $this->outboxProcessors;
|
||||
} else {
|
||||
if (isset($this->outboxProcessors[$scope])) {
|
||||
return $this->outboxProcessors[$scope];
|
||||
} else {
|
||||
throw new MissingOutboxProcessorException(__('Processors for {0} not found', $scope));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function loadProcessors()
|
||||
{
|
||||
$processorDir = new Folder($this->processorsDirectory);
|
||||
$processorFiles = $processorDir->find('.*OutboxProcessor\.php', true);
|
||||
foreach ($processorFiles as $processorFile) {
|
||||
if ($processorFile == 'GenericOutboxProcessor.php') {
|
||||
continue;
|
||||
}
|
||||
$processorMainClassName = str_replace('.php', '', $processorFile);
|
||||
$processorMainClassNameShort = str_replace('OutboxProcessor.php', '', $processorFile);
|
||||
$processorMainClass = $this->getProcessorClass($processorDir->pwd() . DS . $processorFile, $processorMainClassName);
|
||||
if (is_object($processorMainClass)) {
|
||||
$this->outboxProcessors[$processorMainClassNameShort] = $processorMainClass;
|
||||
foreach ($this->outboxProcessors[$processorMainClassNameShort]->getRegisteredActions() as $registeredAction) {
|
||||
$scope = $this->outboxProcessors[$processorMainClassNameShort]->getScope();
|
||||
if (!empty($this->enabledProcessors[$scope][$registeredAction])) {
|
||||
$this->outboxProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = true;
|
||||
} else {
|
||||
$this->outboxProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->outboxProcessors[$processorMainClassNameShort] = new \stdClass();
|
||||
$this->outboxProcessors[$processorMainClassNameShort]->{$registeredAction} = new \stdClass();
|
||||
$this->outboxProcessors[$processorMainClassNameShort]->{$registeredAction}->action = "N/A";
|
||||
$this->outboxProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = false;
|
||||
$this->outboxProcessors[$processorMainClassNameShort]->{$registeredAction}->error = $processorMainClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* getProcessorClass
|
||||
*
|
||||
* @param string $filePath
|
||||
* @param string $processorMainClassName
|
||||
* @return object|string Object loading success, string containing the error if failure
|
||||
*/
|
||||
private function getProcessorClass($filePath, $processorMainClassName)
|
||||
{
|
||||
try {
|
||||
require_once($filePath);
|
||||
try {
|
||||
$reflection = new \ReflectionClass($processorMainClassName);
|
||||
} catch (\ReflectionException $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
$processorMainClass = $reflection->newInstance(true);
|
||||
if ($processorMainClass->checkLoading() === 'Assimilation successful!') {
|
||||
return $processorMainClass;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* createOutboxEntry
|
||||
*
|
||||
* @param Object|Array $processor can either be the processor object or an array containing data to fetch it
|
||||
* @param Array $data
|
||||
* @return Array
|
||||
*/
|
||||
public function createOutboxEntry($processor, $data)
|
||||
{
|
||||
if (!is_object($processor) && !is_array($processor)) {
|
||||
throw new MethodNotAllowedException(__("Invalid processor passed"));
|
||||
}
|
||||
if (is_array($processor)) {
|
||||
if (empty($processor['scope']) || empty($processor['action'])) {
|
||||
throw new MethodNotAllowedException(__("Invalid data passed. Missing either `scope` or `action`"));
|
||||
}
|
||||
$processor = $this->getProcessor('User', 'Registration');
|
||||
}
|
||||
return $processor->create($data);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
<?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;
|
||||
use Cake\Http\Exception\NotFoundException;
|
||||
|
||||
Type::map('json', 'Cake\Database\Type\JsonType');
|
||||
|
||||
class OutboxTable 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')
|
||||
->datetime('created')
|
||||
|
||||
->requirePresence([
|
||||
'scope' => ['message' => __('The field `scope` is required')],
|
||||
'action' => ['message' => __('The field `action` is required')],
|
||||
'title' => ['message' => __('The field `title` 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 createEntry($entryData, $user = null)
|
||||
{
|
||||
$savedEntry = $this->save($entryData);
|
||||
return $savedEntry;
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ class MonadView extends AppView
|
|||
{
|
||||
private $additionalTemplatePaths = [
|
||||
ROOT . '/libraries/default/InboxProcessors/templates/',
|
||||
ROOT . '/libraries/default/OutboxProcessors/templates/',
|
||||
];
|
||||
|
||||
protected function _paths(?string $plugin = null, bool $cached = true): array
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<?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' => 'user',
|
||||
'sort' => 'user_id',
|
||||
'data_path' => 'user',
|
||||
'element' => 'user'
|
||||
],
|
||||
[
|
||||
'name' => 'description',
|
||||
'sort' => 'description',
|
||||
'data_path' => 'description',
|
||||
],
|
||||
[
|
||||
'name' => 'comment',
|
||||
'sort' => 'comment',
|
||||
'data_path' => 'comment',
|
||||
],
|
||||
],
|
||||
'title' => __('Outbox'),
|
||||
'description' => __('A list of requests to be manually processed'),
|
||||
'actions' => [
|
||||
[
|
||||
'url' => '/outbox/view',
|
||||
'url_params_data_paths' => ['id'],
|
||||
'icon' => 'eye',
|
||||
'title' => __('View request')
|
||||
],
|
||||
[
|
||||
'open_modal' => '/outbox/process/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
'icon' => 'cogs',
|
||||
'title' => __('Process request')
|
||||
],
|
||||
[
|
||||
'open_modal' => '/outbox/delete/[onclick_params_data_path]',
|
||||
'modal_params_data_path' => 'id',
|
||||
'icon' => 'trash',
|
||||
'title' => __('Discard request')
|
||||
],
|
||||
]
|
||||
]
|
||||
]);
|
||||
echo '</div>';
|
||||
?>
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
echo $this->element('genericElements/IndexTable/index_table', [
|
||||
'data' => [
|
||||
'skip_pagination' => true,
|
||||
'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' => __('Enabled'),
|
||||
'data_path' => 'enabled',
|
||||
'element' => 'boolean'
|
||||
],
|
||||
[
|
||||
'name' => __('Processor scope'),
|
||||
'data_path' => 'scope',
|
||||
],
|
||||
[
|
||||
'name' => __('Processor action'),
|
||||
'data_path' => 'action',
|
||||
],
|
||||
[
|
||||
'name' => __('Description'),
|
||||
'data_path' => 'description',
|
||||
],
|
||||
[
|
||||
'name' => __('Notice'),
|
||||
'data_path' => 'notice',
|
||||
],
|
||||
[
|
||||
'name' => __('Error'),
|
||||
'data_path' => 'error',
|
||||
],
|
||||
],
|
||||
'title' => __('Available Outbox Request Processors'),
|
||||
'description' => __('The list of Outbox Request Processors available on this server.'),
|
||||
'actions' => [
|
||||
]
|
||||
]
|
||||
]);
|
|
@ -0,0 +1,47 @@
|
|||
<?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' => 'user_id',
|
||||
'path' => 'user_id',
|
||||
],
|
||||
[
|
||||
'key' => 'description',
|
||||
'path' => 'description',
|
||||
],
|
||||
[
|
||||
'key' => 'comment',
|
||||
'path' => 'comment',
|
||||
],
|
||||
[
|
||||
'key' => 'data',
|
||||
'path' => 'data',
|
||||
'type' => 'json'
|
||||
],
|
||||
],
|
||||
'children' => []
|
||||
]
|
||||
);
|
Loading…
Reference in New Issue