Completed
Push — issue#666 ( 5c565a...94715f )
by Guilherme
03:27
created

TagUri::getFragment()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of the login-cidadao project or it's bundles.
4
 *
5
 * (c) Guilherme Donato <guilhermednt on github>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace LoginCidadao\RemoteClaimsBundle\Model;
12
13
use Egulias\EmailValidator\EmailValidator;
14
use Psr\Http\Message\UriInterface;
15
use Symfony\Component\Intl\Exception\NotImplementedException;
16
17
class TagUri implements UriInterface
18
{
19
    const REGEX_OVERALL = '^tag:(?<taggingEntity>[^:]+):(?<specific>[^#]+)(?:#(?<fragment>[\w\d&?@:_]+))?$';
20
    const REGEX_DATE = '\d{4}(?:-(?:0\d|1[012])(?:-(?:[012]\d|3[01]))?)?';
21
    const REGEX_DATE_YEAR = '(?<year>\d{4})';
22
    const REGEX_DATE_MONTH = '(?<month>0\d|1[012])';
23
    const REGEX_DATE_DAY = '(?<day>[012]\d|3[01])';
24
    const REGEX_DNScomp = '(?:[\w\d](?:[\w\d-]*[\w\d])?)';
0 ignored issues
show
Coding Style introduced by
This class constant is not uppercase (expected REGEX_DNSCOMP).
Loading history...
25
    const REGEX_date = '(?:\d{4}(?:-\d{2}(?:-\d{2})?)?)';
0 ignored issues
show
Coding Style introduced by
This class constant is not uppercase (expected REGEX_DATE).
Loading history...
26
27
    protected static $supportedSchemes = ['tag'];
28
29
    /** @var string */
30
    private $authorityName;
31
32
    /** @var string */
33
    private $host;
0 ignored issues
show
Unused Code introduced by
The property $host is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
34
35
    /** @var string */
36
    private $date;
37
38
    /** @var string */
39
    private $specific;
40
41
    /** @var string */
42
    private $fragment;
43
44
    private static function getDnsNameRegex()
45
    {
46
        return '(?:'.self::REGEX_DNScomp.'(?:[.]'.self::REGEX_DNScomp.')*)';
47
    }
48
49
    private static function getTaggingEntityRegex()
50
    {
51
        return '(?<authorityName>'.self::getDnsNameRegex().'|'.static::getEmailAddressRegex().'),(?<date>'.self::REGEX_date.')';
52
    }
53
54
    private static function getEmailAddressRegex()
55
    {
56
        return '(?:[\w\d-._+]*@'.self::getDnsNameRegex().')';
57
    }
58
59
    private static function getDateRegex()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
60
    {
61
        $day = self::REGEX_DATE_DAY;
62
        $month = self::REGEX_DATE_MONTH;
63
        $year = self::REGEX_DATE_YEAR;
64
65
        return "$year(?:-$month(?:-$day)?)?";
66
    }
67
68
    /**
69 21
     * Retrieve the scheme component of the URI.
70
     *
71 21
     * If no scheme is present, this method MUST return an empty string.
72
     *
73
     * The value returned MUST be normalized to lowercase, per RFC 3986
74 21
     * Section 3.1.
75
     *
76 21
     * The trailing ":" character is not part of the scheme and MUST NOT be
77
     * added.
78
     *
79 21
     * @see https://tools.ietf.org/html/rfc3986#section-3.1
80
     * @return string The URI scheme.
81 21
     */
82
    public function getScheme()
83
    {
84 19
        return 'tag';
85
    }
86 19
87 19
    /**
88 19
     * Retrieve the authority component of the URI.
89
     *
90 19
     * If no authority information is present, this method MUST return an empty
91
     * string.
92
     *
93
     * The authority syntax of the URI is:
94
     *
95
     * <pre>
96
     * [user-info@]host[:port]
97
     * </pre>
98
     *
99
     * If the port component is not set or is the standard port for the current
100
     * scheme, it SHOULD NOT be included.
101
     *
102
     * @see https://tools.ietf.org/html/rfc3986#section-3.2
103
     * @return string The URI authority, in "[user-info@]host[:port]" format.
104
     */
105
    public function getAuthority()
106
    {
107
        return $this->authorityName;
108
    }
109
110
    public function getAuthorityName()
111
    {
112
        return $this->getAuthority();
113
    }
114
115
    /**
116
     * Retrieve the user information component of the URI.
117
     *
118
     * If no user information is present, this method MUST return an empty
119
     * string.
120
     *
121
     * If a user is present in the URI, this will return that value;
122
     * additionally, if the password is also present, it will be appended to the
123
     * user value, with a colon (":") separating the values.
124
     *
125
     * The trailing "@" character is not part of the user information and MUST
126
     * NOT be added.
127
     *
128
     * @return string The URI user information, in "username[:password]" format.
129
     */
130 14
    public function getUserInfo()
131
    {
132 14
        return '';
133
    }
134
135 7
    /**
136
     * Retrieve the host component of the URI.
137 7
     *
138
     * If no host is present, this method MUST return an empty string.
139
     *
140
     * The value returned MUST be normalized to lowercase, per RFC 3986
141
     * Section 3.2.2.
142
     *
143
     * @see http://tools.ietf.org/html/rfc3986#section-3.2.2
144
     * @return string The URI host.
145
     */
146
    public function getHost()
147
    {
148
        return $this->extractHost($this->getAuthority());
149
    }
150
151
    /**
152
     * Retrieve the port component of the URI.
153
     *
154
     * If a port is present, and it is non-standard for the current scheme,
155
     * this method MUST return it as an integer. If the port is the standard port
156
     * used with the current scheme, this method SHOULD return null.
157
     *
158
     * If no port is present, and no scheme is present, this method MUST return
159
     * a null value.
160
     *
161
     * If no port is present, but a scheme is present, this method MAY return
162
     * the standard port for that scheme, but SHOULD return null.
163
     *
164
     * @return null|int The URI port.
165
     */
166
    public function getPort()
167
    {
168
        return null;
169
    }
170
171
    /**
172
     * Retrieve the path component of the URI.
173
     *
174
     * The path can either be empty or absolute (starting with a slash) or
175
     * rootless (not starting with a slash). Implementations MUST support all
176
     * three syntaxes.
177
     *
178
     * Normally, the empty path "" and absolute path "/" are considered equal as
179
     * defined in RFC 7230 Section 2.7.3. But this method MUST NOT automatically
180
     * do this normalization because in contexts with a trimmed base path, e.g.
181
     * the front controller, this difference becomes significant. It's the task
182
     * of the user to handle both "" and "/".
183
     *
184
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
185
     * any characters. To determine what characters to encode, please refer to
186
     * RFC 3986, Sections 2 and 3.3.
187
     *
188
     * As an example, if the value should include a slash ("/") not intended as
189
     * delimiter between path segments, that value MUST be passed in encoded
190
     * form (e.g., "%2F") to the instance.
191
     *
192
     * @see https://tools.ietf.org/html/rfc3986#section-2
193
     * @see https://tools.ietf.org/html/rfc3986#section-3.3
194
     * @return string The URI path.
195
     */
196
    public function getPath()
197
    {
198
        return '';
199
    }
200
201
    /**
202
     * Retrieve the query string of the URI.
203
     *
204
     * If no query string is present, this method MUST return an empty string.
205
     *
206
     * The leading "?" character is not part of the query and MUST NOT be
207
     * added.
208
     *
209
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
210
     * any characters. To determine what characters to encode, please refer to
211
     * RFC 3986, Sections 2 and 3.4.
212
     *
213
     * As an example, if a value in a key/value pair of the query string should
214
     * include an ampersand ("&") not intended as a delimiter between values,
215
     * that value MUST be passed in encoded form (e.g., "%26") to the instance.
216
     *
217
     * @see https://tools.ietf.org/html/rfc3986#section-2
218
     * @see https://tools.ietf.org/html/rfc3986#section-3.4
219
     * @return string The URI query string.
220
     */
221
    public function getQuery()
222
    {
223
        return '';
224
    }
225
226
    /**
227
     * Retrieve the fragment component of the URI.
228
     *
229
     * If no fragment is present, this method MUST return an empty string.
230
     *
231
     * The leading "#" character is not part of the fragment and MUST NOT be
232
     * added.
233
     *
234
     * The value returned MUST be percent-encoded, but MUST NOT double-encode
235
     * any characters. To determine what characters to encode, please refer to
236
     * RFC 3986, Sections 2 and 3.5.
237
     *
238
     * @see https://tools.ietf.org/html/rfc3986#section-2
239
     * @see https://tools.ietf.org/html/rfc3986#section-3.5
240
     * @return string The URI fragment.
241
     */
242
    public function getFragment()
243
    {
244
        return $this->fragment;
245
    }
246
247
    /**
248
     * Retrieve the specific component of the tag URI.
249
     *
250
     * If no specific is present, this method MUST return an empty string.
251
     *
252
     * The leading ":" character is not part of the specific and MUST NOT be
253
     * added.
254
     *
255
     * @see https://tools.ietf.org/html/rfc4151#section-2
256
     * @return string The tag URI specific.
257
     */
258
    public function getSpecific()
259
    {
260
        return $this->specific;
261
    }
262
263
    /**
264
     * Retrieve the date component of the tag URI.
265
     *
266
     * The leading "," character is not part of the date and MUST NOT be
267 10
     * added.
268
     *
269 10
     * @see https://tools.ietf.org/html/rfc4151#section-2
270
     * @return string The tag URI date.
271
     */
272
    public function getDate()
273
    {
274
        return $this->date;
275
    }
276
277
    public function getTaggingEntity()
278
    {
279
        return sprintf("%s,%s", $this->getAuthority(), $this->getDate());
280
    }
281
282
    /**
283 10
     * Return an instance with the specified scheme.
284
     *
285 10
     * This method MUST retain the state of the current instance, and return
286
     * an instance that contains the specified scheme.
287
     *
288
     * Implementations MUST support the schemes "http" and "https" case
289
     * insensitively, and MAY accommodate other schemes if required.
290
     *
291
     * An empty scheme is equivalent to removing the scheme.
292
     *
293
     * @param string $scheme The scheme to use with the new instance.
294
     * @return static A new instance with the specified scheme.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use NoType.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
295
     * @throws \InvalidArgumentException for invalid or unsupported schemes.
296
     */
297 10
    public function withScheme($scheme)
298
    {
299 10
        throw new \BadMethodCallException("This method is not supported");
300
    }
301
302 10
    /**
303
     * Return an instance with the specified user information.
304 10
     *
305
     * This method MUST retain the state of the current instance, and return
306
     * an instance that contains the specified user information.
307
     *
308
     * Password is optional, but the user information MUST include the
309
     * user; an empty string for the user is equivalent to removing user
310
     * information.
311
     *
312
     * @param string $user The user name to use for authority.
313
     * @param null|string $password The password associated with $user.
314
     * @return static A new instance with the specified user information.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use NoType.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
315
     */
316
    public function withUserInfo($user, $password = null)
317
    {
318
        throw new \BadMethodCallException("This method is not supported");
319
    }
320
321
    /**
322
     * Return an instance with the specified host.
323
     *
324
     * This method MUST retain the state of the current instance, and return
325
     * an instance that contains the specified host.
326
     *
327
     * An empty host value is equivalent to removing the host.
328
     *
329
     * @param string $host The hostname to use with the new instance.
330
     * @return static A new instance with the specified host.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use NoType.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
331
     * @throws \InvalidArgumentException for invalid hostnames.
332
     */
333
    public function withHost($host)
334
    {
335
        throw new \BadMethodCallException("This method is not supported");
336
    }
337
338
    /**
339
     * Return an instance with the specified port.
340
     *
341
     * This method MUST retain the state of the current instance, and return
342
     * an instance that contains the specified port.
343
     *
344
     * Implementations MUST raise an exception for ports outside the
345
     * established TCP and UDP port ranges.
346
     *
347
     * A null value provided for the port is equivalent to removing the port
348
     * information.
349
     *
350
     * @param null|int $port The port to use with the new instance; a null value
351
     *     removes the port information.
352
     * @return static A new instance with the specified port.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use NoType.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
353
     * @throws \InvalidArgumentException for invalid ports.
354
     */
355
    public function withPort($port)
356
    {
357
        throw new \BadMethodCallException("This method is not supported");
358
    }
359
360
    /**
361
     * Return an instance with the specified path.
362
     *
363
     * This method MUST retain the state of the current instance, and return
364
     * an instance that contains the specified path.
365
     *
366
     * The path can either be empty or absolute (starting with a slash) or
367
     * rootless (not starting with a slash). Implementations MUST support all
368
     * three syntaxes.
369
     *
370
     * If the path is intended to be domain-relative rather than path relative then
371
     * it must begin with a slash ("/"). Paths not starting with a slash ("/")
372
     * are assumed to be relative to some base path known to the application or
373
     * consumer.
374
     *
375
     * Users can provide both encoded and decoded path characters.
376
     * Implementations ensure the correct encoding as outlined in getPath().
377
     *
378
     * @param string $path The path to use with the new instance.
379
     * @return static A new instance with the specified path.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use NoType.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
380
     * @throws \InvalidArgumentException for invalid paths.
381
     */
382
    public function withPath($path)
383
    {
384
        throw new \BadMethodCallException("This method is not supported");
385
    }
386
387
    /**
388
     * Return an instance with the specified query string.
389
     *
390
     * This method MUST retain the state of the current instance, and return
391
     * an instance that contains the specified query string.
392
     *
393
     * Users can provide both encoded and decoded query characters.
394
     * Implementations ensure the correct encoding as outlined in getQuery().
395
     *
396
     * An empty query string value is equivalent to removing the query string.
397
     *
398
     * @param string $query The query string to use with the new instance.
399
     * @return static A new instance with the specified query string.
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use NoType.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
400
     * @throws \InvalidArgumentException for invalid query strings.
401
     */
402
    public function withQuery($query)
403
    {
404
        throw new \BadMethodCallException("This method is not supported");
405
    }
406
407
    /**
408
     * Return an instance with the specified URI fragment.
409
     *
410
     * This method MUST retain the state of the current instance, and return
411
     * an instance that contains the specified URI fragment.
412
     *
413
     * Users can provide both encoded and decoded fragment characters.
414
     * Implementations ensure the correct encoding as outlined in getFragment().
415
     *
416
     * An empty fragment value is equivalent to removing the fragment.
417
     *
418
     * @param string $fragment The fragment to use with the new instance.
419
     * @return static A new instance with the specified fragment.
420
     */
421
    public function withFragment($fragment)
422
    {
423
        return (new TagUri())
424
            ->setAuthorityName($this->getAuthorityName())
425
            ->setDate($this->getDate())
426
            ->setSpecific($this->getSpecific())
427
            ->setFragment($fragment);
428
    }
429
430
    /**
431
     * Return the string representation as a URI reference.
432
     *
433
     * Depending on which components of the URI are present, the resulting
434
     * string is either a full URI or relative reference according to RFC 3986,
435
     * Section 4.1. The method concatenates the various components of the URI,
436
     * using the appropriate delimiters:
437
     *
438
     * - If a scheme is present, it MUST be suffixed by ":".
439
     * - If an authority is present, it MUST be prefixed by "//".
440
     * - The path can be concatenated without delimiters. But there are two
441
     *   cases where the path has to be adjusted to make the URI reference
442
     *   valid as PHP does not allow to throw an exception in __toString():
443
     *     - If the path is rootless and an authority is present, the path MUST
444
     *       be prefixed by "/".
445
     *     - If the path is starting with more than one "/" and no authority is
446
     *       present, the starting slashes MUST be reduced to one.
447
     * - If a query is present, it MUST be prefixed by "?".
448
     * - If a fragment is present, it MUST be prefixed by "#".
449
     *
450
     * @see http://tools.ietf.org/html/rfc3986#section-4.1
451
     * @return string
452
     */
453
    public function __toString()
454
    {
455
        $tagURI = sprintf("tag:%s:%s", $this->getTaggingEntity(), $this->getSpecific());
456
457
        if ('' !== $fragment = $this->getFragment()) {
458
            $tagURI = sprintf("%s#%s", $tagURI, $fragment);
459
        }
460
461
        return $tagURI;
462
    }
463
464
    public function setAuthorityName($authorityName)
465
    {
466
        if (strstr($authorityName, '@') !== false) {
467
            $this->checkEmail($authorityName);
468
        }
469
470
        $this->authorityName = $authorityName;
471
472
        return $this;
473
    }
474 10
475
    public function setDate($date)
476 10
    {
477
        $dateRegex = self::getDateRegex();
478 10
        if (!preg_match("/^$dateRegex$/", $date, $m)) {
479
            throw new \InvalidArgumentException('Invalid date: '.$date);
480
        }
481
482 10
        $parts = array_merge([
483
            'year' => $m['year'],
484
            'month' => '01',
485 19
            'day' => '01',
486
        ], $m);
487 19
488
        if (!checkdate($parts['month'], $parts['day'], $parts['year'])) {
489 19
            throw new \InvalidArgumentException('Invalid date: '.$date);
490
        }
491
        $this->date = $date;
492 19
493
        return $this;
494 19
    }
495 19
496 1
    public function setSpecific($specific)
497
    {
498
        $this->specific = $specific;
499 18
500 18
        return $this;
501 18
    }
502 18
503 18
    public function setFragment($fragment)
504
    {
505 18
        $this->fragment = $fragment ?: '';
506 1
507
        return $this;
508 17
    }
509
510 17
    private function extractHost($authorityName)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
511
    {
512
        $parts = explode('@', $authorityName);
513 17
514
        return end($parts);
515 17
    }
516
517 17
    public static function createFromString($string)
518
    {
519
        $overallRegex = self::REGEX_OVERALL;
520 17
        if (!preg_match("/{$overallRegex}/", $string, $overall)) {
521
            throw new \InvalidArgumentException("The provided tag URI doesn't seem to be valid: {$string}");
522 17
        }
523
524 17
        $taggingEntity = self::getTaggingEntityRegex();
525
        if (preg_match("/^{$taggingEntity}/", $overall['taggingEntity'], $m) !== 1) {
526
            throw new \InvalidArgumentException('Invalid taggingEntity: '.$overall['taggingEntity']);
527
        }
528
529
        $tagUri = (new TagUri())
530
            ->setAuthorityName($m['authorityName'])
531
            ->setDate($m['date'])
532
            ->setSpecific($overall['specific'])
533
            ->setFragment($overall['fragment']);
534 21
535
        return $tagUri;
536 21
    }
537 21
538 1
    private function checkEmail($email)
0 ignored issues
show
Coding Style introduced by
function checkEmail() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
539
    {
540
        $validator = new EmailValidator();
541 21
        if (!$validator->isValid($email)) {
542 21
            throw new \InvalidArgumentException("Invalid authority name: '{$email}'. It doesn't seem to be a valid email address.");
543 2
        }
544
545
        return true;
546 19
    }
547
}
548