Completed
Push — master ( 4ee0fb...e0645e )
by Oscar
02:50
created

FormatNegotiator::__invoke()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 2
Metric Value
c 5
b 1
f 2
dl 0
loc 16
rs 9.2
cc 4
eloc 9
nc 8
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 = 'html';
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
     *
115
     * @param string $format
116
     *
117
     * @return self
118
     */
119
    public function defaultFormat($format)
120
    {
121
        $this->default = $format;
122
123
        return $this;
124
    }
125
126
    /**
127
     * Execute the middleware.
128
     *
129
     * @param ServerRequestInterface $request
130
     * @param ResponseInterface      $response
131
     * @param callable               $next
132
     *
133
     * @return ResponseInterface
134
     */
135
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
136
    {
137
        $format = $this->getFromExtension($request) ?: $this->getFromHeader($request) ?: $this->default;
138
        $contentType = $this->formats[$format][1][0].'; charset=utf-8';
139
140
        $response = $next(
141
            self::setAttribute($request, self::KEY, $format),
142
            $response->withHeader('Content-Type', $contentType)
143
        );
144
145
        if (!$response->hasHeader('Content-Type')) {
146
            $response = $response->withHeader('Content-Type', $contentType);
147
        }
148
149
        return $response;
150
    }
151
152
    /**
153
     * Returns the format using the file extension.
154
     *
155
     * @return null|string
156
     */
157
    private function getFromExtension(ServerRequestInterface $request)
158
    {
159
        $extension = strtolower(pathinfo($request->getUri()->getPath(), PATHINFO_EXTENSION));
160
161
        if (empty($extension)) {
162
            return;
163
        }
164
165 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...
166
            if (in_array($extension, $data[0], true)) {
167
                return $format;
168
            }
169
        }
170
    }
171
172
    /**
173
     * Returns the format using the Accept header.
174
     *
175
     * @return null|string
176
     */
177
    private function getFromHeader(ServerRequestInterface $request)
178
    {
179
        $headers = call_user_func_array('array_merge', array_column($this->formats, 1));
180
        $mime = $this->negotiateHeader($request->getHeaderLine('Accept'), new Negotiator(), $headers);
181
182
        if ($mime !== null) {
183 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...
184
                if (in_array($mime, $data[1], true)) {
185
                    return $format;
186
                }
187
            }
188
        }
189
    }
190
}
191