Passed
Push — master ( 4a79ed...7ae6f1 )
by Sebastian
02:30
created

URLInfo_Parser::validate_schemeIsSet()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 6
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 15
rs 10
1
<?php
2
/**
3
 * File containing the {@see AppUtils\URLInfo_Parser} class.
4
 *
5
 * @package Application Utils
6
 * @subpackage URLInfo
7
 * @see AppUtils\URLInfo_Parser
8
 */
9
10
declare(strict_types=1);
11
12
namespace AppUtils;
13
14
/**
15
 * Handles the URL parsing.
16
 *
17
 * @package Application Utils
18
 * @subpackage URLInfo
19
 * @author Sebastian Mordziol <[email protected]>
20
 */
21
class URLInfo_Parser
22
{
23
   /**
24
    * @var string
25
    */
26
    protected $url;
27
    
28
   /**
29
    * @var bool
30
    */
31
    protected $isValid = false;
32
    
33
   /**
34
    * @var array
35
    */
36
    protected $info;
37
    
38
   /**
39
    * @var array|NULL
40
    */
41
    protected $error;
42
    
43
    /**
44
     * @var array
45
     */
46
    protected $knownSchemes = array(
47
        'ftp',
48
        'http',
49
        'https',
50
        'mailto',
51
        'tel',
52
        'data',
53
        'file'
54
    );
55
    
56
    public function __construct(string $url)
57
    {
58
        $this->url = $url;
59
        
60
        $this->parse();
61
        
62
        if(!$this->detectType()) {
63
            $this->validate();
64
        }
65
    }
66
67
    public function getInfo() : array
68
    {
69
        return $this->info;
70
    }
71
    
72
    protected function parse()
73
    {
74
        // fix for parsing unicode characters in URLs:
75
        // this is dependent on the machine's locale,
76
        // so to ensure this works we temporarily change
77
        // it to the always available US UTF8 locale.
78
        $prev = setlocale(LC_CTYPE, 'en_US.UTF-8');
79
        
80
        $this->info = parse_url($this->url);
81
        
82
        // restore the previous locale
83
        setlocale(LC_CTYPE, $prev);
84
        
85
        $this->filterParsed();
86
    }
87
    
88
    protected function detectType() : bool
89
    {
90
        $types = array(
91
            'email',
92
            'fragmentLink',
93
            'phoneLink'
94
        );
95
        
96
        foreach($types as $type)
97
        {
98
            $method = 'detectType_'.$type;
99
            
100
            if($this->$method() === true) 
101
            {
102
                $this->isValid = true;
103
                return true;
104
            }
105
        }
106
        
107
        return false;
108
    }
109
    
110
    protected function validate()
111
    {
112
        $validations = array(
113
            'schemeIsSet',
114
            'schemeIsKnown',
115
            'hostIsPresent'
116
        );
117
        
118
        foreach($validations as $validation) 
119
        {
120
            $method = 'validate_'.$validation;
121
            
122
            if($this->$method() !== true) {
123
                return;
124
            }
125
        }
126
        
127
        $this->isValid = true;
128
    }
129
    
130
    protected function validate_hostIsPresent() : bool
131
    {
132
        // every link needs a host. This case can happen for ex, if
133
        // the link starts with a typo with only one slash, like:
134
        // "http:/hostname"
135
        if(isset($this->info['host'])) {
136
            return true;
137
        }
138
        
139
        $this->setError(
140
            URLInfo::ERROR_MISSING_HOST,
141
            t('Cannot determine the link\'s host name.') . ' ' .
142
            t('This usually happens when there\'s a typo somewhere.')
143
        );
144
145
        return false;
146
    }
147
    
148
    protected function validate_schemeIsSet() : bool
149
    {
150
        if(isset($this->info['scheme'])) {
151
            return true;
152
        }
153
        
154
        // no scheme found: it may be an email address without the mailto:
155
        // It can't be a variable, since without the scheme it would already
156
        // have been recognized as a vaiable only link.
157
        $this->setError(
158
            URLInfo::ERROR_MISSING_SCHEME,
159
            t('Cannot determine the link\'s scheme, e.g. %1$s.', 'http')
160
        );
161
        
162
        return false;
163
    }
164
    
165
    protected function validate_schemeIsKnown() : bool
166
    {
167
        if(in_array($this->info['scheme'], $this->knownSchemes)) {
168
            return true;
169
        }
170
        
171
        $this->setError(
172
            URLInfo::ERROR_INVALID_SCHEME,
173
            t('The scheme %1$s is not supported for links.', $this->info['scheme']) . ' ' .
174
            t('Valid schemes are: %1$s.', implode(', ', $this->knownSchemes))
175
        );
176
177
        return false;
178
    }
179
180
   /**
181
    * Goes through all information in the parse_url result
182
    * array, and attempts to fix any user errors in formatting
183
    * that can be recovered from, mostly regarding stray spaces.
184
    */
185
    protected function filterParsed()
186
    {
187
        foreach($this->info as $key => $val)
188
        {
189
            if(is_string($val)) {
190
                $this->info[$key] = trim($val);
191
            }
192
        }
193
        
194
        $this->info['params'] = array();
195
        $this->info['type'] = URLInfo::TYPE_URL;
196
        
197
        if(isset($this->info['host'])) {
198
            $this->info['host'] = str_replace(' ', '', $this->info['host']);
199
        }
200
        
201
        if(isset($this->info['path'])) {
202
            $this->info['path'] = str_replace(' ', '', $this->info['path']);
203
        }
204
        
205
        if(isset($this->info['query']) && !empty($this->info['query']))
206
        {
207
            $this->info['params'] = \AppUtils\ConvertHelper::parseQueryString($this->info['query']);
208
            ksort($this->info['params']);
209
        }
210
    }
211
    
212
    protected function detectType_email() : bool
213
    {
214
        if(isset($this->info['scheme']) && $this->info['scheme'] == 'mailto') {
215
            $this->info['type'] = URLInfo::TYPE_EMAIL;
216
            return true;
217
        }
218
        
219
        if(isset($this->info['path']) && preg_match(\AppUtils\RegexHelper::REGEX_EMAIL, $this->info['path']))
220
        {
221
            $this->info['scheme'] = 'mailto';
222
            $this->info['type'] = URLInfo::TYPE_EMAIL;
223
            return true;
224
        }
225
        
226
        return false;
227
    }
228
    
229
    protected function detectType_fragmentLink() : bool
230
    {
231
        if(isset($this->info['fragment']) && !isset($this->info['scheme'])) {
232
            $this->info['type'] = URLInfo::TYPE_FRAGMENT;
233
            return true;
234
        }
235
        
236
        return false;
237
    }
238
    
239
    protected function detectType_phoneLink() : bool
240
    {
241
        if(isset($this->info['scheme']) && $this->info['scheme'] == 'tel') {
242
            $this->info['type'] = URLInfo::TYPE_PHONE;
243
            return true;
244
        }
245
        
246
        return false;
247
    }
248
249
    protected function setError(int $code, string $message)
250
    {
251
        $this->isValid = false;
252
        
253
        $this->error = array(
254
            'code' => $code,
255
            'message' => $message
256
        );
257
    }
258
    
259
    public function isValid() : bool
260
    {
261
        return $this->isValid;
262
    }
263
264
    public function getErrorMessage() : string
265
    {
266
        if(isset($this->error)) {
267
            return $this->error['message'];
268
        }
269
        
270
        return '';
271
    }
272
    
273
    public function getErrorCode() : int
274
    {
275
        if(isset($this->error)) {
276
            return $this->error['code'];
277
        }
278
        
279
        return -1;
280
    }
281
}
282