Merge branch 'develop' of github.com:cerebrate-project/cerebrate into develop

refacto/CRUDComponent
Sami Mokaddem 2023-11-02 08:10:01 +01:00
commit f0ba0d8316
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
11 changed files with 481 additions and 46 deletions

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
use Migrations\AbstractMigration;
use Phinx\Db\Adapter\MysqlAdapter;
final class SGExtend extends AbstractMigration
{
public $autoId = false; // turn off automatic `id` column create. We want it to be `int(10) unsigned`
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
$table = $this->table('sgo');
$exists = $table->hasColumn('extend');
if (!$exists) {
$table
->addColumn('extend', 'boolean', [
'default' => 0,
'null' => false,
])->update();
}
}
}

View File

@ -8,6 +8,7 @@ use Cake\Utility\Text;
use \Cake\Database\Expression\QueryExpression; use \Cake\Database\Expression\QueryExpression;
use Cake\Error\Debugger; use Cake\Error\Debugger;
use Cake\Http\Exception\NotFoundException; use Cake\Http\Exception\NotFoundException;
use Cake\ORM\TableRegistry;
class SharingGroupsController extends AppController class SharingGroupsController extends AppController
{ {
@ -171,9 +172,13 @@ class SharingGroupsController extends AppController
$input['organisation_id'] = [$input['organisation_id']]; $input['organisation_id'] = [$input['organisation_id']];
} }
$result = true; $result = true;
$this->SGO = TableRegistry::getTableLocator()->get('SGOs');
foreach ($input['organisation_id'] as $org_id) { foreach ($input['organisation_id'] as $org_id) {
$org = $this->SharingGroups->SharingGroupOrgs->get($org_id); $additional_data = [];
$result &= (bool)$this->SharingGroups->SharingGroupOrgs->link($sharingGroup, [$org]); if (!empty($input['extend'])) {
$additional_data['extend'] = $input['extend'];
}
$result &= $this->SGO->attach($sharingGroup['id'], $org_id, $additional_data);
} }
if ($result) { if ($result) {
$message = __('Organisation(s) added to the sharing group.'); $message = __('Organisation(s) added to the sharing group.');
@ -216,8 +221,8 @@ class SharingGroupsController extends AppController
throw new NotFoundException(__('Invalid SharingGroup.')); throw new NotFoundException(__('Invalid SharingGroup.'));
} }
if ($this->request->is('post')) { if ($this->request->is('post')) {
$org = $this->SharingGroups->SharingGroupOrgs->get($org_id); $this->SGO = TableRegistry::getTableLocator()->get('SGOs');
$result = (bool)$this->SharingGroups->SharingGroupOrgs->unlink($sharingGroup, [$org]); $result = (bool)$this->SharingGroups->SharingGroupOrgs->unlink($sharingGroup['id'], $org_id);
if ($result) { if ($result) {
$message = __('Organisation(s) removed from the sharing group.'); $message = __('Organisation(s) removed from the sharing group.');
} else { } else {
@ -253,9 +258,10 @@ class SharingGroupsController extends AppController
public function listOrgs($id) public function listOrgs($id)
{ {
$sharingGroup = $this->SharingGroups->get($id, [ $sharingGroup = $this->SharingGroups->find()->where(['id' => $id])->contain(['SharingGroupOrgs'])->first();
'contain' => 'SharingGroupOrgs' foreach ($sharingGroup['sharing_group_orgs'] as $k => $org) {
]); $sharingGroup['sharing_group_orgs'][$k]['extend'] = $org['_joinData']['extend'];
}
$params = $this->ParamHandler->harvestParams(['quickFilter']); $params = $this->ParamHandler->harvestParams(['quickFilter']);
if (!empty($params['quickFilter'])) { if (!empty($params['quickFilter'])) {
foreach ($sharingGroup['sharing_group_orgs'] as $k => $org) { foreach ($sharingGroup['sharing_group_orgs'] as $k => $org) {

View File

@ -104,6 +104,16 @@ class CommonConnectorTools
return $orgs; return $orgs;
} }
public function getSharingGroups(): array
{
$sgs = \Cake\ORM\TableRegistry::getTableLocator()->get('SharingGroups');
$sgs = $sgs->find()
->contain(['Organisations' => ['fields' => ['uuid']], 'SharingGroupOrgs' => ['fields' => ['uuid']]])
->disableHydration()
->toArray();
return $sgs;
}
public function getFilteredOrganisations($filters, $returnObjects = false): array public function getFilteredOrganisations($filters, $returnObjects = false): array
{ {
$organisations = \Cake\ORM\TableRegistry::getTableLocator()->get('Organisations'); $organisations = \Cake\ORM\TableRegistry::getTableLocator()->get('Organisations');
@ -219,6 +229,11 @@ class CommonConnectorTools
$this->remoteToolConnectionStatus($params, self::STATE_CONNECTED); $this->remoteToolConnectionStatus($params, self::STATE_CONNECTED);
return false; return false;
} }
public function diagnostics(array $params): array
{
return [];
}
} }
?> ?>

View File

@ -15,6 +15,12 @@ class MispConnector extends CommonConnectorTools
public $name = 'MISP'; public $name = 'MISP';
public $exposedFunctions = [ public $exposedFunctions = [
'diagnosticsAction' => [
'type' => 'index',
'scope' => 'child',
'params' => [
]
],
'serverSettingsAction' => [ 'serverSettingsAction' => [
'type' => 'index', 'type' => 'index',
'scope' => 'child', 'scope' => 'child',
@ -48,6 +54,11 @@ class MispConnector extends CommonConnectorTools
'direction' 'direction'
] ]
], ],
'restartWorkersAction' => [
'type' => 'formAction',
'scope' => 'childAction',
'redirect' => 'diagnosticsAction'
],
'fetchOrganisationAction' => [ 'fetchOrganisationAction' => [
'type' => 'formAction', 'type' => 'formAction',
'scope' => 'childAction', 'scope' => 'childAction',
@ -132,7 +143,7 @@ class MispConnector extends CommonConnectorTools
'icon' => 'terminal', 'icon' => 'terminal',
'variant' => 'primary', 'variant' => 'primary',
] ]
] ],
]; ];
public $version = '0.1'; public $version = '0.1';
public $settings = [ public $settings = [
@ -261,7 +272,7 @@ class MispConnector extends CommonConnectorTools
if (!empty($params['softError'])) { if (!empty($params['softError'])) {
return $response; return $response;
} }
$errorMsg = __('Could not post to the requested resource for `{0}`. Remote returned:', $url) . PHP_EOL . $response->getStringBody(); $errorMsg = __('Could not GET from the requested resource for `{0}`. Remote returned:', $url) . PHP_EOL . $response->getStringBody();
$this->logError($errorMsg); $this->logError($errorMsg);
throw new NotFoundException($errorMsg); throw new NotFoundException($errorMsg);
} }
@ -312,10 +323,183 @@ class MispConnector extends CommonConnectorTools
return $url; return $url;
} }
public function diagnostics(array $params): array
{
$urlParams = h($params['connection']['id']) . '/serverSettingsAction';
$response = $this->getData('/servers/serverSettings', $params);
if (!$response->isOk()) {
return [];
}
$data = $response->getJson();
$issues = [];
if ($data['version']['upToDate'] !== 'same') {
$issues['version'] = [
'type' => 'danger',
'message' => __('Outdated ({0}).', $data['version']['current']),
'remediation' => [
'icon' => 'fa-sync',
'title' => __('Update MISP'),
'url' => '/localTools/action/' . h($params['connection']['id']) . '/updateMISP'
]
];
}
if ($data['phpSettings']['memory_limit']['value'] < $data['phpSettings']['memory_limit']['recommended']) {
$issues['php_memory'] = [
'type' => 'warning',
'message' => __('Low PHP memory ({0}M).', $data['phpSettings']['memory_limit']['value'])
];
}
$worker_issues = [];
foreach ($data['workers'] as $queue => $worker_data) {
if (in_array($queue, ['proc_accessible', 'scheduler', 'controls'])) {
continue;
}
if (empty($worker_data['ok'])) {
$worker_issues['down'][] = $queue;
}
if ($worker_data['jobCount'] > 100) {
$worker_issues['stalled'][] = $queue;
}
}
if (!empty($worker_issues['down'])) {
$issues['workers_down'] = [
'type' => 'danger',
'message' => __('Worker(s) down: {0}', implode(', ', $worker_issues['down'])),
'remediation' => [
'icon' => 'fa-sync',
'title' => __('Restart workers'),
'url' => '/localTools/action/' . h($params['connection']['id']) . '/restartWorkersAction'
]
];
}
if (!empty($worker_issues['stalled'])) {
$issues['workers_stalled'] = [
'type' => 'warning',
'message' => __('Worker(s) stalled: {0}', implode(', ', $worker_issues['stalled'])),
'remediation' => [
'icon' => 'fa-sync',
'title' => __('Restart workers'),
'url' => '/localTools/action/' . h($params['connection']['id']) . '/restartWorkersAction'
]
];
}
if (!empty($data['dbConfiguration'])) {
foreach ($data['dbConfiguration'] as $dbConfig) {
if ($dbConfig['name'] === 'innodb_buffer_pool_size' && $dbConfig['value'] < $dbConfig['recommended']) {
$issues['innodb_buffer_pool_size'] = [
'type' => 'warning',
'message' => __('InnoDB buffer pool size is low ({0}M).', (round($dbConfig['value']/1024/1024)))
];
}
}
}
if (!empty($data['dbSchemaDiagnostics'])) {
if ($data['dbSchemaDiagnostics']['expected_db_version'] > $data['dbSchemaDiagnostics']['actual_db_version'])
$issues['schema_version'] = [
'type' => 'danger',
'message' => __('DB schame outdated.'),
'remediation' => [
'icon' => 'fa-sync',
'title' => __('Update DB schema'),
'url' => '/localTools/action/' . h($params['connection']['id']) . '/updateSchemaAction'
]
];
}
return $issues;
}
public function restartWorkersAction(array $params): array
{
if ($params['request']->is(['get'])) {
return [
'data' => [
'title' => __('Restart workers'),
'description' => __('Would you like to trigger a restart of all attached workers?'),
'submit' => [
'action' => $params['request']->getParam('action')
],
'url' => ['controller' => 'localTools', 'action' => 'action', $params['connection']['id'], 'restartWorkersAction']
]
];
} elseif ($params['request']->is(['post'])) {
$response = $this->postData('/servers/restartWorkers', $params);
if ($response->isOk()) {
return ['success' => 1, 'message' => __('Workers restarted.')];
} else {
return ['success' => 0, 'message' => __('Could not restart workers.')];
}
}
$response = $this->postData('/servers/restartWorkers', $params);
if ($response->isOk()) {
return [
'type' => 'success',
'message' => __('Workers restarted.')
];
} else {
return [
'type' => 'danger',
'message' => __('Something went wrong.')
];
}
}
public function diagnosticsAction(array $params): array public function diagnosticsAction(array $params): array
{ {
$diagnostics = $this->diagnostics($params);
$data = [];
foreach ($diagnostics as $error => $error_data) {
$data[] = [
'error' => $error,
'type' => $error_data['type'],
'message' => $error_data['message'],
'remediation' => $error_data['remediation'] ?? false
];
}
return [
'type' => 'index',
'data' => [
'data' => $data,
'skip_pagination' => 1,
'top_bar' => [
'children' => []
],
'fields' => [
[
'name' => 'error',
'data_path' => 'error',
'name' => __('Error'),
],
[
'name' => 'message',
'data_path' => 'message',
'name' => __('Message'),
],
[
'name' => __('Remediation'),
'element' => 'function',
'function' => function($row, $context) {
$remediation = $context->Hash->extract($row, 'remediation');
if (!empty($remediation['title'])) {
echo sprintf(
'<a href="%s" class="btn btn-primary btn-sm" title="%s"><i class="fa %s"></i></a>',
h($remediation['url']),
h($remediation['title']),
h($remediation['icon'])
);
}
}
]
],
'title' => false,
'description' => false,
'pull' => 'right'
]
];
} }
public function serverSettingsAction(array $params): array public function serverSettingsAction(array $params): array
@ -576,6 +760,56 @@ class MispConnector extends CommonConnectorTools
} }
} }
private function __compareOrgs(array $data, array $existingOrgs): array
{
foreach ($data as $k => $v) {
$data[$k]['Organisation']['local_copy'] = false;
if (!empty($existingOrgs[$v['Organisation']['uuid']])) {
$remoteOrg = $existingOrgs[$v['Organisation']['uuid']];
$localOrg = $v['Organisation'];
$same = true;
$fieldsToCheck = [
'nationality', 'sector', 'type', 'name'
];
foreach (['nationality', 'sector', 'type', 'name'] as $fieldToCheck) {
if ($remoteOrg[$fieldToCheck] != $localOrg[$fieldToCheck]) {
$same = false;
}
}
$data[$k]['Organisation']['local_copy'] = $same ? 'same' : 'different';
} else {
$data[$k]['Organisation']['local_copy'] = 'not_found';
}
}
return $data;
}
private function __compareSgs(array $data, array $existingSgs): array
{
debug($data);
debug($existingSgs);
foreach ($data as $k => $v) {
$data[$k]['SharingGroup']['local_copy'] = false;
if (!empty($existingOrgs[$v['SharingGroup']['uuid']])) {
$remoteOrg = $existingOrgs[$v['SharingGroup']['uuid']];
$localOrg = $v['SharingGroup'];
$same = true;
$fieldsToCheck = [
'nationality', 'sector', 'type', 'name'
];
foreach (['nationality', 'sector', 'type', 'name'] as $fieldToCheck) {
if ($remoteOrg[$fieldToCheck] != $localOrg[$fieldToCheck]) {
$same = false;
}
}
$data[$k]['SharingGroup']['local_copy'] = $same ? 'same' : 'different';
} else {
$data[$k]['SharingGroup']['local_copy'] = 'not_found';
}
}
return $data;
}
public function organisationsAction(array $params): array public function organisationsAction(array $params): array
{ {
@ -610,25 +844,7 @@ class MispConnector extends CommonConnectorTools
'icon' => 'exclamation-triangle' 'icon' => 'exclamation-triangle'
] ]
]; ];
foreach ($data as $k => $v) { $data = $this->__compareOrgs($data, $existingOrgs);
$data[$k]['Organisation']['local_copy'] = false;
if (!empty($existingOrgs[$v['Organisation']['uuid']])) {
$remoteOrg = $existingOrgs[$v['Organisation']['uuid']];
$localOrg = $v['Organisation'];
$same = true;
$fieldsToCheck = [
'nationality', 'sector', 'type', 'name'
];
foreach (['nationality', 'sector', 'type', 'name'] as $fieldToCheck) {
if ($remoteOrg[$fieldToCheck] != $localOrg[$fieldToCheck]) {
$same = false;
}
}
$data[$k]['Organisation']['local_copy'] = $same ? 'same' : 'different';
} else {
$data[$k]['Organisation']['local_copy'] = 'not_found';
}
}
if (!empty($data)) { if (!empty($data)) {
return [ return [
'type' => 'index', 'type' => 'index',
@ -764,6 +980,35 @@ class MispConnector extends CommonConnectorTools
$urlParams = h($params['connection']['id']) . '/sharingGroupsAction'; $urlParams = h($params['connection']['id']) . '/sharingGroupsAction';
$response = $this->getData('/sharing_groups/index', $params); $response = $this->getData('/sharing_groups/index', $params);
$data = $response->getJson(); $data = $response->getJson();
$temp = $this->getSharingGroups();
$existingOrgs = [];
foreach ($temp as $k => $v) {
$existingSGs[$v['uuid']] = $v;
unset($temp[$k]);
}
$data = $this->__compareSgs($data, $existingSGs);
$existingSGs = [];
foreach ($temp as $k => $v) {
$existingSGs[$v['uuid']] = $v;
unset($temp[$k]);
}
$statusLevels = [
'same' => [
'colour' => 'success',
'message' => __('Remote sharing group is the same as local copy'),
'icon' => 'check-circle'
],
'different' => [
'colour' => 'warning',
'message' => __('Local and remote versions of the sharing groups are different.'),
'icon' => 'exclamation-circle'
],
'not_found' => [
'colour' => 'danger',
'message' => __('Local sharing group not found'),
'icon' => 'exclamation-triangle'
]
];
if (!empty($data)) { if (!empty($data)) {
return [ return [
'type' => 'index', 'type' => 'index',
@ -773,21 +1018,48 @@ class MispConnector extends CommonConnectorTools
'top_bar' => [ 'top_bar' => [
'children' => [ 'children' => [
[ [
'type' => 'search', 'type' => 'simple',
'button' => __('Search'), 'children' => [
'placeholder' => __('Enter value to search'), [
'data' => '', 'class' => 'hidden mass-select',
'searchKey' => 'value', 'text' => __('Fetch selected sharing groups'),
'additionalUrlParams' => $urlParams 'html' => '<i class="fas fa-download"></i> ',
] 'reload_url' => '/localTools/action/' . h($params['connection']['id']) . '/sharingGroupsAction',
'popover_url' => '/localTools/action/' . h($params['connection']['id']) . '/fetchSelectedSharingGroupsAction'
],
[
'text' => __('Push sharing groups'),
'html' => '<i class="fas fa-upload"></i> ',
'class' => 'btn btn-primary',
'reload_url' => '/localTools/action/' . h($params['connection']['id']) . '/SharingGroupsAction',
'popover_url' => '/localTools/action/' . h($params['connection']['id']) . '/pushSharingGroupsAction'
]
]
],
] ]
], ],
'fields' => [ 'fields' => [
[
'element' => 'selector',
'class' => 'short',
'data' => [
'id' => [
'value_path' => 'SharingGroup.uuid'
]
]
],
[ [
'name' => 'Name', 'name' => 'Name',
'sort' => 'SharingGroup.name', 'sort' => 'SharingGroup.name',
'data_path' => 'SharingGroup.name', 'data_path' => 'SharingGroup.name',
], ],
[
'name' => 'Status',
'sort' => 'SharingGroup.local_copy',
'data_path' => 'SharingGroup.local_copy',
'element' => 'status',
'status_levels' => $statusLevels
],
[ [
'name' => 'uuid', 'name' => 'uuid',
'sort' => 'SharingGroup.uuid', 'sort' => 'SharingGroup.uuid',

11
src/Model/Entity/SGO.php Normal file
View File

@ -0,0 +1,11 @@
<?php
namespace App\Model\Entity;
use App\Model\Entity\AppModel;
use Cake\ORM\Entity;
class SGO extends AppModel
{
}

View File

@ -249,7 +249,7 @@ class InstanceTable extends AppTable
} }
$data = [ $data = [
'broods' => $broods, 'broods' => $broods,
'tools' => $LocalToolsModel->extractMeta($connectors, true) 'tools' => $connections
]; ];
if ($mermaid) { if ($mermaid) {
return $this->generateTopologyMermaid($data); return $this->generateTopologyMermaid($data);
@ -319,8 +319,19 @@ class InstanceTable extends AppTable
h($tool['name']) h($tool['name'])
); );
foreach ($tool['connections'] as $k2 => $connection) { foreach ($tool['connections'] as $k2 => $connection) {
$diagnostic_output = '';
if (!empty($connection['diagnostics'])) {
foreach ($connection['diagnostics'] as $diagnostic => $diagnostic_data) {
$diagnostic_output .= sprintf(
"%s: <span class='text-%s'>%s</span><br />",
h($diagnostic),
h($diagnostic_data['type']),
h($diagnostic_data['message'])
);
}
}
$tools .= sprintf( $tools .= sprintf(
" connection%s[%s<br />%s<br />%s]" . PHP_EOL, " connection%s[\"%s<br />%s<br />%s%s\"]" . PHP_EOL,
h($k2), h($k2),
h($connection['name']), h($connection['name']),
sprintf( sprintf(
@ -329,6 +340,7 @@ class InstanceTable extends AppTable
$connection['health'] === 1 ? 'text-success' : 'text-danger', $connection['health'] === 1 ? 'text-success' : 'text-danger',
$connection['health'] === 1 ? 'fas:fa-check' : 'fas:fa-times' $connection['health'] === 1 ? 'fas:fa-check' : 'fas:fa-times'
), ),
empty($diagnostic_data) ? '' : 'Diagnostics:<br />' . $diagnostic_output,
sprintf( sprintf(
"<a href='%s'>fas:fa-eye</a>", "<a href='%s'>fas:fa-eye</a>",
h($connection['url']) h($connection['url'])

View File

@ -147,14 +147,14 @@ class LocalToolsTable extends AppTable
'connector_settings_placeholder' => $connector_class->settingsPlaceholder ?? [], 'connector_settings_placeholder' => $connector_class->settingsPlaceholder ?? [],
]; ];
if ($includeConnections) { if ($includeConnections) {
$connector['connections'] = $this->healthCheck($connector_type, $connector_class); $connector['connections'] = $this->healthCheck($connector_type, $connector_class, true);
} }
$connectors[] = $connector; $connectors[] = $connector;
} }
return $connectors; return $connectors;
} }
public function healthCheck(string $connector_type, Object $connector_class): array public function healthCheck(string $connector_type, Object $connector_class, bool $includeDiagnostics = false): array
{ {
$query = $this->find(); $query = $this->find();
$query->where([ $query->where([
@ -162,11 +162,28 @@ class LocalToolsTable extends AppTable
]); ]);
$connections = $query->all()->toList(); $connections = $query->all()->toList();
foreach ($connections as &$connection) { foreach ($connections as &$connection) {
$connection = $this->healthCheckIndividual($connection); $temp = $this->healthCheckIndividual($connection);
if ($includeDiagnostics && !empty($temp['health']) && $temp['health'] === 1) {
$temp['diagnostics'] = $this->diagnosticCheckIndividual($connection);
}
$connection = $temp;
} }
return $connections; return $connections;
} }
public function diagnosticCheckIndividual(Object $connection): array
{
$connector_class = $this->getConnectors($connection->connector);
if (empty($connector_class[$connection->connector])) {
return [];
}
$connector_class = $connector_class[$connection->connector];
return $connector_class->diagnostics([
'connection' => $connection,
'softError' => 1
]);
}
public function healthCheckIndividual(Object $connection): array public function healthCheckIndividual(Object $connection): array
{ {
$connector_class = $this->getConnectors($connection->connector); $connector_class = $this->getConnectors($connection->connector);

View File

@ -0,0 +1,53 @@
<?php
namespace App\Model\Table;
use App\Model\Table\AppTable;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Cake\ORM\RulesChecker;
use Cake\ORM\TableRegistry;
class SGOsTable extends AppTable
{
public function initialize(array $config): void
{
$this->setTable('sgo');
parent::initialize($config);
$this->belongsTo('SharingGroups');
$this->belongsTo('Organisations');
}
public function attach(int $sg_id, int $org_id, array $additional_data = []): bool
{
$sgo = $this->find()->where([
'sharing_group_id' => $sg_id,
'organisation_id' => $org_id
])->first();
if (empty($sgo)) {
$sgo = $this->newEmptyEntity();
$sgo->sharing_group_id = $sg_id;
$sgo->organisation_id = $org_id;
}
$sgo->extend = empty($additional_data['extend']) ? 0 : 1;
if ($this->save($sgo)) {
return true;
}
return false;
}
public function detach(): bool
{
$sgo = $this->find()->where([
'sharing_group_id' => $sg_id,
'organisation_id' => $org_id
])->first();
if (!empty($sgo)) {
if (!$this->delete($sgo)) {
return false;
}
}
return true;
}
}

View File

@ -25,11 +25,11 @@ class SharingGroupsTable extends AppTable
$this->belongsToMany( $this->belongsToMany(
'SharingGroupOrgs', 'SharingGroupOrgs',
[ [
'through' => 'SGOs',
'className' => 'Organisations', 'className' => 'Organisations',
'foreignKey' => 'sharing_group_id', 'foreignKey' => 'sharing_group_id',
'joinTable' => 'sgo',
'targetForeignKey' => 'organisation_id' 'targetForeignKey' => 'organisation_id'
] ],
); );
$this->setDisplayField('name'); $this->setDisplayField('name');
} }
@ -77,11 +77,14 @@ class SharingGroupsTable extends AppTable
public function postCaptureActions($savedEntity, $input): void public function postCaptureActions($savedEntity, $input): void
{ {
$orgs = []; $additional_data = [];
if (!empty($input['extend'])) {
$additional_data['extend'] = $input['extend'];
}
$this->SGO = TableRegistry::getTableLocator()->get('SGOs');
foreach ($input['sharing_group_orgs'] as $sgo) { foreach ($input['sharing_group_orgs'] as $sgo) {
$organisation_id = $this->Organisations->captureOrg($sgo); $organisation_id = $this->Organisations->captureOrg($sgo);
$orgs[] = $this->SharingGroupOrgs->get($organisation_id); $this->SGO->attach($savedEntity->id, $organisation_id, $additional_data);
} }
$this->SharingGroupOrgs->link($savedEntity, $orgs);
} }
} }

View File

@ -9,6 +9,11 @@
'label' => __('Owner organisation'), 'label' => __('Owner organisation'),
'options' => $dropdownData['organisation'] 'options' => $dropdownData['organisation']
], ],
[
'field' => 'extend',
'type' => 'checkbox',
'label' => __('Can extend/administer')
],
], ],
'submit' => [ 'submit' => [
'action' => $this->request->getParam('action') 'action' => $this->request->getParam('action')

View File

@ -42,6 +42,13 @@ echo $this->element('genericElements/IndexTable/index_table', [
'sort' => 'uuid', 'sort' => 'uuid',
'class' => 'short', 'class' => 'short',
'data_path' => 'uuid', 'data_path' => 'uuid',
],
[
'name' => __('Can extend/administer'),
'sort' => 'extend',
'element' => 'boolean',
'class' => 'short',
'data_path' => 'extend',
] ]
], ],
'pull' => 'right', 'pull' => 'right',