diff --git a/admin/code/CMSBatchActionHandler.php b/admin/code/CMSBatchActionHandler.php index 4bc6c4f3b..23ee16129 100644 --- a/admin/code/CMSBatchActionHandler.php +++ b/admin/code/CMSBatchActionHandler.php @@ -68,7 +68,7 @@ class CMSBatchActionHandler extends RequestHandler { function handleAction($request) { // This method can't be called without ajax. - if(!$this->parentController->isAjax()) { + if(!$request->isAjax()) { $this->parentController->redirectBack(); return; } diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index b93a468f0..35ea95489 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -193,7 +193,7 @@ class LeftAndMain extends Controller implements PermissionProvider { if(Director::redirected_to()) return; // Audit logging hook - if(empty($_REQUEST['executeForm']) && !$this->isAjax()) $this->extend('accessedCMS'); + if(empty($_REQUEST['executeForm']) && !$this->request->isAjax()) $this->extend('accessedCMS'); // Set the members html editor config HtmlEditorConfig::set_active(Member::currentUser()->getHtmlEditorConfigForCMS()); @@ -341,7 +341,7 @@ class LeftAndMain extends Controller implements PermissionProvider { } function index($request) { - return ($this->isAjax()) ? $this->show($request) : $this->getViewer('index')->process($this); + return ($request->isAjax()) ? $this->show($request) : $this->getViewer('index')->process($this); } diff --git a/control/Controller.php b/control/Controller.php index 0f7b124e3..8071387dd 100644 --- a/control/Controller.php +++ b/control/Controller.php @@ -535,17 +535,6 @@ class Controller extends RequestHandler implements TemplateGlobalProvider { $this->session = $session; } - /** - * Returns true if this controller is processing an ajax request - * @return boolean True if this controller is processing an ajax request - */ - function isAjax() { - return ( - isset($this->requestParams['ajax']) || isset($_REQUEST['ajax']) || - (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == "XMLHttpRequest") - ); - } - /** * Joins two or more link segments together, putting a slash between them if necessary. * Use this for building the results of {@link Link()} methods. diff --git a/control/Director.php b/control/Director.php index 9c2b33745..980fa3588 100644 --- a/control/Director.php +++ b/control/Director.php @@ -682,7 +682,7 @@ class Director implements TemplateGlobalProvider { */ static function is_ajax() { if(Controller::has_curr()) { - return Controller::curr()->isAjax(); + return Controller::curr()->getRequest()->isAjax(); } else { return ( isset($_REQUEST['ajax']) || diff --git a/control/HTTPRequest.php b/control/HTTPRequest.php index 06bc90063..8a34ba3ad 100644 --- a/control/HTTPRequest.php +++ b/control/HTTPRequest.php @@ -232,6 +232,20 @@ class SS_HTTPRequest implements ArrayAccess { function getURL() { return ($this->getExtension()) ? $this->url . '.' . $this->getExtension() : $this->url; } + + /** + * Returns true if this request an ajax request, + * based on custom HTTP ajax added by common JavaScript libraries, + * or based on an explicit "ajax" request parameter. + * + * @return boolean + */ + function isAjax() { + return ( + $this->requestVar('ajax') || + $this->getHeader('X-Requested-With') && $this->getHeader('X-Requested-With') == "XMLHttpRequest" + ); + } /** * Enables the existence of a key-value pair in the request to be checked using diff --git a/control/RequestHandler.php b/control/RequestHandler.php index 73a5bc1dc..6167e19bb 100644 --- a/control/RequestHandler.php +++ b/control/RequestHandler.php @@ -338,6 +338,59 @@ class RequestHandler extends ViewableData { throw $e; } + + /** + * @deprecated 3.0 Use SS_HTTPRequest->isAjax() instead (through Controller->getRequest()) + */ + function isAjax() { + Deprecation::notice('3.0', 'Use SS_HTTPRequest->isAjax() instead (through Controller->getRequest())'); + return $this->request->isAjax(); + } + + /** + * Handle the X-Get-Fragment header that AJAX responses may provide, returning the + * fragment, or, in the case of non-AJAX form submissions, redirecting back to the submitter. + * + * X-Get-Fragment ensures that users won't end up seeing the unstyled form HTML in their browser + * If a JS error prevents the Ajax overriding of form submissions from happening. It also provides + * better non-JS operation. + * + * Out of the box, the handler "CurrentForm" value, which will return the rendered form. Non-Ajax + * calls will redirect back. + * + * To extend its responses, pass a map to the $options argument. Each key is the value of X-Get-Fragment + * that will work, and the value is a PHP 'callable' value that will return the response for that + * value. + * + * If you specify $options['default'], this will be used as the non-ajax response. + * + * Note that if you use handleFragmentResponse, then any Ajax requests will have to include X-Get-Fragment + * or an error will be thrown. + */ + function handleFragmentResponse($form, $options = array()) { + // Prepare the default options and combine with the others + $lOptions = array( + 'currentform' => array($form, 'forTemplate'), + 'default' => array('Director', 'redirectBack'), + ); + if($options) foreach($options as $k => $v) { + $lOptions[strtolower($k)] = $v; + } + + if($fragment = $this->request->getHeader('X-Get-Fragment')) { + $fragment = strtolower($fragment); + if(isset($lOptions[$fragment])) { + return call_user_func($lOptions[$fragment]); + } else { + throw new SS_HTTPResponse_Exception("X-Get-Fragment = '$fragment' not supported for this URL.", 400); + } + + } else { + if($this->isAjax()) throw new SS_HTTPResponse_Exception("Ajax requests to this URL require an X-Get-Fragment header.", 400); + return call_user_func($lOptions['default']); + } + + } /** * Returns the SS_HTTPRequest object that this controller is using. diff --git a/forms/gridfield/GridFieldDetailForm.php b/forms/gridfield/GridFieldDetailForm.php index a3585a2e3..58bd39d5c 100755 --- a/forms/gridfield/GridFieldDetailForm.php +++ b/forms/gridfield/GridFieldDetailForm.php @@ -196,7 +196,7 @@ class GridFieldDetailForm_ItemRequest extends RequestHandler { 'ItemEditForm' => $form, ))->renderWith($this->template); - if($controller->isAjax()) { + if($request->isAjax()) { return $return; } else { // If not requested by ajax, we need to render it within the controller context+template diff --git a/tests/control/HTTPRequestTest.php b/tests/control/HTTPRequestTest.php index 4ae6b1b0a..fa92643f1 100644 --- a/tests/control/HTTPRequestTest.php +++ b/tests/control/HTTPRequestTest.php @@ -230,4 +230,16 @@ class HTTPRequestTest extends SapphireTest { 'Nested GET parameters should supplement POST parameters' ); } + + function testIsAjax() { + $req = new SS_HTTPRequest('GET', '/', array('ajax' => 0)); + $this->assertFalse($req->isAjax()); + + $req = new SS_HTTPRequest('GET', '/', array('ajax' => 1)); + $this->assertTrue($req->isAjax()); + + $req = new SS_HTTPRequest('GET', '/'); + $req->addHeader('X-Requested-With', 'XMLHttpRequest'); + $this->assertTrue($req->isAjax()); + } }