diff --git a/src/Tonic/Request.php b/src/Tonic/Request.php index 7cbc771..4e2dc87 100644 --- a/src/Tonic/Request.php +++ b/src/Tonic/Request.php @@ -13,6 +13,7 @@ class Request protected $contentType; protected $data; protected $accept = array(); + protected $acceptParams = array(); protected $acceptLanguage = array(); protected $ifMatch = array(); protected $ifNoneMatch = array(); @@ -62,7 +63,9 @@ public function __construct($options = array()) $this->contentType = $this->getContentTypeFromEnvironment($options); $this->data = $this->getDataFromEnvironment($options); - $this->accept = array_unique(array_merge($this->accept, $this->getAcceptArrayFromEnvironment($this->getOption($options, 'accept')))); + $accept = $this->getAcceptArrayFromEnvironment($this->getOption($options, 'accept'), $acceptParams); + $this->acceptParams = $acceptParams; + $this->accept = array_merge($this->accept, $accept); $this->acceptLanguage = array_unique(array_merge($this->acceptLanguage, $this->getAcceptArrayFromEnvironment($this->getOption($options, 'acceptLanguage')))); $this->ifMatch = $this->getMatchArrayFromEnvironment($this->getOption($options, 'ifMatch')); @@ -175,6 +178,11 @@ public function setAccept($value) } } + public function getAcceptParams() + { + return $this->acceptParams; + } + public function getAcceptLanguage() { return $this->acceptLanguage; @@ -243,7 +251,7 @@ private function getMethodFromEnvironment($options) $override = strtoupper($this->getHeader('xHttpMethodOverride')); if ($override && $method == 'POST') { $method = $override; - + } else { // get override value from URL and use if applicable if ( @@ -254,7 +262,7 @@ private function getMethodFromEnvironment($options) if (preg_match('/![A-Z]+$/', $this->uri, $match, PREG_OFFSET_CAPTURE)) { $method = strtoupper(substr($this->uri, $match[0][1] + 1)); $this->uri = substr($this->uri, 0, $match[0][1]); - + // get override value from _method querystring } elseif (isset($_GET['_method'])) { $method = strtoupper($_GET['_method']); @@ -326,28 +334,51 @@ private function getURIFromEnvironment($options) } /** - * Get accepted content mimetypes from request header - * @param str $acceptString + * Get accepted content mimetypes and accept parameters from request header + * @param str $acceptString accept string + * @param array $params accept parameters * @return str[] */ - private function getAcceptArrayFromEnvironment($acceptString) + private function getAcceptArrayFromEnvironment($acceptString, &$acceptParamArray = array()) { - $accept = $acceptArray = array(); - foreach (explode(',', strtolower($acceptString)) as $part) { - $parts = preg_split('/\s*;\s*q=/', $part); - if (isset($parts) && isset($parts[1]) && $parts[1]) { - $num = $parts[1] * 10; - } else { - $num = 10; + $accept = $acceptArray = $acceptParam = $acceptParamArray = array(); + $parts = preg_split('/\s*,\s*/', strtolower($acceptString)); + foreach ($parts as $part) { + if (empty($part)) { + continue; + } + $partParams = preg_split('/\s*;\s*/', $part); + $type = array_shift($partParams); + $num = 10; + $acceptParams = array(); + foreach ($partParams as $param) { + $keyValue = preg_split('/\s*=\s*/', $param); + if ($keyValue[0] === 'q') { + if (array_key_exists(1, $keyValue) && is_numeric($keyValue[1]) && (int)$keyValue[1] >= 0 + && (int)$keyValue[1] <= 1) { + $num = $keyValue[1] * 10; + } + } else { + $key = $keyValue[0]; + $value = array_key_exists(1, $keyValue) ? $keyValue[1] : null; + $acceptParams[] = $key . '=' . $value; + } + } + // quality of 0 is not acceptable to the client - rfc2616 sec 3.9 + if ($num === 0) { + $type = null; } - if ($parts[0]) { - $accept[$num][] = $parts[0]; + if ($type) { + $accept[$num][] = $type; + $acceptParam[$num][] = $acceptParams; } } krsort($accept); - foreach ($accept as $parts) { - foreach ($parts as $part) { - $acceptArray[] = trim($part); + krsort($acceptParam); + foreach ($accept as $i => $parts) { + foreach ($parts as $j => $part) { + $acceptArray[] = $part; + $acceptParamArray[] = $acceptParam[$i][$j]; } } diff --git a/src/Tonic/Resource.php b/src/Tonic/Resource.php index 7c4366a..6f7ab72 100644 --- a/src/Tonic/Resource.php +++ b/src/Tonic/Resource.php @@ -167,17 +167,12 @@ public function exec() */ public function options() { - $options = array(); - - $resourceMetadata = $this->app->getResourceMetadata($this); - - foreach ($resourceMetadata->getMethods() as $method => $methodMetadata) { - $options[] = strtoupper($method); - } - - return new Response(200, $options, array( - 'Allow' => implode(',', $options) + $options = implode(',', $this->allowedMethods()); + $response = new Response(200, $options, array( + 'Allow' => $options )); + $response->contentType = 'text/plain'; + return $response; } /** @@ -241,18 +236,48 @@ protected function accepts($mimetype) /** * Provides condition mimetype must be in request accept array, returns a number * based on the priority of the match. - * @param str $mimetype + * @param str $mimetype + * @param str $parameter variable number of key=value parameters to check for in the accept header * @return int */ - protected function provides($mimetype) + protected function provides() { - if (count($this->request->getAccept()) == 0) return 0; - $pos = array_search($mimetype, $this->request->getAccept()); + $params = func_get_args(); + $mimetype = array_shift($params); + + if (count($this->request->getAccept()) == 0) { + return 0; + } + $acceptParams = $this->request->getAcceptParams(); + $pos = false; + foreach ($this->request->getAccept() as $i => $acceptMimetype) { + if ($mimetype === $acceptMimetype) { + foreach ($acceptParams[$i] as $acceptParam) { + if (array_search($acceptParam, $params) === false) { + continue 2; + } + } + $pos = $i; + break; + } + } if ($pos === FALSE) { if (in_array('*/*', $this->request->getAccept())) { return 0; } else { - throw new NotAcceptableException('No matching method for response type "'.join(', ', $this->request->getAccept()).'"'); + $responseTypes = array(); + + foreach ($this->request->getAccept() as $i => $acceptMimetype) { + $responseType = $acceptMimetype; + foreach ($acceptParams[$i] as $acceptParam) { + if (! empty($acceptParam)) { + $responseType .= ';' . $acceptParam; + } + } + $responseTypes[] = $responseType; + } + + throw new NotAcceptableException('No matching method for response type "'.join(', ', $responseTypes).'"'); } } else { $this->after(function ($response) use ($mimetype) { @@ -297,12 +322,20 @@ public function allowedMethods() { $metadata = $this->app->getResourceMetadata($this); $allowedMethods = array(); - foreach ($metadata['methods'] as $method => $properties) { - foreach ($properties['method'] as $method) { - $allowedMethods[] = strtoupper($method[0]); + foreach ($metadata->getMethods() as $method => $methodMetadata) { + foreach ($methodMetadata->getConditions() as $key => $values) { + if ($key !== 'method') { + continue; + } + foreach ($values as $value) { + if (in_array($value, $allowedMethods)) { + continue; + } + $allowedMethods[] = $value; + } } } - return array_values(array_unique($allowedMethods)); + return array_unique($allowedMethods); } public function __toString() diff --git a/src/Tonic/Response.php b/src/Tonic/Response.php index 1506b50..28e2073 100644 --- a/src/Tonic/Response.php +++ b/src/Tonic/Response.php @@ -57,7 +57,7 @@ class Response public $code = self::NOCONTENT, $body = null, - $headers = array('content-type' => 'text/html'); + $headers = array('Content-Type' => 'text/html'); public function __construct($code = null, $body = null, $headers = array()) {