Completed
Pull Request — master (#20)
by
unknown
13:54
created

functions.php ➔ parseContentTypeHeader()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 36
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 5
nop 1
dl 0
loc 36
ccs 0
cts 0
cp 0
crap 42
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
namespace League\JsonReference;
4
5
use Sabre\Uri;
6
7
/**
8
 * @param object|array $json
9
 *
10
 * @return Pointer
11
 */
12
function pointer(&$json)
13
{
14 24
    return new Pointer($json);
15
}
16
17
/**
18
 * Escape a JSON Pointer.
19
 *
20
 * @param  string $pointer
21
 * @return string
22
 */
23
function escape_pointer($pointer)
24
{
25
    return str_replace(['~', '/'], ['~0', '~1'], $pointer);
26
}
27
28
29
/**
30
 * Push a segment onto the given JSON Pointer.
31
 *
32
 * @param string   $pointer
33
 * @param string[] $segments
34
 *
35
 * @return string
36
 *
37
 */
38
function pointer_push($pointer, ...$segments)
39
{
40 50
    $segments = str_replace(['~', '/'], ['~0', '~1'], $segments);
41 50
    return ($pointer !== '/' ? $pointer : '') . '/' . implode('/', $segments);
42
}
43
44
/**
45
 * Removes the fragment from a reference.
46
 *
47
 * @param  string $ref
48
 * @return string
49
 */
50
function strip_fragment($ref)
51
{
52 46
    $fragment = Uri\parse($ref)['fragment'];
53
54 46
    return $fragment ? str_replace('#'.$fragment, '#', $ref) : $ref;
55
}
56
57
/**
58
 * Check if the reference contains a fragment and resolve
59
 * the pointer.  Otherwise returns the original schema.
60
 *
61
 * @param  string $ref
62
 * @param  object $schema
63
 *
64
 * @return object
65
 */
66
function resolve_fragment($ref, $schema)
67
{
68 44
    $fragment = Uri\parse($ref)['fragment'];
69
70 44
    if (!is_internal_ref($ref) && is_string($fragment)) {
71 6
        return (new Pointer($schema))->get($fragment);
72
    }
73
74 40
    return $schema;
75
}
76
77
/**
78
 * @param string $keyword
79
 * @param mixed  $value
80
 *
81
 * @return bool
82
 */
83
function is_ref($keyword, $value)
84
{
85 50
    return $keyword === '$ref' && is_string($value);
86
}
87
88
/**
89
 * Determine if a reference is relative.
90
 * A reference is relative if it does not being with a prefix.
91
 *
92
 * @param string $ref
93
 *
94
 * @return bool
95
 */
96
function is_relative_ref($ref)
97
{
98 48
    return !preg_match('#^.+\:\/\/.*#', $ref);
99
}
100
101
/**
102
 * @param string $value
103
 *
104
 * @return bool
105
 */
106
function is_internal_ref($value)
107
{
108 62
    return is_string($value) && substr($value, 0, 1) === '#';
109
}
110
111
/**
112
 * Parse an external reference returning the prefix and path.
113
 *
114
 * @param string $ref
115
 *
116
 * @return array
117
 *
118
 * @throws \InvalidArgumentException
119
 */
120
function parse_external_ref($ref)
121
{
122 48
    if (is_relative_ref($ref)) {
123 2
        throw new \InvalidArgumentException(
124 2
            sprintf(
125
                'The path  "%s" was expected to be an external reference but is missing a prefix.  ' .
126 2
                'The schema path should start with a prefix i.e. "file://".',
127 1
                $ref
128 1
            )
129 1
        );
130
    }
131
132 46
    list($prefix, $path) = explode('://', $ref, 2);
133 46
    $path = rtrim(strip_fragment($path), '#');
134
135 46
    return [$prefix, $path];
136
}
137
138
/**
139
 * Resolve the given id against the parent scope and return the resolved URI.
140
 *
141
 * @param string $id          The id to resolve.  This should be a valid relative or absolute URI.
142
 * @param string $parentScope The parent scope to resolve against.  Should be a valid URI or empty.
143
 *
144
 * @return string
145
 */
146
function resolve_uri($id, $parentScope)
147
{
148
    // If there is no parent scope, there is nothing to resolve against.
149 38
    if ($parentScope === '') {
150 10
        return $id;
151
    }
152
153 30
    return Uri\resolve($parentScope, $id);
154
}
155
156
/**
157
 * Recursively iterates over each value in the schema passing them to the callback function.
158
 * If the callback function returns true the value is returned into the result array, keyed by a JSON Pointer.
159
 *
160
 * @param mixed    $schema
161
 * @param callable $callback
162
 * @param string   $pointer
163
 *
164
 * @return array
165
 */
166
function schema_extract($schema, callable $callback, $pointer = '')
167
{
168 52
    $matches = [];
169
170 52
    if ($schema instanceof Reference || (!is_array($schema) && !is_object($schema))) {
171 18
        return $matches;
172
    }
173
174 52
    foreach ($schema as $keyword => $value) {
175 26
        switch (true) {
176 52
            case is_object($value):
177 46
                $matches = array_merge($matches, schema_extract($value, $callback, pointer_push($pointer, $keyword)));
178 46
                break;
179 52
            case is_array($value):
180 22
                foreach ($value as $k => $v) {
181 22
                    if ($callback($k, $v)) {
182
                        $matches[pointer_push($pointer, $keyword)] = $v;
183
                    } else {
184 22
                        $matches = array_merge(
185 22
                            $matches,
186 22
                            schema_extract($v, $callback, pointer_push($pointer, $keyword, $k))
187 11
                        );
188
                    }
189 11
                }
190 22
                break;
191 52
            case $callback($keyword, $value):
192 50
                $matches[$pointer] = $value;
193 51
                break;
194
        }
195 26
    }
196
197 52
    return $matches;
198
}
199
200
/**
201
 * @param object $schema
202
 * @param object $resolvedRef
203
 * @param string $path
204
 *
205
 * @return object
206
 */
207
function merge_ref($schema, $resolvedRef, $path = '')
208
{
209 48
    if ($path === '') {
210 6
        pointer($schema)->remove('$ref');
211 6
        foreach ($resolvedRef as $prop => $value) {
212 4
            pointer($schema)->set($prop, $value);
213 2
        }
214 4
        return $schema;
215
    }
216
217 42
    $pointer = new Pointer($schema);
218 42
    if ($pointer->has($path)) {
219 42
        $pointer->set($path, $resolvedRef);
220 21
    }
221 42
    return $schema;
222
}
223
224
/**
225
 * Parses Content-Type header and returns an array with type, subtype, suffix and parameters
226
 *
227
 * @param string $contentType
228
 *
229
 * @return array
230
 */
231
function parseContentTypeHeader($contentType)
232
{
233
    if (!preg_match('%
234
        ^
235
            (?<type>[^\/;+]+)
236
            (
237
                \/
238
                (?<subtype>[^;+]*)
239
                (?<has_suffix>\+(?<suffix>[^;]*))?
240
                ;?
241
                (?<parameter>.*)?
242
            )
243
        $
244
        %x', $contentType, $matches)) {
245
        return null;
246
    }
247
248
    $result = [
249
        'type' => strtolower($matches['type']),
250
        'subtype' => strtolower($matches['subtype']),
251
        'suffix' => $matches['has_suffix'] ? strtolower($matches['suffix']) : null,
252
        'parameter' => null
253
    ];
254
255
    if ($matches['parameter']) {
256
        $result['parameter'] = [];
257
        
258
        preg_match_all('/\s*([^=;]+)\s*(=([^;]*))?;?/', $matches['parameter'], $parameters, PREG_SET_ORDER);
259
260
        foreach ($parameters as $parameter) {
0 ignored issues
show
Bug introduced by
The expression $parameters of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
261
            $result['parameter'][strtolower($parameter[1])] = isset($parameter[3]) ? $parameter[3] : '';
262
        }
263
    }
264
265
    return $result;
266
}
267
268
/**
269
 * Determine the file type based on the given context (http-headers, uri)
270
 *
271
 * @param mixed $context
272
 *
273
 * @return string
274
 */
275
function determineMediaType($context)
276
{
277
    if (isset($context['headers']) && $context['headers']) {
278
        $headers = $context['headers'];
279
280
        if (isset($headers['Content-Type'])) {
281
            $contentType = parseContentTypeHeader($headers['Content-Type']);
282
283
            if (isset($contentType['suffix'])) {
284
                return '+'.$contentType['suffix'];
285
            } else {
286
                return $contentType['type'].'/'.$contentType['subtype'];
287
            }
288
        }
289
    }
290
291
    if (isset($context['uri']) && $context['uri']) {
292
        $path = Uri\parse($context['uri'])['path'];
293
        $info = pathinfo($path);
294
295
        if (isset($info['extension'])) {
296
            return $info['extension'];
297
        }
298
    }
299
300
    return null;
301
}
302