RequestHandlerComponent.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 0.10.4
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Controller\Component;
  16. use Cake\Controller\Component;
  17. use Cake\Controller\ComponentRegistry;
  18. use Cake\Controller\Controller;
  19. use Cake\Core\App;
  20. use Cake\Core\Configure;
  21. use Cake\Core\Exception\Exception;
  22. use Cake\Event\Event;
  23. use Cake\Network\Response;
  24. use Cake\Routing\Router;
  25. use Cake\Utility\Exception\XmlException;
  26. use Cake\Utility\Inflector;
  27. use Cake\Utility\Xml;
  28. use RuntimeException;
  29. /**
  30. * Request object for handling alternative HTTP requests
  31. *
  32. * Alternative HTTP requests can come from wireless units like mobile phones, palmtop computers,
  33. * and the like. These units have no use for AJAX requests, and this Component can tell how Cake
  34. * should respond to the different needs of a handheld computer and a desktop machine.
  35. *
  36. * @link http://book.cakephp.org/3.0/en/controllers/components/request-handling.html
  37. */
  38. class RequestHandlerComponent extends Component
  39. {
  40. /**
  41. * Determines whether or not callbacks will be fired on this component
  42. *
  43. * @var bool
  44. */
  45. public $enabled = true;
  46. /**
  47. * Holds the reference to Controller::$response
  48. *
  49. * @var \Cake\Network\Response
  50. */
  51. public $response;
  52. /**
  53. * Contains the file extension parsed out by the Router
  54. *
  55. * @var string
  56. * @see \Cake\Routing\Router::extensions()
  57. */
  58. public $ext = null;
  59. /**
  60. * The template to use when rendering the given content type.
  61. *
  62. * @var string
  63. */
  64. protected $_renderType = null;
  65. /**
  66. * Default config
  67. *
  68. * These are merged with user-provided config when the component is used.
  69. *
  70. * - `checkHttpCache` - Whether to check for HTTP cache.
  71. * - `viewClassMap` - Mapping between type and view classes. If undefined
  72. * json, xml, and ajax will be mapped. Defining any types will omit the defaults.
  73. * - `inputTypeMap` - A mapping between types and deserializers for request bodies.
  74. * If undefined json & xml will be mapped. Defining any types will omit the defaults.
  75. *
  76. * @var array
  77. */
  78. protected $_defaultConfig = [
  79. 'checkHttpCache' => true,
  80. 'viewClassMap' => [],
  81. 'inputTypeMap' => []
  82. ];
  83. /**
  84. * Constructor. Parses the accepted content types accepted by the client using HTTP_ACCEPT
  85. *
  86. * @param \Cake\Controller\ComponentRegistry $registry ComponentRegistry object.
  87. * @param array $config Array of config.
  88. */
  89. public function __construct(ComponentRegistry $registry, array $config = [])
  90. {
  91. $config += [
  92. 'viewClassMap' => [
  93. 'json' => 'Json',
  94. 'xml' => 'Xml',
  95. 'ajax' => 'Ajax'
  96. ],
  97. 'inputTypeMap' => [
  98. 'json' => ['json_decode', true],
  99. 'xml' => [[$this, 'convertXml']],
  100. ]
  101. ];
  102. parent::__construct($registry, $config);
  103. }
  104. /**
  105. * Events supported by this component.
  106. *
  107. * @return array
  108. */
  109. public function implementedEvents()
  110. {
  111. return [
  112. 'Controller.startup' => 'startup',
  113. 'Controller.beforeRender' => 'beforeRender',
  114. 'Controller.beforeRedirect' => 'beforeRedirect',
  115. ];
  116. }
  117. /**
  118. * Checks to see if a specific content type has been requested and sets RequestHandler::$ext
  119. * accordingly. Checks the following in order: 1. The '_ext' value parsed by the Router. 2. A specific
  120. * AJAX type request indicated by the presence of a header. 3. The Accept header. With the exception
  121. * of an AJAX request indicated using the second header based method above, the type must have
  122. * been configured in {@link Cake\Routing\Router}.
  123. *
  124. * @param array $config The config data.
  125. * @return void
  126. * @see \Cake\Routing\Router::extensions()
  127. */
  128. public function initialize(array $config)
  129. {
  130. $controller = $this->_registry->getController();
  131. $this->response =& $controller->response;
  132. }
  133. /**
  134. * Set the extension based on the accept headers.
  135. * Compares the accepted types and configured extensions.
  136. * If there is one common type, that is assigned as the ext/content type for the response.
  137. * The type with the highest weight will be set. If the highest weight has more
  138. * than one type matching the extensions, the order in which extensions are specified
  139. * determines which type will be set.
  140. *
  141. * If html is one of the preferred types, no content type will be set, this
  142. * is to avoid issues with browsers that prefer HTML and several other content types.
  143. *
  144. * @param \Cake\Network\Request $request The request instance.
  145. * @param \Cake\Network\Response $response The response instance.
  146. * @return void
  147. */
  148. protected function _setExtension($request, $response)
  149. {
  150. $accept = $request->parseAccept();
  151. if (empty($accept) || current($accept)[0] === 'text/html') {
  152. return;
  153. }
  154. $accepts = $response->mapType($accept);
  155. $preferredTypes = current($accepts);
  156. if (array_intersect($preferredTypes, ['html', 'xhtml'])) {
  157. return;
  158. }
  159. $extensions = array_unique(
  160. array_merge(Router::extensions(), array_keys($this->config('viewClassMap')))
  161. );
  162. foreach ($accepts as $types) {
  163. $ext = array_intersect($extensions, $types);
  164. if (!empty($ext)) {
  165. $this->ext = current($ext);
  166. break;
  167. }
  168. }
  169. }
  170. /**
  171. * The startup method of the RequestHandler enables several automatic behaviors
  172. * related to the detection of certain properties of the HTTP request, including:
  173. *
  174. * If the XML data is POSTed, the data is parsed into an XML object, which is assigned
  175. * to the $data property of the controller, which can then be saved to a model object.
  176. *
  177. * @param \Cake\Event\Event $event The startup event that was fired.
  178. * @return void
  179. */
  180. public function startup(Event $event)
  181. {
  182. $controller = $event->subject();
  183. $request = $controller->request;
  184. if (isset($request->params['_ext'])) {
  185. $this->ext = $request->params['_ext'];
  186. }
  187. if (empty($this->ext) || in_array($this->ext, ['html', 'htm'])) {
  188. $this->_setExtension($request, $this->response);
  189. }
  190. $request->params['isAjax'] = $request->is('ajax');
  191. if (empty($this->ext) && $request->params['isAjax']) {
  192. $this->ext = 'ajax';
  193. }
  194. if ($request->is(['get', 'head', 'options'])) {
  195. return;
  196. }
  197. foreach ($this->config('inputTypeMap') as $type => $handler) {
  198. if (!is_callable($handler[0])) {
  199. throw new RuntimeException(sprintf("Invalid callable for '%s' type.", $type));
  200. }
  201. if ($this->requestedWith($type)) {
  202. $input = call_user_func_array([$request, 'input'], $handler);
  203. $request->data = (array)$input;
  204. }
  205. }
  206. }
  207. /**
  208. * Helper method to parse xml input data, due to lack of anonymous functions
  209. * this lives here.
  210. *
  211. * @param string $xml XML string.
  212. * @return array Xml array data
  213. */
  214. public function convertXml($xml)
  215. {
  216. try {
  217. $xml = Xml::build($xml, ['readFile' => false]);
  218. if (isset($xml->data)) {
  219. return Xml::toArray($xml->data);
  220. }
  221. return Xml::toArray($xml);
  222. } catch (XmlException $e) {
  223. return [];
  224. }
  225. }
  226. /**
  227. * Handles (fakes) redirects for AJAX requests using requestAction()
  228. *
  229. * @param \Cake\Event\Event $event The Controller.beforeRedirect event.
  230. * @param string|array $url A string or array containing the redirect location
  231. * @param \Cake\Network\Response $response The response object.
  232. * @return \Cake\Network\Response|null The response object if the redirect is caught.
  233. */
  234. public function beforeRedirect(Event $event, $url, Response $response)
  235. {
  236. $request = $this->request;
  237. if (!$request->is('ajax')) {
  238. return null;
  239. }
  240. if (empty($url)) {
  241. return null;
  242. }
  243. if (is_array($url)) {
  244. $url = Router::url($url + ['_base' => false]);
  245. }
  246. $query = [];
  247. if (strpos($url, '?') !== false) {
  248. list($url, $querystr) = explode('?', $url, 2);
  249. parse_str($querystr, $query);
  250. }
  251. $controller = $event->subject();
  252. $response->body($controller->requestAction($url, [
  253. 'return',
  254. 'bare' => false,
  255. 'environment' => [
  256. 'REQUEST_METHOD' => 'GET'
  257. ],
  258. 'query' => $query,
  259. 'cookies' => $request->cookies
  260. ]));
  261. $response->statusCode(200);
  262. return $response;
  263. }
  264. /**
  265. * Checks if the response can be considered different according to the request
  266. * headers, and the caching response headers. If it was not modified, then the
  267. * render process is skipped. And the client will get a blank response with a
  268. * "304 Not Modified" header.
  269. *
  270. * - If Router::extensions() is enabled, the layout and template type are
  271. * switched based on the parsed extension or `Accept` header. For example,
  272. * if `controller/action.xml` is requested, the view path becomes
  273. * `app/View/Controller/xml/action.ctp`. Also if `controller/action` is
  274. * requested with `Accept: application/xml` in the headers the view
  275. * path will become `app/View/Controller/xml/action.ctp`. Layout and template
  276. * types will only switch to mime-types recognized by Cake\Network\Response.
  277. * If you need to declare additional mime-types, you can do so using
  278. * Cake\Network\Response::type() in your controller's beforeFilter() method.
  279. * - If a helper with the same name as the extension exists, it is added to
  280. * the controller.
  281. * - If the extension is of a type that RequestHandler understands, it will
  282. * set that Content-type in the response header.
  283. *
  284. * @param \Cake\Event\Event $event The Controller.beforeRender event.
  285. * @return bool false if the render process should be aborted
  286. */
  287. public function beforeRender(Event $event)
  288. {
  289. $isRecognized = (
  290. !in_array($this->ext, ['html', 'htm']) &&
  291. $this->response->getMimeType($this->ext)
  292. );
  293. if (!empty($this->ext) && $isRecognized) {
  294. $this->renderAs($event->subject(), $this->ext);
  295. } else {
  296. $this->response->charset(Configure::read('App.encoding'));
  297. }
  298. if ($this->_config['checkHttpCache'] &&
  299. $this->response->checkNotModified($this->request)
  300. ) {
  301. return false;
  302. }
  303. }
  304. /**
  305. * Returns true if the current call accepts an XML response, false otherwise
  306. *
  307. * @return bool True if client accepts an XML response
  308. */
  309. public function isXml()
  310. {
  311. return $this->prefers('xml');
  312. }
  313. /**
  314. * Returns true if the current call accepts an RSS response, false otherwise
  315. *
  316. * @return bool True if client accepts an RSS response
  317. */
  318. public function isRss()
  319. {
  320. return $this->prefers('rss');
  321. }
  322. /**
  323. * Returns true if the current call accepts an Atom response, false otherwise
  324. *
  325. * @return bool True if client accepts an RSS response
  326. */
  327. public function isAtom()
  328. {
  329. return $this->prefers('atom');
  330. }
  331. /**
  332. * Returns true if user agent string matches a mobile web browser, or if the
  333. * client accepts WAP content.
  334. *
  335. * @return bool True if user agent is a mobile web browser
  336. */
  337. public function isMobile()
  338. {
  339. $request = $this->request;
  340. return $request->is('mobile') || $this->accepts('wap');
  341. }
  342. /**
  343. * Returns true if the client accepts WAP content
  344. *
  345. * @return bool
  346. */
  347. public function isWap()
  348. {
  349. return $this->prefers('wap');
  350. }
  351. /**
  352. * Determines which content types the client accepts. Acceptance is based on
  353. * the file extension parsed by the Router (if present), and by the HTTP_ACCEPT
  354. * header. Unlike Cake\Network\Request::accepts() this method deals entirely with mapped content types.
  355. *
  356. * Usage:
  357. *
  358. * ```
  359. * $this->RequestHandler->accepts(['xml', 'html', 'json']);
  360. * ```
  361. *
  362. * Returns true if the client accepts any of the supplied types.
  363. *
  364. * ```
  365. * $this->RequestHandler->accepts('xml');
  366. * ```
  367. *
  368. * Returns true if the client accepts xml.
  369. *
  370. * @param string|array|null $type Can be null (or no parameter), a string type name, or an
  371. * array of types
  372. * @return mixed If null or no parameter is passed, returns an array of content
  373. * types the client accepts. If a string is passed, returns true
  374. * if the client accepts it. If an array is passed, returns true
  375. * if the client accepts one or more elements in the array.
  376. */
  377. public function accepts($type = null)
  378. {
  379. $request = $this->request;
  380. $response = $this->response;
  381. $accepted = $request->accepts();
  382. if (!$type) {
  383. return $response->mapType($accepted);
  384. }
  385. if (is_array($type)) {
  386. foreach ($type as $t) {
  387. $t = $this->mapAlias($t);
  388. if (in_array($t, $accepted)) {
  389. return true;
  390. }
  391. }
  392. return false;
  393. }
  394. if (is_string($type)) {
  395. return in_array($this->mapAlias($type), $accepted);
  396. }
  397. return false;
  398. }
  399. /**
  400. * Determines the content type of the data the client has sent (i.e. in a POST request)
  401. *
  402. * @param string|array|null $type Can be null (or no parameter), a string type name, or an array of types
  403. * @return mixed If a single type is supplied a boolean will be returned. If no type is provided
  404. * The mapped value of CONTENT_TYPE will be returned. If an array is supplied the first type
  405. * in the request content type will be returned.
  406. */
  407. public function requestedWith($type = null)
  408. {
  409. $request = $this->request;
  410. if (!$request->is('post') &&
  411. !$request->is('put') &&
  412. !$request->is('patch') &&
  413. !$request->is('delete')
  414. ) {
  415. return null;
  416. }
  417. if (is_array($type)) {
  418. foreach ($type as $t) {
  419. if ($this->requestedWith($t)) {
  420. return $t;
  421. }
  422. }
  423. return false;
  424. }
  425. list($contentType) = explode(';', $request->env('CONTENT_TYPE'));
  426. if ($contentType === '') {
  427. list($contentType) = explode(';', $request->header('CONTENT_TYPE'));
  428. }
  429. $response = $this->response;
  430. if ($type === null) {
  431. return $response->mapType($contentType);
  432. }
  433. if (is_string($type)) {
  434. return ($type === $response->mapType($contentType));
  435. }
  436. }
  437. /**
  438. * Determines which content-types the client prefers. If no parameters are given,
  439. * the single content-type that the client most likely prefers is returned. If $type is
  440. * an array, the first item in the array that the client accepts is returned.
  441. * Preference is determined primarily by the file extension parsed by the Router
  442. * if provided, and secondarily by the list of content-types provided in
  443. * HTTP_ACCEPT.
  444. *
  445. * @param string|array|null $type An optional array of 'friendly' content-type names, i.e.
  446. * 'html', 'xml', 'js', etc.
  447. * @return mixed If $type is null or not provided, the first content-type in the
  448. * list, based on preference, is returned. If a single type is provided
  449. * a boolean will be returned if that type is preferred.
  450. * If an array of types are provided then the first preferred type is returned.
  451. * If no type is provided the first preferred type is returned.
  452. */
  453. public function prefers($type = null)
  454. {
  455. $request = $this->request;
  456. $response = $this->response;
  457. $acceptRaw = $request->parseAccept();
  458. if (empty($acceptRaw)) {
  459. return $this->ext;
  460. }
  461. $accepts = $response->mapType(array_shift($acceptRaw));
  462. if (!$type) {
  463. if (empty($this->ext) && !empty($accepts)) {
  464. return $accepts[0];
  465. }
  466. return $this->ext;
  467. }
  468. $types = (array)$type;
  469. if (count($types) === 1) {
  470. if (!empty($this->ext)) {
  471. return in_array($this->ext, $types);
  472. }
  473. return in_array($types[0], $accepts);
  474. }
  475. $intersect = array_values(array_intersect($accepts, $types));
  476. if (empty($intersect)) {
  477. return false;
  478. }
  479. return $intersect[0];
  480. }
  481. /**
  482. * Sets either the view class if one exists or the layout and template path of the view.
  483. * The names of these are derived from the $type input parameter.
  484. *
  485. * ### Usage:
  486. *
  487. * Render the response as an 'ajax' response.
  488. *
  489. * ```
  490. * $this->RequestHandler->renderAs($this, 'ajax');
  491. * ```
  492. *
  493. * Render the response as an xml file and force the result as a file download.
  494. *
  495. * ```
  496. * $this->RequestHandler->renderAs($this, 'xml', ['attachment' => 'myfile.xml'];
  497. * ```
  498. *
  499. * @param \Cake\Controller\Controller $controller A reference to a controller object
  500. * @param string $type Type of response to send (e.g: 'ajax')
  501. * @param array $options Array of options to use
  502. * @return void
  503. * @see \Cake\Controller\Component\RequestHandlerComponent::respondAs()
  504. */
  505. public function renderAs(Controller $controller, $type, array $options = [])
  506. {
  507. $defaults = ['charset' => 'UTF-8'];
  508. $viewClassMap = $this->config('viewClassMap');
  509. if (Configure::read('App.encoding') !== null) {
  510. $defaults['charset'] = Configure::read('App.encoding');
  511. }
  512. $options += $defaults;
  513. $builder = $controller->viewBuilder();
  514. if (array_key_exists($type, $viewClassMap)) {
  515. $view = $viewClassMap[$type];
  516. } else {
  517. $view = Inflector::classify($type);
  518. }
  519. $viewClass = null;
  520. if ($builder->className() === null) {
  521. $viewClass = App::className($view, 'View', 'View');
  522. }
  523. if ($viewClass) {
  524. $controller->viewClass = $viewClass;
  525. $builder->className($viewClass);
  526. } else {
  527. if (empty($this->_renderType)) {
  528. $builder->templatePath($builder->templatePath() . DIRECTORY_SEPARATOR . $type);
  529. } else {
  530. $builder->templatePath(preg_replace(
  531. "/([\/\\\\]{$this->_renderType})$/",
  532. DIRECTORY_SEPARATOR . $type,
  533. $builder->templatePath()
  534. ));
  535. }
  536. $this->_renderType = $type;
  537. $builder->layoutPath($type);
  538. }
  539. $response = $this->response;
  540. if ($response->getMimeType($type)) {
  541. $this->respondAs($type, $options);
  542. }
  543. $helper = ucfirst($type);
  544. if (!in_array($helper, $controller->helpers) && empty($controller->helpers[$helper])) {
  545. $helperClass = App::className($helper, 'View/Helper', 'Helper');
  546. if ($helperClass !== false) {
  547. $controller->helpers[] = $helper;
  548. }
  549. }
  550. }
  551. /**
  552. * Sets the response header based on type map index name. This wraps several methods
  553. * available on Cake\Network\Response. It also allows you to use Content-Type aliases.
  554. *
  555. * @param string|array $type Friendly type name, i.e. 'html' or 'xml', or a full content-type,
  556. * like 'application/x-shockwave'.
  557. * @param array $options If $type is a friendly type name that is associated with
  558. * more than one type of content, $index is used to select which content-type to use.
  559. * @return bool Returns false if the friendly type name given in $type does
  560. * not exist in the type map, or if the Content-type header has
  561. * already been set by this method.
  562. */
  563. public function respondAs($type, array $options = [])
  564. {
  565. $defaults = ['index' => null, 'charset' => null, 'attachment' => false];
  566. $options += $defaults;
  567. $cType = $type;
  568. $response = $this->response;
  569. if (strpos($type, '/') === false) {
  570. $cType = $response->getMimeType($type);
  571. }
  572. if (is_array($cType)) {
  573. if (isset($cType[$options['index']])) {
  574. $cType = $cType[$options['index']];
  575. }
  576. if ($this->prefers($cType)) {
  577. $cType = $this->prefers($cType);
  578. } else {
  579. $cType = $cType[0];
  580. }
  581. }
  582. if (!$type) {
  583. return false;
  584. }
  585. if (empty($this->request->params['requested'])) {
  586. $response->type($cType);
  587. }
  588. if (!empty($options['charset'])) {
  589. $response->charset($options['charset']);
  590. }
  591. if (!empty($options['attachment'])) {
  592. $response->download($options['attachment']);
  593. }
  594. return true;
  595. }
  596. /**
  597. * Returns the current response type (Content-type header), or null if not alias exists
  598. *
  599. * @return mixed A string content type alias, or raw content type if no alias map exists,
  600. * otherwise null
  601. */
  602. public function responseType()
  603. {
  604. $response = $this->response;
  605. return $response->mapType($response->type());
  606. }
  607. /**
  608. * Maps a content type alias back to its mime-type(s)
  609. *
  610. * @param string|array $alias String alias to convert back into a content type. Or an array of aliases to map.
  611. * @return string|null|array Null on an undefined alias. String value of the mapped alias type. If an
  612. * alias maps to more than one content type, the first one will be returned. If an array is provided
  613. * for $alias, an array of mapped types will be returned.
  614. */
  615. public function mapAlias($alias)
  616. {
  617. if (is_array($alias)) {
  618. return array_map([$this, 'mapAlias'], $alias);
  619. }
  620. $response = $this->response;
  621. $type = $response->getMimeType($alias);
  622. if ($type) {
  623. if (is_array($type)) {
  624. return $type[0];
  625. }
  626. return $type;
  627. }
  628. return null;
  629. }
  630. /**
  631. * Add a new mapped input type. Mapped input types are automatically
  632. * converted by RequestHandlerComponent during the startup() callback.
  633. *
  634. * @param string $type The type alias being converted, ie. json
  635. * @param array $handler The handler array for the type. The first index should
  636. * be the handling callback, all other arguments should be additional parameters
  637. * for the handler.
  638. * @return void
  639. * @throws \Cake\Core\Exception\Exception
  640. * @deprecated 3.1.0 Use config('addInputType', ...) instead.
  641. */
  642. public function addInputType($type, $handler)
  643. {
  644. trigger_error(
  645. 'RequestHandlerComponent::addInputType() is deprecated. Use config("inputTypeMap", ...) instead.',
  646. E_USER_DEPRECATED
  647. );
  648. if (!is_array($handler) || !isset($handler[0]) || !is_callable($handler[0])) {
  649. throw new Exception('You must give a handler callback.');
  650. }
  651. $this->config('inputTypeMap.' . $type, $handler);
  652. }
  653. /**
  654. * Getter/setter for viewClassMap
  655. *
  656. * @param array|string|null $type The type string or array with format `['type' => 'viewClass']` to map one or more
  657. * @param array|null $viewClass The viewClass to be used for the type without `View` appended
  658. * @return array|string Returns viewClass when only string $type is set, else array with viewClassMap
  659. * @deprecated 3.1.0 Use config('viewClassMap', ...) instead.
  660. */
  661. public function viewClassMap($type = null, $viewClass = null)
  662. {
  663. trigger_error(
  664. 'RequestHandlerComponent::viewClassMap() is deprecated. Use config("viewClassMap", ...) instead.',
  665. E_USER_DEPRECATED
  666. );
  667. if (!$viewClass && is_string($type)) {
  668. return $this->config('viewClassMap.' . $type);
  669. }
  670. if (is_string($type)) {
  671. $this->config('viewClassMap.' . $type, $viewClass);
  672. } elseif (is_array($type)) {
  673. $this->config('viewClassMap', $type, true);
  674. }
  675. return $this->config('viewClassMap');
  676. }
  677. }