Completed
Push — master ( 612ab5...954270 )
by Auke
02:57 queued 01:02
created

Url::__toString()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 18
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 9
Bugs 4 Features 3
Metric Value
c 9
b 4
f 3
dl 0
loc 18
ccs 12
cts 12
cp 1
rs 8.8571
cc 6
eloc 15
nc 7
nop 0
crap 6
1
<?php
2
3
/*
4
 * This file is part of the Ariadne Component Library.
5
 *
6
 * (c) Muze <[email protected]>
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 arc\url;
13
14
/**
15
 *	Url parses a URL string and returns an object with the seperate parts. You can change
16
 *	these and when cast to a string Url will regenerate the URL string and make sure it
17
 *	is valid.
18
 *
19
 *	Usage:
20
 *		$url = new \arc\url\Url( 'http://www.ariadne-cms.org/' );
21
 *		$url->path = '/docs/search/';
22
 *		$url->query = 'a=1&a=2';
23
 *		echo $url; // => 'http://www.ariadne-cms.org/docs/search/?a=1&a=2'
24
 * @property Query $query The query arguments
25
 */
26
class Url
27
{
28
    /**
29
     *	All parts of the URL format, as returned by parse_url.
30
     *	scheme://user:pass@host:port/path?query#fragment
31
     */
32
    public $scheme, $user, $pass, $host, $port, $path, $fragment;
33
    private $query;
34
35
    /**
36
     *	@param string $url The URL to parse, the query part will remain a string.
37
     *  @param QueryInterface queryObject Optional. An object that parses the query string.
38
     */
39 14
    public function __construct($url, $queryObject = null)
40
    {
41
        $componentList = [
42 14
            'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment'
43 14
        ];
44 14
        $this->importUrlComponents( parse_url( $url ), $componentList );
45 14
        if ( isset( $queryObject ) ) {
46 14
            $this->query = $queryObject->import( $this->query );
47 14
        }
48 14
    }
49
50
    /**
51
     * @return string
52
     */
53 12
    public function __toString()
54
    {
55 12
        switch ($this->scheme) {
56 12
            case 'file':
57 1
                return $this->getScheme() . '//' . $this->host . $this->getFilePath();
58
            break;
59 12
            case 'mailto':
60 12
            case 'news':
61 1
                return ( $this->path ? $this->getScheme() . $this->getPath() : '' );
62
            break;
63 12
            case 'ldap':
64 1
                return $this->getSchemeAndAuthority() . $this->getPath() . $this->getLdapQuery();
65
            break;
66 12
            default:
67 12
                return $this->getSchemeAndAuthority() . $this->getPath() . $this->getQuery() . $this->getFragment();
68
            break;
69 12
        }
70
    }
71
72
    /**
73
     * @param $name
74
     * @return mixed
75
     */
76 5
    public function __get($name)
77
    {
78 5
        switch ( (string) $name ) {
79 5
            case 'password':
80
                return $this->pass;
81
            break;
82 5
            case 'query':
83 5
                return $this->query;
84
            break;
85
        }
86
    }
87
88
    /**
89
     * @param $name
90
     * @param $value
91
     */
92
    public function __set($name, $value)
93
    {
94
        switch ( (string) $name ) {
95
            case 'password':
96
                $this->pass = $value;
97
            break;
98
            case 'query':
99
                if ( is_object( $this->query ) ) {
100
                    $this->query->reset()->import( $value );
101
                } else {
102
                    $this->query = $value;
103
                }
104
            break;
105
        }
106
    }
107
108
    /**
109
     *
110
     */
111
    public function __clone()
112
    {
113
        if ( is_object( $this->query ) ) {
114
            $this->query = clone $this->query;
115
        }
116
    }
117
118
    /**
119
     * @param $components
120
     * @param $validComponents
121
     */
122
    private function importUrlComponents($components, $validComponents)
123
    {
124 14
        array_walk( $validComponents, function ($componentName) use ($components) {
125 14
            $this->{$componentName} = ( isset( $components[$componentName] ) ? $components[$componentName] : '' );
126 14
        } );
127 14
    }
128
129
    /**
130
     * @return string
131
     */
132 12
    private function getSchemeAndAuthority()
133
    {
134
        // note: both '//google.com/' and 'file:///C:/' are valid URL's - so if either a scheme or host is set, add the // part
135 12
        return ( ( $this->scheme || $this->host ) ? $this->getScheme() . $this->getAuthority() : '' );
136
    }
137
138
    /**
139
     * @return string
140
     */
141 10
    private function getScheme()
142
    {
143 10
        return ( $this->scheme ? $this->scheme . ':' : '' );
144
    }
145
146
    /**
147
     * @return string
148
     */
149 10
    private function getAuthority()
150
    {
151 10
        return ( $this->host ? '//' . $this->getUser() . $this->host . $this->getPort() : '' );
152
    }
153
154
    /**
155
     * @return string
156
     */
157 10
    private function getUser()
158
    {
159 10
        return ( $this->user ? rawurlencode( $this->user ) . $this->getPassword() . '@' : '' );
160
    }
161
162
    /**
163
     * @return string
164
     */
165 1
    private function getPassword()
166
    {
167 1
        return ( $this->user && $this->pass ?  ':' . rawurlencode( $this->pass ) : '' );
168
    }
169
170
    /**
171
     * @return string
172
     */
173 10
    private function getPort()
174
    {
175 10
        return ( $this->port ? ':' . (int) $this->port : '' );
176
    }
177
178
    /**
179
     * @return mixed|string
180
     */
181 12
    private function getPath()
182
    {
183 12
        if (!$this->path) {
184 1
            return '';
185
        }
186 12
        $path = $this->path;
187 12
        if ( $this->host && ( !$path || $path[0] !== '/' ) ) {
188
            // note: if a host is set, the path _must_ be made absolute or the URL will be invalid
189
            $path = '/' . $path;
190
        }
191
        // urlencode encodes too many characters for the path part, so we decode them back to get readable urls.
192 12
        return str_replace( [ '%3D', '%2B', '%3A', '%40' ], [ '=', '+', ':', '@' ], join( '/', array_map( 'urlencode', explode( '/', $path ) ) ) );
193
    }
194
195
    /**
196
     * @return mixed|string
197
     */
198 1
    private function getFilePath()
199
    {
200
        // in the file: scheme, a path must start with a '/' even if no host is set. This contradicts with the email: scheme.
201 1
        $path = $this->getPath();
202 1
        if ($path && $path[0]!=='/') {
203 1
            $path = '/' . $path;
204 1
        }
205
206 1
        return $path;
207
    }
208
209
    /**
210
     * @return string
211
     */
212 12
    private function getQuery()
213
    {
214
        // queries are assumed to handle themselves, so no encoding here.
215 12
        $query = (string) $this->query; // convert explicitly to string first, because the query object may exist but still return an empty string
216
217 12
        return ( $query ? '?' . $query : '' );
218
    }
219
220
    /**
221
     * @return string
222
     */
223 1
    private function getLdapQuery()
224
    {
225
        // ldap queries may contain multiple ? tokens - so these are unencoded here.
226 1
        $query = (string) $this->query; // convert explicitly to string first, because the query object may exist but still return an empty string
227
228 1
        return ( $query ? '?' . str_replace( '%3F', '?', $query ) : '' );
229
    }
230
231
    /**
232
     * @return string
233
     */
234 12
    private function getFragment()
235
    {
236 12
        return ( $this->fragment ? '#' . urlencode($this->fragment) : '' );
237
    }
238
239
}
240