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

remotes/origin/main
Alexandre Dulaunoy 2020-06-04 12:25:59 +02:00
commit b53001654f
No known key found for this signature in database
GPG Key ID: 09E2CD4944E6CBCD
25 changed files with 338 additions and 80 deletions

View File

@ -18,7 +18,6 @@ return [
* Development Mode:
* true: Errors and warnings shown.
*/
'debug' => filter_var(env('DEBUG', true), FILTER_VALIDATE_BOOLEAN),
/*
* Configure basic information about the application.

View File

@ -24,6 +24,7 @@ use Cake\Utility\Text;
use Cake\Http\Exception\NotFoundException;
use Cake\Http\Exception\MethodNotAllowedException;
use Cake\Http\Exception\ForbiddenException;
use Cake\Error\Debugger;
/**
* Application Controller
@ -101,4 +102,30 @@ class AppController extends Controller
$uuid = Text::uuid();
return $this->RestResponse->viewData(['uuid' => $uuid], 'json');
}
/*
* Harvest parameters form a request
*
* Requires the request object and a list of keys to filter as input
* Order of precedence:
* ordered uri components (/foo/bar/baz) > query strings (?foo=bar) > posted data (json body {"foo": "bar"})
*/
protected function _harvestParams(\Cake\Http\ServerRequest $request, array $filterList): array
{
$parsedParams = array();
foreach ($filterList as $k => $filter) {
if (isset($request->getAttribute('params')['pass'][$k])) {
$parsedParams[$filter] = $request->getAttribute('params')['pass'][$k];
continue;
}
if (($request->getQuery($filter)) !== null) {
$parsedParams[$filter] = $request->getQuery($filter);
continue;
}
if (($this->request->is('post') || $this->request->is('put')) && $this->request->getData($filter) !== null) {
$parsedParams[$filter] = $this->request->getData($filter);
}
}
return $parsedParams;
}
}

View File

@ -14,15 +14,17 @@ use Cake\Error\Debugger;
class EncryptionKeysController extends AppController
{
public function index($owner_type = null, $owner_id = null)
public function index()
{
$params = $this->_harvestParams($this->request, ['owner_type', 'owner_id']);
$query = $this->EncryptionKeys->find();
if (!empty($owner_type)) {
$query->where(['owner_type' => $owner_type]);
if (!empty($params['owner_type'])) {
$query->where(['owner_type' => $params['owner_type']]);
}
if (!empty($owner_id)) {
$query->where(['owner_id' => $owner_id]);
if (!empty($params['owner_id'])) {
$query->where(['owner_id' => $params['owner_id']]);
}
$query->contain(['Individuals', 'Organisations']);
if ($this->_isRest()) {
$alignments = $query->all();
return $this->RestResponse->viewData($alignments, 'json');
@ -37,15 +39,15 @@ class EncryptionKeysController extends AppController
public function delete($id)
{
if (empty($id)) {
throw new NotFoundException(__('Invalid encrpyion keys.'));
throw new NotFoundException(__('Invalid encryption key.'));
}
$individual = $this->Alignments->get($id);
$key = $this->EncryptionKeys->get($id);
if ($this->request->is('post') || $this->request->is('delete')) {
if ($this->Alignments->delete($individual)) {
$message = __('Individual deleted.');
if ($this->EncryptionKey->delete($individual)) {
$message = __('Encryption key deleted.');
if ($this->_isRest()) {
$individual = $this->Alignments->get($id);
return $this->RestResponse->saveSuccessResponse('Alignments', 'delete', $id, 'json', $message);
$individual = $this->EncryptionKeys->get($id);
return $this->RestResponse->saveSuccessResponse('EncryptionKeys', 'delete', $id, 'json', $message);
} else {
$this->Flash->success($message);
return $this->redirect($this->referer());
@ -53,41 +55,50 @@ class EncryptionKeysController extends AppController
}
}
$this->set('metaGroup', 'ContactDB');
$this->set('scope', 'alignments');
$this->set('id', $individual['id']);
$this->set('alignment', $individual);
$this->set('scope', 'encryptionKeys');
$this->set('id', $key['id']);
$this->set('key', $key);
$this->viewBuilder()->setLayout('ajax');
$this->render('/genericTemplates/delete');
}
public function add($owner_type = false, $owner_id = false)
public function add()
{
if (empty($owner_type) && !empty($this->request->getData('owner_type'))) {
$owner_type = $this->request->getData('owner_type');
}
if (empty($owner_id) && !empty($this->request->getData('owner_id'))) {
$owner_id = $this->request->getData('owner_id');
}
if (empty($owner_type) || empty($owner_id)) {
throw new NotAcceptableException(__('Invalid input. owner_type and owner_id expected as parameters in the format /encryption_keys/add/[owner_type]/[owner_id] or passed as a JSON.'));
}
if ($owner_type === 'individual') {
$this->loadModel('Individuals');
$owner = $this->Individuals->find()->where(['id' => $owner_id])->first();
if (empty($owner)) {
throw new NotFoundException(__('Invalid owner individual.'));
}
} else {
$this->loadModel('Organisations');
$owner = $this->Organisations->find()->where(['id' => $owner_id])->first();
if (empty($owner)) {
throw new NotFoundException(__('Invalid owner individual.'));
}
}
$params = $this->_harvestParams($this->request, ['owner_type', 'owner_id', 'organisation_id', 'individual_id', 'encryption_key', 'expires', 'uuid', 'revoked', 'type']);
$input = $this->request->getData();
$encryptionKey = $this->EncryptionKeys->newEmptyEntity();
if (!empty($params['owner_type'])) {
if (!empty($params[$params['owner_type'] . '_id'])) {
$params['owner_id'] = $params[$params['owner_type'] . '_id'];
}
$params[$params['owner_type'] . '_id'] = $params['owner_id'];
}
$this->loadModel('Organisations');
$this->loadModel('Individuals');
$dropdownData = [
'organisation' => $this->Organisations->find('list', [
'sort' => ['name' => 'asc']
]),
'individual' => $this->Individuals->find('list', [
'sort' => ['email' => 'asc']
])
];
if ($this->request->is('post')) {
$this->EncryptionKeys->patchEntity($encryptionKey, $this->request->getData());
$encrypionKey['owner_type'] = $owner_type;
if (empty($params['owner_type']) || empty($params['owner_id'])) {
throw new NotAcceptableException(__('Invalid input. owner_type and owner_id expected as parameters in the format /encryption_keys/add/[owner_type]/[owner_id] or passed as a JSON.'));
}
if ($params['owner_type'] === 'individual') {
$owner = $this->Individuals->find()->where(['id' => $params['owner_id']])->first();
if (empty($owner)) {
throw new NotFoundException(__('Invalid owner individual.'));
}
} else {
$owner = $this->Organisations->find()->where(['id' => $params['owner_id']])->first();
if (empty($owner)) {
throw new NotFoundException(__('Invalid owner organisation.'));
}
}
$encryptionKey = $this->EncryptionKeys->patchEntity($encryptionKey, $params);
if ($this->EncryptionKeys->save($encryptionKey)) {
$message = __('EncryptionKey added.');
if ($this->_isRest()) {
@ -95,7 +106,7 @@ class EncryptionKeysController extends AppController
return $this->RestResponse->viewData($encryptionKey, 'json');
} else {
$this->Flash->success($message);
$this->redirect($this->referer());
return $this->redirect(['action' => 'index']);
}
} else {
$message = __('EncryptionKey could not be added.');
@ -107,11 +118,8 @@ class EncryptionKeysController extends AppController
}
}
}
$this->set(compact('owner'));
$this->set(compact('encryptionKey'));
$this->set(compact('owner_id'));
$this->set(compact('owner_type'));
$this->set('encryptionKey', $encryptionKey);
$this->set(compact('dropdownData'));
$this->set('metaGroup', 'ContactDB');
}
}

View File

@ -25,6 +25,7 @@ class EncryptionKeysTable extends AppTable
'conditions' => ['owner_type' => 'organisation']
]
);
$this->setDisplayField('encryption_key');
}
public function validationDefault(Validator $validator): Validator

View File

@ -11,7 +11,13 @@ class IndividualsTable extends AppTable
public function initialize(array $config): void
{
parent::initialize($config);
$this->hasMany('Alignments');
$this->hasMany(
'Alignments',
[
'dependent' => true,
'cascadeCallbacks' => true
]
);
$this->hasMany(
'EncryptionKeys',
[
@ -19,6 +25,7 @@ class IndividualsTable extends AppTable
'conditions' => ['owner_type' => 'individual']
]
);
$this->setDisplayField('email');
}
public function validationDefault(Validator $validator): Validator

View File

@ -11,7 +11,13 @@ class OrganisationsTable extends AppTable
public function initialize(array $config): void
{
parent::initialize($config);
$this->hasMany('Alignments');
$this->hasMany(
'Alignments',
[
'dependent' => true,
'cascadeCallbacks' => true
]
);
$this->hasMany(
'EncryptionKeys',
[
@ -19,6 +25,7 @@ class OrganisationsTable extends AppTable
'conditions' => ['owner_type' => 'organisation']
]
);
$this->setDisplayField('name');
}
public function validationDefault(Validator $validator): Validator

View File

@ -40,6 +40,7 @@ class AppView extends View
{
parent::initialize();
$this->loadHelper('Hash');
$this->loadHelper('FormFieldMassage');
$this->loadHelper('Paginator', ['templates' => 'cerebrate-pagination-templates']);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\View\Helper;
use Cake\View\Helper;
class FormFieldMassageHelper extends Helper
{
public function prepareFormElement(\Cake\View\Helper\FormHelper $form, array $controlParams, array $fieldData): string
{
if (!empty($fieldData['stateDependence'])) {
$controlParams['data-dependence-source'] = h($fieldData['stateDependence']['source']);
$controlParams['data-dependence-option'] = h($fieldData['stateDependence']['option']);
}
$controlParams['id'] = $fieldData['field'] . '-field';
$formFieldElement = $form->control($fieldData['field'], $controlParams);
if (!empty($fieldData['hidden'])) {
$formFieldElement = '<span class="hidden">' . $formFieldElement . '</span>';
}
return $formFieldElement;
}
}

View File

@ -4,17 +4,44 @@ echo $this->element('genericElements/Form/genericForm', array(
'form' => $this->Form,
'data' => array(
'entity' => $encryptionKey,
'title' => __('Add new encryption key for {0} #{1} ({2})', h($owner_type), h($owner_id), h($owner[$primaryIdentifiers[$owner_type]])),
'title' => __('Add new encryption key'),
'description' => __('Alignments indicate that an individual belongs to an organisation in one way or another. The type of relationship is defined by the type field.'),
'model' => 'Organisations',
'fields' => array(
array(
'field' => 'owner_type',
'label' => __('Owner type'),
'options' => array_combine(array_keys($dropdownData), array_keys($dropdownData)),
'type' => 'dropdown'
),
array(
'field' => 'organisation_id',
'label' => __('Owner organisation'),
'options' => $dropdownData['organisation'],
'type' => 'dropdown',
'stateDependence' => [
'source' => '#owner_type-field',
'option' => 'organisation'
]
),
array(
'field' => 'individual_id',
'label' => __('Owner individual'),
'options' => $dropdownData['individual'],
'type' => 'dropdown',
'stateDependence' => [
'source' => '#owner_type-field',
'option' => 'individual'
]
),
array(
'field' => 'uuid',
'type' => 'uuid'
),
array(
'field' => 'type',
'options' => array('pgp' => 'PGP', 'smime' => 'S/MIME')
'options' => array('pgp' => 'PGP', 'smime' => 'S/MIME'),
'type' => 'dropdown'
),
array(
'field' => 'encryption_key',

View File

@ -0,0 +1,89 @@
<?php
echo $this->element('genericElements/IndexTable/index_table', [
'data' => [
'row_modifier' => function(App\Model\Entity\EncryptionKey $row): string {
return !empty($row['revoked']) ? 'text-light bg-secondary' : '';
},
'data' => $data,
'top_bar' => [
'pull' => 'right',
'children' => [
[
'type' => 'simple',
'children' => [
'data' => [
'type' => 'simple',
'text' => __('Add encryption key'),
'class' => 'btn btn-primary',
'popover_url' => '/encryptionKeys/add'
]
]
],
[
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'value'
]
]
],
'fields' => [
[
'name' => '#',
'sort' => 'id',
'data_path' => 'id',
],
[
'name' => __('Owner type'),
'sort' => 'owner_type',
'data_path' => 'owner_type',
],
[
'name' => __('Owner ID'),
'sort' => 'owner_id',
'data_path' => 'owner_id',
],
[
'name' => __('Owner'),
'data_path' => 'owner_id',
'owner_type_path' => 'owner_type',
'element' => 'owner'
],
[
'name' => __('Revoked'),
'sort' => 'revoked',
'data_path' => 'revoked',
'element' => 'boolean'
],
[
'name' => __('Expires'),
'sort' => 'expires',
'data_path' => 'expires',
'element' => 'timestamp'
],
[
'name' => __('Key'),
'data_path' => 'encryption_key',
'privacy' => 1
],
],
'title' => __('Encryption key Index'),
'description' => __('A list encryption / signing keys that are bound to an individual or an organsiation.'),
'pull' => 'right',
'actions' => [
[
'url' => '/endcrpyionKeys/view',
'url_params_data_paths' => ['id'],
'icon' => 'eye'
],
[
'onclick' => 'populateAndLoadModal(\'/encryptionKeys/delete/[onclick_params_data_path]\');',
'onclick_params_data_path' => 'id',
'icon' => 'trash'
]
]
]
]);
echo '</div>';
?>

View File

@ -1,8 +1,4 @@
<?php
$params['div'] = false;
$temp = $form->control($fieldData['field'], $params);
if (!empty($fieldData['hidden'])) {
$temp = '<span class="hidden">' . $temp . '</span>';
}
echo $temp;
echo $this->FormFieldMassage->prepareFormElement($this->Form, $params, $fieldData);
?>

View File

@ -0,0 +1,6 @@
<?php
$controlParams = [
'options' => $fieldData['options'],
'class' => ($fieldData['class'] ?? '') . ' formDropdown custom-select'
];
echo $this->FormFieldMassage->prepareFormElement($this->Form, $controlParams, $fieldData);

View File

@ -1,9 +1,5 @@
<?php
$params['div'] = false;
$params['class'] .= ' form-control';
$temp = $form->control($fieldData['field'], $params);
if (!empty($fieldData['hidden'])) {
$temp = '<span class="hidden">' . $temp . '</span>';
}
echo $temp;
echo $this->FormFieldMassage->prepareFormElement($this->Form, $params, $fieldData);
?>

View File

@ -7,6 +7,7 @@
'formGroup' => '{{input}}',
]);
$label = $fieldData['label'];
$formElement = $this->FormFieldMassage->prepareFormElement($this->Form, $params, $fieldData);
$temp = sprintf(
'<div class="form-group row ">
<div class="col-sm-2 col-form-label">%s</div>
@ -17,7 +18,7 @@
</div>
</div>',
h($label),
$form->control($fieldData['field'], $params),
$formElement,
sprintf(
'<span id="uuid-gen-%s" class="btn btn-secondary">%s</span>',
$random,
@ -31,7 +32,7 @@
$('#uuid-gen-<?= h($random) ?>').on('click', function() {
$.ajax({
success:function (data, textStatus) {
$('#uuid').val(data["uuid"]);
$('#uuid-field').val(data["uuid"]);
},
type: "get",
cache: false,

View File

@ -17,7 +17,7 @@
h($data['model']);
$fieldsString = '';
$simpleFieldWhitelist = array(
'default', 'type', 'options', 'placeholder', 'label', 'empty', 'rows', 'div', 'required'
'default', 'type', 'placeholder', 'label', 'empty', 'rows', 'div', 'required'
);
$fieldsArrayForPersistence = array();
if (empty($data['url'])) {
@ -34,7 +34,7 @@
'select' => '<select name="{{name}}" {{attrs}}>{{content}}</select>',
'checkbox' => '<input type="checkbox" name="{{name}}" value="{{value}}"{{attrs}}>',
'checkboxFormGroup' => '{{label}}',
'checkboxWrapper' => '<div class="checkbox">{{label}}</div>',
'select' => '<select name="{{name}}" {{attrs}}>{{content}}</select>',
'formGroup' => '<div class="col-sm-2 col-form-label" {{attrs}}>{{label}}</div><div class="col-sm-10">{{input}}</div>',
'nestingLabel' => '{{hidden}}<div class="col-sm-2 col-form-label">{{text}}</div><div class="col-sm-10">{{input}}</div>',
];
@ -80,15 +80,14 @@
$params['class'] .= ' form-control';
}
//$params['class'] = sprintf('form-control %s', $params['class']);
foreach ($simpleFieldWhitelist as $f) {
if (!empty($fieldData[$f])) {
$params[$f] = $fieldData[$f];
foreach ($fieldData as $k => $fd) {
if (in_array($k, $simpleFieldWhitelist) || strpos($k, 'data-') === 0) {
$params[$k] = $fd;
}
}
$temp = $this->element('genericElements/Form/Fields/' . $fieldTemplate, array(
'fieldData' => $fieldData,
'params' => $params,
'form' => $this->Form
'params' => $params
));
if (!empty($fieldData['hidden'])) {
$temp = '<span class="hidden">' . $temp . '</span>';
@ -158,3 +157,11 @@
);
}
?>
<script type="text/javascript">
$(document).ready(function() {
executeStateDependencyChecks();
$('.formDropdown').on('change', function() {
executeStateDependencyChecks('#' + this.id);
})
});
</script>

View File

@ -82,7 +82,7 @@
}
echo sprintf(
'<a href="%s" title="%s" aria-label="%s" %s %s><i class="black %s"></i></a> ',
'<a href="%s" title="%s" aria-label="%s" %s %s class="link-unstyled"><i class="%s"></i></a> ',
$url,
empty($action['title']) ? '' : h($action['title']),
empty($action['title']) ? '' : h($action['title']),

View File

@ -0,0 +1,32 @@
<?php
$type = $this->Hash->extract($row, $field['owner_type_path'])[0];
$owner = $row[$type];
$types = [
'individual' => [
'uri' => 'individuals',
'name' => __('Individual'),
'identityField' => 'email'
],
'organisation' => [
'uri' => 'organisations',
'name' => __('Organisation'),
'identityField' => 'name'
]
];
echo sprintf(
'<span class="font-weight-bold">%s</span>: %s',
$types[$type]['name'],
$this->Html->link(
sprintf(
'(%s) %s',
h($owner['id']),
h($owner[$types[$type]['identityField']])
),
['controller' => $types[$type]['uri'], 'action' => 'view', $owner['id']],
[
'class' => 'link-unstyled'
]
)
);
?>

View File

@ -1,7 +1,9 @@
<?php
$timestamp = $this->Hash->extract($row, $field['data_path'])[0];
if (!empty($field['time_format'])) {
$timestamp = date($field['time_format'], $timestamp);
if (!empty($this->Hash->extract($row, $field['data_path']))) {
$timestamp = $this->Hash->extract($row, $field['data_path'])[0];
if (!empty($field['time_format'])) {
$timestamp = date($field['time_format'], $timestamp);
}
echo h($timestamp);
}
echo h($timestamp);
?>

View File

@ -68,10 +68,12 @@
$dbclickAction = sprintf("changeLocationFromIndexDblclick(%s)", $k);
}
$rows .= sprintf(
'<tr data-row-id="%s" %s %s>%s</tr>',
'<tr data-row-id="%s" %s %s class="%s %s">%s</tr>',
h($k),
empty($dbclickAction) ? '' : 'ondblclick="' . $dbclickAction . '"',
empty($primary) ? '' : 'data-primary-id="' . $primary . '"',
empty($data['row_modifier']) ? '' : h($data['row_modifier']($data_row)),
empty($data['class']) ? '' : h($data['row_class']),
$this->element(
'/genericElements/IndexTable/' . $row_element,
array(

View File

@ -25,7 +25,8 @@
'data_path' => empty($field['data_path']) ? '' : $field['data_path'],
'k' => $k,
'primary' => $primary,
'tableRandomValue' => $tableRandomValue
'tableRandomValue' => $tableRandomValue,
'stateDependence' => isset($field['stateDependence']) ? $field['stateDependence'] : []
)
);
}

View File

@ -1,6 +1,12 @@
<?php
if (!isset($data['requirement']) || $data['requirement']) {
if (!empty($data['onClick']) || empty($data['url'])) {
if (!empty($data['popover_url'])) {
$onClick = sprintf(
'onClick="populateAndLoadModal(%s)"',
sprintf("'%s'", h($data['popover_url']))
);
}
if (empty($onClick) && (!empty($data['onClick']) || empty($data['url']))) {
$onClickParams = array();
if (!empty($data['onClickParams'])) {
foreach ($data['onClickParams'] as $param) {
@ -34,7 +40,7 @@
}
$dataFields = implode(' ', $dataFields);
echo sprintf(
'<a class="btn btn-small %s %s" %s href="%s" %s %s %s %s %s>%s%s%s</a>',
'<button class="btn btn-small %s %s" %s href="%s" %s %s %s %s %s>%s%s%s</button>',
empty($data['class']) ? '' : h($data['class']),
empty($data['active']) ? 'btn-inverse' : 'btn-primary', // Change the default class for highlighted/active toggles here
empty($data['id']) ? '' : 'id="' . h($data['id']) . '"',

View File

@ -138,7 +138,7 @@
);
echo sprintf(
'<nav class="navbar navbar-expand-lg navbar-dark fixed-top bg-dark">%s%s%s</nav>',
'<nav class="navbar navbar-expand-lg navbar-dark bg-dark">%s%s%s</nav>',
$homeButton,
'<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>',
$navdata

View File

@ -43,7 +43,7 @@ $cakeDescription = 'Cerebrate';
<?= $this->element('genericElements/header') ?>
</header>
<main role="main" class="container-fluid">
<div class="container-fluid" style="padding:0px;padding-top:70px;">
<div class="container-fluid">
<div class="row">
<div class="col-md-2 d-none d-md-block col-lg-1 bg-light sidebar" style="padding:0px;margin:0px;">
<?= $this->element('/genericElements/SideMenu/side_menu') ?>

View File

@ -109,3 +109,8 @@
.text-black {color:black;}
.text-white {color:white;}
.link-unstyled, .link-unstyled:link, .link-unstyled:hover {
color: inherit;
text-decoration: inherit;
}

View File

@ -21,3 +21,19 @@ function executePagination(randomValue, url) {
url:url,
});
}
function executeStateDependencyChecks(dependenceSourceSelector) {
if (dependenceSourceSelector === undefined) {
var tempSelector = "[data-dependence-source]";
} else {
var tempSelector = '*[data-dependence-source="' + dependenceSourceSelector + '"]';
}
$(tempSelector).each(function(index, dependent) {
var dependenceSource = $(dependent).data('dependence-source');
if ($(dependent).data('dependence-option') === $(dependenceSource).val()) {
$(dependent).parent().parent().removeClass('d-none');
} else {
$(dependent).parent().parent().addClass('d-none');
}
});
}