Browse Source

Merge pull request #2932 from sam-at-github/requesthandlercomponent

Minor Tidy up of RequestHandlerComponent around AJAX
José Lorenzo Rodríguez 12 years ago
parent
commit
2d87876ea6

+ 34 - 36
src/Controller/Component/RequestHandlerComponent.php

@@ -82,15 +82,12 @@ class RequestHandlerComponent extends Component {
  *
  * - `checkHttpCache` - Whether to check for http cache.
  * - `viewClassMap` - Mapping between type and view class.
- * - `ajaxLayout` - The layout that will be switched to for Ajax requests.
- *   See RequestHandler::setAjax()
  *
  * @var array
  */
 	protected $_defaultConfig = [
 		'checkHttpCache' => true,
 		'viewClassMap' => '',
-		'ajaxLayout' => 'ajax'
 	];
 
 /**
@@ -104,14 +101,15 @@ class RequestHandlerComponent extends Component {
 	);
 
 /**
- * A mapping between type and viewClass
- * By default only JSON and XML are mapped, use RequestHandlerComponent::viewClassMap()
+ * A mapping between type and viewClass. By default only JSON, XML, and AJAX are mapped.
+ * Use RequestHandlerComponent::viewClassMap() to manipulate this map.
  *
  * @var array
  */
 	protected $_viewClassMap = array(
 		'json' => 'Json',
-		'xml' => 'Xml'
+		'xml' => 'Xml',
+		'ajax' => 'Ajax'
 	);
 
 /**
@@ -130,10 +128,11 @@ class RequestHandlerComponent extends Component {
 	}
 
 /**
- * Checks to see if a file extension has been parsed by the Router, or if the
- * HTTP_ACCEPT_TYPE has matches only one content type with the supported extensions.
- * If there is only one matching type between the supported content types & extensions,
- * and the requested mime-types, RequestHandler::$ext is set to that value.
+ * Checks to see if a specific content type has been requested and sets RequestHandler::$ext
+ * accordingly. Checks the following in order: 1. The '_ext' value parsed by the Router. 2. A specific
+ * AJAX type request indicated by the presence of a header. 3. The Accept header. With the exception
+ * of an ajax request indicated using the second header based method above, the type must have
+ * been configured in {@link Cake\Routing\Router}.
  *
  * @param Event $event The initialize event that was fired.
  * @return void
@@ -143,7 +142,10 @@ class RequestHandlerComponent extends Component {
 		if (isset($this->request->params['_ext'])) {
 			$this->ext = $this->request->params['_ext'];
 		}
-		if (empty($this->ext) || $this->ext === 'html') {
+		if (empty($this->ext) && $this->request->is('ajax')) {
+			$this->ext = 'ajax';
+		}
+		if (empty($this->ext) || in_array($this->ext, array('html', 'htm'))) {
 			$this->_setExtension();
 		}
 
@@ -156,10 +158,9 @@ class RequestHandlerComponent extends Component {
 /**
  * Set the extension based on the accept headers.
  * Compares the accepted types and configured extensions.
- * If there is one common type, that is assigned as the ext/content type
- * for the response.
- * Type with the highest weight will be set. If the highest weight has more
- * then one type matching the extensions, the order in which extensions are specified
+ * If there is one common type, that is assigned as the ext/content type for the response.
+ * The type with the highest weight will be set. If the highest weight has more
+ * than one type matching the extensions, the order in which extensions are specified
  * determines which type will be set.
  *
  * If html is one of the preferred types, no content type will be set, this
@@ -176,7 +177,7 @@ class RequestHandlerComponent extends Component {
 		$accepts = $this->response->mapType($accept);
 		$preferedTypes = current($accepts);
 		if (array_intersect($preferedTypes, array('html', 'xhtml'))) {
-			return null;
+			return;
 		}
 
 		$extensions = Router::extensions();
@@ -193,7 +194,6 @@ class RequestHandlerComponent extends Component {
  * The startup method of the RequestHandler enables several automatic behaviors
  * related to the detection of certain properties of the HTTP request, including:
  *
- * - Disabling layout rendering for Ajax requests (based on the HTTP_X_REQUESTED_WITH header)
  * - If Router::parseExtensions() is enabled, the layout and template type are
  *   switched based on the parsed extension or Accept-Type header. For example, if `controller/action.xml`
  *   is requested, the view path becomes `app/View/Controller/xml/action.ctp`. Also if
@@ -221,8 +221,6 @@ class RequestHandlerComponent extends Component {
 
 		if (!empty($this->ext) && $isRecognized) {
 			$this->renderAs($controller, $this->ext);
-		} elseif ($this->request->is('ajax')) {
-			$this->renderAs($controller, 'ajax');
 		} elseif (empty($this->ext) || in_array($this->ext, array('html', 'htm'))) {
 			$this->respondAs('html', array('charset' => Configure::read('App.encoding')));
 		}
@@ -481,7 +479,8 @@ class RequestHandlerComponent extends Component {
 	}
 
 /**
- * Sets the layout and template paths for the content type defined by $type.
+ * Sets either the view class if one exists or the layout and template path of the view.
+ * The names of these are derived from the $type input parameter.
  *
  * ### Usage:
  *
@@ -502,18 +501,14 @@ class RequestHandlerComponent extends Component {
  */
 	public function renderAs(Controller $controller, $type, array $options = array()) {
 		$defaults = array('charset' => 'UTF-8');
+		$view = null;
+		$viewClassMap = $this->viewClassMap();
 
 		if (Configure::read('App.encoding') !== null) {
 			$defaults['charset'] = Configure::read('App.encoding');
 		}
 		$options += $defaults;
 
-		if ($type === 'ajax') {
-			$controller->layout = $this->_config['ajaxLayout'];
-			return $this->respondAs('html', $options);
-		}
-
-		$viewClassMap = $this->viewClassMap();
 		if (array_key_exists($type, $viewClassMap)) {
 			$view = $viewClassMap[$type];
 		} else {
@@ -523,17 +518,20 @@ class RequestHandlerComponent extends Component {
 
 		if ($viewClass) {
 			$controller->viewClass = $viewClass;
-		} elseif (empty($this->_renderType)) {
-			$controller->viewPath .= DS . $type;
 		} else {
-			$controller->viewPath = preg_replace(
-				"/([\/\\\\]{$this->_renderType})$/",
-				DS . $type,
-				$controller->viewPath
-			);
-		}
-		$this->_renderType = $type;
-		$controller->layoutPath = $type;
+			if (empty($this->_renderType)) {
+				$controller->viewPath .= DS . $type;
+			} else {
+				$controller->viewPath = preg_replace(
+					"/([\/\\\\]{$this->_renderType})$/",
+					DS . $type,
+					$controller->viewPath
+				);
+			}
+
+			$this->_renderType = $type;
+			$controller->layoutPath = $type;
+		}
 
 		if ($this->response->getMimeType($type)) {
 			$this->respondAs($type, $options);

+ 3 - 2
src/Network/Response.php

@@ -78,7 +78,7 @@ class Response {
 	);
 
 /**
- * Holds known mime type mappings
+ * Holds type key to mime type mappings for known mime types.
  *
  * @var array
  */
@@ -300,7 +300,8 @@ class Response {
 		'vcf' => 'text/x-vcard',
 		'vtt' => 'text/vtt',
 		'mkv' => 'video/x-matroska',
-		'pkpass' => 'application/vnd.apple.pkpass'
+		'pkpass' => 'application/vnd.apple.pkpass',
+		'ajax' => 'text/html'
 	);
 
 /**

+ 2 - 2
src/Routing/Route/Route.php

@@ -337,8 +337,8 @@ class Route {
 	}
 
 /**
- * Removes the extension if the $url contains a known extension.
- * If there are no known extensions all extensions are supported.
+ * Removes the extension from $url if it contains a registered extension.
+ * If no registered extension is found, no extension is returned and the URL is returned unmodified.
  *
  * @param string $url The url to parse.
  * @return array containing url, extension

+ 52 - 0
src/View/AjaxView.php

@@ -0,0 +1,52 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\View;
+
+use Cake\Controller\Controller;
+use Cake\Core\Configure;
+use Cake\Event\EventManager;
+use Cake\Network\Request;
+use Cake\Network\Response;
+
+/**
+ * A view class that is used for AJAX responses.
+ * Currently only switches the default layout and sets the response type - which just maps to
+ * text/html by default.
+ */
+class AjaxView extends View {
+
+/**
+ *
+ * @var string
+ */
+	public $layout = 'ajax';
+
+/**
+ * Constructor
+ *
+ * @param Request $request
+ * @param Response $response
+ * @param EventManager $eventManager
+ * @param array $viewOptions
+ */
+	public function __construct(Request $request = null, Response $response = null,
+		EventManager $eventManager = null, array $viewOptions = []) {
+		parent::__construct($request, $response, $eventManager, $viewOptions);
+
+		if ($response && $response instanceof Response) {
+			$response->type('ajax');
+		}
+	}
+}

+ 8 - 2
src/View/JsonView.php

@@ -56,8 +56,14 @@ use Cake\Network\Response;
 class JsonView extends View {
 
 /**
- * JSON views are always located in the 'json' sub directory for
- * controllers' views.
+ * JSON layouts are located in the json sub directory of `Layouts/`
+ *
+ * @var string 
+ */
+	public $layoutPath = 'json';
+
+/**
+ * JSON views are located in the 'json' sub directory for controllers' views.
  *
  * @var string
  */

+ 8 - 1
src/View/XmlView.php

@@ -57,7 +57,14 @@ use Cake\Utility\Xml;
 class XmlView extends View {
 
 /**
- * The subdirectory. XML views are always in xml.
+ * XML layouts are located in the xml sub directory of `Layouts/`
+ *
+ * @var string
+ */
+	public $layoutPath = 'xml';
+
+/**
+ * XML views are located in the 'xml' sub directory for controllers' views.
  *
  * @var string
  */

+ 65 - 5
tests/TestCase/Controller/Component/RequestHandlerComponentTest.php

@@ -93,13 +93,11 @@ class RequestHandlerComponentTest extends TestCase {
  */
 	public function testConstructorConfig() {
 		$config = array(
-			'ajaxLayout' => 'test_ajax',
 			'viewClassMap' => array('json' => 'MyPlugin.MyJson')
 		);
 		$controller = $this->getMock('Cake\Controller\Controller');
 		$collection = new ComponentRegistry($controller);
 		$requestHandler = new RequestHandlerComponent($collection, $config);
-		$this->assertEquals('test_ajax', $requestHandler->config('ajaxLayout'));
 		$this->assertEquals(array('json' => 'MyPlugin.MyJson'), $requestHandler->config('viewClassMap'));
 	}
 
@@ -295,7 +293,8 @@ class RequestHandlerComponentTest extends TestCase {
 		$result = $this->RequestHandler->viewClassMap();
 		$expected = array(
 			'json' => 'CustomJson',
-			'xml' => 'Xml'
+			'xml' => 'Xml',
+			'ajax' => 'Ajax'
 		);
 		$this->assertEquals($expected, $result);
 
@@ -303,6 +302,7 @@ class RequestHandlerComponentTest extends TestCase {
 		$expected = array(
 			'json' => 'CustomJson',
 			'xml' => 'Xml',
+			'ajax' => 'Ajax',
 			'xls' => 'Excel.Excel'
 		);
 		$this->assertEquals($expected, $result);
@@ -334,19 +334,79 @@ class RequestHandlerComponentTest extends TestCase {
 	public function testAutoAjaxLayout() {
 		$event = new Event('Controller.startup', $this->Controller);
 		$_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest';
+		$this->RequestHandler->initialize($event);
 		$this->RequestHandler->startup($event);
-		$this->assertEquals($this->Controller->layout, $this->RequestHandler->config('ajaxLayout'));
+		$this->assertEquals($this->Controller->viewClass, 'Cake\View\AjaxView');
+		$view = $this->Controller->createView();
+		$this->assertEquals('ajax', $view->layout);
 
 		$this->_init();
 		$this->Controller->request->params['_ext'] = 'js';
 		$this->RequestHandler->initialize($event);
 		$this->RequestHandler->startup($event);
-		$this->assertNotEquals('ajax', $this->Controller->layout);
+		$this->assertNotEquals($this->Controller->viewClass, 'Cake\View\AjaxView');
 
 		unset($_SERVER['HTTP_X_REQUESTED_WITH']);
 	}
 
 /**
+ * test custom JsonView class is loaded and correct.
+ */
+	public function testJsonViewLoaded() {
+		Router::parseExtensions('json', 'xml', 'ajax');
+		$this->Controller->request->params['_ext'] = 'json';
+		$event = new Event('Controller.startup', $this->Controller);
+		$this->RequestHandler->initialize($event);
+		$this->RequestHandler->startup($event);
+		$this->assertEquals('Cake\View\JsonView', $this->Controller->viewClass);
+		$view = $this->Controller->createView();
+		$this->assertEquals('json', $view->layoutPath);
+		$this->assertEquals('json', $view->subDir);
+	}
+
+/**
+ * test custom XmlView class is loaded and correct.
+ */
+	public function testXmlViewLoaded() {
+		Router::parseExtensions('json', 'xml', 'ajax');
+		$this->Controller->request->params['_ext'] = 'xml';
+		$event = new Event('Controller.startup', $this->Controller);
+		$this->RequestHandler->initialize($event);
+		$this->RequestHandler->startup($event);
+		$this->assertEquals('Cake\View\XmlView', $this->Controller->viewClass);
+		$view = $this->Controller->createView();
+		$this->assertEquals('xml', $view->layoutPath);
+		$this->assertEquals('xml', $view->subDir);
+	}
+
+/**
+ * test custom AjaxView class is loaded and correct.
+ */
+	public function testAjaxViewLoaded() {
+		Router::parseExtensions('json', 'xml', 'ajax');
+		$this->Controller->request->params['_ext'] = 'ajax';
+		$event = new Event('Controller.startup', $this->Controller);
+		$this->RequestHandler->initialize($event);
+		$this->RequestHandler->startup($event);
+		$this->assertEquals('Cake\View\AjaxView', $this->Controller->viewClass);
+		$view = $this->Controller->createView();
+		$this->assertEquals('ajax', $view->layout);
+	}
+
+/**
+ * test configured extension but no view class set.
+ */
+	public function testNoViewClassExtension() {
+		Router::parseExtensions('json', 'xml', 'ajax', 'csv');
+		$this->Controller->request->params['_ext'] = 'csv';
+		$event = new Event('Controller.startup', $this->Controller);
+		$this->RequestHandler->initialize($event);
+		$this->RequestHandler->startup($event);
+		$this->assertEquals('RequestHandlerTest/csv', $this->Controller->viewPath);
+		$this->assertEquals('csv', $this->Controller->layoutPath);
+	}
+
+/**
  * testStartupCallback method
  *
  * @return void