CVE-2021-4092: Cross-Site Request Forgery (CSRF) (CWE-352)
value: yetiforcecrm is vulnerable to Cross-Site Request Forgery (CSRF)
CWE description: The web application does not, or can not, sufficiently verify whether a well-formed, valid, consistent request was intentionally provided by the user who submitted the request.
CVSS v3 base score: 4.3
Published: 2021-12-11T14:15Z
Repository: https://github.com/yetiforcecompany/yetiforcecrm
Language: JavaScript
Files changed: app/Controller/View/Page.php, config/version.php, layouts/basic/modules/Users/Modals/PasswordModalFooter.tpl, layouts/basic/modules/Vtiger/Index.tpl, modules/Users/actions/Logout.php, public_html/layouts/basic/modules/Vtiger/resources/List.js, public_html/layouts/basic/modules/Vtiger/resources/RelatedList.js, public_html/layouts/basic/modules/Vtiger/resources/Vtiger.js, public_html/layouts/resources/Connector.js, public_html/layouts/resources/app.js
Commit date: 2021-12-10 10:29:46 +0100
Commit message: Added minor improvements

Diff: diff --git a/app/Controller/View/Page.php b/app/Controller/View/Page.php
index dac682a697..f19711229e 100644
--- a/app/Controller/View/Page.php
+++ b/app/Controller/View/Page.php
@@ -173,11 +173,11 @@ abstract class Page extends Base
 		$headerLinks[] = [
 			'linktype' => 'HEADERLINK',
 			'linklabel' => 'LBL_SIGN_OUT',
 			'linkurl' => 'index.php?module=Users&parent=Settings&action=Logout',
 			'icon' => 'fas fa-power-off fa-fw',
-			'linkclass' => 'btn-danger d-md-none',
+			'linkclass' => 'btn-danger d-md-none js-post-action',
 		];
 		$headerLinkInstances = [];
 		foreach ($headerLinks as $headerLink) {
 			$headerLinkInstance = \Vtiger_Link_Model::getInstanceFromValues($headerLink);
 			if (isset($headerLink['childlinks'])) {
@@ -252,11 +252,11 @@ abstract class Page extends Base
 		$headerLinks[] = [
 			'linktype' => 'HEADERLINK',
 			'linklabel' => 'LBL_SIGN_OUT',
 			'linkurl' => 'index.php?module=Users&parent=Settings&action=Logout',
 			'icon' => 'fas fa-power-off fa-fw',
-			'linkclass' => 'd-none d-sm-none d-md-block',
+			'linkclass' => 'd-none d-sm-none d-md-block js-post-action',
 		];
 		$headerLinkInstances = [];
 		foreach ($headerLinks as $headerLink) {
 			$headerLinkInstances[] = \Vtiger_Link_Model::getInstanceFromValues($headerLink);
 		}
diff --git a/config/version.php b/config/version.php
index c64b7f7d36..6bb53d72b5 100644
--- a/config/version.php
+++ b/config/version.php
@@ -1,7 +1,7 @@
 <?php
 
 return [
-	'appVersion' => '6.2.262',
-	'patchVersion' => '2021.12.09',
+	'appVersion' => '6.2.263',
+	'patchVersion' => '2021.12.10',
 	'lib_roundcube' => '0.2.3',
 ];
diff --git a/layouts/basic/modules/Users/Modals/PasswordModalFooter.tpl b/layouts/basic/modules/Users/Modals/PasswordModalFooter.tpl
index bf719b0d27..0a2c530bd3 100644
--- a/layouts/basic/modules/Users/Modals/PasswordModalFooter.tpl
+++ b/layouts/basic/modules/Users/Modals/PasswordModalFooter.tpl
@@ -1,29 +1,29 @@
 {*<!-- {[The file is published on the basis of YetiForce Public License 4.0 that can be found in the following directory: licenses/LicenseEN.txt or yetiforce.com]} -->*}
 {strip}
-<!-- tpl-Users-Modals-PasswordModalFooter -->
-<div class="modal-footer">
-	{if ($MODE === 'massReset' || $MODE === 'reset') &&  $ACTIVE_SMTP}
-		<button class="btn btn-success" type="submit" name="saveButton"
-				{if App\Config::main('systemMode') === 'demo'}disabled="disabled"{/if}>
-			<span class="fas fa-redo-alt mr-2"></span><strong>{\App\Language::translate('BTN_RESET_PASSWORD', $MODULE_NAME)}</strong>
-		</button>
-	{/if}
-	{if $MODE === 'change'}
-		<button class="btn btn-success" type="submit" name="saveButton"
-				{if App\Config::main('systemMode') === 'demo'}disabled="disabled"{/if}>
-			<span class="fas fa-redo-alt mr-2"></span><strong>{\App\Language::translate('LBL_CHANGE_PASSWORD', $MODULE_NAME)}</strong>
-		</button>
-	{/if}
-	{if $LOCK_EXIT}
-		<a class="btn btn-danger" role="button" href="index.php?module=Users&amp;parent=Settings&amp;action=Logout">
-			<span class="fas fa-power-off mr-2"></span><strong>{\App\Language::translate('LBL_SIGN_OUT', $MODULE_NAME)}</strong>
-		</a>
-	{else}
-		<button class="btn btn-danger" type="reset" data-dismiss="modal">
-			<span class="fas fa-times mr-2"></span><strong>{\App\Language::translate('LBL_CANCEL', $MODULE_NAME)}</strong>
-		</button>
-	{/if}
-</div>
-</form>
-<!-- /tpl-Users-Modals-PasswordModalFooter -->
+	<!-- tpl-Users-Modals-PasswordModalFooter -->
+	<div class="modal-footer">
+		{if ($MODE === 'massReset' || $MODE === 'reset') &&  $ACTIVE_SMTP}
+			<button class="btn btn-success" type="submit" name="saveButton"
+				{if App\Config::main('systemMode') === 'demo'}disabled="disabled" {/if}>
+				<span class="fas fa-redo-alt mr-2"></span><strong>{\App\Language::translate('BTN_RESET_PASSWORD', $MODULE_NAME)}</strong>
+			</button>
+		{/if}
+		{if $MODE === 'change'}
+			<button class="btn btn-success" type="submit" name="saveButton"
+				{if App\Config::main('systemMode') === 'demo'}disabled="disabled" {/if}>
+				<span class="fas fa-redo-alt mr-2"></span><strong>{\App\Language::translate('LBL_CHANGE_PASSWORD', $MODULE_NAME)}</strong>
+			</button>
+		{/if}
+		{if $LOCK_EXIT}
+			<a class="btn btn-danger js-post-action" role="button" href="index.php?module=Users&amp;parent=Settings&amp;action=Logout">
+				<span class="fas fa-power-off mr-2"></span><strong>{\App\Language::translate('LBL_SIGN_OUT', $MODULE_NAME)}</strong>
+			</a>
+		{else}
+			<button class="btn btn-danger" type="reset" data-dismiss="modal">
+				<span class="fas fa-times mr-2"></span><strong>{\App\Language::translate('LBL_CANCEL', $MODULE_NAME)}</strong>
+			</button>
+		{/if}
+	</div>
+	</form>
+	<!-- /tpl-Users-Modals-PasswordModalFooter -->
 {/strip}
diff --git a/layouts/basic/modules/Vtiger/Index.tpl b/layouts/basic/modules/Vtiger/Index.tpl
index d38344764d..3d437aa38f 100644
--- a/layouts/basic/modules/Vtiger/Index.tpl
+++ b/layouts/basic/modules/Vtiger/Index.tpl
@@ -18,14 +18,14 @@
 					<h2>{\App\Language::translate('LBL_NO_FOUND_VIEW')}</h2>
 					<p class="my-5">
 						<a class="btn btn-success mr-2" role="button" href="javascript:window.history.back();">
 							<span class="fas fa-chevron-left mr-2"></span>{\App\Language::translate('LBL_GO_BACK')}
 						</a>
-						<a class="btn btn-warning mr-2" role="button" href="?module=Users&action=Logout">
+						<a class="btn btn-warning mr-2 js-post-action" role="button" href="index.php?module=Users&action=Logout">
 							<span class="fas fa-power-off fa-fw mr-2"></span>{\App\Language::translate('LBL_SIGN_OUT')}
 						</a>
-						<a class="btn btn-primary" role="button" href="?module=Users&action=Logout">
+						<a class="btn btn-primary" role="button" href="index.php">
 							<i class="fas fa-home mr-2"></i>{\App\Language::translate('LBL_MAIN_PAGE')}
 						</a>
 					</p>
 				</div>
 			</div>
diff --git a/modules/Users/actions/Logout.php b/modules/Users/actions/Logout.php
index 293dce0781..1d0c92af33 100644
--- a/modules/Users/actions/Logout.php
+++ b/modules/Users/actions/Logout.php
@@ -36,14 +36,6 @@ class Users_Logout_Action extends \App\Controller\Action
 		OSSMail_Logout_Model::logoutCurrentUser();
 		App\Session::destroy();
 		//End
 		header('location: index.php');
 	}
-
-	/**
-	 * {@inheritdoc}
-	 */
-	public function validateRequest(App\Request $request)
-	{
-		$request->validateReadAccess();
-	}
 }
diff --git a/public_html/layouts/basic/modules/Vtiger/resources/List.js b/public_html/layouts/basic/modules/Vtiger/resources/List.js
index afcc93eb55..f8182a8387 100644
--- a/public_html/layouts/basic/modules/Vtiger/resources/List.js
+++ b/public_html/layouts/basic/modules/Vtiger/resources/List.js
@@ -193,11 +193,11 @@ $.Class(
 			app.showModalWindow(null, url, function (container) {
 				container.find('.js-modal__save').on('click', (e) => {
 					let formData = container.find('.js-modal-form').serializeFormData();
 					const listInstance = Vtiger_List_Js.getInstance();
 					$.extend(formData, listInstance.getSearchParams());
-					app.openUrlMethodPost('index.php', formData);
+					AppConnector.requestForm('index.php', formData);
 					Vtiger_Helper_Js.showMessage({
 						text: app.vtranslate('JS_STARTED_GENERATING_FILE'),
 						type: 'info'
 					});
 					app.hideModalWindow();
@@ -353,11 +353,11 @@ $.Class(
 			if (newTab) {
 				formAttr['target'] = '_blank';
 			}
 			let params = Vtiger_List_Js.getInstance().getSearchParams();
 			delete params.view;
-			app.openUrlMethodPost(exportActionUrl, params, formAttr);
+			AppConnector.requestForm(exportActionUrl, params, formAttr);
 		},
 		/**
 		 * Function to reload list
 		 */
 		clearList: function () {
@@ -1486,11 +1486,11 @@ $.Class(
 					thisInstance.getFilterSelectElement().data('select2').close();
 					const liElement = $(event.currentTarget).closest('.select2-results__option');
 					Vtiger_Helper_Js.showConfirmationBox({
 						message: app.vtranslate('JS_LBL_ARE_YOU_SURE_YOU_WANT_TO_DELETE_FILTER')
 					}).done((e) => {
-						app.openUrlMethodPost(thisInstance.getSelectOptionFromChosenOption(liElement).data('deleteurl'));
+						AppConnector.requestForm(thisInstance.getSelectOptionFromChosenOption(liElement).data('deleteurl'));
 					});
 					event.stopPropagation();
 				});
 			}
 		},
@@ -1503,11 +1503,11 @@ $.Class(
 			if (listViewFilterBlock != false) {
 				listViewFilterBlock.on('mouseup', '.js-filter-approve', (event) => {
 					//to close the dropdown
 					thisInstance.getFilterSelectElement().data('select2').close();
 					const liElement = $(event.currentTarget).closest('.select2-results__option');
-					app.openUrlMethodPost(thisInstance.getSelectOptionFromChosenOption(liElement).data('approveurl'));
+					AppConnector.requestForm(thisInstance.getSelectOptionFromChosenOption(liElement).data('approveurl'));
 					event.stopPropagation();
 				});
 			}
 		},
 		/*
@@ -1519,11 +1519,11 @@ $.Class(
 			if (listViewFilterBlock != false) {
 				listViewFilterBlock.on('mouseup', '.js-filter-deny', (event) => {
 					//to close the dropdown
 					thisInstance.getFilterSelectElement().data('select2').close();
 					const liElement = $(event.currentTarget).closest('.select2-results__option');
-					app.openUrlMethodPost(thisInstance.getSelectOptionFromChosenOption(liElement).data('denyurl'));
+					AppConnector.requestForm(thisInstance.getSelectOptionFromChosenOption(liElement).data('denyurl'));
 					event.stopPropagation();
 				});
 			}
 		},
 		/*
diff --git a/public_html/layouts/basic/modules/Vtiger/resources/RelatedList.js b/public_html/layouts/basic/modules/Vtiger/resources/RelatedList.js
index 1301ef13ce..ba07361655 100644
--- a/public_html/layouts/basic/modules/Vtiger/resources/RelatedList.js
+++ b/public_html/layouts/basic/modules/Vtiger/resources/RelatedList.js
@@ -84,11 +84,11 @@ jQuery.Class(
 					type: 'POST',
 					url: massActionUrl,
 					data: postData
 				};
 				if (type === 'sendByForm') {
-					app.openUrlMethodPost(massActionUrl, postData);
+					AppConnector.requestForm(massActionUrl, postData);
 					progressIndicatorElement.progressIndicator({ mode: 'hide' });
 				} else {
 					AppConnector.request(actionParams)
 						.done(function (responseData) {
 							progressIndicatorElement.progressIndicator({ mode: 'hide' });
diff --git a/public_html/layouts/basic/modules/Vtiger/resources/Vtiger.js b/public_html/layouts/basic/modules/Vtiger/resources/Vtiger.js
index a03d697a3a..2ff5e497f4 100644
--- a/public_html/layouts/basic/modules/Vtiger/resources/Vtiger.js
+++ b/public_html/layouts/basic/modules/Vtiger/resources/Vtiger.js
@@ -169,11 +169,11 @@ var Vtiger_Index_Js = {
 			});
 			for (let i in postData) {
 				form.append($('<input>', { name: i, value: JSON.stringify(postData[i]) }));
 			}
 			$('body').append(form);
-			form.submit();
+			form.trigger('submit');
 		} else {
 			window.location.href = url;
 		}
 	},
 	registerWidgetsEvents: function () {
diff --git a/public_html/layouts/resources/Connector.js b/public_html/layouts/resources/Connector.js
index 63904e11a8..4c98f2e552 100644
--- a/public_html/layouts/resources/Connector.js
+++ b/public_html/layouts/resources/Connector.js
@@ -144,10 +144,33 @@ window.AppConnector = {
 		if (pjaxMode) {
 			app.changeUrl(params);
 		}
 		return aDeferred.promise();
 	},
-
-	requestForm: function (url, params = {}) {
-		app.openUrlMethodPost(url, params);
+	/**
+	 * Send form data
+	 * @param {string} url
+	 * @param {object} postData
+	 * @param {object} formAttr
+	 */
+	requestForm: function (url, postData = {}, formAttr = {}) {
+		$.extend(formAttr, {
+			method: 'post',
+			action: url,
+			style: 'display:none;'
+		});
+		let form = $('<form></form>', formAttr);
+		if (typeof csrfMagicName !== 'undefined') {
+			postData[csrfMagicName] = csrfMagicToken;
+		}
+		$.each(postData, (index, value) => {
+			let input = $(document.createElement('input'));
+			input.attr('type', 'hidden');
+			input.attr('name', index);
+			input.val(value);
+			form.append(input);
+		});
+		$('body').append(form);
+		form.trigger('submit');
+		form.remove();
 	}
 };
diff --git a/public_html/layouts/resources/app.js b/public_html/layouts/resources/app.js
index 29205a5b1c..af4a6d77af 100644
--- a/public_html/layouts/resources/app.js
+++ b/public_html/layouts/resources/app.js
@@ -304,11 +304,11 @@ var App = (window.App = {
 				form.append('<input type="hidden" name="view" value="Edit" />');
 				$.each(form.find('[data-validation-engine]'), function (key, data) {
 					$(data).removeAttr('data-validation-engine');
 				});
 				form.addClass('not_validation');
-				form.submit();
+				form.trigger('submit');
 			},
 			/**
 			 * Register tab events
 			 *
 			 * @param   {object}  form  jQuery
@@ -482,11 +482,11 @@ var App = (window.App = {
 				form.append('<input type="hidden" name="view" value="Edit" />');
 				$.each(form.find('[data-validation-engine]'), function (key, data) {
 					$(data).removeAttr('data-validation-engine');
 				});
 				form.addClass('not_validation');
-				form.submit();
+				form.trigger('submit');
 			},
 			/**
 			 * Save quick create form
 			 *
 			 * @param   {object}  form  jQuery
@@ -2625,31 +2625,10 @@ var app = (window.app = {
 			window.parent.location.href = url;
 		} else {
 			window.location.href = url;
 		}
 	},
-	openUrlMethodPost(url, postData = {}, formAttr = {}) {
-		$.extend(formAttr, {
-			method: 'post',
-			action: url,
-			style: 'display:none;'
-		});
-		let form = $('<form></form>', formAttr);
-		if (typeof csrfMagicName !== 'undefined') {
-			postData[csrfMagicName] = csrfMagicToken;
-		}
-		$.each(postData, (index, value) => {
-			let input = $(document.createElement('input'));
-			input.attr('type', 'hidden');
-			input.attr('name', index);
-			input.val(value);
-			form.append(input);
-		});
-		$('body').append(form);
-		form.submit();
-		form.remove();
-	},
 	/**
 	 * Convert url string to object
 	 *
 	 * @param   {string}  url  example: index.php?module=LayoutEditor&parent=Settings
 	 */
@@ -3175,10 +3154,23 @@ var app = (window.app = {
 				if (CONFIG['isQuickCreateSupported'] && event.altKey && event.code === 'KeyQ') {
 					App.Components.QuickCreate.createRecord(app.getModuleName());
 				}
 			});
 		}
+	},
+	/**
+	 * Register POST action
+	 * @param {jQuery} container
+	 */
+	registerPostActionEvent: function (container) {
+		container.on('click', '.js-post-action', function (e) {
+			e.preventDefault();
+			let element = $(this);
+			if (element.attr('href')) {
+				AppConnector.requestForm(element.attr('href'));
+			}
+		});
 	}
 });
 CKEDITOR.disableAutoInline = true;
 $(function () {
 	Quasar.iconSet.set(Quasar.iconSet.mdiV3);
@@ -3201,10 +3193,11 @@ $(function () {
 	app.registerShowHideBlock(document);
 	app.registerAfterLoginEvents(document);
 	app.registerFormsEvents(document);
 	app.registerRecordActionsEvents(document);
 	app.registerKeyboardShortcutsEvent(document);
+	app.registerPostActionEvent(document);
 	App.Components.QuickCreate.register(document);
 	App.Components.Scrollbar.initPage();
 	App.Clipboard.register(document);
 	String.prototype.toCamelCase = function () {
 		let value = this.valueOf();

Vulnerable code: 			'linkclass' => 'btn-danger d-md-none',
			'linkclass' => 'd-none d-sm-none d-md-block',
	'appVersion' => '6.2.262',
	'patchVersion' => '2021.12.09',
<!-- tpl-Users-Modals-PasswordModalFooter -->
<div class="modal-footer">
	{if ($MODE === 'massReset' || $MODE === 'reset') &&  $ACTIVE_SMTP}
		<button class="btn btn-success" type="submit" name="saveButton"
				{if App\Config::main('systemMode') === 'demo'}disabled="disabled"{/if}>
			<span class="fas fa-redo-alt mr-2"></span><strong>{\App\Language::translate('BTN_RESET_PASSWORD', $MODULE_NAME)}</strong>
		</button>
	{/if}
	{if $MODE === 'change'}
		<button class="btn btn-success" type="submit" name="saveButton"
				{if App\Config::main('systemMode') === 'demo'}disabled="disabled"{/if}>
			<span class="fas fa-redo-alt mr-2"></span><strong>{\App\Language::translate('LBL_CHANGE_PASSWORD', $MODULE_NAME)}</strong>
		</button>
	{/if}
	{if $LOCK_EXIT}
		<a class="btn btn-danger" role="button" href="index.php?module=Users&amp;parent=Settings&amp;action=Logout">
			<span class="fas fa-power-off mr-2"></span><strong>{\App\Language::translate('LBL_SIGN_OUT', $MODULE_NAME)}</strong>
		</a>
	{else}
		<button class="btn btn-danger" type="reset" data-dismiss="modal">
			<span class="fas fa-times mr-2"></span><strong>{\App\Language::translate('LBL_CANCEL', $MODULE_NAME)}</strong>
		</button>
	{/if}
</div>
</form>
<!-- /tpl-Users-Modals-PasswordModalFooter -->
						<a class="btn btn-warning mr-2" role="button" href="?module=Users&action=Logout">
						<a class="btn btn-primary" role="button" href="?module=Users&action=Logout">

	/**
	 * {@inheritdoc}
	 */
	public function validateRequest(App\Request $request)
	{
		$request->validateReadAccess();
	}
					app.openUrlMethodPost('index.php', formData);
			app.openUrlMethodPost(exportActionUrl, params, formAttr);
						app.openUrlMethodPost(thisInstance.getSelectOptionFromChosenOption(liElement).data('deleteurl'));
					app.openUrlMethodPost(thisInstance.getSelectOptionFromChosenOption(liElement).data('approveurl'));
					app.openUrlMethodPost(thisInstance.getSelectOptionFromChosenOption(liElement).data('denyurl'));
					app.openUrlMethodPost(massActionUrl, postData);
			form.submit();

	requestForm: function (url, params = {}) {
		app.openUrlMethodPost(url, params);
				form.submit();
				form.submit();
	openUrlMethodPost(url, postData = {}, formAttr = {}) {
		$.extend(formAttr, {
			method: 'post',
			action: url,
			style: 'display:none;'
		});
		let form = $('<form></form>', formAttr);
		if (typeof csrfMagicName !== 'undefined') {
			postData[csrfMagicName] = csrfMagicToken;
		}
		$.each(postData, (index, value) => {
			let input = $(document.createElement('input'));
			input.attr('type', 'hidden');
			input.attr('name', index);
			input.val(value);
			form.append(input);
		});
		$('body').append(form);
		form.submit();
		form.remove();
	},
Fixed code: 			'linkclass' => 'btn-danger d-md-none js-post-action',
			'linkclass' => 'd-none d-sm-none d-md-block js-post-action',
	'appVersion' => '6.2.263',
	'patchVersion' => '2021.12.10',
	<!-- tpl-Users-Modals-PasswordModalFooter -->
	<div class="modal-footer">
		{if ($MODE === 'massReset' || $MODE === 'reset') &&  $ACTIVE_SMTP}
			<button class="btn btn-success" type="submit" name="saveButton"
				{if App\Config::main('systemMode') === 'demo'}disabled="disabled" {/if}>
				<span class="fas fa-redo-alt mr-2"></span><strong>{\App\Language::translate('BTN_RESET_PASSWORD', $MODULE_NAME)}</strong>
			</button>
		{/if}
		{if $MODE === 'change'}
			<button class="btn btn-success" type="submit" name="saveButton"
				{if App\Config::main('systemMode') === 'demo'}disabled="disabled" {/if}>
				<span class="fas fa-redo-alt mr-2"></span><strong>{\App\Language::translate('LBL_CHANGE_PASSWORD', $MODULE_NAME)}</strong>
			</button>
		{/if}
		{if $LOCK_EXIT}
			<a class="btn btn-danger js-post-action" role="button" href="index.php?module=Users&amp;parent=Settings&amp;action=Logout">
				<span class="fas fa-power-off mr-2"></span><strong>{\App\Language::translate('LBL_SIGN_OUT', $MODULE_NAME)}</strong>
			</a>
		{else}
			<button class="btn btn-danger" type="reset" data-dismiss="modal">
				<span class="fas fa-times mr-2"></span><strong>{\App\Language::translate('LBL_CANCEL', $MODULE_NAME)}</strong>
			</button>
		{/if}
	</div>
	</form>
	<!-- /tpl-Users-Modals-PasswordModalFooter -->
						<a class="btn btn-warning mr-2 js-post-action" role="button" href="index.php?module=Users&action=Logout">
						<a class="btn btn-primary" role="button" href="index.php">
					AppConnector.requestForm('index.php', formData);
			AppConnector.requestForm(exportActionUrl, params, formAttr);
						AppConnector.requestForm(thisInstance.getSelectOptionFromChosenOption(liElement).data('deleteurl'));
					AppConnector.requestForm(thisInstance.getSelectOptionFromChosenOption(liElement).data('approveurl'));
					AppConnector.requestForm(thisInstance.getSelectOptionFromChosenOption(liElement).data('denyurl'));
					AppConnector.requestForm(massActionUrl, postData);
			form.trigger('submit');
	/**
	 * Send form data
	 * @param {string} url
	 * @param {object} postData
	 * @param {object} formAttr
	 */
	requestForm: function (url, postData = {}, formAttr = {}) {
		$.extend(formAttr, {
			method: 'post',
			action: url,
			style: 'display:none;'
		});
		let form = $('<form></form>', formAttr);
		if (typeof csrfMagicName !== 'undefined') {
			postData[csrfMagicName] = csrfMagicToken;
		}
		$.each(postData, (index, value) => {
			let input = $(document.createElement('input'));
			input.attr('type', 'hidden');
			input.attr('name', index);
			input.val(value);
			form.append(input);
		});
		$('body').append(form);
		form.trigger('submit');
		form.remove();
				form.trigger('submit');
				form.trigger('submit');
	},
	/**
	 * Register POST action
	 * @param {jQuery} container
	 */
	registerPostActionEvent: function (container) {
		container.on('click', '.js-post-action', function (e) {
			e.preventDefault();
			let element = $(this);
			if (element.attr('href')) {
				AppConnector.requestForm(element.attr('href'));
			}
		});
	app.registerPostActionEvent(document);