commit
1e9ce1d0d5
|
@ -54,6 +54,7 @@ $iframeClass = $isIframe ? ' iframe' : '';
|
|||
<div id="layout" class="default-layout<?php if ($showFullscreen): ?> fullscreen-layout<?php endif ?>">
|
||||
<?= $this->render('body.phtml') ?>
|
||||
</div>
|
||||
<iframe id="fileupload-frame-target" name="fileupload-frame-target"></iframe>
|
||||
<!--[if IE 8]>
|
||||
<script type="text/javascript" src="<?= $this->href($ie8jsfile) ?>"></script>
|
||||
<![endif]-->
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<head>
|
||||
<?php if (isset($this->layout()->redirectUrl)): ?>
|
||||
<meta name="redirectUrl" content="<?= $this->layout()->redirectUrl; ?>">
|
||||
<?php endif ?>
|
||||
</head>
|
||||
<body>
|
||||
<?= $this->render('inline.phtml'); ?>
|
||||
</body>
|
||||
</html>
|
|
@ -96,6 +96,9 @@ class ActionController extends Zend_Controller_Action
|
|||
if ($this->rerenderLayout = $request->getUrl()->shift('renderLayout')) {
|
||||
$this->xhrLayout = 'body';
|
||||
}
|
||||
if ($request->getUrl()->shift('_disableLayout')) {
|
||||
$this->_helper->layout()->disableLayout();
|
||||
}
|
||||
|
||||
if ($this->requiresLogin()) {
|
||||
$this->redirectToLogin(Url::fromRequest());
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
namespace Icinga\Web;
|
||||
|
||||
use LogicException;
|
||||
use Zend_Config;
|
||||
use Zend_Form;
|
||||
use Zend_Form_Element;
|
||||
use Zend_View_Interface;
|
||||
use Icinga\Application\Icinga;
|
||||
use Icinga\Authentication\Manager;
|
||||
use Icinga\Exception\ProgrammingError;
|
||||
use Icinga\Security\SecurityException;
|
||||
use Icinga\Util\Translator;
|
||||
use Icinga\Web\Form\ErrorLabeller;
|
||||
|
@ -84,7 +84,7 @@ class Form extends Zend_Form
|
|||
/**
|
||||
* The url to redirect to upon success
|
||||
*
|
||||
* @var string|Url
|
||||
* @var Url
|
||||
*/
|
||||
protected $redirectUrl;
|
||||
|
||||
|
@ -229,12 +229,12 @@ class Form extends Zend_Form
|
|||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws LogicException If the callback is not callable
|
||||
* @throws ProgrammingError If the callback is not callable
|
||||
*/
|
||||
public function setOnSuccess($onSuccess)
|
||||
{
|
||||
if (! is_callable($onSuccess)) {
|
||||
throw new LogicException('The option `onSuccess\' is not callable');
|
||||
throw new ProgrammingError('The option `onSuccess\' is not callable');
|
||||
}
|
||||
$this->onSuccess = $onSuccess;
|
||||
return $this;
|
||||
|
@ -269,9 +269,17 @@ class Form extends Zend_Form
|
|||
* @param string|Url $url The url to redirect to
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @throws ProgrammingError In case $url is neither a string nor a instance of Icinga\Web\Url
|
||||
*/
|
||||
public function setRedirectUrl($url)
|
||||
{
|
||||
if (is_string($url)) {
|
||||
$url = Url::fromPath($url, array(), $this->getRequest());
|
||||
} elseif (! $url instanceof Url) {
|
||||
throw new ProgrammingError('$url must be a string or instance of Icinga\Web\Url');
|
||||
}
|
||||
|
||||
$this->redirectUrl = $url;
|
||||
return $this;
|
||||
}
|
||||
|
@ -279,12 +287,12 @@ class Form extends Zend_Form
|
|||
/**
|
||||
* Return the url to redirect to upon success
|
||||
*
|
||||
* @return string|Url
|
||||
* @return Url
|
||||
*/
|
||||
public function getRedirectUrl()
|
||||
{
|
||||
if ($this->redirectUrl === null) {
|
||||
$url = Url::fromRequest(array(), $this->getRequest());
|
||||
$url = $this->getRequest()->getUrl();
|
||||
// Be sure to remove all form dependent params because we do not want to submit it again
|
||||
$this->redirectUrl = $url->without(array_keys($this->getElements()));
|
||||
}
|
||||
|
@ -665,7 +673,7 @@ class Form extends Zend_Form
|
|||
// TODO(el): Re-evalute this necessity. JavaScript could use the container's URL if there's no action set.
|
||||
// We MUST set an action as JS gets confused otherwise, if
|
||||
// this form is being displayed in an additional column
|
||||
$this->setAction(Url::fromRequest()->without(array_keys($this->getElements())));
|
||||
$this->setAction($this->getRequest()->getUrl()->without(array_keys($this->getElements())));
|
||||
}
|
||||
|
||||
$this->created = true;
|
||||
|
@ -996,12 +1004,20 @@ class Form extends Zend_Form
|
|||
|
||||
$formData = $this->getRequestData();
|
||||
if ($this->getUidDisabled() || $this->wasSent($formData)) {
|
||||
if (($frameUpload = (bool) $request->getUrl()->shift('_frameUpload', false))) {
|
||||
$this->getView()->layout()->setLayout('wrapped');
|
||||
}
|
||||
|
||||
$this->populate($formData); // Necessary to get isSubmitted() to work
|
||||
if (! $this->getSubmitLabel() || $this->isSubmitted()) {
|
||||
if ($this->isValid($formData)
|
||||
&& (($this->onSuccess !== null && false !== call_user_func($this->onSuccess, $this))
|
||||
|| ($this->onSuccess === null && false !== $this->onSuccess()))) {
|
||||
$this->getResponse()->redirectAndExit($this->getRedirectUrl());
|
||||
if (! $frameUpload) {
|
||||
$this->getResponse()->redirectAndExit($this->getRedirectUrl());
|
||||
} else {
|
||||
$this->getView()->layout()->redirectUrl = $this->getRedirectUrl()->getAbsoluteUrl();
|
||||
}
|
||||
}
|
||||
} elseif ($this->getValidatePartial()) {
|
||||
// The form can't be processed but we may want to show validation errors though
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace Icinga\Web\Form;
|
|||
use BadMethodCallException;
|
||||
use Zend_Translate_Adapter;
|
||||
use Zend_Validate_NotEmpty;
|
||||
use Zend_Validate_File_MimeType;
|
||||
use Icinga\Web\Form\Validator\DateTimeValidator;
|
||||
use Icinga\Web\Form\Validator\ReadablePathValidator;
|
||||
use Icinga\Web\Form\Validator\WritablePathValidator;
|
||||
|
@ -42,10 +43,15 @@ class ErrorLabeller extends Zend_Translate_Adapter
|
|||
$label = $element->getLabel() ?: $element->getName();
|
||||
|
||||
return array(
|
||||
Zend_Validate_NotEmpty::IS_EMPTY => sprintf(t('%s is required and must not be empty'), $label),
|
||||
WritablePathValidator::NOT_WRITABLE => sprintf(t('%s is not writable', 'config.path'), $label),
|
||||
WritablePathValidator::DOES_NOT_EXIST => sprintf(t('%s does not exist', 'config.path'), $label),
|
||||
ReadablePathValidator::NOT_READABLE => sprintf(t('%s is not readable', 'config.path'), $label),
|
||||
Zend_Validate_NotEmpty::IS_EMPTY => sprintf(t('%s is required and must not be empty'), $label),
|
||||
Zend_Validate_File_MimeType::FALSE_TYPE => sprintf(
|
||||
t('%s (%%value%%) has a false MIME type of "%%type%%"'),
|
||||
$label
|
||||
),
|
||||
Zend_Validate_File_MimeType::NOT_DETECTED => sprintf(t('%s (%%value%%) has no MIME type'), $label),
|
||||
WritablePathValidator::NOT_WRITABLE => sprintf(t('%s is not writable', 'config.path'), $label),
|
||||
WritablePathValidator::DOES_NOT_EXIST => sprintf(t('%s does not exist', 'config.path'), $label),
|
||||
ReadablePathValidator::NOT_READABLE => sprintf(t('%s is not readable', 'config.path'), $label),
|
||||
DateTimeValidator::INVALID_DATETIME_FORMAT => sprintf(
|
||||
t('%s not in the expected format: %%value%%'),
|
||||
$label
|
||||
|
|
|
@ -309,7 +309,7 @@ EOT;
|
|||
|
||||
private function renderRefreshTab()
|
||||
{
|
||||
$url = Url::fromRequest()->without('renderLayout');
|
||||
$url = Icinga::app()->getFrontController()->getRequest()->getUrl();
|
||||
$tab = $this->get($this->getActiveName());
|
||||
|
||||
if ($tab !== null) {
|
||||
|
|
|
@ -47,6 +47,10 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
#fileupload-frame-target {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#responsive-debug {
|
||||
font-size: 0.9em;
|
||||
font-family: Courier new, monospace;
|
||||
|
|
|
@ -192,13 +192,13 @@
|
|||
*
|
||||
*/
|
||||
submitForm: function (event, autosubmit) {
|
||||
//return false;
|
||||
var self = event.data.self;
|
||||
var icinga = self.icinga;
|
||||
// .closest is not required unless subelements to trigger this
|
||||
var $form = $(event.currentTarget).closest('form');
|
||||
var url = $form.attr('action');
|
||||
var method = $form.attr('method');
|
||||
var encoding = $form.attr('enctype');
|
||||
var $button = $('input[type=submit]:focus', $form).add('button[type=submit]:focus', $form);
|
||||
var $target;
|
||||
var data;
|
||||
|
@ -230,13 +230,14 @@
|
|||
method = method.toUpperCase();
|
||||
}
|
||||
|
||||
if (typeof encoding === 'undefined') {
|
||||
encoding = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
|
||||
if ($button.length === 0) {
|
||||
$button = $('input[type=submit]', $form).add('button[type=submit]', $form).first();
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
if ($button.length) {
|
||||
// Activate spinner
|
||||
if ($button.hasClass('spinner')) {
|
||||
|
@ -266,14 +267,47 @@
|
|||
|
||||
url = icinga.utils.addUrlParams(url, dataObj);
|
||||
} else {
|
||||
data = $form.serializeArray();
|
||||
if (encoding === 'multipart/form-data') {
|
||||
if (typeof window.FormData === 'undefined') {
|
||||
icinga.loader.submitFormToIframe($form, url, $target);
|
||||
|
||||
// Disable all form controls to prevent resubmission as early as possible.
|
||||
// (This relies on native form submission, so using setTimeout is the only possible solution)
|
||||
setTimeout(function () {
|
||||
$form.find(':input:not(:disabled)').prop('disabled', true);
|
||||
}, 0);
|
||||
|
||||
if (! typeof autosubmit === 'undefined' && autosubmit) {
|
||||
if ($button.length) {
|
||||
// We're autosubmitting the form so the button has not been clicked, however,
|
||||
// to be really safe, we're disabling the button explicitly, just in case..
|
||||
$button.prop('disabled', true);
|
||||
}
|
||||
|
||||
$form[0].submit(); // This should actually not trigger the onSubmit event, let's hope that this is true for all browsers..
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
data = new window.FormData($form[0]);
|
||||
} else {
|
||||
data = $form.serializeArray();
|
||||
}
|
||||
|
||||
if (typeof autosubmit === 'undefined' || ! autosubmit) {
|
||||
if ($button.length && $button.attr('name') !== 'undefined') {
|
||||
data.push({
|
||||
name: $button.attr('name'),
|
||||
value: $button.attr('value')
|
||||
});
|
||||
if (encoding === 'multipart/form-data') {
|
||||
data.append($button.attr('name'), $button.attr('value'));
|
||||
} else {
|
||||
data.push({
|
||||
name: $button.attr('name'),
|
||||
value: $button.attr('value')
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -284,6 +318,8 @@
|
|||
|
||||
icinga.loader.loadUrl(url, $target, data, method);
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
},
|
||||
|
||||
|
|
|
@ -100,13 +100,25 @@
|
|||
headers['X-Icinga-WindowId'] = 'undefined';
|
||||
}
|
||||
|
||||
// This is jQuery's default content type
|
||||
var contentType = 'application/x-www-form-urlencoded; charset=UTF-8';
|
||||
|
||||
var isFormData = typeof window.FormData !== 'undefined' && data instanceof window.FormData;
|
||||
if (isFormData) {
|
||||
// Setting false is mandatory as the form's data
|
||||
// won't be recognized by the server otherwise
|
||||
contentType = false;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var req = $.ajax({
|
||||
type : method,
|
||||
url : url,
|
||||
data : data,
|
||||
headers: headers,
|
||||
context: self
|
||||
context: self,
|
||||
contentType: contentType,
|
||||
processData: ! isFormData
|
||||
});
|
||||
|
||||
req.$target = $target;
|
||||
|
@ -128,6 +140,41 @@
|
|||
return req;
|
||||
},
|
||||
|
||||
/**
|
||||
* Mimic XHR form submission by using an iframe
|
||||
*
|
||||
* @param {object} $form The form being submitted
|
||||
* @param {string} action The form's action URL
|
||||
* @param {object} $target The target container
|
||||
*/
|
||||
submitFormToIframe: function ($form, action, $target) {
|
||||
var self = this;
|
||||
|
||||
$form.prop('action', self.icinga.utils.addUrlParams(action, {
|
||||
'_frameUpload': true
|
||||
}));
|
||||
$form.prop('target', 'fileupload-frame-target');
|
||||
$('#fileupload-frame-target').on('load', function (event) {
|
||||
var $frame = $(event.target);
|
||||
var $contents = $frame.contents();
|
||||
|
||||
var $redirectMeta = $contents.find('meta[name="redirectUrl"]');
|
||||
if ($redirectMeta.length) {
|
||||
self.redirectToUrl($redirectMeta.attr('content'), $target);
|
||||
} else {
|
||||
// Fetch the frame's new content and paste it into the target
|
||||
self.renderContentToContainer(
|
||||
$contents.find('body').html(),
|
||||
$target,
|
||||
'replace'
|
||||
);
|
||||
}
|
||||
|
||||
$frame.prop('src', 'about:blank'); // Clear the frame's dom
|
||||
$frame.off('load'); // Unbind the event as it's set on demand
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Create an URL relative to the Icinga base Url, still unused
|
||||
*
|
||||
|
@ -279,16 +326,34 @@
|
|||
}
|
||||
}
|
||||
|
||||
this.redirectToUrl(redirect, req.$target, req.getResponseHeader('X-Icinga-Rerender-Layout'));
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Redirect to the given url
|
||||
*
|
||||
* @param {string} url
|
||||
* @param {object} $target
|
||||
* @param {boolean} rerenderLayout
|
||||
*/
|
||||
redirectToUrl: function (url, $target, rerenderLayout) {
|
||||
var icinga = this.icinga;
|
||||
|
||||
if (typeof rerenderLayout === 'undefined') {
|
||||
rerenderLayout = false;
|
||||
}
|
||||
|
||||
icinga.logger.debug(
|
||||
'Got redirect for ', req.$target, ', URL was ' + redirect
|
||||
'Got redirect for ', $target, ', URL was ' + url
|
||||
);
|
||||
|
||||
if (req.getResponseHeader('X-Icinga-Rerender-Layout')) {
|
||||
var parts = redirect.split(/#!/);
|
||||
redirect = parts.shift();
|
||||
var redirectionUrl = this.addUrlFlag(redirect, 'renderLayout');
|
||||
if (rerenderLayout) {
|
||||
var parts = url.split(/#!/);
|
||||
url = parts.shift();
|
||||
var redirectionUrl = this.addUrlFlag(url, 'renderLayout');
|
||||
var r = this.loadUrl(redirectionUrl, $('#layout'));
|
||||
r.url = redirect;
|
||||
r.url = url;
|
||||
if (parts.length) {
|
||||
r.loadNext = parts;
|
||||
} else if (!! document.location.hash) {
|
||||
|
@ -298,28 +363,24 @@
|
|||
r.loadNext = parts;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (redirect.match(/#!/)) {
|
||||
var parts = redirect.split(/#!/);
|
||||
if (url.match(/#!/)) {
|
||||
var parts = url.split(/#!/);
|
||||
icinga.ui.layout2col();
|
||||
this.loadUrl(parts.shift(), $('#col1'));
|
||||
this.loadUrl(parts.shift(), $('#col2'));
|
||||
} else {
|
||||
|
||||
if (req.$target.attr('id') === 'col2') { // TODO: multicol
|
||||
if ($('#col1').data('icingaUrl').split('?')[0] === redirect.split('?')[0]) {
|
||||
if ($target.attr('id') === 'col2') { // TODO: multicol
|
||||
if ($('#col1').data('icingaUrl').split('?')[0] === url.split('?')[0]) {
|
||||
icinga.ui.layout1col();
|
||||
req.$target = $('#col1');
|
||||
$target = $('#col1');
|
||||
delete(this.requests['col2']);
|
||||
}
|
||||
}
|
||||
|
||||
this.loadUrl(redirect, req.$target);
|
||||
this.loadUrl(url, $target);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
cacheLoadedIcons: function($container) {
|
||||
|
|
|
@ -92,7 +92,8 @@ class FormTest extends BaseTestCase
|
|||
|
||||
public function testWhetherAnExplicitlySetRedirectUrlIsUsedForRedirection()
|
||||
{
|
||||
$this->getResponseMock()->shouldReceive('redirectAndExit')->atLeast()->once()->with('special/route');
|
||||
$this->getResponseMock()->shouldReceive('redirectAndExit')->atLeast()->once()
|
||||
->with(Mockery::on(function ($url) { return $url->getRelativeUrl() === 'special/route'; }));
|
||||
|
||||
$form = new SuccessfulForm();
|
||||
$form->setTokenDisabled();
|
||||
|
|
Loading…
Reference in New Issue