Browse Source

Apply house style

master
J. King 4 years ago
parent
commit
2e9598b18b
  1. 3
      .gitignore
  2. 79
      .php_cs.dist
  3. 14
      RoboFile.php
  4. 2
      lib/Category/Category.php
  5. 10
      lib/Category/Collection.php
  6. 19
      lib/Collection.php
  7. 2
      lib/Date.php
  8. 6
      lib/Enclosure/Collection.php
  9. 4
      lib/Entry.php
  10. 8
      lib/Exception.php
  11. 22
      lib/Feed.php
  12. 2
      lib/Metadata.php
  13. 9
      lib/Parser/Construct.php
  14. 2
      lib/Parser/Entry.php
  15. 2
      lib/Parser/Exception.php
  16. 2
      lib/Parser/Feed.php
  17. 2
      lib/Parser/JSON/Construct.php
  18. 10
      lib/Parser/JSON/Entry.php
  19. 13
      lib/Parser/JSON/Feed.php
  20. 34
      lib/Parser/XML/Construct.php
  21. 8
      lib/Parser/XML/Entry.php
  22. 22
      lib/Parser/XML/Feed.php
  23. 22
      lib/Parser/XML/Primitives/Construct.php
  24. 9
      lib/Parser/XML/Primitives/Entry.php
  25. 7
      lib/Parser/XML/Primitives/Feed.php
  26. 12
      lib/Person/Collection.php
  27. 2
      lib/Person/Person.php
  28. 32
      lib/Sanitizer.php
  29. 2
      lib/Schedule.php
  30. 2
      lib/Text.php
  31. 16
      lib/Url.php
  32. 7
      tests/cases/JSON/JSONTest.php
  33. 118
      tests/cases/Util/Url/AbstractUriTestCase.php
  34. 2
      tests/cases/Util/UrlTest.php
  35. 5
      vendor-bin/csfixer/composer.json
  36. 1383
      vendor-bin/csfixer/composer.lock

3
.gitignore

@ -1,4 +1,5 @@
/vendor /vendor
/vendor-bin/*/vendor /vendor-bin/*/vendor
/tests/coverage /tests/coverage
/tests/.phpunit.result.cache /tests/.phpunit.result.cache
/.php_cs.cache

79
.php_cs.dist

@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
const BASE = __DIR__.DIRECTORY_SEPARATOR;
$paths = [
__FILE__,
BASE."RoboFile.php",
BASE."lib",
BASE."tests",
];
$rules = [
// house rules where PSR series is silent
'align_multiline_comment' => ['comment_type' => "phpdocs_only"],
'array_syntax' => ['syntax' => "short"],
'binary_operator_spaces' => [
'default' => "single_space",
'operators' => ['=>' => "align_single_space"],
],
'cast_spaces' => ['space' => "single"],
'concat_space' => ['spacing' => "none"],
'list_syntax' => ['syntax' => "short"],
'magic_constant_casing' => true,
'magic_method_casing' => true,
'modernize_types_casting' => true,
'native_function_casing' => true,
'native_function_type_declaration_casing' => true,
'no_binary_string' => true,
'no_blank_lines_after_phpdoc' => true,
'no_empty_comment' => true,
'no_empty_phpdoc' => true,
'no_extra_blank_lines' => true, // this could probably use more configuration
'no_mixed_echo_print' => ['use' => "echo"],
'no_short_bool_cast' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_unneeded_control_parentheses' => true,
'no_unneeded_curly_braces' => true,
'no_unused_imports' => true,
'no_whitespace_before_comma_in_array' => true,
'normalize_index_brace' => true,
'object_operator_without_whitespace' => true,
'pow_to_exponentiation' => true,
'set_type_to_cast' => true,
'standardize_not_equals' => true,
'trailing_comma_in_multiline_array' => true,
'unary_operator_spaces' => true,
'yoda_style' => false,
// PSR standard to apply
'@PSR2' => true,
// PSR-12 rules; php-cs-fixer does not yet support PSR-12 natively
'compact_nullable_typehint' => true,
'declare_equal_normalize' => ['space' => "none"],
'function_typehint_space' => true,
'lowercase_cast' => true,
'lowercase_static_reference' => true,
'no_alternative_syntax' => true,
'no_empty_statement' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_whitespace_in_blank_line' => true,
'return_type_declaration' => ['space_before' => "none"],
'single_trait_insert_per_statement' => true,
'short_scalar_cast' => true,
'visibility_required' => ['elements' => ["const", "property", "method"]],
// house exceptions to PSR rules
'braces' => ['position_after_functions_and_oop_constructs' => "same"],
'function_declaration' => ['closure_function_spacing' => "none"],
'new_with_braces' => false, // no option to specify absence of braces
];
$finder = \PhpCsFixer\Finder::create();
foreach ($paths as $path) {
if (is_file($path)) {
$finder = $finder->append([$path]);
} else {
$finder = $finder->in($path);
}
}
return \PhpCsFixer\Config::create()->setRiskyAllowed(true)->setRules($rules)->setFinder($finder);

14
RoboFile.php

@ -24,7 +24,7 @@ class RoboFile extends \Robo\Tasks {
* ./robo test --testsuite TTRSS --exclude-group slow --testdox * ./robo test --testsuite TTRSS --exclude-group slow --testdox
* *
* Please see the PHPUnit documentation for available options. * Please see the PHPUnit documentation for available options.
*/ */
public function test(array $args): Result { public function test(array $args): Result {
return $this->runTests(escapeshellarg(\PHP_BINARY), "typical", $args); return $this->runTests(escapeshellarg(\PHP_BINARY), "typical", $args);
} }
@ -33,7 +33,7 @@ class RoboFile extends \Robo\Tasks {
* *
* This includes pedantic tests which may help to identify problems. * This includes pedantic tests which may help to identify problems.
* See help for the "test" task for more details. * See help for the "test" task for more details.
*/ */
public function testFull(array $args): Result { public function testFull(array $args): Result {
return $this->runTests(escapeshellarg(\PHP_BINARY), "full", $args); return $this->runTests(escapeshellarg(\PHP_BINARY), "full", $args);
} }
@ -42,7 +42,7 @@ class RoboFile extends \Robo\Tasks {
* Runs a quick subset of the test suite * Runs a quick subset of the test suite
* *
* See help for the "test" task for more details. * See help for the "test" task for more details.
*/ */
public function testQuick(array $args): Result { public function testQuick(array $args): Result {
return $this->runTests(escapeshellarg(\PHP_BINARY), "quick", $args); return $this->runTests(escapeshellarg(\PHP_BINARY), "quick", $args);
} }
@ -54,9 +54,9 @@ class RoboFile extends \Robo\Tasks {
* arguments to this task as one would to PHPUnit. * arguments to this task as one would to PHPUnit.
* *
* Robo first tries to use pcov and will fall back first to xdebug then * Robo first tries to use pcov and will fall back first to xdebug then
* phpdbg. Neither pcov nor xdebug need to be enabled to be used; they * phpdbg. Neither pcov nor xdebug need to be enabled to be used; they
* only need to be present in the extension load path to be used. * only need to be present in the extension load path to be used.
*/ */
public function coverage(array $args): Result { public function coverage(array $args): Result {
// run tests with code coverage reporting enabled // run tests with code coverage reporting enabled
$exec = $this->findCoverageEngine(); $exec = $this->findCoverageEngine();
@ -71,7 +71,7 @@ class RoboFile extends \Robo\Tasks {
* run all tests which may cover code. * run all tests which may cover code.
* *
* See also help for the "coverage" task for more details. * See also help for the "coverage" task for more details.
*/ */
public function coverageFull(array $args): Result { public function coverageFull(array $args): Result {
// run tests with code coverage reporting enabled // run tests with code coverage reporting enabled
$exec = $this->findCoverageEngine(); $exec = $this->findCoverageEngine();
@ -121,7 +121,7 @@ class RoboFile extends \Robo\Tasks {
return $all ? ">$hole 2>&1" : "2>$hole"; return $all ? ">$hole 2>&1" : "2>$hole";
} }
protected function runTests(string $executor, string $set, array $args) : Result { protected function runTests(string $executor, string $set, array $args): Result {
switch ($set) { switch ($set) {
case "typical": case "typical":
$set = ["--exclude-group", "optional"]; $set = ["--exclude-group", "optional"];

2
lib/Category/Category.php

@ -9,7 +9,7 @@ namespace JKingWeb\Lax\Category;
class Category { class Category {
public $name = ""; public $name = "";
public $label = ""; public $label = "";
public $domain = ""; public $domain = "";
public function __toString() { public function __toString() {
return strlen(strlen((string) $this->label)) ? $this->label : $this->name; return strlen(strlen((string) $this->label)) ? $this->label : $this->name;

10
lib/Category/Collection.php

@ -8,16 +8,16 @@ namespace JKingWeb\Lax\Category;
class Collection extends \JKingWeb\Lax\Collection { class Collection extends \JKingWeb\Lax\Collection {
protected static $ranks = [ protected static $ranks = [
'webmaster' => 10, 'webmaster' => 10,
'editor' => 20, 'editor' => 20,
'contributor' => 30, 'contributor' => 30,
'author' => 40, 'author' => 40,
]; ];
/** Returns the collection formatted as an array of strings /** Returns the collection formatted as an array of strings
* *
* The $humanFriendly parameter controls whether or not an effort is made to return human-friendly category names. Only Atom categories have this distinction * The $humanFriendly parameter controls whether or not an effort is made to return human-friendly category names. Only Atom categories have this distinction
* *
*/ */
public function list(bool $humanFriendly = true) { public function list(bool $humanFriendly = true) {
$out = []; $out = [];

19
lib/Collection.php

@ -7,7 +7,6 @@ declare(strict_types=1);
namespace JKingWeb\Lax; namespace JKingWeb\Lax;
abstract class Collection implements \IteratorAggregate, \ArrayAccess, \Countable, \JsonSerializable { abstract class Collection implements \IteratorAggregate, \ArrayAccess, \Countable, \JsonSerializable {
protected $data = []; protected $data = [];
/** Implementation for IteratorAggregate */ /** Implementation for IteratorAggregate */
@ -49,10 +48,10 @@ abstract class Collection implements \IteratorAggregate, \ArrayAccess, \Countabl
unset($this->data[$offset]); unset($this->data[$offset]);
} }
/** Merges one or more other collections' items into this one /** Merges one or more other collections' items into this one
* *
* The returned collection is the original instance, modified * The returned collection is the original instance, modified
*/ */
public function merge(Collection ...$coll): self { public function merge(Collection ...$coll): self {
foreach ($coll as $c) { foreach ($coll as $c) {
foreach ($c as $p) { foreach ($c as $p) {
@ -62,14 +61,14 @@ abstract class Collection implements \IteratorAggregate, \ArrayAccess, \Countabl
return $this; return $this;
} }
/** Returns a collection filtered along a given axis which includes or excludes only the specified terms /** Returns a collection filtered along a given axis which includes or excludes only the specified terms
* *
* $terms is the list of values to include or exclude in the result * $terms is the list of values to include or exclude in the result
* *
* $axis is the property of each collection member which value is to be checked against the terms * $axis is the property of each collection member which value is to be checked against the terms
* *
* $inclusive specified whether the terms are to included in (true) or excluded from (false) the result * $inclusive specified whether the terms are to included in (true) or excluded from (false) the result
*/ */
protected function filter(array $terms, string $axis, bool $inclusive): self { protected function filter(array $terms, string $axis, bool $inclusive): self {
$out = new static; $out = new static;
foreach ($this as $item) { foreach ($this as $item) {

2
lib/Date.php

@ -7,7 +7,7 @@ declare(strict_types=1);
namespace JKingWeb\Lax; namespace JKingWeb\Lax;
class Date extends \DateTimeImmutable implements \JsonSerializable { class Date extends \DateTimeImmutable implements \JsonSerializable {
static public $supportedFormats = [ public static $supportedFormats = [
// RFC 3339 formats // RFC 3339 formats
'Y-m-d\TH:i:s.u\Z', 'Y-m-d\TH:i:s.u\Z',
'Y-m-d\TH:i:s.u\z', 'Y-m-d\TH:i:s.u\z',

6
lib/Enclosure/Collection.php

@ -9,11 +9,11 @@ namespace JKingWeb\Lax\Enclosure;
class Collection extends \JKingWeb\Lax\Collection { class Collection extends \JKingWeb\Lax\Collection {
/** Returns the primary ("best") enclosure of the collection /** Returns the primary ("best") enclosure of the collection
* *
* Videos are preferred over audios, which are preferred over images, which are preferred over anything else * Videos are preferred over audios, which are preferred over images, which are preferred over anything else
* *
* Videos are first ranked by length, then resolution (total pixels), then bitrate, then size; audios are ranked by length, then bitrate, then size; images are ranked by resolution (total pixels), then size * Videos are first ranked by length, then resolution (total pixels), then bitrate, then size; audios are ranked by length, then bitrate, then size; images are ranked by resolution (total pixels), then size
* *
*/ */
public function primary(): ?Enclosure { public function primary(): ?Enclosure {
# stub # stub

4
lib/Entry.php

@ -12,7 +12,7 @@ use JKingWeb\Lax\Enclosure\Collection as EnclosureCollection;
class Entry { class Entry {
/** @var string $id The persistent identifier of the entry. This is often identical to the URL of the entry, but the latter may change /** @var string $id The persistent identifier of the entry. This is often identical to the URL of the entry, but the latter may change
* *
* While identifiers are usually supposed to be globally unique, in practice they are frequently only unique within the context of a particular newsfeed * While identifiers are usually supposed to be globally unique, in practice they are frequently only unique within the context of a particular newsfeed
*/ */
public $id; public $id;
@ -25,7 +25,7 @@ class Entry {
/** @var \JKingWeb\Lax\Text $title The title of the entry */ /** @var \JKingWeb\Lax\Text $title The title of the entry */
public $title; public $title;
/** @var \JKingWeb\Lax\Text $content The content of the entry /** @var \JKingWeb\Lax\Text $content The content of the entry
* *
* This may be merely a summary or excerpt for many newsfeeds */ * This may be merely a summary or excerpt for many newsfeeds */
public $content; public $content;
/** @var \JKingWeb\Lax\Text $summary A short summary or excerpt of the entry, distinct from the content */ /** @var \JKingWeb\Lax\Text $summary A short summary or excerpt of the entry, distinct from the content */

8
lib/Exception.php

@ -9,9 +9,9 @@ namespace JKingWeb\Lax;
abstract class Exception extends \Exception { abstract class Exception extends \Exception {
public const SYMBOLS = [ public const SYMBOLS = [
// Parsing: 0x1100 // Parsing: 0x1100
"notJSONType" => [0x1111, "Document Content-Type is not either that of JSON Feed or generic JSON"], "notJSONType" => [0x1111, "Document Content-Type is not either that of JSON Feed or generic JSON"],
"notJSON" => [0x1112, "Document is not valid JSON"], "notJSON" => [0x1112, "Document is not valid JSON"],
"notJSONFeed" => [0x1113, "Document is not a JSON Feed document"] "notJSONFeed" => [0x1113, "Document is not a JSON Feed document"],
]; ];
public function __construct(string $symbol, \Exception $e = null) { public function __construct(string $symbol, \Exception $e = null) {
@ -19,7 +19,7 @@ abstract class Exception extends \Exception {
if (!$data) { if (!$data) {
throw new \Exception("Exception symbol \"$symbol\" does not exist"); throw new \Exception("Exception symbol \"$symbol\" does not exist");
} }
list($code, $msg) = $data; [$code, $msg] = $data;
parent::__construct($msg, $code, $e); parent::__construct($msg, $code, $e);
} }
} }

22
lib/Feed.php

@ -10,30 +10,30 @@ use JKingWeb\Lax\Category\Collection as CategoryCollection;
use JKingWeb\Lax\Person\Collection as PersonCollection; use JKingWeb\Lax\Person\Collection as PersonCollection;
/** Represents a newsfeed, in arbitrary format /** Represents a newsfeed, in arbitrary format
* *
* All properties may be null. * All properties may be null.
*/ */
class Feed { class Feed {
/** @var string $format The format of newsfeed, one of the following: /** @var string $format The format of newsfeed, one of the following:
* *
* - `rss` for RSS 0.9x or RSS 2.0.x * - `rss` for RSS 0.9x or RSS 2.0.x
* - `rdf` for RSS 1.0 * - `rdf` for RSS 1.0
* - `atom` for Atom feeds * - `atom` for Atom feeds
* - `json` for JSON Feed * - `json` for JSON Feed
* - `hfeed` for a microformat h-feeds * - `hfeed` for a microformat h-feeds
* *
* The format is largely advisory, but may be used when converting between formats * The format is largely advisory, but may be used when converting between formats
*/ */
public $format; public $format;
/** @var string $version The format version of the newsfeed /** @var string $version The format version of the newsfeed
* *
* The version is largely advisory, but may be used when converting between formats * The version is largely advisory, but may be used when converting between formats
*/ */
public $version; public $version;
/** @var string $lang The human language of the newsfeed as a whole */ /** @var string $lang The human language of the newsfeed as a whole */
public $lang; public $lang;
/** @var string $id The globally unique identifier for the newsfeed /** @var string $id The globally unique identifier for the newsfeed
* *
* For some formats, such as RSS 2.0 and JSON Feed, this may be he same as the newsfeed URL * For some formats, such as RSS 2.0 and JSON Feed, this may be he same as the newsfeed URL
*/ */
public $id; public $id;
@ -46,7 +46,7 @@ class Feed {
/** @var \JKingWeb\Lax\Text $summary A short description or summary of the newsfeed */ /** @var \JKingWeb\Lax\Text $summary A short description or summary of the newsfeed */
public $summary; public $summary;
/** @var \JKingWeb\Lax\Date $dateModified The date at which the newsfeed was last modified /** @var \JKingWeb\Lax\Date $dateModified The date at which the newsfeed was last modified
* *
* This property only records a date embedded in the newsfeed itself, not any dates from HTTP or the file system * This property only records a date embedded in the newsfeed itself, not any dates from HTTP or the file system
*/ */
public $dateModified; public $dateModified;
@ -54,7 +54,7 @@ class Feed {
public $icon; public $icon;
/** @var \JKingWeb\Lax\Url $image URL to a large banner or poster image for the newsfeed */ /** @var \JKingWeb\Lax\Url $image URL to a large banner or poster image for the newsfeed */
public $image; public $image;
/** @var \JKingWeb\Lax\Category\Collection $categories A list of categories associated with the newsfeed as a whole */ /** @var \JKingWeb\Lax\Category\Collection $categories A list of categories associated with the newsfeed as a whole */
public $categories; public $categories;
/** @var \JKingWeb\Lax\Person\Collection $people A list of people (e.g. authors, contributors) associated with the newsfeed as a whole */ /** @var \JKingWeb\Lax\Person\Collection $people A list of people (e.g. authors, contributors) associated with the newsfeed as a whole */
@ -73,14 +73,14 @@ class Feed {
$this->categories = new CategoryCollection; $this->categories = new CategoryCollection;
$this->sched = new Schedule; $this->sched = new Schedule;
} }
/** Parses a string to produce a Feed object /** Parses a string to produce a Feed object
* *
* Most users will probably rather want the Feed::fetch() method * Most users will probably rather want the Feed::fetch() method
* *
* @param string $data The newsfeed to parse * @param string $data The newsfeed to parse
* @param string|null $contentType The HTTP Content-Type of the document, if available * @param string|null $contentType The HTTP Content-Type of the document, if available
* @param string|null $url The URL used to retrieve the newsfeed, if applicable * @param string|null $url The URL used to retrieve the newsfeed, if applicable
*/ */
public static function parse(string $data, ?string $contentType = null, ?string $url = null): self { public static function parse(string $data, ?string $contentType = null, ?string $url = null): self {
$out = new self; $out = new self;

2
lib/Metadata.php

@ -14,4 +14,4 @@ class Metadata {
public $etag; public $etag;
public $expires; public $expires;
public $maxAge; public $maxAge;
} }

9
lib/Parser/Construct.php

@ -17,7 +17,7 @@ trait Construct {
} }
/** Takes an HTML string as input and returns a sanitized version of that string /** Takes an HTML string as input and returns a sanitized version of that string
* *
* The $outputHtml parameter, when false, outputs only the plain-text content of the sanitized HTML * The $outputHtml parameter, when false, outputs only the plain-text content of the sanitized HTML
*/ */
protected function sanitizeString(string $markup, bool $outputHtml = true): string { protected function sanitizeString(string $markup, bool $outputHtml = true): string {
@ -30,7 +30,7 @@ trait Construct {
} }
/** Tests whether a string is a valid e-mail address /** Tests whether a string is a valid e-mail address
* *
* Accepts IDN hosts and Unicode localparts * Accepts IDN hosts and Unicode localparts
*/ */
protected function validateMail(string $addr): bool { protected function validateMail(string $addr): bool {
@ -42,7 +42,8 @@ trait Construct {
$domain = $match[2]; $domain = $match[2];
// PHP's filter_var does not accept IDN hosts, so we have to perform an IDNA transformation first // PHP's filter_var does not accept IDN hosts, so we have to perform an IDNA transformation first
$domain = idn_to_ascii($domain, \IDNA_NONTRANSITIONAL_TO_ASCII | \IDNA_CHECK_BIDI | \IDNA_CHECK_CONTEXTJ, \INTL_IDNA_VARIANT_UTS46); // settings for IDNA2008 algorithm (I think) $domain = idn_to_ascii($domain, \IDNA_NONTRANSITIONAL_TO_ASCII | \IDNA_CHECK_BIDI | \IDNA_CHECK_CONTEXTJ, \INTL_IDNA_VARIANT_UTS46); // settings for IDNA2008 algorithm (I think)
if ($domain !== false) {$addr = "$local@$domain"; if ($domain !== false) {
$addr = "$local@$domain";
return (bool) filter_var($addr, \FILTER_VALIDATE_EMAIL, \FILTER_FLAG_EMAIL_UNICODE); return (bool) filter_var($addr, \FILTER_VALIDATE_EMAIL, \FILTER_FLAG_EMAIL_UNICODE);
} }
return false; return false;
@ -65,7 +66,7 @@ trait Construct {
protected function parseMediaType(string $type, ?Url $url = null): ?string { protected function parseMediaType(string $type, ?Url $url = null): ?string {
if (preg_match('<^\s*([0-9a-z]+(?:/[!#$%&\'\*\+\-\.^_`|~0-9a-z]+)?)(?:\s|;|,|$)>i', $type, $match)) { if (preg_match('<^\s*([0-9a-z]+(?:/[!#$%&\'\*\+\-\.^_`|~0-9a-z]+)?)(?:\s|;|,|$)>i', $type, $match)) {
/* NOTE: The pattern used here is a subset of what is /* NOTE: The pattern used here is a subset of what is
technically allowed by RFC 7231: the "type" portion technically allowed by RFC 7231: the "type" portion
is supposed to be as general as the "subtype" portion, is supposed to be as general as the "subtype" portion,
but in practice only alphabetic types have ever been but in practice only alphabetic types have ever been

2
lib/Parser/Entry.php

@ -42,7 +42,7 @@ interface Entry {
public function getDateModified(): ?Date; public function getDateModified(): ?Date;
/** Returns the URL of a large image used as a banner when displaying the entry /** Returns the URL of a large image used as a banner when displaying the entry
* *
* This is only used by JSON Feed entries * This is only used by JSON Feed entries
*/ */
public function getBanner(): ?Url; public function getBanner(): ?Url;

2
lib/Parser/Exception.php

@ -9,4 +9,4 @@ namespace JKingWeb\Lax\Parser;
use JKingWeb\Lax\Exception as BaseException; use JKingWeb\Lax\Exception as BaseException;
class Exception extends BaseException { class Exception extends BaseException {
} }

2
lib/Parser/Feed.php

@ -48,7 +48,7 @@ interface Feed {
public function getPeople(): PersonCollection; public function getPeople(): PersonCollection;
/** Returns the list of entries /** Returns the list of entries
* *
* @param \JKingWeb\Lax\Feed $feed The newsfeed to which the entry belongs. Some data from the newsfeed may be used in parsing the entry * @param \JKingWeb\Lax\Feed $feed The newsfeed to which the entry belongs. Some data from the newsfeed may be used in parsing the entry
*/ */
public function getEntries(FeedStruct $feed = null): array; public function getEntries(FeedStruct $feed = null): array;

2
lib/Parser/JSON/Construct.php

@ -16,7 +16,7 @@ trait Construct {
use \JKingWeb\Lax\Parser\Construct; use \JKingWeb\Lax\Parser\Construct;
/** Returns an object member if the member exists and is of the expected type /** Returns an object member if the member exists and is of the expected type
* *
* Returns null otherwise * Returns null otherwise
*/ */
protected function fetchMember(string $key, string $type, ?\stdClass $obj = null) { protected function fetchMember(string $key, string $type, ?\stdClass $obj = null) {

10
lib/Parser/JSON/Entry.php

@ -51,7 +51,7 @@ class Entry implements \JKingWeb\Lax\Parser\Entry {
$entry->people = $this->getPeople(); $entry->people = $this->getPeople();
$entry->categories = $this->getCategories(); $entry->categories = $this->getCategories();
$entry->enclosures = $this->getEnclosures(); $entry->enclosures = $this->getEnclosures();
return $entry; return $entry;
} }
public function getId(): ?string { public function getId(): ?string {
@ -69,7 +69,7 @@ class Entry implements \JKingWeb\Lax\Parser\Entry {
$exp = (int) $id[1]; $exp = (int) $id[1];
$mul = $exp > -1; $mul = $exp > -1;
$exp = abs($exp); $exp = abs($exp);
list($int, $dec) = explode(".", $id[0]); [$int, $dec] = explode(".", $id[0]);
$dec = strlen($dec) ? str_split($dec, 1) : []; $dec = strlen($dec) ? str_split($dec, 1) : [];
$int = str_split($int, 1); $int = str_split($int, 1);
if ($int[0] === "-") { if ($int[0] === "-") {
@ -89,7 +89,7 @@ class Entry implements \JKingWeb\Lax\Parser\Entry {
array_unshift($dec, "0"); array_unshift($dec, "0");
} }
} }
return ($neg ? "-" : "") . ($int ? implode("", $int) : "0") . ($dec ? ("." . rtrim(implode("", $dec), "0")) : ""); return ($neg ? "-" : "").($int ? implode("", $int) : "0").($dec ? (".".rtrim(implode("", $dec), "0")) : "");
} }
} }
} else { } else {
@ -156,9 +156,9 @@ class Entry implements \JKingWeb\Lax\Parser\Entry {
$out[] = $m; $out[] = $m;
} }
// handle other attachments // handle other attachments
foreach ($this->fetchMember("attachments", "array") ?? [] as $attachment) { foreach ($this->fetchMember("attachments", "array") ?? [] as $attachment) {
$url = $this->fetchUrl("url", $attachment); $url = $this->fetchUrl("url", $attachment);
if ($url) { if ($url) {
$m = new Enclosure; $m = new Enclosure;
$m->url = $url; $m->url = $url;
$m->type = $this->fetchType("mime_type", $url, $attachment); $m->type = $this->fetchType("mime_type", $url, $attachment);

13
lib/Parser/JSON/Feed.php

@ -8,7 +8,6 @@ namespace JKingWeb\Lax\Parser\JSON;
use JKingWeb\Lax\Text; use JKingWeb\Lax\Text;
use JKingWeb\Lax\Date; use JKingWeb\Lax\Date;
use JKingWeb\Lax\Entry;
use JKingWeb\Lax\Feed as FeedStruct; use JKingWeb\Lax\Feed as FeedStruct;
use JKingWeb\Lax\Person\Collection as PersonCollection; use JKingWeb\Lax\Person\Collection as PersonCollection;
use JKingWeb\Lax\Category\Collection as CategoryCollection; use JKingWeb\Lax\Category\Collection as CategoryCollection;
@ -82,9 +81,9 @@ class Feed implements \JKingWeb\Lax\Parser\Feed {
} }
/** {@inheritdoc} /** {@inheritdoc}
* *
* For JSON feeds this is always the feed URL specified in the feed * For JSON feeds this is always the feed URL specified in the feed
*/ */
public function getId(): ?string { public function getId(): ?string {
return $this->fetchMember("feed_url", "str"); return $this->fetchMember("feed_url", "str");
} }
@ -106,9 +105,9 @@ class Feed implements \JKingWeb\Lax\Parser\Feed {
} }
/** {@inheritdoc} /** {@inheritdoc}
* *
* JSON feeds themselves don't have dates, so this always returns null * JSON feeds themselves don't have dates, so this always returns null
*/ */
public function getDateModified(): ?Date { public function getDateModified(): ?Date {
return null; return null;
} }
@ -118,9 +117,9 @@ class Feed implements \JKingWeb\Lax\Parser\Feed {
} }
/** {@inheritdoc} /** {@inheritdoc}
* *
* JSON Feed does not have categories at the feed level, so this always returns and empty collection * JSON Feed does not have categories at the feed level, so this always returns and empty collection
*/ */
public function getCategories(): CategoryCollection { public function getCategories(): CategoryCollection {
return new CategoryCollection; return new CategoryCollection;
} }

34
lib/Parser/XML/Construct.php

@ -15,7 +15,7 @@ trait Construct {
use \JKingWeb\Lax\Parser\Construct; use \JKingWeb\Lax\Parser\Construct;
/** @var \DOMDocument */ /** @var \DOMDocument */
public $document; protected $document;
/** @var \DOMXPath */ /** @var \DOMXPath */
protected $xpath; protected $xpath;
/** @var \DOMElement */ /** @var \DOMElement */
@ -24,7 +24,7 @@ trait Construct {
/** Retrieves an element node based on an XPath query */ /** Retrieves an element node based on an XPath query */
protected function fetchElement(string $query, \DOMNode $context = null) { protected function fetchElement(string $query, \DOMNode $context = null) {
$node = @$this->xpath->query("(".$query.")[1]", $context ?? $this->subject); $node = @$this->xpath->query("(".$query.")[1]", $context ?? $this->subject);
if ($node===false) { if ($node === false) {
throw new \Exception("Invalid XPath query: $query"); // @codeCoverageIgnore throw new \Exception("Invalid XPath query: $query"); // @codeCoverageIgnore
} }
return ($node->length) ? $node->item(0) : null; return ($node->length) ? $node->item(0) : null;
@ -55,12 +55,12 @@ trait Construct {
protected function fetchStringAtom(string $query, bool $html = false): ?Text { protected function fetchStringAtom(string $query, bool $html = false): ?Text {
$node = $this->fetchElement($query); $node = $this->fetchElement($query);
if ($node) { if ($node) {
if (!$node->hasAttribute("type") || $node->getAttribute("type")=="text") { if (!$node->hasAttribute("type") || $node->getAttribute("type") == "text") {
return $html ? htmlspecialchars($this->trimText($node->textContent), \ENT_QUOTES | \ENT_HTML5) : $this->trimText($node->textContent); return $html ? htmlspecialchars($this->trimText($node->textContent), \ENT_QUOTES | \ENT_HTML5) : $this->trimText($node->textContent);
} elseif ($node->getAttribute("type")=="xhtml") { } elseif ($node->getAttribute("type") == "xhtml") {
$node = $node->getElementsByTagNameNS(self::NS['xhtml'], "div")->item(0); $node = $node->getElementsByTagNameNS(self::NS['xhtml'], "div")->item(0);
return $node ? $this->sanitizeElement($node, $html) : null; return $node ? $this->sanitizeElement($node, $html) : null;
} elseif ($node->getAttribute("type")=="html") { } elseif ($node->getAttribute("type") == "html") {
return $this->sanitizeString($node->textContent, $html); return $this->sanitizeString($node->textContent, $html);
} else { } else {
return null; return null;
@ -76,19 +76,19 @@ trait Construct {
} }
/** Returns a node-list of Atom link elements with the desired relation or equivalents. /** Returns a node-list of Atom link elements with the desired relation or equivalents.
* *
* Links without an href attribute are excluded. * Links without an href attribute are excluded.
* *
* @see https://tools.ietf.org/html/rfc4287#section-4.2.7.2 * @see https://tools.ietf.org/html/rfc4287#section-4.2.7.2
*/ */
protected function fetchAtomRelations(string $rel = ""): \DOMNodeList { protected function fetchAtomRelations(string $rel = ""): \DOMNodeList {
// FIXME: The XPath evaluation will fail if the relation contains an apostrophe. This is a known and difficult-to-overcome limitation of XPath 1.0 which I consider not worth the effort to address at this time // FIXME: The XPath evaluation will fail if the relation contains an apostrophe. This is a known and difficult-to-overcome limitation of XPath 1.0 which I consider not worth the effort to address at this time
if ($rel=="" || $rel=="alternate" || $rel=="http://www.iana.org/assignments/relation/alternate") { if ($rel == "" || $rel == "alternate" || $rel == "http://www.iana.org/assignments/relation/alternate") {
$cond = "not(@rel) or @rel='' or @rel='alternate' or @rel='http://www.iana.org/assignments/relation/alternate'"; $cond = "not(@rel) or @rel='' or @rel='alternate' or @rel='http://www.iana.org/assignments/relation/alternate'";
} elseif (strpos($rel, ":")===false) { } elseif (strpos($rel, ":") === false) {
// FIXME: Checking only for a colon in a link relation is a hack that does not strictly follow IRI rules, but it's adequate for our needs // FIXME: Checking only for a colon in a link relation is a hack that does not strictly follow IRI rules, but it's adequate for our needs
$cond = "@rel='$rel' or @rel='http://www.iana.org/assignments/relation/$rel'"; $cond = "@rel='$rel' or @rel='http://www.iana.org/assignments/relation/$rel'";
} elseif (strlen($rel) > 41 && strpos($rel, "http://www.iana.org/assignments/relation/")===0) { } elseif (strlen($rel) > 41 && strpos($rel, "http://www.iana.org/assignments/relation/") === 0) {
$rel = substr($rel, 41); $rel = substr($rel, 41);
$cond = "@rel='$rel' or @rel='http://www.iana.org/assignments/relation/$rel'"; $cond = "@rel='$rel' or @rel='http://www.iana.org/assignments/relation/$rel'";
} else { } else {
@ -98,11 +98,11 @@ trait Construct {
} }
/** Finds and parses RSS person-texts and returns a collection of person objects /** Finds and parses RSS person-texts and returns a collection of person objects
* *
* Each can have a name, e-mail address, or both * Each can have a name, e-mail address, or both
* *
* The following forms will yield both a name and address: * The following forms will yield both a name and address:
* *
* - user@example.com (Full Name) * - user@example.com (Full Name)
* - Full Name <user@example.com> * - Full Name <user@example.com>
*/ */
@ -157,12 +157,12 @@ trait Construct {
return count($out) ? $out : null; return count($out) ? $out : null;
} }
/** Resolves a URL contained in a DOM element's atrribute or text /** Resolves a URL contained in a DOM element's atrribute or text
* *
* This automatically performs xml:base and HTML <base> resolution * This automatically performs xml:base and HTML <base> resolution
* *
* Specifying the empty string for $attr results in the element content being used as a URL * Specifying the empty string for $attr results in the element content being used as a URL
*/ */
protected function resolveNodeUrl(\DOMElement $node = null, string $attr = "", string $ns = null): string { protected function resolveNodeUrl(\DOMElement $node = null, string $attr = "", string $ns = null): string {
$base = $node->baseURI; $base = $node->baseURI;
$url = strlen($attr) ? $node->getAttributeNS($ns, $attr) : $this->trimText($node->textContent); $url = strlen($attr) ? $node->getAttributeNS($ns, $attr) : $this->trimText($node->textContent);

8
lib/Parser/XML/Entry.php

@ -45,9 +45,9 @@ class Entry implements \JKingWeb\Lax\Parser\Entry {
$entry->title = strlen($this->title) ? $this->title : $this->link; $entry->title = strlen($this->title) ? $this->title : $this->link;
// do extra stuff just to test it // do extra stuff just to test it
$entry->categories = $this->getCategories(); $entry->categories = $this->getCategories();
return $entry; return $entry;
} }
/** General function to fetch the entry title */ /** General function to fetch the entry title */
public function getTitle(): ?Text { public function getTitle(): ?Text {
return $this->getTitleAtom() ?? $this->getTitleRss1() ?? $this->getTitleRss2() ?? $this->getTitleDC() ?? $this->getTitlePod() ?? ""; return $this->getTitleAtom() ?? $this->getTitleRss1() ?? $this->getTitleRss2() ?? $this->getTitleDC() ?? $this->getTitlePod() ?? "";
@ -86,7 +86,7 @@ class Entry implements \JKingWeb\Lax\Parser\Entry {
} }
/** General function to fetch the URL of a article related to the entry /** General function to fetch the URL of a article related to the entry
* *
* This is only reliable with Atom feeds * This is only reliable with Atom feeds
*/ */
public function getRelatedLink(): ?Url { public function getRelatedLink(): ?Url {
@ -102,7 +102,7 @@ class Entry implements \JKingWeb\Lax\Parser\Entry {
} }
public function getEnclosures(): EnclosureCollection { public function getEnclosures(): EnclosureCollection {
return new EnclosureCollection; return new EnclosureCollection;
} }
public function getLang(): ?string { public function getLang(): ?string {

22
lib/Parser/XML/Feed.php

@ -24,8 +24,6 @@ class Feed implements \JKingWeb\Lax\Parser\Feed {
protected $contentType; protected $contentType;
/** @var \JKingWeb\Lax\Url */ /** @var \JKingWeb\Lax\Url */
protected $url; protected $url;
/** @var \DOMDocument */
protected $document;
/** @var \DOMElement */ /** @var \DOMElement */
protected $subject; protected $subject;
/** @var \DOMXpath */ /** @var \DOMXpath */
@ -49,23 +47,23 @@ class Feed implements \JKingWeb\Lax\Parser\Feed {
$this->subject = $this->document->documentElement; $this->subject = $this->document->documentElement;
$ns = $this->subject->namespaceURI; $ns = $this->subject->namespaceURI;
$name = $this->subject->localName; $name = $this->subject->localName;
if (is_null($ns) && $name=="rss") { if (is_null($ns) && $name === "rss") {
$this->subject = $this->fetchElement("channel") ?? $this->subject; $this->subject = $this->fetchElement("channel") ?? $this->subject;
$feed->format = "rss"; $feed->format = "rss";
$feed->version = $this->document->documentElement->getAttribute("version"); $feed->version = $this->document->documentElement->getAttribute("version");
} elseif ($ns==XPath::NS['rdf'] && $name=="RDF") { } elseif ($ns === XPath::NS['rdf'] && $name === "RDF") {
$feed->format = "rdf"; $feed->format = "rdf";
$channel = $this->fetchElement("rss1:channel|rss0:channel"); $channel = $this->fetchElement("rss1:channel|rss0:channel");
if ($channel) { if ($channel) {
$this->subject = $channel; $this->subject = $channel;
$feed->version = ($channel->namespaceURI==XPath::NS['rss1']) ? "1.0" : "0.90"; $feed->version = ($channel->namespaceURI === XPath::NS['rss1']) ? "1.0" : "0.90";
} else { } else {
$element = $this->fetchElement("rss1:item|rss0:item|rss1:image|rss0:image"); $element = $this->fetchElement("rss1:item|rss0:item|rss1:image|rss0:image");
if ($element) { if ($element) {
$feed->version = ($element->namespaceURI==XPath::NS['rss1']) ? "1.0" : "0.90"; $feed->version = ($element->namespaceURI === XPath::NS['rss1']) ? "1.0" : "0.90";
} }
} }
} elseif ($ns==XPath::NS['atom'] && $name=="feed") { } elseif ($ns === XPath::NS['atom'] && $name === "feed") {
$feed->format = "atom"; $feed->format = "atom";
$feed->version = "1.0"; $feed->version = "1.0";
} else { } else {
@ -98,11 +96,11 @@ class Feed implements \JKingWeb\Lax\Parser\Feed {
public function getId(): ?string { public function getId(): ?string {
return $this->getIdAtom() ?? $this->getIdDC() ?? $this->getIdRss2() ?? ""; return $this->getIdAtom() ?? $this->getIdDC() ?? $this->getIdRss2() ?? "";
} }
public function getUrl(): ?Url { public function getUrl(): ?Url {
return $this->getUrlAtom() ?? $this->getUrlRss1() ?? $this->getUrlPod() ?? $this->reqUrl; return $this->getUrlAtom() ?? $this->getUrlRss1() ?? $this->getUrlPod() ?? $this->reqUrl;
} }
public function getTitle(): ?Text { public function getTitle(): ?Text {
return $this->getTitleAtom() ?? $this->getTitleRss1() ?? $this->getTitleRss2() ?? $this->getTitleDC() ?? $this->getTitlePod() ?? ""; return $this->getTitleAtom() ?? $this->getTitleRss1() ?? $this->getTitleRss2() ?? $this->getTitleDC() ?? $this->getTitlePod() ?? "";
} }

22
lib/Parser/XML/Primitives/Construct.php

@ -90,9 +90,9 @@ trait Construct {
} }
/** Primitive to fetch Dublin Core feed/entry categories /** Primitive to fetch Dublin Core feed/entry categories
* *
* Dublin Core doesn't have an obvious category type, so we use 'subject' as a nearest approximation * Dublin Core doesn't have an obvious category type, so we use 'subject' as a nearest approximation
*/ */
protected function getCategoriesDC(): ?CategoryCollection { protected function getCategoriesDC(): ?CategoryCollection {
$out = new CategoryCollection; $out = new CategoryCollection;
foreach ($this->fetchStringMulti("dc:subject") ?? [] as $text) { foreach ($this->fetchStringMulti("dc:subject") ?? [] as $text) {
@ -123,10 +123,10 @@ trait Construct {
return $this->fetchString("atom:id"); return $this->fetchString("atom:id");
} }
/** Primitive to fetch an RSS feed/entry identifier /** Primitive to fetch an RSS feed/entry identifier
* *
* Using RSS' <guid> for feed identifiers is non-standard, but harmless * Using RSS' <guid> for feed identifiers is non-standard, but harmless
*/ */
protected function getIdRss2(): ?string { protected function getIdRss2(): ?string {
return $this->fetchString("guid"); return $this->fetchString("guid");
} }
@ -166,10 +166,10 @@ trait Construct {
return $this->fetchPeopleAtom("atom:contributor", "contributor"); return $this->fetchPeopleAtom("atom:contributor", "contributor");
} }
/** Primitive to fetch a collection of authors associated with a podcast/episode /** Primitive to fetch a collection of authors associated with a podcast/episode
* *
* The collection only ever contains the first author found: podcasts implicitly have only one author * The collection only ever contains the first author found: podcasts implicitly have only one author
*/ */
protected function getAuthorsPod(): ?PersonCollection { protected function getAuthorsPod(): ?PersonCollection {
$out = new PersonCollection; $out = new PersonCollection;
$p = new Person; $p = new Person;
@ -182,10 +182,10 @@ trait Construct {
return count($out) ? $out : null; return count($out) ? $out : null;
} }
/** Primitive to fetch a collection of webmasters associated with a podcast /** Primitive to fetch a collection of webmasters associated with a podcast
* *
* The collection only ever contains the first webmaster found: podcasts implicitly have only one webmaster * The collection only ever contains the first webmaster found: podcasts implicitly have only one webmaster
*/ */
protected function getWebmastersPod(): ?PersonCollection { protected function getWebmastersPod(): ?PersonCollection {
$out = new PersonCollection; $out = new PersonCollection;
$node = $this->fetchElement("gplay:owner|apple:owner"); $node = $this->fetchElement("gplay:owner|apple:owner");

9
lib/Parser/XML/Primitives/Entry.php

@ -6,14 +6,13 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Lax\Parser\XML\Primitives; namespace JKingWeb\Lax\Parser\XML\Primitives;
use JKingWeb\Lax\Person\Collection as PersonCollection;
use JKingWeb\Lax\Parser\XML\XPath; use JKingWeb\Lax\Parser\XML\XPath;
trait Entry { trait Entry {
/** Primitive to fetch a collection of authors associated with an Atom entry /** Primitive to fetch a collection of authors associated with an Atom entry
* *
* This differs from feeds in that an entry's <source> element (which possibly contains metadata for the source feed) is checked for authors if the entry itself has none * This differs from feeds in that an entry's <source> element (which possibly contains metadata for the source feed) is checked for authors if the entry itself has none
*/ */
protected function getAuthorsAtom() { protected function getAuthorsAtom() {
return $this->fetchPeopleAtom("atom:author", "author") ?? $this->fetchPeopleAtom("atom:source[1]/atom:author", "author"); return $this->fetchPeopleAtom("atom:author", "author") ?? $this->fetchPeopleAtom("atom:source[1]/atom:author", "author");
} }
@ -22,7 +21,7 @@ trait Entry {
protected function getUrlRss1() { protected function getUrlRss1() {
// XPath doesn't seem to like the query we'd need for this, so it must be done the hard way. // XPath doesn't seem to like the query we'd need for this, so it must be done the hard way.
$node = $this->subject; $node = $this->subject;
if ($node->localName=="item" && ($node->namespaceURI==XPath::NS['rss1'] || $node->namespaceURI==XPath::NS['rss0']) && $node->hasAttributeNS(XPath::NS['rdf'], "about")) { if ($node->localName === "item" && ($node->namespaceURI === XPath::NS['rss1'] || $node->namespaceURI == XPath::NS['rss0']) && $node->hasAttributeNS(XPath::NS['rdf'], "about")) {
return $this->resolveNodeUrl($node, "about", XPath::NS['rdf']); return $this->resolveNodeUrl($node, "about", XPath::NS['rdf']);
} else { } else {
return null; return null;

7
lib/Parser/XML/Primitives/Feed.php

@ -6,12 +6,11 @@
declare(strict_types=1); declare(strict_types=1);
namespace JKingWeb\Lax\Parser\XML\Primitives; namespace JKingWeb\Lax\Parser\XML\Primitives;
use JKingWeb\Lax\Person\Collection as PersonCollection;
use JKingWeb\Lax\Parser\XML\XPath; use JKingWeb\Lax\Parser\XML\XPath;
trait Feed { trait Feed {
/** Primitive to fetch an Atom feed summary /** Primitive to fetch an Atom feed summary
* *
* Atom does not have a 'description' element like the RSSes, but it does have 'subtitle', which fills roughly the same function * Atom does not have a 'description' element like the RSSes, but it does have 'subtitle', which fills roughly the same function
*/ */
protected function getSummaryAtom() { protected function getSummaryAtom() {
@ -49,8 +48,8 @@ trait Feed {
$node = $this->subject; $node = $this->subject;
if ($node->hasAttributeNS(XPath::NS['rdf'], "about")) { if ($node->hasAttributeNS(XPath::NS['rdf'], "about")) {
if ( if (
($node->localName=="channel" && ($node->namespaceURI==XPath::NS['rss1'] || $node->namespaceURI==XPath::NS['rss0'])) || ($node->localName === "channel" && ($node->namespaceURI === XPath::NS['rss1'] || $node->namespaceURI === XPath::NS['rss0'])) ||
($node==$node->ownerDocument->documentElement && $node->localName=="RDF" && $node->namespaceURI==XPath::NS['rdf']) ($node === $node->ownerDocument->documentElement && $node->localName === "RDF" && $node->namespaceURI === XPath::NS['rdf'])
) { ) {
return $this->resolveNodeUrl($node, "about", XPath::NS['rdf']); return $this->resolveNodeUrl($node, "about", XPath::NS['rdf']);
} }

12
lib/Person/Collection.php

@ -9,18 +9,18 @@ namespace JKingWeb\Lax\Person;
class Collection extends \JKingWeb\Lax\Collection { class Collection extends \JKingWeb\Lax\Collection {
protected static $ranks = [ protected static $ranks = [
'contributor' => -10, 'contributor' => -10,
'webmaster' => 10, 'webmaster' => 10,
'editor' => 20, 'editor' => 20,
'author' => 30, 'author' => 30,
]; ];
/** Returns the primary person of the collection /** Returns the primary person of the collection
* *
* The primary is the first member of the highest-weight role * The primary is the first member of the highest-weight role
* *
* Roles are ranked thus: * Roles are ranked thus:
* author > contributor > editor > webmaster > (anything else) * author > contributor > editor > webmaster > (anything else)
* *
*/ */
public function primary() { public function primary() {
$out = null; $out = null;

2
lib/Person/Person.php

@ -9,7 +9,7 @@ namespace JKingWeb\Lax\Person;
class Person { class Person {
public $name = null; public $name = null;
public $mail = null; public $mail = null;
public $url = null; public $url = null;
public $role = null; public $role = null;
public $avatar = null; public $avatar = null;

32
lib/Sanitizer.php

@ -172,24 +172,24 @@ class Sanitizer {
//"autocapitalize", // not useful for static content //"autocapitalize", // not useful for static content
//"contenteditable", // not useful for static content //"contenteditable", // not useful for static content
"class", "class",
"dir", "dir",
//"draggable", // not useful for static content //"draggable", // not useful for static content
"hidden", "hidden",
//"inputmode", // not useful for static content //"inputmode", // not useful for static content
//"is", // only used with custom elements //"is", // only used with custom elements
"id", "id",
"itemid", "itemid",
"itemprop", "itemprop",
"itemref", "itemref",
"itemscope", "itemscope",
"itemtype", "itemtype",
"lang", "lang",
//"nonce", // only used via scripts (I think) //"nonce", // only used via scripts (I think)
//"slot", // only used via scripts (I think) //"slot", // only used via scripts (I think)
//"spellcheck", // not useful for static content //"spellcheck", // not useful for static content
//"style", // arbitrary styling; potentially unsafe //"style", // arbitrary styling; potentially unsafe
"tabindex", "tabindex",
"title", "title",
"translate", "translate",
// WAI-ARIA // WAI-ARIA
"aria-describedby", "aria-describedby",
@ -207,22 +207,22 @@ class Sanitizer {
"poster", "poster",
]; ];
/** /**
* Sanitizes a DOMDocument object, returning the same document, modified * Sanitizes a DOMDocument object, returning the same document, modified
* *
* The document may be an HTML document or or any partial XHTML tree, possibly mixed with other XML vocabularies * The document may be an HTML document or or any partial XHTML tree, possibly mixed with other XML vocabularies
* *
* The document's documentURI is assumed to already be set * The document's documentURI is assumed to already be set
*/ */
public function processDocument(\DOMDocument $doc, string $url): \DOMDocument { public function processDocument(\DOMDocument $doc, string $url): \DOMDocument {
// determine if the document is non-XML HTML // determine if the document is non-XML HTML
$isHtml = ($doc->documentElement->tagName=="html" && $doc->documentElement->namespaceURI==""); $isHtml = ($doc->documentElement->tagName === "html" && $doc->documentElement->namespaceURI === "");
// loop through each element in the document // loop through each element in the document
foreach ((new \DOMXPath($doc))->query("//*") as $node) { foreach ((new \DOMXPath($doc))->query("//*") as $node) {
// resolve a qualified name for the element // resolve a qualified name for the element
if (($isHtml && $node->namespaceURI=="") || $node->namespaceURI=="http://www.w3.org/1999/xhtml") { if (($isHtml && $node->namespaceURI === "") || $node->namespaceURI === "http://www.w3.org/1999/xhtml") {
$qName = "html:".$node->tagName; $qName = "html:".$node->tagName;
} elseif ($node->namespaceURI=="") { } elseif ($node->namespaceURI === "") {
$qName = $node->tagName; $qName = $node->tagName;
} elseif (isset($this->namespaces[$node->namespaceURI])) { } elseif (isset($this->namespaces[$node->namespaceURI])) {
$qName = $this->namespaces[$node->namespaceURI].":".$node->tagName; $qName = $this->namespaces[$node->namespaceURI].":".$node->tagName;
@ -244,7 +244,7 @@ class Sanitizer {
$node->parentNode->removeChild($node); $node->parentNode->removeChild($node);
} else { } else {
// if the element is in the keep list, clean up its attributes // if the element is in the keep list, clean up its attributes
foreach (iterator_to_array($node->attributes) as $attr) { // we use an array foreach (iterator_to_array($node->attributes) as $attr) { // we use an array
if (!in_array($attr->name, $this->attrKeep) && !(isset($this->elemKeep[$qName]) && in_array($attr->name, $this->elemKeep[$qName]))) { if (!in_array($attr->name, $this->attrKeep) && !(isset($this->elemKeep[$qName]) && in_array($attr->name, $this->elemKeep[$qName]))) {
// if the attribute is not allowed globally or for the element, remove it // if the attribute is not allowed globally or for the element, remove it
$attr->ownerElement->removeAttributeNode($attr); $attr->ownerElement->removeAttributeNode($attr);

2
lib/Schedule.php

@ -8,4 +8,4 @@ namespace JKingWeb\Lax;
class Schedule { class Schedule {
public $expired; public $expired;
} }

2
lib/Text.php

@ -19,4 +19,4 @@ class Text {
assert(in_array($type, ["plain", "html", "xhtml", "loose"]), new \InvalidArgumentException); assert(in_array($type, ["plain", "html", "xhtml", "loose"]), new \InvalidArgumentException);
$this->$type = $data; $this->$type = $data;
} }
} }

16
lib/Url.php

@ -9,9 +9,9 @@ namespace JKingWeb\Lax;
use Psr\Http\Message\UriInterface; use Psr\Http\Message\UriInterface;
/** Normalized URI representation, compatible with the PSR-7 URI interface /** Normalized URI representation, compatible with the PSR-7 URI interface
* *
* The following features are implemented: * The following features are implemented:
* *
* - The full PSR-7 `UriInterface` interface * - The full PSR-7 `UriInterface` interface
* - Correct handling of both URLs and URNs * - Correct handling of both URLs and URNs
* - Relative URL resolution * - Relative URL resolution
@ -20,13 +20,13 @@ use Psr\Http\Message\UriInterface;
* - IDNA normalization * - IDNA normalization
* - IPv6 address normalization * - IPv6 address normalization
* - Empty query and fragment removal * - Empty query and fragment removal
* *
* Some things this class does not do: * Some things this class does not do:
* *
* - Handle non-standard schemes (e.g. ed2k) * - Handle non-standard schemes (e.g. ed2k)
* - Collapse paths * - Collapse paths
* - Drop default ports * - Drop default ports
* *
* This class should not be used with XML namespace URIs, * This class should not be used with XML namespace URIs,
* as the normalizations performed will change the values * as the normalizations performed will change the values
* of some namespaces. * of some namespaces.
@ -243,7 +243,7 @@ PCRE;
} }
break; break;
default: default:
$this->$name = $this->normalizeEncoding((string) $value, $name); $this->$name = $this->normalizeEncoding((string) $value, $name);
} }
} }
@ -266,7 +266,7 @@ PCRE;
$this->fragment = $fragment; $this->fragment = $fragment;
} }
} }
} elseif(strlen($path)) { } elseif (strlen($path)) {
if ($this->path[0] !== "/") { if ($this->path[0] !== "/") {
if ($path[-1] === "/") { if ($path[-1] === "/") {
$this->path = $path.$this->path; $this->path = $path.$this->path;
@ -345,4 +345,4 @@ PCRE;
} }
return $host; return $host;
} }
} }

7
tests/cases/JSON/JSONTest.php

@ -26,11 +26,11 @@ namespace JKingWeb\Lax\TestCase\JSON;
be applied to it be applied to it
- Any collections should be represented as sequences of maps, which will - Any collections should be represented as sequences of maps, which will
all be transformed accordingly all be transformed accordingly
- Rich text can either be supplied as a string (which will yield a Text object - Rich text can either be supplied as a string (which will yield a Text object
with plain-text content) or as a map with any of the properties of the with plain-text content) or as a map with any of the properties of the
Text class listed Text class listed
The transformations as performed by the `makeFeed` and `makeEntry` methods The transformations as performed by the `makeFeed` and `makeEntry` methods
of the abstract test case. of the abstract test case.
*/ */
@ -51,8 +51,7 @@ use JKingWeb\Lax\Enclosure\Collection as EnclosureCollection;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
use Symfony\Component\Yaml\Parser as YamlParser; use Symfony\Component\Yaml\Parser as YamlParser;
/**
/**
* @covers JKingWeb\Lax\Parser\Construct<extended> * @covers JKingWeb\Lax\Parser\Construct<extended>
* @covers JKingWeb\Lax\Parser\JSON\Feed<extended> * @covers JKingWeb\Lax\Parser\JSON\Feed<extended>
* @covers JKingWeb\Lax\Parser\JSON\Entry<extended> * @covers JKingWeb\Lax\Parser\JSON\Entry<extended>

118
tests/cases/Util/Url/AbstractUriTestCase.php

@ -6,8 +6,7 @@ use Psr\Http\Message\UriInterface;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use InvalidArgumentException; use InvalidArgumentException;
abstract class AbstractUriTestCase extends TestCase abstract class AbstractUriTestCase extends TestCase {
{
/** /**
* @var UriInterface * @var UriInterface
*/ */
@ -24,13 +23,11 @@ abstract class AbstractUriTestCase extends TestCase
*/ */
abstract protected function createUri($uri = ''); abstract protected function createUri($uri = '');
protected function setUp(): void protected function setUp(): void {
{
$this->uri = $this->createUri($this->uri_string); $this->uri = $this->createUri($this->uri_string);
} }
protected function tearDown(): void protected function tearDown(): void {
{
$this->uri = null; $this->uri = null;
} }
@ -41,15 +38,13 @@ abstract class AbstractUriTestCase extends TestCase
* The value returned MUST be normalized to lowercase, per RFC 3986 * The value returned MUST be normalized to lowercase, per RFC 3986
* Section 3.1. * Section 3.1.
*/ */
public function testGetScheme($scheme, $expected) public function testGetScheme($scheme, $expected) {
{
$uri = $this->uri->withScheme($scheme); $uri = $this->uri->withScheme($scheme);
$this->assertInstanceOf(UriInterface::class, $uri); $this->assertInstanceOf(UriInterface::class, $uri);
$this->assertSame($expected, $uri->getScheme(), 'Scheme must be normalized according to RFC3986'); $this->assertSame($expected, $uri->getScheme(), 'Scheme must be normalized according to RFC3986');
} }
public function schemeProvider() public function schemeProvider() {
{
return [ return [
'normalized scheme' => ['HtTpS', 'https'], 'normalized scheme' => ['HtTpS', 'https'],
'simple scheme' => ['http', 'http'], 'simple scheme' => ['http', 'http'],
@ -66,15 +61,13 @@ abstract class AbstractUriTestCase extends TestCase
* user value, with a colon (":") separating the values. * user value, with a colon (":") separating the values.
* *
*/ */
public function testGetUserInfo($user, $pass, $expected) public function testGetUserInfo($user, $pass, $expected) {
{
$uri = $this->uri->withUserInfo($user, $pass); $uri = $this->uri->withUserInfo($user, $pass);
$this->assertInstanceOf(UriInterface::class, $uri); $this->assertInstanceOf(UriInterface::class, $uri);
$this->assertSame($expected, $uri->getUserInfo(), 'UserInfo must be normalized according to RFC3986'); $this->assertSame($expected, $uri->getUserInfo(), 'UserInfo must be normalized according to RFC3986');
} }
public function userInfoProvider() public function userInfoProvider() {
{
return [ return [
'with userinfo' => ['iGoR', 'rAsMuZeN', 'iGoR:rAsMuZeN'], 'with userinfo' => ['iGoR', 'rAsMuZeN', 'iGoR:rAsMuZeN'],
'no userinfo' => ['', '', ''], 'no userinfo' => ['', '', ''],
@ -92,15 +85,13 @@ abstract class AbstractUriTestCase extends TestCase
* Section 3.2.2. * Section 3.2.2.
* *
*/ */
public function testGetHost($host, $expected) public function testGetHost($host, $expected) {
{
$uri = $this->uri->withHost($host); $uri = $this->uri->withHost($host);
$this->assertInstanceOf(UriInterface::class, $uri); $this->assertInstanceOf(UriInterface::class, $uri);
$this->assertSame($expected, $uri->getHost(), 'Host must be normalized according to RFC3986'); $this->assertSame($expected, $uri->getHost(), 'Host must be normalized according to RFC3986');
} }
public function hostProvider() public function hostProvider() {
{
return [ return [
'normalized host' => ["MaStEr.eXaMpLe.CoM", "master.example.com"], 'normalized host' => ["MaStEr.eXaMpLe.CoM", "master.example.com"],
"simple host" => ["www.example.com", "www.example.com"], "simple host" => ["www.example.com", "www.example.com"],
@ -122,18 +113,16 @@ abstract class AbstractUriTestCase extends TestCase
* If no port is present, but a scheme is present, this method MAY return * If no port is present, but a scheme is present, this method MAY return
* the standard port for that scheme, but SHOULD return null. * the standard port for that scheme, but SHOULD return null.
*/ */
public function testPort($uri, $port, $expected) public function testPort($uri, $port, $expected) {
{
$uri = $this->createUri($uri)->withPort($port); $uri = $this->createUri($uri)->withPort($port);
$this->assertInstanceOf(UriInterface::class, $uri); $this->assertInstanceOf(UriInterface::class, $uri);
$this->assertSame($expected, $uri->getPort(), 'port must be an int or null'); $this->assertSame($expected, $uri->getPort(), 'port must be an int or null');
} }
public function portProvider() public function portProvider() {
{
return [ return [
'non standard port for http' => ['http://www.example.com', 443, 443], 'non standard port for http' => ['http://www.example.com', 443, 443],
'remove port' => ['http://www.example.com', null, null], 'remove port' => ['http://www.example.com', null, null],
'standard port on schemeless http url' => ['//www.example.com', 80, 80], 'standard port on schemeless http url' => ['//www.example.com', 80, 80],
]; ];
} }
@ -141,13 +130,11 @@ abstract class AbstractUriTestCase extends TestCase
/** /**
* @group port * @group port
*/ */
public function testUriWithStandardPort() public function testUriWithStandardPort() {
{
$uri = $this->createUri('http://example.com:80'); $uri = $this->createUri('http://example.com:80');
$this->assertContains($uri->getPort(), [80, null], "If no port is present, but a scheme is present, this method MAY return the standard port for that scheme, but SHOULD return null."); $this->assertContains($uri->getPort(), [80, null], "If no port is present, but a scheme is present, this method MAY return the standard port for that scheme, but SHOULD return null.");
} }
/** /**
* @group authority * @group authority
* @dataProvider authorityProvider * @dataProvider authorityProvider
@ -155,8 +142,7 @@ abstract class AbstractUriTestCase extends TestCase
* If the port component is not set or is the standard port for the current * If the port component is not set or is the standard port for the current
* scheme, it SHOULD NOT be included. * scheme, it SHOULD NOT be included.
*/ */
public function testGetAuthority($scheme, $user, $pass, $host, $port, $authority) public function testGetAuthority($scheme, $user, $pass, $host, $port, $authority) {
{
$uri = $this $uri = $this
->createUri() ->createUri()
->withHost($host) ->withHost($host)
@ -168,8 +154,7 @@ abstract class AbstractUriTestCase extends TestCase
$this->assertSame($authority, $uri->getAuthority()); $this->assertSame($authority, $uri->getAuthority());
} }
public function authorityProvider() public function authorityProvider() {
{
return [ return [
'authority' => [ 'authority' => [
'scheme' => 'http', 'scheme' => 'http',
@ -222,15 +207,13 @@ abstract class AbstractUriTestCase extends TestCase
* any characters. To determine what characters to encode, please refer to * any characters. To determine what characters to encode, please refer to
* RFC 3986, Sections 2 and 3.4. * RFC 3986, Sections 2 and 3.4.
*/ */
public function testGetQuery($query, $expected) public function testGetQuery($query, $expected) {
{
$uri = $this->uri->withQuery($query); $uri = $this->uri->withQuery($query);
$this->assertInstanceOf(UriInterface::class, $uri); $this->assertInstanceOf(UriInterface::class, $uri);
$this->assertSame($expected, $uri->getQuery(), 'Query must be normalized according to RFC3986'); $this->assertSame($expected, $uri->getQuery(), 'Query must be normalized according to RFC3986');
} }
public function queryProvider() public function queryProvider() {
{
return [ return [
'normalized query' => ['foo.bar=%7evalue', 'foo.bar=~value'], 'normalized query' => ['foo.bar=%7evalue', 'foo.bar=~value'],
'empty query' => ['', ''], 'empty query' => ['', ''],
@ -247,15 +230,13 @@ abstract class AbstractUriTestCase extends TestCase
* any characters. To determine what characters to encode, please refer to * any characters. To determine what characters to encode, please refer to
* RFC 3986, Sections 2 and 3.5. * RFC 3986, Sections 2 and 3.5.
*/ */
public function testGetFragment($fragment, $expected) public function testGetFragment($fragment, $expected) {
{
$uri = $this->uri->withFragment($fragment); $uri = $this->uri->withFragment($fragment);
$this->assertInstanceOf(UriInterface::class, $uri); $this->assertInstanceOf(UriInterface::class, $uri);
$this->assertSame($expected, $uri->getFragment(), 'Fragment must be normalized according to RFC3986'); $this->assertSame($expected, $uri->getFragment(), 'Fragment must be normalized according to RFC3986');
} }
public function fragmentProvider() public function fragmentProvider() {
{
return [ return [
'URL with full components' => ['fragment', 'fragment'], 'URL with full components' => ['fragment', 'fragment'],
'URL with non-encodable fragment' => ["azAZ0-9/?-._~!$&'()*+,;=:@", "azAZ0-9/?-._~!$&'()*+,;=:@"], 'URL with non-encodable fragment' => ["azAZ0-9/?-._~!$&'()*+,;=:@", "azAZ0-9/?-._~!$&'()*+,;=:@"],
@ -278,8 +259,7 @@ abstract class AbstractUriTestCase extends TestCase
* - If a query is present, it MUST be prefixed by "?". * - If a query is present, it MUST be prefixed by "?".
* - If a fragment is present, it MUST be prefixed by "#". * - If a fragment is present, it MUST be prefixed by "#".
*/ */
public function testToString($scheme, $user, $pass, $host, $port, $path, $query, $fragment, $expected) public function testToString($scheme, $user, $pass, $host, $port, $path, $query, $fragment, $expected) {
{
$uri = $this->createUri() $uri = $this->createUri()
->withHost($host) ->withHost($host)
->withScheme($scheme) ->withScheme($scheme)
@ -295,8 +275,7 @@ abstract class AbstractUriTestCase extends TestCase
'URI string must be normalized according to RFC3986 rules' 'URI string must be normalized according to RFC3986 rules'
); );
} }
public function stringProvider() public function stringProvider() {
{
return [ return [
'URL normalized' => [ 'URL normalized' => [
'scheme' => 'HtTps', 'scheme' => 'HtTps',
@ -307,7 +286,7 @@ abstract class AbstractUriTestCase extends TestCase
'path' => '/%7ejohndoe/%a1/index.php', 'path' => '/%7ejohndoe/%a1/index.php',
'query' => 'foo.bar=%7evalue', 'query' => 'foo.bar=%7evalue',
'fragment' => 'fragment', 'fragment' => 'fragment',
'uri' => 'https://iGoR:rAsMuZeN@master.example.com:443/~johndoe/%A1/index.php?foo.bar=~value#fragment' 'uri' => 'https://iGoR:rAsMuZeN@master.example.com:443/~johndoe/%A1/index.php?foo.bar=~value#fragment',
], ],
'URL without scheme' => [ 'URL without scheme' => [
'scheme' => '', 'scheme' => '',
@ -337,8 +316,7 @@ abstract class AbstractUriTestCase extends TestCase
/** /**
* @group fragment * @group fragment
*/ */
public function testRemoveFragment() public function testRemoveFragment() {
{
$uri = 'http://example.com/path/to/me'; $uri = 'http://example.com/path/to/me';
$this->assertSame($uri, (string) $this->createUri($uri.'#doc')->withFragment('')); $this->assertSame($uri, (string) $this->createUri($uri.'#doc')->withFragment(''));
} }
@ -346,8 +324,7 @@ abstract class AbstractUriTestCase extends TestCase
/** /**
* @group query * @group query
*/ */
public function testRemoveQuery() public function testRemoveQuery() {
{
$uri = 'http://example.com/path/to/me'; $uri = 'http://example.com/path/to/me';
$this->assertSame($uri, (string) (string) $this->createUri($uri.'?name=value')->withQuery('')); $this->assertSame($uri, (string) (string) $this->createUri($uri.'?name=value')->withQuery(''));
} }
@ -355,8 +332,7 @@ abstract class AbstractUriTestCase extends TestCase
/** /**
* @group path * @group path
*/ */
public function testRemovePath() public function testRemovePath() {
{
$uri = 'http://example.com'; $uri = 'http://example.com';
$this->assertContains( $this->assertContains(
(string) $this->createUri($uri.'/path/to/me')->withPath(''), (string) $this->createUri($uri.'/path/to/me')->withPath(''),
@ -367,8 +343,7 @@ abstract class AbstractUriTestCase extends TestCase
/** /**
* @group port * @group port
*/ */
public function testRemovePort() public function testRemovePort() {
{
$this->assertSame( $this->assertSame(
'http://example.com/path/to/me', 'http://example.com/path/to/me',
(string) $this->createUri('http://example.com:81/path/to/me')->withPort(null) (string) $this->createUri('http://example.com:81/path/to/me')->withPort(null)
@ -378,8 +353,7 @@ abstract class AbstractUriTestCase extends TestCase
/** /**
* @group userinfo * @group userinfo
*/ */
public function testRemoveUserInfo() public function testRemoveUserInfo() {
{
$this->assertSame( $this->assertSame(
'http://example.com/path/to/me', 'http://example.com/path/to/me',
(string) $this->createUri('http://user:pass@example.com/path/to/me')->withUserInfo('') (string) $this->createUri('http://user:pass@example.com/path/to/me')->withUserInfo('')
@ -389,8 +363,7 @@ abstract class AbstractUriTestCase extends TestCase
/** /**
* @group scheme * @group scheme
*/ */
public function testRemoveScheme() public function testRemoveScheme() {
{
$this->assertSame( $this->assertSame(
'//example.com/path/to/me', '//example.com/path/to/me',
(string) $this->createUri('http://example.com/path/to/me')->withScheme('') (string) $this->createUri('http://example.com/path/to/me')->withScheme('')
@ -400,8 +373,7 @@ abstract class AbstractUriTestCase extends TestCase
/** /**
* @group authority * @group authority
*/ */
public function testRemoveAuthority() public function testRemoveAuthority() {
{
$uri = 'http://user:login@example.com:82/path?q=v#doc'; $uri = 'http://user:login@example.com:82/path?q=v#doc';
$uri_with_host = $this->createUri($uri) $uri_with_host = $this->createUri($uri)
@ -418,14 +390,12 @@ abstract class AbstractUriTestCase extends TestCase
* @group scheme * @group scheme
* @dataProvider withSchemeFailedProvider * @dataProvider withSchemeFailedProvider
*/ */
public function testWithSchemeFailed($scheme) public function testWithSchemeFailed($scheme) {
{
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->uri->withScheme($scheme); $this->uri->withScheme($scheme);
} }
public function withSchemeFailedProvider() public function withSchemeFailedProvider() {
{
return [ return [
'invalid char' => ['in,valid'], 'invalid char' => ['in,valid'],
'integer like string' => ['123'], 'integer like string' => ['123'],
@ -436,14 +406,12 @@ abstract class AbstractUriTestCase extends TestCase
* @group host * @group host
* @dataProvider withHostFailedProvider * @dataProvider withHostFailedProvider
*/ */
public function testWithHostFailed($host) public function testWithHostFailed($host) {
{
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->uri->withHost($host); $this->uri->withHost($host);
} }
public function withHostFailedProvider() public function withHostFailedProvider() {
{
return [ return [
'dot in front' => ['.example.com'], 'dot in front' => ['.example.com'],
'hyphen suffix' => ['host.com-'], 'hyphen suffix' => ['host.com-'],
@ -469,8 +437,7 @@ abstract class AbstractUriTestCase extends TestCase
/** /**
* @group host * @group host
*/ */
public function testModificationFailedWithInvalidHost() public function testModificationFailedWithInvalidHost() {
{
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->createUri('http://example.com')->withHost('?'); $this->createUri('http://example.com')->withHost('?');
} }
@ -479,15 +446,12 @@ abstract class AbstractUriTestCase extends TestCase
* @group uri * @group uri
* @dataProvider invalidURI * @dataProvider invalidURI
*/ */
public function testCreateFromInvalidUrlKO($uri) public function testCreateFromInvalidUrlKO($uri) {
{
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->createUri($uri); $this->createUri($uri);
} }
public function invalidURI() public function invalidURI() {
{
return [ return [
['http://user@:80'], ['http://user@:80'],
]; ];
@ -496,8 +460,7 @@ abstract class AbstractUriTestCase extends TestCase
/** /**
* @group uri * @group uri
*/ */
public function testEmptyValueDetection() public function testEmptyValueDetection() {
{
$expected = '//0:0@0/0?0#0'; $expected = '//0:0@0/0?0#0';
$this->assertSame($expected, (string) $this->createUri($expected)); $this->assertSame($expected, (string) $this->createUri($expected));
} }
@ -506,9 +469,8 @@ abstract class AbstractUriTestCase extends TestCase
* @group path * @group path
* @group uri * @group uri
*/ */
public function testPathDetection() public function testPathDetection() {
{
$expected = 'foo/bar:'; $expected = 'foo/bar:';
$this->assertSame($expected, $this->createUri($expected)->getPath()); $this->assertSame($expected, $this->createUri($expected)->getPath());
} }
} }

2
tests/cases/Util/UrlTest.php

@ -14,4 +14,4 @@ class UrlTest extends AbstractUriTestCase {
protected function createUri($uri = '') { protected function createUri($uri = '') {
return new Url($uri); return new Url($uri);
} }
} }

5
vendor-bin/csfixer/composer.json

@ -0,0 +1,5 @@
{
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.16"
}
}

1383
vendor-bin/csfixer/composer.lock

File diff suppressed because it is too large
Loading…
Cancel
Save