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 (http://cakephp.org)
10: * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
11: *
12: * Licensed under The MIT License
13: * Redistributions of files must retain the above copyright notice.
14: *
15: * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
16: * @link http://cakephp.org CakePHP(tm) Project
17: * @package cake
18: * @subpackage cake.cake.libs.controller.components
19: * @since CakePHP(tm) v 0.10.4.1076
20: * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
21: */
22:
23: /**
24: * Request object for handling HTTP requests
25: *
26: * @package cake
27: * @subpackage cake.cake.libs.controller.components
28: * @link http://book.cakephp.org/1.3/en/The-Manual/Core-Components/Request-Handling.html
29: *
30: */
31: class RequestHandlerComponent extends Object {
32:
33: /**
34: * The layout that will be switched to for Ajax requests
35: *
36: * @var string
37: * @access public
38: * @see RequestHandler::setAjax()
39: */
40: var $ajaxLayout = 'ajax';
41:
42: /**
43: * Determines whether or not callbacks will be fired on this component
44: *
45: * @var boolean
46: * @access public
47: */
48: var $enabled = true;
49:
50: /**
51: * Holds the content-type of the response that is set when using
52: * RequestHandler::respondAs()
53: *
54: * @var string
55: * @access private
56: */
57: var $__responseTypeSet = null;
58:
59: /**
60: * Holds the copy of Controller::$params
61: *
62: * @var array
63: * @access public
64: */
65: var $params = array();
66:
67: /**
68: * Friendly content-type mappings used to set response types and determine
69: * request types. Can be modified with RequestHandler::setContent()
70: *
71: * @var array
72: * @access private
73: * @see RequestHandlerComponent::setContent
74: */
75: var $__requestContent = array(
76: 'javascript' => 'text/javascript',
77: 'js' => 'text/javascript',
78: 'json' => 'application/json',
79: 'css' => 'text/css',
80: 'html' => array('text/html', '*/*'),
81: 'text' => 'text/plain',
82: 'txt' => 'text/plain',
83: 'csv' => array('text/csv', 'application/vnd.ms-excel', 'text/plain'),
84: 'form' => 'application/x-www-form-urlencoded',
85: 'file' => 'multipart/form-data',
86: 'xhtml' => array('application/xhtml+xml', 'application/xhtml', 'text/xhtml'),
87: 'xhtml-mobile' => 'application/vnd.wap.xhtml+xml',
88: 'xml' => array('application/xml', 'text/xml'),
89: 'rss' => 'application/rss+xml',
90: 'atom' => 'application/atom+xml',
91: 'amf' => 'application/x-amf',
92: 'wap' => array(
93: 'text/vnd.wap.wml',
94: 'text/vnd.wap.wmlscript',
95: 'image/vnd.wap.wbmp'
96: ),
97: 'wml' => 'text/vnd.wap.wml',
98: 'wmlscript' => 'text/vnd.wap.wmlscript',
99: 'wbmp' => 'image/vnd.wap.wbmp',
100: 'pdf' => 'application/pdf',
101: 'zip' => 'application/x-zip',
102: 'tar' => 'application/x-tar'
103: );
104:
105: /**
106: * List of regular expressions for matching mobile device's user agent string
107: *
108: * @var array
109: * @access public
110: */
111: var $mobileUA = array(
112: 'Android',
113: 'AvantGo',
114: 'BlackBerry',
115: 'DoCoMo',
116: 'iPod',
117: 'iPhone',
118: 'iPad',
119: 'J2ME',
120: 'MIDP',
121: 'NetFront',
122: 'Nokia',
123: 'Opera Mini',
124: 'Opera Mobi',
125: 'PalmOS',
126: 'PalmSource',
127: 'portalmmm',
128: 'Plucker',
129: 'ReqwirelessWeb',
130: 'SonyEricsson',
131: 'Symbian',
132: 'UP\.Browser',
133: 'webOS',
134: 'Windows CE',
135: 'Windows Phone OS',
136: 'Xiino'
137: );
138:
139: /**
140: * Content-types accepted by the client. If extension parsing is enabled in the
141: * Router, and an extension is detected, the corresponding content-type will be
142: * used as the overriding primary content-type accepted.
143: *
144: * @var array
145: * @access private
146: * @see Router::parseExtensions()
147: */
148: var $__acceptTypes = array();
149:
150: /**
151: * The template to use when rendering the given content type.
152: *
153: * @var string
154: * @access private
155: */
156: var $__renderType = null;
157:
158: /**
159: * Contains the file extension parsed out by the Router
160: *
161: * @var string
162: * @access public
163: * @see Router::parseExtensions()
164: */
165: var $ext = null;
166:
167: /**
168: * Flag set when MIME types have been initialized
169: *
170: * @var boolean
171: * @access private
172: * @see RequestHandler::__initializeTypes()
173: */
174: var $__typesInitialized = false;
175:
176: /**
177: * Constructor. Parses the accepted content types accepted by the client using HTTP_ACCEPT
178: *
179: */
180: function __construct() {
181: $this->__acceptTypes = explode(',', env('HTTP_ACCEPT'));
182: $this->__acceptTypes = array_map('trim', $this->__acceptTypes);
183:
184: foreach ($this->__acceptTypes as $i => $type) {
185: if (strpos($type, ';')) {
186: $type = explode(';', $type);
187: $this->__acceptTypes[$i] = trim($type[0]);
188: }
189: }
190: parent::__construct();
191: }
192:
193: /**
194: * Initializes the component, gets a reference to Controller::$parameters, and
195: * checks to see if a file extension has been parsed by the Router. If yes, the
196: * corresponding content-type is pushed onto the list of accepted content-types
197: * as the first item.
198: *
199: * @param object $controller A reference to the controller
200: * @param array $settings Array of settings to _set().
201: * @return void
202: * @see Router::parseExtensions()
203: * @access public
204: */
205: function initialize(&$controller, $settings = array()) {
206: if (isset($controller->params['url']['ext'])) {
207: $this->ext = $controller->params['url']['ext'];
208: }
209: $this->params = $controller->params;
210: $this->_set($settings);
211: }
212:
213: /**
214: * The startup method of the RequestHandler enables several automatic behaviors
215: * related to the detection of certain properties of the HTTP request, including:
216: *
217: * - Disabling layout rendering for Ajax requests (based on the HTTP_X_REQUESTED_WITH header)
218: * - If Router::parseExtensions() is enabled, the layout and template type are
219: * switched based on the parsed extension. For example, if controller/action.xml
220: * is requested, the view path becomes <i>app/views/controller/xml/action.ctp</i>.
221: * - If a helper with the same name as the extension exists, it is added to the controller.
222: * - If the extension is of a type that RequestHandler understands, it will set that
223: * Content-type in the response header.
224: * - If the XML data is POSTed, the data is parsed into an XML object, which is assigned
225: * to the $data property of the controller, which can then be saved to a model object.
226: *
227: * @param object $controller A reference to the controller
228: * @return void
229: * @access public
230: */
231: function startup(&$controller) {
232: if (!$this->enabled) {
233: return;
234: }
235:
236: $this->__initializeTypes();
237: $controller->params['isAjax'] = $this->isAjax();
238: $isRecognized = (
239: !in_array($this->ext, array('html', 'htm')) &&
240: in_array($this->ext, array_keys($this->__requestContent))
241: );
242:
243: if (!empty($this->ext) && $isRecognized) {
244: $this->renderAs($controller, $this->ext);
245: } elseif ($this->isAjax()) {
246: $this->renderAs($controller, 'ajax');
247: } elseif (empty($this->ext) || in_array($this->ext, array('html', 'htm'))) {
248: $this->respondAs('html', array('charset' => Configure::read('App.encoding')));
249: }
250:
251: if ($this->requestedWith('xml')) {
252: if (!class_exists('XmlNode')) {
253: App::import('Core', 'Xml');
254: }
255: $xml = new Xml(trim(file_get_contents('php://input')));
256:
257: if (count($xml->children) == 1 && is_object($dataNode = $xml->child('data'))) {
258: $controller->data = $dataNode->toArray();
259: } else {
260: $controller->data = $xml->toArray();
261: }
262: }
263: }
264:
265: /**
266: * Handles (fakes) redirects for Ajax requests using requestAction()
267: *
268: * @param object $controller A reference to the controller
269: * @param mixed $url A string or array containing the redirect location
270: * @param mixed HTTP Status for redirect
271: * @access public
272: */
273: function beforeRedirect(&$controller, $url, $status = null) {
274: if (!$this->isAjax()) {
275: return;
276: }
277: foreach ($_POST as $key => $val) {
278: unset($_POST[$key]);
279: }
280: if (is_array($url)) {
281: $url = Router::url($url + array('base' => false));
282: }
283: if (!empty($status)) {
284: $statusCode = $controller->httpCodes($status);
285: $code = key($statusCode);
286: $msg = $statusCode[$code];
287: $controller->header("HTTP/1.1 {$code} {$msg}");
288: }
289: echo $this->requestAction($url, array('return', 'bare' => false));
290: $this->_stop();
291: }
292:
293: /**
294: * Returns true if the current HTTP request is Ajax, false otherwise
295: *
296: * @return boolean True if call is Ajax
297: * @access public
298: */
299: function isAjax() {
300: return env('HTTP_X_REQUESTED_WITH') === "XMLHttpRequest";
301: }
302:
303: /**
304: * Returns true if the current HTTP request is coming from a Flash-based client
305: *
306: * @return boolean True if call is from Flash
307: * @access public
308: */
309: function isFlash() {
310: return (preg_match('/^(Shockwave|Adobe) Flash/', env('HTTP_USER_AGENT')) == 1);
311: }
312:
313: /**
314: * Returns true if the current request is over HTTPS, false otherwise.
315: *
316: * @return bool True if call is over HTTPS
317: * @access public
318: */
319: function isSSL() {
320: return env('HTTPS');
321: }
322:
323: /**
324: * Returns true if the current call accepts an XML response, false otherwise
325: *
326: * @return boolean True if client accepts an XML response
327: * @access public
328: */
329: function isXml() {
330: return $this->prefers('xml');
331: }
332:
333: /**
334: * Returns true if the current call accepts an RSS response, false otherwise
335: *
336: * @return boolean True if client accepts an RSS response
337: * @access public
338: */
339: function isRss() {
340: return $this->prefers('rss');
341: }
342:
343: /**
344: * Returns true if the current call accepts an Atom response, false otherwise
345: *
346: * @return boolean True if client accepts an RSS response
347: * @access public
348: */
349: function isAtom() {
350: return $this->prefers('atom');
351: }
352:
353: /**
354: * Returns true if user agent string matches a mobile web browser, or if the
355: * client accepts WAP content.
356: *
357: * @return boolean True if user agent is a mobile web browser
358: * @access public
359: * @deprecated Use of constant REQUEST_MOBILE_UA is deprecated and will be removed in future versions
360: */
361: function isMobile() {
362: if (defined('REQUEST_MOBILE_UA')) {
363: $regex = '/' . REQUEST_MOBILE_UA . '/i';
364: } else {
365: $regex = '/' . implode('|', $this->mobileUA) . '/i';
366: }
367:
368: if (preg_match($regex, env('HTTP_USER_AGENT')) || $this->accepts('wap')) {
369: return true;
370: }
371: return false;
372: }
373:
374: /**
375: * Returns true if the client accepts WAP content
376: *
377: * @return bool
378: * @access public
379: */
380: function isWap() {
381: return $this->prefers('wap');
382: }
383:
384: /**
385: * Returns true if the current call a POST request
386: *
387: * @return boolean True if call is a POST
388: * @access public
389: */
390: function isPost() {
391: return (strtolower(env('REQUEST_METHOD')) == 'post');
392: }
393:
394: /**
395: * Returns true if the current call a PUT request
396: *
397: * @return boolean True if call is a PUT
398: * @access public
399: */
400: function isPut() {
401: return (strtolower(env('REQUEST_METHOD')) == 'put');
402: }
403:
404: /**
405: * Returns true if the current call a GET request
406: *
407: * @return boolean True if call is a GET
408: * @access public
409: */
410: function isGet() {
411: return (strtolower(env('REQUEST_METHOD')) == 'get');
412: }
413:
414: /**
415: * Returns true if the current call a DELETE request
416: *
417: * @return boolean True if call is a DELETE
418: * @access public
419: */
420: function isDelete() {
421: return (strtolower(env('REQUEST_METHOD')) == 'delete');
422: }
423:
424: /**
425: * Gets Prototype version if call is Ajax, otherwise empty string.
426: * The Prototype library sets a special "Prototype version" HTTP header.
427: *
428: * @return string Prototype version of component making Ajax call
429: * @access public
430: */
431: function getAjaxVersion() {
432: if (env('HTTP_X_PROTOTYPE_VERSION') != null) {
433: return env('HTTP_X_PROTOTYPE_VERSION');
434: }
435: return false;
436: }
437:
438: /**
439: * Adds/sets the Content-type(s) for the given name. This method allows
440: * content-types to be mapped to friendly aliases (or extensions), which allows
441: * RequestHandler to automatically respond to requests of that type in the
442: * startup method.
443: *
444: * @param string $name The name of the Content-type, i.e. "html", "xml", "css"
445: * @param mixed $type The Content-type or array of Content-types assigned to the name,
446: * i.e. "text/html", or "application/xml"
447: * @return void
448: * @access public
449: */
450: function setContent($name, $type = null) {
451: if (is_array($name)) {
452: $this->__requestContent = array_merge($this->__requestContent, $name);
453: return;
454: }
455: $this->__requestContent[$name] = $type;
456: }
457:
458: /**
459: * Gets the server name from which this request was referred
460: *
461: * @return string Server address
462: * @access public
463: */
464: function getReferer() {
465: if (env('HTTP_HOST') != null) {
466: $sessHost = env('HTTP_HOST');
467: }
468:
469: if (env('HTTP_X_FORWARDED_HOST') != null) {
470: $sessHost = env('HTTP_X_FORWARDED_HOST');
471: }
472: return trim(preg_replace('/(?:\:.*)/', '', $sessHost));
473: }
474:
475: /**
476: * Gets remote client IP
477: *
478: * @return string Client IP address
479: * @access public
480: */
481: function getClientIP($safe = true) {
482: if (!$safe && env('HTTP_X_FORWARDED_FOR') != null) {
483: $ipaddr = preg_replace('/(?:,.*)/', '', env('HTTP_X_FORWARDED_FOR'));
484: } else {
485: if (env('HTTP_CLIENT_IP') != null) {
486: $ipaddr = env('HTTP_CLIENT_IP');
487: } else {
488: $ipaddr = env('REMOTE_ADDR');
489: }
490: }
491:
492: if (env('HTTP_CLIENTADDRESS') != null) {
493: $tmpipaddr = env('HTTP_CLIENTADDRESS');
494:
495: if (!empty($tmpipaddr)) {
496: $ipaddr = preg_replace('/(?:,.*)/', '', $tmpipaddr);
497: }
498: }
499: return trim($ipaddr);
500: }
501:
502: /**
503: * Determines which content types the client accepts. Acceptance is based on
504: * the file extension parsed by the Router (if present), and by the HTTP_ACCEPT
505: * header.
506: *
507: * @param mixed $type Can be null (or no parameter), a string type name, or an
508: * array of types
509: * @return mixed If null or no parameter is passed, returns an array of content
510: * types the client accepts. If a string is passed, returns true
511: * if the client accepts it. If an array is passed, returns true
512: * if the client accepts one or more elements in the array.
513: * @access public
514: * @see RequestHandlerComponent::setContent()
515: */
516: function accepts($type = null) {
517: $this->__initializeTypes();
518:
519: if ($type == null) {
520: return $this->mapType($this->__acceptTypes);
521:
522: } elseif (is_array($type)) {
523: foreach ($type as $t) {
524: if ($this->accepts($t) == true) {
525: return true;
526: }
527: }
528: return false;
529: } elseif (is_string($type)) {
530:
531: if (!isset($this->__requestContent[$type])) {
532: return false;
533: }
534:
535: $content = $this->__requestContent[$type];
536:
537: if (is_array($content)) {
538: foreach ($content as $c) {
539: if (in_array($c, $this->__acceptTypes)) {
540: return true;
541: }
542: }
543: } else {
544: if (in_array($content, $this->__acceptTypes)) {
545: return true;
546: }
547: }
548: }
549: }
550:
551: /**
552: * Determines the content type of the data the client has sent (i.e. in a POST request)
553: *
554: * @param mixed $type Can be null (or no parameter), a string type name, or an array of types
555: * @return mixed
556: * @access public
557: */
558: function requestedWith($type = null) {
559: if (!$this->isPost() && !$this->isPut()) {
560: return null;
561: }
562:
563: list($contentType) = explode(';', env('CONTENT_TYPE'));
564: if ($type == null) {
565: return $this->mapType($contentType);
566: } elseif (is_array($type)) {
567: foreach ($type as $t) {
568: if ($this->requestedWith($t)) {
569: return $this->mapType($t);
570: }
571: }
572: return false;
573: } elseif (is_string($type)) {
574: return ($type == $this->mapType($contentType));
575: }
576: }
577:
578: /**
579: * Determines which content-types the client prefers. If no parameters are given,
580: * the content-type that the client most likely prefers is returned. If $type is
581: * an array, the first item in the array that the client accepts is returned.
582: * Preference is determined primarily by the file extension parsed by the Router
583: * if provided, and secondarily by the list of content-types provided in
584: * HTTP_ACCEPT.
585: *
586: * @param mixed $type An optional array of 'friendly' content-type names, i.e.
587: * 'html', 'xml', 'js', etc.
588: * @return mixed If $type is null or not provided, the first content-type in the
589: * list, based on preference, is returned.
590: * @access public
591: * @see RequestHandlerComponent::setContent()
592: */
593: function prefers($type = null) {
594: $this->__initializeTypes();
595: $accept = $this->accepts();
596:
597: if ($type == null) {
598: if (empty($this->ext)) {
599: if (is_array($accept)) {
600: return $accept[0];
601: }
602: return $accept;
603: }
604: return $this->ext;
605: }
606:
607: $types = $type;
608: if (is_string($type)) {
609: $types = array($type);
610: }
611:
612: if (count($types) === 1) {
613: if (!empty($this->ext)) {
614: return ($types[0] == $this->ext);
615: }
616: return ($types[0] == $accept[0]);
617: }
618: $accepts = array();
619:
620: foreach ($types as $type) {
621: if (in_array($type, $accept)) {
622: $accepts[] = $type;
623: }
624: }
625:
626: if (count($accepts) === 0) {
627: return false;
628: } elseif (count($types) === 1) {
629: return ($types[0] === $accepts[0]);
630: } elseif (count($accepts) === 1) {
631: return $accepts[0];
632: }
633:
634: $acceptedTypes = array();
635: foreach ($this->__acceptTypes as $type) {
636: $acceptedTypes[] = $this->mapType($type);
637: }
638: $accepts = array_intersect($acceptedTypes, $accepts);
639: return $accepts[0];
640: }
641:
642: /**
643: * Sets the layout and template paths for the content type defined by $type.
644: *
645: * @param object $controller A reference to a controller object
646: * @param string $type Type of response to send (e.g: 'ajax')
647: * @return void
648: * @access public
649: * @see RequestHandlerComponent::setContent()
650: * @see RequestHandlerComponent::respondAs()
651: */
652: function renderAs(&$controller, $type) {
653: $this->__initializeTypes();
654: $options = array('charset' => 'UTF-8');
655:
656: if (Configure::read('App.encoding') !== null) {
657: $options = array('charset' => Configure::read('App.encoding'));
658: }
659:
660: if ($type == 'ajax') {
661: $controller->layout = $this->ajaxLayout;
662: return $this->respondAs('html', $options);
663: }
664: $controller->ext = '.ctp';
665:
666: if (empty($this->__renderType)) {
667: $controller->viewPath .= DS . $type;
668: } else {
669: $remove = preg_replace("/([\/\\\\]{$this->__renderType})$/", DS . $type, $controller->viewPath);
670: $controller->viewPath = $remove;
671: }
672: $this->__renderType = $type;
673: $controller->layoutPath = $type;
674:
675: if (isset($this->__requestContent[$type])) {
676: $this->respondAs($type, $options);
677: }
678:
679: $helper = ucfirst($type);
680: $isAdded = (
681: in_array($helper, $controller->helpers) ||
682: array_key_exists($helper, $controller->helpers)
683: );
684:
685: if (!$isAdded) {
686: if (App::import('Helper', $helper)) {
687: $controller->helpers[] = $helper;
688: }
689: }
690: }
691:
692: /**
693: * Sets the response header based on type map index name. If DEBUG is greater than 2, the header
694: * is not set.
695: *
696: * @param mixed $type Friendly type name, i.e. 'html' or 'xml', or a full content-type,
697: * like 'application/x-shockwave'.
698: * @param array $options If $type is a friendly type name that is associated with
699: * more than one type of content, $index is used to select which content-type to use.
700: * @return boolean Returns false if the friendly type name given in $type does
701: * not exist in the type map, or if the Content-type header has
702: * already been set by this method.
703: * @access public
704: * @see RequestHandlerComponent::setContent()
705: */
706: function respondAs($type, $options = array()) {
707: $this->__initializeTypes();
708: if (!array_key_exists($type, $this->__requestContent) && strpos($type, '/') === false) {
709: return false;
710: }
711: $defaults = array('index' => 0, 'charset' => null, 'attachment' => false);
712: $options = array_merge($defaults, $options);
713:
714: if (strpos($type, '/') === false && isset($this->__requestContent[$type])) {
715: $cType = null;
716: if (is_array($this->__requestContent[$type]) && isset($this->__requestContent[$type][$options['index']])) {
717: $cType = $this->__requestContent[$type][$options['index']];
718: } elseif (is_array($this->__requestContent[$type]) && isset($this->__requestContent[$type][0])) {
719: $cType = $this->__requestContent[$type][0];
720: } elseif (isset($this->__requestContent[$type])) {
721: $cType = $this->__requestContent[$type];
722: } else {
723: return false;
724: }
725:
726: if (is_array($cType)) {
727: if ($this->prefers($cType)) {
728: $cType = $this->prefers($cType);
729: } else {
730: $cType = $cType[0];
731: }
732: }
733: } else {
734: $cType = $type;
735: }
736:
737: if ($cType != null) {
738: $header = 'Content-type: ' . $cType;
739:
740: if (!empty($options['charset'])) {
741: $header .= '; charset=' . $options['charset'];
742: }
743: if (!empty($options['attachment'])) {
744: $this->_header("Content-Disposition: attachment; filename=\"{$options['attachment']}\"");
745: }
746: if (Configure::read() < 2 && !defined('CAKEPHP_SHELL') && empty($this->params['requested'])) {
747: $this->_header($header);
748: }
749: $this->__responseTypeSet = $cType;
750: return true;
751: }
752: return false;
753: }
754:
755: /**
756: * Wrapper for header() so calls can be easily tested.
757: *
758: * @param string $header The header to be sent.
759: * @return void
760: * @access protected
761: */
762: function _header($header) {
763: header($header);
764: }
765:
766: /**
767: * Returns the current response type (Content-type header), or null if none has been set
768: *
769: * @return mixed A string content type alias, or raw content type if no alias map exists,
770: * otherwise null
771: * @access public
772: */
773: function responseType() {
774: if ($this->__responseTypeSet == null) {
775: return null;
776: }
777: return $this->mapType($this->__responseTypeSet);
778: }
779:
780: /**
781: * Maps a content-type back to an alias
782: *
783: * @param mixed $type Content type
784: * @return mixed Alias
785: * @access public
786: */
787: function mapType($ctype) {
788: if (is_array($ctype)) {
789: $out = array();
790: foreach ($ctype as $t) {
791: $out[] = $this->mapType($t);
792: }
793: return $out;
794: } else {
795: $keys = array_keys($this->__requestContent);
796: $count = count($keys);
797:
798: for ($i = 0; $i < $count; $i++) {
799: $name = $keys[$i];
800: $type = $this->__requestContent[$name];
801:
802: if (is_array($type) && in_array($ctype, $type)) {
803: return $name;
804: } elseif (!is_array($type) && $type == $ctype) {
805: return $name;
806: }
807: }
808: return $ctype;
809: }
810: }
811:
812: /**
813: * Initializes MIME types
814: *
815: * @return void
816: * @access private
817: */
818: function __initializeTypes() {
819: if ($this->__typesInitialized) {
820: return;
821: }
822: if (isset($this->__requestContent[$this->ext])) {
823: $content = $this->__requestContent[$this->ext];
824: if (is_array($content)) {
825: $content = $content[0];
826: }
827: array_unshift($this->__acceptTypes, $content);
828: }
829: $this->__typesInitialized = true;
830: }
831: }
832: