Merge branch 'inbox-misp-sync' into develop
|
@ -56,11 +56,6 @@ class InboxSystem extends AbstractMigration
|
|||
'null' => false,
|
||||
'limit' => 191,
|
||||
])
|
||||
// ->addColumn('ip', 'string', [
|
||||
// 'limit' => 191,
|
||||
// 'default' => null,
|
||||
// 'null' => true,
|
||||
// ])
|
||||
->addColumn('user_id', 'integer', [
|
||||
'default' => null,
|
||||
'null' => true,
|
||||
|
@ -92,7 +87,6 @@ class InboxSystem extends AbstractMigration
|
|||
->addIndex('action')
|
||||
->addIndex('title')
|
||||
->addIndex('origin')
|
||||
// ->addIndex('ip')
|
||||
->addIndex('created')
|
||||
->addIndex('user_id');
|
||||
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use Migrations\AbstractMigration;
|
||||
use Phinx\Db\Adapter\MysqlAdapter;
|
||||
|
||||
class RemoteToolConnections 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('remote_tool_connections', [
|
||||
'signed' => false,
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
]);
|
||||
$table
|
||||
->addColumn('id', 'integer', [
|
||||
'autoIncrement' => true,
|
||||
'limit' => 10,
|
||||
'signed' => false,
|
||||
])
|
||||
->addPrimaryKey('id')
|
||||
->addColumn('local_tool_id', 'integer', [
|
||||
'null' => false,
|
||||
'signed' => false,
|
||||
'length' => 10,
|
||||
])
|
||||
->addColumn('remote_tool_id', 'integer', [
|
||||
'null' => false,
|
||||
'signed' => false,
|
||||
'length' => 10,
|
||||
])
|
||||
->addColumn('remote_tool_name', 'string', [
|
||||
'null' => false,
|
||||
'limit' => 191,
|
||||
])
|
||||
->addColumn('brood_id', 'integer', [
|
||||
'null' => false,
|
||||
'signed' => false,
|
||||
'length' => 10,
|
||||
])
|
||||
->addColumn('name', 'string', [
|
||||
'null' => true,
|
||||
'limit' => 191,
|
||||
])
|
||||
->addColumn('settings', 'text', [
|
||||
'null' => true,
|
||||
'limit' => MysqlAdapter::TEXT_LONG
|
||||
])
|
||||
->addColumn('status', 'string', [
|
||||
'null' => true,
|
||||
'limit' => 32,
|
||||
'encoding' => 'ascii',
|
||||
])
|
||||
->addColumn('created', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
])
|
||||
->addColumn('modified', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
|
||||
$table->addForeignKey('local_tool_id', 'local_tools', 'id', ['delete'=> 'CASCADE', 'update'=> 'CASCADE']);
|
||||
|
||||
$table->addIndex('remote_tool_id')
|
||||
->addIndex('remote_tool_name')
|
||||
->addIndex('status')
|
||||
->addIndex('name');
|
||||
|
||||
$table->create();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 112 KiB |
After Width: | Height: | Size: 218 KiB |
|
@ -0,0 +1 @@
|
|||
<mxfile host="app.diagrams.net" modified="2021-06-01T09:17:40.738Z" agent="5.0 (X11)" etag="oCi7v6cwN3m9GtYeR8kD" version="14.7.3" type="device"><diagram id="kgpKYQtTHZ0yAKxKKP6v" name="Page-1">7Vxbk5s2FP41nrYP9iAJcXm0vZs0bTrN7Haay0sHg9ZmgsEBvLHz6ytAMkjChrXxNZuHjTnIByF95zsXnd0eGs9Xb2NnMfsr8kjQg5q36qG7HoRQhwb9L5OsCwnQNVBIprHvMVkpePR/ECbUmHTpeyQRBqZRFKT+QhS6URgSNxVkThxH38VhT1EgPnXhTIkieHSdQJV+9L10VkgtrJXy34k/nfEnA43dmTt8MBMkM8eLvldE6L6HxnEUpcWn+WpMgmz1+LoU33uz5e5mYjEJ0zZfQOFy9GYV2+76vbYI//74D3Te9pmWZydYshcek5hMYiclVDxkM0/XfDnoSyyyj8t58N5/IoEf0qvRgsT+nKQkpncCJv5QykZ0c1KHyrL7IL8OAmeR+JNcrUYlMXGXceI/kweSFBjIpdEy9IjHrjYLmF+kcfR1syWZUnU9+MuROCWrioitz1sS0QnGazqE4xXA4isbtDL4fi+3frPBs8q266bOIMfgNt3oLneEfmCb8oINgsoGKXuyiPwwzZ+LRz18J+1HFKezaBqFTlDdkQNXdieWWi+3joXVNrWaxVbXGtnakdYaKWs99OZ+SEUx+bYkCV1kqP317vFDL6M1tBrStaFLT+I+Yx8/CpXdyZbDp3wyDPxpSEWTKE2jeb7OTpwOM4rKdok+lMpI6HHJJIjcr3wY40Wr672iuqdk1ziGNuIJLKnuKNvCPt+wmAROSs1Z5OOaDWOqPmQYLpHRt+qhwTUk0TJ2CftSlfO4Hj4wenpKSKogYzPzVmCBk4n/35c/Pj18fvjxGbsz8wt4qmFOioqAruXI85/px2ma70YhShZOKMDC+LbMeH80cdyv09wYKYaCiJrmMFu96eRXiOkS0Nems9Okz7+V3+cPGseEUnaGT89PHMqrHn80fbni6eKMqLhmnueY+jKhjESHjO97o8JFZ5ejF85fsrpZOg+YRWw1wBpjC5wJCUabFxsX73UXRrmbe6Ju7I0z94MMlf+S2HNCh4mZgVL/QfUGk1wpf3AmovbzyGZXEvJ9KR1R9/ec83FpxJLRtbfqwjq6sv6t9K0NgAlEO0XdmD8WtcqE32j93Ptooh4afIqKigVQFFFUOOvKMOZgt05Xfo6tvWxaSBhPPxQTODml2RcTaygxRFvwbwUrBhJQ2wUbAHcQbNT7j+0O5OiUex9mLiLzFhL5bqHcmyJXsvLTTxn+Bphdfc6vIOJ371YMnvnFunJRgXUuO4iodxCrZhqWyBG4G2I1RSvQ8UBD+5GrIaZKyJL0dMatUJ5yA7nWz+uU5Fq75Vgx92qmrRrZbWfaGCLJNZ890zYuxvs10gd+6XJLmbbVzvlB41jOT7+Ytd4n0sC711qTkI3bRRo85e18seuAfe5MVQo9wNVkq0V2OvyZs9NtARSw7JMFUMxWqpnuLqKpZrrbx+0IyJCl64JZ95HZSUTWR8ZA04AOLWxmPw3TEOnDtAe2zW7Sn0hiibbhWh9pA5tmPeU/MRSkDxKfYx0lnBMXEVi7M2VxtHTAsS2UW0kbJG/Y8fNotY5MfVi1RMzryZlfyxwD9Qs8B/sly8qGH97Rn3+StcIcFVZwAydJfJcaUYVOWthT2yIRPMx0Ku4O17g73E2lCGEp3MfGfvaBpdQI2pKijgxAnjB/Ti2ouwKkeZkRwOM6dAdX4/kFA06jahQwiWum/RoXsLgAA/0YcUENj+3l2iEQU9E+7KiIbYh6kYWph63825OoJLU63Km2I9rCUKocWcbuWUrjeSPI2WvcVqNvpn7ZmQR+MqPAFvzzqYimyIjkQKA105wkYjgo2LbPGTHodj0yX2yI0JIUgeNEDLpsSvgEEQOoiWHPHjIkNxAzjF5jhjMexjSdu0DDkurS3ZAONGxBLdbNvQ9eoCmmxVguHHaVqQBdnvPOeSnjLeGk5nwun8+rpq/LcV2yuJ62rjZe+sBSV18bAE06Lujo8NGEYmUcAel4ofXRIxAVQQu2soCTgk497+Oge/IpI9KdTyp4qyJtI7wW0O0CkwawVDjlVb0D0QSQlAeZ+/KpnFDJii4BTeoZzpX3aTecuzX1aYsEYPJmmqbTYwSl9Li7dp66Gpu0KZdxpLkDYG3Pj1t2asPjNU+pafy1tmrvpNedZNDo6QtMqpv6EzZr17U2XkI6+9qtff057KF23FhXO9jgXxu2b7NhmydUtxl0YDHIA7w009RIBaSD/s6ijk2b4xlath/IPHp+bdk+SZVwH7AesWdbJB+oGwMNVs77JLS3LqLINRSpr7YzqtXk6Tdwbf28zs+1UDH+K2/gPigFx/wrl5OCw7ozq+vxhsVs26bgLVu4j/f7S7yMd6OrvV8Tt3GsHm6oFgAvvKdwZ5WsMZOBZ+4qFLffllW07yqUrNaQFHXWVajVPueoPQKwuZfGI27u904VJRcdO1fQKsPZs9kQtN4ZDUG3pUTZ3tMQoFQjtK3jGIIOxBjcBicwBKSm4bwazQzgNqrRbU+Yt5H3CY6YpfKWJcOsfXYkKTKlNOsCDgWRWo642SPmA5jyJGfRYjJk6/vSpCEqAhoyhe7edr+Vc1IUqnnxxgPLDjirDNX6/+xGv8hmM8dPAbBSnfujS0In9rMuOlCMG1djjaKXpzxKqfX/xQwU8WRrTEATi1SMAhxmGXQ2RcYim8zc97y8hhVnCbpTZu5qOuQs04gl8WBjH1KVrXd4si7XkICmJusI1YQR8FjZI9reX3o81MAa1DAH/YqaZtQY5tlRozZVvRgkRh1IDsderdoi5NPeKaGelnvpSv3wBVDbMvQmESj1+SC16KXXZT97AJBeln+Ks/CN5V80Rff/Aw==</diagram></mxfile>
|
After Width: | Height: | Size: 119 KiB |
After Width: | Height: | Size: 202 KiB |
After Width: | Height: | Size: 123 KiB |
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 155 KiB |
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'InboxProcessors' . DS . 'GenericInboxProcessor.php');
|
||||
|
||||
class BroodInboxProcessor extends GenericInboxProcessor
|
||||
{
|
||||
protected $scope = 'Brood';
|
||||
protected $action = 'not-specified'; //overriden when extending
|
||||
protected $description = ''; // overriden when extending
|
||||
protected $registeredActions = [
|
||||
'ToolInterconnection',
|
||||
];
|
||||
|
||||
public function __construct($loadFromAction=false) {
|
||||
parent::__construct($loadFromAction);
|
||||
}
|
||||
|
||||
public function create($requestData)
|
||||
{
|
||||
return parent::create($requestData);
|
||||
}
|
||||
}
|
||||
|
||||
class ToolInterconnectionProcessor extends BroodInboxProcessor implements GenericInboxProcessorActionI {
|
||||
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, $inboxRequest)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
}
|
|
@ -5,26 +5,31 @@ use Cake\Utility\Inflector;
|
|||
use Cake\Validation\Validator;
|
||||
use Cake\View\ViewBuilder;
|
||||
|
||||
interface GenericProcessorActionI
|
||||
interface GenericInboxProcessorActionI
|
||||
{
|
||||
public function create($requestData);
|
||||
public function process($requestID, $serverRequest);
|
||||
public function process($requestID, $serverRequest, $inboxRequest);
|
||||
public function discard($requestID ,$requestData);
|
||||
}
|
||||
|
||||
class GenericRequestProcessor
|
||||
class GenericInboxProcessor
|
||||
{
|
||||
protected $Inbox;
|
||||
protected $registeredActions = [];
|
||||
protected $validator;
|
||||
private $processingTemplate = '/genericTemplates/confirm';
|
||||
private $processingTemplatesDirectory = ROOT . '/libraries/default/RequestProcessors/templates';
|
||||
protected $processingTemplate = '/genericTemplates/confirm';
|
||||
protected $processingTemplatesDirectory = ROOT . '/libraries/default/InboxProcessors/templates';
|
||||
|
||||
public function __construct($registerActions=false) {
|
||||
$this->Inbox = TableRegistry::getTableLocator()->get('Inbox');
|
||||
if ($registerActions) {
|
||||
$this->registerActionInProcessor();
|
||||
}
|
||||
$this->assignProcessingTemplate();
|
||||
}
|
||||
|
||||
private function assignProcessingTemplate()
|
||||
{
|
||||
$processingTemplatePath = $this->getProcessingTemplatePath();
|
||||
$file = new File($this->processingTemplatesDirectory . DS . $processingTemplatePath);
|
||||
if ($file->exists()) {
|
||||
|
@ -33,6 +38,10 @@ class GenericRequestProcessor
|
|||
$file->close();
|
||||
}
|
||||
|
||||
protected function updateProcessingTemplate($request)
|
||||
{
|
||||
}
|
||||
|
||||
public function getRegisteredActions()
|
||||
{
|
||||
return $this->registeredActions;
|
||||
|
@ -41,14 +50,16 @@ class GenericRequestProcessor
|
|||
{
|
||||
return $this->scope;
|
||||
}
|
||||
|
||||
private function getProcessingTemplatePath()
|
||||
public function getDescription()
|
||||
{
|
||||
return $this->description ?? '';
|
||||
}
|
||||
|
||||
protected 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
|
||||
$this->scope,
|
||||
$this->action
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -57,15 +68,17 @@ class GenericRequestProcessor
|
|||
return $this->processingTemplate;
|
||||
}
|
||||
|
||||
public function render($request=[])
|
||||
public function render($request=[], Cake\Http\ServerRequest $serverRequest)
|
||||
{
|
||||
$processingTemplate = $this->getProcessingTemplate();
|
||||
$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();
|
||||
}
|
||||
|
||||
|
@ -141,7 +154,7 @@ class GenericRequestProcessor
|
|||
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);
|
||||
$response = $controller->RestResponse->ajaxSuccessResponse('InboxProcessor', "{$scope}.{$action}", $processResult['data'], $message);
|
||||
} else {
|
||||
$controller->Flash->success($message);
|
||||
if (!is_null($redirect)) {
|
||||
|
@ -155,7 +168,7 @@ class GenericRequestProcessor
|
|||
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']);
|
||||
$response = $controller->RestResponse->ajaxFailResponse('InboxProcessor', "{$scope}.{$action}", $processResult['data'], $message, $processResult['errors']);
|
||||
} else {
|
||||
$controller->Flash->error($message);
|
||||
if (!is_null($redirect)) {
|
||||
|
@ -180,7 +193,7 @@ class GenericRequestProcessor
|
|||
$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,418 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
use Cake\Filesystem\File;
|
||||
use Cake\Http\Exception\NotFoundException;
|
||||
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'InboxProcessors' . DS . 'GenericInboxProcessor.php');
|
||||
|
||||
class LocalToolInboxProcessor extends GenericInboxProcessor
|
||||
{
|
||||
protected $scope = 'LocalTool';
|
||||
protected $action = 'not-specified'; //overriden when extending
|
||||
protected $description = ''; // overriden when extending
|
||||
protected $registeredActions = [
|
||||
'IncomingConnectionRequest',
|
||||
'AcceptedRequest',
|
||||
'DeclinedRequest',
|
||||
];
|
||||
protected $processingTemplate = 'LocalTool/GenericRequest';
|
||||
protected $Broods;
|
||||
protected $LocalTools;
|
||||
|
||||
public function __construct($loadFromAction=false)
|
||||
{
|
||||
parent::__construct($loadFromAction);
|
||||
$this->Broods = TableRegistry::getTableLocator()->get('Broods');
|
||||
$this->LocalTools = TableRegistry::getTableLocator()->get('LocalTools');
|
||||
}
|
||||
|
||||
public function create($requestData)
|
||||
{
|
||||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
protected function updateProcessingTemplate($request)
|
||||
{
|
||||
$connectorName = $request->connector['connector'];
|
||||
$processingTemplatePath = sprintf('%s/%s/%s.php', $this->scope, $connectorName, $this->action);
|
||||
$file = new File($this->processingTemplatesDirectory . DS . $processingTemplatePath);
|
||||
if ($file->exists()) {
|
||||
$this->processingTemplate = str_replace('.php', '', $processingTemplatePath);
|
||||
}
|
||||
$file->close();
|
||||
}
|
||||
|
||||
protected function validateConnectorName($requestData)
|
||||
{
|
||||
if (empty($requestData['data']['connectorName'])) {
|
||||
throw new NotFoundException('Error while validating request data. Connector name is missing.');
|
||||
}
|
||||
$connector = $this->getConnectorFromClassname($requestData['data']['connectorName']);
|
||||
if (is_null($connector)) {
|
||||
throw new NotFoundException(__('Error while validating request data. Unkown connector `{0}`', $requestData['data']['connectorName']));
|
||||
}
|
||||
}
|
||||
|
||||
protected function getIssuerBrood($request)
|
||||
{
|
||||
$brood = $this->Broods->find()
|
||||
->where(['url' => $request['origin']])
|
||||
->first();
|
||||
return $brood;
|
||||
}
|
||||
|
||||
protected function getConnection($requestData)
|
||||
{
|
||||
$local_tool_id = $requestData['remote_tool_id']; // local_tool_id is actually the remote_tool_id for the sender
|
||||
$connection = $this->LocalTools->find()->where(['id' => $local_tool_id])->first();
|
||||
return $connection;
|
||||
}
|
||||
|
||||
protected function filterAlignmentsForBrood($individual, $brood)
|
||||
{
|
||||
foreach ($individual->alignments as $i => $alignment) {
|
||||
if ($alignment->organisation_id != $brood->organisation_id) {
|
||||
unset($individual->alignments[$i]);
|
||||
}
|
||||
}
|
||||
return $individual;
|
||||
}
|
||||
|
||||
protected function getConnector($request)
|
||||
{
|
||||
try {
|
||||
$connectorClasses = $this->LocalTools->getConnectors($request->local_tool_connector_name);
|
||||
if (!empty($connectorClasses)) {
|
||||
$connector = array_values($connectorClasses)[0];
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
$connector = null;
|
||||
}
|
||||
return $connector;
|
||||
}
|
||||
|
||||
protected function getConnectorMeta($request)
|
||||
{
|
||||
try {
|
||||
$className = $request->local_tool_connector_name;
|
||||
$connector = $this->getConnectorFromClassname($className);
|
||||
$connectorMeta = $this->LocalTools->extractMeta([$className => $connector])[0];
|
||||
} catch (NotFoundException $e) {
|
||||
$connectorMeta = [];
|
||||
}
|
||||
return $connectorMeta;
|
||||
}
|
||||
|
||||
protected function getConnectorFromClassname($className)
|
||||
{
|
||||
try {
|
||||
$connectorClasses = $this->LocalTools->getConnectors($className);
|
||||
if (!empty($connectorClasses)) {
|
||||
$connector = array_values($connectorClasses)[0];
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
$connector = null;
|
||||
}
|
||||
return $connector;
|
||||
}
|
||||
|
||||
protected function getConnectorMetaFromClassname($className)
|
||||
{
|
||||
try {
|
||||
$connector = $this->getConnectorFromClassname($className);
|
||||
$connectorMeta = $this->LocalTools->extractMeta([$className => $connector])[0];
|
||||
} catch (NotFoundException $e) {
|
||||
$connectorMeta = [];
|
||||
}
|
||||
return $connectorMeta;
|
||||
}
|
||||
|
||||
protected function attachRequestAssociatedData($request)
|
||||
{
|
||||
$request->brood = $this->getIssuerBrood($request);
|
||||
$request->connector = $this->getConnectorMeta($request);
|
||||
$request->individual = $request->user->individual;
|
||||
$request->individual = $this->filterAlignmentsForBrood($request->individual, $request->brood);
|
||||
return $request;
|
||||
}
|
||||
|
||||
protected function genBroodParam($remoteCerebrate, $connection, $connector, $requestData)
|
||||
{
|
||||
$local_tool_id = $requestData['remote_tool_id']; // local_tool_id is actually the remote_tool_id for the sender
|
||||
$remote_tool_id = $requestData['local_tool_id']; // remote_tool_id is actually the local_tool_id for the sender
|
||||
$remote_org = $this->Broods->Organisations->find()->where(['id' => $remoteCerebrate->organisation_id])->first();
|
||||
return [
|
||||
'remote_tool' => [
|
||||
'id' => $remote_tool_id,
|
||||
'connector' => $connector->connectorName,
|
||||
'name' => $requestData['tool_name'],
|
||||
],
|
||||
'remote_org' => $remote_org,
|
||||
'remote_tool_data' => $requestData,
|
||||
'remote_cerebrate' => $remoteCerebrate,
|
||||
'connection' => $connection,
|
||||
'connector' => [$connector->connectorName => $connector],
|
||||
];
|
||||
}
|
||||
|
||||
protected function addBaseValidatorRules($validator)
|
||||
{
|
||||
return $validator
|
||||
->requirePresence('connectorName')
|
||||
->notEmpty('connectorName', 'The connector name must be provided')
|
||||
->requirePresence('cerebrateURL')
|
||||
->notEmpty('cerebrateURL', 'A url must be provided')
|
||||
->requirePresence('local_tool_id')
|
||||
->numeric('local_tool_id', 'A local_tool_id must be provided')
|
||||
->requirePresence('remote_tool_id')
|
||||
->numeric('remote_tool_id', 'A remote_tool_id must be provided');
|
||||
// ->add('url', 'validFormat', [
|
||||
// 'rule' => 'url',
|
||||
// 'message' => 'URL must be valid'
|
||||
// ]);
|
||||
}
|
||||
}
|
||||
|
||||
class IncomingConnectionRequestProcessor extends LocalToolInboxProcessor implements GenericInboxProcessorActionI {
|
||||
public $action = 'IncomingConnectionRequest';
|
||||
protected $description;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->description = __('Handle Phase I of inter-connection when another cerebrate instance performs the request.');
|
||||
}
|
||||
|
||||
protected function addValidatorRules($validator)
|
||||
{
|
||||
return $this->addBaseValidatorRules($validator);
|
||||
}
|
||||
|
||||
public function create($requestData) {
|
||||
$this->validateConnectorName($requestData);
|
||||
$this->validateRequestData($requestData);
|
||||
$connectorMeta = $this->getConnectorMetaFromClassname($requestData['data']['connectorName']);
|
||||
$requestData['title'] = __('Request for {0} Inter-connection', $connectorMeta['name']);
|
||||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
public function getViewVariables($request)
|
||||
{
|
||||
$request = $this->attachRequestAssociatedData($request);
|
||||
return [
|
||||
'request' => $request,
|
||||
'progressStep' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
public function process($id, $requestData, $inboxRequest)
|
||||
{
|
||||
/**
|
||||
* /!\ Should how should sent message be? be fire and forget? Only for delined?
|
||||
*/
|
||||
$interConnectionResult = [];
|
||||
$remoteCerebrate = $this->getIssuerBrood($inboxRequest);
|
||||
$connector = $this->getConnector($inboxRequest);
|
||||
if (!empty($requestData['is_discard'])) { // -> declined
|
||||
$connectorResult = $this->declineConnection($connector, $remoteCerebrate, $inboxRequest['data']); // Fire-and-forget?
|
||||
$connectionSuccessfull = !empty($connectorResult['success']);
|
||||
$resultTitle = __('Could not sent declined message to `{0}`\'s for {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
|
||||
$errors = [];
|
||||
if ($connectionSuccessfull) {
|
||||
$resultTitle = __('Declined message successfully sent to `{0}`\'s for {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
|
||||
$this->discard($id, $inboxRequest);
|
||||
}
|
||||
} else {
|
||||
$errors = [];
|
||||
$connectorResult = [];
|
||||
$thrownErrorMessage = '';
|
||||
try {
|
||||
$connectorResult = $this->acceptConnection($connector, $remoteCerebrate, $inboxRequest['data']);
|
||||
$connectionSuccessfull = !empty($connectorResult['success']);
|
||||
} catch (\Throwable $th) {
|
||||
$connectionSuccessfull = false;
|
||||
$thrownErrorMessage = $th->getMessage();
|
||||
}
|
||||
$resultTitle = $connectorResult['message'] ?? __('Could not inter-connect `{0}`\'s {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
|
||||
$errors = $connectorResult['errors'] ?? $thrownErrorMessage;
|
||||
if ($connectionSuccessfull) {
|
||||
$resultTitle = __('Interconnection for `{0}`\'s {1} created', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
|
||||
}
|
||||
if ($connectionSuccessfull || !empty($connectorResult['placed_in_outbox'])) {
|
||||
$this->discard($id, $inboxRequest);
|
||||
}
|
||||
}
|
||||
return $this->genActionResult(
|
||||
$connectorResult,
|
||||
$connectionSuccessfull,
|
||||
$resultTitle,
|
||||
$errors
|
||||
);
|
||||
}
|
||||
|
||||
public function discard($id, $requestData)
|
||||
{
|
||||
return parent::discard($id, $requestData);
|
||||
}
|
||||
|
||||
protected function acceptConnection($connector, $remoteCerebrate, $requestData)
|
||||
{
|
||||
$connection = $this->getConnection($requestData);
|
||||
$params = $this->genBroodParam($remoteCerebrate, $connection, $connector, $requestData);
|
||||
$connectorResult = $connector->acceptConnectionWrapper($params);
|
||||
$response = $this->sendAcceptedRequestToRemote($params, $connectorResult);
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function declineConnection($connector, $remoteCerebrate, $requestData)
|
||||
{
|
||||
$connection = $this->getConnection($requestData);
|
||||
$params = $this->genBroodParam($remoteCerebrate, $connection, $connector, $requestData);
|
||||
$connectorResult = $connector->declineConnectionWrapper($params);
|
||||
$response = $this->sendDeclinedRequestToRemote($params, $connectorResult);
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function sendAcceptedRequestToRemote($params, $connectorResult)
|
||||
{
|
||||
$response = $this->Broods->sendLocalToolAcceptedRequest($params, $connectorResult);
|
||||
return $response;
|
||||
}
|
||||
|
||||
protected function sendDeclinedRequestToRemote($remoteCerebrate, $connectorResult)
|
||||
{
|
||||
$response = $this->Broods->sendLocalToolDeclinedRequest($params, $connectorResult);
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
class AcceptedRequestProcessor extends LocalToolInboxProcessor implements GenericInboxProcessorActionI {
|
||||
public $action = 'AcceptedRequest';
|
||||
protected $description;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->description = __('Handle Phase II of inter-connection when initial request has been accepted by the remote cerebrate.');
|
||||
}
|
||||
|
||||
protected function addValidatorRules($validator)
|
||||
{
|
||||
return $this->addBaseValidatorRules($validator);
|
||||
}
|
||||
|
||||
public function create($requestData) {
|
||||
$this->validateConnectorName($requestData);
|
||||
$this->validateRequestData($requestData);
|
||||
$connectorMeta = $this->getConnectorMetaFromClassname($requestData['data']['connectorName']);
|
||||
$requestData['title'] = __('Inter-connection for {0} has been accepted', $connectorMeta['name']);
|
||||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
public function getViewVariables($request)
|
||||
{
|
||||
$request = $this->attachRequestAssociatedData($request);
|
||||
return [
|
||||
'request' => $request,
|
||||
'progressStep' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
public function process($id, $requestData, $inboxRequest)
|
||||
{
|
||||
$connector = $this->getConnector($inboxRequest);
|
||||
$remoteCerebrate = $this->getIssuerBrood($inboxRequest);
|
||||
|
||||
$errors = [];
|
||||
$connectorResult = [];
|
||||
$thrownErrorMessage = '';
|
||||
try {
|
||||
$connectorResult = $this->finaliseConnection($connector, $remoteCerebrate, $inboxRequest['data']);
|
||||
$connectionSuccessfull = !empty($connectorResult['success']);
|
||||
} catch (\Throwable $th) {
|
||||
$connectionSuccessfull = false;
|
||||
$errors = $th->getMessage();
|
||||
}
|
||||
$resultTitle = __('Could not finalize inter-connection for `{0}`\'s {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
|
||||
$errors = $connectorResult['errors'] ?? $thrownErrorMessage;
|
||||
if ($connectionSuccessfull) {
|
||||
$resultTitle = __('Interconnection for `{0}`\'s {1} finalised', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
|
||||
$this->discard($id, $requestData);
|
||||
}
|
||||
return $this->genActionResult(
|
||||
$connectorResult,
|
||||
$connectionSuccessfull,
|
||||
$resultTitle,
|
||||
$errors
|
||||
);
|
||||
}
|
||||
|
||||
public function discard($id, $requestData)
|
||||
{
|
||||
return parent::discard($id, $requestData);
|
||||
}
|
||||
|
||||
protected function finaliseConnection($connector, $remoteCerebrate, $requestData)
|
||||
{
|
||||
$connection = $this->getConnection($requestData);
|
||||
$params = $this->genBroodParam($remoteCerebrate, $connection, $connector, $requestData);
|
||||
$connectorResult = $connector->finaliseConnectionWrapper($params);
|
||||
return [
|
||||
'success' => true
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class DeclinedRequestProcessor extends LocalToolInboxProcessor implements GenericInboxProcessorActionI {
|
||||
public $action = 'DeclinedRequest';
|
||||
protected $description;
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
$this->description = __('Handle Phase II of MISP inter-connection when initial request has been declined by the remote cerebrate.');
|
||||
}
|
||||
|
||||
protected function addValidatorRules($validator)
|
||||
{
|
||||
return $this->addBaseValidatorRules($validator);
|
||||
}
|
||||
|
||||
public function create($requestData) {
|
||||
$this->validateConnectorName($requestData);
|
||||
$this->validateRequestData($requestData);
|
||||
$connectorMeta = $this->getConnectorMetaFromClassname($requestData['data']['connectorName']);
|
||||
$requestData['title'] = __('Declined inter-connection for {0}', $connectorMeta['name']);
|
||||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
public function getViewVariables($request)
|
||||
{
|
||||
$request = $this->attachRequestAssociatedData($request);
|
||||
return [
|
||||
'request' => $request,
|
||||
'progressStep' => 1,
|
||||
'progressVariant' => 'danger',
|
||||
'steps' => [
|
||||
1 => ['icon' => 'times', 'text' => __('Request Declined'), 'confirmButton' => __('Clean-up')],
|
||||
2 => ['icon' => 'trash', 'text' => __('Clean-up')],
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function process($id, $requestData, $inboxRequest)
|
||||
{
|
||||
$connectionSuccessfull = false;
|
||||
$interConnectionResult = [];
|
||||
if ($connectionSuccessfull) {
|
||||
$this->discard($id, $requestData);
|
||||
}
|
||||
return $this->genActionResult(
|
||||
$interConnectionResult,
|
||||
$connectionSuccessfull,
|
||||
$connectionSuccessfull ? __('Interconnection for `{0}`\'s {1} finalised', $requestData['origin'], $requestData['local_tool_name']) : __('Could not inter-connect `{0}`\'s {1}', $requestData['origin'], $requestData['local_tool_name']),
|
||||
[]
|
||||
);
|
||||
}
|
||||
public function discard($id, $requestData)
|
||||
{
|
||||
return parent::discard($id, $requestData);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'RequestProcessors' . DS . 'GenericRequestProcessor.php');
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'InboxProcessors' . DS . 'GenericInboxProcessor.php');
|
||||
|
||||
class ProposalRequestProcessor extends GenericRequestProcessor
|
||||
class ProposalInboxProcessor extends GenericInboxProcessor
|
||||
{
|
||||
protected $scope = 'Proposal';
|
||||
protected $action = 'not-specified'; //overriden when extending
|
||||
|
@ -22,7 +22,7 @@ class ProposalRequestProcessor extends GenericRequestProcessor
|
|||
}
|
||||
}
|
||||
|
||||
class ProposalEditProcessor extends ProposalRequestProcessor implements GenericProcessorActionI {
|
||||
class ProposalEditProcessor extends ProposalInboxProcessor implements GenericInboxProcessorActionI {
|
||||
public $action = 'ProposalEdit';
|
||||
protected $description;
|
||||
|
||||
|
@ -43,7 +43,7 @@ class ProposalEditProcessor extends ProposalRequestProcessor implements GenericP
|
|||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
public function process($id, $requestData)
|
||||
public function process($id, $requestData, $inboxRequest)
|
||||
{
|
||||
$proposalAccepted = false;
|
||||
$saveResult = [];
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'RequestProcessors' . DS . 'GenericRequestProcessor.php');
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'InboxProcessors' . DS . 'GenericInboxProcessor.php');
|
||||
|
||||
class SynchronisationRequestProcessor extends GenericRequestProcessor
|
||||
class SynchronisationInboxProcessor extends GenericInboxProcessor
|
||||
{
|
||||
protected $scope = 'Synchronisation';
|
||||
protected $action = 'not-specified'; //overriden when extending
|
||||
|
@ -22,7 +22,7 @@ class SynchronisationRequestProcessor extends GenericRequestProcessor
|
|||
}
|
||||
}
|
||||
|
||||
class DataExchangeProcessor extends SynchronisationRequestProcessor implements GenericProcessorActionI {
|
||||
class DataExchangeProcessor extends SynchronisationInboxProcessor implements GenericInboxProcessorActionI {
|
||||
public $action = 'DataExchange';
|
||||
protected $description;
|
||||
|
||||
|
@ -43,7 +43,7 @@ class DataExchangeProcessor extends SynchronisationRequestProcessor implements G
|
|||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
public function process($id, $requestData)
|
||||
public function process($id, $requestData, $inboxRequest)
|
||||
{
|
||||
$dataExchangeAccepted = false;
|
||||
$saveResult = [];
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'RequestProcessors' . DS . 'GenericRequestProcessor.php');
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'InboxProcessors' . DS . 'GenericInboxProcessor.php');
|
||||
|
||||
class SCOPE_RequestProcessor extends GenericRequestProcessor
|
||||
class SCOPE_InboxProcessor extends GenericInboxProcessor
|
||||
{
|
||||
protected $scope = '~to-be-defined~';
|
||||
protected $action = 'not-specified'; //overriden when extending
|
||||
|
@ -22,7 +22,7 @@ class SCOPE_RequestProcessor extends GenericRequestProcessor
|
|||
}
|
||||
}
|
||||
|
||||
class SCOPE_ACTION_Processor extends ProposalRequestProcessor implements GenericProcessorActionI {
|
||||
class SCOPE_ACTION_Processor extends ProposalInboxProcessor implements GenericInboxProcessorActionI {
|
||||
public $action = 'ACTION';
|
||||
protected $description;
|
||||
|
||||
|
@ -43,7 +43,7 @@ class SCOPE_ACTION_Processor extends ProposalRequestProcessor implements Generic
|
|||
return parent::create($requestData);
|
||||
}
|
||||
|
||||
public function process($id, $requestData)
|
||||
public function process($id, $requestData, $inboxRequest)
|
||||
{
|
||||
$proposalAccepted = false;
|
||||
$saveResult = [];
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
use Cake\ORM\TableRegistry;
|
||||
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'RequestProcessors' . DS . 'GenericRequestProcessor.php');
|
||||
require_once(ROOT . DS . 'libraries' . DS . 'default' . DS . 'InboxProcessors' . DS . 'GenericInboxProcessor.php');
|
||||
|
||||
class UserRequestProcessor extends GenericRequestProcessor
|
||||
class UserInboxProcessor extends GenericInboxProcessor
|
||||
{
|
||||
protected $scope = 'User';
|
||||
protected $action = 'not-specified'; //overriden when extending
|
||||
|
@ -24,7 +24,7 @@ class UserRequestProcessor extends GenericRequestProcessor
|
|||
}
|
||||
}
|
||||
|
||||
class RegistrationProcessor extends UserRequestProcessor implements GenericProcessorActionI {
|
||||
class RegistrationProcessor extends UserInboxProcessor implements GenericInboxProcessorActionI {
|
||||
public $action = 'Registration';
|
||||
protected $description;
|
||||
|
||||
|
@ -80,7 +80,7 @@ class RegistrationProcessor extends UserRequestProcessor implements GenericProce
|
|||
];
|
||||
}
|
||||
|
||||
public function process($id, $requestData)
|
||||
public function process($id, $requestData, $inboxRequest)
|
||||
{
|
||||
if ($requestData['individual_id'] == -1) {
|
||||
$individual = $this->Users->Individuals->newEntity([
|
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
$defaultSteps = [
|
||||
[
|
||||
'text' => __('Request Sent'),
|
||||
'icon' => 'paper-plane',
|
||||
'title' => __(''),
|
||||
'confirmButton' => __('Accept Request'),
|
||||
'canDiscard' => true,
|
||||
],
|
||||
[
|
||||
'text' => __('Request Accepted'),
|
||||
'icon' => 'check-square',
|
||||
'title' => __(''),
|
||||
'confirmButton' => __('Finalize Connection')
|
||||
],
|
||||
[
|
||||
'text' => __('Connection Done'),
|
||||
'icon' => 'exchange-alt',
|
||||
'title' => __(''),
|
||||
]
|
||||
];
|
||||
|
||||
$footerButtons = [];
|
||||
|
||||
$progressVariant = !empty($progressVariant) ? $progressVariant : 'info';
|
||||
$finalSteps = array_replace($defaultSteps, $steps ?? []);
|
||||
$currentStep = $finalSteps[$progressStep];
|
||||
$progress = $this->Bootstrap->progressTimeline([
|
||||
'variant' => $progressVariant,
|
||||
'selected' => !empty($progressStep) ? $progressStep : 0,
|
||||
'steps' => $finalSteps,
|
||||
]);
|
||||
|
||||
$footerButtons[] = [
|
||||
'clickFunction' => 'cancel',
|
||||
'variant' => 'secondary',
|
||||
'text' => __('Cancel'),
|
||||
];
|
||||
if (!empty($currentStep['canDiscard'])) {
|
||||
$footerButtons[] = [
|
||||
'clickFunction' => 'discard',
|
||||
'variant' => 'danger',
|
||||
'text' => __('Decline Request'),
|
||||
];
|
||||
}
|
||||
$footerButtons[] = [
|
||||
'clickFunction' => 'accept',
|
||||
'text' => $currentStep['confirmButton'] ?? __('Submit'),
|
||||
];
|
||||
|
||||
$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' => 'connector', 'label' => __('Tool Name'), 'formatter' => function($connector, $row) {
|
||||
return sprintf('<a href="%s" target="_blank">%s</a>',
|
||||
$this->Url->build(['controller' => 'localTools', 'action' => 'viewConnector', $connector['name']]),
|
||||
sprintf('%s (v%s)', h($connector['name']), h($connector['connector_version']))
|
||||
);
|
||||
}],
|
||||
['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()],
|
||||
]);
|
||||
$form = $this->element('genericElements/Form/genericForm', [
|
||||
'entity' => null,
|
||||
'ajax' => false,
|
||||
'raw' => true,
|
||||
'data' => [
|
||||
'model' => 'Inbox',
|
||||
'fields' => [],
|
||||
'submit' => [
|
||||
'action' => $this->request->getParam('action')
|
||||
]
|
||||
]
|
||||
]);
|
||||
$localToolHTML = $this->fetch('content', sprintf('<div class="d-none">%s</div><div class="form-error-container"></div>', $form));;
|
||||
|
||||
$requestData = $this->Bootstrap->collapse(
|
||||
[
|
||||
'title' => __('Inter-connection data'),
|
||||
'open' => true,
|
||||
],
|
||||
sprintf('<pre class="p-2 rounded mb-0" style="background: #eeeeee55;"><code>%s</code></pre>', json_encode($request['data'], JSON_PRETTY_PRINT))
|
||||
);
|
||||
|
||||
$bodyHtml = sprintf('<div class="py-2"><div>%s</div>%s</div>%s',
|
||||
$table,
|
||||
$requestData,
|
||||
$localToolHTML
|
||||
);
|
||||
|
||||
echo $this->Bootstrap->modal([
|
||||
'title' => __('Interconnection Request for {0}', h($request->local_tool_connector_name)),
|
||||
'size' => 'xl',
|
||||
'type' => 'custom',
|
||||
'bodyHtml' => sprintf('<div class="p-3">%s</div><div class="description-container">%s</div>',
|
||||
$progress,
|
||||
$bodyHtml
|
||||
),
|
||||
'footerButtons' => $footerButtons
|
||||
]);
|
||||
|
||||
?>
|
||||
|
||||
<script>
|
||||
function accept(modalObject, tmpApi) {
|
||||
const $form = modalObject.$modal.find('form')
|
||||
return tmpApi.postForm($form[0]).catch((errors) => {
|
||||
const formHelper = new FormValidationHelper($form[0])
|
||||
const errorHTMLNode = formHelper.buildValidationMessageNode(errors, true)
|
||||
modalObject.$modal.find('div.form-error-container').append(errorHTMLNode)
|
||||
return errors
|
||||
})
|
||||
}
|
||||
function discard(modalObject, tmpApi) {
|
||||
const $form = modalObject.$modal.find('form')
|
||||
const $discardField = $form.find('input#is_discard-field')
|
||||
$discardField.prop('checked', true)
|
||||
return tmpApi.postForm($form[0])
|
||||
}
|
||||
function cancel(modalObject, tmpApi) {
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
$this->extend('LocalTool/GenericRequest');
|
||||
$form = $this->element('genericElements/Form/genericForm', [
|
||||
'entity' => null,
|
||||
'ajax' => false,
|
||||
'raw' => true,
|
||||
'data' => [
|
||||
'model' => 'Inbox',
|
||||
'fields' => [
|
||||
[
|
||||
'field' => 'is_discard',
|
||||
'type' => 'checkbox',
|
||||
'default' => false
|
||||
]
|
||||
],
|
||||
'submit' => [
|
||||
'action' => $this->request->getParam('action')
|
||||
]
|
||||
]
|
||||
]);
|
||||
echo sprintf('<div class="d-none">%s</div><div class="form-error-container"></div>', $form);
|
|
@ -0,0 +1,137 @@
|
|||
<?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;
|
||||
}
|
||||
|
||||
protected function getConnector($className)
|
||||
{
|
||||
try {
|
||||
$connectorClasses = $this->LocalTools->getConnectors($className);
|
||||
if (!empty($connectorClasses)) {
|
||||
$connector = array_values($connectorClasses)[0];
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
$connector = null;
|
||||
}
|
||||
return $connector;
|
||||
}
|
||||
|
||||
protected function setRemoteToolConnectionStatus(Object $brood, Object $outboxRequest, String $status): void
|
||||
{
|
||||
$connector = $this->getConnector($outboxRequest->data['remote_tool']['connector']);
|
||||
$connection = $this->getLocalTool($outboxRequest->data['local_tool_id']);
|
||||
$connectorParams = [
|
||||
'connection' => $connection,
|
||||
'remote_tool' => $outboxRequest->data['remote_tool'],
|
||||
'remote_cerebrate' => $brood,
|
||||
];
|
||||
$connector->remoteToolConnectionStatus($connectorParams, constant(get_class($connector) . '::' . $status));
|
||||
}
|
||||
}
|
||||
|
||||
class ResendFailedMessageProcessor extends BroodsOutboxProcessor implements GenericOutboxProcessorActionI {
|
||||
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)
|
||||
{
|
||||
$brood = $this->getIssuerBrood((int) $outboxRequest->data['brood_id']);
|
||||
if (!empty($requestData['is_delete'])) { // -> declined
|
||||
$success = true;
|
||||
$messageSucess = __('Message successfully deleted');
|
||||
$messageFail = '';
|
||||
$this->setRemoteToolConnectionStatus($brood, $outboxRequest, 'STATE_CANCELLED');
|
||||
} else {
|
||||
$url = $outboxRequest->data['url'];
|
||||
$dataSent = $outboxRequest->data['sent'];
|
||||
$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->setRemoteToolConnectionStatus($brood, $outboxRequest, $outboxRequest->data['next_connector_state']);
|
||||
} else {
|
||||
$this->setRemoteToolConnectionStatus($brood, $outboxRequest, 'STATE_SENDING_ERROR');
|
||||
}
|
||||
}
|
||||
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 GenericOutboxProcessorActionI
|
||||
{
|
||||
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 GenericOutboxProcessorActionI {
|
||||
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,143 @@
|
|||
<?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-borderless"><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);
|
||||
|
||||
$messageSent = $this->Bootstrap->card([
|
||||
'headerHTML' => __('Message Sent'),
|
||||
'bodyHTML' => sprintf('%s%s', $table2, $requestData),
|
||||
'bodyClass' => 'p-2',
|
||||
]);
|
||||
|
||||
$bodyHtml = sprintf('<div><div class="my-1">%s</div><div class="my-1">%s</div><div class="my-1">%s</div></div>%s',
|
||||
$tools,
|
||||
$table,
|
||||
$messageSent,
|
||||
$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>
|
|
@ -1,108 +0,0 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
|
@ -21,15 +21,14 @@ use Cake\Core\Exception\MissingPluginException;
|
|||
use Cake\Error\Middleware\ErrorHandlerMiddleware;
|
||||
use Cake\Http\BaseApplication;
|
||||
use Cake\Http\MiddlewareQueue;
|
||||
use Cake\Http\Middleware\BodyParserMiddleware;
|
||||
use Cake\Routing\Middleware\AssetMiddleware;
|
||||
use Cake\Routing\Middleware\RoutingMiddleware;
|
||||
use Authentication\AuthenticationService;
|
||||
use Authentication\AuthenticationServiceInterface;
|
||||
use Authentication\AuthenticationServiceProviderInterface;
|
||||
use Authentication\Middleware\AuthenticationMiddleware;
|
||||
use Cake\Http\Middleware\BodyParserMiddleware;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Application setup class.
|
||||
*
|
||||
|
|
|
@ -115,6 +115,10 @@ class AppController extends Controller
|
|||
$this->Security->setConfig('validatePost', false);
|
||||
}
|
||||
$this->Security->setConfig('unlockedActions', ['index']);
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
$this->Security->setConfig('unlockedActions', [$this->request->getParam('action')]);
|
||||
$this->Security->setConfig('validatePost', false);
|
||||
}
|
||||
|
||||
$this->ACL->checkAccess();
|
||||
$this->set('menu', $this->ACL->getMenu());
|
||||
|
|
|
@ -158,8 +158,8 @@ class BroodsController extends AppController
|
|||
|
||||
public function interconnectTools()
|
||||
{
|
||||
$this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
|
||||
$processor = $this->requestProcessor->getProcessor('Brood', 'ToolInterconnection');
|
||||
$this->InboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
|
||||
$processor = $this->InboxProcessors->getProcessor('Brood', 'ToolInterconnection');
|
||||
$data = [
|
||||
'origin' => '127.0.0.1',
|
||||
'comment' => 'Test comment',
|
||||
|
|
|
@ -650,18 +650,56 @@ 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 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
|
||||
]
|
||||
]
|
||||
],
|
||||
|
|
|
@ -7,6 +7,8 @@ use Cake\Error\Debugger;
|
|||
use Cake\Utility\Hash;
|
||||
use Cake\Utility\Inflector;
|
||||
use Cake\View\ViewBuilder;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use Cake\Http\Exception\NotFoundException;
|
||||
|
||||
class CRUDComponent extends Component
|
||||
{
|
||||
|
@ -340,33 +342,117 @@ class CRUDComponent extends Component
|
|||
$this->Controller->set('entity', $data);
|
||||
}
|
||||
|
||||
public function delete(int $id): void
|
||||
public function delete($id=false): void
|
||||
{
|
||||
if (empty($id)) {
|
||||
throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
|
||||
}
|
||||
$data = $this->Table->get($id);
|
||||
if ($this->request->is('post') || $this->request->is('delete')) {
|
||||
if ($this->Table->delete($data)) {
|
||||
$message = __('{0} deleted.', $this->ObjectAlias);
|
||||
if ($this->Controller->ParamHandler->isRest()) {
|
||||
$this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
|
||||
} else if ($this->Controller->ParamHandler->isAjax()) {
|
||||
$this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'delete', $data, $message);
|
||||
} else {
|
||||
$this->Controller->Flash->success($message);
|
||||
$this->Controller->redirect($this->Controller->referer());
|
||||
if ($this->request->is('get')) {
|
||||
if(!empty($id)) {
|
||||
$data = $this->Table->get($id);
|
||||
$this->Controller->set('id', $data['id']);
|
||||
$this->Controller->set('data', $data);
|
||||
$this->Controller->set('bulkEnabled', false);
|
||||
} else {
|
||||
$this->Controller->set('bulkEnabled', true);
|
||||
}
|
||||
} else if ($this->request->is('post') || $this->request->is('delete')) {
|
||||
$ids = $this->getIdsOrFail($id);
|
||||
$isBulk = count($ids) > 1;
|
||||
$bulkSuccesses = 0;
|
||||
foreach ($ids as $id) {
|
||||
$data = $this->Table->get($id);
|
||||
$success = $this->Table->delete($data);
|
||||
$success = true;
|
||||
if ($success) {
|
||||
$bulkSuccesses++;
|
||||
}
|
||||
}
|
||||
$message = $this->getMessageBasedOnResult(
|
||||
$bulkSuccesses == count($ids),
|
||||
$isBulk,
|
||||
__('{0} deleted.', $this->ObjectAlias),
|
||||
__('All {0} have been deleted.', Inflector::pluralize($this->ObjectAlias)),
|
||||
__('Could not delete {0}.', $this->ObjectAlias),
|
||||
__('{0} / {1} {2} have been deleted.',
|
||||
$bulkSuccesses,
|
||||
count($ids),
|
||||
Inflector::pluralize($this->ObjectAlias)
|
||||
)
|
||||
);
|
||||
$this->setResponseForController('delete', $bulkSuccesses, $message, $data);
|
||||
}
|
||||
$this->Controller->set('metaGroup', 'ContactDB');
|
||||
$this->Controller->set('scope', 'users');
|
||||
$this->Controller->set('id', $data['id']);
|
||||
$this->Controller->set('data', $data);
|
||||
$this->Controller->viewBuilder()->setLayout('ajax');
|
||||
$this->Controller->render('/genericTemplates/delete');
|
||||
}
|
||||
|
||||
public function setResponseForController($action, $success, $message, $data=[], $errors=null)
|
||||
{
|
||||
if ($success) {
|
||||
if ($this->Controller->ParamHandler->isRest()) {
|
||||
$this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
|
||||
} elseif ($this->Controller->ParamHandler->isAjax()) {
|
||||
$this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxSuccessResponse($this->ObjectAlias, $action, $data, $message);
|
||||
} else {
|
||||
$this->Controller->Flash->success($message);
|
||||
$this->Controller->redirect($this->Controller->referer());
|
||||
}
|
||||
} else {
|
||||
if ($this->Controller->ParamHandler->isRest()) {
|
||||
$this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
|
||||
} elseif ($this->Controller->ParamHandler->isAjax()) {
|
||||
$this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxFailResponse($this->ObjectAlias, $action, $data, $message, !is_null($errors) ? $errors : $data->getErrors());
|
||||
} else {
|
||||
$this->Controller->Flash->error($message);
|
||||
$this->Controller->redirect($this->Controller->referer());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getMessageBasedOnResult($isSuccess, $isBulk, $messageSingleSuccess, $messageBulkSuccess, $messageSingleFailure, $messageBulkFailure)
|
||||
{
|
||||
if ($isSuccess) {
|
||||
$message = $isBulk ? $messageBulkSuccess : $messageSingleSuccess;
|
||||
} else {
|
||||
$message = $isBulk ? $messageBulkFailure : $messageSingleFailure;
|
||||
}
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* getIdsOrFail
|
||||
*
|
||||
* @param mixed $id
|
||||
* @return Array The ID converted to a list or the list of provided IDs from the request
|
||||
* @throws NotFoundException when no ID could be found
|
||||
*/
|
||||
public function getIdsOrFail($id=false): Array
|
||||
{
|
||||
$params = $this->Controller->ParamHandler->harvestParams(['ids']);
|
||||
if (!empty($params['ids'])) {
|
||||
$params['ids'] = json_decode($params['ids']);
|
||||
}
|
||||
$ids = [];
|
||||
if (empty($id)) {
|
||||
if (empty($params['ids'])) {
|
||||
throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
|
||||
}
|
||||
$ids = $params['ids'];
|
||||
} else {
|
||||
$id = $this->getInteger($id);
|
||||
if (!is_null($id)) {
|
||||
$ids = [$id];
|
||||
} else {
|
||||
throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
|
||||
}
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
private function getInteger($value)
|
||||
{
|
||||
return is_numeric($value) ? intval($value) : null;
|
||||
}
|
||||
|
||||
protected function massageFilters(array $params): array
|
||||
{
|
||||
$massagedFilters = [
|
||||
|
|
|
@ -6,6 +6,7 @@ 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;
|
||||
|
@ -32,7 +33,6 @@ class InboxController extends AppController
|
|||
'contextFilters' => [
|
||||
'fields' => [
|
||||
'scope',
|
||||
'action',
|
||||
]
|
||||
],
|
||||
'contain' => ['Users']
|
||||
|
@ -57,17 +57,46 @@ class InboxController extends AppController
|
|||
}
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
public function delete($id=false)
|
||||
{
|
||||
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);
|
||||
if ($this->request->is('post')) { // cannot rely on CRUD's delete as inbox's processor discard function is responsible to handle their messages
|
||||
$ids = $this->CRUD->getIdsOrFail($id);
|
||||
$discardSuccesses = 0;
|
||||
$discardResults = [];
|
||||
$discardErrors = [];
|
||||
foreach ($ids as $id) {
|
||||
$request = $this->Inbox->get($id, ['contain' => ['Users' => ['Individuals' => ['Alignments' => 'Organisations']]]]);
|
||||
$this->inboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
|
||||
$processor = $this->inboxProcessors->getProcessor($request->scope, $request->action);
|
||||
$discardResult = $processor->discard($id, $request);
|
||||
$discardResults[] = $discardResult;
|
||||
if ($discardResult['success']) {
|
||||
$discardSuccesses++;
|
||||
} else {
|
||||
$discardErrors[] = $discardResult;
|
||||
}
|
||||
}
|
||||
if (count($ids) == 1) {
|
||||
return $processor->genHTTPReply($this, $discardResult);
|
||||
} else {
|
||||
$success = $discardSuccesses == count($ids);
|
||||
$message = __('{0} {1} have been discarded.',
|
||||
$discardSuccesses == count($ids) ? __('All') : sprintf('%s / %s', $discardSuccesses, count($ids)),
|
||||
sprintf('%s %s', Inflector::singularize($this->Inbox->getAlias()), __('messages'))
|
||||
);
|
||||
$this->CRUD->setResponseForController('delete', $success, $message, $discardResults, $discardResults);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
if (!empty($responsePayload)) {
|
||||
return $responsePayload;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->set('deletionTitle', __('Discard request'));
|
||||
$this->set('deletionText', __('Are you sure you want to discard request #{0}?', $id));
|
||||
if (!empty($id)) {
|
||||
$this->set('deletionText', __('Are you sure you want to discard request #{0}?', $id));
|
||||
} else {
|
||||
$this->set('deletionText', __('Are you sure you want to discard the selected requests?'));
|
||||
}
|
||||
$this->set('deletionConfirm', __('Discard'));
|
||||
$this->CRUD->delete($id);
|
||||
$responsePayload = $this->CRUD->getResponsePayload();
|
||||
|
@ -78,54 +107,78 @@ class InboxController extends AppController
|
|||
|
||||
public function process($id)
|
||||
{
|
||||
$request = $this->Inbox->get($id);
|
||||
$request = $this->Inbox->get($id, ['contain' => ['Users' => ['Individuals' => ['Alignments' => 'Organisations']]]]);
|
||||
$scope = $request->scope;
|
||||
$action = $request->action;
|
||||
$this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
|
||||
$processor = $this->requestProcessor->getProcessor($request->scope, $request->action);
|
||||
$this->InboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
|
||||
if ($scope == 'LocalTool') {
|
||||
$processor = $this->InboxProcessors->getLocalToolProcessor($action, $request->local_tool_name);
|
||||
} else {
|
||||
$processor = $this->InboxProcessors->getProcessor($scope, $action);
|
||||
}
|
||||
if ($this->request->is('post')) {
|
||||
$processResult = $processor->process($id, $this->request->getData());
|
||||
$processResult = $processor->process($id, $this->request->getData(), $request);
|
||||
return $processor->genHTTPReply($this, $processResult);
|
||||
} else {
|
||||
$renderedView = $processor->render($request);
|
||||
$renderedView = $processor->render($request, $this->request);
|
||||
return $this->response->withStringBody($renderedView);
|
||||
}
|
||||
}
|
||||
|
||||
public function listProcessors()
|
||||
{
|
||||
$this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
|
||||
$requestProcessors = $this->requestProcessor->listProcessors();
|
||||
$this->inboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
|
||||
$processors = $this->inboxProcessors->listProcessors();
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
return $this->RestResponse->viewData($requestProcessors, 'json');
|
||||
return $this->RestResponse->viewData($processors, 'json');
|
||||
}
|
||||
$data = [];
|
||||
foreach ($requestProcessors as $scope => $processors) {
|
||||
foreach ($processors as $processor) {
|
||||
foreach ($processors as $scope => $scopedProcessors) {
|
||||
foreach ($scopedProcessors as $processor) {
|
||||
$data[] = [
|
||||
'enabled' => $processor->enabled,
|
||||
'scope' => $scope,
|
||||
'action' => $processor->action
|
||||
'action' => $processor->action,
|
||||
'description' => isset($processor->getDescription) ? $processor->getDescription() : null,
|
||||
'notice' => $processor->notice ?? null,
|
||||
'error' => $processor->error ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
$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');
|
||||
}
|
||||
|
||||
public function createEntry($scope, $action)
|
||||
{
|
||||
if (!$this->request->is('post')) {
|
||||
throw new MethodNotAllowedException(__('Only POST method is accepted'));
|
||||
}
|
||||
$entryData = [
|
||||
'origin' => $this->request->clientIp(),
|
||||
'user_id' => $this->ACL->getUser()['id'],
|
||||
];
|
||||
$entryData['data'] = $this->request->getData() ?? [];
|
||||
$this->inboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
|
||||
if ($scope == 'LocalTool') {
|
||||
$this->validateLocalToolRequestEntry($entryData);
|
||||
$entryData['origin'] = $entryData['data']['cerebrateURL'];
|
||||
$processor = $this->inboxProcessors->getLocalToolProcessor($action, $entryData['data']['connectorName']);
|
||||
$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()), 'createEntry', [], $message, $errors);
|
||||
}
|
||||
} else {
|
||||
$processor = $this->inboxProcessors->getProcessor($scope, $action);
|
||||
}
|
||||
$creationResult = $this->inboxProcessors->createInboxEntry($processor, $entryData);
|
||||
return $processor->genHTTPReply($this, $creationResult);
|
||||
}
|
||||
|
||||
private function validateLocalToolRequestEntry($entryData)
|
||||
{
|
||||
if (empty($entryData['data']['connectorName']) || empty($entryData['data']['cerebrateURL'])) {
|
||||
throw new MethodNotAllowedException(__('Could not create entry. Tool name or URL is missing'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,11 +186,9 @@ class LocalToolsController extends AppController
|
|||
},
|
||||
'afterFind' => function($data) {
|
||||
foreach ($data as $connector) {
|
||||
$connector = [
|
||||
'id' => $connector['id'],
|
||||
'name' => $connector['name'],
|
||||
'connector' => $connector['connector']
|
||||
];
|
||||
$connectorById = $this->LocalTools->getConnectorByConnectionId($connector['id']);
|
||||
$className = array_keys($connectorById)[0];
|
||||
$connector['connectorName'] = $className;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
@ -222,18 +220,37 @@ class LocalToolsController extends AppController
|
|||
'cerebrate_id' => $cerebrate_id,
|
||||
'remote_tool_id' => $remote_tool_id
|
||||
];
|
||||
$this->loadModel('Broods');
|
||||
$remoteCerebrate = $this->Broods->find()->where(['id' => $params['cerebrate_id']])->first();
|
||||
if ($this->request->is(['post', 'put'])) {
|
||||
$postParams = $this->ParamHandler->harvestParams(['local_tool_id']);
|
||||
if (empty($postParams['local_tool_id'])) {
|
||||
throw new MethodNotAllowedException(__('No local tool ID supplied.'));
|
||||
}
|
||||
$params['local_tool_id'] = $postParams['local_tool_id'];
|
||||
$result = $this->LocalTools->encodeConnection($params);
|
||||
// Send message to remote inbox
|
||||
debug($result);
|
||||
$encodingResult = $this->LocalTools->encodeConnection($params);
|
||||
$inboxResult = $encodingResult['inboxResult'];
|
||||
if ($inboxResult['success']) {
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
$response = $this->RestResponse->viewData($inboxResult, 'json');
|
||||
} else if ($this->ParamHandler->isAjax()) {
|
||||
$response = $this->RestResponse->ajaxSuccessResponse('LocalTool', 'connectionRequest', [], $inboxResult['message']);
|
||||
} else {
|
||||
$this->Flash->success($inboxResult['message']);
|
||||
$response = $this->redirect(['action' => 'broodTools', $cerebrate_id]);
|
||||
}
|
||||
} else {
|
||||
if ($this->ParamHandler->isRest()) {
|
||||
$response = $this->RestResponse->viewData($inboxResult, 'json');
|
||||
} else if ($this->ParamHandler->isAjax()) {
|
||||
$response = $this->RestResponse->ajaxFailResponse('LocalTool', 'connectionRequest', [], $inboxResult['message'], $inboxResult['errors']);
|
||||
} else {
|
||||
$this->Flash->error($inboxResult['message']);
|
||||
$response = $this->redirect($this->referer());
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
} else {
|
||||
$this->loadModel('Broods');
|
||||
$remoteCerebrate = $this->Broods->find()->where(['id' => $params['cerebrate_id']])->first();
|
||||
$remoteTool = $this->LocalTools->getRemoteToolById($params);
|
||||
$local_tools = $this->LocalTools->encodeConnectionChoice($params);
|
||||
if (empty($local_tools)) {
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
<?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=false)
|
||||
{
|
||||
$this->set('deletionTitle', __('Confirm message deletion'));
|
||||
if (!empty($id)) {
|
||||
$this->set('deletionText', __('Are you sure you want to delete message #{0}?', $id));
|
||||
} else {
|
||||
$this->set('deletionText', __('Are you sure you want to delete the selected messages?'));
|
||||
}
|
||||
$this->set('deletionConfirm', __('Delete'));
|
||||
$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);
|
||||
}
|
||||
}
|
|
@ -138,8 +138,8 @@ class UsersController extends AppController
|
|||
|
||||
public function register()
|
||||
{
|
||||
$this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
|
||||
$processor = $this->requestProcessor->getProcessor('User', 'Registration');
|
||||
$this->InboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
|
||||
$processor = $this->InboxProcessors->getProcessor('User', 'Registration');
|
||||
$data = [
|
||||
'origin' => '127.0.0.1',
|
||||
'comment' => 'Hi there!, please create an account',
|
||||
|
|
|
@ -7,6 +7,7 @@ class CommonConnectorTools
|
|||
{
|
||||
public $description = '';
|
||||
public $name = '';
|
||||
public $connectorName = '';
|
||||
public $exposedFunctions = [
|
||||
'diagnostics'
|
||||
];
|
||||
|
@ -15,6 +16,9 @@ class CommonConnectorTools
|
|||
const STATE_INITIAL = 'Request issued';
|
||||
const STATE_ACCEPT = 'Request accepted';
|
||||
const STATE_CONNECTED = 'Connected';
|
||||
const STATE_SENDING_ERROR = 'Error while sending request';
|
||||
const STATE_CANCELLED = 'Request cancelled';
|
||||
const STATE_DECLINED = 'Request declined by remote';
|
||||
|
||||
public function addExposedFunction(string $functionName): void
|
||||
{
|
||||
|
@ -100,6 +104,7 @@ class CommonConnectorTools
|
|||
|
||||
public function finaliseConnectionWrapper(array $params): bool
|
||||
{
|
||||
$result = $this->finaliseConnection($params);
|
||||
$this->remoteToolConnectionStatus($params, self::STATE_CONNECTED);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ class MispConnector extends CommonConnectorTools
|
|||
{
|
||||
public $description = 'MISP connector, handling diagnostics, organisation and sharing group management of your instance. Synchronisation requests can also be managed through the connector.';
|
||||
|
||||
public $connectorName = 'MispConnector';
|
||||
public $name = 'MISP';
|
||||
|
||||
public $exposedFunctions = [
|
||||
|
@ -111,18 +112,48 @@ class MispConnector extends CommonConnectorTools
|
|||
}
|
||||
}
|
||||
|
||||
public function health(Object $connection): array
|
||||
private function genHTTPClient(Object $connection, array $options=[]): Object
|
||||
{
|
||||
$settings = json_decode($connection->settings, true);
|
||||
$http = new Client();
|
||||
$defaultOptions = [
|
||||
'headers' => [
|
||||
'Authorization' => $settings['authkey'],
|
||||
],
|
||||
];
|
||||
if (empty($options['type'])) {
|
||||
$options['type'] = 'json';
|
||||
}
|
||||
if (!empty($settings['skip_ssl'])) {
|
||||
$options['ssl_verify_peer'] = false;
|
||||
$options['ssl_verify_host'] = false;
|
||||
$options['ssl_verify_peer_name'] = false;
|
||||
$options['ssl_allow_self_signed'] = true;
|
||||
}
|
||||
$options = array_merge($defaultOptions, $options);
|
||||
$http = new Client($options);
|
||||
return $http;
|
||||
}
|
||||
|
||||
public function HTTPClientGET(String $relativeURL, Object $connection, array $data=[], array $options=[]): Object
|
||||
{
|
||||
$settings = json_decode($connection->settings, true);
|
||||
$http = $this->genHTTPClient($connection, $options);
|
||||
$url = sprintf('%s%s', $settings['url'], $relativeURL);
|
||||
return $http->get($url, $data, $options);
|
||||
}
|
||||
|
||||
public function HTTPClientPOST(String $relativeURL, Object $connection, $data, array $options=[]): Object
|
||||
{
|
||||
$settings = json_decode($connection->settings, true);
|
||||
$http = $this->genHTTPClient($connection, $options);
|
||||
$url = sprintf('%s%s', $settings['url'], $relativeURL);
|
||||
return $http->post($url, $data, $options);
|
||||
}
|
||||
|
||||
public function health(Object $connection): array
|
||||
{
|
||||
try {
|
||||
$response = $http->post($settings['url'] . '/users/view/me.json', '{}', [
|
||||
'headers' => [
|
||||
'AUTHORIZATION' => $settings['authkey'],
|
||||
'Accept' => 'Application/json',
|
||||
'Content-type' => 'Application/json'
|
||||
]
|
||||
]);
|
||||
$response = $this->HTTPClientPOST('/users/view/me.json', $connection, '{}');
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'status' => 0,
|
||||
|
@ -152,8 +183,6 @@ class MispConnector extends CommonConnectorTools
|
|||
if (empty($params['connection'])) {
|
||||
throw new NotFoundException(__('No connection object received.'));
|
||||
}
|
||||
$settings = json_decode($params['connection']->settings, true);
|
||||
$http = new Client();
|
||||
if (!empty($params['sort'])) {
|
||||
$list = explode('.', $params['sort']);
|
||||
$params['sort'] = end($list);
|
||||
|
@ -162,13 +191,7 @@ class MispConnector extends CommonConnectorTools
|
|||
$params['limit'] = 50;
|
||||
}
|
||||
$url = $this->urlAppendParams($url, $params);
|
||||
$response = $http->get($settings['url'] . $url, false, [
|
||||
'headers' => [
|
||||
'AUTHORIZATION' => $settings['authkey'],
|
||||
'Accept' => 'application/json',
|
||||
'Content-type' => 'application/json'
|
||||
]
|
||||
]);
|
||||
$response = $this->HTTPClientGET($url, $params['connection']);
|
||||
if ($response->isOk()) {
|
||||
return $response;
|
||||
} else {
|
||||
|
@ -184,20 +207,12 @@ class MispConnector extends CommonConnectorTools
|
|||
if (empty($params['connection'])) {
|
||||
throw new NotFoundException(__('No connection object received.'));
|
||||
}
|
||||
$settings = json_decode($params['connection']->settings, true);
|
||||
$http = new Client();
|
||||
$url = $this->urlAppendParams($url, $params);
|
||||
$response = $http->post($settings['url'] . $url, json_encode($params['body']), [
|
||||
'headers' => [
|
||||
'AUTHORIZATION' => $settings['authkey'],
|
||||
'Accept' => 'application/json'
|
||||
],
|
||||
'type' => 'json'
|
||||
]);
|
||||
$response = $this->HTTPClientPOST($url, $params['connection'], json_encode($params['body']));
|
||||
if ($response->isOk()) {
|
||||
return $response;
|
||||
} else {
|
||||
throw new NotFoundException(__('Could not post to the requested resource.'));
|
||||
throw new NotFoundException(__('Could not post to the requested resource. Remote returned:') . PHP_EOL . $response->getStringBody());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -758,11 +773,12 @@ class MispConnector extends CommonConnectorTools
|
|||
{
|
||||
$params['connection_settings'] = json_decode($params['connection']['settings'], true);
|
||||
$params['misp_organisation'] = $this->getSetOrg($params);
|
||||
$params['sync_user'] = $this->createSyncUser($params);
|
||||
$params['sync_user'] = $this->createSyncUser($params, true);
|
||||
return [
|
||||
'email' => $params['sync_user']['email'],
|
||||
'user_id' => $params['sync_user']['id'],
|
||||
'authkey' => $params['sync_user']['authkey'],
|
||||
'url' => $params['connection_settings']['url']
|
||||
'url' => $params['connection_settings']['url'],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -771,28 +787,35 @@ class MispConnector extends CommonConnectorTools
|
|||
$params['sync_user_enabled'] = true;
|
||||
$params['connection_settings'] = json_decode($params['connection']['settings'], true);
|
||||
$params['misp_organisation'] = $this->getSetOrg($params);
|
||||
$params['sync_user'] = $this->createSyncUser($params);
|
||||
$params['sync_connection'] = $this->addServer([
|
||||
'authkey' => $params['remote_tool']['authkey'],
|
||||
'url' => $params['remote_tool']['url'],
|
||||
'name' => $params['remote_tool']['name'],
|
||||
$params['sync_user'] = $this->createSyncUser($params, false);
|
||||
$serverParams = $params;
|
||||
$serverParams['body'] = [
|
||||
'authkey' => $params['remote_tool_data']['authkey'],
|
||||
'url' => $params['remote_tool_data']['url'],
|
||||
'name' => !empty($params['remote_tool_data']['tool_name']) ? $params['remote_tool_data']['tool_name'] : sprintf('MISP for %s', $params['remote_tool_data']['url']),
|
||||
'remote_org_id' => $params['misp_organisation']['id']
|
||||
]);
|
||||
];
|
||||
$params['sync_connection'] = $this->addServer($serverParams);
|
||||
return [
|
||||
'email' => $params['sync_user']['email'],
|
||||
'authkey' => $params['sync_user']['authkey'],
|
||||
'url' => $params['connection_settings']['url']
|
||||
'url' => $params['connection_settings']['url'],
|
||||
'reflected_user_id' => $params['remote_tool_data']['user_id'] // request initiator Cerebrate to enable the MISP user
|
||||
];
|
||||
}
|
||||
|
||||
public function finaliseConnection(array $params): bool
|
||||
{
|
||||
$params['sync_connection'] = $this->addServer([
|
||||
'authkey' => $params['remote_tool']['authkey'],
|
||||
'url' => $params['remote_tool']['url'],
|
||||
'name' => $params['remote_tool']['name'],
|
||||
$params['misp_organisation'] = $this->getSetOrg($params);
|
||||
$user = $this->enableUser($params, intval($params['remote_tool_data']['reflected_user_id']));
|
||||
$serverParams = $params;
|
||||
$serverParams['body'] = [
|
||||
'authkey' => $params['remote_tool_data']['authkey'],
|
||||
'url' => $params['remote_tool_data']['url'],
|
||||
'name' => !empty($params['remote_tool_data']['tool_name']) ? $params['remote_tool_data']['tool_name'] : sprintf('MISP for %s', $params['remote_tool_data']['url']),
|
||||
'remote_org_id' => $params['misp_organisation']['id']
|
||||
]);
|
||||
];
|
||||
$params['sync_connection'] = $this->addServer($serverParams);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -804,6 +827,7 @@ class MispConnector extends CommonConnectorTools
|
|||
$organisation = $response->getJson()['Organisation'];
|
||||
if (!$organisation['local']) {
|
||||
$organisation['local'] = 1;
|
||||
$params['body'] = $organisation;
|
||||
$response = $this->postData('/admin/organisations/edit/' . $organisation['id'], $params);
|
||||
if (!$response->isOk()) {
|
||||
throw new MethodNotAllowedException(__('Could not update the organisation in MISP.'));
|
||||
|
@ -825,27 +849,36 @@ class MispConnector extends CommonConnectorTools
|
|||
return $organisation;
|
||||
}
|
||||
|
||||
private function createSyncUser(array $params): array
|
||||
private function createSyncUser(array $params, $disabled=true): array
|
||||
{
|
||||
$params['softError'] = 1;
|
||||
$user = [
|
||||
'email' => 'sync_%s@' . parse_url($params['remote_cerebrate']['url'])['host'],
|
||||
'org_id' => $params['misp_organisation']['id'],
|
||||
'role_id' => empty($params['connection_settings']['role_id']) ? 5 : $params['connection_settings']['role_id'],
|
||||
'disabled' => 1,
|
||||
'disabled' => $disabled,
|
||||
'change_pw' => 0,
|
||||
'termsaccepted' => 1
|
||||
];
|
||||
return $this->createUser($user, $params);
|
||||
}
|
||||
|
||||
private function enableUser(array $params, int $userID): array
|
||||
{
|
||||
$params['softError'] = 1;
|
||||
$user = [
|
||||
'disabled' => false,
|
||||
];
|
||||
return $this->updateUser($userID, $user, $params);
|
||||
}
|
||||
|
||||
private function addServer(array $params): array
|
||||
{
|
||||
if (
|
||||
empty($params['authkey']) ||
|
||||
empty($params['url']) ||
|
||||
empty($params['remote_org_id']) ||
|
||||
empty($params['name'])
|
||||
empty($params['body']['authkey']) ||
|
||||
empty($params['body']['url']) ||
|
||||
empty($params['body']['remote_org_id']) ||
|
||||
empty($params['body']['name'])
|
||||
) {
|
||||
throw new MethodNotAllowedException(__('Required data missing from the sync connection object. The following fields are required: [name, url, authkey, org_id].'));
|
||||
}
|
||||
|
@ -871,6 +904,16 @@ class MispConnector extends CommonConnectorTools
|
|||
}
|
||||
return $response->getJson()['User'];
|
||||
}
|
||||
|
||||
private function updateUser(int $userID, array $user, array $params): array
|
||||
{
|
||||
$params['body'] = $user;
|
||||
$response = $this->postData(sprintf('/admin/users/edit/%s', $userID), $params);
|
||||
if (!$response->isOk()) {
|
||||
throw new MethodNotAllowedException(__('Could not edit the user in MISP.'));
|
||||
}
|
||||
return $response->getJson()['User'];
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -7,5 +7,14 @@ use Cake\ORM\Entity;
|
|||
|
||||
class Inbox extends AppModel
|
||||
{
|
||||
protected $_virtual = ['local_tool_connector_name'];
|
||||
|
||||
protected function _getLocalToolConnectorName()
|
||||
{
|
||||
$localConnectorName = null;
|
||||
if (!empty($this->data) && !empty($this->data['connectorName'])) {
|
||||
$localConnectorName = $this->data['connectorName'];
|
||||
}
|
||||
return $localConnectorName;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,10 @@ namespace App\Model\Table;
|
|||
use App\Model\Table\AppTable;
|
||||
use Cake\ORM\Table;
|
||||
use Cake\Validation\Validator;
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Http\Client;
|
||||
use Cake\Http\Client\Response;
|
||||
use Cake\Http\Exception\NotFoundException;
|
||||
use Cake\ORM\TableRegistry;
|
||||
use Cake\Error\Debugger;
|
||||
|
||||
|
@ -26,18 +29,40 @@ class BroodsTable extends AppTable
|
|||
return $validator;
|
||||
}
|
||||
|
||||
public function genHTTPClient(Object $brood, array $options=[]): Object
|
||||
{
|
||||
$defaultOptions = [
|
||||
'headers' => [
|
||||
'Authorization' => $brood->authkey,
|
||||
],
|
||||
];
|
||||
if (empty($options['type'])) {
|
||||
$options['type'] = 'json';
|
||||
}
|
||||
$options = array_merge($defaultOptions, $options);
|
||||
$http = new Client($options);
|
||||
return $http;
|
||||
}
|
||||
|
||||
public function HTTPClientGET(String $relativeURL, Object $brood, array $data=[], array $options=[]): Object
|
||||
{
|
||||
$http = $this->genHTTPClient($brood, $options);
|
||||
$url = sprintf('%s%s', $brood->url, $relativeURL);
|
||||
return $http->get($url, $data, $options);
|
||||
}
|
||||
|
||||
public function HTTPClientPOST(String $relativeURL, Object $brood, $data, array $options=[]): Object
|
||||
{
|
||||
$http = $this->genHTTPClient($brood, $options);
|
||||
$url = sprintf('%s%s', $brood->url, $relativeURL);
|
||||
return $http->post($url, $data, $options);
|
||||
}
|
||||
|
||||
public function queryStatus($id)
|
||||
{
|
||||
$brood = $this->find()->where(['id' => $id])->first();
|
||||
$http = new Client();
|
||||
$start = microtime(true);
|
||||
$response = $http->get($brood['url'] . '/instance/status.json', [], [
|
||||
'headers' => [
|
||||
'Authorization' => $brood['authkey'],
|
||||
'Accept' => 'Application/json',
|
||||
'Content-type' => 'Application/json'
|
||||
]
|
||||
]);
|
||||
$response = $this->HTTPClientGET('/instance/status.json', $brood);
|
||||
$ping = ((int)(100 * (microtime(true) - $start)));
|
||||
$errors = [
|
||||
403 => [
|
||||
|
@ -81,15 +106,8 @@ class BroodsTable extends AppTable
|
|||
if (empty($brood)) {
|
||||
throw new NotFoundException(__('Brood not found'));
|
||||
}
|
||||
$http = new Client();
|
||||
$filterQuery = empty($filter) ? '' : '?quickFilter=' . urlencode($filter);
|
||||
$response = $http->get($brood['url'] . '/' . $scope . '/index.json' . $filterQuery , [], [
|
||||
'headers' => [
|
||||
'Authorization' => $brood['authkey'],
|
||||
'Accept' => 'Application/json',
|
||||
'Content-type' => 'Application/json'
|
||||
]
|
||||
]);
|
||||
$response = $this->HTTPClientGET(sprintf('/%s/index.json%s', $scope, $filterQuery), $brood);
|
||||
if ($response->isOk()) {
|
||||
return $response->getJson();
|
||||
} else {
|
||||
|
@ -104,14 +122,7 @@ class BroodsTable extends AppTable
|
|||
if (empty($brood)) {
|
||||
throw new NotFoundException(__('Brood not found'));
|
||||
}
|
||||
$http = new Client();
|
||||
$response = $http->get($brood['url'] . '/' . $scope . '/view/' . $org_id . '/index.json' , [], [
|
||||
'headers' => [
|
||||
'Authorization' => $brood['authkey'],
|
||||
'Accept' => 'Application/json',
|
||||
'Content-type' => 'Application/json'
|
||||
]
|
||||
]);
|
||||
$response = $this->HTTPClientGET(sprintf('/%s/view/%s/index.json', $scope, $org_id), $brood);
|
||||
if ($response->isOk()) {
|
||||
$org = $response->getJson();
|
||||
$this->Organisation = TableRegistry::getTableLocator()->get('Organisations');
|
||||
|
@ -129,14 +140,7 @@ class BroodsTable extends AppTable
|
|||
if (empty($brood)) {
|
||||
throw new NotFoundException(__('Brood not found'));
|
||||
}
|
||||
$http = new Client();
|
||||
$response = $http->get($brood['url'] . '/organisations/view/' . $org_id . '/index.json' , [], [
|
||||
'headers' => [
|
||||
'Authorization' => $brood['authkey'],
|
||||
'Accept' => 'Application/json',
|
||||
'Content-type' => 'Application/json'
|
||||
]
|
||||
]);
|
||||
$response = $this->HTTPClientGET(sprintf('/organisations/view/%s/index.json', $org_id), $brood);
|
||||
if ($response->isOk()) {
|
||||
$org = $response->getJson();
|
||||
$this->Organisation = TableRegistry::getTableLocator()->get('Organisations');
|
||||
|
@ -154,14 +158,7 @@ class BroodsTable extends AppTable
|
|||
if (empty($brood)) {
|
||||
throw new NotFoundException(__('Brood not found'));
|
||||
}
|
||||
$http = new Client();
|
||||
$response = $http->get($brood['url'] . '/individuals/view/' . $individual_id . '/index.json' , [], [
|
||||
'headers' => [
|
||||
'Authorization' => $brood['authkey'],
|
||||
'Accept' => 'Application/json',
|
||||
'Content-type' => 'Application/json'
|
||||
]
|
||||
]);
|
||||
$response = $this->HTTPClientGET(sprintf('/individuals/view/%s/index.json', $individual_id), $brood);
|
||||
if ($response->isOk()) {
|
||||
$org = $response->getJson();
|
||||
$this->Individual = TableRegistry::getTableLocator()->get('Individual');
|
||||
|
@ -179,17 +176,161 @@ class BroodsTable extends AppTable
|
|||
if (empty($brood)) {
|
||||
throw new NotFoundException(__('Brood not found'));
|
||||
}
|
||||
$http = new Client();
|
||||
$response = $http->get($brood['url'] . '/localTools/exposedTools' , [], [
|
||||
'headers' => [
|
||||
'Authorization' => $brood['authkey']
|
||||
],
|
||||
'type' => 'json'
|
||||
]);
|
||||
$response = $this->HTTPClientGET('/localTools/exposedTools', $brood);
|
||||
if ($response->isOk()) {
|
||||
return $response->getJson();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function sendRequest($brood, $urlPath, $methodPost = true, $data = []): Response
|
||||
{
|
||||
if ($methodPost) {
|
||||
$response = $this->HTTPClientPOST($urlPath, $brood, json_encode($data));
|
||||
} else {
|
||||
$response = $this->HTTPClientGET($urlPath, $brood, $data);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function injectRequiredData($params, $data): Array
|
||||
{
|
||||
$data['connectorName'] = $params['remote_tool']['connector'];
|
||||
$data['cerebrateURL'] = Configure::read('App.fullBaseUrl');
|
||||
$data['local_tool_id'] = $params['connection']['id'];
|
||||
$data['remote_tool_id'] = $params['remote_tool']['id'];
|
||||
$data['tool_name'] = $params['connection']['name'];
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function sendLocalToolConnectionRequest($params, $data): array
|
||||
{
|
||||
$url = '/inbox/createEntry/LocalTool/IncomingConnectionRequest';
|
||||
$data = $this->injectRequiredData($params, $data);
|
||||
try {
|
||||
$response = $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
|
||||
$jsonReply = $response->getJson();
|
||||
if (empty($jsonReply['success'])) {
|
||||
$jsonReply = $this->handleMessageNotCreated($params['remote_cerebrate'], $url, $data, 'LocalTool', 'IncomingConnectionRequest', $response, $params, 'STATE_INITIAL');
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
$jsonReply = $this->handleSendingFailed($params['remote_cerebrate'], $url, $data, 'LocalTool', 'IncomingConnectionRequest', $e, $params, 'STATE_INITIAL');
|
||||
}
|
||||
return $jsonReply;
|
||||
}
|
||||
|
||||
public function sendLocalToolAcceptedRequest($params, $data): array
|
||||
{
|
||||
$url = '/inbox/createEntry/LocalTool/AcceptedRequest';
|
||||
$data = $this->injectRequiredData($params, $data);
|
||||
try {
|
||||
$response = $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
|
||||
$jsonReply = $response->getJson();
|
||||
if (empty($jsonReply['success'])) {
|
||||
$jsonReply = $this->handleMessageNotCreated($params['remote_cerebrate'], $url, $data, 'LocalTool', 'AcceptedRequest', $response, $params, 'STATE_CONNECTED');
|
||||
} else {
|
||||
$this->setRemoteToolConnectionStatus($params, 'STATE_CONNECTED');
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
$jsonReply = $this->handleSendingFailed($params['remote_cerebrate'], $url, $data, 'LocalTool', 'AcceptedRequest', $e, $params, 'STATE_CONNECTED');
|
||||
}
|
||||
return $jsonReply;
|
||||
}
|
||||
|
||||
public function sendLocalToolDeclinedRequest($params, $data): array
|
||||
{
|
||||
$url = '/inbox/createEntry/LocalTool/DeclinedRequest';
|
||||
$data = $this->injectRequiredData($params, $data);
|
||||
try {
|
||||
$response = $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
|
||||
$jsonReply = $response->getJson();
|
||||
if (empty($jsonReply['success'])) {
|
||||
$jsonReply = $this->handleMessageNotCreated($params['remote_cerebrate'], $url, $data, 'LocalTool', 'AcceptedRequest', $response, $params, 'STATE_DECLINED');
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
$jsonReply = $this->handleSendingFailed($params['remote_cerebrate'], $url, $data, 'LocalTool', 'AcceptedRequest', $e, $params, 'STATE_DECLINED');
|
||||
}
|
||||
return $jsonReply;
|
||||
}
|
||||
|
||||
/**
|
||||
* handleSendingFailed - Handle the case if the request could not be sent or if the remote rejected the connection request
|
||||
*
|
||||
* @param Object $response
|
||||
* @return array
|
||||
*/
|
||||
private function handleSendingFailed($brood, $url, $data, $model, $action, $e, $params, $next_connector_state): array
|
||||
{
|
||||
$connector = $params['connector'][$params['remote_tool']['connector']];
|
||||
$reason = [
|
||||
'message' => __('Failed to send message to remote cerebrate. It has been placed in the outbox.'),
|
||||
'errors' => [$e->getMessage()],
|
||||
];
|
||||
$outboxSaveResult = $this->saveErrorInOutbox($brood, $url, $data, $reasonMessage, $params, $next_connector_state);
|
||||
$connector->remoteToolConnectionStatus($params, $connector::STATE_SENDING_ERROR);
|
||||
$creationResult = [
|
||||
'success' => false,
|
||||
'message' => $reason['message'],
|
||||
'errors' => $reason['errors'],
|
||||
'placed_in_outbox' => !empty($outboxSaveResult['success']),
|
||||
];
|
||||
return $creationResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* handleMessageNotCreated - Handle the case if the request was sent but the remote brood did not save the message in the inbox
|
||||
*
|
||||
* @param Object $response
|
||||
* @return array
|
||||
*/
|
||||
private function handleMessageNotCreated($brood, $url, $data, $model, $action, $response, $params, $next_connector_state): array
|
||||
{
|
||||
$connector = $params['connector'][$params['remote_tool']['connector']];
|
||||
$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],
|
||||
];
|
||||
$outboxSaveResult = $this->saveErrorInOutbox($brood, $url, $data, $reason, $model, $action, $params, $next_connector_state);
|
||||
$connector->remoteToolConnectionStatus($params, $connector::STATE_SENDING_ERROR);
|
||||
$creationResult = [
|
||||
'success' => false,
|
||||
'message' => $reason['message'],
|
||||
'errors' => $reason['errors'],
|
||||
'placed_in_outbox' => !empty($outboxSaveResult['success']),
|
||||
];
|
||||
return $creationResult;
|
||||
}
|
||||
|
||||
private function saveErrorInOutbox($brood, $url, $data, $reason, $model, $action, $params, $next_connector_state): 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'],
|
||||
'next_connector_state' => $next_connector_state,
|
||||
],
|
||||
'brood' => $brood,
|
||||
'model' => $model,
|
||||
'action' => $action,
|
||||
];
|
||||
$creationResult = $processor->create($entryData);
|
||||
return $creationResult;
|
||||
}
|
||||
|
||||
private function setRemoteToolConnectionStatus($params, String $status): void
|
||||
{
|
||||
$connector = $params['connector'][$params['remote_tool']['connector']];
|
||||
$connector->remoteToolConnectionStatus($params, constant(get_class($connector) . '::' . $status));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
<?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 MissingInboxProcessorException extends Exception
|
||||
{
|
||||
protected $_defaultCode = 404;
|
||||
}
|
||||
|
||||
class InboxProcessorsTable extends AppTable
|
||||
{
|
||||
private $processorsDirectory = ROOT . '/libraries/default/InboxProcessors';
|
||||
private $inboxProcessors;
|
||||
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->inboxProcessors[$scope])) {
|
||||
if (is_null($action)) {
|
||||
return $this->inboxProcessors[$scope];
|
||||
} else if (!empty($this->inboxProcessors[$scope]->{$action})) {
|
||||
return $this->inboxProcessors[$scope]->{$action};
|
||||
} else {
|
||||
throw new \Exception(__('Processor {0}.{1} not found', $scope, $action));
|
||||
}
|
||||
}
|
||||
throw new MissingInboxProcessorException(__('Processor not found'));
|
||||
}
|
||||
|
||||
public function getLocalToolProcessor($action, $connectorName)
|
||||
{
|
||||
$scope = "LocalTool";
|
||||
$specificScope = "{$connectorName}LocalTool";
|
||||
try { // try to get specific processor for module name or fall back to generic local tool processor
|
||||
$processor = $this->getProcessor($specificScope, $action);
|
||||
} catch (MissingInboxProcessorException $e) {
|
||||
$processor = $this->getProcessor($scope, $action);
|
||||
}
|
||||
return $processor;
|
||||
}
|
||||
|
||||
public function listProcessors($scope=null)
|
||||
{
|
||||
if (is_null($scope)) {
|
||||
return $this->inboxProcessors;
|
||||
} else {
|
||||
if (isset($this->inboxProcessors[$scope])) {
|
||||
return $this->inboxProcessors[$scope];
|
||||
} else {
|
||||
throw new MissingInboxProcessorException(__('Processors for {0} not found', $scope));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function loadProcessors()
|
||||
{
|
||||
$processorDir = new Folder($this->processorsDirectory);
|
||||
$processorFiles = $processorDir->find('.*InboxProcessor\.php', true);
|
||||
foreach ($processorFiles as $processorFile) {
|
||||
if ($processorFile == 'GenericInboxProcessor.php') {
|
||||
continue;
|
||||
}
|
||||
$processorMainClassName = str_replace('.php', '', $processorFile);
|
||||
$processorMainClassNameShort = str_replace('InboxProcessor.php', '', $processorFile);
|
||||
$processorMainClass = $this->getProcessorClass($processorDir->pwd() . DS . $processorFile, $processorMainClassName);
|
||||
if (is_object($processorMainClass)) {
|
||||
$this->inboxProcessors[$processorMainClassNameShort] = $processorMainClass;
|
||||
foreach ($this->inboxProcessors[$processorMainClassNameShort]->getRegisteredActions() as $registeredAction) {
|
||||
$scope = $this->inboxProcessors[$processorMainClassNameShort]->getScope();
|
||||
if (!empty($this->enabledProcessors[$scope][$registeredAction])) {
|
||||
$this->inboxProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = true;
|
||||
} else {
|
||||
$this->inboxProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->inboxProcessors[$processorMainClassNameShort] = new \stdClass();
|
||||
$this->inboxProcessors[$processorMainClassNameShort]->{$registeredAction} = new \stdClass();
|
||||
$this->inboxProcessors[$processorMainClassNameShort]->{$registeredAction}->action = "N/A";
|
||||
$this->inboxProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = false;
|
||||
$this->inboxProcessors[$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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* createInboxEntry
|
||||
*
|
||||
* @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 createInboxEntry($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);
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ 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');
|
||||
|
||||
|
@ -44,7 +45,7 @@ class InboxTable extends AppTable
|
|||
->notEmptyString('title')
|
||||
->notEmptyString('origin')
|
||||
->datetime('created')
|
||||
|
||||
|
||||
->requirePresence([
|
||||
'scope' => ['message' => __('The field `scope` is required')],
|
||||
'action' => ['message' => __('The field `action` is required')],
|
||||
|
@ -62,4 +63,33 @@ class InboxTable extends AppTable
|
|||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function checkUserBelongsToBroodOwnerOrg($user, $entryData) {
|
||||
$this->Broods = \Cake\ORM\TableRegistry::getTableLocator()->get('Broods');
|
||||
$this->Individuals = \Cake\ORM\TableRegistry::getTableLocator()->get('Individuals');
|
||||
$errors = [];
|
||||
$brood = $this->Broods->find()
|
||||
->where(['url' => $entryData['origin']])
|
||||
->first();
|
||||
if (empty($brood)) {
|
||||
$errors[] = __('Unkown brood `{0}`', $entryData['data']['cerebrateURL']);
|
||||
}
|
||||
|
||||
$found = false;
|
||||
foreach ($user->individual->organisations as $organisations) {
|
||||
if ($organisations->id == $brood->organisation_id) {
|
||||
$found = true;
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
$errors[] = __('User `{0}` is not part of the brood\'s organisation. Make sure `{0}` is aligned with the organisation owning the brood.', $user->individual->email);
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
public function createEntry($entryData)
|
||||
{
|
||||
$savedEntry = $this->save($entryData);
|
||||
return $savedEntry;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,6 +73,12 @@ class LocalToolsTable extends AppTable
|
|||
throw new NotFoundException(__('Invalid connector module action requested.'));
|
||||
}
|
||||
|
||||
public function getConnectorByToolName($toolName): array
|
||||
{
|
||||
$toolName = sprintf('%sConnector', ucfirst(strtolower($toolName)));
|
||||
return $this->getConnectors($toolName);
|
||||
}
|
||||
|
||||
public function getConnectors(string $name = null): array
|
||||
{
|
||||
$connectors = [];
|
||||
|
@ -222,8 +228,12 @@ class LocalToolsTable extends AppTable
|
|||
public function encodeConnection(array $params): array
|
||||
{
|
||||
$params = $this->buildConnectionParams($params);
|
||||
$result = $params['connector'][$params['remote_tool']['connector']]->initiateConnectionWrapper($params);
|
||||
return $result;
|
||||
$localResult = $params['connector'][$params['remote_tool']['connector']]->initiateConnectionWrapper($params);
|
||||
$inboxResult = $this->sendEncodedConnection($params, $localResult);
|
||||
return [
|
||||
'inboxResult' => $inboxResult,
|
||||
'localResult' => $localResult
|
||||
];
|
||||
}
|
||||
|
||||
public function buildConnectionParams(array $params): array
|
||||
|
@ -260,6 +270,13 @@ class LocalToolsTable extends AppTable
|
|||
return $local_tools;
|
||||
}
|
||||
|
||||
public function sendEncodedConnection($params, $encodedConnection)
|
||||
{
|
||||
$this->Broods = \Cake\ORM\TableRegistry::getTableLocator()->get('Broods');
|
||||
$jsonReply = $this->Broods->sendLocalToolConnectionRequest($params, $encodedConnection);
|
||||
return $jsonReply;
|
||||
}
|
||||
|
||||
public function findConnectable($local_tool): array
|
||||
{
|
||||
$connectors = $this->getInterconnectors($local_tool['connector']);
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
<?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);
|
||||
if (empty($this->outboxProcessors)) {
|
||||
$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;
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -42,12 +42,15 @@
|
|||
namespace App\View\Helper;
|
||||
|
||||
use Cake\View\Helper;
|
||||
use Cake\Utility\Hash;
|
||||
use Cake\Utility\Inflector;
|
||||
use Cake\Utility\Security;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class BootstrapHelper extends Helper
|
||||
{
|
||||
public $helpers = ['FontAwesome'];
|
||||
|
||||
public function tabs($options)
|
||||
{
|
||||
$bsTabs = new BootstrapTabs($options);
|
||||
|
@ -80,14 +83,50 @@ class BootstrapHelper extends Helper
|
|||
|
||||
public function modal($options)
|
||||
{
|
||||
$bsButton = new BoostrapModal($options);
|
||||
return $bsButton->modal();
|
||||
$bsModal = new BoostrapModal($options);
|
||||
return $bsModal->modal();
|
||||
}
|
||||
|
||||
public function card($options)
|
||||
{
|
||||
$bsCard = new BoostrapCard($options);
|
||||
return $bsCard->card();
|
||||
}
|
||||
|
||||
public function progress($options)
|
||||
{
|
||||
$bsProgress = new BoostrapProgress($options);
|
||||
return $bsProgress->progress();
|
||||
}
|
||||
|
||||
public function collapse($options, $content)
|
||||
{
|
||||
$bsCollapse = new BoostrapCollapse($options, $content, $this);
|
||||
return $bsCollapse->collapse();
|
||||
}
|
||||
|
||||
public function progressTimeline($options)
|
||||
{
|
||||
$bsProgressTimeline = new BoostrapProgressTimeline($options, $this);
|
||||
return $bsProgressTimeline->progressTimeline();
|
||||
}
|
||||
}
|
||||
|
||||
class BootstrapGeneric
|
||||
{
|
||||
public static $variants = ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'white', 'transparent'];
|
||||
public static $textClassByVariants = [
|
||||
'primary' => 'text-white',
|
||||
'secondary' => 'text-white',
|
||||
'success' => 'text-white',
|
||||
'danger' => 'text-white',
|
||||
'warning' => 'text-black',
|
||||
'info' => 'text-white',
|
||||
'light' => 'text-black',
|
||||
'dark' => 'text-white',
|
||||
'white' => 'text-black',
|
||||
'transparent' => 'text-black'
|
||||
];
|
||||
protected $allowedOptionValues = [];
|
||||
protected $options = [];
|
||||
|
||||
|
@ -146,6 +185,11 @@ class BootstrapGeneric
|
|||
'arial-hidden' => 'true'
|
||||
], '×'));
|
||||
}
|
||||
|
||||
protected static function getTextClassForVariant($variant)
|
||||
{
|
||||
return !empty(self::$textClassByVariants[$variant]) ? self::$textClassByVariants[$variant] : 'text-black';
|
||||
}
|
||||
}
|
||||
|
||||
class BootstrapTabs extends BootstrapGeneric
|
||||
|
@ -543,7 +587,7 @@ class BoostrapTable extends BootstrapGeneric {
|
|||
} else {
|
||||
$key = $field;
|
||||
}
|
||||
$cellValue = $row[$key];
|
||||
$cellValue = Hash::get($row, $key);
|
||||
$html .= $this->genCell($cellValue, $field, $row);
|
||||
}
|
||||
} else { // indexed array
|
||||
|
@ -571,7 +615,7 @@ class BoostrapTable extends BootstrapGeneric {
|
|||
|
||||
private function genCaption()
|
||||
{
|
||||
return $this->genNode('caption', [], h($this->caption));
|
||||
return !empty($this->caption) ? $this->genNode('caption', [], h($this->caption)) : '';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -730,7 +774,7 @@ class BoostrapModal extends BootstrapGeneric {
|
|||
function __construct($options) {
|
||||
$this->allowedOptionValues = [
|
||||
'size' => ['sm', 'lg', 'xl', ''],
|
||||
'type' => ['ok-only','confirm','confirm-success','confirm-warning','confirm-danger'],
|
||||
'type' => ['ok-only','confirm','confirm-success','confirm-warning','confirm-danger', 'custom'],
|
||||
'variant' => array_merge(BootstrapGeneric::$variants, ['']),
|
||||
];
|
||||
$this->processOptions($options);
|
||||
|
@ -796,7 +840,10 @@ class BoostrapModal extends BootstrapGeneric {
|
|||
|
||||
private function genFooter()
|
||||
{
|
||||
$footer = $this->openNode('div', ['class' => array_merge(['modal-footer'], $this->options['footerClass'])]);
|
||||
$footer = $this->openNode('div', [
|
||||
'class' => array_merge(['modal-footer'], $this->options['footerClass']),
|
||||
'data-custom-footer' => $this->options['type'] == 'custom'
|
||||
]);
|
||||
if (!empty($this->options['footerHtml'])) {
|
||||
$footer .= $this->options['footerHtml'];
|
||||
} else {
|
||||
|
@ -811,6 +858,8 @@ class BoostrapModal extends BootstrapGeneric {
|
|||
return $this->getFooterOkOnly();
|
||||
} else if (str_contains($this->options['type'], 'confirm')) {
|
||||
return $this->getFooterConfirm();
|
||||
} else if ($this->options['type'] == 'custom') {
|
||||
return $this->getFooterCustom();
|
||||
} else {
|
||||
return $this->getFooterOkOnly();
|
||||
}
|
||||
|
@ -849,10 +898,350 @@ class BoostrapModal extends BootstrapGeneric {
|
|||
'text' => h($this->options['confirmText']),
|
||||
'class' => 'modal-confirm-button',
|
||||
'params' => [
|
||||
'data-dismiss' => $this->options['confirmFunction'] ? '' : 'modal',
|
||||
// 'data-dismiss' => $this->options['confirmFunction'] ? '' : 'modal',
|
||||
'data-confirmFunction' => sprintf('%s', $this->options['confirmFunction'])
|
||||
]
|
||||
]))->button();
|
||||
return $buttonCancel . $buttonConfirm;
|
||||
}
|
||||
|
||||
private function getFooterCustom()
|
||||
{
|
||||
$buttons = [];
|
||||
foreach ($this->options['footerButtons'] as $buttonConfig) {
|
||||
$buttons[] = (new BoostrapButton([
|
||||
'variant' => h($buttonConfig['variant'] ?? 'primary'),
|
||||
'text' => h($buttonConfig['text']),
|
||||
'class' => 'modal-confirm-button',
|
||||
'params' => [
|
||||
'data-dismiss' => !empty($buttonConfig['clickFunction']) ? '' : 'modal',
|
||||
'data-clickFunction' => sprintf('%s', $buttonConfig['clickFunction'])
|
||||
]
|
||||
]))->button();
|
||||
}
|
||||
return implode('', $buttons);
|
||||
}
|
||||
}
|
||||
|
||||
class BoostrapCard extends BootstrapGeneric
|
||||
{
|
||||
private $defaultOptions = [
|
||||
'variant' => '',
|
||||
'headerText' => '',
|
||||
'footerText' => '',
|
||||
'bodyText' => '',
|
||||
'headerHTML' => '',
|
||||
'footerHTML' => '',
|
||||
'bodyHTML' => '',
|
||||
'headerClass' => '',
|
||||
'bodyClass' => '',
|
||||
'footerClass' => '',
|
||||
];
|
||||
|
||||
public function __construct($options)
|
||||
{
|
||||
$this->allowedOptionValues = [
|
||||
'variant' => array_merge(BootstrapGeneric::$variants, ['']),
|
||||
];
|
||||
$this->processOptions($options);
|
||||
}
|
||||
|
||||
private function processOptions($options)
|
||||
{
|
||||
$this->options = array_merge($this->defaultOptions, $options);
|
||||
$this->checkOptionValidity();
|
||||
}
|
||||
|
||||
public function card()
|
||||
{
|
||||
return $this->genCard();
|
||||
}
|
||||
|
||||
private function genCard()
|
||||
{
|
||||
$card = $this->genNode('div', [
|
||||
'class' => [
|
||||
'card',
|
||||
!empty($this->options['variant']) ? "bg-{$this->options['variant']}" : '',
|
||||
!empty($this->options['variant']) ? $this->getTextClassForVariant($this->options['variant']) : '',
|
||||
],
|
||||
], implode('', [$this->genHeader(), $this->genBody(), $this->genFooter()]));
|
||||
return $card;
|
||||
}
|
||||
|
||||
private function genHeader()
|
||||
{
|
||||
if (empty($this->options['headerHTML']) && empty($this->options['headerText'])) {
|
||||
return '';
|
||||
}
|
||||
$content = !empty($this->options['headerHTML']) ? $this->options['headerHTML'] : h($this->options['headerText']);
|
||||
$header = $this->genNode('div', [
|
||||
'class' => [
|
||||
'card-header',
|
||||
h($this->options['headerClass']),
|
||||
],
|
||||
], $content);
|
||||
return $header;
|
||||
}
|
||||
|
||||
private function genBody()
|
||||
{
|
||||
if (empty($this->options['bodyHTML']) && empty($this->options['bodyText'])) {
|
||||
return '';
|
||||
}
|
||||
$content = !empty($this->options['bodyHTML']) ? $this->options['bodyHTML'] : h($this->options['bodyText']);
|
||||
$body = $this->genNode('div', [
|
||||
'class' => [
|
||||
'card-body',
|
||||
h($this->options['bodyClass']),
|
||||
],
|
||||
], $content);
|
||||
return $body;
|
||||
}
|
||||
|
||||
private function genFooter()
|
||||
{
|
||||
if (empty($this->options['footerHTML']) && empty($this->options['footerText'])) {
|
||||
return '';
|
||||
}
|
||||
$content = !empty($this->options['footerHTML']) ? $this->options['footerHTML'] : h($this->options['footerText']);
|
||||
$footer = $this->genNode('div', [
|
||||
'class' => [
|
||||
'card-footer',
|
||||
h($this->options['footerClass']),
|
||||
],
|
||||
], $content);
|
||||
return $footer;
|
||||
}
|
||||
}
|
||||
|
||||
class BoostrapProgress extends BootstrapGeneric {
|
||||
private $defaultOptions = [
|
||||
'value' => 0,
|
||||
'total' => 100,
|
||||
'text' => '',
|
||||
'title' => '',
|
||||
'variant' => 'primary',
|
||||
'height' => '',
|
||||
'striped' => false,
|
||||
'animated' => false,
|
||||
'label' => true
|
||||
];
|
||||
|
||||
function __construct($options) {
|
||||
$this->allowedOptionValues = [
|
||||
'variant' => BootstrapGeneric::$variants,
|
||||
];
|
||||
$this->processOptions($options);
|
||||
}
|
||||
|
||||
private function processOptions($options)
|
||||
{
|
||||
$this->options = array_merge($this->defaultOptions, $options);
|
||||
$this->checkOptionValidity();
|
||||
}
|
||||
|
||||
public function progress()
|
||||
{
|
||||
return $this->genProgress();
|
||||
}
|
||||
|
||||
private function genProgress()
|
||||
{
|
||||
$percentage = round(100 * $this->options['value'] / $this->options['total']);
|
||||
$heightStyle = !empty($this->options['height']) ? sprintf('height: %s;', h($this->options['height'])) : '';
|
||||
$widthStyle = sprintf('width: %s%%;', $percentage);
|
||||
$label = $this->options['label'] ? "{$percentage}%" : '';
|
||||
$pb = $this->genNode('div', [
|
||||
'class' => [
|
||||
'progress-bar',
|
||||
"bg-{$this->options['variant']}",
|
||||
$this->options['striped'] ? 'progress-bar-striped' : '',
|
||||
$this->options['animated'] ? 'progress-bar-animated' : '',
|
||||
],
|
||||
'role' => "progressbar",
|
||||
'aria-valuemin' => "0", 'aria-valuemax' => "100",'aria-valuenow' => $percentage,
|
||||
'style' => "${widthStyle}",
|
||||
'title' => $this->options['title']
|
||||
], $label);
|
||||
$container = $this->genNode('div', [
|
||||
'class' => [
|
||||
'progress',
|
||||
],
|
||||
'style' => "${heightStyle}",
|
||||
'title' => h($this->options['title']),
|
||||
], $pb);
|
||||
return $container;
|
||||
}
|
||||
}
|
||||
|
||||
class BoostrapCollapse extends BootstrapGeneric {
|
||||
private $defaultOptions = [
|
||||
'text' => '',
|
||||
'open' => false,
|
||||
];
|
||||
|
||||
function __construct($options, $content, $btHelper) {
|
||||
$this->allowedOptionValues = [];
|
||||
$this->processOptions($options);
|
||||
$this->content = $content;
|
||||
$this->btHelper = $btHelper;
|
||||
}
|
||||
|
||||
private function processOptions($options)
|
||||
{
|
||||
$this->options = array_merge($this->defaultOptions, $options);
|
||||
$this->checkOptionValidity();
|
||||
}
|
||||
|
||||
public function collapse()
|
||||
{
|
||||
return $this->genCollapse();
|
||||
}
|
||||
|
||||
private function genControl()
|
||||
{
|
||||
$html = $this->genNode('a', [
|
||||
'class' => ['text-decoration-none'],
|
||||
'data-toggle' => 'collapse',
|
||||
'href' => '#collapseExample',
|
||||
'role' => 'button',
|
||||
'aria-expanded' => 'false',
|
||||
'aria-controls' => 'collapseExample',
|
||||
], h($this->options['title']));
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function genContent()
|
||||
{
|
||||
$content = $this->genNode('div', [
|
||||
'class' => 'card',
|
||||
], $this->content);
|
||||
$container = $this->genNode('div', [
|
||||
'class' => ['collapse', $this->options['open'] ? 'show' : ''],
|
||||
'id' => 'collapseExample',
|
||||
], $content);
|
||||
return $container;
|
||||
}
|
||||
|
||||
private function genCollapse()
|
||||
{
|
||||
$html = $this->genControl();
|
||||
$html .= $this->genContent();
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
class BoostrapProgressTimeline extends BootstrapGeneric {
|
||||
private $defaultOptions = [
|
||||
'steps' => [],
|
||||
'selected' => 0,
|
||||
'variant' => 'info',
|
||||
'variantInactive' => 'secondary',
|
||||
];
|
||||
|
||||
function __construct($options, $btHelper) {
|
||||
$this->allowedOptionValues = [
|
||||
'variant' => BootstrapGeneric::$variants,
|
||||
'variantInactive' => BootstrapGeneric::$variants,
|
||||
];
|
||||
$this->processOptions($options);
|
||||
$this->btHelper = $btHelper;
|
||||
}
|
||||
|
||||
private function processOptions($options)
|
||||
{
|
||||
$this->options = array_merge($this->defaultOptions, $options);
|
||||
$this->checkOptionValidity();
|
||||
}
|
||||
|
||||
public function progressTimeline()
|
||||
{
|
||||
return $this->genProgressTimeline();
|
||||
}
|
||||
|
||||
private function getStepIcon($step, $i, $nodeActive, $lineActive)
|
||||
{
|
||||
$icon = $this->genNode('b', [
|
||||
'class' => [
|
||||
!empty($step['icon']) ? h($this->btHelper->FontAwesome->getClass($step['icon'])) : '',
|
||||
$this->getTextClassForVariant($this->options['variant'])
|
||||
],
|
||||
], empty($step['icon']) ? h($i+1) : '');
|
||||
$iconContainer = $this->genNode('span', [
|
||||
'class' => [
|
||||
'd-flex', 'align-items-center', 'justify-content-center',
|
||||
'rounded-circle',
|
||||
$nodeActive ? "bg-{$this->options['variant']}" : "bg-{$this->options['variantInactive']}"
|
||||
],
|
||||
'style' => 'width:50px; height:50px'
|
||||
], $icon);
|
||||
$li = $this->genNode('li', [
|
||||
'class' => [
|
||||
'd-flex', 'flex-column',
|
||||
$nodeActive ? 'progress-active' : 'progress-inactive',
|
||||
],
|
||||
], $iconContainer);
|
||||
$html = $li . $this->getHorizontalLine($i, $nodeActive, $lineActive);
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function getHorizontalLine($i, $nodeActive, $lineActive)
|
||||
{
|
||||
$stepCount = count($this->options['steps']);
|
||||
if ($i == $stepCount-1) {
|
||||
return '';
|
||||
}
|
||||
$progressBar = (new BoostrapProgress([
|
||||
'label' => false,
|
||||
'value' => $nodeActive ? ($lineActive ? 100 : 50) : 0,
|
||||
'height' => '2px',
|
||||
'variant' => $this->options['variant']
|
||||
]))->progress();
|
||||
$line = $this->genNode('span', [
|
||||
'class' => [
|
||||
'progress-line',
|
||||
'flex-grow-1', 'align-self-center',
|
||||
$lineActive ? "bg-{$this->options['variant']}" : ''
|
||||
],
|
||||
], $progressBar);
|
||||
return $line;
|
||||
}
|
||||
|
||||
private function getStepText($step, $isActive)
|
||||
{
|
||||
return $this->genNode('li', [
|
||||
'class' => [
|
||||
'text-center',
|
||||
'font-weight-bold',
|
||||
$isActive ? 'progress-active' : 'progress-inactive',
|
||||
],
|
||||
], h($step['text'] ?? ''));
|
||||
}
|
||||
|
||||
private function genProgressTimeline()
|
||||
{
|
||||
$iconLis = '';
|
||||
$textLis = '';
|
||||
foreach ($this->options['steps'] as $i => $step) {
|
||||
$nodeActive = $i <= $this->options['selected'];
|
||||
$lineActive = $i < $this->options['selected'];
|
||||
$iconLis .= $this->getStepIcon($step, $i, $nodeActive, $lineActive);
|
||||
$textLis .= $this->getStepText($step, $nodeActive);
|
||||
}
|
||||
$ulIcons = $this->genNode('ul', [
|
||||
'class' => [
|
||||
'd-flex', 'justify-content-around',
|
||||
],
|
||||
], $iconLis);
|
||||
$ulText = $this->genNode('ul', [
|
||||
'class' => [
|
||||
'd-flex', 'justify-content-between',
|
||||
],
|
||||
], $textLis);
|
||||
$html = $this->genNode('div', [
|
||||
'class' => ['progress-timeline', 'mw-75', 'mx-auto']
|
||||
], $ulIcons . $ulText);
|
||||
return $html;
|
||||
}
|
||||
}
|
|
@ -22,7 +22,8 @@ namespace App\View;
|
|||
class MonadView extends AppView
|
||||
{
|
||||
private $additionalTemplatePaths = [
|
||||
ROOT . '/libraries/default/RequestProcessors/templates/',
|
||||
ROOT . '/libraries/default/InboxProcessors/templates/',
|
||||
ROOT . '/libraries/default/OutboxProcessors/templates/',
|
||||
];
|
||||
|
||||
protected function _paths(?string $plugin = null, bool $cached = true): array
|
||||
|
|
|
@ -8,6 +8,16 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
'data' => $data,
|
||||
'top_bar' => [
|
||||
'children' => [
|
||||
[
|
||||
'children' => [
|
||||
[
|
||||
'text' => __('Discard requests'),
|
||||
'variant' => 'danger',
|
||||
'onclick' => 'discardRequests',
|
||||
]
|
||||
],
|
||||
'type' => 'multi_select_actions',
|
||||
],
|
||||
[
|
||||
'type' => 'context_filters',
|
||||
'context_filters' => !empty($filteringContexts) ? $filteringContexts : []
|
||||
|
@ -23,6 +33,15 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
]
|
||||
],
|
||||
'fields' => [
|
||||
[
|
||||
'element' => 'selector',
|
||||
'class' => 'short',
|
||||
'data' => [
|
||||
'id' => [
|
||||
'value_path' => 'id'
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => '#',
|
||||
'sort' => 'id',
|
||||
|
@ -95,5 +114,62 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
]
|
||||
]
|
||||
]);
|
||||
echo '</div>';
|
||||
?>
|
||||
|
||||
<script>
|
||||
function discardRequests(idList, selectedData, $table) {
|
||||
const successCallback = function([data, modalObject]) {
|
||||
UI.reload('/inbox/index', UI.getContainerForTable($table), $table)
|
||||
}
|
||||
const failCallback = ([data, modalObject]) => {
|
||||
const tableData = selectedData.map(row => {
|
||||
entryInError = data.filter(error => error.data.id == row.id)[0]
|
||||
$faIcon = $('<i class="fa"></i>').addClass(entryInError.success ? 'fa-check text-success' : 'fa-times text-danger')
|
||||
return [row.id, row.scope, row.action, row.title, entryInError.message, JSON.stringify(entryInError.errors), $faIcon]
|
||||
});
|
||||
handleMessageTable(
|
||||
modalObject.$modal,
|
||||
['<?= __('ID') ?>', '<?= __('Scope') ?>', '<?= __('Action') ?>', '<?= __('Title') ?>', '<?= __('Message') ?>', '<?= __('Error') ?>', '<?= __('State') ?>'],
|
||||
tableData
|
||||
)
|
||||
const $footer = $(modalObject.ajaxApi.statusNode).parent()
|
||||
modalObject.ajaxApi.statusNode.remove()
|
||||
const $cancelButton = $footer.find('button[data-dismiss="modal"]')
|
||||
$cancelButton.text('<?= __('OK') ?>').removeClass('btn-secondary').addClass('btn-primary')
|
||||
}
|
||||
UI.submissionModal('/inbox/delete', successCallback, failCallback).then(([modalObject, ajaxApi]) => {
|
||||
const $idsInput = modalObject.$modal.find('form').find('input#ids-field')
|
||||
$idsInput.val(JSON.stringify(idList))
|
||||
const tableData = selectedData.map(row => {
|
||||
return [row.id, row.scope, row.action, row.title]
|
||||
});
|
||||
handleMessageTable(
|
||||
modalObject.$modal,
|
||||
['<?= __('ID') ?>', '<?= __('Scope') ?>', '<?= __('Action') ?>', '<?= __('Title') ?>'],
|
||||
tableData
|
||||
)
|
||||
})
|
||||
|
||||
function constructMessageTable(header, data) {
|
||||
return HtmlHelper.table(
|
||||
header,
|
||||
data,
|
||||
{
|
||||
small: true,
|
||||
borderless: true,
|
||||
tableClass: ['message-table', 'mt-4 mb-0'],
|
||||
}
|
||||
)
|
||||
}
|
||||
function handleMessageTable($modal, header, data) {
|
||||
const $modalBody = $modal.find('.modal-body')
|
||||
const $messageTable = $modalBody.find('table.message-table')
|
||||
const messageTableHTML = constructMessageTable(header, data)[0].outerHTML
|
||||
if ($messageTable.length) {
|
||||
$messageTable.html(messageTableHTML)
|
||||
} else {
|
||||
$modalBody.append(messageTableHTML)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -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 Inbox Request Processors'),
|
||||
'description' => __('The list of Inbox Request Processors available on this server.'),
|
||||
'actions' => [
|
||||
]
|
||||
]
|
||||
]);
|
|
@ -43,6 +43,7 @@ echo $this->element('genericElements/IndexTable/index_table', [
|
|||
'actions' => [
|
||||
[
|
||||
'open_modal' => sprintf('/localTools/connectionRequest/%s/[onclick_params_data_path]', h($id)),
|
||||
'reload_url' => $this->Url->build(['action' => 'broodTools', $id]),
|
||||
'modal_params_data_path' => 'id',
|
||||
'title' => 'Issue a connection request',
|
||||
'icon' => 'plug'
|
||||
|
|
|
@ -16,7 +16,12 @@
|
|||
'field' => 'local_tool_id',
|
||||
'options' => $dropdown,
|
||||
'type' => 'dropdown'
|
||||
]
|
||||
],
|
||||
[
|
||||
'field' => 'tool_name',
|
||||
'default' => $data['remoteTool']['connectorName'],
|
||||
'type' => 'hidden'
|
||||
],
|
||||
],
|
||||
'submit' => [
|
||||
'action' => $this->request->getParam('action')
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
<?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' => [
|
||||
[
|
||||
'children' => [
|
||||
[
|
||||
'text' => __('Delete messages'),
|
||||
'variant' => 'danger',
|
||||
'onclick' => 'deleteMessages',
|
||||
]
|
||||
],
|
||||
'type' => 'multi_select_actions',
|
||||
],
|
||||
[
|
||||
'type' => 'context_filters',
|
||||
'context_filters' => !empty($filteringContexts) ? $filteringContexts : []
|
||||
],
|
||||
[
|
||||
'type' => 'search',
|
||||
'button' => __('Filter'),
|
||||
'placeholder' => __('Enter value to search'),
|
||||
'data' => '',
|
||||
'searchKey' => 'value',
|
||||
'allowFilering' => true
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => [
|
||||
[
|
||||
'element' => 'selector',
|
||||
'class' => 'short',
|
||||
'data' => [
|
||||
'id' => [
|
||||
'value_path' => 'id'
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'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')
|
||||
],
|
||||
]
|
||||
]
|
||||
]);
|
||||
?>
|
||||
|
||||
<script>
|
||||
function deleteMessages(idList, selectedData, $table) {
|
||||
UI.submissionModalForIndex('/outbox/delete', '/outbox/index', $table).then(([modalObject, ajaxApi]) => {
|
||||
const $idsInput = modalObject.$modal.find('form').find('input#ids-field')
|
||||
$idsInput.val(JSON.stringify(idList))
|
||||
const tableData = selectedData.map(row => {
|
||||
return [row.id, row.scope, row.action, row.title]
|
||||
});
|
||||
handleMessageTable(
|
||||
modalObject.$modal,
|
||||
['<?= __('ID') ?>', '<?= __('Scope') ?>', '<?= __('Action') ?>', '<?= __('Title') ?>'],
|
||||
tableData
|
||||
)
|
||||
})
|
||||
|
||||
function constructMessageTable(header, data) {
|
||||
return HtmlHelper.table(
|
||||
header,
|
||||
data,
|
||||
{
|
||||
small: true,
|
||||
borderless: true,
|
||||
tableClass: ['message-table', 'mt-4 mb-0'],
|
||||
}
|
||||
)
|
||||
}
|
||||
function handleMessageTable($modal, header, data) {
|
||||
const $modalBody = $modal.find('.modal-body')
|
||||
const $messageTable = $modalBody.find('table.message-table')
|
||||
const messageTableHTML = constructMessageTable(header, data)[0].outerHTML
|
||||
if ($messageTable.length) {
|
||||
$messageTable.html(messageTableHTML)
|
||||
} else {
|
||||
$modalBody.append(messageTableHTML)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -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' => []
|
||||
]
|
||||
);
|
|
@ -83,7 +83,7 @@
|
|||
$submitButtonData['ajaxSubmit'] = $ajaxSubmit;
|
||||
}
|
||||
$ajaxFlashMessage = '';
|
||||
if ($ajax) {
|
||||
if (!empty($ajax)) {
|
||||
$ajaxFlashMessage = sprintf(
|
||||
'<div id="flashContainer"><div id="main-view-container">%s</div></div>',
|
||||
$this->Flash->render()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
if ($ajax) {
|
||||
if (!empty($ajax)) {
|
||||
echo sprintf(
|
||||
'%s',
|
||||
sprintf(
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* - function($row, $options): the lambda function. $row contain the row data
|
||||
* - options: array of options. datapaths described in the datapath keyname will be extracted and replaced with the actual row value
|
||||
*/
|
||||
echo '<td class="action-links text-right">';
|
||||
echo '<td class="action-links text-right text-nowrap">';
|
||||
foreach ($actions as $action) {
|
||||
if (isset($action['requirement']) && !$action['requirement']) {
|
||||
continue;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
}
|
||||
}
|
||||
echo sprintf(
|
||||
'<input class="select_attribute select" type="checkbox" data-rowid="%s" %s>',
|
||||
'<input class="selectable_row select" type="checkbox" data-rowid="%s" %s>',
|
||||
h($k),
|
||||
empty($data) ? '' : implode(' ', $data)
|
||||
);
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
$header_data = sprintf(
|
||||
'<input id="select_all" class="%s" type="checkbox" %s>',
|
||||
empty($header['select_all_class']) ? 'select_all' : $header['select_all_class'],
|
||||
empty($header['select_all_function']) ? 'onclick="toggleAllAttributeCheckboxes();"' : 'onclick="' . $header['select_all_function'] . '"'
|
||||
empty($header['select_all_function']) ? 'onclick="toggleAllAttributeCheckboxes(this);"' : 'onclick="' . $header['select_all_function'] . '"'
|
||||
);
|
||||
} else {
|
||||
$header_data = h($header['name']);
|
||||
|
@ -38,14 +38,3 @@
|
|||
$thead .= '</thead>';
|
||||
echo $thead;
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('.select_attribute').add('#select_all').on('change', function() {
|
||||
if ($('.select_attribute:checked').length > 0) {
|
||||
$('.mass-select').show();
|
||||
} else {
|
||||
$('.mass-select').hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -93,7 +93,8 @@
|
|||
}
|
||||
$tbody = '<tbody>' . $rows . '</tbody>';
|
||||
echo sprintf(
|
||||
'<table class="table table-hover" id="index-table-%s">%s%s</table>',
|
||||
'<table class="table table-hover" id="index-table-%s" data-table-random-value="%s">%s%s</table>',
|
||||
$tableRandomValue,
|
||||
$tableRandomValue,
|
||||
$this->element(
|
||||
'/genericElements/IndexTable/headers',
|
||||
|
@ -114,6 +115,7 @@
|
|||
?>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('#index-table-<?= $tableRandomValue ?>').data('data', <?= json_encode($data['data']) ?>);
|
||||
$('.privacy-toggle').on('click', function() {
|
||||
var $privacy_target = $(this).parent().find('.privacy-value');
|
||||
if ($(this).hasClass('fa-eye')) {
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
if (!isset($data['requirement']) || $data['requirement']) {
|
||||
$buttons = '';
|
||||
foreach ($data['children'] as $child) {
|
||||
$buttons .= $this->Bootstrap->button([
|
||||
'variant' => $child['variant'] ?? 'primary',
|
||||
'text' => $child['text'],
|
||||
'outline' => !empty($child['outline']),
|
||||
'params' => [
|
||||
'data-onclick-function' => $child['onclick'] ?? '',
|
||||
'data-table-random-value' => $tableRandomValue,
|
||||
'onclick' => 'multiActionClickHandler(this)'
|
||||
]
|
||||
]);
|
||||
}
|
||||
echo sprintf(
|
||||
'<div class="multi_select_actions btn-group mr-2 flex-wrap collapse" role="group" aria-label="button-group" data-table-random-value="%s">%s</div>',
|
||||
$tableRandomValue,
|
||||
$buttons
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
let $table = $('#index-table-<?= $tableRandomValue ?>')
|
||||
$table.find('input.select_all').on('change', function() {
|
||||
toggleMultiSelectActions($table)
|
||||
|
||||
});
|
||||
$table.find('input.selectable_row').on('change', function() {
|
||||
toggleMultiSelectActions($table)
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
function toggleMultiSelectActions($table) {
|
||||
const randomValue = $table.data('table-random-value');
|
||||
let $multiSelectActions = $('div.multi_select_actions').filter(function() {
|
||||
return $(this).data('table-random-value') == randomValue
|
||||
})
|
||||
if (getSelected($table).length > 0) {
|
||||
$multiSelectActions.show()
|
||||
} else {
|
||||
$multiSelectActions.hide()
|
||||
}
|
||||
}
|
||||
|
||||
function getSelected($table) {
|
||||
return $table.find('input.selectable_row:checked')
|
||||
}
|
||||
|
||||
function multiActionClickHandler(clicked) {
|
||||
let $clicked = $(clicked)
|
||||
const randomValue = $clicked.data('table-random-value')
|
||||
let $table = $(`#index-table-${randomValue}`)
|
||||
let rowDataByID = {}
|
||||
$table.data('data').forEach(row => {
|
||||
rowDataByID[row.id] = row
|
||||
})
|
||||
const $selected = getSelected($table)
|
||||
let selectedIDs = []
|
||||
let selectedData = []
|
||||
$selected.each(function() {
|
||||
const dataID = $(this).data('id')
|
||||
selectedIDs.push(dataID)
|
||||
selectedData.push(rowDataByID[dataID])
|
||||
})
|
||||
const functionName = $clicked.data('onclick-function')
|
||||
if (functionName && typeof window[functionName] === 'function') {
|
||||
window[functionName](selectedIDs, selectedData, $table)
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -4,7 +4,7 @@
|
|||
$string = $field['raw'];
|
||||
} else {
|
||||
$value = Cake\Utility\Hash::extract($data, $field['path']);
|
||||
$string = empty($value[0]) ? '' : $value[0];
|
||||
$string = count($value) == 0 ? '' : $value;
|
||||
}
|
||||
echo sprintf(
|
||||
'<div class="json_container_%s"></div>',
|
||||
|
|
|
@ -1,39 +1,31 @@
|
|||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<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>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<?php if (empty($deletionText)): ?>
|
||||
<p><?= __('Are you sure you want to delete {0} #{1}?', h(Cake\Utility\Inflector::singularize($this->request->getParam('controller'))), h($id)) ?></p>
|
||||
<?php else: ?>
|
||||
<p><?= h($deletionText) ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<?= $this->Form->postLink(
|
||||
!empty($deletionConfirm) ? h($deletionConfirm) : __('Delete'),
|
||||
(empty($postLinkParameters) ? ['action' => 'delete', $id] : $postLinkParameters),
|
||||
['class' => 'btn btn-danger button-execute', 'id' => 'submitButton']
|
||||
)
|
||||
?>
|
||||
<button type="button" class="btn btn-secondary cancel-button" data-dismiss="modal"><?= __('Cancel') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$(document).keydown(function(e) {
|
||||
if(e.which === 13 && e.ctrlKey) {
|
||||
$('.button-execute').click();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
$form = $this->element('genericElements/Form/genericForm', [
|
||||
'entity' => null,
|
||||
'ajax' => false,
|
||||
'raw' => true,
|
||||
'data' => [
|
||||
'fields' => [
|
||||
[
|
||||
'type' => 'text',
|
||||
'field' => 'ids',
|
||||
'default' => !empty($id) ? json_encode([$id]) : ''
|
||||
]
|
||||
],
|
||||
'submit' => [
|
||||
'action' => $this->request->getParam('action')
|
||||
]
|
||||
]
|
||||
]);
|
||||
$formHTML = sprintf('<div class="d-none">%s</div>', $form);
|
||||
|
||||
$bodyMessage = !empty($deletionText) ? __($deletionText) : __('Are you sure you want to delete {0} #{1}?', h(Cake\Utility\Inflector::singularize($this->request->getParam('controller'))), h($id));
|
||||
$bodyHTML = sprintf('%s%s', $formHTML, $bodyMessage);
|
||||
|
||||
echo $this->Bootstrap->modal([
|
||||
'size' => 'lg',
|
||||
'title' => !empty($deletionTitle) ? $deletionTitle : __('Delete {0}', h(Cake\Utility\Inflector::singularize(Cake\Utility\Inflector::humanize($this->request->getParam('controller'))))),
|
||||
'type' => 'confirm-danger',
|
||||
'confirmText' => !empty($deletionConfirm) ? $deletionConfirm : __('Delete'),
|
||||
'bodyHtml' => $bodyHTML,
|
||||
]);
|
||||
?>
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/* utils */
|
||||
.mw-75 {
|
||||
max-width: 75% !important;
|
||||
}
|
||||
.mw-50 {
|
||||
max-width: 50% !important;
|
||||
}
|
||||
.mw-25 {
|
||||
max-width: 25% !important;
|
||||
}
|
||||
.mh-75 {
|
||||
max-width: 75% !important;
|
||||
}
|
||||
.mh-50 {
|
||||
max-width: 50% !important;
|
||||
}
|
||||
.mh-25 {
|
||||
max-width: 25% !important;
|
||||
}
|
||||
|
||||
/* Toast */
|
||||
.toast {
|
||||
min-width: 250px;
|
||||
|
@ -66,3 +86,25 @@
|
|||
.toast-dark strong {
|
||||
color: #040505;
|
||||
}
|
||||
|
||||
div.progress-timeline {
|
||||
padding: 0.2em 0.2em 0.5em 0.2em;
|
||||
}
|
||||
div.progress-timeline ul {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
div.progress-timeline li {
|
||||
list-style-type: none;
|
||||
position: relative
|
||||
}
|
||||
div.progress-timeline li.progress-inactive {
|
||||
opacity: 0.5;
|
||||
}
|
||||
div.progress-timeline .progress-line {
|
||||
height: 2px;
|
||||
/* background: gray; */
|
||||
}
|
||||
div.progress-timeline .progress-line.progress-inactive {
|
||||
opacity: 0.5;
|
||||
}
|
|
@ -31,7 +31,7 @@ class UIFactory {
|
|||
* @param {ModalFactory~POSTFailCallback} POSTFailCallback - The callback that handles form submissions errors and validation errors.
|
||||
* @return {Promise<Object>} Promise object resolving to the ModalFactory object
|
||||
*/
|
||||
submissionModal(url, POSTSuccessCallback, POSTFailCallback) {
|
||||
async submissionModal(url, POSTSuccessCallback, POSTFailCallback) {
|
||||
return AJAXApi.quickFetchURL(url).then((modalHTML) => {
|
||||
const theModal = new ModalFactory({
|
||||
rawHtml: modalHTML,
|
||||
|
@ -41,7 +41,13 @@ class UIFactory {
|
|||
theModal.makeModal()
|
||||
theModal.show()
|
||||
theModal.$modal.data('modalObject', theModal)
|
||||
return theModal
|
||||
return [theModal, theModal.ajaxApi]
|
||||
}).catch((error) => {
|
||||
UI.toast({
|
||||
variant: 'danger',
|
||||
title: 'Error while loading the processor',
|
||||
body: error.message
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -83,6 +89,11 @@ class UIFactory {
|
|||
return UI.submissionReloaderModal(url, reloadUrl, $reloadedElement, $statusNode);
|
||||
}
|
||||
|
||||
getContainerForTable($table) {
|
||||
const tableRandomID = $table.data('table-random-value')
|
||||
return $table.closest(`#table-container-${tableRandomID}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -113,7 +124,7 @@ class UIFactory {
|
|||
$statusNode = $elligibleTable
|
||||
} else {
|
||||
if ($table instanceof jQuery) {
|
||||
$reloadedElement = $table
|
||||
$reloadedElement = this.getContainerForTable($table)
|
||||
$statusNode = $table.find('table.table')
|
||||
} else {
|
||||
$reloadedElement = $(`#table-container-${$table}`)
|
||||
|
@ -141,7 +152,6 @@ class UIFactory {
|
|||
*/
|
||||
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)
|
||||
|
@ -386,6 +396,7 @@ class ModalFactory {
|
|||
if (this.options.backdropStatic) {
|
||||
this.bsModalOptions['backdrop'] = 'static'
|
||||
}
|
||||
this.ajaxApi = new AJAXApi()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -651,8 +662,8 @@ class ModalFactory {
|
|||
const $buttonConfirm = $('<button type="button" class="btn"></button>')
|
||||
.addClass('btn-' + variant)
|
||||
.text(this.options.confirmText)
|
||||
.click(this.getConfirmationHandlerFunction())
|
||||
.attr('data-dismiss', (this.options.closeManually || this.options.closeOnSuccess) ? '' : 'modal')
|
||||
$buttonConfirm.click(this.getConfirmationHandlerFunction($buttonConfirm))
|
||||
return [$buttonCancel, $buttonConfirm]
|
||||
}
|
||||
|
||||
|
@ -662,14 +673,28 @@ class ModalFactory {
|
|||
}
|
||||
|
||||
/** Generate the function that will be called when the user confirm the modal */
|
||||
getConfirmationHandlerFunction() {
|
||||
getConfirmationHandlerFunction($buttonConfirm, buttonIndex) {
|
||||
if (this.options.APIConfirms) {
|
||||
if (Array.isArray(this.ajaxApi)) {
|
||||
const tmpApi = new AJAXApi({
|
||||
statusNode: $buttonConfirm[0]
|
||||
})
|
||||
this.ajaxApi.push(tmpApi)
|
||||
} else {
|
||||
this.ajaxApi.statusNode = $buttonConfirm[0]
|
||||
this.ajaxApi = [this.ajaxApi];
|
||||
}
|
||||
} else {
|
||||
this.ajaxApi.statusNode = $buttonConfirm[0]
|
||||
}
|
||||
return (evt) => {
|
||||
let confirmFunction = this.options.confirm
|
||||
if (this.options.APIConfirm) {
|
||||
const tmpApi = new AJAXApi({
|
||||
statusNode: evt.target
|
||||
})
|
||||
confirmFunction = () => { return this.options.APIConfirm(tmpApi) }
|
||||
if (this.options.APIConfirms) {
|
||||
if (buttonIndex !== undefined && this.options.APIConfirms[buttonIndex] !== undefined) {
|
||||
confirmFunction = () => { return this.options.APIConfirms[buttonIndex](this.ajaxApi[buttonIndex]) }
|
||||
}
|
||||
} else if (this.options.APIConfirm) {
|
||||
confirmFunction = () => { return this.options.APIConfirm(this.ajaxApi) }
|
||||
}
|
||||
let confirmResult = confirmFunction(() => { this.hide() }, this, evt)
|
||||
if (confirmResult === undefined) {
|
||||
|
@ -689,58 +714,91 @@ class ModalFactory {
|
|||
|
||||
/** Attach the submission click listener for modals that have been generated by raw HTML */
|
||||
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
|
||||
if (formID) {
|
||||
$form = $(formID)
|
||||
} else {
|
||||
$form = this.$modal.find('form')
|
||||
let $modalFooter = this.$modal.find('.modal-footer')
|
||||
if ($modalFooter.data('custom-footer')) { // setup basic listener as callback are defined in the template
|
||||
let $submitButtons = this.$modal.find('.modal-footer .modal-confirm-button')
|
||||
var selfModal = this;
|
||||
selfModal.options.APIConfirms = [];
|
||||
$submitButtons.each(function(i) {
|
||||
const $submitButton = $(this)
|
||||
if ($submitButton.data('clickfunction') !== undefined && $submitButton.data('clickfunction') !== '') {
|
||||
const clickHandler = window[$submitButton.data('clickfunction')]
|
||||
selfModal.options.APIConfirms[i] = (tmpApi) => {
|
||||
let clickResult = clickHandler(selfModal, tmpApi)
|
||||
if (clickResult !== undefined) {
|
||||
return clickResult
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
selfModal.options.POSTSuccessCallback(data)
|
||||
} else { // Validation error
|
||||
selfModal.injectFormValidationFeedback(form, data.errors)
|
||||
return Promise.reject('Validation error');
|
||||
}
|
||||
})
|
||||
.catch((errorMessage) => {
|
||||
selfModal.options.POSTFailCallback(errorMessage)
|
||||
return Promise.reject(errorMessage);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
$submitButton.click(selfModal.getConfirmationHandlerFunction($submitButton, i))
|
||||
})
|
||||
} else {
|
||||
let $submitButton = this.$modal.find('.modal-footer #submitButton')
|
||||
if (!$submitButton[0]) {
|
||||
$submitButton = this.$modal.find('.modal-footer .modal-confirm-button')
|
||||
}
|
||||
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
|
||||
if ($submitButton[0]) {
|
||||
const formID = $submitButton.data('form-id')
|
||||
let $form
|
||||
if (formID) {
|
||||
$form = $(formID)
|
||||
} else {
|
||||
$form = this.$modal.find('form')
|
||||
}
|
||||
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)
|
||||
// this.options.POSTSuccessCallback(data)
|
||||
this.options.POSTSuccessCallback([data, this])
|
||||
} else { // Validation error
|
||||
this.injectFormValidationFeedback(form, data.errors)
|
||||
return Promise.reject('Validation error');
|
||||
}
|
||||
})
|
||||
.catch((errorMessage) => {
|
||||
this.options.POSTFailCallback(errorMessage)
|
||||
this.options.POSTFailCallback([errorMessage, this])
|
||||
// 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);
|
||||
})
|
||||
}
|
||||
$submitButton.click(this.getConfirmationHandlerFunction($submitButton))
|
||||
}
|
||||
$submitButton.click(this.getConfirmationHandlerFunction())
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -956,13 +1014,17 @@ class FormValidationHelper {
|
|||
} else {
|
||||
$messageNode.addClass('invalid-feedback')
|
||||
}
|
||||
const hasMultipleErrors = Object.keys(errors).length > 1
|
||||
for (const [ruleName, error] of Object.entries(errors)) {
|
||||
if (hasMultipleErrors) {
|
||||
$messageNode.append($('<li></li>').text(error))
|
||||
} else {
|
||||
$messageNode.text(error)
|
||||
if (typeof errors === 'object') {
|
||||
const hasMultipleErrors = Object.keys(errors).length > 1
|
||||
for (const [ruleName, error] of Object.entries(errors)) {
|
||||
if (hasMultipleErrors) {
|
||||
$messageNode.append($('<li></li>').text(error))
|
||||
} else {
|
||||
$messageNode.text(error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$messageNode.text(errors)
|
||||
}
|
||||
return $messageNode
|
||||
}
|
||||
|
|
|
@ -18,6 +18,13 @@ function executeStateDependencyChecks(dependenceSourceSelector) {
|
|||
});
|
||||
}
|
||||
|
||||
function toggleAllAttributeCheckboxes(clicked) {
|
||||
let $clicked = $(clicked)
|
||||
let $table = $clicked.closest('table')
|
||||
let $inputs = $table.find('input.selectable_row')
|
||||
$inputs.prop('checked', $clicked.prop('checked'))
|
||||
}
|
||||
|
||||
function testConnection(id) {
|
||||
$container = $(`#connection_test_${id}`)
|
||||
UI.overlayUntilResolve(
|
||||
|
|