Created
February 3, 2012 18:35
-
-
Save Freeaqingme/1731637 to your computer and use it in GitHub Desktop.
BSD'ed AcceptHandler Plugin by Freeaqingme, Enrise
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Determne the media type and format to serve based on the accept headers | |
* | |
* How this class works: | |
* It first determines which mediatypes that are part of the accept header can be | |
* possibly used based on their formats. In a later stage one can pick | |
* a specific mediatype using the method match(). This method is meant to select | |
* the most specific entry that still matches the requested media type. | |
* | |
* @author Dolf Schimmel - www.enrise.com | |
* @license 3clause BSD | |
* | |
*/ | |
class Project_Controller_Action_Helper_AcceptHandler | |
extends Zend_Controller_Action_Helper_Abstract | |
{ | |
public $debug = false; | |
const RETURN_FLAT = 1; | |
const RETURN_NESTED = 2; | |
const RETURN_AS_ARRAY = 4; | |
const RETURN_AS_OBJECT = 8; | |
protected $_formatsAllowed = array(); | |
protected $_formatDefault; | |
protected $_viableTypes; | |
protected $_priorities; | |
public function setAllowedFormats(array $formats = array()) | |
{ | |
$this->_formatsAllowed = $formats; | |
} | |
public function getAllowedFormats() | |
{ | |
return $this->_formatsAllowed; | |
} | |
/** | |
* Return the possible matching types | |
* @param intger $returnMethod Default RETURN_FLAT | RETURN_AS_OBJECT | |
*/ | |
public function getViableTypes($returnMethod = 5, $headerString = null) | |
{ | |
if (!$this->_viableTypes) { | |
if (!$headerString) { | |
$headerString = $this->getRequest()->getHeader('Accept'); | |
} | |
$this->_viableTypes = $this->_determineViableTypes($headerString); | |
} | |
if ($returnMethod & self::RETURN_FLAT) { | |
return $this->_getFlattenedViableTypeTree($returnMethod); | |
} | |
if ($returnMethod & self::RETURN_AS_ARRAY && $returnMethod & self::RETURN_NESTED) { | |
return $this->_viableTypes; | |
} | |
$out = array(); | |
foreach($this->_viableTypes as $prio => $branch) { | |
foreach($branch as $type) { | |
$out[$prio][] = (object) $type; | |
} | |
} | |
return $out; | |
} | |
public function clearViableTypes() | |
{ | |
$this->_viableTypes = null; | |
return $this; | |
} | |
protected function _determineViableTypes($headerString) | |
{ | |
if (!$headerString) { | |
$this->_viableTypes = array(); | |
return; | |
} | |
$mediaTypes = explode(',', $headerString); | |
$out = array(); | |
foreach($mediaTypes as $mediaTypeString) { | |
$mediaType = $this->mediaTypeToArray($mediaTypeString); | |
foreach($this->getAllowedFormats() as $typeAllowed => $subTypeAllowed) { | |
if (($mediaType['format'] == $subTypeAllowed && | |
is_numeric($typeAllowed)) | |
|| | |
(!is_numeric($typeAllowed) && | |
$mediaType['format'] == $subTypeAllowed && | |
$mediaType['type'] == $typeAllowed) | |
|| | |
($mediaType['type'] == (string)$typeAllowed && $mediaType['format'] == '*') | |
|| | |
($mediaType['type'] == '*' && $mediaType['format'] == '*') | |
) { | |
$out[$mediaType['priority']][] = $mediaType; | |
break; | |
} | |
} | |
} | |
foreach($out as $prio => $value) { | |
$out[$prio] = $this->_sortTypeBranch($value); | |
} | |
krsort($out); | |
return $out; | |
} | |
protected function _sortTypeBranch(array $branch) { | |
$sort = function($a, $b) // If A has higher prio than B, return -1. | |
{ | |
// Asterisks | |
$values = array('type', 'subtype','format'); | |
foreach($values as $value) { | |
if($a[$value] == '*' && $b[$value] == '*') { | |
return 0; | |
} elseif($a[$value] == '*') { | |
return 1; | |
} elseif($b[$value] == '*') { | |
return -1; | |
} | |
} | |
if($a['type'] == 'application' && $b['type'] != 'application') { | |
return -1; | |
} elseif($b['type'] == 'application' && $a['type'] != 'application') { | |
return 1; | |
} | |
//@todo count number of dots in case of type==application in subtype | |
// So far they're still the same. Longest stringlength may be more specific | |
if(strlen($a['raw']) == strlen($b['raw'])) return 0; | |
return (strlen($a['raw']) > strlen($b['raw'])) ? -1 : 1; | |
}; | |
usort($branch, $sort); | |
return $branch; | |
} | |
public function match($match, $setHeaders = true, $headerString = null) | |
{ | |
if (! ($result = $this->_match($match, $headerString)) ) { | |
return false; | |
} | |
if($setHeaders) { | |
if ($result['format'] == $result['subtypeRaw']) { | |
$contentType = $result['type'] . '/' | |
. $result['subtype']; | |
} else { | |
$contentType = $result['type'] . '/' | |
. $result['subtype'] . '+' | |
. $result['format']; | |
} | |
$response = $this->getResponse(); | |
$response->setHeader('Vary', 'Accept') | |
->setHeader('Content-Type', $contentType) | |
->setOutputFormat($result['format']); | |
$this->getRequest()->setParam('format', $result['format']); | |
} | |
return $result; | |
} | |
protected function _match($collectionMatch, $headerString) | |
{ | |
$viableTypes = $this->getViableTypes(self::RETURN_NESTED|self::RETURN_AS_ARRAY, $headerString); | |
$matches = array(); | |
foreach ((array)$collectionMatch as $match) { | |
if (is_array($match)) { | |
$matches[] = & $match; | |
} else { | |
$matches[] = $this->mediaTypeToArray($match); | |
} | |
} | |
foreach($viableTypes as $prio => $typeGroup) | |
{ | |
foreach($typeGroup as $type) { | |
foreach($matches as $match) { | |
if($type['type'] == '*') { | |
if ($match['format'] == '*' && $match['format'] == '*') { | |
$allowedFormats = $this->getAllowedFormats(); | |
if(array_key_exists($match['type'], $allowedFormats)) { | |
$match['format'] = $allowedFormats[$match['type']]; | |
} else { | |
$match['format'] = $this->getDefaultFormat(true); | |
} | |
} | |
if ($res = $this->_matchParams($match, $type)) { | |
return $res; | |
} | |
} | |
if ($match['type'] == $type['type']) | |
{ | |
if ((($match['subtype'] == $type['subtype'] || | |
$match['subtype'] == '*') && | |
($match['format'] == $type['format'] || | |
$match['format'] == '*'))) | |
{ | |
if ($res = $this->_matchParams($type, $match)) { | |
return $res; | |
} | |
} | |
} | |
} | |
} | |
} | |
return false; | |
} | |
protected function _matchParams(array $match1, array $match2) | |
{ | |
foreach($match2['params'] as $key => $value) { | |
if (isset($match1['params'][$key])) { | |
if (strpos($value, '-')) { | |
$values = explode('-', $value, 2); | |
if($values[0] > $match1['params'][$key] || | |
$values[1] < $match1['params'][$key]) | |
{ | |
return false; | |
} | |
} elseif (strpos($value, '|')) { | |
$options = explode('|', $value); | |
$good = false; | |
foreach($options as $option) { | |
if($option == $match1['params'][$key]) { | |
$good = true; | |
break; | |
} | |
} | |
if (!$good) { | |
return false; | |
} | |
} elseif($match1['params'][$key] != $value) { | |
return false; | |
} | |
} | |
} | |
return $match1; | |
} | |
public function mediaTypeToArray($mediaType) | |
{ | |
$raw = $mediaType; | |
if ($pos = strpos($mediaType, '/')) { | |
$type = trim(substr($mediaType, 0, $pos)); | |
} else { | |
$type = trim(substr($mediaType, 0)); | |
} | |
$params = array(); | |
if(($pos = strpos($mediaType,';'))) { | |
$paramsStrings = explode(';', substr($mediaType, $pos+1)); | |
foreach($paramsStrings as $value) { // fetch q=0.2 to array | |
$explode = explode('=', $value, 2); | |
$params[trim($explode[0])] = trim($explode[1]); | |
} | |
} | |
if ($pos = strpos($mediaType, ';')) { | |
$mediaType = trim(substr($mediaType, 0, $pos)); | |
} | |
if ($pos = strpos($mediaType, '/')) { | |
$subtypeWhole = $format = $subtype = trim(substr($mediaType, strpos($mediaType, '/')+1)); | |
} else { | |
$subtypeWhole = ''; | |
$format = '*'; | |
$subtype = '*'; | |
} | |
$pos = strpos($subtype, '+'); | |
if (false !== $pos) { | |
$format = trim(substr($subtype, $pos+1)); | |
$subtype = trim(substr($subtype, 0, $pos)); | |
} | |
return array( | |
'typeString' => trim($mediaType), | |
'type' => $type, | |
'subtype' => $subtype, | |
'subtypeRaw' => $subtypeWhole, | |
'format' => $format, | |
'priority' => isset($params['q']) ? $params['q'] : 1, | |
'params' => $params, | |
'raw' => trim($raw) | |
); | |
} | |
protected function _getFlattenedViableTypeTree($returnMethod) | |
{ | |
if($returnMethod & self::RETURN_AS_ARRAY) { | |
$tree = $this->getViableTypes(self::RETURN_NESTED|self::RETURN_AS_ARRAY); | |
} else { | |
$tree = $this->getViableTypes(self::RETURN_NESTED|self::RETURN_AS_OBJECT); | |
} | |
$flat = array(); | |
foreach ($tree as $branch) { | |
$flat = array_merge($flat, $branch); | |
} | |
return $flat; | |
} | |
public function getDefaultFormat($feelingLucky = false) { | |
if (!$feelingLucky) { | |
return $this->_formatDefault; | |
} | |
if (null == $this->_formatDefault && $formats = $this->getAllowedFormats()) { | |
return $formats[0]; | |
} | |
return $this->_formatDefault; | |
} | |
public function setDefaultFormat($format) | |
{ | |
if (!in_array($format, $this->_formatDefault)) { | |
throw new exception( | |
'A default format was tried to set that is not an allowed format' | |
); | |
} | |
$this->_formatDefault = (string) $format; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment