Passed
Branch master (cbed4f)
by Enjoys
28:49
created

UrlConverter::relativeToAbsolute()   D

Complexity

Conditions 19
Paths 62

Size

Total Lines 65
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 19
eloc 38
c 1
b 0
f 0
nc 62
nop 2
dl 0
loc 65
rs 4.5166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
4
namespace Enjoys\AssetsCollector\Content;
5
6
7
class UrlConverter
8
{
9
10
    public function relativeToAbsolute(string $baseUrl, string $relativeUrl)
11
    {
12
        // If relative URL has a scheme, clean path and return.
13
        $r = $this->split_url($relativeUrl);
14
        if ($r === false) {
0 ignored issues
show
introduced by
The condition $r === false is always true.
Loading history...
15
            return false;
16
        }
17
        if (!empty($r['scheme'])) {
18
            if (!empty($r['path']) && $r['path'][0] == '/') {
19
                $r['path'] = $this->url_remove_dot_segments($r['path']);
20
            }
21
            return $this->join_url($r);
22
        }
23
24
        // Make sure the base URL is absolute.
25
        $b = $this->split_url($baseUrl);
26
        if ($b === false || empty($b['scheme']) || empty($b['host'])) {
27
            return false;
28
        }
29
        $r['scheme'] = $b['scheme'];
30
31
        // If relative URL has an authority, clean path and return.
32
        if (isset($r['host'])) {
33
            if (!empty($r['path'])) {
34
                $r['path'] = $this->url_remove_dot_segments($r['path']);
35
            }
36
            return $this->join_url($r);
37
        }
38
        unset($r['port']);
39
        unset($r['user']);
40
        unset($r['pass']);
41
42
        // Copy base authority.
43
        $r['host'] = $b['host'];
44
        if (isset($b['port'])) {
45
            $r['port'] = $b['port'];
46
        }
47
        if (isset($b['user'])) {
48
            $r['user'] = $b['user'];
49
        }
50
        if (isset($b['pass'])) {
51
            $r['pass'] = $b['pass'];
52
        }
53
54
        // If relative URL has no path, use base path
55
        if (empty($r['path'])) {
56
            if (!empty($b['path'])) {
57
                $r['path'] = $b['path'];
58
            }
59
            if (!isset($r['query']) && isset($b['query'])) {
60
                $r['query'] = $b['query'];
61
            }
62
            return $this->join_url($r);
63
        }
64
65
        // If relative URL path doesn't start with /, merge with base path
66
        if ($r['path'][0] != '/') {
67
            $base = \mb_strrchr($b['path'], '/', true, 'UTF-8');
68
            if ($base === false) {
69
                $base = '';
70
            }
71
            $r['path'] = $base . '/' . $r['path'];
72
        }
73
        $r['path'] = $this->url_remove_dot_segments($r['path']);
74
        return $this->join_url($r);
75
    }
76
77
78
    private function url_remove_dot_segments($path)
79
    {
80
        // multi-byte character explode
81
        $inSegs = preg_split('!/!u', $path);
82
        $outSegs = array();
83
        foreach ($inSegs as $seg) {
84
            if ($seg == '' || $seg == '.') {
85
                continue;
86
            }
87
            if ($seg == '..') {
88
                array_pop($outSegs);
89
            } else {
90
                array_push($outSegs, $seg);
91
            }
92
        }
93
        $outPath = implode('/', $outSegs);
94
        if ($path[0] == '/') {
95
            $outPath = '/' . $outPath;
96
        }
97
        // compare last multi-byte character against '/'
98
        if ($outPath != '/' &&
99
            (\mb_strlen($path) - 1) == \mb_strrpos($path, '/')) {
100
            $outPath .= '/';
101
        }
102
        return $outPath;
103
    }
104
105
    private function join_url($parts, $encode = true)
106
    {
107
        if ($encode) {
108
            if (isset($parts['user'])) {
109
                $parts['user'] = rawurlencode($parts['user']);
110
            }
111
            if (isset($parts['pass'])) {
112
                $parts['pass'] = rawurlencode($parts['pass']);
113
            }
114
            if (isset($parts['host']) &&
115
                !preg_match('!^(\[[\da-f.:]+\]])|([\da-f.:]+)$!ui', $parts['host'])) {
116
                $parts['host'] = rawurlencode($parts['host']);
117
            }
118
            if (!empty($parts['path'])) {
119
                $parts['path'] = preg_replace(
120
                    '!%2F!ui',
121
                    '/',
122
                    rawurlencode($parts['path'])
123
                );
124
            }
125
            if (isset($parts['query'])) {
126
                $parts['query'] = rawurlencode($parts['query']);
127
            }
128
            if (isset($parts['fragment'])) {
129
                $parts['fragment'] = rawurlencode($parts['fragment']);
130
            }
131
        }
132
133
        $url = '';
134
        if (!empty($parts['scheme'])) {
135
            $url .= $parts['scheme'] . ':';
136
        }
137
        if (isset($parts['host'])) {
138
            $url .= '//';
139
            if (isset($parts['user'])) {
140
                $url .= $parts['user'];
141
                if (isset($parts['pass'])) {
142
                    $url .= ':' . $parts['pass'];
143
                }
144
                $url .= '@';
145
            }
146
            if (preg_match('!^[\da-f]*:[\da-f.:]+$!ui', $parts['host'])) {
147
                $url .= '[' . $parts['host'] . ']';
148
            }    // IPv6
149
            else {
150
                $url .= $parts['host'];
151
            }            // IPv4 or name
152
            if (isset($parts['port'])) {
153
                $url .= ':' . $parts['port'];
154
            }
155
            if (!empty($parts['path']) && $parts['path'][0] != '/') {
156
                $url .= '/';
157
            }
158
        }
159
        if (!empty($parts['path'])) {
160
            $url .= $parts['path'];
161
        }
162
        if (isset($parts['query'])) {
163
            $url .= '?' . $parts['query'];
164
        }
165
        if (isset($parts['fragment'])) {
166
            $url .= '#' . $parts['fragment'];
167
        }
168
        return $url;
169
    }
170
171
    private function split_url($url, $decode = true)
172
    {
173
        // Character sets from RFC3986.
174
        $xunressub = 'a-zA-Z\d\-._~\!$&\'()*+,;=';
175
        $xpchar = $xunressub . ':@%';
176
177
        // Scheme from RFC3986.
178
        $xscheme = '([a-zA-Z][a-zA-Z\d+\-.]*)';
179
180
        // User info (user + password) from RFC3986.
181
        $xuserinfo = '(([' . $xunressub . '%]*)' .
182
            '(:([' . $xunressub . ':%]*))?)';
183
184
        // IPv4 from RFC3986 (without digit constraints).
185
        $xipv4 = '(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})';
186
187
        // IPv6 from RFC2732 (without digit and grouping constraints).
188
        $xipv6 = '(\[([a-fA-F\d.:]+)\])';
189
190
        // Host name from RFC1035.  Technically, must start with a letter.
191
        // Relax that restriction to better parse URL structure, then
192
        // leave host name validation to application.
193
        $xhost_name = '([a-zA-Z\d\-.%]+)';
194
195
        // Authority from RFC3986.  Skip IP future.
196
        $xhost = '(' . $xhost_name . '|' . $xipv4 . '|' . $xipv6 . ')';
197
        $xport = '(\d*)';
198
        $xauthority = '((' . $xuserinfo . '@)?' . $xhost .
199
            '?(:' . $xport . ')?)';
200
201
        // Path from RFC3986.  Blend absolute & relative for efficiency.
202
        $xslash_seg = '(/[' . $xpchar . ']*)';
203
        $xpath_authabs = '((//' . $xauthority . ')((/[' . $xpchar . ']*)*))';
204
        $xpath_rel = '([' . $xpchar . ']+' . $xslash_seg . '*)';
205
        $xpath_abs = '(/(' . $xpath_rel . ')?)';
206
        $xapath = '(' . $xpath_authabs . '|' . $xpath_abs .
207
            '|' . $xpath_rel . ')';
208
209
        // Query and fragment from RFC3986.
210
        $xqueryfrag = '([' . $xpchar . '/?' . ']*)';
211
212
        // URL.
213
        $xurl = '^(' . $xscheme . ':)?' . $xapath . '?' .
214
            '(\?' . $xqueryfrag . ')?(#' . $xqueryfrag . ')?$';
215
216
217
        // Split the URL into components.
218
        if (!preg_match('!' . $xurl . '!', $url, $m)) {
219
            return false;
220
        }
221
222
        if (!empty($m[2])) {
223
            $parts['scheme'] = strtolower($m[2]);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$parts was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parts = array(); before regardless.
Loading history...
224
        }
225
226
        if (!empty($m[7])) {
227
            if (isset($m[9])) {
228
                $parts['user'] = $m[9];
229
            } else {
230
                $parts['user'] = '';
231
            }
232
        }
233
        if (!empty($m[10])) {
234
            $parts['pass'] = $m[11];
235
        }
236
237
        if (!empty($m[13])) {
238
            $h = $parts['host'] = $m[13];
239
        } else {
240
            if (!empty($m[14])) {
241
                $parts['host'] = $m[14];
242
            } else {
243
                if (!empty($m[16])) {
244
                    $parts['host'] = $m[16];
245
                } else {
246
                    if (!empty($m[5])) {
247
                        $parts['host'] = '';
248
                    }
249
                }
250
            }
251
        }
252
        if (!empty($m[17])) {
253
            $parts['port'] = $m[18];
254
        }
255
256
        if (!empty($m[19])) {
257
            $parts['path'] = $m[19];
258
        } else {
259
            if (!empty($m[21])) {
260
                $parts['path'] = $m[21];
261
            } else {
262
                if (!empty($m[25])) {
263
                    $parts['path'] = $m[25];
264
                }
265
            }
266
        }
267
268
        if (!empty($m[27])) {
269
            $parts['query'] = $m[28];
270
        }
271
        if (!empty($m[29])) {
272
            $parts['fragment'] = $m[30];
273
        }
274
275
        if (!$decode) {
276
            return $parts;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $parts does not seem to be defined for all execution paths leading up to this point.
Loading history...
277
        }
278
        if (!empty($parts['user'])) {
279
            $parts['user'] = rawurldecode($parts['user']);
280
        }
281
        if (!empty($parts['pass'])) {
282
            $parts['pass'] = rawurldecode($parts['pass']);
283
        }
284
        if (!empty($parts['path'])) {
285
            $parts['path'] = rawurldecode($parts['path']);
286
        }
287
        if (isset($h)) {
288
            $parts['host'] = rawurldecode($parts['host']);
289
        }
290
        if (!empty($parts['query'])) {
291
            $parts['query'] = rawurldecode($parts['query']);
292
        }
293
        if (!empty($parts['fragment'])) {
294
            $parts['fragment'] = rawurldecode($parts['fragment']);
295
        }
296
        return $parts;
297
    }
298
}