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