Completed
Push — master ( 165ed7...f173bc )
by Oscar
12s
created

FormatNegotiator::__invoke()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 8.7624
c 0
b 0
f 0
cc 5
eloc 12
nc 12
nop 3
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;
24
25
    /**
26
     * @var array Available formats with the mime types
27
     */
28
    private $formats = [
29
        //text
30
        'html' => [['html', 'htm', 'php'], ['text/html', 'application/xhtml+xml']],
31
        'txt' => [['txt'], ['text/plain']],
32
        'css' => [['css'], ['text/css']],
33
        'json' => [['json'], ['application/json', 'text/json', 'application/x-json']],
34
        'jsonp' => [['jsonp'], ['text/javascript', 'application/javascript', 'application/x-javascript']],
35
        'js' => [['js'], ['text/javascript', 'application/javascript', 'application/x-javascript']],
36
37
        //xml
38
        'rdf' => [['rdf'], ['application/rdf+xml']],
39
        'rss' => [['rss'], ['application/rss+xml']],
40
        'atom' => [['atom'], ['application/atom+xml']],
41
        'xml' => [['xml'], ['text/xml', 'application/xml', 'application/x-xml']],
42
43
        //images
44
        'bmp' => [['bmp'], ['image/bmp']],
45
        'gif' => [['gif'], ['image/gif']],
46
        'png' => [['png'], ['image/png', 'image/x-png']],
47
        'jpg' => [['jpg', 'jpeg', 'jpe'], ['image/jpeg', 'image/jpg']],
48
        'svg' => [['svg', 'svgz'], ['image/svg+xml']],
49
        'psd' => [['psd'], ['image/vnd.adobe.photoshop']],
50
        'eps' => [['ai', 'eps', 'ps'], ['application/postscript']],
51
        'ico' => [['ico'], ['image/x-icon', 'image/vnd.microsoft.icon']],
52
53
        //audio/video
54
        'mov' => [['mov', 'qt'], ['video/quicktime']],
55
        'mp3' => [['mp3'], ['audio/mpeg']],
56
        'mp4' => [['mp4'], ['video/mp4']],
57
        'ogg' => [['ogg'], ['audio/ogg']],
58
        'ogv' => [['ogv'], ['video/ogg']],
59
        'webm' => [['webm'], ['video/webm']],
60
        'webp' => [['webp'], ['image/webp']],
61
62
        //fonts
63
        'eot' => [['eot'], ['application/vnd.ms-fontobject']],
64
        'otf' => [['otf'], ['font/opentype', 'application/x-font-opentype']],
65
        'ttf' => [['ttf'], ['font/ttf', 'application/font-ttf', 'application/x-font-ttf']],
66
        'woff' => [['woff'], ['font/woff', 'application/font-woff', 'application/x-font-woff']],
67
        'woff2' => [['woff2'], ['font/woff2', 'application/font-woff2', 'application/x-font-woff2']],
68
69
        //other formats
70
        'pdf' => [['pdf'], ['application/pdf', 'application/x-download']],
71
        'zip' => [['zip'], ['application/zip', 'application/x-zip', 'application/x-zip-compressed']],
72
        'rar' => [['rar'], ['application/rar', 'application/x-rar', 'application/x-rar-compressed']],
73
        'exe' => [['exe'], ['application/x-msdownload']],
74
        'msi' => [['msi'], ['application/x-msdownload']],
75
        'cab' => [['cab'], ['application/vnd.ms-cab-compressed']],
76
        'doc' => [['doc'], ['application/msword']],
77
        'rtf' => [['rtf'], ['application/rtf']],
78
        'xls' => [['xls'], ['application/vnd.ms-excel']],
79
        'ppt' => [['ppt'], ['application/vnd.ms-powerpoint']],
80
        'odt' => [['odt'], ['application/vnd.oasis.opendocument.text']],
81
        'ods' => [['ods'], ['application/vnd.oasis.opendocument.spreadsheet']],
82
    ];
83
84
    /**
85
     * Returns the format.
86
     *
87
     * @param ServerRequestInterface $request
88
     *
89
     * @return string|null
90
     */
91
    public static function getFormat(ServerRequestInterface $request)
92
    {
93
        return self::getAttribute($request, self::KEY);
94
    }
95
96
    /**
97
     * Add a new format.
98
     *
99
     * @param string     $format
100
     * @param array      $mimeTypes
101
     * @param array|null $extensions
102
     *
103
     * @return self
104
     */
105
    public function addFormat($format, array $mimeTypes, array $extensions = null)
106
    {
107
        $this->formats[$format] = [is_null($extensions) ? [$format] : $extensions, $mimeTypes];
108
109
        return $this;
110
    }
111
112
    /**
113
     * Set the default format.
114
     * Set this to null if you want a 406 Not Acceptable response to be generated if no valid format was found.
115
     *
116
     * @param string|null $format
117
     *
118
     * @return self
119
     */
120
    public function defaultFormat($format)
121
    {
122
        $this->default = $format;
123
124
        if (isset($this->formats[$format])) {
125
            $item = $this->formats[$format];
126
            $this->formats = [$format => $item] + $this->formats;
127
        }
128
129
        return $this;
130
    }
131
132
    /**
133
     * @param array|null $formats Formats which the server supports, in priority order.
134
     */
135
    public function __construct($formats = null)
136
    {
137
        if (!empty($formats)) {
138
            $this->formats = $formats;
139
        }
140
        $this->default = key($this->formats);
141
    }
142
143
    /**
144
     * Execute the middleware.
145
     *
146
     * @param ServerRequestInterface $request
147
     * @param ResponseInterface      $response
148
     * @param callable               $next
149
     *
150
     * @return ResponseInterface
151
     */
152
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
153
    {
154
        $format = $this->getFromExtension($request) ?: $this->getFromHeader($request) ?: $this->default;
155
        if (empty($format)) {
156
            //If no valid accept type was found, and no default was specified, then return 406 Not Acceptable.
157
            $response = $response->withStatus(406);
158
        } else {
159
            $contentType = $this->formats[$format][1][0].'; charset=utf-8';
160
161
            $response = $next(
162
                self::setAttribute($request, self::KEY, $format),
163
                $response->withHeader('Content-Type', $contentType)
164
            );
165
166
            if (!$response->hasHeader('Content-Type')) {
167
                $response = $response->withHeader('Content-Type', $contentType);
168
            }
169
        }
170
171
        return $response;
172
    }
173
174
    /**
175
     * Returns the format using the file extension.
176
     *
177
     * @return null|string
178
     */
179
    private function getFromExtension(ServerRequestInterface $request)
180
    {
181
        $extension = strtolower(pathinfo($request->getUri()->getPath(), PATHINFO_EXTENSION));
182
183
        if (empty($extension)) {
184
            return;
185
        }
186
187 View Code Duplication
        foreach ($this->formats as $format => $data) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
188
            if (in_array($extension, $data[0], true)) {
189
                return $format;
190
            }
191
        }
192
    }
193
194
    /**
195
     * Returns the format using the Accept header.
196
     *
197
     * @return null|string
198
     */
199
    private function getFromHeader(ServerRequestInterface $request)
200
    {
201
        $headers = call_user_func_array('array_merge', array_column($this->formats, 1));
202
        $mime = $this->negotiateHeader($request->getHeaderLine('Accept'), new Negotiator(), $headers);
203
204
        if ($mime !== null) {
205 View Code Duplication
            foreach ($this->formats as $format => $data) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
206
                if (in_array($mime, $data[1], true)) {
207
                    return $format;
208
                }
209
            }
210
        }
211
    }
212
}
213