Completed
Push — master ( 60e205...4a2001 )
by Jonathan
01:47
created

Url::join()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 21
rs 8.7624
cc 5
eloc 11
nc 12
nop 1
1
<?php
2
3
/*
4
 * This file is part of the Purl package, a project by Jonathan H. Wage.
5
 *
6
 * (c) 2013 Jonathan H. Wage
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Purl;
13
14
use Pdp\PublicSuffixListManager;
15
use Pdp\Parser as PslParser;
16
17
/**
18
 * Url is a simple OO class for manipulating Urls in PHP.
19
 *
20
 * @author      Jonathan H. Wage <[email protected]>
21
 *
22
 * @property string $scheme
23
 * @property string $host
24
 * @property integer $port
25
 * @property string $user
26
 * @property string $pass
27
 * @property \Purl\Path $path
28
 * @property \Purl\Query $query
29
 * @property \Purl\Fragment $fragment
30
 * @property string $publicSuffix
31
 * @property string $registerableDomain
32
 * @property string $subdomain
33
 * @property string $canonical
34
 * @property string $resource
35
 */
36
class Url extends AbstractPart
37
{
38
    /**
39
     * @var string The original url string.
40
     */
41
    private $url;
42
43
    /**
44
     * @var ParserInterface
45
     */
46
    private $parser;
47
48
    /**
49
     * @var array
50
     */
51
    protected $data = array(
52
        'scheme'             => null,
53
        'host'               => null,
54
        'port'               => null,
55
        'user'               => null,
56
        'pass'               => null,
57
        'path'               => null,
58
        'query'              => null,
59
        'fragment'           => null,
60
        'publicSuffix'       => null,
61
        'registerableDomain' => null,
62
        'subdomain'          => null,
63
        'canonical'          => null,
64
        'resource'           => null
65
    );
66
67
    /**
68
     * @var array
69
     */
70
    protected $partClassMap = array(
71
        'path' => 'Purl\Path',
72
        'query' => 'Purl\Query',
73
        'fragment' => 'Purl\Fragment'
74
    );
75
76
    /**
77
     * Construct a new Url instance.
78
     *
79
     * @param string $url
80
     * @param ParserInterface $parser
81
     */
82
    public function __construct($url = null, ParserInterface $parser = null)
83
    {
84
        $this->url = $url;
85
        $this->parser = $parser;
86
    }
87
88
    /**
89
     * Static convenience method for creating a new Url instance.
90
     *
91
     * @param string $url
92
     * @return Url
93
     */
94
    public static function parse($url)
95
    {
96
        return new self($url);
97
    }
98
99
    /**
100
     * Extracts urls from a string of text.
101
     *
102
     * @param string $string
103
     * @return array $urls
104
     */
105
    public static function extract($string)
106
    {
107
        $regex = "/(http|https|ftp|ftps)\:\/\/[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(\/\S*)?/";
108
109
        preg_match_all($regex, $string, $matches);
110
        $urls = array();
111
        foreach ($matches[0] as $url) {
112
            $urls[] = self::parse($url);
113
        }
114
115
        return $urls;
116
    }
117
118
    /**
119
     * Creates an Url instance based on data available on $_SERVER variable.
120
     *
121
     * @return Url
122
     */
123
    public static function fromCurrent()
124
    {
125
        $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) ? 'https' : 'http';
126
127
        $host = $_SERVER['HTTP_HOST'];
128
        $baseUrl = "$scheme://$host";
129
130
        $url = new self($baseUrl);
131
132
        if (!empty($_SERVER['REQUEST_URI'])) {
133
            if (strpos($_SERVER['REQUEST_URI'], '?') !== false) {
134
                list($path, $query) = explode('?', $_SERVER['REQUEST_URI'], 2);
135
            } else {
136
                $path = $_SERVER['REQUEST_URI'];
137
                $query = '';
138
            }
139
140
            $url->set('path', $path);
141
            $url->set('query', $query);
142
        }
143
144
        // Only set port if different from default (80 or 443)
145
        if (!empty($_SERVER['SERVER_PORT'])) {
146
            $port = $_SERVER['SERVER_PORT'];
147
            if (($scheme == 'http' && $port != 80) ||
148
                ($scheme == 'https' && $port != 443)) {
149
                $url->set('port', $port);
150
            }
151
        }
152
153
        // Authentication
154
        if (!empty($_SERVER['PHP_AUTH_USER'])) {
155
            $url->set('user', $_SERVER['PHP_AUTH_USER']);
156
            if (!empty($_SERVER['PHP_AUTH_PW'])) {
157
                $url->set('pass', $_SERVER['PHP_AUTH_PW']);
158
            }
159
        }
160
161
        return $url;
162
    }
163
164
    /**
165
     * Gets the ParserInterface instance used to parse this Url instance.
166
     *
167
     * @return ParserInterface
168
     */
169
    public function getParser()
170
    {
171
        if ($this->parser === null) {
172
            $this->parser = self::createDefaultParser();
173
        }
174
175
        return $this->parser;
176
    }
177
178
    /**
179
     * Sets the ParserInterface instance to use to parse this Url instance.
180
     *
181
     * @param ParserInterface $parser
182
     */
183
    public function setParser(ParserInterface $parser)
184
    {
185
        $this->parser = $parser;
186
    }
187
188
    /**
189
     * Join this Url instance together with another Url instance or a string url.
190
     *
191
     * @param Url|string $url
192
     * @return Url
193
     */
194
    public function join($url)
195
    {
196
        $this->initialize();
197
        $parts = $this->getParser()->parseUrl($url);
198
199
        if ($this->data['scheme'] !== null) {
200
            $parts['scheme'] = $this->data['scheme'];
201
        }
202
203
        foreach ($parts as $key => $value) {
204
            if ($value !== null) {
205
                $this->data[$key] = $value;
206
            }
207
        }
208
209
        foreach ($this->data as $key => $value) {
210
            $this->data[$key] = $this->preparePartValue($key, $value);
211
        }
212
213
        return $this;
214
    }
215
216
    /**
217
     * @inheritDoc
218
     * @override
219
     */
220
    public function set($key, $value)
221
    {
222
        $this->initialize();
223
        $this->data[$key] = $this->preparePartValue($key, $value);
224
225
        return $this;
226
    }
227
228
    /**
229
     * Set the Path instance.
230
     *
231
     * @param Path
232
     */
233
    public function setPath(Path $path)
234
    {
235
        $this->data['path'] = $path;
236
237
        return $this;
238
    }
239
240
    /**
241
     * Get the Path instance.
242
     *
243
     * @return Path
244
     */
245
    public function getPath()
246
    {
247
        $this->initialize();
248
        return $this->data['path'];
249
    }
250
251
    /**
252
     * Set the Query instance.
253
     *
254
     * @param Query
255
     */
256
    public function setQuery(Query $query)
257
    {
258
        $this->data['query'] = $query;
259
260
        return $this;
261
    }
262
263
    /**
264
     * Get the Query instance.
265
     *
266
     * @return Query
267
     */
268
    public function getQuery()
269
    {
270
        $this->initialize();
271
        return $this->data['query'];
272
    }
273
274
    /**
275
     * Set the Fragment instance.
276
     *
277
     * @param Fragment
278
     */
279
    public function setFragment(Fragment $fragment)
280
    {
281
        $this->data['fragment'] = $fragment;
282
283
        return $this;
284
    }
285
286
    /**
287
     * Get the Fragment instance.
288
     *
289
     * @return Fragment
290
     */
291
    public function getFragment()
292
    {
293
        $this->initialize();
294
        return $this->data['fragment'];
295
    }
296
297
    /**
298
     * Gets the netloc part of the Url. It is the user, pass, host and port returned as a string.
299
     *
300
     * @return string
301
     */
302
    public function getNetloc()
303
    {
304
        $this->initialize();
305
        return ($this->user && $this->pass ? $this->user.($this->pass ? ':'.$this->pass : '').'@' : '').$this->host.($this->port ? ':'.$this->port : '');
306
    }
307
308
    /**
309
     * Builds a string url from this Url instance internal data and returns it.
310
     *
311
     * @return string
312
     */
313
    public function getUrl()
314
    {
315
        $this->initialize();
316
        
317
        $parts = array_map('strval', $this->data);
318
        
319
        if(!$this->isAbsolute()) {
320
            return self::httpBuildRelativeUrl($parts);
321
        }
322
                
323
        return self::httpBuildUrl($parts);
324
    }
325
326
    /**
327
     * Set the string url for this Url instance and sets initialized to false.
328
     *
329
     * @param string
330
     */
331
    public function setUrl($url)
332
    {
333
        $this->initialized = false;
334
        $this->data = array();
335
        $this->url = $url;
336
    }
337
338
    /**
339
     * Checks if the Url instance is absolute or not.
340
     *
341
     * @return boolean
342
     */
343
    public function isAbsolute()
344
    {
345
        $this->initialize();
346
        return $this->scheme && $this->host;
347
    }
348
349
    /**
350
     * @inheritDoc
351
     */
352
    public function __toString()
353
    {
354
        return $this->getUrl();
355
    }
356
357
    /**
358
     * @inheritDoc
359
     */
360
    protected function doInitialize()
361
    {
362
        $parts = $this->getParser()->parseUrl($this->url);
363
364
        foreach ($parts as $k => $v) {
365
            if (!isset($this->data[$k])) {
366
                $this->data[$k] = $v;
367
            }
368
        }
369
370
        foreach ($this->data as $key => $value) {
371
            $this->data[$key] = $this->preparePartValue($key, $value);
372
        }
373
    }
374
375
    /**
376
     * Reconstructs a string URL from an array of parts.
377
     *
378
     * @param array $parts
379
     * @return string $url
380
     */
381
    private static function httpBuildUrl(array $parts)
382
    {
383
        $relative = self::httpBuildRelativeUrl($parts);
384
385
        return sprintf('%s://%s%s%s%s',
386
            $parts['scheme'],
387
            $parts['user'] ? sprintf('%s%s@', $parts['user'], $parts['pass'] ? sprintf(':%s', $parts['pass']) : '') : '',
388
            $parts['host'],
389
            $parts['port'] ? sprintf(':%d', $parts['port']) : '',
390
            $relative
391
        );
392
    }
393
    
394
    /**
395
     * Reconstructs relative part of URL from an array of parts.
396
     *
397
     * @param array $parts
398
     * @return string $url
399
     */
400
    private static function httpBuildRelativeUrl(array $parts)
401
    {
402
        $parts['path'] = ltrim($parts['path'], '/');
403
404
        return sprintf('/%s%s%s',
405
            $parts['path'] ? $parts['path'] : '',
406
            $parts['query'] ? '?'.$parts['query'] : '',
407
            $parts['fragment'] ? '#'.$parts['fragment'] : ''
408
        );
409
    }
410
411
    /**
412
     * Creates the default Parser instance to parse urls.
413
     *
414
     * @return Parser
415
     */
416
    private static function createDefaultParser()
417
    {
418
        $pslManager = new PublicSuffixListManager(dirname(dirname(__DIR__)) . '/data');
419
        $pslParser = new PslParser($pslManager->getList());
420
        
421
        return new Parser($pslParser);
422
    }
423
}
424