Merge pull request #39 from mokaddem/feature-updater

[new] Updater system
connector
Andras Iklody 2021-03-19 13:00:32 +01:00 committed by GitHub
commit c4a41bebd9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 626 additions and 74 deletions

View File

@ -704,6 +704,10 @@ class ACLComponent extends Component
'home' => [
'url' => '/instance/home',
'label' => __('Home')
],
'migration' => [
'url' => '/instance/migrationIndex',
'label' => __('Database migration')
]
]
],

View File

@ -6,12 +6,18 @@ use App\Controller\AppController;
use Cake\Utility\Hash;
use Cake\Utility\Text;
use \Cake\Database\Expression\QueryExpression;
use Cake\Event\EventInterface;
class InstanceController extends AppController
{
public function beforeFilter(EventInterface $event)
{
parent::beforeFilter($event);
$this->set('metaGroup', !empty($this->isAdmin) ? 'Cerebrate' : 'Administration');
}
public function home()
{
$this->set('metaGroup', $this->isAdmin ? 'Administration' : 'Cerebrate');
$this->set('md', file_get_contents(ROOT . '/README.md'));
}
@ -22,4 +28,77 @@ class InstanceController extends AppController
$data['user'] = $this->ACL->getUser();
return $this->RestResponse->viewData($data, 'json');
}
public function migrationIndex()
{
$migrationStatus = $this->Instance->getMigrationStatus();
$this->loadModel('Phinxlog');
$status = $this->Phinxlog->mergeMigrationLogIntoStatus($migrationStatus['status']);
$this->set('status', $status);
$this->set('updateAvailables', $migrationStatus['updateAvailables']);
}
public function migrate($version=null) {
if ($this->request->is('post')) {
if (is_null($version)) {
$migrateResult = $this->Instance->migrate();
} else {
$migrateResult = $this->Instance->migrate(['target' => $version]);
}
if ($this->ParamHandler->isRest() || $this->ParamHandler->isAjax()) {
if ($migrateResult['success']) {
return $this->RestResponse->saveSuccessResponse('instance', 'migrate', false, false, __('Migration sucessful'));
} else {
return $this->RestResponse->saveFailResponse('instance', 'migrate', false, $migrateResult['error']);
}
} else {
if ($migrateResult['success']) {
$this->Flash->success(__('Migration sucessful'));
$this->redirect(['action' => 'migrationIndex']);
} else {
$this->Flash->error(__('Migration fail'));
$this->redirect(['action' => 'migrationIndex']);
}
}
}
$migrationStatus = $this->Instance->getMigrationStatus();
$this->set('title', __n('Run database update?', 'Run all database updates?', count($migrationStatus['updateAvailables'])));
$this->set('question', __('The process might take some time.'));
$this->set('actionName', __n('Run update', 'Run all updates', count($migrationStatus['updateAvailables'])));
$this->set('path', ['controller' => 'instance', 'action' => 'migrate']);
$this->render('/genericTemplates/confirm');
}
public function rollback($version=null) {
if ($this->request->is('post')) {
if (is_null($version)) {
$migrateResult = $this->Instance->rollback();
} else {
$migrateResult = $this->Instance->rollback(['target' => $version]);
}
if ($this->ParamHandler->isRest() || $this->ParamHandler->isAjax()) {
if ($migrateResult['success']) {
return $this->RestResponse->saveSuccessResponse('instance', 'rollback', false, false, __('Rollback sucessful'));
} else {
return $this->RestResponse->saveFailResponse('instance', 'rollback', false, $migrateResult['error']);
}
} else {
if ($migrateResult['success']) {
$this->Flash->success(__('Rollback sucessful'));
$this->redirect(['action' => 'migrationIndex']);
} else {
$this->Flash->error(__('Rollback fail'));
$this->redirect(['action' => 'migrationIndex']);
}
}
}
$migrationStatus = $this->Instance->getMigrationStatus();
$this->set('title', __('Run database rollback?'));
$this->set('question', __('The process might take some time.'));
$this->set('actionName', __('Run rollback'));
$this->set('path', ['controller' => 'instance', 'action' => 'rollback']);
$this->render('/genericTemplates/confirm');
}
}

View File

@ -5,6 +5,7 @@ namespace App\Model\Table;
use App\Model\Table\AppTable;
use Cake\ORM\Table;
use Cake\Validation\Validator;
use Migrations\Migrations;
class InstanceTable extends AppTable
{
@ -17,4 +18,43 @@ class InstanceTable extends AppTable
{
return $validator;
}
public function getMigrationStatus()
{
$migrations = new Migrations();
$status = $migrations->status();
$status = array_reverse($status);
$updateAvailables = array_filter($status, function ($update) {
return $update['status'] != 'up';
});
return [
'status' => $status,
'updateAvailables' => $updateAvailables,
];
}
public function migrate($version=null) {
$migrations = new Migrations();
if (is_null($version)) {
$migrationResult = $migrations->migrate();
} else {
$migrationResult = $migrations->migrate(['target' => $version]);
}
return [
'success' => true
];
}
public function rollback($version=null) {
$migrations = new Migrations();
if (is_null($version)) {
$migrationResult = $migrations->rollback();
} else {
$migrationResult = $migrations->rollback(['target' => $version]);
}
return [
'success' => true
];
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Model\Table;
use App\Model\Table\AppTable;
use Cake\ORM\Table;
class PhinxlogTable extends AppTable
{
public function initialize(array $config): void
{
parent::initialize($config);
}
public function mergeMigrationLogIntoStatus(array $status): array
{
$logs = $this->find('list', [
'keyField' => 'version',
'valueField' => function ($entry) {
return $entry;
}
])->toArray();
foreach ($status as &$entry) {
if (!empty($logs[$entry['id']])) {
$logEntry = $logs[$entry['id']];
$startTime = $logEntry['start_time'];
$startTime->setToStringFormat('yyyy-MM-dd HH:mm:ss');
$endTime = $logEntry['end_time'];
$endTime->setToStringFormat('yyyy-MM-dd HH:mm:ss');
$timeTaken = $logEntry['end_time']->diff($logEntry['start_time']);
$timeTakenFormated = sprintf('%s min %s sec',
floor(abs($logEntry['end_time']->getTimestamp() - $logEntry['start_time']->getTimestamp()) / 60),
abs($logEntry['end_time']->getTimestamp() - $logEntry['start_time']->getTimestamp()) % 60
);
} else {
$startTime = 'N/A';
$endTime = 'N/A';
$timeTaken = 'N/A';
$timeTakenFormated = 'N/A';
}
$entry['start_time'] = $startTime;
$entry['end_time'] = $endTime;
$entry['time_taken'] = $timeTaken;
$entry['time_taken_formated'] = $timeTakenFormated;
}
return $status;
}
}

View File

@ -52,9 +52,78 @@ class BootstrapHelper extends Helper
$bsTabs = new BootstrapTabs($options);
return $bsTabs->tabs();
}
public function alert($options)
{
$bsAlert = new BoostrapAlert($options);
return $bsAlert->alert();
}
public function table($options, $data)
{
$bsTable = new BoostrapTable($options, $data);
return $bsTable->table();
}
public function button($options)
{
$bsButton = new BoostrapButton($options);
return $bsButton->button();
}
}
class BootstrapTabs extends Helper
class BootstrapGeneric
{
public static $variants = ['primary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'white', 'transparent'];
protected $allowedOptionValues = [];
protected $options = [];
protected function checkOptionValidity()
{
foreach ($this->allowedOptionValues as $option => $values) {
if (!isset($this->options[$option])) {
throw new InvalidArgumentException(__('Option `{0}` should have a value', $option));
}
if (!in_array($this->options[$option], $values)) {
throw new InvalidArgumentException(__('Option `{0}` is not a valid option for `{1}`. Accepted values: {2}', json_encode($this->options[$option]), $option, json_encode($values)));
}
}
}
protected static function genNode($node, $params=[], $content="")
{
return sprintf('<%s %s>%s</%s>', $node, BootstrapGeneric::genHTMLParams($params), $content, $node);
}
protected static function openNode($node, $params=[])
{
return sprintf('<%s %s>', $node, BootstrapGeneric::genHTMLParams($params));
}
protected static function closeNode($node)
{
return sprintf('</%s>', $node);
}
protected static function genHTMLParams($params)
{
$html = '';
foreach ($params as $k => $v) {
$html .= BootstrapGeneric::genHTMLParam($k, $v) . ' ';
}
return $html;
}
protected static function genHTMLParam($paramName, $values)
{
if (!is_array($values)) {
$values = [$values];
}
return sprintf('%s="%s"', $paramName, implode(' ', $values));
}
}
class BootstrapTabs extends BootstrapGeneric
{
private $defaultOptions = [
'fill' => false,
@ -73,17 +142,14 @@ class BootstrapTabs extends Helper
'content' => [],
],
];
private $allowedOptionValues = [
'justify' => [false, 'center', 'end'],
'body-variant' => ['primary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'white', 'transparent', ''],
'header-variant' => ['primary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'white', 'transparent'],
];
private $options = null;
private $bsClasses = null;
function __construct($options) {
$this->allowedOptionValues = [
'justify' => [false, 'center', 'end'],
'body-variant' => array_merge(BootstrapGeneric::$variants, ['']),
'header-variant' => BootstrapGeneric::$variants,
];
$this->processOptions($options);
}
@ -97,6 +163,9 @@ class BootstrapTabs extends Helper
$this->options = array_merge($this->defaultOptions, $options);
$this->data = $this->options['data'];
$this->checkOptionValidity();
if (empty($this->data['navs'])) {
throw new InvalidArgumentException(__('No navigation data provided'));
}
$this->bsClasses = [
'nav' => [],
'nav-item' => $this->options['nav-item-class'],
@ -162,21 +231,6 @@ class BootstrapTabs extends Helper
}
}
private function checkOptionValidity()
{
foreach ($this->allowedOptionValues as $option => $values) {
if (!isset($this->options[$option])) {
throw new InvalidArgumentException(__('Option `{0}` should have a value', $option));
}
if (!in_array($this->options[$option], $values)) {
throw new InvalidArgumentException(__('Option `{0}` is not a valid option for `{1}`. Accepted values: {2}', json_encode($this->options[$option]), $option, json_encode($values)));
}
}
if (empty($this->data['navs'])) {
throw new InvalidArgumentException(__('No navigation data provided'));
}
}
private function genTabs()
{
$html = '';
@ -192,55 +246,55 @@ class BootstrapTabs extends Helper
{
$html = '';
if ($this->options['card']) {
$html .= $this->genNode('div', ['class' => array_merge(['card'], ["border-{$this->options['header-border-variant']}"])]);
$html .= $this->genNode('div', ['class' => array_merge(['card-header'], ["bg-{$this->options['header-variant']}", "text-{$this->options['header-text-variant']}"])]);
$html .= $this->openNode('div', ['class' => array_merge(['card'], ["border-{$this->options['header-border-variant']}"])]);
$html .= $this->openNode('div', ['class' => array_merge(['card-header'], ["bg-{$this->options['header-variant']}", "text-{$this->options['header-text-variant']}"])]);
}
$html .= $this->genNav();
if ($this->options['card']) {
$html .= '</div>';
$html .= $this->genNode('div', ['class' => array_merge(['card-body'], ["bg-{$this->options['body-variant']}", "text-{$this->options['body-text-variant']}"])]);
$html .= $this->closeNode('div');
$html .= $this->openNode('div', ['class' => array_merge(['card-body'], ["bg-{$this->options['body-variant']}", "text-{$this->options['body-text-variant']}"])]);
}
$html .= $this->genContent();
if ($this->options['card']) {
$html .= '</div>';
$html .= '</div>';
$html .= $this->closeNode('div');
$html .= $this->closeNode('div');
}
return $html;
}
private function genVerticalTabs()
{
$html = $this->genNode('div', ['class' => array_merge(['row', ($this->options['card'] ? 'card flex-row' : '')], ["border-{$this->options['header-border-variant']}"])]);
$html .= $this->genNode('div', ['class' => array_merge(['col-' . $this->options['vertical-size'], ($this->options['card'] ? 'card-header border-right' : '')], ["bg-{$this->options['header-variant']}", "text-{$this->options['header-text-variant']}", "border-{$this->options['header-border-variant']}"])]);
$html = $this->openNode('div', ['class' => array_merge(['row', ($this->options['card'] ? 'card flex-row' : '')], ["border-{$this->options['header-border-variant']}"])]);
$html .= $this->openNode('div', ['class' => array_merge(['col-' . $this->options['vertical-size'], ($this->options['card'] ? 'card-header border-right' : '')], ["bg-{$this->options['header-variant']}", "text-{$this->options['header-text-variant']}", "border-{$this->options['header-border-variant']}"])]);
$html .= $this->genNav();
$html .= '</div>';
$html .= $this->genNode('div', ['class' => array_merge(['col-' . (12 - $this->options['vertical-size']), ($this->options['card'] ? 'card-body2' : '')], ["bg-{$this->options['body-variant']}", "text-{$this->options['body-text-variant']}"])]);
$html .= $this->closeNode('div');
$html .= $this->openNode('div', ['class' => array_merge(['col-' . (12 - $this->options['vertical-size']), ($this->options['card'] ? 'card-body2' : '')], ["bg-{$this->options['body-variant']}", "text-{$this->options['body-text-variant']}"])]);
$html .= $this->genContent();
$html .= '</div>';
$html .= '</div>';
$html .= $this->closeNode('div');
$html .= $this->closeNode('div');
return $html;
}
private function genNav()
{
$html = $this->genNode('ul', [
$html = $this->openNode('ul', [
'class' => array_merge(['nav'], $this->bsClasses['nav'], $this->options['nav-class']),
'role' => 'tablist',
]);
foreach ($this->data['navs'] as $navItem) {
$html .= $this->genNavItem($navItem);
}
$html .= '</ul>';
$html .= $this->closeNode('ul');
return $html;
}
private function genNavItem($navItem)
{
$html = $this->genNode('li', [
$html = $this->openNode('li', [
'class' => array_merge(['nav-item'], $this->bsClasses['nav-item'], $this->options['nav-item-class']),
'role' => 'presentation',
]);
$html .= $this->genNode('a', [
$html .= $this->openNode('a', [
'class' => array_merge(
['nav-link'],
[!empty($navItem['active']) ? 'active' : ''],
@ -258,56 +312,322 @@ class BootstrapTabs extends Helper
} else {
$html .= h($navItem['text']);
}
$html .= '</a></li>';
$html .= $this->closeNode('a');
$html .= $this->closeNode('li');
return $html;
}
private function genContent()
{
$html = $this->genNode('div', [
$html = $this->openNode('div', [
'class' => array_merge(['tab-content'], $this->options['content-class']),
]);
foreach ($this->data['content'] as $i => $content) {
$navItem = $this->data['navs'][$i];
$html .= $this->genContentItem($navItem, $content);
}
$html .= '</div>';
$html .= $this->closeNode('div');
return $html;
}
private function genContentItem($navItem, $content)
{
$html = $this->genNode('div', [
return $this->genNode('div', [
'class' => array_merge(['tab-pane', 'fade'], [!empty($navItem['active']) ? 'show active' : '']),
'role' => 'tabpanel',
'id' => $navItem['id'],
'aria-labelledby' => $navItem['id'] . '-tab'
]);
$html .= $content;
$html .= '</div>';
return $html;
}
private function genNode($node, $params)
{
return sprintf('<%s %s>', $node, $this->genHTMLParams($params));
}
private function genHTMLParams($params)
{
$html = '';
foreach ($params as $k => $v) {
$html .= $this->genHTMLParam($k, $v) . ' ';
}
return $html;
}
private function genHTMLParam($paramName, $values)
{
if (!is_array($values)) {
$values = [$values];
}
return sprintf('%s="%s"', $paramName, implode(' ', $values));
], $content);
}
}
class BoostrapAlert extends BootstrapGeneric {
private $defaultOptions = [
'text' => '',
'html' => null,
'dismissible' => true,
'variant' => 'primary',
'fade' => true
];
private $bsClasses = null;
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 alert()
{
return $this->genAlert();
}
private function genAlert()
{
$html = $this->openNode('div', [
'class' => [
'alert',
"alert-{$this->options['variant']}",
$this->options['dismissible'] ? 'alert-dismissible' : '',
$this->options['fade'] ? 'fade show' : '',
],
'role' => "alert"
]);
$html .= $this->genContent();
$html .= $this->genCloseButton();
$html .= $this->closeNode('div');
return $html;
}
private function genCloseButton()
{
$html = '';
if ($this->options['dismissible']) {
$html .= $this->openNode('button', [
'type' => 'button',
'class' => 'close',
'data-dismiss' => 'alert',
'arial-label' => 'close'
]);
$html .= $this->genNode('span', [
'arial-hidden' => 'true'
], '&times;');
$html .= $this->closeNode('button');
}
return $html;
}
private function genContent()
{
return !is_null($this->options['html']) ? $this->options['html'] : $this->options['text'];
}
}
class BoostrapTable extends BootstrapGeneric {
private $defaultOptions = [
'striped' => true,
'bordered' => true,
'borderless' => false,
'hover' => true,
'small' => false,
'variant' => '',
'tableClass' => [],
'headerClass' => [],
'bodyClass' => [],
];
private $bsClasses = null;
function __construct($options, $data) {
$this->allowedOptionValues = [
'variant' => array_merge(BootstrapGeneric::$variants, [''])
];
$this->processOptions($options);
$this->fields = $data['fields'];
$this->items = $data['items'];
$this->caption = !empty($data['caption']) ? $data['caption'] : '';
}
private function processOptions($options)
{
$this->options = array_merge($this->defaultOptions, $options);
$this->checkOptionValidity();
}
public function table()
{
return $this->genTable();
}
private function genTable()
{
$html = $this->openNode('table', [
'class' => [
'table',
"table-{$this->options['variant']}",
$this->options['striped'] ? 'table-striped' : '',
$this->options['bordered'] ? 'table-bordered' : '',
$this->options['borderless'] ? 'table-borderless' : '',
$this->options['hover'] ? 'table-hover' : '',
$this->options['small'] ? 'table-sm' : '',
!empty($this->options['variant']) ? "table-{$this->options['variant']}" : '',
!empty($this->options['tableClass']) ? $this->options['tableClass'] : ''
],
]);
$html .= $this->genCaption();
$html .= $this->genHeader();
$html .= $this->genBody();
$html .= $this->closeNode('table');
return $html;
}
private function genHeader()
{
$head = $this->openNode('thead', [
'class' => [
!empty($this->options['headerClass']) ? $this->options['headerClass'] : ''
],
]);
$head .= $this->openNode('tr');
foreach ($this->fields as $i => $field) {
if (is_array($field)) {
$label = !empty($field['label']) ? $field['label'] : Inflector::humanize($field['key']);
} else {
$label = Inflector::humanize($field);
}
$head .= $this->genNode('th', [], h($label));
}
$head .= $this->closeNode('tr');
$head .= $this->closeNode('thead');
return $head;
}
private function genBody()
{
$body = $this->openNode('tbody', [
'class' => [
!empty($this->options['bodyClass']) ? $this->options['bodyClass'] : ''
],
]);
foreach ($this->items as $i => $row) {
$body .= $this->genRow($row);
}
$body .= $this->closeNode('tbody');
return $body;
}
private function genRow($row)
{
$html = $this->openNode('tr',[
'class' => [
!empty($row['_rowVariant']) ? "table-{$row['_rowVariant']}" : ''
]
]);
if (array_keys($row) !== range(0, count($row) - 1)) { // associative array
foreach ($this->fields as $i => $field) {
if (is_array($field)) {
$key = $field['key'];
} else {
$key = $field;
}
$cellValue = $row[$key];
$html .= $this->genCell($cellValue, $field, $row);
}
} else { // indexed array
foreach ($row as $cellValue) {
$html .= $this->genCell($cellValue, $field, $row);
}
}
$html .= $this->closeNode('tr');
return $html;
}
private function genCell($value, $field=[], $row=[])
{
if (isset($field['formatter'])) {
$cellContent = $field['formatter']($value, $row);
} else {
$cellContent = h($value);
}
return $this->genNode('td', [
'class' => [
!empty($row['_cellVariant']) ? "bg-{$row['_cellVariant']}" : ''
]
], $cellContent);
}
private function genCaption()
{
return $this->genNode('caption', [], h($this->caption));
}
}
class BoostrapButton extends BootstrapGeneric {
private $defaultOptions = [
'id' => '',
'text' => '',
'html' => null,
'variant' => 'primary',
'outline' => false,
'size' => '',
'block' => false,
'icon' => null,
'class' => [],
'type' => 'button',
'nodeType' => 'button',
'params' => []
];
private $bsClasses = [];
function __construct($options) {
$this->allowedOptionValues = [
'variant' => BootstrapGeneric::$variants,
'size' => ['', 'sm', 'lg'],
'type' => ['button', 'submit', 'reset']
];
$options['class'] = !is_array($options['class']) ? [$options['class']] : $options['class'];
$this->processOptions($options);
}
private function processOptions($options)
{
$this->options = array_merge($this->defaultOptions, $options);
$this->checkOptionValidity();
$this->bsClasses[] = 'btn';
if ($this->options['outline']) {
$this->bsClasses[] = "btn-outline-{$this->options['variant']}";
} else {
$this->bsClasses[] = "btn-{$this->options['variant']}";
}
if (!empty($this->options['size'])) {
$this->bsClasses[] = "btn-$this->options['size']";
}
if ($this->options['block']) {
$this->bsClasses[] = 'btn-block';
}
}
public function button()
{
return $this->genButton();
}
private function genButton()
{
$html = $this->openNode($this->options['nodeType'], array_merge($this->options['params'], [
'class' => array_merge($this->options['class'], $this->bsClasses),
'role' => "alert",
'type' => $this->options['type']
]));
$html .= $this->genIcon();
$html .= $this->genContent();
$html .= $this->closeNode($this->options['nodeType']);
return $html;
}
private function genIcon()
{
return $this->genNode('span', [
'class' => ['mr-1', "fa fa-{$this->options['icon']}"],
]);
}
private function genContent()
{
return !is_null($this->options['html']) ? $this->options['html'] : $this->options['text'];
}
}

View File

@ -0,0 +1,61 @@
<?php
if (!empty($updateAvailables)) {
$alertHtml = sprintf(
'<h5 class="alert-heading">%s</h5>%s<div>%s</div>',
__n('A new update is available!', 'New updates are available!', count($updateAvailables)),
__('Updating to the latest version is highly recommanded.'),
$this->Bootstrap->button([
'variant' => 'success',
'icon' => 'arrow-alt-circle-up',
'class' => 'mt-1',
'text' => __n('Run update', 'Run all updates', count($updateAvailables)),
'params' => [
'onclick' => 'runAllUpdate()'
]
])
);
echo $this->Bootstrap->alert([
'variant' => 'warning',
'html' => $alertHtml,
'dismissible' => false,
]);
}
foreach ($status as $i => &$update) {
if ($update['status'] == 'up') {
$update['_rowVariant'] = 'success';
} else if ($update['status'] == 'down') {
$update['_rowVariant'] = 'danger';
}
}
echo $this->Bootstrap->table([], [
'fields' => [
['key' => 'id', 'label' => __('ID')],
['key' => 'name', 'label' => __('Name')],
['key' => 'end_time', 'label' => __('End Time')],
['key' => 'time_taken_formated', 'label' => __('Time Taken')],
['key' => 'status', 'label' => __('Status')]
],
'items' => $status,
]);
?>
<script>
function runAllUpdate() {
const url = '/instance/migrate'
const reloadUrl = '/instance/migrate-index'
const modalOptions = {
title: '<?= __n('Run database update?', 'Run all database updates?', count($updateAvailables)) ?>',
body: '<?= __('The process might take some time.') ?>',
type: 'confirm-success',
confirmText: '<?= __n('Run update', 'Run all updates', count($updateAvailables)) ?>',
APIConfirm: (tmpApi) => {
tmpApi.fetchAndPostForm(url, {}).then(() => {
location.reload()
})
},
}
UI.modal(modalOptions)
}
</script>

View File

@ -142,7 +142,7 @@ class AJAXApi {
static async quickFetchAndPostForm(url, dataToMerge={}, options={}) {
const constAlteredOptions = Object.assign({}, {}, options)
const tmpApi = new AJAXApi(constAlteredOptions)
return tmpApi.fetchAndPostForm(url, dataToMerge, constAlteredOptions.skipRequestHooks)
return tmpApi.fetchAndPostForm(url, dataToMerge, constAlteredOptions.skipRequestHooks, constAlteredOptions.skipFeedback)
}
/**
@ -335,14 +335,14 @@ class AJAXApi {
* @param {boolean} [skipRequestHooks=false] - If true, default request hooks will be skipped
* @return {Promise<Object>} Promise object resolving to the result of the POST operation
*/
async fetchAndPostForm(url, dataToMerge={}, skipRequestHooks=false) {
async fetchAndPostForm(url, dataToMerge={}, skipRequestHooks=false, skipFeedback=false) {
if (!skipRequestHooks) {
this.beforeRequest()
}
let toReturn
try {
const form = await this.fetchForm(url, true, true);
toReturn = await this.postForm(form, dataToMerge, true, true)
toReturn = await this.postForm(form, dataToMerge, true, skipFeedback)
} catch (error) {
toReturn = Promise.reject(error);
} finally {