1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Psr7Middlewares\Middleware; |
4
|
|
|
|
5
|
|
|
use Psr7Middlewares\Utils; |
6
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
7
|
|
|
use Psr\Http\Message\ResponseInterface; |
8
|
|
|
use Negotiation\Negotiator; |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Middleware returns the client preferred format. |
12
|
|
|
*/ |
13
|
|
|
class FormatNegotiator |
14
|
|
|
{ |
15
|
|
|
use Utils\NegotiateTrait; |
16
|
|
|
use Utils\AttributeTrait; |
17
|
|
|
|
18
|
|
|
const KEY = 'FORMAT'; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* @var string Default format |
22
|
|
|
*/ |
23
|
|
|
private $default = 'html'; |
24
|
|
|
|
25
|
|
|
/** |
26
|
|
|
* @var string[] Formats which the server supports |
27
|
|
|
*/ |
28
|
|
|
private $priorities = array(); |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* @var array Available formats with the mime types |
32
|
|
|
*/ |
33
|
|
|
private $formats = [ |
34
|
|
|
//text |
35
|
|
|
'html' => [['html', 'htm', 'php'], ['text/html', 'application/xhtml+xml']], |
36
|
|
|
'txt' => [['txt'], ['text/plain']], |
37
|
|
|
'css' => [['css'], ['text/css']], |
38
|
|
|
'json' => [['json'], ['application/json', 'text/json', 'application/x-json']], |
39
|
|
|
'jsonp' => [['jsonp'], ['text/javascript', 'application/javascript', 'application/x-javascript']], |
40
|
|
|
'js' => [['js'], ['text/javascript', 'application/javascript', 'application/x-javascript']], |
41
|
|
|
|
42
|
|
|
//xml |
43
|
|
|
'rdf' => [['rdf'], ['application/rdf+xml']], |
44
|
|
|
'rss' => [['rss'], ['application/rss+xml']], |
45
|
|
|
'atom' => [['atom'], ['application/atom+xml']], |
46
|
|
|
'xml' => [['xml'], ['text/xml', 'application/xml', 'application/x-xml']], |
47
|
|
|
|
48
|
|
|
//images |
49
|
|
|
'bmp' => [['bmp'], ['image/bmp']], |
50
|
|
|
'gif' => [['gif'], ['image/gif']], |
51
|
|
|
'png' => [['png'], ['image/png', 'image/x-png']], |
52
|
|
|
'jpg' => [['jpg', 'jpeg', 'jpe'], ['image/jpeg', 'image/jpg']], |
53
|
|
|
'svg' => [['svg', 'svgz'], ['image/svg+xml']], |
54
|
|
|
'psd' => [['psd'], ['image/vnd.adobe.photoshop']], |
55
|
|
|
'eps' => [['ai', 'eps', 'ps'], ['application/postscript']], |
56
|
|
|
'ico' => [['ico'], ['image/x-icon', 'image/vnd.microsoft.icon']], |
57
|
|
|
|
58
|
|
|
//audio/video |
59
|
|
|
'mov' => [['mov', 'qt'], ['video/quicktime']], |
60
|
|
|
'mp3' => [['mp3'], ['audio/mpeg']], |
61
|
|
|
'mp4' => [['mp4'], ['video/mp4']], |
62
|
|
|
'ogg' => [['ogg'], ['audio/ogg']], |
63
|
|
|
'ogv' => [['ogv'], ['video/ogg']], |
64
|
|
|
'webm' => [['webm'], ['video/webm']], |
65
|
|
|
'webp' => [['webp'], ['image/webp']], |
66
|
|
|
|
67
|
|
|
//fonts |
68
|
|
|
'eot' => [['eot'], ['application/vnd.ms-fontobject']], |
69
|
|
|
'otf' => [['otf'], ['font/opentype', 'application/x-font-opentype']], |
70
|
|
|
'ttf' => [['ttf'], ['font/ttf', 'application/font-ttf', 'application/x-font-ttf']], |
71
|
|
|
'woff' => [['woff'], ['font/woff', 'application/font-woff', 'application/x-font-woff']], |
72
|
|
|
'woff2' => [['woff2'], ['font/woff2', 'application/font-woff2', 'application/x-font-woff2']], |
73
|
|
|
|
74
|
|
|
//other formats |
75
|
|
|
'pdf' => [['pdf'], ['application/pdf', 'application/x-download']], |
76
|
|
|
'zip' => [['zip'], ['application/zip', 'application/x-zip', 'application/x-zip-compressed']], |
77
|
|
|
'rar' => [['rar'], ['application/rar', 'application/x-rar', 'application/x-rar-compressed']], |
78
|
|
|
'exe' => [['exe'], ['application/x-msdownload']], |
79
|
|
|
'msi' => [['msi'], ['application/x-msdownload']], |
80
|
|
|
'cab' => [['cab'], ['application/vnd.ms-cab-compressed']], |
81
|
|
|
'doc' => [['doc'], ['application/msword']], |
82
|
|
|
'rtf' => [['rtf'], ['application/rtf']], |
83
|
|
|
'xls' => [['xls'], ['application/vnd.ms-excel']], |
84
|
|
|
'ppt' => [['ppt'], ['application/vnd.ms-powerpoint']], |
85
|
|
|
'odt' => [['odt'], ['application/vnd.oasis.opendocument.text']], |
86
|
|
|
'ods' => [['ods'], ['application/vnd.oasis.opendocument.spreadsheet']], |
87
|
|
|
]; |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Returns the format. |
91
|
|
|
* |
92
|
|
|
* @param ServerRequestInterface $request |
93
|
|
|
* |
94
|
|
|
* @return string|null |
95
|
|
|
*/ |
96
|
|
|
public static function getFormat(ServerRequestInterface $request) |
97
|
|
|
{ |
98
|
|
|
return self::getAttribute($request, self::KEY); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Add a new format. |
103
|
|
|
* |
104
|
|
|
* @param string $format |
105
|
|
|
* @param array $mimeTypes |
106
|
|
|
* @param array|null $extensions |
107
|
|
|
* |
108
|
|
|
* @return self |
109
|
|
|
*/ |
110
|
|
|
public function addFormat($format, array $mimeTypes, array $extensions = null) |
111
|
|
|
{ |
112
|
|
|
$this->formats[$format] = [is_null($extensions) ? [$format] : $extensions, $mimeTypes]; |
113
|
|
|
|
114
|
|
|
return $this; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Set the default format. |
119
|
|
|
* |
120
|
|
|
* @param string $format |
121
|
|
|
* |
122
|
|
|
* @return self |
123
|
|
|
*/ |
124
|
|
|
public function defaultFormat($format) |
125
|
|
|
{ |
126
|
|
|
$this->default = $format; |
127
|
|
|
|
128
|
|
|
return $this; |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* @param string[]|null $priorities Formats which the server supports, in priority order. |
133
|
|
|
*/ |
134
|
|
|
public function __construct($priorities = null) |
135
|
|
|
{ |
136
|
|
|
$this->priorities = $priorities; |
|
|
|
|
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* Execute the middleware. |
141
|
|
|
* |
142
|
|
|
* @param ServerRequestInterface $request |
143
|
|
|
* @param ResponseInterface $response |
144
|
|
|
* @param callable $next |
145
|
|
|
* |
146
|
|
|
* @return ResponseInterface |
147
|
|
|
*/ |
148
|
|
|
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) |
149
|
|
|
{ |
150
|
|
|
$format = $this->getFromExtension($request) ?: $this->getFromHeader($request) ?: $this->default; |
151
|
|
|
$contentType = $this->formats[$format][1][0].'; charset=utf-8'; |
152
|
|
|
|
153
|
|
|
$response = $next( |
154
|
|
|
self::setAttribute($request, self::KEY, $format), |
155
|
|
|
$response->withHeader('Content-Type', $contentType) |
156
|
|
|
); |
157
|
|
|
|
158
|
|
|
if (!$response->hasHeader('Content-Type')) { |
159
|
|
|
$response = $response->withHeader('Content-Type', $contentType); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
return $response; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Returns the format using the file extension. |
167
|
|
|
* |
168
|
|
|
* @return null|string |
169
|
|
|
*/ |
170
|
|
|
private function getFromExtension(ServerRequestInterface $request) |
171
|
|
|
{ |
172
|
|
|
$extension = strtolower(pathinfo($request->getUri()->getPath(), PATHINFO_EXTENSION)); |
173
|
|
|
|
174
|
|
|
if (empty($extension)) { |
175
|
|
|
return; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
View Code Duplication |
foreach ($this->formats as $format => $data) { |
|
|
|
|
179
|
|
|
if (in_array($extension, $data[0], true)) { |
180
|
|
|
return $format; |
181
|
|
|
} |
182
|
|
|
} |
183
|
|
|
} |
184
|
|
|
|
185
|
|
|
/** |
186
|
|
|
* Returns the format using the Accept header. |
187
|
|
|
* |
188
|
|
|
* @return null|string |
189
|
|
|
*/ |
190
|
|
|
private function getFromHeader(ServerRequestInterface $request) |
191
|
|
|
{ |
192
|
|
|
$accept = $request->getHeaderLine('Accept'); |
193
|
|
|
|
194
|
|
|
//If the client accepts everything, then return null and allow the default to be used. |
195
|
|
|
if (empty($accept) || $accept === "*" || $accept === "*/*") { |
196
|
|
|
return null; |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
if (empty($this->priorities)) { |
200
|
|
|
$formats = $this->formats; |
201
|
|
|
} else { |
202
|
|
|
//Filter the list of formats. |
203
|
|
|
$formats = []; |
204
|
|
|
foreach ($this->priorities as $priority) { |
205
|
|
|
if (isset($this->formats[$priority])) { |
206
|
|
|
$formats[$priority] = $this->formats[$priority]; |
207
|
|
|
} |
208
|
|
|
} |
209
|
|
|
} |
210
|
|
|
$headers = call_user_func_array('array_merge', array_column($formats, 1)); |
211
|
|
|
$mime = $this->negotiateHeader($accept, new Negotiator(), $headers); |
212
|
|
|
|
213
|
|
|
if ($mime !== null) { |
214
|
|
View Code Duplication |
foreach ($this->formats as $format => $data) { |
|
|
|
|
215
|
|
|
if (in_array($mime, $data[1], true)) { |
216
|
|
|
return $format; |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|
Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.
To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.
The function can be called with either null or an array for the parameter
$needle
but will only accept an array as$haystack
.