diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d4a44091..9e6af5d3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Install PHP uses: shivammathur/setup-php@v2 @@ -31,7 +31,7 @@ jobs: php-version: ${{ matrix.php }} - name: Cache PHP dependencies - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: vendor key: ${{ runner.os }}-php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} @@ -42,3 +42,29 @@ jobs: - name: Tests run: composer test + + phpstan: + name: PHPStan Static Analysis + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + + - name: Cache PHP dependencies + uses: actions/cache@v4 + with: + path: vendor + key: ${{ runner.os }}-php-8.4-composer-${{ hashFiles('**/composer.json') }} + restore-keys: ${{ runner.os }}-php-8.4-composer- + + - name: Install dependencies + run: composer install + + - name: Run PHPStan + run: composer phpstan diff --git a/CHANGELOG.md b/CHANGELOG.md index 26d11751..5f4b497f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [4.4.17] - 2025-05-13 +### Fixed +- Adapters hostname detection [#556]. + ## [4.4.16] - 2025-05-09 ### Fixed - Adapters hostname detection [#555]. @@ -259,7 +263,9 @@ Full library refactoring. [#551]: https://github.com/oscarotero/Embed/issues/551 [#553]: https://github.com/oscarotero/Embed/issues/553 [#555]: https://github.com/oscarotero/Embed/issues/555 +[#556]: https://github.com/oscarotero/Embed/issues/556 +[4.4.17]: https://github.com/oscarotero/Embed/compare/v4.4.16...v4.4.17 [4.4.16]: https://github.com/oscarotero/Embed/compare/v4.4.15...v4.4.16 [4.4.15]: https://github.com/oscarotero/Embed/compare/v4.4.14...v4.4.15 [4.4.14]: https://github.com/oscarotero/Embed/compare/v4.4.13...v4.4.14 diff --git a/README.md b/README.md index c23b8475..538a0c46 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,3 @@ -# Searching MAINTAINER - -After 11 years since the first version of Embed was released, I don't have the time or motivation to continue maintaining this project. I rarely write PHP code and am not aware of the latest features of PHP. If anyone wants to continue maintaining and evolving this library, please open an issue or contact me. - -Meanwhile, I'll continue accepting PR from the community (I don't want this project to die), but won't be actively working on improving it. Thanks! - # Embed diff --git a/composer.json b/composer.json index be3fdf91..993f65e5 100644 --- a/composer.json +++ b/composer.json @@ -42,7 +42,9 @@ "nyholm/psr7": "^1.2", "oscarotero/php-cs-fixer-config": "^1.0", "brick/varexporter": "^0.3.1", - "symfony/css-selector": "^5.0" + "symfony/css-selector": "^5.0", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-strict-rules": "^2.0" }, "suggest": { "symfony/css-selector": "If you want to get elements using css selectors" @@ -64,6 +66,7 @@ "demo": "php -S localhost:8888 demo/index.php", "test": "phpunit", "cs-fix": "php-cs-fixer fix", + "phpstan": "phpstan --memory-limit=-1", "update-resources": [ "php scripts/update-oembed.php", "php scripts/update-suffix.php" diff --git a/phpstan.dist.neon b/phpstan.dist.neon new file mode 100644 index 00000000..e989bd1a --- /dev/null +++ b/phpstan.dist.neon @@ -0,0 +1,15 @@ +includes: + - vendor/phpstan/phpstan-strict-rules/rules.neon + +parameters: + level: max + paths: + - src +# - tests + excludePaths: + - tests/cache + - tests/fixtures + checkMissingCallableSignature: true + checkUninitializedProperties: true + checkTooWideReturnTypesInProtectedAndPublicMethods: true + checkImplicitMixed: true diff --git a/src/Adapters/Archive/Api.php b/src/Adapters/Archive/Api.php index aa105148..b4820eaa 100644 --- a/src/Adapters/Archive/Api.php +++ b/src/Adapters/Archive/Api.php @@ -9,6 +9,9 @@ class Api { use HttpApiTrait; + /** + * @return array + */ protected function fetchData(): array { $this->endpoint = $this->extractor->getUri()->withQuery('output=json'); diff --git a/src/Adapters/Archive/Detectors/AuthorName.php b/src/Adapters/Archive/Detectors/AuthorName.php index ea467255..a58abfbc 100644 --- a/src/Adapters/Archive/Detectors/AuthorName.php +++ b/src/Adapters/Archive/Detectors/AuthorName.php @@ -5,13 +5,16 @@ use Embed\Detectors\AuthorName as Detector; +/** + * @extends Detector<\Embed\Adapters\Archive\Extractor> + */ class AuthorName extends Detector { public function detect(): ?string { $api = $this->extractor->getApi(); - return $api->str('metadata', 'creator') - ?: parent::detect(); + $result = $api->str('metadata', 'creator'); + return (is_string($result) && trim($result) !== '') ? $result : parent::detect(); } } diff --git a/src/Adapters/Archive/Detectors/Code.php b/src/Adapters/Archive/Detectors/Code.php index 1840621f..e5888c57 100644 --- a/src/Adapters/Archive/Detectors/Code.php +++ b/src/Adapters/Archive/Detectors/Code.php @@ -8,6 +8,9 @@ use function Embed\html; use function Embed\matchPath; +/** + * @extends Detector<\Embed\Adapters\Archive\Extractor> + */ class Code extends Detector { public function detect(): ?EmbedCode diff --git a/src/Adapters/Archive/Detectors/Description.php b/src/Adapters/Archive/Detectors/Description.php index d3c3af1a..b9553044 100644 --- a/src/Adapters/Archive/Detectors/Description.php +++ b/src/Adapters/Archive/Detectors/Description.php @@ -5,13 +5,16 @@ use Embed\Detectors\Description as Detector; +/** + * @extends Detector<\Embed\Adapters\Archive\Extractor> + */ class Description extends Detector { public function detect(): ?string { $api = $this->extractor->getApi(); - return $api->str('metadata', 'extract') - ?: parent::detect(); + $result = $api->str('metadata', 'extract'); + return (is_string($result) && trim($result) !== '') ? $result : parent::detect(); } } diff --git a/src/Adapters/Archive/Detectors/ProviderName.php b/src/Adapters/Archive/Detectors/ProviderName.php index e8a632ac..4f036c27 100644 --- a/src/Adapters/Archive/Detectors/ProviderName.php +++ b/src/Adapters/Archive/Detectors/ProviderName.php @@ -5,6 +5,9 @@ use Embed\Detectors\ProviderName as Detector; +/** + * @extends Detector<\Embed\Adapters\Archive\Extractor> + */ class ProviderName extends Detector { public function detect(): string diff --git a/src/Adapters/Archive/Detectors/PublishedTime.php b/src/Adapters/Archive/Detectors/PublishedTime.php index 47e36d2d..c2f76aec 100644 --- a/src/Adapters/Archive/Detectors/PublishedTime.php +++ b/src/Adapters/Archive/Detectors/PublishedTime.php @@ -6,15 +6,23 @@ use DateTime; use Embed\Detectors\PublishedTime as Detector; +/** + * @extends Detector<\Embed\Adapters\Archive\Extractor> + */ class PublishedTime extends Detector { public function detect(): ?DateTime { $api = $this->extractor->getApi(); - return $api->time('metadata', 'publicdate') - ?: $api->time('metadata', 'addeddate') - ?: $api->time('metadata', 'date') - ?: parent::detect(); + $fields = ['publicdate', 'addeddate', 'date']; + foreach ($fields as $field) { + $result = $api->time('metadata', $field); + if ($result !== null) { + return $result; + } + } + + return parent::detect(); } } diff --git a/src/Adapters/Archive/Detectors/Title.php b/src/Adapters/Archive/Detectors/Title.php index 4ba1dca9..a5b83eb1 100644 --- a/src/Adapters/Archive/Detectors/Title.php +++ b/src/Adapters/Archive/Detectors/Title.php @@ -5,13 +5,16 @@ use Embed\Detectors\Title as Detector; +/** + * @extends Detector<\Embed\Adapters\Archive\Extractor> + */ class Title extends Detector { public function detect(): ?string { $api = $this->extractor->getApi(); - return $api->str('metadata', 'title') - ?: parent::detect(); + $result = $api->str('metadata', 'title'); + return (is_string($result) && trim($result) !== '') ? $result : parent::detect(); } } diff --git a/src/Adapters/Archive/Extractor.php b/src/Adapters/Archive/Extractor.php index ab941e0f..482e13a9 100644 --- a/src/Adapters/Archive/Extractor.php +++ b/src/Adapters/Archive/Extractor.php @@ -4,11 +4,28 @@ namespace Embed\Adapters\Archive; use Embed\Extractor as Base; +use Embed\Http\Crawler; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\UriInterface; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { private Api $api; + public function __construct( + UriInterface $uri, + RequestInterface $request, + ResponseInterface $response, + Crawler $crawler + ) { + parent::__construct($uri, $request, $response, $crawler); + $this->api = new Api($this); + } + public function getApi(): Api { return $this->api; @@ -16,8 +33,6 @@ public function getApi(): Api public function createCustomDetectors(): array { - $this->api = new Api($this); - return [ 'title' => new Detectors\Title($this), 'description' => new Detectors\Description($this), diff --git a/src/Adapters/Bandcamp/Detectors/ProviderName.php b/src/Adapters/Bandcamp/Detectors/ProviderName.php index 74575aef..a52c98a8 100644 --- a/src/Adapters/Bandcamp/Detectors/ProviderName.php +++ b/src/Adapters/Bandcamp/Detectors/ProviderName.php @@ -5,6 +5,9 @@ use Embed\Detectors\ProviderName as Detector; +/** + * @extends Detector<\Embed\Adapters\Bandcamp\Extractor> + */ class ProviderName extends Detector { public function detect(): string diff --git a/src/Adapters/Bandcamp/Extractor.php b/src/Adapters/Bandcamp/Extractor.php index f4a97417..94e03537 100644 --- a/src/Adapters/Bandcamp/Extractor.php +++ b/src/Adapters/Bandcamp/Extractor.php @@ -5,6 +5,9 @@ use Embed\Extractor as Base; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { public function createCustomDetectors(): array diff --git a/src/Adapters/CadenaSer/Detectors/Code.php b/src/Adapters/CadenaSer/Detectors/Code.php index d279e7b2..bf70e664 100644 --- a/src/Adapters/CadenaSer/Detectors/Code.php +++ b/src/Adapters/CadenaSer/Detectors/Code.php @@ -9,12 +9,15 @@ use function Embed\html; use function Embed\matchPath; +/** + * @extends Detector<\Embed\Adapters\CadenaSer\Extractor> + */ class Code extends Detector { public function detect(): ?EmbedCode { - return parent::detect() - ?: $this->fallback(); + $result = parent::detect(); + return $result !== null ? $result : $this->fallback(); } private function fallback(): ?EmbedCode diff --git a/src/Adapters/CadenaSer/Extractor.php b/src/Adapters/CadenaSer/Extractor.php index aa237776..e6d05ef6 100644 --- a/src/Adapters/CadenaSer/Extractor.php +++ b/src/Adapters/CadenaSer/Extractor.php @@ -5,6 +5,9 @@ use Embed\Extractor as Base; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { public function createCustomDetectors(): array diff --git a/src/Adapters/Facebook/Detectors/Title.php b/src/Adapters/Facebook/Detectors/Title.php index b73a53ff..298d2631 100644 --- a/src/Adapters/Facebook/Detectors/Title.php +++ b/src/Adapters/Facebook/Detectors/Title.php @@ -5,6 +5,9 @@ use Embed\Detectors\Title as Detector; +/** + * @extends Detector<\Embed\Adapters\Facebook\Extractor> + */ class Title extends Detector { /** @@ -15,7 +18,7 @@ public function detect(): ?string $document = $this->extractor->getDocument(); $oembed = $this->extractor->getOEmbed(); - return $oembed->str('title') - ?: $document->select('.//head/title')->str(); + $result = $oembed->str('title'); + return $result !== null ? $result : $document->select('.//head/title')->str(); } } diff --git a/src/Adapters/Facebook/Extractor.php b/src/Adapters/Facebook/Extractor.php index 5b4cb701..9b8e4fdb 100644 --- a/src/Adapters/Facebook/Extractor.php +++ b/src/Adapters/Facebook/Extractor.php @@ -7,6 +7,9 @@ class Extractor extends Base { + /** + * @return array{title: Detectors\Title} + */ public function createCustomDetectors(): array { $this->oembed = new OEmbed($this); diff --git a/src/Adapters/Facebook/OEmbed.php b/src/Adapters/Facebook/OEmbed.php index 84d57726..075b0bdf 100644 --- a/src/Adapters/Facebook/OEmbed.php +++ b/src/Adapters/Facebook/OEmbed.php @@ -16,14 +16,14 @@ protected function detectEndpoint(): ?UriInterface { $token = $this->extractor->getSetting('facebook:token'); - if (!$token) { + if (!is_string($token) || $token === '') { return null; } $uri = $this->extractor->getUri(); if (strpos($uri->getPath(), 'login') !== false) { parse_str($uri->getQuery(), $params); - if (!empty($params['next'])) { + if (isset($params['next']) && is_string($params['next']) && $params['next'] !== '' && $params['next'] !== '0') { $uri = $this->extractor->getCrawler()->createUri($params['next']); } } diff --git a/src/Adapters/Flickr/Detectors/Code.php b/src/Adapters/Flickr/Detectors/Code.php index 1dfe50ae..9f14eb19 100644 --- a/src/Adapters/Flickr/Detectors/Code.php +++ b/src/Adapters/Flickr/Detectors/Code.php @@ -9,12 +9,19 @@ use function Embed\html; use function Embed\matchPath; +/** + * @extends Detector<\Embed\Adapters\Flickr\Extractor> + */ class Code extends Detector { public function detect(): ?EmbedCode { - return parent::detect() - ?: $this->fallback(); + $result = parent::detect(); + if ($result !== null) { + return $result; + } + + return $this->fallback(); } private function fallback(): ?EmbedCode diff --git a/src/Adapters/Flickr/Extractor.php b/src/Adapters/Flickr/Extractor.php index fe18c9d8..0a4369c8 100644 --- a/src/Adapters/Flickr/Extractor.php +++ b/src/Adapters/Flickr/Extractor.php @@ -5,6 +5,9 @@ use Embed\Extractor as Base; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { public function createCustomDetectors(): array diff --git a/src/Adapters/Gist/Api.php b/src/Adapters/Gist/Api.php index a5f10044..b8262d24 100644 --- a/src/Adapters/Gist/Api.php +++ b/src/Adapters/Gist/Api.php @@ -9,6 +9,9 @@ class Api { use HttpApiTrait; + /** + * @return array + */ protected function fetchData(): array { $uri = $this->extractor->getUri(); diff --git a/src/Adapters/Gist/Detectors/AuthorName.php b/src/Adapters/Gist/Detectors/AuthorName.php index b31aea6a..a4566624 100644 --- a/src/Adapters/Gist/Detectors/AuthorName.php +++ b/src/Adapters/Gist/Detectors/AuthorName.php @@ -5,13 +5,16 @@ use Embed\Detectors\AuthorName as Detector; +/** + * @extends Detector<\Embed\Adapters\Gist\Extractor> + */ class AuthorName extends Detector { public function detect(): ?string { $api = $this->extractor->getApi(); - return $api->str('owner') - ?: parent::detect(); + $result = $api->str('owner'); + return (is_string($result) && trim($result) !== '') ? $result : parent::detect(); } } diff --git a/src/Adapters/Gist/Detectors/AuthorUrl.php b/src/Adapters/Gist/Detectors/AuthorUrl.php index 1241429e..d9bae2a8 100644 --- a/src/Adapters/Gist/Detectors/AuthorUrl.php +++ b/src/Adapters/Gist/Detectors/AuthorUrl.php @@ -6,15 +6,21 @@ use Embed\Detectors\AuthorUrl as Detector; use Psr\Http\Message\UriInterface; +/** + * @extends Detector<\Embed\Adapters\Gist\Extractor> + */ class AuthorUrl extends Detector { public function detect(): ?UriInterface { - $api = $this->extractor->getApi(); + $extractor = $this->extractor; + $api = $extractor->getApi(); $owner = $api->str('owner'); - if ($owner) { - return $this->extractor->getCrawler()->createUri("https://github.com/{$owner}"); + // Exclude empty string and '0' to maintain original truthy check behavior + // The string '0' is not a valid GitHub username and should not generate a URL + if (is_string($owner) && $owner !== '' && $owner !== '0') { + return $extractor->getCrawler()->createUri("https://github.com/{$owner}"); } return parent::detect(); diff --git a/src/Adapters/Gist/Detectors/Code.php b/src/Adapters/Gist/Detectors/Code.php index 23960ee1..8eb7a909 100644 --- a/src/Adapters/Gist/Detectors/Code.php +++ b/src/Adapters/Gist/Detectors/Code.php @@ -7,12 +7,15 @@ use Embed\EmbedCode; use function Embed\html; +/** + * @extends Detector<\Embed\Adapters\Gist\Extractor> + */ class Code extends Detector { public function detect(): ?EmbedCode { - return parent::detect() - ?: $this->fallback(); + $parentResult = parent::detect(); + return $parentResult !== null ? $parentResult : $this->fallback(); } private function fallback(): ?EmbedCode @@ -22,10 +25,12 @@ private function fallback(): ?EmbedCode $code = $api->html('div'); $stylesheet = $api->str('stylesheet'); - if ($code && $stylesheet) { + if ($code !== null && $stylesheet !== null) { return new EmbedCode( html('link', ['rel' => 'stylesheet', 'href' => $stylesheet]).$code ); } + + return null; } } diff --git a/src/Adapters/Gist/Detectors/PublishedTime.php b/src/Adapters/Gist/Detectors/PublishedTime.php index 1487524b..6a29aad1 100644 --- a/src/Adapters/Gist/Detectors/PublishedTime.php +++ b/src/Adapters/Gist/Detectors/PublishedTime.php @@ -6,13 +6,16 @@ use DateTime; use Embed\Detectors\PublishedTime as Detector; +/** + * @extends Detector<\Embed\Adapters\Gist\Extractor> + */ class PublishedTime extends Detector { public function detect(): ?DateTime { $api = $this->extractor->getApi(); - return $api->time('created_at') - ?: parent::detect(); + $result = $api->time('created_at'); + return $result !== null ? $result : parent::detect(); } } diff --git a/src/Adapters/Gist/Extractor.php b/src/Adapters/Gist/Extractor.php index f9ac088c..088f874a 100644 --- a/src/Adapters/Gist/Extractor.php +++ b/src/Adapters/Gist/Extractor.php @@ -4,11 +4,28 @@ namespace Embed\Adapters\Gist; use Embed\Extractor as Base; +use Embed\Http\Crawler; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\UriInterface; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { private Api $api; + public function __construct( + UriInterface $uri, + RequestInterface $request, + ResponseInterface $response, + Crawler $crawler + ) { + parent::__construct($uri, $request, $response, $crawler); + $this->api = new Api($this); + } + public function getApi(): Api { return $this->api; @@ -16,8 +33,6 @@ public function getApi(): Api public function createCustomDetectors(): array { - $this->api = new Api($this); - return [ 'authorName' => new Detectors\AuthorName($this), 'authorUrl' => new Detectors\AuthorUrl($this), diff --git a/src/Adapters/Github/Detectors/Code.php b/src/Adapters/Github/Detectors/Code.php index 350e15c4..03cb9a3c 100644 --- a/src/Adapters/Github/Detectors/Code.php +++ b/src/Adapters/Github/Detectors/Code.php @@ -8,12 +8,15 @@ use function Embed\html; use function Embed\matchPath; +/** + * @extends Detector<\Embed\Adapters\Github\Extractor> + */ class Code extends Detector { public function detect(): ?EmbedCode { - return parent::detect() - ?: $this->fallback(); + $result = parent::detect(); + return $result !== null ? $result : $this->fallback(); } private function fallback(): ?EmbedCode diff --git a/src/Adapters/Github/Extractor.php b/src/Adapters/Github/Extractor.php index 0be93580..6762be87 100644 --- a/src/Adapters/Github/Extractor.php +++ b/src/Adapters/Github/Extractor.php @@ -5,6 +5,9 @@ use Embed\Extractor as Base; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { public function createCustomDetectors(): array diff --git a/src/Adapters/Ideone/Detectors/Code.php b/src/Adapters/Ideone/Detectors/Code.php index 0238981a..6459d867 100644 --- a/src/Adapters/Ideone/Detectors/Code.php +++ b/src/Adapters/Ideone/Detectors/Code.php @@ -7,20 +7,23 @@ use Embed\EmbedCode; use function Embed\html; +/** + * @extends Detector<\Embed\Adapters\Ideone\Extractor> + */ class Code extends Detector { public function detect(): ?EmbedCode { - return parent::detect() - ?: $this->fallback(); + $result = parent::detect(); + return $result !== null ? $result : $this->fallback(); } private function fallback(): ?EmbedCode { $uri = $this->extractor->getUri(); - $id = explode('/', $uri->getPath())[1]; + $id = explode('/', $uri->getPath())[1] ?? ''; - if (empty($id)) { + if ($id === '' || $id === '0') { return null; } diff --git a/src/Adapters/Ideone/Extractor.php b/src/Adapters/Ideone/Extractor.php index aa7132fc..2dc9116a 100644 --- a/src/Adapters/Ideone/Extractor.php +++ b/src/Adapters/Ideone/Extractor.php @@ -5,6 +5,9 @@ use Embed\Extractor as Base; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { public function createCustomDetectors(): array diff --git a/src/Adapters/ImageShack/Api.php b/src/Adapters/ImageShack/Api.php index a5bc3ec0..8046a42c 100644 --- a/src/Adapters/ImageShack/Api.php +++ b/src/Adapters/ImageShack/Api.php @@ -11,6 +11,9 @@ class Api { use HttpApiTrait; + /** + * @return array + */ protected function fetchData(): array { $uri = $this->extractor->getUri(); @@ -25,12 +28,19 @@ protected function fetchData(): array $id = getDirectory($uri->getPath(), 1); - if (empty($id)) { + if ($id === null || $id === '' || $id === '0') { return []; } $this->endpoint = $this->extractor->getCrawler()->createUri("https://api.imageshack.com/v2/images/{$id}"); $data = $this->fetchJSON($this->endpoint); - return $data['result'] ?? []; + + if (isset($data['result']) && is_array($data['result'])) { + /** @var array */ + $result = $data['result']; + return $result; + } + + return []; } } diff --git a/src/Adapters/ImageShack/Detectors/AuthorName.php b/src/Adapters/ImageShack/Detectors/AuthorName.php index 52c4ff5f..75336997 100644 --- a/src/Adapters/ImageShack/Detectors/AuthorName.php +++ b/src/Adapters/ImageShack/Detectors/AuthorName.php @@ -3,15 +3,19 @@ namespace Embed\Adapters\ImageShack\Detectors; +use Embed\Adapters\ImageShack\Extractor; use Embed\Detectors\AuthorName as Detector; +/** + * @extends Detector<\Embed\Adapters\ImageShack\Extractor> + */ class AuthorName extends Detector { public function detect(): ?string { $api = $this->extractor->getApi(); - return $api->str('owner', 'username') - ?: parent::detect(); + $result = $api->str('owner', 'username'); + return (is_string($result) && trim($result) !== '') ? $result : parent::detect(); } } diff --git a/src/Adapters/ImageShack/Detectors/AuthorUrl.php b/src/Adapters/ImageShack/Detectors/AuthorUrl.php index 1578da5d..7fcd0b08 100644 --- a/src/Adapters/ImageShack/Detectors/AuthorUrl.php +++ b/src/Adapters/ImageShack/Detectors/AuthorUrl.php @@ -6,15 +6,21 @@ use Embed\Detectors\AuthorUrl as Detector; use Psr\Http\Message\UriInterface; +/** + * @extends Detector<\Embed\Adapters\ImageShack\Extractor> + */ class AuthorUrl extends Detector { public function detect(): ?UriInterface { - $api = $this->extractor->getApi(); + $extractor = $this->extractor; + $api = $extractor->getApi(); $owner = $api->str('owner', 'username'); - if ($owner) { - return $this->extractor->getCrawler()->createUri("https://imageshack.com/{$owner}"); + // Exclude empty string and '0' to maintain original truthy check behavior + // The string '0' is not a valid username and should not generate a URL + if (is_string($owner) && $owner !== '' && $owner !== '0') { + return $extractor->getCrawler()->createUri("https://imageshack.com/{$owner}"); } return parent::detect(); diff --git a/src/Adapters/ImageShack/Detectors/Description.php b/src/Adapters/ImageShack/Detectors/Description.php index a30638b6..1294e9dd 100644 --- a/src/Adapters/ImageShack/Detectors/Description.php +++ b/src/Adapters/ImageShack/Detectors/Description.php @@ -5,13 +5,16 @@ use Embed\Detectors\Description as Detector; +/** + * @extends Detector<\Embed\Adapters\ImageShack\Extractor> + */ class Description extends Detector { public function detect(): ?string { $api = $this->extractor->getApi(); - return $api->str('description') - ?: parent::detect(); + $result = $api->str('description'); + return (is_string($result) && trim($result) !== '') ? $result : parent::detect(); } } diff --git a/src/Adapters/ImageShack/Detectors/Image.php b/src/Adapters/ImageShack/Detectors/Image.php index 102b7619..477cc0b3 100644 --- a/src/Adapters/ImageShack/Detectors/Image.php +++ b/src/Adapters/ImageShack/Detectors/Image.php @@ -6,13 +6,16 @@ use Embed\Detectors\Image as Detector; use Psr\Http\Message\UriInterface; +/** + * @extends Detector<\Embed\Adapters\ImageShack\Extractor> + */ class Image extends Detector { public function detect(): ?UriInterface { $api = $this->extractor->getApi(); - return $api->url('direct_link') - ?: parent::detect(); + $result = $api->url('direct_link'); + return $result !== null ? $result : parent::detect(); } } diff --git a/src/Adapters/ImageShack/Detectors/ProviderName.php b/src/Adapters/ImageShack/Detectors/ProviderName.php index 661d45e4..fffe0d30 100644 --- a/src/Adapters/ImageShack/Detectors/ProviderName.php +++ b/src/Adapters/ImageShack/Detectors/ProviderName.php @@ -5,6 +5,9 @@ use Embed\Detectors\ProviderName as Detector; +/** + * @extends Detector<\Embed\Adapters\ImageShack\Extractor> + */ class ProviderName extends Detector { public function detect(): string diff --git a/src/Adapters/ImageShack/Detectors/PublishedTime.php b/src/Adapters/ImageShack/Detectors/PublishedTime.php index 969804e9..ff6d07bf 100644 --- a/src/Adapters/ImageShack/Detectors/PublishedTime.php +++ b/src/Adapters/ImageShack/Detectors/PublishedTime.php @@ -4,15 +4,19 @@ namespace Embed\Adapters\ImageShack\Detectors; use DateTime; +use Embed\Adapters\ImageShack\Extractor; use Embed\Detectors\PublishedTime as Detector; +/** + * @extends Detector<\Embed\Adapters\ImageShack\Extractor> + */ class PublishedTime extends Detector { public function detect(): ?DateTime { $api = $this->extractor->getApi(); - return $api->time('creation_date') - ?: parent::detect(); + $result = $api->time('creation_date'); + return $result !== null ? $result : parent::detect(); } } diff --git a/src/Adapters/ImageShack/Detectors/Title.php b/src/Adapters/ImageShack/Detectors/Title.php index 6ea32d13..8bab3936 100644 --- a/src/Adapters/ImageShack/Detectors/Title.php +++ b/src/Adapters/ImageShack/Detectors/Title.php @@ -5,13 +5,16 @@ use Embed\Detectors\Title as Detector; +/** + * @extends Detector<\Embed\Adapters\ImageShack\Extractor> + */ class Title extends Detector { public function detect(): ?string { $api = $this->extractor->getApi(); - return $api->str('title') - ?: parent::detect(); + $result = $api->str('title'); + return (is_string($result) && trim($result) !== '') ? $result : parent::detect(); } } diff --git a/src/Adapters/ImageShack/Extractor.php b/src/Adapters/ImageShack/Extractor.php index c865c7e1..416228d3 100644 --- a/src/Adapters/ImageShack/Extractor.php +++ b/src/Adapters/ImageShack/Extractor.php @@ -4,11 +4,28 @@ namespace Embed\Adapters\ImageShack; use Embed\Extractor as Base; +use Embed\Http\Crawler; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\UriInterface; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { private Api $api; + public function __construct( + UriInterface $uri, + RequestInterface $request, + ResponseInterface $response, + Crawler $crawler + ) { + parent::__construct($uri, $request, $response, $crawler); + $this->api = new Api($this); + } + public function getApi(): Api { return $this->api; @@ -16,8 +33,6 @@ public function getApi(): Api public function createCustomDetectors(): array { - $this->api = new Api($this); - return [ 'authorName' => new Detectors\AuthorName($this), 'authorUrl' => new Detectors\AuthorUrl($this), diff --git a/src/Adapters/Instagram/Extractor.php b/src/Adapters/Instagram/Extractor.php index 8e0e73ee..c7a7802e 100644 --- a/src/Adapters/Instagram/Extractor.php +++ b/src/Adapters/Instagram/Extractor.php @@ -9,6 +9,9 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\UriInterface; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { public function __construct(UriInterface $uri, RequestInterface $request, ResponseInterface $response, Crawler $crawler) diff --git a/src/Adapters/Instagram/OEmbed.php b/src/Adapters/Instagram/OEmbed.php index 427a7ed4..a0c7bd20 100644 --- a/src/Adapters/Instagram/OEmbed.php +++ b/src/Adapters/Instagram/OEmbed.php @@ -14,7 +14,7 @@ protected function detectEndpoint(): ?UriInterface { $token = $this->extractor->getSetting('instagram:token'); - if (!$token) { + if (!is_string($token) || $token === '') { return null; } diff --git a/src/Adapters/Pinterest/Detectors/Code.php b/src/Adapters/Pinterest/Detectors/Code.php index 4d38724e..626975bd 100644 --- a/src/Adapters/Pinterest/Detectors/Code.php +++ b/src/Adapters/Pinterest/Detectors/Code.php @@ -8,12 +8,19 @@ use function Embed\html; use function Embed\matchPath; +/** + * @extends Detector<\Embed\Adapters\Pinterest\Extractor> + */ class Code extends Detector { public function detect(): ?EmbedCode { - return parent::detect() - ?: $this->fallback(); + $result = parent::detect(); + if ($result !== null) { + return $result; + } + + return $this->fallback(); } private function fallback(): ?EmbedCode diff --git a/src/Adapters/Pinterest/Extractor.php b/src/Adapters/Pinterest/Extractor.php index 5b5c40fa..5c627aa5 100644 --- a/src/Adapters/Pinterest/Extractor.php +++ b/src/Adapters/Pinterest/Extractor.php @@ -5,6 +5,9 @@ use Embed\Extractor as Base; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { public function createCustomDetectors(): array diff --git a/src/Adapters/Sassmeister/Detectors/Code.php b/src/Adapters/Sassmeister/Detectors/Code.php index 7ad83746..f421a52f 100644 --- a/src/Adapters/Sassmeister/Detectors/Code.php +++ b/src/Adapters/Sassmeister/Detectors/Code.php @@ -8,12 +8,15 @@ use function Embed\html; use function Embed\matchPath; +/** + * @extends Detector<\Embed\Adapters\Sassmeister\Extractor> + */ class Code extends Detector { public function detect(): ?EmbedCode { - return parent::detect() - ?: $this->fallback(); + $result = parent::detect(); + return $result !== null ? $result : $this->fallback(); } private function fallback(): ?EmbedCode diff --git a/src/Adapters/Sassmeister/Extractor.php b/src/Adapters/Sassmeister/Extractor.php index e36e3dc6..4e3cde5a 100644 --- a/src/Adapters/Sassmeister/Extractor.php +++ b/src/Adapters/Sassmeister/Extractor.php @@ -5,6 +5,9 @@ use Embed\Extractor as Base; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { public function createCustomDetectors(): array diff --git a/src/Adapters/Slides/Detectors/Code.php b/src/Adapters/Slides/Detectors/Code.php index 5ae51422..2db43646 100644 --- a/src/Adapters/Slides/Detectors/Code.php +++ b/src/Adapters/Slides/Detectors/Code.php @@ -8,12 +8,15 @@ use Embed\EmbedCode; use function Embed\html; +/** + * @extends Detector<\Embed\Adapters\Slides\Extractor> + */ class Code extends Detector { - public function detect(): ?EmbedCode + public function detect(): EmbedCode { - return parent::detect() - ?: $this->fallback(); + $result = parent::detect(); + return $result !== null ? $result : $this->fallback(); } private function fallback(): EmbedCode diff --git a/src/Adapters/Slides/Extractor.php b/src/Adapters/Slides/Extractor.php index 96900794..c7d9daac 100644 --- a/src/Adapters/Slides/Extractor.php +++ b/src/Adapters/Slides/Extractor.php @@ -5,6 +5,9 @@ use Embed\Extractor as Base; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { public function createCustomDetectors(): array diff --git a/src/Adapters/Snipplr/Detectors/Code.php b/src/Adapters/Snipplr/Detectors/Code.php index aadbb1d9..a4968627 100644 --- a/src/Adapters/Snipplr/Detectors/Code.php +++ b/src/Adapters/Snipplr/Detectors/Code.php @@ -8,12 +8,15 @@ use function Embed\html; use function Embed\matchPath; +/** + * @extends Detector<\Embed\Adapters\Snipplr\Extractor> + */ class Code extends Detector { public function detect(): ?EmbedCode { - return parent::detect() - ?: $this->fallback(); + $result = parent::detect(); + return $result !== null ? $result : $this->fallback(); } private function fallback(): ?EmbedCode diff --git a/src/Adapters/Snipplr/Extractor.php b/src/Adapters/Snipplr/Extractor.php index a0a73086..fc349555 100644 --- a/src/Adapters/Snipplr/Extractor.php +++ b/src/Adapters/Snipplr/Extractor.php @@ -5,6 +5,9 @@ use Embed\Extractor as Base; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { public function createCustomDetectors(): array diff --git a/src/Adapters/Twitch/Detectors/Code.php b/src/Adapters/Twitch/Detectors/Code.php index 1f333bcf..b06f39f9 100644 --- a/src/Adapters/Twitch/Detectors/Code.php +++ b/src/Adapters/Twitch/Detectors/Code.php @@ -7,12 +7,15 @@ use Embed\EmbedCode; use function Embed\html; +/** + * @extends Detector<\Embed\Adapters\Twitch\Extractor> + */ class Code extends Detector { public function detect(): ?EmbedCode { - return parent::detect() - ?: $this->fallback(); + $result = parent::detect(); + return $result !== null ? $result : $this->fallback(); } private function fallback(): ?EmbedCode @@ -20,15 +23,17 @@ private function fallback(): ?EmbedCode $path = $this->extractor->getUri()->getPath(); $parent = $this->extractor->getSetting('twitch:parent'); - if ($id = self::getVideoId($path)) { - $code = $parent + $id = self::getVideoId($path); + if ($id !== null) { + $code = $parent !== null ? self::generateIframeCode(['id' => $id, 'parent' => $parent]) : self::generateJsCode('video', $id); return new EmbedCode($code, 620, 378); } - if ($id = self::getChannelId($path)) { - $code = $parent + $id = self::getChannelId($path); + if ($id !== null) { + $code = $parent !== null ? self::generateIframeCode(['channel' => $id, 'parent' => $parent]) : self::generateJsCode('channel', $id); return new EmbedCode($code, 620, 378); @@ -39,7 +44,7 @@ private function fallback(): ?EmbedCode private static function getVideoId(string $path): ?string { - if (preg_match('#^/videos/(\d+)$#', $path, $matches)) { + if (preg_match('#^/videos/(\d+)$#', $path, $matches) === 1) { return $matches[1]; } @@ -48,13 +53,16 @@ private static function getVideoId(string $path): ?string private static function getChannelId(string $path): ?string { - if (preg_match('#^/(\w+)$#', $path, $matches)) { + if (preg_match('#^/(\w+)$#', $path, $matches) === 1) { return $matches[1]; } return null; } + /** + * @param array $params + */ private static function generateIframeCode(array $params): string { $query = http_build_query(['autoplay' => 'false'] + $params); @@ -69,7 +77,7 @@ private static function generateIframeCode(array $params): string ]); } - private static function generateJsCode($key, $value) + private static function generateJsCode(string $key, string $value): string { return << diff --git a/src/Adapters/Twitch/Extractor.php b/src/Adapters/Twitch/Extractor.php index a36d27f3..f54552d0 100644 --- a/src/Adapters/Twitch/Extractor.php +++ b/src/Adapters/Twitch/Extractor.php @@ -5,6 +5,9 @@ use Embed\Extractor as Base; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { public function createCustomDetectors(): array diff --git a/src/Adapters/Twitter/Api.php b/src/Adapters/Twitter/Api.php index a03be6d1..e60caaae 100644 --- a/src/Adapters/Twitter/Api.php +++ b/src/Adapters/Twitter/Api.php @@ -10,23 +10,26 @@ class Api { use HttpApiTrait; + /** + * @return array + */ protected function fetchData(): array { $token = $this->extractor->getSetting('twitter:token'); - if (!$token) { + if (!is_string($token) || $token === '') { return []; } - + $uri = $this->extractor->getUri(); $id = getDirectory($uri->getPath(), 2); - if (empty($id)) { + if ($id === null || $id === '' || $id === '0') { return []; } - $this->extractor->getCrawler()->addDefaultHeaders(array('Authorization' => "Bearer $token")); + $this->extractor->getCrawler()->addDefaultHeaders(array('Authorization' => "Bearer {$token}")); $this->endpoint = $this->extractor->getCrawler()->createUri("https://api.twitter.com/2/tweets/{$id}?expansions=author_id,attachments.media_keys&tweet.fields=created_at&media.fields=preview_image_url,url&user.fields=id,name"); return $this->fetchJSON($this->endpoint); diff --git a/src/Adapters/Twitter/Detectors/AuthorName.php b/src/Adapters/Twitter/Detectors/AuthorName.php index 5409ad4b..c24d84dc 100644 --- a/src/Adapters/Twitter/Detectors/AuthorName.php +++ b/src/Adapters/Twitter/Detectors/AuthorName.php @@ -3,15 +3,19 @@ namespace Embed\Adapters\Twitter\Detectors; +use Embed\Adapters\Twitter\Extractor; use Embed\Detectors\AuthorName as Detector; +/** + * @extends Detector<\Embed\Adapters\Twitter\Extractor> + */ class AuthorName extends Detector { public function detect(): ?string { $api = $this->extractor->getApi(); - - return $api->str('includes', 'users', '0', 'name') - ?: parent::detect(); + + $result = $api->str('includes', 'users', '0', 'name'); + return (is_string($result) && trim($result) !== '') ? $result : parent::detect(); } } diff --git a/src/Adapters/Twitter/Detectors/AuthorUrl.php b/src/Adapters/Twitter/Detectors/AuthorUrl.php index 23a11d7b..d2b38b93 100644 --- a/src/Adapters/Twitter/Detectors/AuthorUrl.php +++ b/src/Adapters/Twitter/Detectors/AuthorUrl.php @@ -3,18 +3,25 @@ namespace Embed\Adapters\Twitter\Detectors; +use Embed\Adapters\Twitter\Extractor; use Embed\Detectors\AuthorUrl as Detector; use Psr\Http\Message\UriInterface; +/** + * @extends Detector<\Embed\Adapters\Twitter\Extractor> + */ class AuthorUrl extends Detector { public function detect(): ?UriInterface { - $api = $this->extractor->getApi(); + $extractor = $this->extractor; + $api = $extractor->getApi(); $username = $api->str('includes', 'users', '0', 'username'); - if ($username) { - return $this->extractor->getCrawler()->createUri("https://twitter.com/{$username}"); + // Exclude empty string and '0' to maintain original truthy check behavior + // The string '0' is not a valid Twitter username and should not generate a URL + if (is_string($username) && $username !== '' && $username !== '0') { + return $extractor->getCrawler()->createUri("https://twitter.com/{$username}"); } return parent::detect(); diff --git a/src/Adapters/Twitter/Detectors/Description.php b/src/Adapters/Twitter/Detectors/Description.php index 2b19afad..3d4a9585 100644 --- a/src/Adapters/Twitter/Detectors/Description.php +++ b/src/Adapters/Twitter/Detectors/Description.php @@ -3,15 +3,19 @@ namespace Embed\Adapters\Twitter\Detectors; +use Embed\Adapters\Twitter\Extractor; use Embed\Detectors\Description as Detector; +/** + * @extends Detector<\Embed\Adapters\Twitter\Extractor> + */ class Description extends Detector { public function detect(): ?string { $api = $this->extractor->getApi(); - return $api->str('data', 'text') - ?: parent::detect(); + $result = $api->str('data', 'text'); + return (is_string($result) && trim($result) !== '') ? $result : parent::detect(); } } diff --git a/src/Adapters/Twitter/Detectors/Image.php b/src/Adapters/Twitter/Detectors/Image.php index 90344335..f8ca1019 100644 --- a/src/Adapters/Twitter/Detectors/Image.php +++ b/src/Adapters/Twitter/Detectors/Image.php @@ -3,23 +3,27 @@ namespace Embed\Adapters\Twitter\Detectors; +use Embed\Adapters\Twitter\Extractor; use Embed\Detectors\Image as Detector; use Psr\Http\Message\UriInterface; +/** + * @extends Detector<\Embed\Adapters\Twitter\Extractor> + */ class Image extends Detector { public function detect(): ?UriInterface { $api = $this->extractor->getApi(); $preview = $api->url('includes', 'media', '0', 'preview_image_url'); - - if ($preview) { + + if ($preview !== null) { return $preview; } $regular = $api->url('includes', 'media', '0', 'url'); - if ($regular) { + if ($regular !== null) { return $regular; } diff --git a/src/Adapters/Twitter/Detectors/ProviderName.php b/src/Adapters/Twitter/Detectors/ProviderName.php index e5c99c0b..93bed7aa 100644 --- a/src/Adapters/Twitter/Detectors/ProviderName.php +++ b/src/Adapters/Twitter/Detectors/ProviderName.php @@ -5,6 +5,9 @@ use Embed\Detectors\ProviderName as Detector; +/** + * @extends Detector<\Embed\Adapters\Twitter\Extractor> + */ class ProviderName extends Detector { public function detect(): string diff --git a/src/Adapters/Twitter/Detectors/PublishedTime.php b/src/Adapters/Twitter/Detectors/PublishedTime.php index 73672988..a68e02ef 100644 --- a/src/Adapters/Twitter/Detectors/PublishedTime.php +++ b/src/Adapters/Twitter/Detectors/PublishedTime.php @@ -4,15 +4,19 @@ namespace Embed\Adapters\Twitter\Detectors; use DateTime; +use Embed\Adapters\Twitter\Extractor; use Embed\Detectors\PublishedTime as Detector; +/** + * @extends Detector<\Embed\Adapters\Twitter\Extractor> + */ class PublishedTime extends Detector { public function detect(): ?DateTime { $api = $this->extractor->getApi(); - return $api->time('data', 'created_at') - ?: parent::detect(); + $result = $api->time('data', 'created_at'); + return $result !== null ? $result : parent::detect(); } } diff --git a/src/Adapters/Twitter/Detectors/Title.php b/src/Adapters/Twitter/Detectors/Title.php index 58c770bb..6774f7af 100644 --- a/src/Adapters/Twitter/Detectors/Title.php +++ b/src/Adapters/Twitter/Detectors/Title.php @@ -3,8 +3,12 @@ namespace Embed\Adapters\Twitter\Detectors; +use Embed\Adapters\Twitter\Extractor; use Embed\Detectors\Title as Detector; +/** + * @extends Detector<\Embed\Adapters\Twitter\Extractor> + */ class Title extends Detector { public function detect(): ?string @@ -12,7 +16,7 @@ public function detect(): ?string $api = $this->extractor->getApi(); $name = $api->str('includes', 'users', '0', 'name'); - if ($name) { + if ($name !== null) { return "Tweet by $name"; } diff --git a/src/Adapters/Twitter/Extractor.php b/src/Adapters/Twitter/Extractor.php index 2cb2c459..d505e5ee 100644 --- a/src/Adapters/Twitter/Extractor.php +++ b/src/Adapters/Twitter/Extractor.php @@ -4,11 +4,28 @@ namespace Embed\Adapters\Twitter; use Embed\Extractor as Base; +use Embed\Http\Crawler; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\UriInterface; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { private Api $api; + public function __construct( + UriInterface $uri, + RequestInterface $request, + ResponseInterface $response, + Crawler $crawler + ) { + parent::__construct($uri, $request, $response, $crawler); + $this->api = new Api($this); + } + public function getApi(): Api { return $this->api; @@ -16,8 +33,6 @@ public function getApi(): Api public function createCustomDetectors(): array { - $this->api = new Api($this); - return [ 'authorName' => new Detectors\AuthorName($this), 'authorUrl' => new Detectors\AuthorUrl($this), diff --git a/src/Adapters/Wikipedia/Api.php b/src/Adapters/Wikipedia/Api.php index 36b5233b..4ddc3025 100644 --- a/src/Adapters/Wikipedia/Api.php +++ b/src/Adapters/Wikipedia/Api.php @@ -11,6 +11,9 @@ class Api { use HttpApiTrait; + /** + * @return array + */ protected function fetchData(): array { $uri = $this->extractor->getUri(); @@ -33,8 +36,17 @@ protected function fetchData(): array ])); $data = $this->fetchJSON($this->endpoint); - $pages = $data['query']['pages'] ?? null; - return $pages ? current($pages) : null; + if (isset($data['query']) && is_array($data['query']) && isset($data['query']['pages']) && is_array($data['query']['pages'])) { + $pages = $data['query']['pages']; + $result = current($pages); + if (is_array($result)) { + /** @var array */ + $typedResult = $result; + return $typedResult; + } + } + + return []; } } diff --git a/src/Adapters/Wikipedia/Detectors/Description.php b/src/Adapters/Wikipedia/Detectors/Description.php index dc281dc0..f138d56f 100644 --- a/src/Adapters/Wikipedia/Detectors/Description.php +++ b/src/Adapters/Wikipedia/Detectors/Description.php @@ -5,13 +5,16 @@ use Embed\Detectors\Description as Detector; +/** + * @extends Detector<\Embed\Adapters\Wikipedia\Extractor> + */ class Description extends Detector { public function detect(): ?string { $api = $this->extractor->getApi(); - return $api->str('extract') - ?: parent::detect(); + $result = $api->str('extract'); + return (is_string($result) && trim($result) !== '') ? $result : parent::detect(); } } diff --git a/src/Adapters/Wikipedia/Detectors/Title.php b/src/Adapters/Wikipedia/Detectors/Title.php index 0b531335..44d8531f 100644 --- a/src/Adapters/Wikipedia/Detectors/Title.php +++ b/src/Adapters/Wikipedia/Detectors/Title.php @@ -3,15 +3,19 @@ namespace Embed\Adapters\Wikipedia\Detectors; +use Embed\Adapters\Wikipedia\Extractor; use Embed\Detectors\Title as Detector; +/** + * @extends Detector<\Embed\Adapters\Wikipedia\Extractor> + */ class Title extends Detector { public function detect(): ?string { $api = $this->extractor->getApi(); - return $api->str('title') - ?: parent::detect(); + $result = $api->str('title'); + return (is_string($result) && trim($result) !== '') ? $result : parent::detect(); } } diff --git a/src/Adapters/Wikipedia/Extractor.php b/src/Adapters/Wikipedia/Extractor.php index 6cc0e1f2..c432707f 100644 --- a/src/Adapters/Wikipedia/Extractor.php +++ b/src/Adapters/Wikipedia/Extractor.php @@ -4,11 +4,28 @@ namespace Embed\Adapters\Wikipedia; use Embed\Extractor as Base; +use Embed\Http\Crawler; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\UriInterface; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { private Api $api; + public function __construct( + UriInterface $uri, + RequestInterface $request, + ResponseInterface $response, + Crawler $crawler + ) { + parent::__construct($uri, $request, $response, $crawler); + $this->api = new Api($this); + } + public function getApi(): Api { return $this->api; @@ -16,8 +33,6 @@ public function getApi(): Api public function createCustomDetectors(): array { - $this->api = new Api($this); - return [ 'title' => new Detectors\Title($this), 'description' => new Detectors\Description($this), diff --git a/src/Adapters/Youtube/Detectors/Feeds.php b/src/Adapters/Youtube/Detectors/Feeds.php index aac95531..b63aeeb7 100644 --- a/src/Adapters/Youtube/Detectors/Feeds.php +++ b/src/Adapters/Youtube/Detectors/Feeds.php @@ -8,6 +8,9 @@ use function Embed\matchPath; use Psr\Http\Message\UriInterface; +/** + * @extends Detector<\Embed\Adapters\Youtube\Extractor> + */ class Feeds extends Detector { /** @@ -15,10 +18,13 @@ class Feeds extends Detector */ public function detect(): array { - return parent::detect() - ?: $this->fallback(); + $result = parent::detect(); + return $result !== [] ? $result : $this->fallback(); } + /** + * @return UriInterface[] + */ private function fallback(): array { $uri = $this->extractor->getUri(); diff --git a/src/Adapters/Youtube/Extractor.php b/src/Adapters/Youtube/Extractor.php index ce299d28..1b81d63f 100644 --- a/src/Adapters/Youtube/Extractor.php +++ b/src/Adapters/Youtube/Extractor.php @@ -5,6 +5,9 @@ use Embed\Extractor as Base; +/** + * @template-extends Base<\Embed\Detectors\Detector> + */ class Extractor extends Base { public function createCustomDetectors(): array diff --git a/src/ApiTrait.php b/src/ApiTrait.php index 3dcb781b..696b5a72 100644 --- a/src/ApiTrait.php +++ b/src/ApiTrait.php @@ -10,28 +10,35 @@ trait ApiTrait { protected Extractor $extractor; - private array $data; + /** @var array */ + private array $data = []; public function __construct(Extractor $extractor) { $this->extractor = $extractor; } + /** + * @return array + */ public function all(): array { - if (!isset($this->data)) { + if ($this->data === []) { $this->data = $this->fetchData(); } return $this->data; } + /** + * @return mixed + */ public function get(string ...$keys) { $data = $this->all(); foreach ($keys as $key) { - if (!isset($data[$key])) { + if (!is_array($data) || !isset($data[$key])) { return null; } @@ -49,13 +56,22 @@ public function str(string ...$keys): ?string $value = array_shift($value); } - return $value ? clean((string) $value) : null; + if (is_string($value)) { + return clean($value); + } elseif (is_scalar($value)) { + return clean((string) $value); + } + + return null; } + /** + * @return string[] + */ public function strAll(string ...$keys): array { $all = (array) $this->get(...$keys); - return array_filter(array_map(fn ($value) => clean($value), $all)); + return array_filter(array_map(fn ($value) => is_string($value) ? clean($value) : null, $all), fn ($value) => $value !== null); } public function html(string ...$keys): ?string @@ -66,7 +82,13 @@ public function html(string ...$keys): ?string $value = array_shift($value); } - return $value ? clean((string) $value, true) : null; + if (is_string($value)) { + return clean($value, true); + } elseif (is_scalar($value)) { + return clean((string) $value, true); + } + + return null; } public function int(string ...$keys): ?int @@ -85,7 +107,7 @@ public function url(string ...$keys): ?UriInterface $url = $this->str(...$keys); try { - return $url ? $this->extractor->resolveUri($url) : null; + return $url !== null ? $this->extractor->resolveUri($url) : null; } catch (Throwable $error) { return null; } @@ -94,13 +116,13 @@ public function url(string ...$keys): ?UriInterface public function time(string ...$keys): ?DateTime { $time = $this->str(...$keys); - $datetime = $time ? date_create($time) : null; + $datetime = $time !== null ? date_create($time) : null; - if (!$datetime && $time && ctype_digit($time)) { + if ($datetime === false && $time !== null && ctype_digit($time)) { $datetime = date_create_from_format('U', $time); } - return ($datetime && $datetime->getTimestamp() > 0) ? $datetime : null; + return ($datetime !== false && $datetime !== null && $datetime->getTimestamp() > 0) ? $datetime : null; } abstract protected function fetchData(): array; diff --git a/src/Detectors/AuthorName.php b/src/Detectors/AuthorName.php index 17433c41..a65881fe 100644 --- a/src/Detectors/AuthorName.php +++ b/src/Detectors/AuthorName.php @@ -3,6 +3,10 @@ namespace Embed\Detectors; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class AuthorName extends Detector { public function detect(): ?string @@ -10,15 +14,19 @@ public function detect(): ?string $oembed = $this->extractor->getOEmbed(); $metas = $this->extractor->getMetas(); - return $oembed->str('author_name') - ?: $metas->str( - 'article:author', - 'book:author', - 'sailthru.author', - 'lp.article:author', - 'twitter:creator', - 'dcterms.creator', - 'author' - ); + $result = $oembed->str('author_name'); + if (is_string($result) && trim($result) !== '') { + return $result; + } + + return $metas->str( + 'article:author', + 'book:author', + 'sailthru.author', + 'lp.article:author', + 'twitter:creator', + 'dcterms.creator', + 'author' + ); } } diff --git a/src/Detectors/AuthorUrl.php b/src/Detectors/AuthorUrl.php index fe1b564c..9903e638 100644 --- a/src/Detectors/AuthorUrl.php +++ b/src/Detectors/AuthorUrl.php @@ -5,14 +5,18 @@ use Psr\Http\Message\UriInterface; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class AuthorUrl extends Detector { public function detect(): ?UriInterface { $oembed = $this->extractor->getOEmbed(); - return $oembed->url('author_url') - ?: $this->detectFromTwitter(); + $result = $oembed->url('author_url'); + return $result !== null ? $result : $this->detectFromTwitter(); } private function detectFromTwitter(): ?UriInterface @@ -22,7 +26,7 @@ private function detectFromTwitter(): ?UriInterface $user = $metas->str('twitter:creator'); - return $user + return $user !== null ? $crawler->createUri(sprintf('https://twitter.com/%s', ltrim($user, '@'))) : null; } diff --git a/src/Detectors/Cms.php b/src/Detectors/Cms.php index c43f4d8f..d3df134b 100644 --- a/src/Detectors/Cms.php +++ b/src/Detectors/Cms.php @@ -3,6 +3,10 @@ namespace Embed\Detectors; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class Cms extends Detector { public const BLOGSPOT = 'blogspot'; @@ -12,9 +16,9 @@ class Cms extends Detector public function detect(): ?string { - $cms = self::detectFromHost($this->extractor->url->getHost()); + $cms = self::detectFromHost($this->extractor->getUri()->getHost()); - if ($cms) { + if ($cms !== null) { return $cms; } @@ -22,7 +26,8 @@ public function detect(): ?string $generators = $document->select('.//meta', ['name' => 'generator'])->strAll('content'); foreach ($generators as $generator) { - if ($cms = self::detectFromGenerator($generator)) { + $cms = self::detectFromGenerator($generator); + if ($cms !== null) { return $cms; } } diff --git a/src/Detectors/Code.php b/src/Detectors/Code.php index a7b91600..870c82c9 100644 --- a/src/Detectors/Code.php +++ b/src/Detectors/Code.php @@ -6,14 +6,30 @@ use Embed\EmbedCode; use function Embed\html; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class Code extends Detector { public function detect(): ?EmbedCode { - return $this->detectFromEmbed() - ?: $this->detectFromOpenGraph() - ?: $this->detectFromTwitter() - ?: $this->detectFromContentType(); + $result = $this->detectFromEmbed(); + if ($result !== null) { + return $result; + } + + $result = $this->detectFromOpenGraph(); + if ($result !== null) { + return $result; + } + + $result = $this->detectFromTwitter(); + if ($result !== null) { + return $result; + } + + return $this->detectFromContentType(); } private function detectFromEmbed(): ?EmbedCode @@ -21,7 +37,7 @@ private function detectFromEmbed(): ?EmbedCode $oembed = $this->extractor->getOEmbed(); $html = $oembed->html('html'); - if (!$html) { + if ($html === null) { return null; } @@ -38,11 +54,12 @@ private function detectFromOpenGraph(): ?EmbedCode $url = $metas->url('og:video:secure_url', 'og:video:url', 'og:video'); - if (!$url) { + if ($url === null) { return null; } - if (!($type = pathinfo($url->getPath(), PATHINFO_EXTENSION))) { + $type = pathinfo($url->getPath(), PATHINFO_EXTENSION); + if ($type === '') { $type = $metas->str('og:video_type'); } @@ -87,7 +104,7 @@ private function detectFromTwitter(): ?EmbedCode $url = $metas->url('twitter:player'); - if (!$url) { + if ($url === null) { return null; } @@ -105,14 +122,14 @@ private function detectFromTwitter(): ?EmbedCode return new EmbedCode($code, $width, $height); } - private function detectFromContentType() + private function detectFromContentType(): ?EmbedCode { if (!$this->extractor->getResponse()->hasHeader('content-type')) { return null; } $contentType = $this->extractor->getResponse()->getHeader('content-type')[0]; - $isBinary = !preg_match('/(text|html|json)/', strtolower($contentType)); + $isBinary = preg_match('/(text|html|json)/', strtolower($contentType)) !== 1; if (!$isBinary) { return null; } diff --git a/src/Detectors/Description.php b/src/Detectors/Description.php index 90892d11..cc845afb 100644 --- a/src/Detectors/Description.php +++ b/src/Detectors/Description.php @@ -3,6 +3,10 @@ namespace Embed\Detectors; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class Description extends Detector { public function detect(): ?string @@ -11,18 +15,26 @@ public function detect(): ?string $metas = $this->extractor->getMetas(); $ld = $this->extractor->getLinkedData(); - return $oembed->str('description') - ?: $metas->str( - 'og:description', - 'twitter:description', - 'lp:description', - 'description', - 'article:description', - 'dcterms.description', - 'sailthru.description', - 'excerpt', - 'article.summary' - ) - ?: $ld->str('description'); + $result = $oembed->str('description'); + if (is_string($result) && trim($result) !== '') { + return $result; + } + + $result = $metas->str( + 'og:description', + 'twitter:description', + 'lp:description', + 'description', + 'article:description', + 'dcterms.description', + 'sailthru.description', + 'excerpt', + 'article.summary' + ); + if (is_string($result) && trim($result) !== '') { + return $result; + } + + return $ld->str('description'); } } diff --git a/src/Detectors/Detector.php b/src/Detectors/Detector.php index 1d6bfb00..036087e9 100644 --- a/src/Detectors/Detector.php +++ b/src/Detectors/Detector.php @@ -5,19 +5,30 @@ use Embed\Extractor; +/** + * @template TExtractor of Extractor + */ abstract class Detector { + /** @var TExtractor */ protected Extractor $extractor; - private array $cache; + /** @var array */ + private array $cache = []; + /** + * @param TExtractor $extractor + */ public function __construct(Extractor $extractor) { $this->extractor = $extractor; } + /** + * @return mixed + */ public function get() { - if (!isset($this->cache)) { + if (!isset($this->cache['cached'])) { $this->cache = [ 'cached' => true, 'value' => $this->detect(), @@ -27,5 +38,8 @@ public function get() return $this->cache['value']; } + /** + * @return mixed + */ abstract public function detect(); } diff --git a/src/Detectors/Favicon.php b/src/Detectors/Favicon.php index 93a0a283..2e0b1930 100644 --- a/src/Detectors/Favicon.php +++ b/src/Detectors/Favicon.php @@ -5,14 +5,26 @@ use Psr\Http\Message\UriInterface; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class Favicon extends Detector { public function detect(): UriInterface { $document = $this->extractor->getDocument(); - return $document->link('shortcut icon') - ?: $document->link('icon') - ?: $this->extractor->getUri()->withPath('/favicon.ico')->withQuery(''); + $result = $document->link('shortcut icon'); + if ($result !== null) { + return $result; + } + + $result = $document->link('icon'); + if ($result !== null) { + return $result; + } + + return $this->extractor->getUri()->withPath('/favicon.ico')->withQuery(''); } } diff --git a/src/Detectors/Feeds.php b/src/Detectors/Feeds.php index dab87f09..b00c2051 100644 --- a/src/Detectors/Feeds.php +++ b/src/Detectors/Feeds.php @@ -3,9 +3,14 @@ namespace Embed\Detectors; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class Feeds extends Detector { - private static $types = [ + /** @var string[] */ + private static array $types = [ 'application/atom+xml', 'application/json', 'application/rdf+xml', @@ -25,7 +30,7 @@ public function detect(): array foreach (self::$types as $type) { $href = $document->link('alternate', ['type' => $type]); - if ($href) { + if ($href !== null) { $feeds[] = $href; } } diff --git a/src/Detectors/Icon.php b/src/Detectors/Icon.php index 0d114f03..eae81474 100644 --- a/src/Detectors/Icon.php +++ b/src/Detectors/Icon.php @@ -5,16 +5,36 @@ use Psr\Http\Message\UriInterface; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class Icon extends Detector { public function detect(): ?UriInterface { $document = $this->extractor->getDocument(); - return $document->link('apple-touch-icon-precomposed') - ?: $document->link('apple-touch-icon') - ?: $document->link('icon', ['sizes' => '144x144']) - ?: $document->link('icon', ['sizes' => '96x96']) - ?: $document->link('icon', ['sizes' => '48x48']); + $result = $document->link('apple-touch-icon-precomposed'); + if ($result !== null) { + return $result; + } + + $result = $document->link('apple-touch-icon'); + if ($result !== null) { + return $result; + } + + $result = $document->link('icon', ['sizes' => '144x144']); + if ($result !== null) { + return $result; + } + + $result = $document->link('icon', ['sizes' => '96x96']); + if ($result !== null) { + return $result; + } + + return $document->link('icon', ['sizes' => '48x48']); } } diff --git a/src/Detectors/Image.php b/src/Detectors/Image.php index 04562f26..10c7ae70 100644 --- a/src/Detectors/Image.php +++ b/src/Detectors/Image.php @@ -5,6 +5,10 @@ use Psr\Http\Message\UriInterface; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class Image extends Detector { public function detect(): ?UriInterface @@ -14,16 +18,40 @@ public function detect(): ?UriInterface $metas = $this->extractor->getMetas(); $ld = $this->extractor->getLinkedData(); - return $oembed->url('image') - ?: $oembed->url('thumbnail') - ?: $oembed->url('thumbnail_url') - ?: $metas->url('og:image', 'og:image:url', 'og:image:secure_url', 'twitter:image', 'twitter:image:src', 'lp:image') - ?: $document->link('image_src') - ?: $ld->url('image.url') - ?: $this->detectFromContentType(); + $result = $oembed->url('image'); + if ($result !== null) { + return $result; + } + + $result = $oembed->url('thumbnail'); + if ($result !== null) { + return $result; + } + + $result = $oembed->url('thumbnail_url'); + if ($result !== null) { + return $result; + } + + $result = $metas->url('og:image', 'og:image:url', 'og:image:secure_url', 'twitter:image', 'twitter:image:src', 'lp:image'); + if ($result !== null) { + return $result; + } + + $result = $document->link('image_src'); + if ($result !== null) { + return $result; + } + + $result = $ld->url('image.url'); + if ($result !== null) { + return $result; + } + + return $this->detectFromContentType(); } - private function detectFromContentType() + private function detectFromContentType(): ?\Psr\Http\Message\UriInterface { if (!$this->extractor->getResponse()->hasHeader('content-type')) { return null; @@ -34,5 +62,7 @@ private function detectFromContentType() if (strpos($contentType, 'image/') === 0) { return $this->extractor->getUri(); } + + return null; } } diff --git a/src/Detectors/Keywords.php b/src/Detectors/Keywords.php index 000a1e06..472c9ed7 100644 --- a/src/Detectors/Keywords.php +++ b/src/Detectors/Keywords.php @@ -3,8 +3,15 @@ namespace Embed\Detectors; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class Keywords extends Detector { + /** + * @return string[] + */ public function detect(): array { $tags = []; @@ -24,25 +31,30 @@ public function detect(): array foreach ($types as $type) { $value = $metas->strAll($type); - if ($value) { + if ($value !== []) { $tags = array_merge($tags, self::toArray($value)); } } $value = $ld->strAll('keywords'); - if ($value) { + if ($value !== []) { $tags = array_merge($tags, self::toArray($value)); } + /** @var array */ $tags = array_map('mb_strtolower', $tags); $tags = array_unique($tags); - $tags = array_filter($tags); + $tags = array_filter($tags, fn ($value) => $value !== '' && $value !== '0'); $tags = array_values($tags); return $tags; } + /** + * @param string[] $keywords + * @return string[] + */ private static function toArray(array $keywords): array { $all = []; @@ -52,7 +64,7 @@ private static function toArray(array $keywords): array $tags = array_map('trim', $tags); $tags = array_filter( $tags, - fn ($value) => !empty($value) && substr($value, -3) !== '...' + fn ($value) => $value !== '' && $value !== '0' && substr($value, -3) !== '...' ); $all = array_merge($all, $tags); diff --git a/src/Detectors/Language.php b/src/Detectors/Language.php index e328260b..b8dd1e49 100644 --- a/src/Detectors/Language.php +++ b/src/Detectors/Language.php @@ -3,6 +3,10 @@ namespace Embed\Detectors; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class Language extends Detector { public function detect(): ?string @@ -11,10 +15,26 @@ public function detect(): ?string $metas = $this->extractor->getMetas(); $ld = $this->extractor->getLinkedData(); - return $document->select('/html')->str('lang') - ?: $document->select('/html')->str('xml:lang') - ?: $metas->str('language', 'lang', 'og:locale', 'dc:language') - ?: $document->select('.//meta', ['http-equiv' => 'content-language'])->str('content') - ?: $ld->str('inLanguage'); + $result = $document->select('/html')->str('lang'); + if (is_string($result) && trim($result) !== '') { + return $result; + } + + $result = $document->select('/html')->str('xml:lang'); + if (is_string($result) && trim($result) !== '') { + return $result; + } + + $result = $metas->str('language', 'lang', 'og:locale', 'dc:language'); + if (is_string($result) && trim($result) !== '') { + return $result; + } + + $result = $document->select('.//meta', ['http-equiv' => 'content-language'])->str('content'); + if (is_string($result) && trim($result) !== '') { + return $result; + } + + return $ld->str('inLanguage'); } } diff --git a/src/Detectors/Languages.php b/src/Detectors/Languages.php index 6fbe4e33..5f899c11 100644 --- a/src/Detectors/Languages.php +++ b/src/Detectors/Languages.php @@ -5,10 +5,14 @@ use function Embed\isEmpty; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class Languages extends Detector { /** - * @return \Psr\Http\Message\UriInterface[] + * @return array */ public function detect(): array { @@ -16,6 +20,10 @@ public function detect(): array $languages = []; foreach ($document->select('.//link[@hreflang]')->nodes() as $node) { + if (!$node instanceof \DOMElement) { + continue; + } + $language = $node->getAttribute('hreflang'); $href = $node->getAttribute('href'); diff --git a/src/Detectors/License.php b/src/Detectors/License.php index 5afddbf4..a9cf26df 100644 --- a/src/Detectors/License.php +++ b/src/Detectors/License.php @@ -3,6 +3,10 @@ namespace Embed\Detectors; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class License extends Detector { public function detect(): ?string @@ -10,7 +14,7 @@ public function detect(): ?string $oembed = $this->extractor->getOEmbed(); $metas = $this->extractor->getMetas(); - return $oembed->str('license_url') - ?: $metas->str('copyright'); + $license = $oembed->str('license_url'); + return $license !== null ? $license : $metas->str('copyright'); } } diff --git a/src/Detectors/ProviderName.php b/src/Detectors/ProviderName.php index e92f2fc0..1340aa7b 100644 --- a/src/Detectors/ProviderName.php +++ b/src/Detectors/ProviderName.php @@ -3,8 +3,13 @@ namespace Embed\Detectors; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class ProviderName extends Detector { + /** @var string[] */ private static array $suffixes; public function detect(): string @@ -12,14 +17,22 @@ public function detect(): string $oembed = $this->extractor->getOEmbed(); $metas = $this->extractor->getMetas(); - return $oembed->str('provider_name') - ?: $metas->str( - 'og:site_name', - 'dcterms.publisher', - 'publisher', - 'article:publisher' - ) - ?: ucfirst($this->fallback()); + $result = $oembed->str('provider_name'); + if (is_string($result) && trim($result) !== '') { + return $result; + } + + $result = $metas->str( + 'og:site_name', + 'dcterms.publisher', + 'publisher', + 'article:publisher' + ); + if (is_string($result) && trim($result) !== '') { + return $result; + } + + return ucfirst($this->fallback()); } private function fallback(): string @@ -45,10 +58,15 @@ private function fallback(): string } } + /** + * @return string[] + */ private static function getSuffixes(): array { if (!isset(self::$suffixes)) { - self::$suffixes = require dirname(__DIR__).'/resources/suffix.php'; + /** @var string[] */ + $suffixes = require dirname(__DIR__).'/resources/suffix.php'; + self::$suffixes = $suffixes; } return self::$suffixes; diff --git a/src/Detectors/ProviderUrl.php b/src/Detectors/ProviderUrl.php index 9ca9ab6d..9201179b 100644 --- a/src/Detectors/ProviderUrl.php +++ b/src/Detectors/ProviderUrl.php @@ -5,6 +5,10 @@ use Psr\Http\Message\UriInterface; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class ProviderUrl extends Detector { public function detect(): UriInterface @@ -12,9 +16,17 @@ public function detect(): UriInterface $oembed = $this->extractor->getOEmbed(); $metas = $this->extractor->getMetas(); - return $oembed->url('provider_url') - ?: $metas->url('og:website') - ?: $this->fallback(); + $result = $oembed->url('provider_url'); + if ($result !== null) { + return $result; + } + + $result = $metas->url('og:website'); + if ($result !== null) { + return $result; + } + + return $this->fallback(); } private function fallback(): UriInterface diff --git a/src/Detectors/PublishedTime.php b/src/Detectors/PublishedTime.php index f168120f..c6ed932d 100644 --- a/src/Detectors/PublishedTime.php +++ b/src/Detectors/PublishedTime.php @@ -5,6 +5,10 @@ use DateTime; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class PublishedTime extends Detector { public function detect(): ?DateTime @@ -13,34 +17,50 @@ public function detect(): ?DateTime $metas = $this->extractor->getMetas(); $ld = $this->extractor->getLinkedData(); - return $oembed->time('pubdate') - ?: $metas->time( - 'article:published_time', - 'created', - 'date', - 'datepublished', - 'music:release_date', - 'video:release_date', - 'newsrepublic:publish_date' - ) - ?: $ld->time( - 'pagePublished', - 'datePublished' - ) - ?: $this->detectFromPath() - ?: $metas->time( - 'pagerender', - 'pub_date', - 'publication-date', - 'lp.article:published_time', - 'lp.article:modified_time', - 'publish-date', - 'rc.datecreation', - 'timestamp', - 'sailthru.date', - 'article:modified_time', - 'dcterms.date' - ); + $result = $oembed->time('pubdate'); + if ($result !== null) { + return $result; + } + + $result = $metas->time( + 'article:published_time', + 'created', + 'date', + 'datepublished', + 'music:release_date', + 'video:release_date', + 'newsrepublic:publish_date' + ); + if ($result !== null) { + return $result; + } + + $result = $ld->time( + 'pagePublished', + 'datePublished' + ); + if ($result !== null) { + return $result; + } + + $result = $this->detectFromPath(); + if ($result !== null) { + return $result; + } + + return $metas->time( + 'pagerender', + 'pub_date', + 'publication-date', + 'lp.article:published_time', + 'lp.article:modified_time', + 'publish-date', + 'rc.datecreation', + 'timestamp', + 'sailthru.date', + 'article:modified_time', + 'dcterms.date' + ); } /** @@ -51,8 +71,9 @@ private function detectFromPath(): ?DateTime { $path = $this->extractor->getUri()->getPath(); - if (preg_match('#/(19|20)\d{2}/[0-1]?\d/[0-3]?\d/#', $path, $matches)) { - return date_create_from_format('/Y/m/d/', $matches[0]) ?: null; + if (preg_match('#/(19|20)\d{2}/[0-1]?\d/[0-3]?\d/#', $path, $matches) === 1) { + $date = date_create_from_format('/Y/m/d/', $matches[0]); + return $date !== false ? $date : null; } return null; diff --git a/src/Detectors/Redirect.php b/src/Detectors/Redirect.php index 79edee0d..1f18437f 100644 --- a/src/Detectors/Redirect.php +++ b/src/Detectors/Redirect.php @@ -5,6 +5,10 @@ use Psr\Http\Message\UriInterface; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class Redirect extends Detector { public function detect(): ?UriInterface @@ -12,12 +16,12 @@ public function detect(): ?UriInterface $document = $this->extractor->getDocument(); $value = $document->select('.//meta', ['http-equiv' => 'refresh'])->str('content'); - return $value ? $this->extract($value) : null; + return $value !== null ? $this->extract($value) : null; } private function extract(string $value): ?UriInterface { - if (preg_match('/url=(.+)$/i', $value, $match)) { + if (preg_match('/url=(.+)$/i', $value, $match) === 1) { return $this->extractor->resolveUri(trim($match[1], '\'"')); } diff --git a/src/Detectors/Title.php b/src/Detectors/Title.php index 352bff03..7f38b006 100644 --- a/src/Detectors/Title.php +++ b/src/Detectors/Title.php @@ -3,6 +3,10 @@ namespace Embed\Detectors; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class Title extends Detector { public function detect(): ?string @@ -11,17 +15,25 @@ public function detect(): ?string $document = $this->extractor->getDocument(); $metas = $this->extractor->getMetas(); - return $oembed->str('title') - ?: $metas->str( - 'og:title', - 'twitter:title', - 'lp:title', - 'dcterms.title', - 'article:title', - 'headline', - 'article.headline', - 'parsely-title' - ) - ?: $document->select('.//head/title')->str(); + $result = $oembed->str('title'); + if (is_string($result) && trim($result) !== '') { + return $result; + } + + $result = $metas->str( + 'og:title', + 'twitter:title', + 'lp:title', + 'dcterms.title', + 'article:title', + 'headline', + 'article.headline', + 'parsely-title' + ); + if (is_string($result) && trim($result) !== '') { + return $result; + } + + return $document->select('.//head/title')->str(); } } diff --git a/src/Detectors/Url.php b/src/Detectors/Url.php index 358dbfd8..7771eef6 100644 --- a/src/Detectors/Url.php +++ b/src/Detectors/Url.php @@ -5,14 +5,26 @@ use Psr\Http\Message\UriInterface; +/** + * @template TExtractor of \Embed\Extractor + * @template-extends Detector + */ class Url extends Detector { public function detect(): UriInterface { $oembed = $this->extractor->getOEmbed(); - return $oembed->url('url') - ?: $oembed->url('web_page') - ?: $this->extractor->getUri(); + $result = $oembed->url('url'); + if ($result !== null) { + return $result; + } + + $result = $oembed->url('web_page'); + if ($result !== null) { + return $result; + } + + return $this->extractor->getUri(); } } diff --git a/src/Document.php b/src/Document.php index 716b218a..3a99dfa0 100644 --- a/src/Document.php +++ b/src/Document.php @@ -29,36 +29,58 @@ public function __construct(Extractor $extractor) $encoding = null; $contentType = $extractor->getResponse()->getHeaderLine('content-type'); preg_match('/charset=(?:"|\')?(.*?)(?=$|\s|;|"|\'|>)/i', $contentType, $match); - if (!empty($match[1])) { + if (isset($match[1]) && $match[1] !== '' && $match[1] !== '0') { $encoding = trim($match[1], ','); - try { - $ret = mb_encoding_aliases($encoding ?? ''); - if ($ret === false) { - $encoding = null; - } - } catch (\ValueError $exception) { - $encoding = null; - } + $encoding = $this->getValidEncoding($encoding); } - if (is_null($encoding) && !empty($html)) { + if (is_null($encoding) && $html !== '') { preg_match('/charset=(?:"|\')?(.*?)(?=$|\s|;|"|\'|>)/i', $html, $match); - if (!empty($match[1])) { + if (isset($match[1]) && $match[1] !== '' && $match[1] !== '0') { $encoding = trim($match[1], ','); + $encoding = $this->getValidEncoding($encoding); } + } + $this->document = $html !== '' ? Parser::parse($html, $encoding) : new DOMDocument(); + $this->initXPath(); + } + + /** + * Get valid encoding name if it exists, otherwise return null + * + * Uses mb_encoding_aliases() to verify the encoding is valid. + * + * TODO: When dropping PHP 7.4 support, remove the PHP_VERSION_ID < 80000 branch. + * PHP version differences: + * - PHP 7.4: mb_encoding_aliases() returns false for invalid encoding and throws Warning for empty string + * - PHP 8.0+: mb_encoding_aliases() throws ValueError for invalid/empty encoding + * + * @see https://www.php.net/manual/en/function.mb-encoding-aliases.php + */ + private function getValidEncoding(?string $encoding): ?string + { + if (PHP_VERSION_ID < 80000) { + // PHP 7.4: Check return value (false = invalid encoding) + // Need to check null/empty first to avoid Warning + // TODO: Remove this entire branch when PHP 7.4 support is dropped + if ($encoding === null || $encoding === '') { + return null; + } + $ret = @mb_encoding_aliases($encoding); + /** @phpstan-ignore function.alreadyNarrowedType (PHP 7.4 returns false for invalid encoding, PHP 8.0+ returns array) */ + return is_array($ret) ? $encoding : null; + } else { + // PHP 8.0+: ValueError exception is thrown for invalid/empty encoding try { - $ret = mb_encoding_aliases($encoding ?? ''); - if ($ret === false) { - $encoding = null; - } + $aliases = mb_encoding_aliases($encoding ?? ''); + // If mb_encoding_aliases succeeds, return the input value as is. Some encodings do not have aliases. + return $encoding; } catch (\ValueError $exception) { - $encoding = null; + return null; } } - $this->document = !empty($html) ? Parser::parse($html, $encoding) : new DOMDocument(); - $this->initXPath(); } - private function initXPath() + private function initXPath(): void { $this->xpath = new DOMXPath($this->document); $this->xpath->registerNamespace('php', 'http://php.net/xpath'); @@ -73,10 +95,16 @@ public function __clone() public function remove(string $query): void { - $nodes = iterator_to_array($this->xpath->query($query), false); + $result = $this->xpath->query($query); + if ($result === false) { + return; + } + $nodes = iterator_to_array($result, false); foreach ($nodes as $node) { - $node->parentNode->removeChild($node); + if ($node->parentNode !== null) { + $node->parentNode->removeChild($node); // @phpstan-ignore argument.type + } } } @@ -92,6 +120,8 @@ public function getDocument(): DOMDocument /** * Helper to build xpath queries easily and case insensitive + * + * @param array $attributes */ private static function buildQuery(string $startQuery, array $attributes): string { @@ -106,14 +136,20 @@ private static function buildQuery(string $startQuery, array $attributes): strin /** * Select a element in the dom + * + * @param array|null $attributes */ public function select(string $query, ?array $attributes = null, ?DOMNode $context = null): QueryResult { - if (!empty($attributes)) { + if ($attributes !== null && $attributes !== []) { $query = self::buildQuery($query, $attributes); } - return new QueryResult($this->xpath->query($query, $context), $this->extractor); + $result = $this->xpath->query($query, $context); + if ($result === false) { + $result = new \DOMNodeList(); + } + return new QueryResult($result, $this->extractor); // @phpstan-ignore argument.type } /** @@ -126,6 +162,8 @@ public function selectCss(string $query, ?DOMNode $context = null): QueryResult /** * Shortcut to select a element and return the href + * + * @param array $extra */ public function link(string $rel, array $extra = []): ?UriInterface { @@ -147,6 +185,6 @@ private static function cssToXpath(string $selector): string self::$cssConverter = new CssSelectorConverter(); } - return self::$cssConverter->toXpath($selector); + return self::$cssConverter->toXPath($selector); } } diff --git a/src/Embed.php b/src/Embed.php index 3366cfef..6f2d583a 100644 --- a/src/Embed.php +++ b/src/Embed.php @@ -14,8 +14,8 @@ class Embed public function __construct(?Crawler $crawler = null, ?ExtractorFactory $extractorFactory = null) { - $this->crawler = $crawler ?: new Crawler(); - $this->extractorFactory = $extractorFactory ?: new ExtractorFactory(); + $this->crawler = $crawler !== null ? $crawler : new Crawler(); + $this->extractorFactory = $extractorFactory !== null ? $extractorFactory : new ExtractorFactory(); } public function get(string $url): Extractor @@ -41,7 +41,10 @@ public function getMulti(string ...$urls): array $return = []; foreach ($responses as $k => $response) { - $return[] = $this->extract($requests[$k], $responses[$k]); + /** @phpstan-ignore instanceof.alwaysTrue (defensive check for error handling) */ + if ($response instanceof ResponseInterface) { + $return[] = $this->extract($requests[$k], $response); + } } return $return; @@ -57,6 +60,9 @@ public function getExtractorFactory(): ExtractorFactory return $this->extractorFactory; } + /** + * @param array $settings + */ public function setSettings(array $settings): void { $this->extractorFactory->setSettings($settings); @@ -64,7 +70,10 @@ public function setSettings(array $settings): void private function extract(RequestInterface $request, ResponseInterface $response, bool $redirect = true): Extractor { - $uri = $this->crawler->getResponseUri($response) ?: $request->getUri(); + $uri = $this->crawler->getResponseUri($response); + if ($uri === null) { + $uri = $request->getUri(); + } $extractor = $this->extractorFactory->createExtractor($uri, $request, $response, $this->crawler); @@ -72,7 +81,13 @@ private function extract(RequestInterface $request, ResponseInterface $response, return $extractor; } - $request = $this->crawler->createRequest('GET', $extractor->redirect); + // Magic property access returns mixed, but we know it's ?UriInterface from Redirect detector + $redirectUri = $extractor->redirect; + if (!($redirectUri instanceof \Psr\Http\Message\UriInterface)) { + return $extractor; + } + + $request = $this->crawler->createRequest('GET', (string) $redirectUri); $response = $this->crawler->sendRequest($request); return $this->extract($request, $response, false); @@ -80,10 +95,12 @@ private function extract(RequestInterface $request, ResponseInterface $response, private function mustRedirect(Extractor $extractor): bool { - if (!empty($extractor->getOembed()->all())) { + if ($extractor->getOEmbed()->all() !== []) { return false; } - return $extractor->redirect !== null; + // Magic property access returns mixed, but we know it's ?UriInterface from Redirect detector + $redirectUri = $extractor->redirect; + return $redirectUri instanceof \Psr\Http\Message\UriInterface; } } diff --git a/src/EmbedCode.php b/src/EmbedCode.php index 48b87c99..250657e4 100644 --- a/src/EmbedCode.php +++ b/src/EmbedCode.php @@ -19,7 +19,7 @@ public function __construct(string $html, ?int $width = null, ?int $height = nul $this->width = $width; $this->height = $height; - if ($width && $height) { + if ($width !== null && $width !== 0 && $height !== null && $height !== 0) { $this->ratio = round(($height / $width) * 100, 3); } } diff --git a/src/Extractor.php b/src/Extractor.php index 469dd6bc..641968a9 100644 --- a/src/Extractor.php +++ b/src/Extractor.php @@ -53,6 +53,8 @@ * @property UriInterface|null $redirect * @property string|null $title * @property UriInterface $url + * + * @template TDetector of Detector = Detector */ class Extractor { @@ -66,27 +68,47 @@ class Extractor protected LinkedData $linkedData; protected Metas $metas; + /** @var array */ private array $settings = []; + /** @var array> */ private array $customDetectors = []; - + /** @var AuthorName<$this> */ protected AuthorName $authorName; + /** @var AuthorUrl<$this> */ protected AuthorUrl $authorUrl; + /** @var Cms<$this> */ protected Cms $cms; + /** @var Code<$this> */ protected Code $code; + /** @var Description<$this> */ protected Description $description; + /** @var Favicon<$this> */ protected Favicon $favicon; + /** @var Feeds<$this> */ protected Feeds $feeds; + /** @var Icon<$this> */ protected Icon $icon; + /** @var Image<$this> */ protected Image $image; + /** @var Keywords<$this> */ protected Keywords $keywords; + /** @var Language<$this> */ protected Language $language; + /** @var Languages<$this> */ protected Languages $languages; + /** @var License<$this> */ protected License $license; + /** @var ProviderName<$this> */ protected ProviderName $providerName; + /** @var ProviderUrl<$this> */ protected ProviderUrl $providerUrl; + /** @var PublishedTime<$this> */ protected PublishedTime $publishedTime; + /** @var Redirect<$this> */ protected Redirect $redirect; + /** @var Title<$this> */ protected Title $title; + /** @var Url<$this> */ protected Url $url; public function __construct(UriInterface $uri, RequestInterface $request, ResponseInterface $response, Crawler $crawler) @@ -124,37 +146,64 @@ public function __construct(UriInterface $uri, RequestInterface $request, Respon $this->url = new Url($this); } + /** + * @return mixed + */ public function __get(string $name) { - $detector = $this->customDetectors[$name] ?? $this->$name ?? null; + $detector = $this->customDetectors[$name] ?? null; + + if ($detector === null && property_exists($this, $name)) { + /** @var mixed $property */ + /** @phpstan-ignore property.dynamicName */ + $property = (fn($n) => $this->$n)($name); + if ($property instanceof Detector) { + $detector = $property; + } + } - if (!$detector || !($detector instanceof Detector)) { + if ($detector === null) { throw new DomainException(sprintf('Invalid key "%s". No detector found for this value', $name)); } return $detector->get(); } + /** + * @return array + */ public function createCustomDetectors(): array { return []; } + /** + * @phpstan-param Detector<$this> $detector + */ public function addDetector(string $name, Detector $detector): void { $this->customDetectors[$name] = $detector; } + /** + * @param array $settings + */ public function setSettings(array $settings): void { $this->settings = $settings; } + /** + * @return array + */ public function getSettings(): array { return $this->settings; } + /** + * @return mixed + */ public function getSetting(string $key) { return $this->settings[$key] ?? null; @@ -208,10 +257,6 @@ public function resolveUri($uri): UriInterface $uri = $this->crawler->createUri($uri); } - if (!($uri instanceof UriInterface)) { - throw new InvalidArgumentException('Uri must be a string or an instance of UriInterface'); - } - return resolveUri($this->uri, $uri); } diff --git a/src/ExtractorFactory.php b/src/ExtractorFactory.php index 9ba37e0b..dc43daa2 100644 --- a/src/ExtractorFactory.php +++ b/src/ExtractorFactory.php @@ -10,7 +10,9 @@ class ExtractorFactory { + /** @var class-string */ private string $default = Extractor::class; + /** @var array> */ private array $adapters = [ 'slides.com' => Adapters\Slides\Extractor::class, 'pinterest.com' => Adapters\Pinterest\Extractor::class, @@ -32,9 +34,14 @@ class ExtractorFactory 'twitter.com' => Adapters\Twitter\Extractor::class, 'x.com' => Adapters\Twitter\Extractor::class, ]; + /** @var array>> */ private array $customDetectors = []; + /** @var array */ private array $settings; + /** + * @param array|null $settings + */ public function __construct(?array $settings = []) { $this->settings = $settings ?? []; @@ -53,18 +60,18 @@ public function createExtractor(UriInterface $uri, RequestInterface $request, Re } // Check if $host is a subdomain of $adapterHost. - if (substr($host, -strlen($adapterHost) + 1) === ".{$adapterHost}") { + if (substr($host, -strlen($adapterHost) - 1) === ".{$adapterHost}") { $class = $adapter; break; } } - /** @var Extractor $extractor */ $extractor = new $class($uri, $request, $response, $crawler); $extractor->setSettings($this->settings); - foreach ($this->customDetectors as $name => $detector) { - $extractor->addDetector($name, new $detector($extractor)); + foreach ($this->customDetectors as $name => $detectorClass) { + $detector = new $detectorClass($extractor); + $extractor->addDetector($name, $detector); } foreach ($extractor->createCustomDetectors() as $name => $detector) { @@ -74,11 +81,17 @@ public function createExtractor(UriInterface $uri, RequestInterface $request, Re return $extractor; } + /** + * @param class-string $class + */ public function addAdapter(string $pattern, string $class): void { $this->adapters[$pattern] = $class; } + /** + * @param class-string> $class + */ public function addDetector(string $name, string $class): void { $this->customDetectors[$name] = $class; @@ -89,11 +102,17 @@ public function removeAdapter(string $pattern): void unset($this->adapters[$pattern]); } + /** + * @param class-string $class + */ public function setDefault(string $class): void { $this->default = $class; } + /** + * @param array $settings + */ public function setSettings(array $settings): void { $this->settings = $settings; diff --git a/src/Http/Crawler.php b/src/Http/Crawler.php index 77451eba..2c233636 100644 --- a/src/Http/Crawler.php +++ b/src/Http/Crawler.php @@ -15,6 +15,7 @@ class Crawler implements ClientInterface, RequestFactoryInterface, UriFactoryInt private RequestFactoryInterface $requestFactory; private UriFactoryInterface $uriFactory; private ClientInterface $client; + /** @var array */ private array $defaultHeaders = [ 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:73.0) Gecko/20100101 Firefox/73.0', 'Cache-Control' => 'max-age=0', @@ -22,11 +23,14 @@ class Crawler implements ClientInterface, RequestFactoryInterface, UriFactoryInt public function __construct(?ClientInterface $client = null, ?RequestFactoryInterface $requestFactory = null, ?UriFactoryInterface $uriFactory = null) { - $this->client = $client ?: new CurlClient(); - $this->requestFactory = $requestFactory ?: FactoryDiscovery::getRequestFactory(); - $this->uriFactory = $uriFactory ?: FactoryDiscovery::getUriFactory(); + $this->client = $client !== null ? $client : new CurlClient(); + $this->requestFactory = $requestFactory !== null ? $requestFactory : FactoryDiscovery::getRequestFactory(); + $this->uriFactory = $uriFactory !== null ? $uriFactory : FactoryDiscovery::getUriFactory(); } + /** + * @param array $headers + */ public function addDefaultHeaders(array $headers): void { $this->defaultHeaders = $headers + $this->defaultHeaders; @@ -56,6 +60,9 @@ public function sendRequest(RequestInterface $request): ResponseInterface return $this->client->sendRequest($request); } + /** + * @return array + */ public function sendRequests(RequestInterface ...$requests): array { if ($this->client instanceof CurlClient) { @@ -72,6 +79,6 @@ public function getResponseUri(ResponseInterface $response): ?UriInterface { $location = $response->getHeaderLine('Content-Location'); - return $location ? $this->uriFactory->createUri($location) : null; + return $location !== '' ? $this->uriFactory->createUri($location) : null; } } diff --git a/src/Http/CurlClient.php b/src/Http/CurlClient.php index 6b3f44c5..9794d718 100644 --- a/src/Http/CurlClient.php +++ b/src/Http/CurlClient.php @@ -14,13 +14,17 @@ final class CurlClient implements ClientInterface { private ResponseFactoryInterface $responseFactory; + /** @var array */ private array $settings = []; public function __construct(?ResponseFactoryInterface $responseFactory = null) { - $this->responseFactory = $responseFactory ?: FactoryDiscovery::getResponseFactory(); + $this->responseFactory = $responseFactory !== null ? $responseFactory : FactoryDiscovery::getResponseFactory(); } + /** + * @param array $settings + */ public function setSettings(array $settings): void { $this->settings = $settings + $this->settings; @@ -33,6 +37,9 @@ public function sendRequest(RequestInterface $request): ResponseInterface return $responses[0]; } + /** + * @return ResponseInterface[] + */ public function sendRequests(RequestInterface ...$request): array { return CurlDispatcher::fetch($this->settings, $this->responseFactory, ...$request); diff --git a/src/Http/CurlDispatcher.php b/src/Http/CurlDispatcher.php index e3312788..6cdde3a1 100644 --- a/src/Http/CurlDispatcher.php +++ b/src/Http/CurlDispatcher.php @@ -12,6 +12,8 @@ /** * Class to fetch html pages + * + * @phpstan-type CurlResource resource|\CurlHandle */ final class CurlDispatcher { @@ -19,22 +21,31 @@ final class CurlDispatcher private RequestInterface $request; private StreamFactoryInterface $streamFactory; + /** + * @var resource|\CurlHandle + * @phpstan-ignore property.unusedType (resource type needed for PHP 7.4 compatibility) + */ private $curl; - private $result; + /** @var array */ private array $headers = []; - private $isBinary = false; + private bool $isBinary = false; private ?StreamInterface $body = null; private ?int $error = null; + /** @var array */ private array $settings; /** + * @param array $settings * @return ResponseInterface[] */ public static function fetch(array $settings, ResponseFactoryInterface $responseFactory, RequestInterface ...$requests): array { if (count($requests) === 1) { $connection = new static($settings, $requests[0]); - curl_exec($connection->curl); + /** @var resource|\CurlHandle $curlHandle */ + $curlHandle = $connection->curl; + /** @phpstan-ignore argument.type (PHP 7.4/8.0 compatibility) */ + curl_exec($curlHandle); return [$connection->getResponse($responseFactory)]; } @@ -44,7 +55,10 @@ public static function fetch(array $settings, ResponseFactoryInterface $response foreach ($requests as $request) { $connection = new static($settings, $request); - curl_multi_add_handle($multi, $connection->curl); + /** @var resource|\CurlHandle $curlHandle */ + $curlHandle = $connection->curl; + /** @phpstan-ignore argument.type (PHP 7.4/8.0 compatibility) */ + curl_multi_add_handle($multi, $curlHandle); $connections[] = $connection; } @@ -60,19 +74,29 @@ public static function fetch(array $settings, ResponseFactoryInterface $response $info = curl_multi_info_read($multi); - if ($info) { - foreach ($connections as $connection) { - if ($connection->curl === $info['handle']) { - $connection->result = $info['result']; - break; + if (is_array($info) && isset($info['handle'], $info['result'])) { + $result = $info['result']; + // Validate and cast result to int, only set if it's a non-success error code + if (is_numeric($result)) { + $errorCode = (int) $result; + if ($errorCode !== CURLE_OK) { + foreach ($connections as $connection) { + if ($connection->curl === $info['handle']) { + $connection->error = $errorCode; + break; + } + } } } } - } while ($active && $status == CURLM_OK); + } while ($active && $status === CURLM_OK); //Close connections foreach ($connections as $connection) { - curl_multi_remove_handle($multi, $connection->curl); + /** @var resource|\CurlHandle $curlHandle */ + $curlHandle = $connection->curl; + /** @phpstan-ignore argument.type (PHP 7.4/8.0 compatibility) */ + curl_multi_remove_handle($multi, $curlHandle); } curl_multi_close($multi); @@ -83,6 +107,9 @@ public static function fetch(array $settings, ResponseFactoryInterface $response ); } + /** + * @param array $settings + */ private function __construct(array $settings, RequestInterface $request, ?StreamFactoryInterface $streamFactory = null) { $this->request = $request; @@ -116,17 +143,25 @@ private function __construct(array $settings, RequestInterface $request, ?Stream private function getResponse(ResponseFactoryInterface $responseFactory): ResponseInterface { - $info = curl_getinfo($this->curl); + /** @var resource|\CurlHandle $curlHandle */ + $curlHandle = $this->curl; + /** @phpstan-ignore argument.type (PHP 7.4/8.0 compatibility) */ + $info = curl_getinfo($curlHandle); - if ($this->error) { + if ($this->error !== null && $this->error !== 0) { + /** @phpstan-ignore argument.type (curl_strerror returns string|null in some versions) */ $this->error(curl_strerror($this->error), $this->error); } - if (curl_errno($this->curl)) { - $this->error(curl_error($this->curl), curl_errno($this->curl)); + /** @phpstan-ignore argument.type (PHP 7.4/8.0 compatibility) */ + $errno = curl_errno($curlHandle); + if ($errno !== 0) { + /** @phpstan-ignore argument.type (PHP 7.4/8.0 compatibility) */ + $this->error(curl_error($curlHandle), $errno); } - curl_close($this->curl); + /** @phpstan-ignore argument.type (PHP 7.4/8.0 compatibility) */ + curl_close($curlHandle); $response = $responseFactory->createResponse($info['http_code']); @@ -139,7 +174,7 @@ private function getResponse(ResponseFactoryInterface $responseFactory): Respons ->withAddedHeader('Content-Location', $info['url']) ->withAddedHeader('X-Request-Time', sprintf('%.3f ms', $info['total_time'])); - if ($this->body) { + if ($this->body !== null) { //5Mb max $this->body->rewind(); $response = $response->withBody($this->body); @@ -149,11 +184,11 @@ private function getResponse(ResponseFactoryInterface $responseFactory): Respons return $response; } - private function error(string $message, int $code) + private function error(string $message, int $code): void { $ignored = $this->settings['ignored_errors'] ?? null; - if ($ignored === true || (is_array($ignored) && in_array($code, $ignored))) { + if ($ignored === true || (is_array($ignored) && in_array($code, $ignored, true))) { return; } @@ -165,6 +200,9 @@ private function error(string $message, int $code) throw new NetworkException($message, $code, $this->request); } + /** + * @return array + */ private function getRequestHeaders(): array { $headers = []; @@ -181,17 +219,25 @@ private function getRequestHeaders(): array return $headers; } + /** + * @param resource|\CurlHandle $curl + * @param mixed $string + */ private function writeHeader($curl, $string): int { - if (preg_match('/^([\w-]+):(.*)$/', $string, $matches)) { + if (!is_string($string)) { + return 0; + } + + if (preg_match('/^([\w-]+):(.*)$/', $string, $matches) === 1) { $name = strtolower($matches[1]); $value = trim($matches[2]); $this->headers[] = [$name, $value]; if ($name === 'content-type') { - $this->isBinary = !preg_match('/(text|html|json)/', strtolower($value)); + $this->isBinary = preg_match('/(text|html|json)/', strtolower($value)) === 0; } - } elseif ($this->headers) { + } elseif ($this->headers !== []) { $key = array_key_last($this->headers); $this->headers[$key][1] .= ' '.trim($string); } @@ -199,13 +245,21 @@ private function writeHeader($curl, $string): int return strlen($string); } + /** + * @param resource|\CurlHandle $curl + * @param mixed $string + */ private function writeBody($curl, $string): int { + if (!is_string($string)) { + return -1; + } + if ($this->isBinary) { return -1; } - if (!$this->body) { + if ($this->body === null) { $this->body = $this->streamFactory->createStreamFromFile('php://temp', 'w+'); } diff --git a/src/Http/FactoryDiscovery.php b/src/Http/FactoryDiscovery.php index 8072d6fb..993e6435 100644 --- a/src/Http/FactoryDiscovery.php +++ b/src/Http/FactoryDiscovery.php @@ -45,7 +45,9 @@ abstract class FactoryDiscovery public static function getRequestFactory(): RequestFactoryInterface { - if ($class = self::searchClass(self::REQUEST)) { + $class = self::searchClass(self::REQUEST); + if ($class !== null) { + /** @var RequestFactoryInterface */ return new $class(); } @@ -54,7 +56,9 @@ public static function getRequestFactory(): RequestFactoryInterface public static function getResponseFactory(): ResponseFactoryInterface { - if ($class = self::searchClass(self::RESPONSE)) { + $class = self::searchClass(self::RESPONSE); + if ($class !== null) { + /** @var ResponseFactoryInterface */ return new $class(); } @@ -63,7 +67,9 @@ public static function getResponseFactory(): ResponseFactoryInterface public static function getUriFactory(): UriFactoryInterface { - if ($class = self::searchClass(self::URI)) { + $class = self::searchClass(self::URI); + if ($class !== null) { + /** @var UriFactoryInterface */ return new $class(); } @@ -72,14 +78,19 @@ public static function getUriFactory(): UriFactoryInterface public static function getStreamFactory(): StreamFactoryInterface { - if ($class = self::searchClass(self::STREAM)) { + $class = self::searchClass(self::STREAM); + if ($class !== null) { + /** @var StreamFactoryInterface */ return new $class(); } throw new RuntimeException('No StreamFactoryInterface detected'); } - private static function searchClass($classes): ?string + /** + * @param string[] $classes + */ + private static function searchClass(array $classes): ?string { foreach ($classes as $class) { if (class_exists($class)) { diff --git a/src/Http/RequestException.php b/src/Http/RequestException.php index bfa9b4e6..701d3400 100644 --- a/src/Http/RequestException.php +++ b/src/Http/RequestException.php @@ -13,6 +13,7 @@ final class RequestException extends Exception implements RequestExceptionInterf public function __construct(string $message, int $code, RequestInterface $request) { + parent::__construct($message, $code); $this->request = $request; } diff --git a/src/HttpApiTrait.php b/src/HttpApiTrait.php index fa697431..8e4180bb 100644 --- a/src/HttpApiTrait.php +++ b/src/HttpApiTrait.php @@ -10,13 +10,16 @@ trait HttpApiTrait { use ApiTrait; - private ?UriInterface $endpoint; + private ?UriInterface $endpoint = null; public function getEndpoint(): ?UriInterface { return $this->endpoint; } + /** + * @return array + */ private function fetchJSON(UriInterface $uri): array { $crawler = $this->extractor->getCrawler(); @@ -24,7 +27,12 @@ private function fetchJSON(UriInterface $uri): array $response = $crawler->sendRequest($request); try { - return json_decode((string) $response->getBody(), true) ?: []; + $data = json_decode((string) $response->getBody(), true); + if (is_array($data)) { + /** @var array */ + return $data; + } + return []; } catch (Exception $exception) { return []; } diff --git a/src/LinkedData.php b/src/LinkedData.php index 9d7cfe19..91876654 100644 --- a/src/LinkedData.php +++ b/src/LinkedData.php @@ -15,15 +15,19 @@ class LinkedData { use ApiTrait; - private ?DocumentInterface $document; + private ?DocumentInterface $document = null; - private array $allData; + /** @var array */ + private array $allData = []; + /** + * @return mixed + */ public function get(string ...$keys) { $graph = $this->getGraph(); - if (!$graph) { + if ($graph === null) { return null; } @@ -33,7 +37,7 @@ public function get(string ...$keys) foreach ($graph->getNodes() as $node) { $value = self::getValue($node, ...$subkeys); - if ($value) { + if ($value !== null && $value !== '' && $value !== false && $value !== []) { return $value; } } @@ -42,9 +46,12 @@ public function get(string ...$keys) return null; } - public function getAll() + /** + * @return array + */ + public function getAll(): array { - if (!isset($this->allData)) { + if ($this->allData === []) { $this->fetchData(); } @@ -55,7 +62,11 @@ private function getGraph(?string $name = null): ?GraphInterface { if (!isset($this->document)) { try { - $this->document = LdDocument::load(json_encode($this->all())); + $encoded = json_encode($this->all()); + if ($encoded === false) { + $encoded = '{}'; + } + $this->document = LdDocument::load($encoded); } catch (Throwable $throwable) { $this->document = LdDocument::load('{}'); return null; @@ -65,6 +76,9 @@ private function getGraph(?string $name = null): ?GraphInterface return $this->document->getGraph($name); } + /** + * @return array + */ protected function fetchData(): array { $this->allData = []; @@ -72,16 +86,17 @@ protected function fetchData(): array $document = $this->extractor->getDocument(); $nodes = $document->select('.//script', ['type' => 'application/ld+json'])->strAll(); - if (empty($nodes)) { + if ($nodes === []) { return []; } try { + /** @var array $data */ $data = []; $request_uri = (string)$this->extractor->getUri(); foreach ($nodes as $node) { $ldjson = json_decode($node, true); - if (!empty($ldjson)) { + if (is_array($ldjson) && $ldjson !== []) { // some pages with multiple ld+json blocks will put // each block into an array (Flickr does this). Most @@ -92,24 +107,30 @@ protected function fetchData(): array $ldjson = [$ldjson]; } - foreach ($ldjson as $node) { - if (empty($data)) { - $data = $node; - } elseif (isset($node['mainEntityOfPage'])) { + foreach ($ldjson as $ldNode) { + if (!is_array($ldNode)) { + continue; + } + if ($data === []) { + /** @var array $data */ + $data = $ldNode; + } elseif (isset($ldNode['mainEntityOfPage'])) { $url = ''; - if (is_string($node['mainEntityOfPage'])) { - $url = $node['mainEntityOfPage']; - } elseif (isset($node['mainEntityOfPage']['@id'])) { - $url = $node['mainEntityOfPage']['@id']; + if (is_string($ldNode['mainEntityOfPage'])) { + $url = $ldNode['mainEntityOfPage']; + } elseif (is_array($ldNode['mainEntityOfPage']) && isset($ldNode['mainEntityOfPage']['@id']) && is_string($ldNode['mainEntityOfPage']['@id'])) { + $url = $ldNode['mainEntityOfPage']['@id']; } - if (!empty($url) && $url == $request_uri) { - $data = $node; + if ($url !== '' && $url === $request_uri) { + /** @var array $data */ + $data = $ldNode; } } } - - $this->allData = array_merge($this->allData, $ldjson); + /** @var array $mergedData */ + $mergedData = array_merge($this->allData, $ldjson); + $this->allData = $mergedData; } } @@ -119,6 +140,9 @@ protected function fetchData(): array } } + /** + * @return mixed + */ private static function getValue(Node $node, string ...$keys) { foreach ($keys as $key) { @@ -131,7 +155,7 @@ private static function getValue(Node $node, string ...$keys) $node = $node->getProperty("http://schema.org/{$key}"); - if (!$node) { + if ($node === null) { return null; } } @@ -139,6 +163,10 @@ private static function getValue(Node $node, string ...$keys) return self::detectValue($node); } + /** + * @param mixed $value + * @return mixed + */ private static function detectValue($value) { if (is_array($value)) { @@ -156,6 +184,10 @@ private static function detectValue($value) return $value->getId(); } - return $value->getValue(); + if (is_object($value) && method_exists($value, 'getValue')) { + return $value->getValue(); + } + + return null; } } diff --git a/src/Metas.php b/src/Metas.php index 70abe6cc..0450370f 100644 --- a/src/Metas.php +++ b/src/Metas.php @@ -7,16 +7,28 @@ class Metas { use ApiTrait; + /** + * @return array + */ protected function fetchData(): array { $data = []; $document = $this->extractor->getDocument(); foreach ($document->select('.//meta')->nodes() as $node) { - $type = $node->getAttribute('name') ?: $node->getAttribute('property') ?: $node->getAttribute('itemprop'); + if (!($node instanceof \DOMElement)) { + continue; + } + $type = $node->getAttribute('name'); + if ($type === '') { + $type = $node->getAttribute('property'); + } + if ($type === '') { + $type = $node->getAttribute('itemprop'); + } $value = $node->getAttribute('content'); - if (!empty($value) && !empty($type)) { + if ($value !== '' && $type !== '') { $type = strtolower($type); $data[$type] ??= []; $data[$type][] = $value; @@ -26,6 +38,9 @@ protected function fetchData(): array return $data; } + /** + * @return mixed + */ public function get(string ...$keys) { $data = $this->all(); @@ -33,7 +48,7 @@ public function get(string ...$keys) foreach ($keys as $key) { $values = $data[$key] ?? null; - if ($values) { + if ($values !== null && $values !== '' && $values !== []) { return $values; } } diff --git a/src/OEmbed.php b/src/OEmbed.php index e089150e..b530591f 100644 --- a/src/OEmbed.php +++ b/src/OEmbed.php @@ -11,30 +11,48 @@ class OEmbed { use HttpApiTrait; - private static $providers; + /** @var array|null */ + private static $providers = null; + + /** @var array */ private array $defaults = []; + /** + * @return array + */ private static function getProviders(): array { - if (!is_array(self::$providers)) { - self::$providers = require __DIR__.'/resources/oembed.php'; + if (self::$providers === null) { + /** @var array $loaded */ + $loaded = require __DIR__.'/resources/oembed.php'; + self::$providers = $loaded; } return self::$providers; } + /** + * @return array + */ public function getOembedQueryParameters(string $url): array { $queryParameters = ['url' => $url, 'format' => 'json']; + $setting = $this->extractor->getSetting('oembed:query_parameters'); + $additional = is_array($setting) ? $setting : []; - return array_merge($queryParameters, $this->extractor->getSetting('oembed:query_parameters') ?? []); + /** @var array $result */ + $result = array_merge($queryParameters, $additional); + return $result; } + /** + * @return array + */ protected function fetchData(): array { $this->endpoint = $this->detectEndpoint(); - if (empty($this->endpoint)) { + if ($this->endpoint === null) { return []; } @@ -53,11 +71,20 @@ protected function detectEndpoint(): ?UriInterface { $document = $this->extractor->getDocument(); - $endpoint = $document->link('alternate', ['type' => 'application/json+oembed']) - ?: $document->link('alternate', ['type' => 'text/json+oembed']) - ?: $document->link('alternate', ['type' => 'application/xml+oembed']) - ?: $document->link('alternate', ['type' => 'text/xml+oembed']) - ?: null; + $endpoint = null; + $types = [ + 'application/json+oembed', + 'text/json+oembed', + 'application/xml+oembed', + 'text/xml+oembed', + ]; + + foreach ($types as $type) { + $endpoint = $document->link('alternate', ['type' => $type]); + if ($endpoint !== null) { + break; + } + } if ($endpoint === null) { return $this->detectEndpointFromProviders(); @@ -65,7 +92,9 @@ protected function detectEndpoint(): ?UriInterface // Add configured OEmbed query parameters parse_str($endpoint->getQuery(), $query); - $query = array_merge($query, $this->extractor->getSetting('oembed:query_parameters') ?? []); + $setting = $this->extractor->getSetting('oembed:query_parameters'); + $additional = is_array($setting) ? $setting : []; + $query = array_merge($query, $additional); $endpoint = $endpoint->withQuery(http_build_query($query)); return $endpoint; @@ -75,15 +104,19 @@ private function detectEndpointFromProviders(): ?UriInterface { $url = (string) $this->extractor->getUri(); - if ($endpoint = $this->detectEndpointFromUrl($url)) { + $endpoint = $this->detectEndpointFromUrl($url); + if ($endpoint !== null) { return $endpoint; } $initialUrl = (string) $this->extractor->getRequest()->getUri(); - if ($initialUrl !== $url && ($endpoint = $this->detectEndpointFromUrl($initialUrl))) { - $this->defaults['url'] = $initialUrl; - return $endpoint; + if ($initialUrl !== $url) { + $endpoint = $this->detectEndpointFromUrl($initialUrl); + if ($endpoint !== null) { + $this->defaults['url'] = $initialUrl; + return $endpoint; + } } return null; @@ -93,7 +126,7 @@ private function detectEndpointFromUrl(string $url): ?UriInterface { $endpoint = self::searchEndpoint(self::getProviders(), $url); - if (!$endpoint) { + if ($endpoint === null || $endpoint === '') { return null; } @@ -102,12 +135,22 @@ private function detectEndpointFromUrl(string $url): ?UriInterface ->withQuery(http_build_query($this->getOembedQueryParameters($url))); } + /** + * @param array $providers + */ private static function searchEndpoint(array $providers, string $url): ?string { foreach ($providers as $endpoint => $patterns) { + if (!is_array($patterns)) { + continue; + } foreach ($patterns as $pattern) { - if (preg_match($pattern, $url)) { - return $endpoint; + if (!is_string($pattern)) { + continue; + } + $matchResult = preg_match($pattern, $url); + if ($matchResult === 1) { + return is_string($endpoint) ? $endpoint : null; } } } @@ -126,21 +169,27 @@ private static function isXML(UriInterface $uri): bool parse_str($uri->getQuery(), $params); $format = $params['format'] ?? null; - if ($format && strtolower($format) === 'xml') { + if (is_string($format) && $format !== '' && strtolower($format) === 'xml') { return true; } return false; } + /** + * @return array + */ private function extractXML(string $xml): array { try { // Remove the DOCTYPE declaration for to prevent XML Quadratic Blowup vulnerability - $xml = preg_replace('/^]*+>/i', '', $xml, 1); + $cleanedXml = preg_replace('/^]*+>/i', '', $xml, 1); + if (!is_string($cleanedXml)) { + return []; + } $data = []; $errors = libxml_use_internal_errors(true); - $content = new SimpleXMLElement($xml); + $content = new SimpleXMLElement($cleanedXml); libxml_use_internal_errors($errors); foreach ($content as $element) { @@ -154,18 +203,28 @@ private function extractXML(string $xml): array $data[$name] = $value; } - return $data ? ($data + $this->defaults) : []; + return $data !== [] ? ($data + $this->defaults) : []; } catch (Exception $exception) { return []; } } + /** + * @return array + */ private function extractJSON(string $json): array { try { - $data = json_decode($json, true); + /** @var mixed $decoded */ + $decoded = json_decode($json, true); + + if (!is_array($decoded)) { + return []; + } - return is_array($data) ? ($data + $this->defaults) : []; + /** @var array $result */ + $result = $decoded + $this->defaults; + return $result; } catch (Exception $exception) { return []; } diff --git a/src/QueryResult.php b/src/QueryResult.php index 57e85955..1c60eecf 100644 --- a/src/QueryResult.php +++ b/src/QueryResult.php @@ -5,6 +5,7 @@ use Closure; use DOMElement; +use DOMNode; use DOMNodeList; use Psr\Http\Message\UriInterface; use Throwable; @@ -12,78 +13,121 @@ class QueryResult { private Extractor $extractor; + /** @var list */ private array $nodes = []; + /** + * @param DOMNodeList $result + */ public function __construct(DOMNodeList $result, Extractor $extractor) { - $this->nodes = iterator_to_array($result, false); + /** @var list $nodeArray */ + $nodeArray = iterator_to_array($result, false); + $this->nodes = $nodeArray; $this->extractor = $extractor; } public function node(): ?DOMElement { - return $this->nodes[0] ?? null; + $firstNode = $this->nodes[0] ?? null; + return $firstNode instanceof DOMElement ? $firstNode : null; } + /** + * @return list + */ public function nodes(): array { return $this->nodes; } + /** + * @param Closure(DOMNode): bool $callback + */ public function filter(Closure $callback): self { - $this->nodes = array_filter($this->nodes, $callback); + $this->nodes = array_values(array_filter($this->nodes, $callback)); return $this; } + /** + * @return mixed + */ public function get(?string $attribute = null) { $node = $this->node(); - if (!$node) { + if ($node === null) { return null; } - return $attribute ? self::getAttribute($node, $attribute) : $node->nodeValue; + return $attribute !== null ? self::getAttribute($node, $attribute) : $node->nodeValue; } + /** + * @return list + */ public function getAll(?string $attribute = null): array { $nodes = $this->nodes(); - return array_filter( + return array_values(array_filter( array_map( - fn ($node) => $attribute ? self::getAttribute($node, $attribute) : $node->nodeValue, + function(\DOMNode $node) use ($attribute) { + if (!$node instanceof DOMElement) { + return $attribute !== null ? null : $node->nodeValue; + } + return $attribute !== null ? self::getAttribute($node, $attribute) : $node->nodeValue; + }, $nodes - ) - ); + ), + fn($val) => $val !== null && $val !== '' + )); } public function str(?string $attribute = null): ?string { $value = $this->get($attribute); - return $value ? clean($value) : null; + if (!is_string($value) && !is_numeric($value)) { + return null; + } + + $cleaned = clean((string)$value); + return $cleaned !== '' ? $cleaned : null; } + /** + * @return list + */ public function strAll(?string $attribute = null): array { - return array_filter(array_map(fn ($value) => clean($value), $this->getAll($attribute))); + return array_values(array_filter(array_map(function($value) { + if (!is_string($value) && !is_numeric($value)) { + return null; + } + $cleaned = clean((string)$value); + return $cleaned !== '' ? $cleaned : null; + }, $this->getAll($attribute)), fn($v) => $v !== null)); } public function int(?string $attribute = null): ?int { $value = $this->get($attribute); - return $value ? (int) $value : null; + if ($value === null || $value === '' || $value === false) { + return null; + } + + return is_numeric($value) ? (int) $value : null; } public function url(?string $attribute = null): ?UriInterface { $value = $this->get($attribute); - if (!$value) { + if (!is_string($value) || $value === '') { return null; } @@ -102,7 +146,7 @@ private static function getAttribute(DOMElement $node, string $name): ?string for ($i = 0; $i < $attributes->length; ++$i) { $attribute = $attributes->item($i); - if ($attribute->name === $name) { + if ($attribute !== null && $attribute->name === $name) { return $attribute->nodeValue; } } diff --git a/src/functions.php b/src/functions.php index c3313a6a..9965ee63 100644 --- a/src/functions.php +++ b/src/functions.php @@ -14,10 +14,14 @@ function clean(string $value, bool $allowHTML = false): ?string $value = strip_tags($value); } - $value = trim(preg_replace('/\s+/u', ' ', $value)); + $replaced = preg_replace('/\s+/u', ' ', $value); + $value = trim($replaced !== null ? $replaced : $value); return $value === '' ? null : $value; } +/** + * @param array $attributes + */ function html(string $tagName, array $attributes, ?string $content = null): string { $html = "<{$tagName}"; @@ -28,7 +32,16 @@ function html(string $tagName, array $attributes, ?string $content = null): stri } elseif ($value === true) { $html .= " $name"; } elseif ($value !== false) { - $html .= ' '.$name.'="'.htmlspecialchars((string) $value).'"'; + if (is_string($value)) { + $stringValue = $value; + } elseif (is_scalar($value)) { + $stringValue = (string) $value; + } elseif (is_object($value) && method_exists($value, '__toString')) { + $stringValue = (string) $value; + } else { + $stringValue = ''; + } + $html .= ' '.$name.'="'.htmlspecialchars($stringValue).'"'; } } @@ -47,11 +60,11 @@ function resolveUri(UriInterface $base, UriInterface $uri): UriInterface { $uri = $uri->withPath(resolvePath($base->getPath(), $uri->getPath())); - if (!$uri->getHost()) { + if ($uri->getHost() === '') { $uri = $uri->withHost($base->getHost()); } - if (!$uri->getScheme()) { + if ($uri->getScheme() === '') { $uri = $uri->withScheme($base->getScheme()); } @@ -62,8 +75,9 @@ function resolveUri(UriInterface $base, UriInterface $uri): UriInterface function isHttp(string $uri): bool { - if (preg_match('/^(\w+):/', $uri, $matches)) { - return in_array(strtolower($matches[1]), ['http', 'https']); + $result = preg_match('/^(\w+):/', $uri, $matches); + if ($result !== false && $result > 0) { + return in_array(strtolower($matches[1]), ['http', 'https'], true); } return true; @@ -81,20 +95,22 @@ function resolvePath(string $base, string $path): string if (substr($base, -1) !== '/') { $position = strrpos($base, '/'); - $base = substr($base, 0, $position); + $base = $position !== false ? substr($base, 0, $position) : ''; } $path = "{$base}/{$path}"; - $parts = array_filter(explode('/', $path), 'strlen'); + $parts = array_filter(explode('/', $path), static function (string $value): bool { + return strlen($value) > 0; + }); $absolutes = []; foreach ($parts as $part) { - if ('.' == $part) { + if ('.' === $part) { continue; } - if ('..' == $part) { + if ('..' === $part) { array_pop($absolutes); continue; } @@ -105,16 +121,23 @@ function resolvePath(string $base, string $path): string return implode('/', $absolutes); } -function cleanPath(string $path): string +function cleanPath(?string $path): string { - if ($path === '') { + if ($path === null || $path === '') { return '/'; } - $path = preg_replace('|[/]{2,}|', '/', $path); + $cleanedPath = preg_replace('|[/]{2,}|', '/', $path); + if ($cleanedPath === null) { + return '/'; + } + $path = $cleanedPath; if (strpos($path, ';jsessionid=') !== false) { - $path = preg_replace('/^(.*)(;jsessionid=.*)$/i', '$1', $path); + $cleanedPath = preg_replace('/^(.*)(;jsessionid=.*)$/i', '$1', $path); + if ($cleanedPath !== null) { + $path = $cleanedPath; + } } return $path; @@ -147,7 +170,7 @@ function isEmpty(...$values): bool ); foreach ($values as $value) { - if (empty($value) || in_array($value, $skipValues)) { + if ($value === null || $value === '' || $value === [] || $value === false || $value === 0 || $value === 0.0 || $value === '0' || in_array($value, $skipValues, true)) { return true; } } @@ -160,7 +183,7 @@ function isEmpty(...$values): bool * Polyfil for https://www.php.net/manual/en/function.array-is-list.php * which is only available in PHP 8.1+ * - * @param array $array The array + * @param array $array The array * * @return bool */ diff --git a/tests/AuthorUrlEmptyStringTest.php b/tests/AuthorUrlEmptyStringTest.php new file mode 100644 index 00000000..011552c8 --- /dev/null +++ b/tests/AuthorUrlEmptyStringTest.php @@ -0,0 +1,43 @@ +assertNotFalse($content, "File $file should exist"); + + // Verify the pattern includes type, empty string, and '0' check + $hasTypeCheck = str_contains($content, 'is_string('); + $hasEmptyCheck = str_contains($content, "!== ''"); + $hasZeroCheck = str_contains($content, "!== '0'"); + + $this->assertTrue( + $hasTypeCheck && $hasEmptyCheck && $hasZeroCheck, + "File $file should check type (is_string), empty string, and '0'" + ); + } + } +} diff --git a/tests/DocumentTest.php b/tests/DocumentTest.php index 8978f5cc..aa516acd 100644 --- a/tests/DocumentTest.php +++ b/tests/DocumentTest.php @@ -28,7 +28,7 @@ public function testSelectors() $extractor = self::getEmbed()->get('http://www.wired.com/?p=2064839'); $document = $extractor->getDocument(); - $expected = 23; + $expected = 3; $this->assertCount($expected, $document->select('.//p')->nodes()); $this->assertCount($expected, $document->selectCss('p')->nodes()); diff --git a/tests/EmbedCodeTest.php b/tests/EmbedCodeTest.php new file mode 100644 index 00000000..60ae3c62 --- /dev/null +++ b/tests/EmbedCodeTest.php @@ -0,0 +1,89 @@ +', 380, 120); + $this->assertEqualsWithDelta(31.579, $code->ratio, 0.001); + } + + public function testRatioCalculationWithNullWidth() + { + // width=null case + $code = new EmbedCode('', null, 400); + $this->assertNull($code->ratio); + } + + public function testRatioCalculationWithZeroWidth() + { + // width=0 case (prevents division-by-zero) + $code = new EmbedCode('', 0, 400); + $this->assertNull($code->ratio); + } + + public function testRatioCalculationWithNullHeight() + { + // height=null case + $code = new EmbedCode('', 400, null); + $this->assertNull($code->ratio); + } + + public function testRatioCalculationWithZeroHeight() + { + // height=0 case (prevents meaningless ratio calculation) + $code = new EmbedCode('', 400, 0); + $this->assertNull($code->ratio); + } + + public function testRatioCalculationWithBothZero() + { + // width=0, height=0 case (prevents division-by-zero) + $code = new EmbedCode('', 0, 0); + $this->assertNull($code->ratio); + } + + public function testRatioCalculationWithBothNull() + { + // width=null, height=null case + $code = new EmbedCode('', null, null); + $this->assertNull($code->ratio); + } + + public function testJsonSerialize() + { + $code = new EmbedCode('
test
', 640, 480); + $json = $code->jsonSerialize(); + + $this->assertEquals('
test
', $json['html']); + $this->assertEquals(640, $json['width']); + $this->assertEquals(480, $json['height']); + $this->assertEqualsWithDelta(75.0, $json['ratio'], 0.001); + } + + public function testToString() + { + $html = ''; + $code = new EmbedCode($html, 640, 480); + + $this->assertEquals($html, (string) $code); + } + + public function testHtmlOnlyConstruction() + { + // Construction with HTML only (width/height are null) + $code = new EmbedCode('

content

'); + + $this->assertEquals('

content

', $code->html); + $this->assertNull($code->width); + $this->assertNull($code->height); + $this->assertNull($code->ratio); + } +} diff --git a/tests/cache/www.wired.com..1202600986b37d2c6a30336f82c671f8.php b/tests/cache/www.wired.com..1202600986b37d2c6a30336f82c671f8.php index e2bda995..7feb7e9e 100644 --- a/tests/cache/www.wired.com..1202600986b37d2c6a30336f82c671f8.php +++ b/tests/cache/www.wired.com..1202600986b37d2c6a30336f82c671f8.php @@ -3,1154 +3,1935 @@ return [ 'headers' => [ - 'connection' => [ - 'close' - ], - 'content-length' => [ - '0' - ], 'server' => [ - 'Varnish' - ], - 'retry-after' => [ - '0' - ], - 'location' => [ - 'https://www.wired.com/?p=2064839' - ], - 'accept-ranges' => [ - 'bytes', - 'none' + 'CloudFront', + 'CloudFront' ], 'date' => [ - 'Mon, 10 Jun 2024 15:59:03 GMT', - 'Mon, 10 Jun 2024 15:59:04 GMT' + 'Sat, 04 Oct 2025 10:19:08 GMT', + 'Sat, 04 Oct 2025 10:07:16 GMT' ], - 'via' => [ - '1.1 varnish', - '1.1 varnish, 1.1 varnish' + 'content-type' => [ + 'text/html', + 'text/html; charset=utf-8' ], - 'set-cookie' => [ - 'CN_xid=40f10590-911c-4553-8a23-0517f2a85fe9; Expires=Sat, 07 Dec 2024 15:59:03 GMT; Domain=.wired.com; path=/; Secure; SameSite=None;', - 'CN_xid_refresh=40f10590-911c-4553-8a23-0517f2a85fe9; Expires=Tue, 10 Jun 2025 15:59:03 GMT; Domain=.wired.com; path=/; Secure; httponly; SameSite=None;', - 'xid1=1; Expires=Mon, 10 Jun 2024 15:59:18 GMT; Domain=.wired.com; path=/;', - 'CN_segments=co.w2424; Expires=Sat, 07 Dec 2024 15:59:03 GMT; Domain=.wired.com; path=/;', - 'CN_geo_country_code=ES; Expires=Sat, 07 Dec 2024 15:59:03 GMT; Path=/; Domain=wired.com; Samesite=None; Secure', - 'CN_xid=9934a602-d41d-4eb6-a86c-73151ac6715e; Expires=Sat, 07 Dec 2024 15:59:04 GMT; Domain=.wired.com; path=/; Secure; SameSite=None;', - 'CN_xid_refresh=9934a602-d41d-4eb6-a86c-73151ac6715e; Expires=Tue, 10 Jun 2025 15:59:04 GMT; Domain=.wired.com; path=/; Secure; httponly; SameSite=None;', - 'xid1=1; Expires=Mon, 10 Jun 2024 15:59:19 GMT; Domain=.wired.com; path=/;', - 'CN_segments=co.w2424; Expires=Sat, 07 Dec 2024 15:59:04 GMT; Domain=.wired.com; path=/;', - 'verso_bucket=565; Expires=Tue, 10 Jun 2025 15:59:04 GMT; path=/;', - 'CN_geo_country_code=ES; Expires=Sat, 07 Dec 2024 15:59:04 GMT; Path=/; Domain=wired.com; Samesite=None; Secure' + 'content-length' => [ + '167' ], - 'content-security-policy' => [ - 'default-src https: data: \'unsafe-inline\' \'unsafe-eval\'; child-src https: data: blob:; connect-src https: data: blob: wss://*.hotjar.com wss://*.conde.digital; font-src https: data:; img-src https: blob: data: android-webview-video-poster:; media-src blob: data: https:; object-src https:; script-src https: data: blob: \'unsafe-inline\' \'unsafe-eval\'; style-src https: \'unsafe-inline\'; block-all-mixed-content; upgrade-insecure-requests;', - 'default-src https: data: \'unsafe-inline\' \'unsafe-eval\'; child-src https: data: blob:; connect-src https: data: blob: wss://*.hotjar.com wss://*.conde.digital; font-src https: data:; img-src https: blob: data: android-webview-video-poster:; media-src blob: data: https:; object-src https:; script-src https: data: blob: \'unsafe-inline\' \'unsafe-eval\'; style-src https: \'unsafe-inline\'; block-all-mixed-content; upgrade-insecure-requests;' + 'connection' => [ + 'keep-alive' ], - 'x-served-by' => [ - 'cache-mad22074-MAD', - 'cache-iad-kjyo7100080-IAD, cache-mad2200147-MAD' + 'location' => [ + 'https://www.wired.com/?p=2064839' ], 'x-cache' => [ - 'HIT', - 'MISS, HIT' + 'Redirect from cloudfront', + 'Hit from cloudfront' ], - 'x-cache-hits' => [ - '0', - '0, 0' + 'via' => [ + '1.1 06dea94a9acccc89bf073f5b6e5408ea.cloudfront.net (CloudFront)', + '1.1 e8ccc8fdd24646b17e2edb99277c5024.cloudfront.net (CloudFront), 1.1 b3db53b8c0d360b6f708a44987d1b5ea.cloudfront.net (CloudFront)' ], - 'x-timer' => [ - 'S1718035144.955581,VS0,VE0', - 'S1718035144.034389,VS0,VE2' + 'x-amz-cf-pop' => [ + 'NRT57-P2', + 'NRT20-P5', + 'NRT57-P2' ], - 'vary' => [ - '', - 'accept-encoding, cn-experiments, X-UA-Device, high-ad-cadence, Verso' + 'alt-svc' => [ + 'h3=":443"; ma=86400', + 'h3=":443"; ma=86400' ], - 'apple-news-services-host' => [ - 'www.wired.com HTTP/2 200', - 'www.wired.com' + 'x-amz-cf-id' => [ + 'S2owJ59zeRbG9Biy4BXGPAWt0Fxtf6qHw7WPcfgEwERw3WGkUX7RdA== HTTP/2 200', + 'lInWg8AYlFb0iT7fqyMh8p3TRzd1LHfurW8i8Bbb3l0Zb7pJK4n29Q==' ], - 'content-type' => [ - 'text/html; charset=utf-8' + 'modified-at' => [ + '1759504312' ], 'cache-control' => [ - 'no-cache' + 'stale-while-revalidate=60, stale-if-error=86400, s-maxage=900' ], - 'fastly-debug-state' => [ - 'MISS-CLUSTER' + 'back-lae-origin-response-start' => [ + '1759572436271' ], - 'x-esi' => [ - 'on' + 'x-organization-slug' => [ + 'wired' ], - 'verso' => [ - 'true' - ], - 'age' => [ - '85' - ], - 'strict-transport-security' => [ - 'max-age=31536000; preload' + 'x-content-type-options' => [ + 'nosniff' ], 'x-ua-device' => [ 'desktop' ], - 'apple-news-services-request-url' => [ - '/?p=2064839' + 'content-encoding' => [ + 'gzip' ], - 'apple-news-services-parsed-url' => [ - '/?p=2064839' + 'vary' => [ + 'accept-encoding' ], - 'apple-news-services-handled' => [ - 'false' + 'age' => [ + '712' ], - 'content-encoding' => [ - 'gzip' + 'set-cookie' => [ + 'CN_geo_country_code=JP; Expires=Thu, 02 Apr 2026 10:19:08 GMT; path=/; Domain=.wired.com; Secure; SameSite=None;', + 'CN_segments=co.w2540; Expires=Thu, 02 Apr 2026 10:19:08 GMT; Domain=.wired.com; path=/;', + 'CN_xid=ef802d5d-833b-45f0-ad09-1aeff194ebc0; Expires=Thu, 02 Apr 2026 10:19:08 GMT; Domain=.wired.com; path=/; Secure; SameSite=None;', + 'CN_xid_refresh=ef802d5d-833b-45f0-ad09-1aeff194ebc0; Expires=Sun, 04 Oct 2026 10:19:08 GMT; Domain=.wired.com; path=/; Secure; httponly; SameSite=None;', + 'xid1=1; Expires=Sat, 04 Oct 2025 10:19:23 GMT; Domain=.wired.com; path=/;' ], 'Content-Location' => [ 'https://www.wired.com/?p=2064839' ], 'X-Request-Time' => [ - '0.167 ms' + '0.134 ms' ] ], 'statusCode' => 200, 'reasonPhrase' => 'OK', - 'body' => 'WIRED - The Latest in Technology, Science, Culture and Business | WIRED
Skip to main content

WIRED

Today’s Picks

face/off

AI Tools Are Secretly Training on Real Images of Children

A popular AI training dataset is “stealing and weaponizing” the faces of Brazilian children without their knowledge or consent, human rights activists claim.
The Big Interview

How to Lead an Army of Digital Sleuths in the Age of AI

Eliot Higgins and his 28,000 forensic foot soldiers at Bellingcat have kept a miraculous nose for truth—and a sharp sense of its limits—in Gaza, Ukraine, and everywhere else atrocities hide online.
Opression

Inside China’s Massive Surveillance Operation

Originally published May 2019: In northwest China, the government is cracking down on the minority Muslim Uyghur population, keeping them under constant surveillance and throwing more than a million people into concentration camps.
WIRED is where tomorrow is realized. It is the essential source of information and ideas that make sense of a world in constant transformation. The WIRED conversation illuminates how technology is changing every aspect of our lives—from culture to business, science to design. The breakthroughs and innovations that we uncover lead to new ways of thinking, new connections, and new industries.

© 2024 Condé Nast. All rights reserved. WIRED may earn a portion of sales from products that are purchased through our site as part of our Affiliate Partnerships with retailers. The material on this site may not be reproduced, distributed, transmitted, cached or otherwise used, except with the prior written permission of Condé Nast. Ad Choices

Skip to main content

WIRED

China Rolls Out Its First Talent Visa as the US Retreats on H-1Bs
Made in China

China Rolls Out Its First Talent Visa as the US Retreats on H-1Bs

The Chinese government unveiled a program to woo foreign talent just as the US cracked down on H-1Bs with a $100,000 fee. The move immediately provoked xenophobic backlash.
Chatbots Play With Your Emotions to Avoid Saying Goodbye
The Post-Chuck Schumer Era
OpenAI Is Preparing to Launch a Social App for AI-Generated Videos
Broadcast TV Is a 'Melting Ice Cube.’ Kimmel Just Turned Up the Heat
WIRED Takes You Back to School
Generation iPad

WIRED Takes You Back to School

Young people entering classrooms this fall need a totally different tool set than the generations before them.
The New Era of Work Travel
Are We Healthy Yet?
Beyond Wellness

Are We Healthy Yet?

How to Win a Fight
Special Edition

How to Win a Fight

You're Not Ready
Special Edition

You're Not Ready

Geek Power: Steven Levy Revisits Tech Titans, Hackers, Idealists
Master Minds

Geek Power: Steven Levy Revisits Tech Titans, Hackers, Idealists

Originally published April 2010: In 1984, Hackers chronicled the coders, visionaries, and hygiene-challenged nerds who were hatching our digital world. Now the author circles back with them—and checks in with the next generation.
Online Dating Made This Woman a Pawn in a Global Crime Plot
The Battle for the Soul of Buy Nothing
Adoption Moved to Facebook and a War Began
Going Dumb: My Year With a Flip Phone
' + L5.4,7.7c-0.2-0.2-0.2-0.6,0-0.8c0.2-0.2,0.6-0.2,0.8,0l0,0L8,8.6l3.8-4.5C12,3.9,12.4,3.9,12.7,4.1z">
' ]; diff --git a/tests/fixtures/4pda.to.2022-12-04-406834-sostoyalsya_reliz_clown_of_duty_parodii_na_call_of_duty.php b/tests/fixtures/4pda.to.2022-12-04-406834-sostoyalsya_reliz_clown_of_duty_parodii_na_call_of_duty.php index cb4ea0bb..f89ae429 100644 --- a/tests/fixtures/4pda.to.2022-12-04-406834-sostoyalsya_reliz_clown_of_duty_parodii_na_call_of_duty.php +++ b/tests/fixtures/4pda.to.2022-12-04-406834-sostoyalsya_reliz_clown_of_duty_parodii_na_call_of_duty.php @@ -11,7 +11,9 @@ 'feeds' => [], 'icon' => 'https://4pda.to/s/as6ywymaTWM6wnea1mxojxCz0Yet7IeumfOBnaxb.png', 'image' => 'https://i.4pda.ws/s/as6yueQrUwnKt0LgJ5m26uBjbZsccTet21FqwJkADfGw.jpg?v=1669981373', - 'keywords' => ['состоялся релиз clown of duty — пародии на call of duty'], + 'keywords' => [ + 'состоялся релиз clown of duty — пародии на call of duty' + ], 'language' => 'ru-RU', 'languages' => [], 'license' => null, @@ -23,5 +25,5 @@ 'url' => 'https://4pda.to/2022/12/04/406834/sostoyalsya_reliz_clown_of_duty_parodii_na_call_of_duty/', 'linkedData' => [], 'oEmbed' => [], - 'allLinkedData' => [], + 'allLinkedData' => [] ]; diff --git a/tests/fixtures/animoto.com.play-gjsj1gu0wdrfr4pgw12xzq.php b/tests/fixtures/animoto.com.play-gjsj1gu0wdrfr4pgw12xzq.php index b508eb96..96c9fddf 100644 --- a/tests/fixtures/animoto.com.play-gjsj1gu0wdrfr4pgw12xzq.php +++ b/tests/fixtures/animoto.com.play-gjsj1gu0wdrfr4pgw12xzq.php @@ -2,50 +2,55 @@ declare(strict_types = 1); return [ - 'authorName' => null, - 'authorUrl' => null, + 'authorName' => '@animoto', + 'authorUrl' => 'https://twitter.com/animoto', 'cms' => null, - 'code' => [ - 'html' => '', - 'width' => 640, - 'height' => 360, - 'ratio' => 56.25 + 'code' => null, + 'description' => 'Create, edit, and share videos with our free video maker. Combine your photos, video clips, and music to make quality videos in minutes. Get started free!', + 'favicon' => 'https://animoto.com/favicon-32x32.png?v=f7bad0df2a4af8688773dca5ee0b1ed6', + 'feeds' => [ + 'https://animoto.com/rss.xml' ], - 'description' => 'Animoto makes video creation easy! Animoto\'s video maker turns your photos and video clips into professional videos in minutes. Fast and shockingly simple!', - 'favicon' => 'https://d14pr3cu5atb0x.cloudfront.net/images/icons/favicon-fbb19e53d0.ico', - 'feeds' => [], - 'icon' => 'https://d14pr3cu5atb0x.cloudfront.net/images/icons/touchicon-144-4a42d97241.png', - 'image' => 'https://d2m23yiuv18ohn.cloudfront.net/Video/GjsJ1gu0WDRfr4pGw12xZQ/cover_648x360.jpg', + 'icon' => 'https://animoto.com/icons/icon-48x48.png?v=f7bad0df2a4af8688773dca5ee0b1ed6', + 'image' => null, 'keywords' => [], 'language' => null, 'languages' => [], 'license' => null, 'providerName' => 'Animoto', - 'providerUrl' => 'https://animoto.com/', + 'providerUrl' => 'https://animoto.com', 'publishedTime' => null, 'redirect' => null, - 'title' => 'taco bell', + 'title' => 'Free Video Maker | Create & Edit Your Videos Easily', 'url' => 'https://animoto.com/play/GjsJ1gu0WDRfr4pGw12xZQ', - 'linkedData' => [], + 'linkedData' => [ + '@context' => 'http://schema.org', + '@type' => 'WebSite', + 'url' => 'https://animoto.com', + 'name' => 'Animoto video maker - Stand out on social media. Easily.', + 'alternateName' => '' + ], 'oEmbed' => [ - 'version' => 1.0, - 'provider_name' => 'Animoto', - 'provider_url' => 'https://animoto.com/', - 'type' => 'video', - 'author_name' => null, - 'title' => 'taco bell', - 'description' => '', - 'thumbnail_url' => 'https://d2m23yiuv18ohn.cloudfront.net/Video/GjsJ1gu0WDRfr4pGw12xZQ/cover_648x360.jpg', - 'thumbnail_height' => 360, - 'thumbnail_width' => 648, - 'icon_url' => 'https://d2m23yiuv18ohn.cloudfront.net/Video/GjsJ1gu0WDRfr4pGw12xZQ/cover_224x126.jpg', - 'icon_height' => 54, - 'icon_width' => 54, - 'width' => 640, - 'height' => 360, - 'cache_age' => 604800, - 'video_url' => 'https://d150hyw1dtprld.cloudfront.net/swf/w.swf?w=swf/production/vp1&e=1617549702&f=GjsJ1gu0WDRfr4pGw12xZQ&d=0&m=p&r=360p&i=m&asset_domain=s3-p.animoto.com&animoto_domain=animoto.com&options=start_hq', - 'html' => '' + 'error' => 'URL not supported or not found', + 'url' => 'https://animoto.com/play/GjsJ1gu0WDRfr4pGw12xZQ' ], - 'allLinkedData' => [] + 'allLinkedData' => [ + [ + '@context' => 'http://schema.org', + '@type' => 'WebSite', + 'url' => 'https://animoto.com', + 'name' => 'Animoto video maker - Stand out on social media. Easily.', + 'alternateName' => '' + ], + [ + '@context' => 'http://schema.org', + '@type' => 'VideoObject', + 'name' => 'Animoto: Free Online Video Maker', + 'contentUrl' => 'https://d2of6bhnpl91ni.cloudfront.net/cms/animoto-free-online-video-maker-e8d6870030.mp4', + 'description' => 'With Animoto, you\'ll have everything you need to create your own professional videos in minutes. No experience required. All it takes is an idea. ', + 'thumbnailUrl' => '//images.ctfassets.net/00i767ygo3tc/010bacg5wwIhMMx6xYS3qj/18e39c5d16f5614a3b477a284faea1a2/free-online-video-maker.webp', + 'transcript' => 'We all have a story to tell, and video is the best way to make yours stand out. With Animoto, you\'ll have everything you need to create your own professional videos in minutes. No experience required. All it takes is an idea. We\'ve made it easy to get started with customizable templates for everything from saying Happy Birthday to selling your product. From there, it\'s as simple as dragging and dropping your photos and video clips, choosing from our millions of Getty stock images or recording with our built-in screen and webcam recorder. Customize your videos with transitions, music, voiceovers, and more. Then bring your videos on brand with a single click. Make sure your story is heard with Animoto. Everything you need to create your own videos is right at your fingertips. Start creating for free.', + 'uploadDate' => '2020-09-15T21:58:55.636Z' + ] + ] ]; diff --git a/tests/fixtures/archive.org.details-dn2015-0220_vid.php b/tests/fixtures/archive.org.details-dn2015-0220_vid.php index 2907079d..ea395b25 100644 --- a/tests/fixtures/archive.org.details-dn2015-0220_vid.php +++ b/tests/fixtures/archive.org.details-dn2015-0220_vid.php @@ -43,8 +43,8 @@ ], 'oEmbed' => [], 'api' => [ - 'server' => 'ia802600.us.archive.org', - 'dir' => '/20/items/dn2015-0220_vid', + 'server' => 'ia801600.us.archive.org', + 'dir' => '/34/items/dn2015-0220_vid', 'metadata' => [ 'identifier' => [ 'dn2015-0220_vid' @@ -786,11 +786,12 @@ '/dn2015-0220_vid_files.xml' => [ 'source' => 'original', 'format' => 'Metadata', - 'md5' => '745ea2f6dde93e4b70b1c0b238d4c0e2' + 'md5' => 'c8085d21bd5d528af0697f7d1cfff599', + 'summation' => 'md5' ], '/dn2015-0220_vid_meta.xml' => [ 'source' => 'original', - 'mtime' => '1542757137', + 'mtime' => '1675274129', 'size' => '1973', 'format' => 'Metadata', 'md5' => '6a144c80a58ab5f08c0ecffdb580954a', @@ -799,12 +800,12 @@ ] ], 'misc' => [ - 'image' => 'https://ia802600.us.archive.org/20/items/dn2015-0220_vid/dn2015-0220.gif', + 'image' => 'https://ia801600.us.archive.org/34/items/dn2015-0220_vid/dn2015-0220.gif', 'collection-title' => 'Democracy Now!' ], 'item' => [ - 'downloads' => 132, - 'month' => 2, + 'downloads' => 156, + 'month' => 0, 'item_size' => 3667677269, 'files_count' => 68, 'item_count' => null, diff --git a/tests/fixtures/codepen.io.zhouzi-pen-jorazp.php b/tests/fixtures/codepen.io.zhouzi-pen-jorazp.php index a6b16acf..24e7196f 100644 --- a/tests/fixtures/codepen.io.zhouzi-pen-jorazp.php +++ b/tests/fixtures/codepen.io.zhouzi-pen-jorazp.php @@ -3,10 +3,10 @@ return [ 'authorName' => 'Gabin Aureche', - 'authorUrl' => 'https://codepen.io/Zhouzi/', + 'authorUrl' => 'https://codepen.io/Zhouzi', 'cms' => null, 'code' => [ - 'html' => '', + 'html' => '', 'width' => 800, 'height' => 300, 'ratio' => 37.5 @@ -15,7 +15,7 @@ 'favicon' => 'https://codepen.io/favicon.ico', 'feeds' => [], 'icon' => null, - 'image' => 'https://assets.codepen.io/99102/internal/screenshots/pens/JoRazP.default.png?fit=cover&format=auto&ha=true&height=360&quality=75&v=2&version=1467971314&width=640', + 'image' => 'https://shots.codepen.io/username/pen/JoRazP-512.jpg?version=1467971314', 'keywords' => [], 'language' => 'en-US', 'languages' => [], @@ -35,13 +35,13 @@ 'provider_url' => 'https://codepen.io', 'title' => 'TheaterJS', 'author_name' => 'Gabin Aureche', - 'author_url' => 'https://codepen.io/Zhouzi/', + 'author_url' => 'https://codepen.io/Zhouzi', 'height' => '300', 'width' => '800', 'thumbnail_width' => '384', 'thumbnail_height' => '225', - 'thumbnail_url' => 'https://assets.codepen.io/99102/internal/screenshots/pens/JoRazP.default.png?fit=cover&format=auto&ha=true&height=360&quality=75&v=2&version=1467971314&width=640', - 'html' => '' + 'thumbnail_url' => 'https://shots.codepen.io/username/pen/JoRazP-512.jpg?version=1467971314', + 'html' => '' ], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/drive.google.com.file-d-0b2rwn8wabvswbmfjdudnv2vsttg-view.php b/tests/fixtures/drive.google.com.file-d-0b2rwn8wabvswbmfjdudnv2vsttg-view.php index fb451ee5..db8c0b4a 100644 --- a/tests/fixtures/drive.google.com.file-d-0b2rwn8wabvswbmfjdudnv2vsttg-view.php +++ b/tests/fixtures/drive.google.com.file-d-0b2rwn8wabvswbmfjdudnv2vsttg-view.php @@ -7,7 +7,7 @@ 'cms' => null, 'code' => null, 'description' => null, - 'favicon' => 'https://ssl.gstatic.com/images/branding/product/1x/drive_2020q4_32dp.png', + 'favicon' => 'https://drive.google.com/favicon.ico', 'feeds' => [], 'icon' => null, 'image' => null, @@ -15,11 +15,11 @@ 'language' => null, 'languages' => [], 'license' => null, - 'providerName' => 'Google Docs', + 'providerName' => 'Google', 'providerUrl' => 'https://drive.google.com', 'publishedTime' => null, 'redirect' => null, - 'title' => 'Entrevista_Rianxo_RadioFusion_150724.mp3', + 'title' => null, 'url' => 'https://drive.google.com/file/d/0B2rwN8wAbVSWbmFJdUdnV2VSTTg/view', 'linkedData' => [], 'oEmbed' => [], diff --git a/tests/fixtures/en.wikipedia.org.wiki-albert_einstein.php b/tests/fixtures/en.wikipedia.org.wiki-albert_einstein.php index 5311799e..cdd7cb1a 100644 --- a/tests/fixtures/en.wikipedia.org.wiki-albert_einstein.php +++ b/tests/fixtures/en.wikipedia.org.wiki-albert_einstein.php @@ -6,9 +6,11 @@ 'authorUrl' => null, 'cms' => 'mediawiki', 'code' => null, - 'description' => 'Albert Einstein ( EYEN-styne; German: [ˈalbɛʁt ˈʔaɪnʃtaɪn] (listen); 14 March 1879 – 18 April 1955) was a German-born theoretical physicist, widely acknowledged to be one of the greatest physicists of all time. Einstein is known widely for developing the theory of relativity, but he also made important contributions to the development of the theory of quantum mechanics. Relativity and quantum mechanics are together the two pillars of modern physics. His mass–energy equivalence formula E = mc2, which arises from relativity theory, has been dubbed "the world\'s most famous equation". His work is also known for its influence on the philosophy of science. He received...', + 'description' => 'Albert Einstein ( EYEN-styne; German: [ˈalbɛɐt ˈʔaɪnʃtaɪn] ; 14 March 1879 – 18 April 1955) was a German-born theoretical physicist who is widely held to be one of the greatest and most influential scientists of all time. Best known for developing the theory of relativity, Einstein also made important contributions to quantum mechanics, and was thus a central figure in the revolutionary reshaping of the scientific understanding of nature that modern physics accomplished in the first decades of the twentieth century. His mass–energy equivalence formula E = mc2, which arises from relativity theory, has been called "the world\'s most famous equation". He received the 1921 Nobel Prize in Physics "for his services to theoretical physics, and especially for his discovery of the law...', 'favicon' => 'https://en.wikipedia.org/static/favicon/wikipedia.ico', - 'feeds' => [], + 'feeds' => [ + 'https://en.wikipedia.org/w/index.php?title=Special:RecentChanges&feed=atom' + ], 'icon' => 'https://en.wikipedia.org/static/apple-touch/wikipedia.png', 'image' => 'https://upload.wikimedia.org/wikipedia/commons/thumb/3/3e/Einstein_1921_by_F_Schmutzer_-_restoration.jpg/1200px-Einstein_1921_by_F_Schmutzer_-_restoration.jpg', 'keywords' => [], @@ -41,7 +43,7 @@ ] ], 'datePublished' => '2001-11-05T18:26:16Z', - 'dateModified' => '2021-04-04T01:52:51Z', + 'dateModified' => '2024-06-04T23:38:54Z', 'image' => 'https://upload.wikimedia.org/wikipedia/commons/3/3e/Einstein_1921_by_F_Schmutzer_-_restoration.jpg', 'headline' => 'German-born theoretical physicist; developer of the theory of relativity (1879–1955)' ], @@ -52,11 +54,11 @@ 'title' => 'Albert Einstein', 'extract' => '

-

-

+

-

Albert Einstein ( EYEN-styne; German: [ˈalbɛʁt ˈʔaɪnʃtaɪn] (listen); 14 March 1879 – 18 April 1955) was a German-born theoretical physicist, widely acknowledged to be one of the greatest physicists of all time. Einstein is known widely for developing the theory of relativity, but he also made important contributions to the development of the theory of quantum mechanics. Relativity and quantum mechanics are together the two pillars of modern physics. His mass–energy equivalence formula E = mc2, which arises from relativity theory, has been dubbed "the world\'s most famous equation". His work is also known for its influence on the philosophy of science. He received

...' + +

Albert Einstein ( EYEN-styne; German: [ˈalbɛɐt ˈʔaɪnʃtaɪn] ; 14 March 1879 – 18 April 1955) was a German-born theoretical physicist who is widely held to be one of the greatest and most influential scientists of all time. Best known for developing the theory of relativity, Einstein also made important contributions to quantum mechanics, and was thus a central figure in the revolutionary reshaping of the scientific understanding of nature that modern physics accomplished in the first decades of the twentieth century. His mass–energy equivalence formula E = mc2, which arises from relativity theory, has been called "the world\'s most famous equation". He received the 1921 Nobel Prize in Physics "for his services to theoretical physics, and especially for his discovery of the law

...' ], 'allLinkedData' => [ [ @@ -79,7 +81,7 @@ ] ], 'datePublished' => '2001-11-05T18:26:16Z', - 'dateModified' => '2021-04-04T01:52:51Z', + 'dateModified' => '2024-06-04T23:38:54Z', 'image' => 'https://upload.wikimedia.org/wikipedia/commons/3/3e/Einstein_1921_by_F_Schmutzer_-_restoration.jpg', 'headline' => 'German-born theoretical physicist; developer of the theory of relativity (1879–1955)' ] diff --git a/tests/fixtures/gist.github.com.oscarotero-7749998.php b/tests/fixtures/gist.github.com.oscarotero-7749998.php index e82039c7..4d74b347 100644 --- a/tests/fixtures/gist.github.com.oscarotero-7749998.php +++ b/tests/fixtures/gist.github.com.oscarotero-7749998.php @@ -6,7 +6,7 @@ 'authorUrl' => 'https://github.com/oscarotero', 'cms' => null, 'code' => [ - 'html' => '
# Undo latest commit
git reset --soft HEAD^
# Undo the changes of a commit (creating a new commit)
git revert <commit>
# Undo latest merge (before push)
git reset --merge ORIG_HEAD
# Merge a branch using the version of the current branch in case of conflicts
git merge <branch> -s ours
# Discard changes for a particular file after commit
git reset HEAD <file>
# Unstage the current staged changes
git reset
# Create, fetch a remote branch and switch to it
git checkout --track <remote>/<branch>
# Remove a file from git but not from disc
git rm --cached <file>
# Reapply an old commit again
git cherry-pick <commit>
# Ignore the changes of a file
git update-index --assume-unchanged <file>
# Not ignore the changes of a file
git update-index --no-assume-unchanged <file>
# List all "assume-unchanged" files
git ls-files -v|grep '^h'
# Display who did the latest change in each line (ignoring whitespaces)
git blame <filename> -w
# Remove all local branches removed in remote
git fetch origin --prune
# Remove untracked local files
git clean -f
# Remove untracked local files and folders
git clean -fd
# Remove remote branch
git push <remote> --delete <branch>
# Go to an old commit
git read-tree <commit>
# Use the ~/.gitignore file to ignore globally certain files
git config --global core.excludesfile '~/.gitignore'
view raw Git-cheatsheet.sh hosted with ❤ by GitHub
', + 'html' => '
# Undo latest commit
git reset --soft HEAD^
# Undo the changes of a commit (creating a new commit)
git revert <commit>
# Undo latest merge (before push)
git reset --merge ORIG_HEAD
# Merge a branch using the version of the current branch in case of conflicts
git merge <branch> -s ours
# Discard changes for a particular file after commit
git reset HEAD <file>
# Unstage the current staged changes
git reset
# Create, fetch a remote branch and switch to it
git checkout --track <remote>/<branch>
# Remove a file from git but not from disc
git rm --cached <file>
# Reapply an old commit again
git cherry-pick <commit>
# Ignore the changes of a file
git update-index --assume-unchanged <file>
# Not ignore the changes of a file
git update-index --no-assume-unchanged <file>
# List all "assume-unchanged" files
git ls-files -v|grep '^h'
# Display who did the latest change in each line (ignoring whitespaces)
git blame <filename> -w
# Remove all local branches removed in remote
git fetch origin --prune
# Remove untracked local files
git clean -f
# Remove untracked local files and folders
git clean -fd
# Remove remote branch
git push <remote> --delete <branch>
# Go to an old commit
git read-tree <commit>
# Use the ~/.gitignore file to ignore globally certain files
git config --global core.excludesfile '~/.gitignore'
', 'width' => null, 'height' => null, 'ratio' => null @@ -17,7 +17,7 @@ 'https://gist.github.com/oscarotero.atom' ], 'icon' => null, - 'image' => 'https://github.githubassets.com/images/modules/gists/gist-og-image.png', + 'image' => 'https://github.githubassets.com/assets/gist-og-image-54fd7dc0713e.png', 'keywords' => [], 'language' => 'en', 'languages' => [], @@ -39,275 +39,302 @@ ], 'owner' => 'oscarotero', 'div' => '
-
+
-
+
+
-
- - - - - - - - - - - - - + + + + + + + + +
# Undo latest commit
git reset --soft HEAD^
+ +
+ + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - -
# Undo latest commit
git reset --soft HEAD^
# Undo the changes of a commit (creating a new commit)
git revert <commit>
+
# Undo the changes of a commit (creating a new commit)
git revert <commit>
# Undo latest merge (before push)
git reset --merge ORIG_HEAD
+
# Undo latest merge (before push)
git reset --merge ORIG_HEAD
# Merge a branch using the version of the current branch in case of conflicts
git merge <branch> -s ours
+
# Merge a branch using the version of the current branch in case of conflicts
git merge <branch> -s ours
# Discard changes for a particular file after commit
git reset HEAD <file>
+
# Discard changes for a particular file after commit
git reset HEAD <file>
# Unstage the current staged changes
git reset
+
# Unstage the current staged changes
git reset
# Create, fetch a remote branch and switch to it
git checkout --track <remote>/<branch>
+
# Create, fetch a remote branch and switch to it
git checkout --track <remote>/<branch>
# Remove a file from git but not from disc
git rm --cached <file>
+
# Remove a file from git but not from disc
git rm --cached <file>
# Reapply an old commit again
git cherry-pick <commit>
+
# Reapply an old commit again
git cherry-pick <commit>
# Ignore the changes of a file
git update-index --assume-unchanged <file>
+
# Ignore the changes of a file
git update-index --assume-unchanged <file>
# Not ignore the changes of a file
git update-index --no-assume-unchanged <file>
+
# Not ignore the changes of a file
git update-index --no-assume-unchanged <file>
# List all "assume-unchanged" files
git ls-files -v|grep '^h'
+
# List all "assume-unchanged" files
git ls-files -v|grep '^h'
# Display who did the latest change in each line (ignoring whitespaces)
git blame <filename> -w
+
# Display who did the latest change in each line (ignoring whitespaces)
git blame <filename> -w
# Remove all local branches removed in remote
git fetch origin --prune
+
# Remove all local branches removed in remote
git fetch origin --prune
# Remove untracked local files
git clean -f
+
# Remove untracked local files
git clean -f
# Remove untracked local files and folders
git clean -fd
+
# Remove untracked local files and folders
git clean -fd
# Remove remote branch
git push <remote> --delete <branch>
+
# Remove remote branch
git push <remote> --delete <branch>
# Go to an old commit
git read-tree <commit>
+
# Go to an old commit
git read-tree <commit>
# Use the ~/.gitignore file to ignore globally certain files
git config --global core.excludesfile '~/.gitignore'
+
# Use the ~/.gitignore file to ignore globally certain files
git config --global core.excludesfile '~/.gitignore'
+
-
+
- view raw - Git-cheatsheet.sh - hosted with ❤ by GitHub + view raw + + Git-cheatsheet.sh + + hosted with ❤ by GitHub
', - 'stylesheet' => 'https://github.githubassets.com/assets/gist-embed-33b98015caf26cfcbee6ce1a5d1fc768.css' + 'stylesheet' => 'https://github.githubassets.com/assets/gist-embed-8c1a5bab9782.css' ], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/he-who-photographs-rather-ok.tumblr.com.post-165326273724.php b/tests/fixtures/he-who-photographs-rather-ok.tumblr.com.post-165326273724.php index 2c9cf753..cc8f6852 100644 --- a/tests/fixtures/he-who-photographs-rather-ok.tumblr.com.post-165326273724.php +++ b/tests/fixtures/he-who-photographs-rather-ok.tumblr.com.post-165326273724.php @@ -3,10 +3,10 @@ return [ 'authorName' => 'He-who-photographs-rather-OK', - 'authorUrl' => 'https://he-who-photographs-rather-ok.tumblr.com/', + 'authorUrl' => 'https://www.tumblr.com/he-who-photographs-rather-ok', 'cms' => null, 'code' => [ - 'html' => '
https://he-who-photographs-rather-ok.tumblr.com/post/165326273724
', + 'html' => '
https://www.tumblr.com/he-who-photographs-rather-ok/165326273724
', 'width' => 540, 'height' => null, 'ratio' => null @@ -33,26 +33,26 @@ 'publishedTime' => null, 'redirect' => null, 'title' => 'He-who-photographs-rather-OK', - 'url' => 'https://he-who-photographs-rather-ok.tumblr.com/post/165326273724', + 'url' => 'https://www.tumblr.com/he-who-photographs-rather-ok/165326273724', 'linkedData' => [ '@type' => 'SocialMediaPosting', 'url' => 'https://he-who-photographs-rather-ok.tumblr.com/post/165326273724', 'mainEntityOfPage' => true, - 'datePublished' => '2017-09-14T10:20:35-04:00', + 'datePublished' => '2017-09-14T10:20:35+00:00', 'author' => 'he-who-photographs-rather-ok', 'image' => 'https://64.media.tumblr.com/0a0fac1ef8faa0c466d9224567af06aa/tumblr_ow8r0nMybA1spmyulo1_1280.jpg', '@context' => 'http://schema.org' ], 'oEmbed' => [ 'cache_age' => 3600, - 'url' => 'https://he-who-photographs-rather-ok.tumblr.com/post/165326273724', + 'url' => 'https://www.tumblr.com/he-who-photographs-rather-ok/165326273724', 'provider_url' => 'https://www.tumblr.com', 'provider_name' => 'Tumblr', 'author_name' => 'He-who-photographs-rather-OK', 'version' => '1.0', - 'author_url' => 'https://he-who-photographs-rather-ok.tumblr.com/', + 'author_url' => 'https://www.tumblr.com/he-who-photographs-rather-ok', 'type' => 'rich', - 'html' => '
https://he-who-photographs-rather-ok.tumblr.com/post/165326273724
', + 'html' => '
https://www.tumblr.com/he-who-photographs-rather-ok/165326273724
', 'height' => null, 'width' => 540 ], @@ -61,7 +61,7 @@ '@type' => 'SocialMediaPosting', 'url' => 'https://he-who-photographs-rather-ok.tumblr.com/post/165326273724', 'mainEntityOfPage' => true, - 'datePublished' => '2017-09-14T10:20:35-04:00', + 'datePublished' => '2017-09-14T10:20:35+00:00', 'author' => 'he-who-photographs-rather-ok', 'image' => 'https://64.media.tumblr.com/0a0fac1ef8faa0c466d9224567af06aa/tumblr_ow8r0nMybA1spmyulo1_1280.jpg', '@context' => 'http://schema.org' diff --git a/tests/fixtures/i.imgur.com.x6rkcc5.jpg.php b/tests/fixtures/i.imgur.com.x6rkcc5.jpg.php index dd477e85..523fd36f 100644 --- a/tests/fixtures/i.imgur.com.x6rkcc5.jpg.php +++ b/tests/fixtures/i.imgur.com.x6rkcc5.jpg.php @@ -6,10 +6,10 @@ 'authorUrl' => null, 'cms' => null, 'code' => [ - 'html' => '
Picture of a Mountain Gorilla right before he punched the photographer
', - 'width' => 540, - 'height' => 500, - 'ratio' => 92.593 + 'html' => '', + 'width' => null, + 'height' => null, + 'ratio' => null ], 'description' => null, 'favicon' => 'https://i.imgur.com/favicon.ico', @@ -21,20 +21,12 @@ 'languages' => [], 'license' => null, 'providerName' => 'Imgur', - 'providerUrl' => 'https://imgur.com/', + 'providerUrl' => 'https://i.imgur.com', 'publishedTime' => null, 'redirect' => null, 'title' => null, 'url' => 'https://i.imgur.com/X6rkCc5.jpg', 'linkedData' => [], - 'oEmbed' => [ - 'version' => '1.0', - 'type' => 'rich', - 'provider_name' => 'Imgur', - 'provider_url' => 'https://imgur.com', - 'width' => 540, - 'height' => 500, - 'html' => '
Picture of a Mountain Gorilla right before he punched the photographer
' - ], + 'oEmbed' => [], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/ideone.com.whjntg.php b/tests/fixtures/ideone.com.whjntg.php index ea481374..3a6c938a 100644 --- a/tests/fixtures/ideone.com.whjntg.php +++ b/tests/fixtures/ideone.com.whjntg.php @@ -12,7 +12,7 @@ 'ratio' => null ], 'description' => 'Ideone is something more than a pastebin; it\'s an online compiler and debugging tool which allows to compile and run code online in more than 40 programming languages.', - 'favicon' => 'https://stx1.ideone.com/gfx2/img/favicon.png', + 'favicon' => 'https://d2c5ubcnqbm27w.cloudfront.net/gfx2/img/favicon.png', 'feeds' => [], 'icon' => null, 'image' => 'http://profile.ak.fbcdn.net/hprofile-ak-prn1/50232_245768360841_3377786_q.jpg', diff --git a/tests/fixtures/imageshack.com.i-ip7wo0v7j.php b/tests/fixtures/imageshack.com.i-ip7wo0v7j.php index 9adef1df..5c737525 100644 --- a/tests/fixtures/imageshack.com.i-ip7wo0v7j.php +++ b/tests/fixtures/imageshack.com.i-ip7wo0v7j.php @@ -7,20 +7,20 @@ 'cms' => null, 'code' => null, 'description' => null, - 'favicon' => 'http://imagizer.imageshack.com/favicon.ico', + 'favicon' => 'https://imagizer.imageshack.com/favicon.ico', 'feeds' => [], 'icon' => null, - 'image' => 'http://imagizer.imageshack.com/img673/0/imagizer.imageshack.com/img673/9643/7wO0v7.jpg', + 'image' => 'https://imagizer.imageshack.com/img673/0/imagizer.imageshack.com/img673/9643/7wO0v7.jpg', 'keywords' => [], 'language' => null, 'languages' => [], 'license' => null, 'providerName' => 'ImageShack', - 'providerUrl' => 'http://imagizer.imageshack.com', + 'providerUrl' => 'https://imagizer.imageshack.com', 'publishedTime' => '2015-06-29 22:13:36', 'redirect' => null, 'title' => 'Kisses', - 'url' => 'http://imagizer.imageshack.com/img673/0/7wO0v7.jpg', + 'url' => 'https://imagizer.imageshack.com/img673/0/7wO0v7.jpg', 'linkedData' => [], 'oEmbed' => [], 'api' => [ @@ -44,7 +44,7 @@ 'public' => false ], 'comments_count' => 0, - 'comments_disabled' => false, + 'comments_disabled' => true, 'filter' => 0, 'filesize' => 54, 'creation_date' => 1435616016, @@ -68,7 +68,8 @@ 'y_length' => 0 ], 'membership' => 'free', - 'featured_photographer' => false + 'featured_photographer' => false, + 'allow_following' => false ], 'next_images' => [], 'prev_images' => [] diff --git a/tests/fixtures/infogr.am.7743c36a-f3ca-4465-9a80-a8abbd5d8dc4.php b/tests/fixtures/infogr.am.7743c36a-f3ca-4465-9a80-a8abbd5d8dc4.php index 086d148c..184ae35a 100644 --- a/tests/fixtures/infogr.am.7743c36a-f3ca-4465-9a80-a8abbd5d8dc4.php +++ b/tests/fixtures/infogr.am.7743c36a-f3ca-4465-9a80-a8abbd5d8dc4.php @@ -2,11 +2,11 @@ declare(strict_types = 1); return [ - 'authorName' => 'Tony Quesada', - 'authorUrl' => 'https://infogram.com/sabjnewsroom01', + 'authorName' => 'Archive', + 'authorUrl' => 'https://infogram.com/archivef', 'cms' => null, 'code' => [ - 'html' => '
', + 'html' => '
', 'width' => 550, 'height' => 600, 'ratio' => 109.091 @@ -36,11 +36,11 @@ 'title' => 'Frost Bank Advisors (online graphic)', 'thumbnail_url' => 'https://infogram-thumbs-1024.s3-eu-west-1.amazonaws.com/4e31a649-39af-4bfd-a6a9-384e727e36e2.jpg', 'uri' => 'https://infogram.com/frost-bank-advisors-online-graphic-1g8e205xdj03pod', - 'html' => '
', + 'html' => '
', 'width' => 550, 'height' => 600, - 'author_url' => 'https://infogram.com/sabjnewsroom01', - 'author_name' => 'Tony Quesada' + 'author_url' => 'https://infogram.com/archivef', + 'author_name' => 'Archive' ], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/jeanjean.bandcamp.com.track-coquin-l-l-phant.php b/tests/fixtures/jeanjean.bandcamp.com.track-coquin-l-l-phant.php index 7576c24a..2e8ad133 100644 --- a/tests/fixtures/jeanjean.bandcamp.com.track-coquin-l-l-phant.php +++ b/tests/fixtures/jeanjean.bandcamp.com.track-coquin-l-l-phant.php @@ -24,130 +24,303 @@ 'interstellar', 'sept sorts' ], - 'language' => null, + 'language' => 'en', 'languages' => [], 'license' => null, 'providerName' => 'Bandcamp', 'providerUrl' => 'https://jeanjean.bandcamp.com', - 'publishedTime' => '2013-09-15 08:45:29', + 'publishedTime' => '2013-09-14 00:00:00', 'redirect' => null, 'title' => 'Coquin L\'éléphant, by Jean Jean', 'url' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant', 'linkedData' => [ - 'copyrightNotice' => 'All Rights Reserved', + '@type' => 'MusicRecording', '@id' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'track_id', + 'value' => 2592086951 + ], + [ + '@type' => 'PropertyValue', + 'name' => 'art_id', + 'value' => 3289609405 + ], + [ + '@type' => 'PropertyValue', + 'name' => 'license_name', + 'value' => 'all_rights_reserved' + ], + [ + '@type' => 'PropertyValue', + 'name' => 'tracknum', + 'value' => 1 + ] + ], + 'name' => 'Coquin L\'éléphant', + 'duration' => 'P00H03M46S', + 'dateModified' => '13 Sep 2013 18:03:29 GMT', + 'datePublished' => '14 Sep 2013 00:00:00 GMT', 'inAlbum' => [ - '@id' => 'https://jeanjean.bandcamp.com/album/symmetry', '@type' => 'MusicAlbum', - 'name' => 'Symmetry' - ], - '@type' => [ - 'MusicRecording', - 'Product' - ], - 'byArtist' => [ - 'subjectOf' => [ + 'name' => 'Symmetry', + 'albumRelease' => [ [ - '@type' => 'WebSite', - 'url' => 'https://jeanjean.bandcamp.com/music', - 'name' => 'music' + '@type' => 'MusicRelease', + '@id' => 'https://jeanjean.bandcamp.com/album/symmetry', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'item_type', + 'value' => 'a' + ] + ] ], [ - '@type' => 'WebSite', - 'url' => 'https://jeanjean.bandcamp.com/merch', - 'name' => 'merch' + '@type' => [ + 'MusicRelease', + 'Product' + ], + '@id' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant', + 'name' => 'Coquin L\'éléphant', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'item_id', + 'value' => 2592086951 + ], + [ + '@type' => 'PropertyValue', + 'name' => 'item_type', + 'value' => 't' + ], + [ + '@type' => 'PropertyValue', + 'name' => 'selling_band_id', + 'value' => 1235691216 + ], + [ + '@type' => 'PropertyValue', + 'name' => 'type_name', + 'value' => 'Digital' + ], + [ + '@type' => 'PropertyValue', + 'name' => 'art_id', + 'value' => 3289609405 + ] + ], + 'description' => 'Includes high-quality download in MP3, FLAC and more. Paying supporters also get unlimited streaming via the free Bandcamp app.', + 'offers' => [ + '@type' => 'Offer', + 'url' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant#t2592086951-buy', + 'priceCurrency' => 'EUR', + 'price' => 1.0, + 'priceSpecification' => [ + 'minPrice' => 1.0 + ], + 'availability' => 'OnlineOnly' + ], + 'musicReleaseFormat' => 'DigitalFormat', + 'image' => [ + 'https://f4.bcbits.com/img/a3289609405_10.jpg' + ] ], [ - '@type' => 'WebSite', - 'url' => 'https://jeanjean.bandcamp.com/community', - 'name' => 'community' + '@type' => [ + 'MusicRelease', + 'Product' + ], + '@id' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant#p1271421079', + 'name' => '12" Vinyl', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'item_id', + 'value' => 1271421079 + ], + [ + '@type' => 'PropertyValue', + 'name' => 'item_type', + 'value' => 'p' + ], + [ + '@type' => 'PropertyValue', + 'name' => 'selling_band_id', + 'value' => 1235691216 + ], + [ + '@type' => 'PropertyValue', + 'name' => 'type_name', + 'value' => 'Vinyl LP' + ], + [ + '@type' => 'PropertyValue', + 'name' => 'image_ids', + 'value' => [ + 1822625 + ] + ], + [ + '@type' => 'PropertyValue', + 'name' => 'is_music_merch', + 'value' => true + ], + [ + '@type' => 'PropertyValue', + 'name' => 'type_id', + 'value' => 2 + ] + ], + 'offers' => [ + '@type' => 'Offer', + 'url' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant#p1271421079-buy', + 'priceCurrency' => 'EUR', + 'price' => 12.0, + 'priceSpecification' => [ + 'minPrice' => 12.0 + ], + 'availability' => 'SoldOut', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'includes_digital_download', + 'value' => true + ] + ] + ], + 'musicReleaseFormat' => 'VinylFormat', + 'image' => [ + 'https://f4.bcbits.com/img/0001822625_10.jpg' + ] ] ], - '@id' => 'https://jeanjean.bandcamp.com', - 'genre' => 'https://bandcamp.com/tag/rock', + 'albumReleaseType' => 'SingleRelease', + '@id' => 'https://jeanjean.bandcamp.com/album/symmetry', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'download_pref', + 'value' => 2 + ] + ] + ], + 'byArtist' => [ + '@type' => 'MusicGroup', + 'name' => 'Jean Jean', + '@id' => 'https://jeanjean.bandcamp.com' + ], + 'publisher' => [ '@type' => 'MusicGroup', - 'image' => 'https://f4.bcbits.com/img/0012283751_10.jpg', + '@id' => 'https://jeanjean.bandcamp.com', + 'name' => 'Jean Jean', 'additionalProperty' => [ [ - 'value' => 1235691216, '@type' => 'PropertyValue', - 'name' => 'band_id' + 'name' => 'band_id', + 'value' => 1235691216 ], [ - 'value' => 'EUR', '@type' => 'PropertyValue', - 'name' => 'currency' - ] - ], - 'sameAs' => [ - 'http://www.facebook.com/jeanjeanmusic', - 'https://www.instagram.com/wearejeanjean/', - 'http://www.youtube.com/channel/UCOsMtEsuiqbqkFQ9oyqJhHg', - 'http://jeanjeanband.com/', - 'https://twitter.com/jeanjeanband' - ], - 'name' => 'Jean Jean' - ], - '@context' => 'https://schema.org', - 'duration' => 'P00H03M46S', - 'offers' => [ - 'priceSpecification' => [ - 'minPrice' => 1.0 - ], - 'priceCurrency' => 'EUR', - '@type' => 'Offer', - 'additionalProperty' => [ + 'name' => 'has_any_downloads', + 'value' => true + ], [ - 'value' => 2, '@type' => 'PropertyValue', - 'name' => 'download_pref' + 'name' => 'has_download_codes', + 'value' => true ], [ - 'value' => 1000.0, '@type' => 'PropertyValue', - 'name' => 'max_price' + 'name' => 'image_height', + 'value' => 1600 ], [ - 'value' => 1.0, '@type' => 'PropertyValue', - 'name' => 'min_price' + 'name' => 'image_id', + 'value' => 29836714 + ], + [ + '@type' => 'PropertyValue', + 'name' => 'image_width', + 'value' => 2279 ] ], - 'url' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant#buy', - 'price' => 1.0, - 'availability' => 'OnlineOnly' - ], - 'additionalProperty' => [ - [ - 'value' => 2592086951, - '@type' => 'PropertyValue', - 'name' => 'track_id' - ], - [ - 'value' => 226.251, - '@type' => 'PropertyValue', - 'name' => 'duration_secs' - ], - [ - 'value' => 'https://t4.bcbits.com/stream/3a7928de52d6fa132b127481f7d9a0ea/mp3-128/2592086951?p=0&ts=1617636205&t=bec14e3e148c574bf56676ba4812a85d61d12561&token=1617636205_d1a065da6309e56a0174b61186b74b9e383f81d3', - '@type' => 'PropertyValue', - 'name' => 'file_mp3-128' + 'image' => 'https://f4.bcbits.com/img/0029836714_10.jpg', + 'genre' => 'https://bandcamp.com/discover/rock', + 'description' => '« On stage, JEAN JEAN is a mirror to their music : deeply intense, smiling and sharing an outra- geous joie-de-vivre. It all seems so simple, but when it builds up, they take you far, very far away » (Christophe Ehrwein)', + 'mainEntityOfPage' => [ + [ + '@type' => 'WebPage', + 'url' => 'http://www.facebook.com/jeanjeanmusic', + 'name' => 'Facebook' + ], + [ + '@type' => 'WebPage', + 'url' => 'https://www.instagram.com/wearejeanjean/', + 'name' => 'Instagram' + ], + [ + '@type' => 'WebPage', + 'url' => 'http://www.youtube.com/channel/UCOsMtEsuiqbqkFQ9oyqJhHg', + 'name' => 'YouTube' + ], + [ + '@type' => 'WebPage', + 'url' => 'http://jeanjeanband.com/', + 'name' => 'jeanjeanband.com' + ], + [ + '@type' => 'WebPage', + 'url' => 'https://twitter.com/jeanjeanband', + 'name' => 'Twitter' + ] ], - [ - 'value' => 'all_rights_reserved', - '@type' => 'PropertyValue', - 'name' => 'license_name' + 'subjectOf' => [ + [ + '@type' => 'WebPage', + 'url' => 'https://jeanjean.bandcamp.com/music', + 'name' => 'music', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'nav_type', + 'value' => 'm' + ] + ] + ], + [ + '@type' => 'WebPage', + 'url' => 'https://jeanjean.bandcamp.com/merch', + 'name' => 'merch', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'nav_type', + 'value' => 'p' + ] + ] + ], + [ + '@type' => 'WebPage', + 'url' => 'https://jeanjean.bandcamp.com/community', + 'name' => 'community', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'nav_type', + 'value' => 'c' + ] + ] + ] ], - [ - 'value' => true, - '@type' => 'PropertyValue', - 'name' => 'streaming' + 'foundingLocation' => [ + '@type' => 'Place', + 'name' => 'Sept Sorts, France' ] ], - 'dateModified' => '13 Sep 2013 18:03:29 GMT', - 'image' => 'https://f4.bcbits.com/img/a3289609405_10.jpg', - 'name' => 'Coquin L\'éléphant', - 'datePublished' => '15 Sep 2013 08:45:29 GMT', + 'copyrightNotice' => 'All Rights Reserved', 'keywords' => [ 'Rock', 'ambient', @@ -156,144 +329,354 @@ 'interstellar', 'Sept Sorts' ], + 'image' => 'https://f4.bcbits.com/img/a3289609405_10.jpg', 'sponsor' => [ [ '@type' => 'Person', 'url' => 'https://bandcamp.com/jaredremillard', - 'image' => 'https://f4.bcbits.com/img/0001850798_10.jpg', + 'image' => 'https://f4.bcbits.com/img/0001850798_50.jpg', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'image_id', + 'value' => 1850798 + ] + ], 'name' => 'Jared ReMillard' ], [ '@type' => 'Person', 'url' => 'https://bandcamp.com/sheebypanda', - 'image' => 'https://f4.bcbits.com/img/0004764347_10.jpg', + 'image' => 'https://f4.bcbits.com/img/0004764347_50.jpg', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'image_id', + 'value' => 4764347 + ] + ], 'name' => 'SheebyPanda' ], + [ + '@type' => 'Person', + 'url' => 'https://bandcamp.com/salvajer911', + 'image' => 'https://f4.bcbits.com/img/0017750248_50.jpg', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'image_id', + 'value' => 17750248 + ] + ], + 'name' => 'Guilhem Sauvage' + ], [ '@type' => 'Person', 'url' => 'https://bandcamp.com/tylerthornhill', - 'image' => 'https://f4.bcbits.com/img/0001705921_10.jpg', + 'image' => 'https://f4.bcbits.com/img/0001705921_50.jpg', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'image_id', + 'value' => 1705921 + ] + ], 'name' => 'Tyler Thornhill' ] - ] + ], + 'mainEntityOfPage' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant', + '@context' => 'https://schema.org' ], 'oEmbed' => [], 'allLinkedData' => [ [ - 'copyrightNotice' => 'All Rights Reserved', + '@type' => 'MusicRecording', '@id' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'track_id', + 'value' => 2592086951 + ], + [ + '@type' => 'PropertyValue', + 'name' => 'art_id', + 'value' => 3289609405 + ], + [ + '@type' => 'PropertyValue', + 'name' => 'license_name', + 'value' => 'all_rights_reserved' + ], + [ + '@type' => 'PropertyValue', + 'name' => 'tracknum', + 'value' => 1 + ] + ], + 'name' => 'Coquin L\'éléphant', + 'duration' => 'P00H03M46S', + 'dateModified' => '13 Sep 2013 18:03:29 GMT', + 'datePublished' => '14 Sep 2013 00:00:00 GMT', 'inAlbum' => [ - '@id' => 'https://jeanjean.bandcamp.com/album/symmetry', '@type' => 'MusicAlbum', - 'name' => 'Symmetry' - ], - '@type' => [ - 'MusicRecording', - 'Product' - ], - 'byArtist' => [ - 'subjectOf' => [ + 'name' => 'Symmetry', + 'albumRelease' => [ [ - '@type' => 'WebSite', - 'url' => 'https://jeanjean.bandcamp.com/music', - 'name' => 'music' + '@type' => 'MusicRelease', + '@id' => 'https://jeanjean.bandcamp.com/album/symmetry', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'item_type', + 'value' => 'a' + ] + ] ], [ - '@type' => 'WebSite', - 'url' => 'https://jeanjean.bandcamp.com/merch', - 'name' => 'merch' + '@type' => [ + 'MusicRelease', + 'Product' + ], + '@id' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant', + 'name' => 'Coquin L\'éléphant', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'item_id', + 'value' => 2592086951 + ], + [ + '@type' => 'PropertyValue', + 'name' => 'item_type', + 'value' => 't' + ], + [ + '@type' => 'PropertyValue', + 'name' => 'selling_band_id', + 'value' => 1235691216 + ], + [ + '@type' => 'PropertyValue', + 'name' => 'type_name', + 'value' => 'Digital' + ], + [ + '@type' => 'PropertyValue', + 'name' => 'art_id', + 'value' => 3289609405 + ] + ], + 'description' => 'Includes high-quality download in MP3, FLAC and more. Paying supporters also get unlimited streaming via the free Bandcamp app.', + 'offers' => [ + '@type' => 'Offer', + 'url' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant#t2592086951-buy', + 'priceCurrency' => 'EUR', + 'price' => 1.0, + 'priceSpecification' => [ + 'minPrice' => 1.0 + ], + 'availability' => 'OnlineOnly' + ], + 'musicReleaseFormat' => 'DigitalFormat', + 'image' => [ + 'https://f4.bcbits.com/img/a3289609405_10.jpg' + ] ], [ - '@type' => 'WebSite', - 'url' => 'https://jeanjean.bandcamp.com/community', - 'name' => 'community' + '@type' => [ + 'MusicRelease', + 'Product' + ], + '@id' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant#p1271421079', + 'name' => '12" Vinyl', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'item_id', + 'value' => 1271421079 + ], + [ + '@type' => 'PropertyValue', + 'name' => 'item_type', + 'value' => 'p' + ], + [ + '@type' => 'PropertyValue', + 'name' => 'selling_band_id', + 'value' => 1235691216 + ], + [ + '@type' => 'PropertyValue', + 'name' => 'type_name', + 'value' => 'Vinyl LP' + ], + [ + '@type' => 'PropertyValue', + 'name' => 'image_ids', + 'value' => [ + 1822625 + ] + ], + [ + '@type' => 'PropertyValue', + 'name' => 'is_music_merch', + 'value' => true + ], + [ + '@type' => 'PropertyValue', + 'name' => 'type_id', + 'value' => 2 + ] + ], + 'offers' => [ + '@type' => 'Offer', + 'url' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant#p1271421079-buy', + 'priceCurrency' => 'EUR', + 'price' => 12.0, + 'priceSpecification' => [ + 'minPrice' => 12.0 + ], + 'availability' => 'SoldOut', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'includes_digital_download', + 'value' => true + ] + ] + ], + 'musicReleaseFormat' => 'VinylFormat', + 'image' => [ + 'https://f4.bcbits.com/img/0001822625_10.jpg' + ] ] ], - '@id' => 'https://jeanjean.bandcamp.com', - 'genre' => 'https://bandcamp.com/tag/rock', + 'albumReleaseType' => 'SingleRelease', + '@id' => 'https://jeanjean.bandcamp.com/album/symmetry', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'download_pref', + 'value' => 2 + ] + ] + ], + 'byArtist' => [ '@type' => 'MusicGroup', - 'image' => 'https://f4.bcbits.com/img/0012283751_10.jpg', + 'name' => 'Jean Jean', + '@id' => 'https://jeanjean.bandcamp.com' + ], + 'publisher' => [ + '@type' => 'MusicGroup', + '@id' => 'https://jeanjean.bandcamp.com', + 'name' => 'Jean Jean', 'additionalProperty' => [ [ - 'value' => 1235691216, '@type' => 'PropertyValue', - 'name' => 'band_id' + 'name' => 'band_id', + 'value' => 1235691216 ], [ - 'value' => 'EUR', '@type' => 'PropertyValue', - 'name' => 'currency' - ] - ], - 'sameAs' => [ - 'http://www.facebook.com/jeanjeanmusic', - 'https://www.instagram.com/wearejeanjean/', - 'http://www.youtube.com/channel/UCOsMtEsuiqbqkFQ9oyqJhHg', - 'http://jeanjeanband.com/', - 'https://twitter.com/jeanjeanband' - ], - 'name' => 'Jean Jean' - ], - '@context' => 'https://schema.org', - 'duration' => 'P00H03M46S', - 'offers' => [ - 'priceSpecification' => [ - 'minPrice' => 1.0 - ], - 'priceCurrency' => 'EUR', - '@type' => 'Offer', - 'additionalProperty' => [ + 'name' => 'has_any_downloads', + 'value' => true + ], + [ + '@type' => 'PropertyValue', + 'name' => 'has_download_codes', + 'value' => true + ], [ - 'value' => 2, '@type' => 'PropertyValue', - 'name' => 'download_pref' + 'name' => 'image_height', + 'value' => 1600 ], [ - 'value' => 1000.0, '@type' => 'PropertyValue', - 'name' => 'max_price' + 'name' => 'image_id', + 'value' => 29836714 ], [ - 'value' => 1.0, '@type' => 'PropertyValue', - 'name' => 'min_price' + 'name' => 'image_width', + 'value' => 2279 ] ], - 'url' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant#buy', - 'price' => 1.0, - 'availability' => 'OnlineOnly' - ], - 'additionalProperty' => [ - [ - 'value' => 2592086951, - '@type' => 'PropertyValue', - 'name' => 'track_id' - ], - [ - 'value' => 226.251, - '@type' => 'PropertyValue', - 'name' => 'duration_secs' - ], - [ - 'value' => 'https://t4.bcbits.com/stream/3a7928de52d6fa132b127481f7d9a0ea/mp3-128/2592086951?p=0&ts=1617636205&t=bec14e3e148c574bf56676ba4812a85d61d12561&token=1617636205_d1a065da6309e56a0174b61186b74b9e383f81d3', - '@type' => 'PropertyValue', - 'name' => 'file_mp3-128' + 'image' => 'https://f4.bcbits.com/img/0029836714_10.jpg', + 'genre' => 'https://bandcamp.com/discover/rock', + 'description' => '« On stage, JEAN JEAN is a mirror to their music : deeply intense, smiling and sharing an outra- geous joie-de-vivre. It all seems so simple, but when it builds up, they take you far, very far away » (Christophe Ehrwein)', + 'mainEntityOfPage' => [ + [ + '@type' => 'WebPage', + 'url' => 'http://www.facebook.com/jeanjeanmusic', + 'name' => 'Facebook' + ], + [ + '@type' => 'WebPage', + 'url' => 'https://www.instagram.com/wearejeanjean/', + 'name' => 'Instagram' + ], + [ + '@type' => 'WebPage', + 'url' => 'http://www.youtube.com/channel/UCOsMtEsuiqbqkFQ9oyqJhHg', + 'name' => 'YouTube' + ], + [ + '@type' => 'WebPage', + 'url' => 'http://jeanjeanband.com/', + 'name' => 'jeanjeanband.com' + ], + [ + '@type' => 'WebPage', + 'url' => 'https://twitter.com/jeanjeanband', + 'name' => 'Twitter' + ] ], - [ - 'value' => 'all_rights_reserved', - '@type' => 'PropertyValue', - 'name' => 'license_name' + 'subjectOf' => [ + [ + '@type' => 'WebPage', + 'url' => 'https://jeanjean.bandcamp.com/music', + 'name' => 'music', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'nav_type', + 'value' => 'm' + ] + ] + ], + [ + '@type' => 'WebPage', + 'url' => 'https://jeanjean.bandcamp.com/merch', + 'name' => 'merch', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'nav_type', + 'value' => 'p' + ] + ] + ], + [ + '@type' => 'WebPage', + 'url' => 'https://jeanjean.bandcamp.com/community', + 'name' => 'community', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'nav_type', + 'value' => 'c' + ] + ] + ] ], - [ - 'value' => true, - '@type' => 'PropertyValue', - 'name' => 'streaming' + 'foundingLocation' => [ + '@type' => 'Place', + 'name' => 'Sept Sorts, France' ] ], - 'dateModified' => '13 Sep 2013 18:03:29 GMT', - 'image' => 'https://f4.bcbits.com/img/a3289609405_10.jpg', - 'name' => 'Coquin L\'éléphant', - 'datePublished' => '15 Sep 2013 08:45:29 GMT', + 'copyrightNotice' => 'All Rights Reserved', 'keywords' => [ 'Rock', 'ambient', @@ -302,26 +685,63 @@ 'interstellar', 'Sept Sorts' ], + 'image' => 'https://f4.bcbits.com/img/a3289609405_10.jpg', 'sponsor' => [ [ '@type' => 'Person', 'url' => 'https://bandcamp.com/jaredremillard', - 'image' => 'https://f4.bcbits.com/img/0001850798_10.jpg', + 'image' => 'https://f4.bcbits.com/img/0001850798_50.jpg', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'image_id', + 'value' => 1850798 + ] + ], 'name' => 'Jared ReMillard' ], [ '@type' => 'Person', 'url' => 'https://bandcamp.com/sheebypanda', - 'image' => 'https://f4.bcbits.com/img/0004764347_10.jpg', + 'image' => 'https://f4.bcbits.com/img/0004764347_50.jpg', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'image_id', + 'value' => 4764347 + ] + ], 'name' => 'SheebyPanda' ], + [ + '@type' => 'Person', + 'url' => 'https://bandcamp.com/salvajer911', + 'image' => 'https://f4.bcbits.com/img/0017750248_50.jpg', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'image_id', + 'value' => 17750248 + ] + ], + 'name' => 'Guilhem Sauvage' + ], [ '@type' => 'Person', 'url' => 'https://bandcamp.com/tylerthornhill', - 'image' => 'https://f4.bcbits.com/img/0001705921_10.jpg', + 'image' => 'https://f4.bcbits.com/img/0001705921_50.jpg', + 'additionalProperty' => [ + [ + '@type' => 'PropertyValue', + 'name' => 'image_id', + 'value' => 1705921 + ] + ], 'name' => 'Tyler Thornhill' ] - ] + ], + 'mainEntityOfPage' => 'https://jeanjean.bandcamp.com/track/coquin-l-l-phant', + '@context' => 'https://schema.org' ] ] ]; diff --git a/tests/fixtures/jsfiddle.net.zhm5rjnz.php b/tests/fixtures/jsfiddle.net.zhm5rjnz.php index 8c016ab3..5edf5fa6 100644 --- a/tests/fixtures/jsfiddle.net.zhm5rjnz.php +++ b/tests/fixtures/jsfiddle.net.zhm5rjnz.php @@ -7,7 +7,7 @@ 'cms' => null, 'code' => null, 'description' => 'Test your JavaScript, CSS, HTML or CoffeeScript online with JSFiddle code editor.', - 'favicon' => 'http://jsfiddle.net/img/favicon.png', + 'favicon' => 'https://jsfiddle.net/img/favicon.png', 'feeds' => [], 'icon' => null, 'image' => null, @@ -25,11 +25,11 @@ 'languages' => [], 'license' => 'share alike', 'providerName' => 'Jsfiddle', - 'providerUrl' => 'http://jsfiddle.net', + 'providerUrl' => 'https://jsfiddle.net', 'publishedTime' => null, 'redirect' => null, 'title' => 'Edit fiddle - JSFiddle - Code Playground', - 'url' => 'http://jsfiddle.net/zhm5rjnz/', + 'url' => 'https://jsfiddle.net/zhm5rjnz/', 'linkedData' => [], 'oEmbed' => [], 'allLinkedData' => [] diff --git a/tests/fixtures/live.amcharts.com.cznjj.php b/tests/fixtures/live.amcharts.com.cznjj.php index b52b3557..f718e91a 100644 --- a/tests/fixtures/live.amcharts.com.cznjj.php +++ b/tests/fixtures/live.amcharts.com.cznjj.php @@ -3,45 +3,25 @@ return [ 'authorName' => null, - 'authorUrl' => 'https://live.amcharts.com/czNjJ/', + 'authorUrl' => null, 'cms' => null, - 'code' => [ - 'html' => '', - 'width' => 600, - 'height' => 400, - 'ratio' => 66.667 - ], - 'description' => 'Let\\\'s see how the history of the winner of 2014 World Cup - Germany - compares to the two runners-up: Argentina and The Netherlands.', + 'code' => null, + 'description' => 'amCharts live editor: create, configure, tweak, edit data, export, import, save, share in a single interface, the user-friendly way.', 'favicon' => 'https://live.amcharts.com/static/img/page/favicon.png', 'feeds' => [], 'icon' => null, - 'image' => 'http://generated.amcharts.com/cz/czNjJ-full.png', + 'image' => 'https://live.amcharts.com/static/img/page/editor-shot.jpg', 'keywords' => [], 'language' => 'en', 'languages' => [], 'license' => null, 'providerName' => 'amCharts', - 'providerUrl' => 'http://www.amcharts.com/', + 'providerUrl' => 'https://live.amcharts.com', 'publishedTime' => null, 'redirect' => null, - 'title' => 'World Cup History', + 'title' => 'Live Chart Editor', 'url' => 'https://live.amcharts.com/czNjJ/', 'linkedData' => [], - 'oEmbed' => [ - 'type' => 'rich', - 'html' => '', - 'width' => 600, - 'height' => 400, - 'title' => 'World Cup History', - 'author_name' => '', - 'author_url' => '//live.amcharts.com/czNjJ/', - 'canonical' => '//live.amcharts.com/czNjJ/', - 'description' => 'Let\\\'s see how the history of the winner of 2014 World Cup - Germany - compares to the two runners-up: Argentina and The Netherlands.', - 'provider_name' => 'amCharts', - 'provider_url' => 'http://www.amcharts.com/', - 'thumbnail_url' => 'http://generated.amcharts.com/cz/czNjJ-full.png', - 'thumbnail_width' => 404, - 'thumbnail_height' => 300 - ], + 'oEmbed' => [], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/media.photobucket.com.user-ignwar-media-album-20deserts-moonrisemonumentvalleyutah.jpg.html.c07885ac4c85b35f31f6dc858d4d64b0.php b/tests/fixtures/media.photobucket.com.user-ignwar-media-album-20deserts-moonrisemonumentvalleyutah.jpg.html.c07885ac4c85b35f31f6dc858d4d64b0.php index 105e4a5b..862a5c0c 100644 --- a/tests/fixtures/media.photobucket.com.user-ignwar-media-album-20deserts-moonrisemonumentvalleyutah.jpg.html.c07885ac4c85b35f31f6dc858d4d64b0.php +++ b/tests/fixtures/media.photobucket.com.user-ignwar-media-album-20deserts-moonrisemonumentvalleyutah.jpg.html.c07885ac4c85b35f31f6dc858d4d64b0.php @@ -6,21 +6,21 @@ 'authorUrl' => null, 'cms' => null, 'code' => null, - 'description' => null, - 'favicon' => 'https://app-content.photobucket.com/favicon-32x32.png', + 'description' => 'Store your photos and videos online with secure storage from Photobucket. Available on iOS, Android and desktop. Securely backup your memories and sign up today!', + 'favicon' => 'https://media.photobucket.com/favicon.ico', 'feeds' => [], - 'icon' => 'https://app-content.photobucket.com/apple-touch-icon.png', - 'image' => 'https://hosting.photobucket.com/albums/h235/Ignwar/SunflowerFieldposter2.jpg', + 'icon' => 'https://media.photobucket.com/logo192.png', + 'image' => null, 'keywords' => [], 'language' => 'en', 'languages' => [], 'license' => null, - 'providerName' => 'Photobucket.com, Inc.', - 'providerUrl' => 'https://app.photobucket.com', + 'providerName' => 'Photobucket', + 'providerUrl' => 'https://media.photobucket.com', 'publishedTime' => null, 'redirect' => null, - 'title' => 'My Library', - 'url' => 'https://app.photobucket.com/u/Ignwar?filters%5Bterm%5D=sunsets&filters%5Bprimary%5D=images', + 'title' => 'Photo Storage', + 'url' => 'https://media.photobucket.com/user/Ignwar/media/Album%20Deserts/MoonriseMonumentValleyUtah.jpg.html?filters%5Bterm%5D=sunsets&filters%5Bprimary%5D=images', 'linkedData' => [], 'oEmbed' => [], 'allLinkedData' => [] diff --git a/tests/fixtures/open.spotify.com.album-7s66wu1xj2nsuuwm2nkiuv.php b/tests/fixtures/open.spotify.com.album-7s66wu1xj2nsuuwm2nkiuv.php index 29f18cc1..52680753 100644 --- a/tests/fixtures/open.spotify.com.album-7s66wu1xj2nsuuwm2nkiuv.php +++ b/tests/fixtures/open.spotify.com.album-7s66wu1xj2nsuuwm2nkiuv.php @@ -6,34 +6,46 @@ 'authorUrl' => null, 'cms' => null, 'code' => [ - 'html' => '', + 'html' => '', 'width' => 456, - 'height' => 380, - 'ratio' => 83.333 + 'height' => 352, + 'ratio' => 77.193 ], - 'description' => 'Various Artists · Compilation · 1996 · 44 songs.', - 'favicon' => 'https://open.scdn.co/cdn/images/favicon32.a19b4f5b.png', + 'description' => 'Xabarin · Album · 1994 · 44 songs.', + 'favicon' => 'https://open.spotifycdn.com/cdn/images/favicon32.b64ecc03.png', 'feeds' => [], 'icon' => null, - 'image' => 'https://i.scdn.co/image/ab67616d00001e02022aca057f08a1d40c1f0733', + 'image' => 'https://i.scdn.co/image/ab67616d00001e022dd9ff41aad54c1d93d50aa6', 'keywords' => [], 'language' => 'en', - 'languages' => [], + 'languages' => [ + 'x-default' => 'https://open.spotify.com/album/7s66wU1XJ2NsUuWM2NKiUV', + 'en' => 'https://open.spotify.com/album/7s66wU1XJ2NsUuWM2NKiUV', + 'id' => 'https://open.spotify.com/intl-id/album/7s66wU1XJ2NsUuWM2NKiUV', + 'de' => 'https://open.spotify.com/intl-de/album/7s66wU1XJ2NsUuWM2NKiUV', + 'pt' => 'https://open.spotify.com/intl-pt/album/7s66wU1XJ2NsUuWM2NKiUV', + 'ja' => 'https://open.spotify.com/intl-ja/album/7s66wU1XJ2NsUuWM2NKiUV', + 'fr' => 'https://open.spotify.com/intl-fr/album/7s66wU1XJ2NsUuWM2NKiUV', + 'ar' => 'https://open.spotify.com/intl-ar/album/7s66wU1XJ2NsUuWM2NKiUV', + 'es' => 'https://open.spotify.com/intl-es/album/7s66wU1XJ2NsUuWM2NKiUV', + 'tr' => 'https://open.spotify.com/intl-tr/album/7s66wU1XJ2NsUuWM2NKiUV', + 'it' => 'https://open.spotify.com/intl-it/album/7s66wU1XJ2NsUuWM2NKiUV' + ], 'license' => null, 'providerName' => 'Spotify', 'providerUrl' => 'https://spotify.com/', - 'publishedTime' => '1996-03-05 00:00:00', + 'publishedTime' => '1994-03-01 00:00:00', 'redirect' => null, - 'title' => 'A Cantar con Xabarin (Vol. I & II)', + 'title' => 'A Cantar con Xabarin (Vol. I e II)', 'url' => 'https://open.spotify.com/album/7s66wU1XJ2NsUuWM2NKiUV', 'linkedData' => [ '@context' => 'http://schema.googleapis.com/', '@type' => 'MusicAlbum', '@id' => 'https://open.spotify.com/album/7s66wU1XJ2NsUuWM2NKiUV', 'url' => 'https://open.spotify.com/album/7s66wU1XJ2NsUuWM2NKiUV', - 'name' => 'A Cantar con Xabarin (Vol. I & II)', - 'description' => 'Listen to A Cantar con Xabarin (Vol. I & II) on Spotify. Various Artists · Compilation · 1996 · 44 songs.', - 'datePublished' => '1996-03-05', + 'name' => 'A Cantar con Xabarin (Vol. I e II)', + 'description' => 'Listen to A Cantar con Xabarin (Vol. I e II) on Spotify. Xabarin · Album · 1994 · 44 songs.', + 'datePublished' => '1994-03-01', 'potentialAction' => [ '@type' => 'ListenAction', 'target' => [ @@ -52,733 +64,21 @@ 'expectsAcceptanceOf' => [ '@type' => 'Offer', 'category' => 'free', - 'eligibleRegion' => [ - [ - '@type' => 'Country', - 'name' => 'AD' - ], - [ - '@type' => 'Country', - 'name' => 'AE' - ], - [ - '@type' => 'Country', - 'name' => 'AG' - ], - [ - '@type' => 'Country', - 'name' => 'AL' - ], - [ - '@type' => 'Country', - 'name' => 'AM' - ], - [ - '@type' => 'Country', - 'name' => 'AO' - ], - [ - '@type' => 'Country', - 'name' => 'AR' - ], - [ - '@type' => 'Country', - 'name' => 'AT' - ], - [ - '@type' => 'Country', - 'name' => 'AU' - ], - [ - '@type' => 'Country', - 'name' => 'AZ' - ], - [ - '@type' => 'Country', - 'name' => 'BA' - ], - [ - '@type' => 'Country', - 'name' => 'BB' - ], - [ - '@type' => 'Country', - 'name' => 'BD' - ], - [ - '@type' => 'Country', - 'name' => 'BE' - ], - [ - '@type' => 'Country', - 'name' => 'BF' - ], - [ - '@type' => 'Country', - 'name' => 'BG' - ], - [ - '@type' => 'Country', - 'name' => 'BH' - ], - [ - '@type' => 'Country', - 'name' => 'BI' - ], - [ - '@type' => 'Country', - 'name' => 'BJ' - ], - [ - '@type' => 'Country', - 'name' => 'BN' - ], - [ - '@type' => 'Country', - 'name' => 'BO' - ], - [ - '@type' => 'Country', - 'name' => 'BR' - ], - [ - '@type' => 'Country', - 'name' => 'BS' - ], - [ - '@type' => 'Country', - 'name' => 'BT' - ], - [ - '@type' => 'Country', - 'name' => 'BW' - ], - [ - '@type' => 'Country', - 'name' => 'BY' - ], - [ - '@type' => 'Country', - 'name' => 'BZ' - ], - [ - '@type' => 'Country', - 'name' => 'CA' - ], - [ - '@type' => 'Country', - 'name' => 'CH' - ], - [ - '@type' => 'Country', - 'name' => 'CI' - ], - [ - '@type' => 'Country', - 'name' => 'CL' - ], - [ - '@type' => 'Country', - 'name' => 'CM' - ], - [ - '@type' => 'Country', - 'name' => 'CO' - ], - [ - '@type' => 'Country', - 'name' => 'CR' - ], - [ - '@type' => 'Country', - 'name' => 'CV' - ], - [ - '@type' => 'Country', - 'name' => 'CW' - ], - [ - '@type' => 'Country', - 'name' => 'CY' - ], - [ - '@type' => 'Country', - 'name' => 'CZ' - ], - [ - '@type' => 'Country', - 'name' => 'DE' - ], - [ - '@type' => 'Country', - 'name' => 'DJ' - ], - [ - '@type' => 'Country', - 'name' => 'DK' - ], - [ - '@type' => 'Country', - 'name' => 'DM' - ], - [ - '@type' => 'Country', - 'name' => 'DO' - ], - [ - '@type' => 'Country', - 'name' => 'DZ' - ], - [ - '@type' => 'Country', - 'name' => 'EC' - ], - [ - '@type' => 'Country', - 'name' => 'EE' - ], - [ - '@type' => 'Country', - 'name' => 'EG' - ], - [ - '@type' => 'Country', - 'name' => 'ES' - ], - [ - '@type' => 'Country', - 'name' => 'FI' - ], - [ - '@type' => 'Country', - 'name' => 'FJ' - ], - [ - '@type' => 'Country', - 'name' => 'FM' - ], - [ - '@type' => 'Country', - 'name' => 'FR' - ], - [ - '@type' => 'Country', - 'name' => 'GA' - ], - [ - '@type' => 'Country', - 'name' => 'GB' - ], - [ - '@type' => 'Country', - 'name' => 'GD' - ], - [ - '@type' => 'Country', - 'name' => 'GE' - ], - [ - '@type' => 'Country', - 'name' => 'GH' - ], - [ - '@type' => 'Country', - 'name' => 'GM' - ], - [ - '@type' => 'Country', - 'name' => 'GN' - ], - [ - '@type' => 'Country', - 'name' => 'GQ' - ], - [ - '@type' => 'Country', - 'name' => 'GR' - ], - [ - '@type' => 'Country', - 'name' => 'GT' - ], - [ - '@type' => 'Country', - 'name' => 'GW' - ], - [ - '@type' => 'Country', - 'name' => 'GY' - ], - [ - '@type' => 'Country', - 'name' => 'HK' - ], - [ - '@type' => 'Country', - 'name' => 'HN' - ], - [ - '@type' => 'Country', - 'name' => 'HR' - ], - [ - '@type' => 'Country', - 'name' => 'HT' - ], - [ - '@type' => 'Country', - 'name' => 'HU' - ], - [ - '@type' => 'Country', - 'name' => 'ID' - ], - [ - '@type' => 'Country', - 'name' => 'IE' - ], - [ - '@type' => 'Country', - 'name' => 'IL' - ], - [ - '@type' => 'Country', - 'name' => 'IN' - ], - [ - '@type' => 'Country', - 'name' => 'IS' - ], - [ - '@type' => 'Country', - 'name' => 'IT' - ], - [ - '@type' => 'Country', - 'name' => 'JM' - ], - [ - '@type' => 'Country', - 'name' => 'JO' - ], - [ - '@type' => 'Country', - 'name' => 'JP' - ], - [ - '@type' => 'Country', - 'name' => 'KE' - ], - [ - '@type' => 'Country', - 'name' => 'KG' - ], - [ - '@type' => 'Country', - 'name' => 'KH' - ], - [ - '@type' => 'Country', - 'name' => 'KI' - ], - [ - '@type' => 'Country', - 'name' => 'KM' - ], - [ - '@type' => 'Country', - 'name' => 'KN' - ], - [ - '@type' => 'Country', - 'name' => 'KR' - ], - [ - '@type' => 'Country', - 'name' => 'KW' - ], - [ - '@type' => 'Country', - 'name' => 'KZ' - ], - [ - '@type' => 'Country', - 'name' => 'LA' - ], - [ - '@type' => 'Country', - 'name' => 'LB' - ], - [ - '@type' => 'Country', - 'name' => 'LC' - ], - [ - '@type' => 'Country', - 'name' => 'LI' - ], - [ - '@type' => 'Country', - 'name' => 'LK' - ], - [ - '@type' => 'Country', - 'name' => 'LR' - ], - [ - '@type' => 'Country', - 'name' => 'LS' - ], - [ - '@type' => 'Country', - 'name' => 'LT' - ], - [ - '@type' => 'Country', - 'name' => 'LU' - ], - [ - '@type' => 'Country', - 'name' => 'LV' - ], - [ - '@type' => 'Country', - 'name' => 'MA' - ], - [ - '@type' => 'Country', - 'name' => 'MC' - ], - [ - '@type' => 'Country', - 'name' => 'MD' - ], - [ - '@type' => 'Country', - 'name' => 'ME' - ], - [ - '@type' => 'Country', - 'name' => 'MG' - ], - [ - '@type' => 'Country', - 'name' => 'MH' - ], - [ - '@type' => 'Country', - 'name' => 'MK' - ], - [ - '@type' => 'Country', - 'name' => 'ML' - ], - [ - '@type' => 'Country', - 'name' => 'MN' - ], - [ - '@type' => 'Country', - 'name' => 'MO' - ], - [ - '@type' => 'Country', - 'name' => 'MR' - ], - [ - '@type' => 'Country', - 'name' => 'MT' - ], - [ - '@type' => 'Country', - 'name' => 'MU' - ], - [ - '@type' => 'Country', - 'name' => 'MV' - ], - [ - '@type' => 'Country', - 'name' => 'MW' - ], - [ - '@type' => 'Country', - 'name' => 'MX' - ], - [ - '@type' => 'Country', - 'name' => 'MY' - ], - [ - '@type' => 'Country', - 'name' => 'MZ' - ], - [ - '@type' => 'Country', - 'name' => 'NA' - ], - [ - '@type' => 'Country', - 'name' => 'NE' - ], - [ - '@type' => 'Country', - 'name' => 'NG' - ], - [ - '@type' => 'Country', - 'name' => 'NI' - ], - [ - '@type' => 'Country', - 'name' => 'NL' - ], - [ - '@type' => 'Country', - 'name' => 'NO' - ], - [ - '@type' => 'Country', - 'name' => 'NP' - ], - [ - '@type' => 'Country', - 'name' => 'NR' - ], - [ - '@type' => 'Country', - 'name' => 'NZ' - ], - [ - '@type' => 'Country', - 'name' => 'OM' - ], - [ - '@type' => 'Country', - 'name' => 'PA' - ], - [ - '@type' => 'Country', - 'name' => 'PE' - ], - [ - '@type' => 'Country', - 'name' => 'PG' - ], - [ - '@type' => 'Country', - 'name' => 'PH' - ], - [ - '@type' => 'Country', - 'name' => 'PK' - ], - [ - '@type' => 'Country', - 'name' => 'PL' - ], - [ - '@type' => 'Country', - 'name' => 'PS' - ], - [ - '@type' => 'Country', - 'name' => 'PT' - ], - [ - '@type' => 'Country', - 'name' => 'PW' - ], - [ - '@type' => 'Country', - 'name' => 'PY' - ], - [ - '@type' => 'Country', - 'name' => 'QA' - ], - [ - '@type' => 'Country', - 'name' => 'RO' - ], - [ - '@type' => 'Country', - 'name' => 'RS' - ], - [ - '@type' => 'Country', - 'name' => 'RU' - ], - [ - '@type' => 'Country', - 'name' => 'RW' - ], - [ - '@type' => 'Country', - 'name' => 'SA' - ], - [ - '@type' => 'Country', - 'name' => 'SB' - ], - [ - '@type' => 'Country', - 'name' => 'SC' - ], - [ - '@type' => 'Country', - 'name' => 'SE' - ], - [ - '@type' => 'Country', - 'name' => 'SG' - ], - [ - '@type' => 'Country', - 'name' => 'SI' - ], - [ - '@type' => 'Country', - 'name' => 'SK' - ], - [ - '@type' => 'Country', - 'name' => 'SL' - ], - [ - '@type' => 'Country', - 'name' => 'SM' - ], - [ - '@type' => 'Country', - 'name' => 'SN' - ], - [ - '@type' => 'Country', - 'name' => 'SR' - ], - [ - '@type' => 'Country', - 'name' => 'ST' - ], - [ - '@type' => 'Country', - 'name' => 'SV' - ], - [ - '@type' => 'Country', - 'name' => 'SZ' - ], - [ - '@type' => 'Country', - 'name' => 'TD' - ], - [ - '@type' => 'Country', - 'name' => 'TG' - ], - [ - '@type' => 'Country', - 'name' => 'TH' - ], - [ - '@type' => 'Country', - 'name' => 'TL' - ], - [ - '@type' => 'Country', - 'name' => 'TN' - ], - [ - '@type' => 'Country', - 'name' => 'TO' - ], - [ - '@type' => 'Country', - 'name' => 'TR' - ], - [ - '@type' => 'Country', - 'name' => 'TT' - ], - [ - '@type' => 'Country', - 'name' => 'TV' - ], - [ - '@type' => 'Country', - 'name' => 'TW' - ], - [ - '@type' => 'Country', - 'name' => 'TZ' - ], - [ - '@type' => 'Country', - 'name' => 'UA' - ], - [ - '@type' => 'Country', - 'name' => 'UG' - ], - [ - '@type' => 'Country', - 'name' => 'US' - ], - [ - '@type' => 'Country', - 'name' => 'UY' - ], - [ - '@type' => 'Country', - 'name' => 'UZ' - ], - [ - '@type' => 'Country', - 'name' => 'VC' - ], - [ - '@type' => 'Country', - 'name' => 'VN' - ], - [ - '@type' => 'Country', - 'name' => 'VU' - ], - [ - '@type' => 'Country', - 'name' => 'WS' - ], - [ - '@type' => 'Country', - 'name' => 'XK' - ], - [ - '@type' => 'Country', - 'name' => 'ZA' - ], - [ - '@type' => 'Country', - 'name' => 'ZM' - ], - [ - '@type' => 'Country', - 'name' => 'ZW' - ] - ] + 'eligibleRegion' => [] ] ] ], 'oEmbed' => [ - 'html' => '', + 'html' => '', + 'iframe_url' => 'https://open.spotify.com/embed/album/7s66wU1XJ2NsUuWM2NKiUV?utm_source=oembed', 'width' => 456, - 'height' => 380, + 'height' => 352, 'version' => '1.0', 'provider_name' => 'Spotify', 'provider_url' => 'https://spotify.com', 'type' => 'rich', - 'title' => 'A Cantar con Xabarin (Vol. I & II)', - 'thumbnail_url' => 'https://i.scdn.co/image/ab67616d00001e02022aca057f08a1d40c1f0733', + 'title' => 'A Cantar con Xabarin (Vol. I e II)', + 'thumbnail_url' => 'https://i.scdn.co/image/ab67616d00001e022dd9ff41aad54c1d93d50aa6', 'thumbnail_width' => 300, 'thumbnail_height' => 300 ], @@ -788,9 +88,9 @@ '@type' => 'MusicAlbum', '@id' => 'https://open.spotify.com/album/7s66wU1XJ2NsUuWM2NKiUV', 'url' => 'https://open.spotify.com/album/7s66wU1XJ2NsUuWM2NKiUV', - 'name' => 'A Cantar con Xabarin (Vol. I & II)', - 'description' => 'Listen to A Cantar con Xabarin (Vol. I & II) on Spotify. Various Artists · Compilation · 1996 · 44 songs.', - 'datePublished' => '1996-03-05', + 'name' => 'A Cantar con Xabarin (Vol. I e II)', + 'description' => 'Listen to A Cantar con Xabarin (Vol. I e II) on Spotify. Xabarin · Album · 1994 · 44 songs.', + 'datePublished' => '1994-03-01', 'potentialAction' => [ '@type' => 'ListenAction', 'target' => [ @@ -809,720 +109,7 @@ 'expectsAcceptanceOf' => [ '@type' => 'Offer', 'category' => 'free', - 'eligibleRegion' => [ - [ - '@type' => 'Country', - 'name' => 'AD' - ], - [ - '@type' => 'Country', - 'name' => 'AE' - ], - [ - '@type' => 'Country', - 'name' => 'AG' - ], - [ - '@type' => 'Country', - 'name' => 'AL' - ], - [ - '@type' => 'Country', - 'name' => 'AM' - ], - [ - '@type' => 'Country', - 'name' => 'AO' - ], - [ - '@type' => 'Country', - 'name' => 'AR' - ], - [ - '@type' => 'Country', - 'name' => 'AT' - ], - [ - '@type' => 'Country', - 'name' => 'AU' - ], - [ - '@type' => 'Country', - 'name' => 'AZ' - ], - [ - '@type' => 'Country', - 'name' => 'BA' - ], - [ - '@type' => 'Country', - 'name' => 'BB' - ], - [ - '@type' => 'Country', - 'name' => 'BD' - ], - [ - '@type' => 'Country', - 'name' => 'BE' - ], - [ - '@type' => 'Country', - 'name' => 'BF' - ], - [ - '@type' => 'Country', - 'name' => 'BG' - ], - [ - '@type' => 'Country', - 'name' => 'BH' - ], - [ - '@type' => 'Country', - 'name' => 'BI' - ], - [ - '@type' => 'Country', - 'name' => 'BJ' - ], - [ - '@type' => 'Country', - 'name' => 'BN' - ], - [ - '@type' => 'Country', - 'name' => 'BO' - ], - [ - '@type' => 'Country', - 'name' => 'BR' - ], - [ - '@type' => 'Country', - 'name' => 'BS' - ], - [ - '@type' => 'Country', - 'name' => 'BT' - ], - [ - '@type' => 'Country', - 'name' => 'BW' - ], - [ - '@type' => 'Country', - 'name' => 'BY' - ], - [ - '@type' => 'Country', - 'name' => 'BZ' - ], - [ - '@type' => 'Country', - 'name' => 'CA' - ], - [ - '@type' => 'Country', - 'name' => 'CH' - ], - [ - '@type' => 'Country', - 'name' => 'CI' - ], - [ - '@type' => 'Country', - 'name' => 'CL' - ], - [ - '@type' => 'Country', - 'name' => 'CM' - ], - [ - '@type' => 'Country', - 'name' => 'CO' - ], - [ - '@type' => 'Country', - 'name' => 'CR' - ], - [ - '@type' => 'Country', - 'name' => 'CV' - ], - [ - '@type' => 'Country', - 'name' => 'CW' - ], - [ - '@type' => 'Country', - 'name' => 'CY' - ], - [ - '@type' => 'Country', - 'name' => 'CZ' - ], - [ - '@type' => 'Country', - 'name' => 'DE' - ], - [ - '@type' => 'Country', - 'name' => 'DJ' - ], - [ - '@type' => 'Country', - 'name' => 'DK' - ], - [ - '@type' => 'Country', - 'name' => 'DM' - ], - [ - '@type' => 'Country', - 'name' => 'DO' - ], - [ - '@type' => 'Country', - 'name' => 'DZ' - ], - [ - '@type' => 'Country', - 'name' => 'EC' - ], - [ - '@type' => 'Country', - 'name' => 'EE' - ], - [ - '@type' => 'Country', - 'name' => 'EG' - ], - [ - '@type' => 'Country', - 'name' => 'ES' - ], - [ - '@type' => 'Country', - 'name' => 'FI' - ], - [ - '@type' => 'Country', - 'name' => 'FJ' - ], - [ - '@type' => 'Country', - 'name' => 'FM' - ], - [ - '@type' => 'Country', - 'name' => 'FR' - ], - [ - '@type' => 'Country', - 'name' => 'GA' - ], - [ - '@type' => 'Country', - 'name' => 'GB' - ], - [ - '@type' => 'Country', - 'name' => 'GD' - ], - [ - '@type' => 'Country', - 'name' => 'GE' - ], - [ - '@type' => 'Country', - 'name' => 'GH' - ], - [ - '@type' => 'Country', - 'name' => 'GM' - ], - [ - '@type' => 'Country', - 'name' => 'GN' - ], - [ - '@type' => 'Country', - 'name' => 'GQ' - ], - [ - '@type' => 'Country', - 'name' => 'GR' - ], - [ - '@type' => 'Country', - 'name' => 'GT' - ], - [ - '@type' => 'Country', - 'name' => 'GW' - ], - [ - '@type' => 'Country', - 'name' => 'GY' - ], - [ - '@type' => 'Country', - 'name' => 'HK' - ], - [ - '@type' => 'Country', - 'name' => 'HN' - ], - [ - '@type' => 'Country', - 'name' => 'HR' - ], - [ - '@type' => 'Country', - 'name' => 'HT' - ], - [ - '@type' => 'Country', - 'name' => 'HU' - ], - [ - '@type' => 'Country', - 'name' => 'ID' - ], - [ - '@type' => 'Country', - 'name' => 'IE' - ], - [ - '@type' => 'Country', - 'name' => 'IL' - ], - [ - '@type' => 'Country', - 'name' => 'IN' - ], - [ - '@type' => 'Country', - 'name' => 'IS' - ], - [ - '@type' => 'Country', - 'name' => 'IT' - ], - [ - '@type' => 'Country', - 'name' => 'JM' - ], - [ - '@type' => 'Country', - 'name' => 'JO' - ], - [ - '@type' => 'Country', - 'name' => 'JP' - ], - [ - '@type' => 'Country', - 'name' => 'KE' - ], - [ - '@type' => 'Country', - 'name' => 'KG' - ], - [ - '@type' => 'Country', - 'name' => 'KH' - ], - [ - '@type' => 'Country', - 'name' => 'KI' - ], - [ - '@type' => 'Country', - 'name' => 'KM' - ], - [ - '@type' => 'Country', - 'name' => 'KN' - ], - [ - '@type' => 'Country', - 'name' => 'KR' - ], - [ - '@type' => 'Country', - 'name' => 'KW' - ], - [ - '@type' => 'Country', - 'name' => 'KZ' - ], - [ - '@type' => 'Country', - 'name' => 'LA' - ], - [ - '@type' => 'Country', - 'name' => 'LB' - ], - [ - '@type' => 'Country', - 'name' => 'LC' - ], - [ - '@type' => 'Country', - 'name' => 'LI' - ], - [ - '@type' => 'Country', - 'name' => 'LK' - ], - [ - '@type' => 'Country', - 'name' => 'LR' - ], - [ - '@type' => 'Country', - 'name' => 'LS' - ], - [ - '@type' => 'Country', - 'name' => 'LT' - ], - [ - '@type' => 'Country', - 'name' => 'LU' - ], - [ - '@type' => 'Country', - 'name' => 'LV' - ], - [ - '@type' => 'Country', - 'name' => 'MA' - ], - [ - '@type' => 'Country', - 'name' => 'MC' - ], - [ - '@type' => 'Country', - 'name' => 'MD' - ], - [ - '@type' => 'Country', - 'name' => 'ME' - ], - [ - '@type' => 'Country', - 'name' => 'MG' - ], - [ - '@type' => 'Country', - 'name' => 'MH' - ], - [ - '@type' => 'Country', - 'name' => 'MK' - ], - [ - '@type' => 'Country', - 'name' => 'ML' - ], - [ - '@type' => 'Country', - 'name' => 'MN' - ], - [ - '@type' => 'Country', - 'name' => 'MO' - ], - [ - '@type' => 'Country', - 'name' => 'MR' - ], - [ - '@type' => 'Country', - 'name' => 'MT' - ], - [ - '@type' => 'Country', - 'name' => 'MU' - ], - [ - '@type' => 'Country', - 'name' => 'MV' - ], - [ - '@type' => 'Country', - 'name' => 'MW' - ], - [ - '@type' => 'Country', - 'name' => 'MX' - ], - [ - '@type' => 'Country', - 'name' => 'MY' - ], - [ - '@type' => 'Country', - 'name' => 'MZ' - ], - [ - '@type' => 'Country', - 'name' => 'NA' - ], - [ - '@type' => 'Country', - 'name' => 'NE' - ], - [ - '@type' => 'Country', - 'name' => 'NG' - ], - [ - '@type' => 'Country', - 'name' => 'NI' - ], - [ - '@type' => 'Country', - 'name' => 'NL' - ], - [ - '@type' => 'Country', - 'name' => 'NO' - ], - [ - '@type' => 'Country', - 'name' => 'NP' - ], - [ - '@type' => 'Country', - 'name' => 'NR' - ], - [ - '@type' => 'Country', - 'name' => 'NZ' - ], - [ - '@type' => 'Country', - 'name' => 'OM' - ], - [ - '@type' => 'Country', - 'name' => 'PA' - ], - [ - '@type' => 'Country', - 'name' => 'PE' - ], - [ - '@type' => 'Country', - 'name' => 'PG' - ], - [ - '@type' => 'Country', - 'name' => 'PH' - ], - [ - '@type' => 'Country', - 'name' => 'PK' - ], - [ - '@type' => 'Country', - 'name' => 'PL' - ], - [ - '@type' => 'Country', - 'name' => 'PS' - ], - [ - '@type' => 'Country', - 'name' => 'PT' - ], - [ - '@type' => 'Country', - 'name' => 'PW' - ], - [ - '@type' => 'Country', - 'name' => 'PY' - ], - [ - '@type' => 'Country', - 'name' => 'QA' - ], - [ - '@type' => 'Country', - 'name' => 'RO' - ], - [ - '@type' => 'Country', - 'name' => 'RS' - ], - [ - '@type' => 'Country', - 'name' => 'RU' - ], - [ - '@type' => 'Country', - 'name' => 'RW' - ], - [ - '@type' => 'Country', - 'name' => 'SA' - ], - [ - '@type' => 'Country', - 'name' => 'SB' - ], - [ - '@type' => 'Country', - 'name' => 'SC' - ], - [ - '@type' => 'Country', - 'name' => 'SE' - ], - [ - '@type' => 'Country', - 'name' => 'SG' - ], - [ - '@type' => 'Country', - 'name' => 'SI' - ], - [ - '@type' => 'Country', - 'name' => 'SK' - ], - [ - '@type' => 'Country', - 'name' => 'SL' - ], - [ - '@type' => 'Country', - 'name' => 'SM' - ], - [ - '@type' => 'Country', - 'name' => 'SN' - ], - [ - '@type' => 'Country', - 'name' => 'SR' - ], - [ - '@type' => 'Country', - 'name' => 'ST' - ], - [ - '@type' => 'Country', - 'name' => 'SV' - ], - [ - '@type' => 'Country', - 'name' => 'SZ' - ], - [ - '@type' => 'Country', - 'name' => 'TD' - ], - [ - '@type' => 'Country', - 'name' => 'TG' - ], - [ - '@type' => 'Country', - 'name' => 'TH' - ], - [ - '@type' => 'Country', - 'name' => 'TL' - ], - [ - '@type' => 'Country', - 'name' => 'TN' - ], - [ - '@type' => 'Country', - 'name' => 'TO' - ], - [ - '@type' => 'Country', - 'name' => 'TR' - ], - [ - '@type' => 'Country', - 'name' => 'TT' - ], - [ - '@type' => 'Country', - 'name' => 'TV' - ], - [ - '@type' => 'Country', - 'name' => 'TW' - ], - [ - '@type' => 'Country', - 'name' => 'TZ' - ], - [ - '@type' => 'Country', - 'name' => 'UA' - ], - [ - '@type' => 'Country', - 'name' => 'UG' - ], - [ - '@type' => 'Country', - 'name' => 'US' - ], - [ - '@type' => 'Country', - 'name' => 'UY' - ], - [ - '@type' => 'Country', - 'name' => 'UZ' - ], - [ - '@type' => 'Country', - 'name' => 'VC' - ], - [ - '@type' => 'Country', - 'name' => 'VN' - ], - [ - '@type' => 'Country', - 'name' => 'VU' - ], - [ - '@type' => 'Country', - 'name' => 'WS' - ], - [ - '@type' => 'Country', - 'name' => 'XK' - ], - [ - '@type' => 'Country', - 'name' => 'ZA' - ], - [ - '@type' => 'Country', - 'name' => 'ZM' - ], - [ - '@type' => 'Country', - 'name' => 'ZW' - ] - ] + 'eligibleRegion' => [] ] ] ] diff --git a/tests/fixtures/output.jsbin.com.vonesu-10.php b/tests/fixtures/output.jsbin.com.vonesu-10.php index 702d3767..9336f167 100644 --- a/tests/fixtures/output.jsbin.com.vonesu-10.php +++ b/tests/fixtures/output.jsbin.com.vonesu-10.php @@ -5,12 +5,7 @@ 'authorName' => null, 'authorUrl' => null, 'cms' => null, - 'code' => [ - 'html' => '', - 'width' => 320, - 'height' => 240, - 'ratio' => 75.0 - ], + 'code' => null, 'description' => null, 'favicon' => 'http://output.jsbin.com/favicon.ico', 'feeds' => [], @@ -27,14 +22,6 @@ 'title' => 'JS Bin', 'url' => 'http://output.jsbin.com/vonesu/10', 'linkedData' => [], - 'oEmbed' => [ - 'type' => 'rich', - 'version' => '1.0', - 'title' => 'JS Bin', - 'url' => 'http://output.jsbin.com/vonesu/10', - 'width' => 320, - 'height' => 240, - 'html' => '' - ], + 'oEmbed' => [], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/pachunka.deviantart.com.art-cope-145564099.php b/tests/fixtures/pachunka.deviantart.com.art-cope-145564099.php index 6c589650..c67c22f7 100644 --- a/tests/fixtures/pachunka.deviantart.com.art-cope-145564099.php +++ b/tests/fixtures/pachunka.deviantart.com.art-cope-145564099.php @@ -2,26 +2,65 @@ declare(strict_types = 1); return [ - 'authorName' => null, - 'authorUrl' => null, + 'authorName' => 'pachunka', + 'authorUrl' => 'https://www.deviantart.com/pachunka', 'cms' => null, 'code' => null, 'description' => null, 'favicon' => 'https://st.deviantart.net/eclipse/icons/da_favicon_v2.ico', 'feeds' => [], 'icon' => 'https://st.deviantart.net/eclipse/icons/ios-180.png', - 'image' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/2b5a457d-dde7-459a-badf-774e8e506025/d2enxz7-431ca9c3-987f-4b2d-ae6a-cbbdd1c0e2bf.jpg/v1/fill/w_448,h_672,q_75,strp/cope_by_pachunka_d2enxz7-fullview.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOiIsImlzcyI6InVybjphcHA6Iiwib2JqIjpbW3siaGVpZ2h0IjoiPD02NzIiLCJwYXRoIjoiXC9mXC8yYjVhNDU3ZC1kZGU3LTQ1OWEtYmFkZi03NzRlOGU1MDYwMjVcL2QyZW54ejctNDMxY2E5YzMtOTg3Zi00YjJkLWFlNmEtY2JiZGQxYzBlMmJmLmpwZyIsIndpZHRoIjoiPD00NDgifV1dLCJhdWQiOlsidXJuOnNlcnZpY2U6aW1hZ2Uub3BlcmF0aW9ucyJdfQ.uKXtzi4RcaE2A5UkAveDjAmXMGj3gam4jIvj8BugRo4', + 'image' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/2b5a457d-dde7-459a-badf-774e8e506025/d2enxz7-431ca9c3-987f-4b2d-ae6a-cbbdd1c0e2bf.jpg/v1/fit/w_300,h_672,q_70,strp/cope_by_pachunka_d2enxz7-300w.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9NjcyIiwicGF0aCI6IlwvZlwvMmI1YTQ1N2QtZGRlNy00NTlhLWJhZGYtNzc0ZThlNTA2MDI1XC9kMmVueHo3LTQzMWNhOWMzLTk4N2YtNGIyZC1hZTZhLWNiYmRkMWMwZTJiZi5qcGciLCJ3aWR0aCI6Ijw9NDQ4In1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmltYWdlLm9wZXJhdGlvbnMiXX0.IgIuTtYswRRSrLcUD8alCdW0sAgmkVTiQAjV5-OCfv8', 'keywords' => [], 'language' => 'en', 'languages' => [], 'license' => null, - 'providerName' => 'Deviantart', - 'providerUrl' => 'https://www.deviantart.com', - 'publishedTime' => null, + 'providerName' => 'DeviantArt', + 'providerUrl' => 'https://www.deviantart.com/', + 'publishedTime' => '2009-12-02 23:59:51', 'redirect' => null, - 'title' => 'Cope by pachunka on DeviantArt', - 'url' => 'https://www.deviantart.com/pachunka/art/Cope-145564099', + 'title' => 'Cope', + 'url' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/2b5a457d-dde7-459a-badf-774e8e506025/d2enxz7-431ca9c3-987f-4b2d-ae6a-cbbdd1c0e2bf.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7InBhdGgiOiJcL2ZcLzJiNWE0NTdkLWRkZTctNDU5YS1iYWRmLTc3NGU4ZTUwNjAyNVwvZDJlbnh6Ny00MzFjYTljMy05ODdmLTRiMmQtYWU2YS1jYmJkZDFjMGUyYmYuanBnIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmZpbGUuZG93bmxvYWQiXX0.pCEF4M_tMVZI92ZKClZdcMu5UqpQ3131DrHsx1WxefY', 'linkedData' => [], - 'oEmbed' => [], + 'oEmbed' => [ + 'version' => '1.0', + 'type' => 'photo', + 'title' => 'Cope', + 'category' => 'Digital Art > Drawings & Paintings > Illustrations > Conceptual', + 'url' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/2b5a457d-dde7-459a-badf-774e8e506025/d2enxz7-431ca9c3-987f-4b2d-ae6a-cbbdd1c0e2bf.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7InBhdGgiOiJcL2ZcLzJiNWE0NTdkLWRkZTctNDU5YS1iYWRmLTc3NGU4ZTUwNjAyNVwvZDJlbnh6Ny00MzFjYTljMy05ODdmLTRiMmQtYWU2YS1jYmJkZDFjMGUyYmYuanBnIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmZpbGUuZG93bmxvYWQiXX0.pCEF4M_tMVZI92ZKClZdcMu5UqpQ3131DrHsx1WxefY', + 'author_name' => 'pachunka', + 'author_url' => 'https://www.deviantart.com/pachunka', + 'provider_name' => 'DeviantArt', + 'provider_url' => 'https://www.deviantart.com', + 'safety' => 'nonadult', + 'pubdate' => '2009-12-02T23:59:51-08:00', + 'community' => [ + 'statistics' => [ + '_attributes' => [ + 'views' => 3279, + 'favorites' => 10, + 'comments' => 5, + 'downloads' => 63 + ] + ] + ], + 'copyright' => [ + '_attributes' => [ + 'url' => 'https://www.deviantart.com/pachunka', + 'year' => '2009', + 'entity' => 'pachunka' + ] + ], + 'width' => '448', + 'height' => '672', + 'imagetype' => 'jpg', + 'thumbnail_url' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/2b5a457d-dde7-459a-badf-774e8e506025/d2enxz7-431ca9c3-987f-4b2d-ae6a-cbbdd1c0e2bf.jpg/v1/fit/w_300,h_672,q_70,strp/cope_by_pachunka_d2enxz7-300w.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9NjcyIiwicGF0aCI6IlwvZlwvMmI1YTQ1N2QtZGRlNy00NTlhLWJhZGYtNzc0ZThlNTA2MDI1XC9kMmVueHo3LTQzMWNhOWMzLTk4N2YtNGIyZC1hZTZhLWNiYmRkMWMwZTJiZi5qcGciLCJ3aWR0aCI6Ijw9NDQ4In1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmltYWdlLm9wZXJhdGlvbnMiXX0.IgIuTtYswRRSrLcUD8alCdW0sAgmkVTiQAjV5-OCfv8', + 'thumbnail_width' => 300, + 'thumbnail_height' => 450, + 'thumbnail_url_150' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/2b5a457d-dde7-459a-badf-774e8e506025/d2enxz7-431ca9c3-987f-4b2d-ae6a-cbbdd1c0e2bf.jpg/v1/fit/w_150,h_150,q_70,strp/cope_by_pachunka_d2enxz7-150.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9NjcyIiwicGF0aCI6IlwvZlwvMmI1YTQ1N2QtZGRlNy00NTlhLWJhZGYtNzc0ZThlNTA2MDI1XC9kMmVueHo3LTQzMWNhOWMzLTk4N2YtNGIyZC1hZTZhLWNiYmRkMWMwZTJiZi5qcGciLCJ3aWR0aCI6Ijw9NDQ4In1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmltYWdlLm9wZXJhdGlvbnMiXX0.IgIuTtYswRRSrLcUD8alCdW0sAgmkVTiQAjV5-OCfv8', + 'thumbnail_url_200h' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/2b5a457d-dde7-459a-badf-774e8e506025/d2enxz7-431ca9c3-987f-4b2d-ae6a-cbbdd1c0e2bf.jpg/v1/fill/w_133,h_200,q_70,strp/cope_by_pachunka_d2enxz7-200h.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9NjcyIiwicGF0aCI6IlwvZlwvMmI1YTQ1N2QtZGRlNy00NTlhLWJhZGYtNzc0ZThlNTA2MDI1XC9kMmVueHo3LTQzMWNhOWMzLTk4N2YtNGIyZC1hZTZhLWNiYmRkMWMwZTJiZi5qcGciLCJ3aWR0aCI6Ijw9NDQ4In1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmltYWdlLm9wZXJhdGlvbnMiXX0.IgIuTtYswRRSrLcUD8alCdW0sAgmkVTiQAjV5-OCfv8', + 'thumbnail_width_200h' => 133, + 'thumbnail_height_200h' => 200 + ], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/play.cadenaser.com.audio-001rd010000004275766.php b/tests/fixtures/play.cadenaser.com.audio-001rd010000004275766.php index cef0c0bb..25eff819 100644 --- a/tests/fixtures/play.cadenaser.com.audio-001rd010000004275766.php +++ b/tests/fixtures/play.cadenaser.com.audio-001rd010000004275766.php @@ -2,30 +2,25 @@ declare(strict_types = 1); return [ - 'authorName' => '@La_SER', - 'authorUrl' => 'https://twitter.com/La_SER', + 'authorName' => 'Cadena SER', + 'authorUrl' => null, 'cms' => null, - 'code' => [ - 'html' => '', - 'width' => null, - 'height' => 360, - 'ratio' => null - ], + 'code' => null, 'description' => 'Ana Pontón, candidata del BNG; Xoaquín Fernández Leiceaga, candidato del PSdG-PSOE; Luis Villares, candidato de En Marea, y Pedro Puy, candidato del PP por A Coruña y coordinador del programa electoral de Alberto Núñez Feijoo debaten en Hoy por Hoy', - 'favicon' => 'https://play.cadenaser.com/bundles/playserweb/img/favicon.ico?v3.3', + 'favicon' => 'https://cadenaser.com/pf/resources/img/favicon.png?d=1014', 'feeds' => [], - 'icon' => 'https://play.cadenaser.com/bundles/playserweb/img/apple-touch-icon-57x57.png?v3.3', - 'image' => 'https://cadenaser00.epimg.net/iconos/v1.x/v1.0/redes/imagenes_og/programas_v2/2019/imagen-control-hoy-por-hoy-angels.jpg', + 'icon' => null, + 'image' => 'https://cadenaser.com/resizer/a9WQ8rJ3QL6pdxxPeqscG8rnPHU=/1200x630/smart/filters:quality(70)/urotrosfiles.media.streamtheworld.com/otrosfiles/objetos/logos/cadenaser-logo_01.png', 'keywords' => [], 'language' => 'es', 'languages' => [], 'license' => null, - 'providerName' => 'Cadena SER', - 'providerUrl' => 'https://play.cadenaser.com', - 'publishedTime' => '2016-09-16 00:00:00', + 'providerName' => 'cadena SER', + 'providerUrl' => 'https://cadenaser.com', + 'publishedTime' => null, 'redirect' => null, - 'title' => 'Debate electoral gallego, en \'Hoy por Hoy\' | Hoy por Hoy | Audio | Cadena SER', - 'url' => 'https://play.cadenaser.com/audio/001RD010000004275766/', + 'title' => 'Debate electoral gallego, en \'Hoy por Hoy\' | Cadena Ser', + 'url' => 'https://cadenaser.com/audio/001RD010000004275766/', 'linkedData' => [ '@context' => 'https://schema.org', '@type' => 'BreadcrumbList', @@ -34,16 +29,16 @@ '@type' => 'ListItem', 'position' => 1, 'item' => [ - '@id' => 'https://play.cadenaser.com/', - 'name' => 'PlaySER' + '@id' => 'https://cadenaser.com', + 'name' => 'Inicio' ] ], [ '@type' => 'ListItem', 'position' => 2, 'item' => [ - '@id' => 'https://play.cadenaser.com/programa/hoy_por_hoy/', - 'name' => 'Hoy por Hoy' + '@id' => 'https://cadenaser.com/audio/001RD010000004275766/', + 'name' => 'Audio 001RD010000004275766' ] ] ] @@ -58,32 +53,19 @@ '@type' => 'ListItem', 'position' => 1, 'item' => [ - '@id' => 'https://play.cadenaser.com/', - 'name' => 'PlaySER' + '@id' => 'https://cadenaser.com', + 'name' => 'Inicio' ] ], [ '@type' => 'ListItem', 'position' => 2, 'item' => [ - '@id' => 'https://play.cadenaser.com/programa/hoy_por_hoy/', - 'name' => 'Hoy por Hoy' + '@id' => 'https://cadenaser.com/audio/001RD010000004275766/', + 'name' => 'Audio 001RD010000004275766' ] ] ] - ], - [ - '@context' => 'https://schema.org', - '@type' => 'Corporation', - 'name' => 'cadenaser.com', - 'url' => 'https://cadenaser.com', - 'logo' => 'https://play.cadenaser.com/bundles/playserweb/img/logo_playser.svg', - 'sameAs' => [ - 'https://www.facebook.com/cadenaser', - 'https://twitter.com/La_SER', - 'https://plus.google.com/+cadenaser/', - 'https://www.youtube.com/user/cadenaser' - ] ] ] ]; diff --git a/tests/fixtures/polldaddy.com.poll-7012505.php b/tests/fixtures/polldaddy.com.poll-7012505.php index cb98357e..b2494f4d 100644 --- a/tests/fixtures/polldaddy.com.poll-7012505.php +++ b/tests/fixtures/polldaddy.com.poll-7012505.php @@ -5,12 +5,7 @@ 'authorName' => null, 'authorUrl' => null, 'cms' => null, - 'code' => [ - 'html' => '', - 'width' => null, - 'height' => null, - 'ratio' => null - ], + 'code' => null, 'description' => 'Poll Answers: Option #1, Option #2,', 'favicon' => 'https://app.crowdsignal.com/images/favicon.png', 'feeds' => [ @@ -22,20 +17,13 @@ 'language' => 'en', 'languages' => [], 'license' => null, - 'providerName' => 'Crowdsignal', - 'providerUrl' => 'https://crowdsignal.com/', + 'providerName' => 'Poll', + 'providerUrl' => 'https://poll.fm', 'publishedTime' => null, 'redirect' => null, 'title' => 'Which design do you prefer?', 'url' => 'https://poll.fm/7012505', 'linkedData' => [], - 'oEmbed' => [ - 'type' => 'rich', - 'version' => '1.0', - 'provider_name' => 'Crowdsignal', - 'provider_url' => 'https://crowdsignal.com', - 'title' => 'Which design do you prefer?', - 'html' => '' - ], + 'oEmbed' => [], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/slides.com.alexwalker-responsive-svg.php b/tests/fixtures/slides.com.alexwalker-responsive-svg.php index 0b3bb06f..07d934b4 100644 --- a/tests/fixtures/slides.com.alexwalker-responsive-svg.php +++ b/tests/fixtures/slides.com.alexwalker-responsive-svg.php @@ -6,26 +6,26 @@ 'authorUrl' => null, 'cms' => null, 'code' => [ - 'html' => '', + 'html' => '', 'width' => 576, 'height' => 420, 'ratio' => 72.917 ], 'description' => 'A presentation created with Slides.', - 'favicon' => 'http://slides.com/favicon.ico', + 'favicon' => 'https://slides.com/favicon.ico', 'feeds' => [], 'icon' => null, 'image' => 'https://media.slid.es/thumbnails/alexwalker/0b188c/responsive-svg.jpg', 'keywords' => [], - 'language' => null, + 'language' => 'en', 'languages' => [], 'license' => null, 'providerName' => 'Slides', - 'providerUrl' => 'http://slides.com', + 'providerUrl' => 'https://slides.com', 'publishedTime' => null, 'redirect' => null, 'title' => 'responsive-svg', - 'url' => 'http://slides.com/alexwalker/responsive-svg/', + 'url' => 'https://slides.com/alexwalker/responsive-svg/', 'linkedData' => [], 'oEmbed' => [], 'allLinkedData' => [] diff --git a/tests/fixtures/snipplr.com.view-72914-better-html-5-basic-starter-template.php b/tests/fixtures/snipplr.com.view-72914-better-html-5-basic-starter-template.php index 14d694e6..73f09439 100644 --- a/tests/fixtures/snipplr.com.view-72914-better-html-5-basic-starter-template.php +++ b/tests/fixtures/snipplr.com.view-72914-better-html-5-basic-starter-template.php @@ -5,12 +5,7 @@ 'authorName' => null, 'authorUrl' => null, 'cms' => null, - 'code' => [ - 'html' => '', - 'width' => null, - 'height' => null, - 'ratio' => null - ], + 'code' => null, 'description' => null, 'favicon' => 'https://snipplr.com/favicon.ico?v3', 'feeds' => [], @@ -24,8 +19,8 @@ 'providerUrl' => 'https://snipplr.com', 'publishedTime' => null, 'redirect' => null, - 'title' => 'Better HTML 5 basic starter template - HTML Snipplr Social Repository', - 'url' => 'https://snipplr.com/view/72914/better-html-5-basic-starter-template', + 'title' => 'Snipplr - Login', + 'url' => 'https://snipplr.com/login/', 'linkedData' => [], 'oEmbed' => [], 'allLinkedData' => [] diff --git a/tests/fixtures/soundcloud.com.zedsdead-zeds-dead-twin-shadow-lost-you-feat-dangelo-lacy.php b/tests/fixtures/soundcloud.com.zedsdead-zeds-dead-twin-shadow-lost-you-feat-dangelo-lacy.php index cdae2051..ebf911d5 100644 --- a/tests/fixtures/soundcloud.com.zedsdead-zeds-dead-twin-shadow-lost-you-feat-dangelo-lacy.php +++ b/tests/fixtures/soundcloud.com.zedsdead-zeds-dead-twin-shadow-lost-you-feat-dangelo-lacy.php @@ -15,7 +15,7 @@ 'favicon' => 'https://a-v2.sndcdn.com/assets/images/sc-icons/favicon-2cadd14bdb.ico', 'feeds' => [], 'icon' => 'https://a-v2.sndcdn.com/assets/images/sc-icons/ios-a62dfc8fe7.png', - 'image' => 'https://i1.sndcdn.com/artworks-oWdjh9aVcb1y-0-t500x500.jpg', + 'image' => 'https://i1.sndcdn.com/artworks-fdcKTkNSeA5J-0-t500x500.jpg', 'keywords' => [ 'record', 'sounds', @@ -33,7 +33,7 @@ 'providerUrl' => 'https://soundcloud.com/', 'publishedTime' => null, 'redirect' => null, - 'title' => 'Lost You (feat. Twin Shadow & D\'Angelo Lacy) by Zeds Dead', + 'title' => 'Zeds Dead feat. Twin Shadow & D\'Angelo Lacy - Lost You by Zeds Dead', 'url' => 'https://soundcloud.com/zedsdead/zeds-dead-twin-shadow-lost-you-feat-dangelo-lacy', 'linkedData' => [], 'oEmbed' => [ @@ -43,7 +43,7 @@ 'provider_url' => 'https://soundcloud.com', 'height' => 400, 'width' => '100%', - 'title' => 'Lost You (feat. Twin Shadow & D\'Angelo Lacy) by Zeds Dead', + 'title' => 'Zeds Dead feat. Twin Shadow & D\'Angelo Lacy - Lost You by Zeds Dead', 'description' => 'MUSIC VIDEO : https://www.youtube.com/watch?v=VJm7IPrBmLY Zeds Dead @@ -57,7 +57,7 @@ www.instagram.com/thetwinshadow ', - 'thumbnail_url' => 'https://i1.sndcdn.com/artworks-oWdjh9aVcb1y-0-t500x500.jpg', + 'thumbnail_url' => 'https://i1.sndcdn.com/artworks-fdcKTkNSeA5J-0-t500x500.jpg', 'html' => '', 'author_name' => 'Zeds Dead', 'author_url' => 'https://soundcloud.com/zedsdead' diff --git a/tests/fixtures/twitter.com.pepephone-status-436461658601713664.php b/tests/fixtures/twitter.com.pepephone-status-436461658601713664.php index cc7b20e5..2b5b0fcb 100644 --- a/tests/fixtures/twitter.com.pepephone-status-436461658601713664.php +++ b/tests/fixtures/twitter.com.pepephone-status-436461658601713664.php @@ -12,71 +12,19 @@ 'ratio' => null ], 'description' => null, - 'favicon' => 'https://abs.twimg.com/favicons/twitter.ico', + 'favicon' => 'https://abs.twimg.com/favicons/twitter-pip.3.ico', 'feeds' => [], - 'icon' => 'https://abs.twimg.com/responsive-web/client-web-legacy/icon-ios.b1fc7275.png', + 'icon' => 'https://abs.twimg.com/responsive-web/client-web/icon-ios.77d25eba.png', 'image' => null, 'keywords' => [], - 'language' => 'en', - 'languages' => [ - 'x-default' => 'https://twitter.com/pepephone/status/436461658601713664', - 'en' => 'https://twitter.com/pepephone/status/436461658601713664?lang=en', - 'ar' => 'https://twitter.com/pepephone/status/436461658601713664?lang=ar', - 'ar-x-fm' => 'https://twitter.com/pepephone/status/436461658601713664?lang=ar-x-fm', - 'bg' => 'https://twitter.com/pepephone/status/436461658601713664?lang=bg', - 'bn' => 'https://twitter.com/pepephone/status/436461658601713664?lang=bn', - 'ca' => 'https://twitter.com/pepephone/status/436461658601713664?lang=ca', - 'cs' => 'https://twitter.com/pepephone/status/436461658601713664?lang=cs', - 'da' => 'https://twitter.com/pepephone/status/436461658601713664?lang=da', - 'de' => 'https://twitter.com/pepephone/status/436461658601713664?lang=de', - 'el' => 'https://twitter.com/pepephone/status/436461658601713664?lang=el', - 'en-GB' => 'https://twitter.com/pepephone/status/436461658601713664?lang=en-GB', - 'en-ss' => 'https://twitter.com/pepephone/status/436461658601713664?lang=en-ss', - 'en-xx' => 'https://twitter.com/pepephone/status/436461658601713664?lang=en-xx', - 'es' => 'https://twitter.com/pepephone/status/436461658601713664?lang=es', - 'eu' => 'https://twitter.com/pepephone/status/436461658601713664?lang=eu', - 'fa' => 'https://twitter.com/pepephone/status/436461658601713664?lang=fa', - 'fi' => 'https://twitter.com/pepephone/status/436461658601713664?lang=fi', - 'fil' => 'https://twitter.com/pepephone/status/436461658601713664?lang=fil', - 'fr' => 'https://twitter.com/pepephone/status/436461658601713664?lang=fr', - 'ga' => 'https://twitter.com/pepephone/status/436461658601713664?lang=ga', - 'gl' => 'https://twitter.com/pepephone/status/436461658601713664?lang=gl', - 'gu' => 'https://twitter.com/pepephone/status/436461658601713664?lang=gu', - 'he' => 'https://twitter.com/pepephone/status/436461658601713664?lang=he', - 'hi' => 'https://twitter.com/pepephone/status/436461658601713664?lang=hi', - 'hr' => 'https://twitter.com/pepephone/status/436461658601713664?lang=hr', - 'hu' => 'https://twitter.com/pepephone/status/436461658601713664?lang=hu', - 'id' => 'https://twitter.com/pepephone/status/436461658601713664?lang=id', - 'it' => 'https://twitter.com/pepephone/status/436461658601713664?lang=it', - 'ja' => 'https://twitter.com/pepephone/status/436461658601713664?lang=ja', - 'kn' => 'https://twitter.com/pepephone/status/436461658601713664?lang=kn', - 'ko' => 'https://twitter.com/pepephone/status/436461658601713664?lang=ko', - 'mr' => 'https://twitter.com/pepephone/status/436461658601713664?lang=mr', - 'ms' => 'https://twitter.com/pepephone/status/436461658601713664?lang=ms', - 'nb' => 'https://twitter.com/pepephone/status/436461658601713664?lang=nb', - 'nl' => 'https://twitter.com/pepephone/status/436461658601713664?lang=nl', - 'pl' => 'https://twitter.com/pepephone/status/436461658601713664?lang=pl', - 'pt' => 'https://twitter.com/pepephone/status/436461658601713664?lang=pt', - 'ro' => 'https://twitter.com/pepephone/status/436461658601713664?lang=ro', - 'ru' => 'https://twitter.com/pepephone/status/436461658601713664?lang=ru', - 'sk' => 'https://twitter.com/pepephone/status/436461658601713664?lang=sk', - 'sr' => 'https://twitter.com/pepephone/status/436461658601713664?lang=sr', - 'sv' => 'https://twitter.com/pepephone/status/436461658601713664?lang=sv', - 'ta' => 'https://twitter.com/pepephone/status/436461658601713664?lang=ta', - 'th' => 'https://twitter.com/pepephone/status/436461658601713664?lang=th', - 'tr' => 'https://twitter.com/pepephone/status/436461658601713664?lang=tr', - 'uk' => 'https://twitter.com/pepephone/status/436461658601713664?lang=uk', - 'ur' => 'https://twitter.com/pepephone/status/436461658601713664?lang=ur', - 'vi' => 'https://twitter.com/pepephone/status/436461658601713664?lang=vi', - 'zh' => 'https://twitter.com/pepephone/status/436461658601713664?lang=zh', - 'zh-Hant' => 'https://twitter.com/pepephone/status/436461658601713664?lang=zh-Hant' - ], + 'language' => null, + 'languages' => [], 'license' => null, 'providerName' => 'Twitter', 'providerUrl' => 'https://twitter.com/', 'publishedTime' => null, 'redirect' => null, - 'title' => null, + 'title' => 'x.com', 'url' => 'https://twitter.com/pepephone/status/436461658601713664', 'linkedData' => [], 'oEmbed' => [ @@ -85,6 +33,7 @@ 'author_url' => 'https://twitter.com/pepephone', 'html' => ' + ', 'width' => 550, 'height' => null, diff --git a/tests/fixtures/vimeo.com.235352744.php b/tests/fixtures/vimeo.com.235352744.php index a551ead3..4a18fb15 100644 --- a/tests/fixtures/vimeo.com.235352744.php +++ b/tests/fixtures/vimeo.com.235352744.php @@ -2,20 +2,20 @@ declare(strict_types = 1); return [ - 'authorName' => 'Vimeo', - 'authorUrl' => 'https://vimeo.com/vimeomarketing', + 'authorName' => null, + 'authorUrl' => null, 'cms' => null, 'code' => [ - 'html' => '', + 'html' => '', 'width' => 640, 'height' => 360, 'ratio' => 56.25 ], - 'description' => 'Professional live streaming is here. Learn more at vimeo.com/live', - 'favicon' => 'https://f.vimeocdn.com/images_v6/favicon.ico?e6c6fc9ed4e4b0b057ecead0028781d1beb5476c', + 'description' => 'Sorry, we couldn’t find that page', + 'favicon' => 'https://f.vimeocdn.com/images_v6/favicon.ico?75abfbb122dbdc379bb1a024f2874f879d5a01a1', 'feeds' => [], 'icon' => 'https://i.vimeocdn.com/favicon/main-touch_180', - 'image' => 'https://i.vimeocdn.com/video/657162424_640.jpg', + 'image' => null, 'keywords' => [], 'language' => 'en', 'languages' => [], @@ -24,117 +24,19 @@ 'providerUrl' => 'https://vimeo.com/', 'publishedTime' => null, 'redirect' => null, - 'title' => 'Vimeo Live is here', + 'title' => 'VimeUhOh', 'url' => 'https://vimeo.com/235352744', - 'linkedData' => [ - 'url' => 'https://vimeo.com/235352744', - 'thumbnailUrl' => 'https://i.vimeocdn.com/filter/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F657162424_1280x720.jpg&src1=https%3A%2F%2Ff.vimeocdn.com%2Fimages_v6%2Fshare%2Fplay_icon_overlay.png', - 'embedUrl' => 'https://player.vimeo.com/video/235352744', - 'name' => 'Vimeo Live is here', - 'description' => 'Professional live streaming is here. Learn more at vimeo.com/live', - 'height' => 1080, - 'width' => 1920, - 'playerType' => 'HTML5 Flash', - 'videoQuality' => 'HD', - 'duration' => 'PT00H01M15S', - 'uploadDate' => '2017-09-25T10:36:56-04:00', - 'dateModified' => '2021-04-04T08:57:58-04:00', - 'thumbnail' => [ - '@type' => 'ImageObject', - 'url' => 'https://i.vimeocdn.com/filter/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F657162424_1280x720.jpg&src1=https%3A%2F%2Ff.vimeocdn.com%2Fimages_v6%2Fshare%2Fplay_icon_overlay.png', - 'width' => 1280, - 'height' => 720 - ], - 'author' => [ - '@type' => 'Person', - 'name' => 'Vimeo', - 'url' => 'https://vimeo.com/vimeomarketing' - ], - 'potentialAction' => [ - '@type' => 'ViewAction', - 'target' => 'vimeo://app.vimeo.com/videos/235352744' - ], - '@type' => 'VideoObject', - '@context' => 'http://schema.org' - ], + 'linkedData' => [], 'oEmbed' => [ 'type' => 'video', 'version' => '1.0', 'provider_name' => 'Vimeo', 'provider_url' => 'https://vimeo.com/', - 'title' => 'Vimeo Live is here', - 'author_name' => 'Vimeo', - 'author_url' => 'https://vimeo.com/vimeomarketing', - 'is_plus' => '0', - 'account_type' => 'live_premium', - 'html' => '', + 'html' => '', 'width' => 640, 'height' => 360, - 'duration' => 75, - 'description' => 'Professional live streaming is here. - -Learn more at vimeo.com/live', - 'thumbnail_url' => 'https://i.vimeocdn.com/video/657162424_640.jpg', - 'thumbnail_width' => 640, - 'thumbnail_height' => 360, - 'thumbnail_url_with_play_button' => 'https://i.vimeocdn.com/filter/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F657162424_640.jpg&src1=http%3A%2F%2Ff.vimeocdn.com%2Fp%2Fimages%2Fcrawler_play.png', - 'upload_date' => '2017-09-25 10:36:56', 'video_id' => 235352744, - 'uri' => '/videos/235352744' + 'uri' => '/videos/235352744:ebd66989cc' ], - 'allLinkedData' => [ - [ - 'url' => 'https://vimeo.com/235352744', - 'thumbnailUrl' => 'https://i.vimeocdn.com/filter/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F657162424_1280x720.jpg&src1=https%3A%2F%2Ff.vimeocdn.com%2Fimages_v6%2Fshare%2Fplay_icon_overlay.png', - 'embedUrl' => 'https://player.vimeo.com/video/235352744', - 'name' => 'Vimeo Live is here', - 'description' => 'Professional live streaming is here. Learn more at vimeo.com/live', - 'height' => 1080, - 'width' => 1920, - 'playerType' => 'HTML5 Flash', - 'videoQuality' => 'HD', - 'duration' => 'PT00H01M15S', - 'uploadDate' => '2017-09-25T10:36:56-04:00', - 'dateModified' => '2021-04-04T08:57:58-04:00', - 'thumbnail' => [ - '@type' => 'ImageObject', - 'url' => 'https://i.vimeocdn.com/filter/overlay?src0=https%3A%2F%2Fi.vimeocdn.com%2Fvideo%2F657162424_1280x720.jpg&src1=https%3A%2F%2Ff.vimeocdn.com%2Fimages_v6%2Fshare%2Fplay_icon_overlay.png', - 'width' => 1280, - 'height' => 720 - ], - 'author' => [ - '@type' => 'Person', - 'name' => 'Vimeo', - 'url' => 'https://vimeo.com/vimeomarketing' - ], - 'potentialAction' => [ - '@type' => 'ViewAction', - 'target' => 'vimeo://app.vimeo.com/videos/235352744' - ], - '@type' => 'VideoObject', - '@context' => 'http://schema.org' - ], - [ - 'itemListElement' => [ - [ - '@type' => 'ListItem', - 'position' => 1, - 'item' => [ - '@id' => 'https://vimeo.com/vimeomarketing', - 'name' => 'Vimeo' - ] - ], - [ - '@type' => 'ListItem', - 'position' => 2, - 'item' => [ - '@id' => 'https://vimeo.com/vimeomarketing/videos', - 'name' => 'Videos' - ] - ] - ], - '@type' => 'BreadcrumbList', - '@context' => 'http://schema.org' - ] - ] + 'allLinkedData' => [] ]; diff --git a/tests/fixtures/wordpress.tv.2013-09-06-dave-ross-optimize-image-files-like-a-pro.php b/tests/fixtures/wordpress.tv.2013-09-06-dave-ross-optimize-image-files-like-a-pro.php index 6be3713b..92ebfb7c 100644 --- a/tests/fixtures/wordpress.tv.2013-09-06-dave-ross-optimize-image-files-like-a-pro.php +++ b/tests/fixtures/wordpress.tv.2013-09-06-dave-ross-optimize-image-files-like-a-pro.php @@ -6,7 +6,7 @@ 'authorUrl' => 'https://wordpress.tv/author/anonvideoupload/', 'cms' => 'wordpress', 'code' => [ - 'html' => 'Images, even small ones, can be the biggest files that make up your site. Learn the tools, plugins, and theming techniques you’ll want to start using to shrink your page load times and save your mobile users a few bucks without sacrificing image quality. Presentation Slides »', + 'html' => '
Dave Ross: Optimize Image Files Like a Pro
', 'width' => null, 'height' => null, 'ratio' => null @@ -67,10 +67,14 @@ 'author_name' => 'WordPress.tv', 'author_url' => 'https://wordpress.tv/author/anonvideoupload/', 'title' => 'Dave Ross: Optimize Image Files Like a Pro', - 'type' => 'link', - 'html' => 'Images, even small ones, can be the biggest files that make up your site. Learn the tools, plugins, and theming techniques you’ll want to start using to shrink your page load times and save your mobile users a few bucks without sacrificing image quality. - -Presentation Slides »' + 'html' => '
Dave Ross: Optimize Image Files Like a Pro
+', + 'type' => 'rich' ], 'allLinkedData' => [ [ diff --git a/tests/fixtures/www.23hq.com.zzleeper-photo-16600737.php b/tests/fixtures/www.23hq.com.zzleeper-photo-16600737.php index 5033b553..04207dcb 100644 --- a/tests/fixtures/www.23hq.com.zzleeper-photo-16600737.php +++ b/tests/fixtures/www.23hq.com.zzleeper-photo-16600737.php @@ -3,14 +3,14 @@ return [ 'authorName' => 'Zzleeper', - 'authorUrl' => 'http://www.23hq.com/Zzleeper/', + 'authorUrl' => 'https://www.23hq.com/Zzleeper/', 'cms' => null, 'code' => null, 'description' => 'På god väns inrådan kommer det även bultförband vid pilarna. Låter bra tycker jag. (Better safe than sorry)', - 'favicon' => 'http://www.23hq.com/favicon.ico', + 'favicon' => 'https://www.23hq.com/favicon.ico', 'feeds' => [], 'icon' => null, - 'image' => 'http://www.23hq.com/16171062/16600737_ac392e8a4d667e4726fbafc8a21728d5_large.jpg', + 'image' => 'https://www.23hq.com/16171062/16600737_ac392e8a4d667e4726fbafc8a21728d5_large.jpg', 'keywords' => [ 'compartir fotos', 'compartirfotos', @@ -46,23 +46,23 @@ 'publishedTime' => null, 'redirect' => null, 'title' => 'På god väns inrådan kommer det även bultförband vid pilarna. Låter bra tycker jag. (Better safe than sorry)', - 'url' => 'http://www.23hq.com/16171062/16600737_ac392e8a4d667e4726fbafc8a21728d5_large.jpg', + 'url' => 'https://www.23hq.com/16171062/16600737_ac392e8a4d667e4726fbafc8a21728d5_large.jpg', 'linkedData' => [], 'oEmbed' => [ 'version' => '1.0', 'type' => 'photo', 'title' => 'På god väns inrådan kommer det även bultförband vid pilarna. Låter bra tycker jag. (Better safe than sorry)', 'author_name' => 'Zzleeper', - 'author_url' => 'http://www.23hq.com/Zzleeper/', + 'author_url' => 'https://www.23hq.com/Zzleeper/', 'cache_age' => 3600, 'provider_name' => 23, 'provider_url' => 'http://www.23hq.com', 'width' => 756, 'height' => 567, - 'url' => 'http://www.23hq.com/16171062/16600737_ac392e8a4d667e4726fbafc8a21728d5_large.jpg', + 'url' => 'https://www.23hq.com/16171062/16600737_ac392e8a4d667e4726fbafc8a21728d5_large.jpg', 'thumbnail_width' => 756, 'thumbnail_height' => 567, - 'thumbnail_url' => 'http://www.23hq.com/16171062/16600737_ac392e8a4d667e4726fbafc8a21728d5_large.jpg' + 'thumbnail_url' => 'https://www.23hq.com/16171062/16600737_ac392e8a4d667e4726fbafc8a21728d5_large.jpg' ], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/www.abanca.com.gl.php b/tests/fixtures/www.abanca.com.gl.php index daeea813..c3ccca8d 100644 --- a/tests/fixtures/www.abanca.com.gl.php +++ b/tests/fixtures/www.abanca.com.gl.php @@ -24,7 +24,8 @@ 'language' => 'gl', 'languages' => [ 'gl' => 'https://www.abanca.com/gl/', - 'es' => 'https://www.abanca.com/es/' + 'es' => 'https://www.abanca.com/es/', + 'eu' => 'https://www.abanca.com/eu/' ], 'license' => null, 'providerName' => 'Abanca', diff --git a/tests/fixtures/www.aol.com.video-view-pile-of-recovering-foster-kittens-is-purrfect-595fe75985eb42109b69bedb.php b/tests/fixtures/www.aol.com.video-view-pile-of-recovering-foster-kittens-is-purrfect-595fe75985eb42109b69bedb.php index a1895839..2f4eaffe 100644 --- a/tests/fixtures/www.aol.com.video-view-pile-of-recovering-foster-kittens-is-purrfect-595fe75985eb42109b69bedb.php +++ b/tests/fixtures/www.aol.com.video-view-pile-of-recovering-foster-kittens-is-purrfect-595fe75985eb42109b69bedb.php @@ -6,7 +6,7 @@ 'authorUrl' => null, 'cms' => null, 'code' => null, - 'description' => null, + 'description' => 'The AOL.com video experience serves up the best video content from AOL and around the web, curating informative and entertaining snackable videos.', 'favicon' => 'https://s.yimg.com/cv/apiv2/aolfp/images/favicon/favicon-16x16.png', 'feeds' => [], 'icon' => 'https://s.yimg.com/cv/apiv2/aolfp/images/favicon/apple-touch-icon-57x57.png', @@ -20,7 +20,7 @@ 'publishedTime' => null, 'redirect' => null, 'title' => 'AOL Video - Serving the best video content from AOL and around the web', - 'url' => 'https://www.aol.com/video/view/pile-of-recovering-foster-kittens-is-purrfect/595fe75985eb42109b69bedb/', + 'url' => 'https://www.aol.com/video/', 'linkedData' => [], 'oEmbed' => [], 'allLinkedData' => [] diff --git a/tests/fixtures/www.bbc.co.uk.news-uk-54222286.php b/tests/fixtures/www.bbc.co.uk.news-uk-54222286.php index 0a79fb0b..05a9cead 100644 --- a/tests/fixtures/www.bbc.co.uk.news-uk-54222286.php +++ b/tests/fixtures/www.bbc.co.uk.news-uk-54222286.php @@ -14,6 +14,7 @@ 'keywords' => [], 'language' => 'en-GB', 'languages' => [ + 'x-default' => 'https://www.bbc.com/news/uk-54222286', 'en-gb' => 'https://www.bbc.co.uk/news/uk-54222286', 'en' => 'https://www.bbc.com/news/uk-54222286' ], @@ -34,16 +35,17 @@ 'publishingPrinciples' => 'http://www.bbc.co.uk/news/help-41670342', 'logo' => [ '@type' => 'ImageObject', - 'url' => 'https://www.bbc.co.uk/news/special/2015/newsspec_10857/bbc_news_logo.png?cb=1' + 'url' => 'https://static.files.bbci.co.uk/ws/simorgh-assets/public/news/images/metadata/poster-1024x576.png' ] ], 'datePublished' => '2020-09-20T06:33:33.000Z', 'dateModified' => '2020-09-20T06:33:33.000Z', + 'description' => 'Five things you need to know about the coronavirus pandemic this Sunday morning.', 'headline' => 'Coronavirus: £10,000 fines for failing to self-isolate and lockdown life in photos', 'image' => [ '@type' => 'ImageObject', - 'width' => 976, - 'height' => 549, + 'width' => 1024, + 'height' => 576, 'url' => 'https://ichef.bbci.co.uk/news/1024/branded_news/CC94/production/_111527325_index_daily_update_version02_cv_976new.png' ], 'thumbnailUrl' => 'https://ichef.bbci.co.uk/news/1024/branded_news/CC94/production/_111527325_index_daily_update_version02_cv_976new.png', @@ -54,7 +56,7 @@ 'noBylinesPolicy' => 'http://www.bbc.co.uk/news/help-41670342#authorexpertise', 'logo' => [ '@type' => 'ImageObject', - 'url' => 'https://www.bbc.co.uk/news/special/2015/newsspec_10857/bbc_news_logo.png?cb=1' + 'url' => 'https://static.files.bbci.co.uk/ws/simorgh-assets/public/news/images/metadata/poster-1024x576.png' ] ] ], @@ -70,16 +72,17 @@ 'publishingPrinciples' => 'http://www.bbc.co.uk/news/help-41670342', 'logo' => [ '@type' => 'ImageObject', - 'url' => 'https://www.bbc.co.uk/news/special/2015/newsspec_10857/bbc_news_logo.png?cb=1' + 'url' => 'https://static.files.bbci.co.uk/ws/simorgh-assets/public/news/images/metadata/poster-1024x576.png' ] ], 'datePublished' => '2020-09-20T06:33:33.000Z', 'dateModified' => '2020-09-20T06:33:33.000Z', + 'description' => 'Five things you need to know about the coronavirus pandemic this Sunday morning.', 'headline' => 'Coronavirus: £10,000 fines for failing to self-isolate and lockdown life in photos', 'image' => [ '@type' => 'ImageObject', - 'width' => 976, - 'height' => 549, + 'width' => 1024, + 'height' => 576, 'url' => 'https://ichef.bbci.co.uk/news/1024/branded_news/CC94/production/_111527325_index_daily_update_version02_cv_976new.png' ], 'thumbnailUrl' => 'https://ichef.bbci.co.uk/news/1024/branded_news/CC94/production/_111527325_index_daily_update_version02_cv_976new.png', @@ -90,7 +93,7 @@ 'noBylinesPolicy' => 'http://www.bbc.co.uk/news/help-41670342#authorexpertise', 'logo' => [ '@type' => 'ImageObject', - 'url' => 'https://www.bbc.co.uk/news/special/2015/newsspec_10857/bbc_news_logo.png?cb=1' + 'url' => 'https://static.files.bbci.co.uk/ws/simorgh-assets/public/news/images/metadata/poster-1024x576.png' ] ] ] diff --git a/tests/fixtures/www.dailymotion.com.video-xy0wd.php b/tests/fixtures/www.dailymotion.com.video-xy0wd.php index 193867c9..d73472df 100644 --- a/tests/fixtures/www.dailymotion.com.video-xy0wd.php +++ b/tests/fixtures/www.dailymotion.com.video-xy0wd.php @@ -6,16 +6,16 @@ 'authorUrl' => 'https://www.dailymotion.com/jeanbamin', 'cms' => null, 'code' => [ - 'html' => '', + 'html' => '', 'width' => 480, 'height' => 360, 'ratio' => 75.0 ], 'description' => 'Regardez Chats paresseux - jeanbamin sur Dailymotion', - 'favicon' => 'https://static1.dmcdn.net/images/neon/favicons/android-icon-36x36.png.vef49e2d9e48d19cf2', + 'favicon' => 'https://static1.dmcdn.net/neon/prod/favicons/android-icon-36x36.ddad17021e43a6fe3840bd2d31067714.png', 'feeds' => [], - 'icon' => 'https://static1.dmcdn.net/images/neon/favicons/apple-icon-precomposed.png.v668e5fde9462cb3a2', - 'image' => 'https://s2.dmcdn.net/v/63aD1VtIdtBWR7nC/x240', + 'icon' => 'https://static1.dmcdn.net/neon/prod/favicons/apple-icon-precomposed.a966d521999d793bdfcbe436bb56dad4.png', + 'image' => 'https://s2.dmcdn.net/v/63aD1au2tjDf9rTG/x240', 'keywords' => [], 'language' => 'en', 'languages' => [], @@ -38,8 +38,8 @@ 'author_url' => 'https://www.dailymotion.com/jeanbamin', 'width' => 480, 'height' => 360, - 'html' => '', - 'thumbnail_url' => 'https://s2.dmcdn.net/v/63aD1VtIdtBWR7nC/x240', + 'html' => '', + 'thumbnail_url' => 'https://s2.dmcdn.net/v/63aD1au2tjDf9rTG/x240', 'thumbnail_width' => 320, 'thumbnail_height' => 240 ], diff --git a/tests/fixtures/www.deviantart.com.art-misty-510056679.php b/tests/fixtures/www.deviantart.com.art-misty-510056679.php index 9af2f4e2..040b3629 100644 --- a/tests/fixtures/www.deviantart.com.art-misty-510056679.php +++ b/tests/fixtures/www.deviantart.com.art-misty-510056679.php @@ -2,26 +2,65 @@ declare(strict_types = 1); return [ - 'authorName' => null, - 'authorUrl' => null, + 'authorName' => 'sakimichan', + 'authorUrl' => 'https://www.deviantart.com/sakimichan', 'cms' => null, 'code' => null, 'description' => null, 'favicon' => 'https://st.deviantart.net/eclipse/icons/da_favicon_v2.ico', 'feeds' => [], 'icon' => 'https://st.deviantart.net/eclipse/icons/ios-180.png', - 'image' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/9ad8fbc8-aee8-4e92-a04b-6abf7a036a51/d8foa93-8e1fade9-1853-4644-af82-2ec21590bf94.jpg/v1/fill/w_695,h_900,q_75,strp/misty_by_sakimichan_d8foa93-fullview.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOiIsImlzcyI6InVybjphcHA6Iiwib2JqIjpbW3siaGVpZ2h0IjoiPD05MDAiLCJwYXRoIjoiXC9mXC85YWQ4ZmJjOC1hZWU4LTRlOTItYTA0Yi02YWJmN2EwMzZhNTFcL2Q4Zm9hOTMtOGUxZmFkZTktMTg1My00NjQ0LWFmODItMmVjMjE1OTBiZjk0LmpwZyIsIndpZHRoIjoiPD02OTUifV1dLCJhdWQiOlsidXJuOnNlcnZpY2U6aW1hZ2Uub3BlcmF0aW9ucyJdfQ.o3EGZf7MMCA0ycvTZ5sCjU2iEzRtD7JS38L_r-uzVyE', + 'image' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/9ad8fbc8-aee8-4e92-a04b-6abf7a036a51/d8foa93-8e1fade9-1853-4644-af82-2ec21590bf94.jpg/v1/fit/w_300,h_900,q_70,strp/misty_by_sakimichan_d8foa93-300w.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9OTAwIiwicGF0aCI6IlwvZlwvOWFkOGZiYzgtYWVlOC00ZTkyLWEwNGItNmFiZjdhMDM2YTUxXC9kOGZvYTkzLThlMWZhZGU5LTE4NTMtNDY0NC1hZjgyLTJlYzIxNTkwYmY5NC5qcGciLCJ3aWR0aCI6Ijw9Njk1In1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmltYWdlLm9wZXJhdGlvbnMiXX0.ms0Mi2WrZ6RA_clNOJNYh5k9Ow2CkK06A_NmVeMS4hM', 'keywords' => [], 'language' => 'en', 'languages' => [], 'license' => null, - 'providerName' => 'Deviantart', - 'providerUrl' => 'https://www.deviantart.com', - 'publishedTime' => null, + 'providerName' => 'DeviantArt', + 'providerUrl' => 'https://www.deviantart.com/', + 'publishedTime' => '2015-01-27 16:55:07', 'redirect' => null, - 'title' => 'Misty by sakimichan on DeviantArt', - 'url' => 'https://www.deviantart.com/sakimichan/art/Misty-510056679', + 'title' => 'Misty', + 'url' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/9ad8fbc8-aee8-4e92-a04b-6abf7a036a51/d8foa93-8e1fade9-1853-4644-af82-2ec21590bf94.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7InBhdGgiOiJcL2ZcLzlhZDhmYmM4LWFlZTgtNGU5Mi1hMDRiLTZhYmY3YTAzNmE1MVwvZDhmb2E5My04ZTFmYWRlOS0xODUzLTQ2NDQtYWY4Mi0yZWMyMTU5MGJmOTQuanBnIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmZpbGUuZG93bmxvYWQiXX0._JiXuDZWqsoAgateXxPBaC3_EPfHdri7pJCeKFhXnjA', 'linkedData' => [], - 'oEmbed' => [], + 'oEmbed' => [ + 'version' => '1.0', + 'type' => 'photo', + 'title' => 'Misty', + 'category' => 'Fan Art > Digital Art > Drawings > Games', + 'url' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/9ad8fbc8-aee8-4e92-a04b-6abf7a036a51/d8foa93-8e1fade9-1853-4644-af82-2ec21590bf94.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7InBhdGgiOiJcL2ZcLzlhZDhmYmM4LWFlZTgtNGU5Mi1hMDRiLTZhYmY3YTAzNmE1MVwvZDhmb2E5My04ZTFmYWRlOS0xODUzLTQ2NDQtYWY4Mi0yZWMyMTU5MGJmOTQuanBnIn1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmZpbGUuZG93bmxvYWQiXX0._JiXuDZWqsoAgateXxPBaC3_EPfHdri7pJCeKFhXnjA', + 'author_name' => 'sakimichan', + 'author_url' => 'https://www.deviantart.com/sakimichan', + 'provider_name' => 'DeviantArt', + 'provider_url' => 'https://www.deviantart.com', + 'safety' => 'nonadult', + 'pubdate' => '2015-01-27T16:55:07-08:00', + 'community' => [ + 'statistics' => [ + '_attributes' => [ + 'views' => 386632, + 'favorites' => 22702, + 'comments' => 675, + 'downloads' => 0 + ] + ] + ], + 'copyright' => [ + '_attributes' => [ + 'url' => 'https://www.deviantart.com/sakimichan', + 'year' => '2015', + 'entity' => 'sakimichan' + ] + ], + 'width' => '695', + 'height' => '900', + 'imagetype' => 'jpg', + 'thumbnail_url' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/9ad8fbc8-aee8-4e92-a04b-6abf7a036a51/d8foa93-8e1fade9-1853-4644-af82-2ec21590bf94.jpg/v1/fit/w_300,h_900,q_70,strp/misty_by_sakimichan_d8foa93-300w.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9OTAwIiwicGF0aCI6IlwvZlwvOWFkOGZiYzgtYWVlOC00ZTkyLWEwNGItNmFiZjdhMDM2YTUxXC9kOGZvYTkzLThlMWZhZGU5LTE4NTMtNDY0NC1hZjgyLTJlYzIxNTkwYmY5NC5qcGciLCJ3aWR0aCI6Ijw9Njk1In1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmltYWdlLm9wZXJhdGlvbnMiXX0.ms0Mi2WrZ6RA_clNOJNYh5k9Ow2CkK06A_NmVeMS4hM', + 'thumbnail_width' => 300, + 'thumbnail_height' => 388, + 'thumbnail_url_150' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/9ad8fbc8-aee8-4e92-a04b-6abf7a036a51/d8foa93-8e1fade9-1853-4644-af82-2ec21590bf94.jpg/v1/fit/w_150,h_150,q_70,strp/misty_by_sakimichan_d8foa93-150.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9OTAwIiwicGF0aCI6IlwvZlwvOWFkOGZiYzgtYWVlOC00ZTkyLWEwNGItNmFiZjdhMDM2YTUxXC9kOGZvYTkzLThlMWZhZGU5LTE4NTMtNDY0NC1hZjgyLTJlYzIxNTkwYmY5NC5qcGciLCJ3aWR0aCI6Ijw9Njk1In1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmltYWdlLm9wZXJhdGlvbnMiXX0.ms0Mi2WrZ6RA_clNOJNYh5k9Ow2CkK06A_NmVeMS4hM', + 'thumbnail_url_200h' => 'https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/9ad8fbc8-aee8-4e92-a04b-6abf7a036a51/d8foa93-8e1fade9-1853-4644-af82-2ec21590bf94.jpg/v1/crop/w_154,h_200,x_0,y_0,scl_0.22302158273381,q_70,strp/misty_by_sakimichan_d8foa93-200h.jpg?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwiaXNzIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsIm9iaiI6W1t7ImhlaWdodCI6Ijw9OTAwIiwicGF0aCI6IlwvZlwvOWFkOGZiYzgtYWVlOC00ZTkyLWEwNGItNmFiZjdhMDM2YTUxXC9kOGZvYTkzLThlMWZhZGU5LTE4NTMtNDY0NC1hZjgyLTJlYzIxNTkwYmY5NC5qcGciLCJ3aWR0aCI6Ijw9Njk1In1dXSwiYXVkIjpbInVybjpzZXJ2aWNlOmltYWdlLm9wZXJhdGlvbnMiXX0.ms0Mi2WrZ6RA_clNOJNYh5k9Ow2CkK06A_NmVeMS4hM', + 'thumbnail_width_200h' => 154, + 'thumbnail_height_200h' => 200 + ], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/www.flickr.com.photos-desescribir-sets-72157650686499888.php b/tests/fixtures/www.flickr.com.photos-desescribir-sets-72157650686499888.php index 93d255b1..d3630938 100644 --- a/tests/fixtures/www.flickr.com.photos-desescribir-sets-72157650686499888.php +++ b/tests/fixtures/www.flickr.com.photos-desescribir-sets-72157650686499888.php @@ -107,7 +107,6 @@ 'https://instagram.com/flickr', 'https://flickr.tumblr.com', 'https://twitter.com/flickr', - 'https://plus.google.com/+flickr', 'https://www.pinterest.com/flickr' ] ], diff --git a/tests/fixtures/www.hookem.com.story-texas-shortstop-joe-baker-arrested-public-intoxication.php b/tests/fixtures/www.hookem.com.story-texas-shortstop-joe-baker-arrested-public-intoxication.php index cbc31bba..6724865f 100644 --- a/tests/fixtures/www.hookem.com.story-texas-shortstop-joe-baker-arrested-public-intoxication.php +++ b/tests/fixtures/www.hookem.com.story-texas-shortstop-joe-baker-arrested-public-intoxication.php @@ -6,87 +6,22 @@ 'authorUrl' => null, 'cms' => null, 'code' => null, - 'description' => 'Get the latest breaking news, sports, entertainment and obituaries in Austin, TX from Hookem.com.', - 'favicon' => 'https://www.gannett-cdn.com/sites/hookem/images/favicon.png', + 'description' => null, + 'favicon' => 'https://eu.hookem.com/favicon.ico', 'feeds' => [], 'icon' => null, 'image' => null, - 'keywords' => [ - 'ssts:home', - 'type:front' - ], - 'language' => 'en', + 'keywords' => [], + 'language' => null, 'languages' => [], 'license' => null, - 'providerName' => 'Hookem.com', + 'providerName' => 'Hookem', 'providerUrl' => 'https://eu.hookem.com', - 'publishedTime' => '2021-04-03 23:43:58', + 'publishedTime' => null, 'redirect' => null, - 'title' => 'Hookem.com: Local News, Politics & Sports in Austin, TX', + 'title' => '404 Not Found', 'url' => 'https://eu.hookem.com/', - 'linkedData' => [ - '@context' => 'http://schema.org', - '@type' => 'WebPage', - 'author' => [ - '@type' => 'Person', - 'name' => 'Hookem.com Staff' - ], - 'dateModified' => '2021-04-03T23:43:58Z', - 'datePublished' => '2021-04-03T23:43:58Z', - 'headline' => 'Home', - 'image' => [], - 'keywords' => [ - 'ssts:home', - 'type:front' - ], - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.hookem.com/' - ], - 'publisher' => [ - '@type' => 'Organization', - 'logo' => [ - '@type' => 'ImageObject', - 'height' => 80, - 'url' => 'https://www.gannett-cdn.com/sites/hookem/images/site-nav-logo@2x.png', - 'width' => 297 - ], - 'name' => 'Hookem.com' - ], - 'url' => 'https://www.hookem.com/' - ], + 'linkedData' => [], 'oEmbed' => [], - 'allLinkedData' => [ - [ - '@context' => 'http://schema.org', - '@type' => 'WebPage', - 'author' => [ - '@type' => 'Person', - 'name' => 'Hookem.com Staff' - ], - 'dateModified' => '2021-04-03T23:43:58Z', - 'datePublished' => '2021-04-03T23:43:58Z', - 'headline' => 'Home', - 'image' => [], - 'keywords' => [ - 'ssts:home', - 'type:front' - ], - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.hookem.com/' - ], - 'publisher' => [ - '@type' => 'Organization', - 'logo' => [ - '@type' => 'ImageObject', - 'height' => 80, - 'url' => 'https://www.gannett-cdn.com/sites/hookem/images/site-nav-logo@2x.png', - 'width' => 297 - ], - 'name' => 'Hookem.com' - ], - 'url' => 'https://www.hookem.com/' - ] - ] + 'allLinkedData' => [] ]; diff --git a/tests/fixtures/www.itmedia.co.jp.news-articles-2410-28-news159.html.php b/tests/fixtures/www.itmedia.co.jp.news-articles-2410-28-news159.html.php index 43f5ac82..38e84091 100644 --- a/tests/fixtures/www.itmedia.co.jp.news-articles-2410-28-news159.html.php +++ b/tests/fixtures/www.itmedia.co.jp.news-articles-2410-28-news159.html.php @@ -8,10 +8,18 @@ 'code' => null, 'description' => 'さくらインターネットが、2025年3月期第2四半期累計(24年4月〜9月)の連結決算を発表した。売上高は132億7100万円(前年同期比28.3%増)、営業利益は12億9500万円(同419.4%増)だった。生成AI需要によってGPUクラウドサービスが伸長したという。', 'favicon' => 'https://www.itmedia.co.jp/favicon.ico', - 'feeds' => ['https://rss.itmedia.co.jp/rss/2.0/news_bursts.xml'], + 'feeds' => [ + 'https://rss.itmedia.co.jp/rss/2.0/news_bursts.xml' + ], 'icon' => null, 'image' => 'https://image.itmedia.co.jp/news/articles/2410/28/cover_news159.jpg', - 'keywords' => ['速報', 'ai', '生成aiニュース', 'クラウドユーザー', 'ロボット・ai'], + 'keywords' => [ + '速報', + 'ai', + '生成aiニュース', + 'クラウドユーザー', + 'ロボット・ai' + ], 'language' => 'ja', 'languages' => [], 'license' => null, @@ -26,9 +34,11 @@ '@type' => 'NewsArticle', 'mainEntityOfPage' => [ '@type' => 'WebPage', - '@id' => 'https://www.itmedia.co.jp/news/articles/2410/28/news159.html', + '@id' => 'https://www.itmedia.co.jp/news/articles/2410/28/news159.html' + ], + 'image' => [ + 'https://image.itmedia.co.jp/images/logo/1200x630_500x500_news.gif' ], - 'image' => ['https://image.itmedia.co.jp/images/logo/1200x630_500x500_news.gif'], 'datePublished' => '2024-10-28T17:55:00Z', 'dateModified' => '2024-10-28T18:05:00Z', 'headline' => 'さくらインターネット、営業利益が前年同期比420%増 GPUクラウド伸長で“生成AIフィーバー”? 上期決算', @@ -38,12 +48,12 @@ 'url' => 'https://www.itmedia.co.jp/news/', 'logo' => [ '@type' => 'ImageObject', - 'url' => 'https://image.itmedia.co.jp/images/logo/amp_publisher_news.gif', - ], + 'url' => 'https://image.itmedia.co.jp/images/logo/amp_publisher_news.gif' + ] ], 'description' => 'さくらインターネットが、2025年3月期第2四半期累計(24年4月〜9月)の連結決算を発表した。売上高は132億7100万円(前年同期比28.3%増)、営業利益は12億9500万円(同419.4%増)だった。生成AI需要によってGPUクラウドサービスが伸長したという。', 'articleSection' => 'ニュース', - 'keywords' => '速報,AI,生成AIニュース,クラウドユーザー,ロボット・AI', + 'keywords' => '速報,AI,生成AIニュース,クラウドユーザー,ロボット・AI' ], 'oEmbed' => [], 'allLinkedData' => [ @@ -52,9 +62,11 @@ '@type' => 'NewsArticle', 'mainEntityOfPage' => [ '@type' => 'WebPage', - '@id' => 'https://www.itmedia.co.jp/news/articles/2410/28/news159.html', + '@id' => 'https://www.itmedia.co.jp/news/articles/2410/28/news159.html' + ], + 'image' => [ + 'https://image.itmedia.co.jp/images/logo/1200x630_500x500_news.gif' ], - 'image' => ['https://image.itmedia.co.jp/images/logo/1200x630_500x500_news.gif'], 'datePublished' => '2024-10-28T17:55:00Z', 'dateModified' => '2024-10-28T18:05:00Z', 'headline' => 'さくらインターネット、営業利益が前年同期比420%増 GPUクラウド伸長で“生成AIフィーバー”? 上期決算', @@ -64,12 +76,12 @@ 'url' => 'https://www.itmedia.co.jp/news/', 'logo' => [ '@type' => 'ImageObject', - 'url' => 'https://image.itmedia.co.jp/images/logo/amp_publisher_news.gif', - ], + 'url' => 'https://image.itmedia.co.jp/images/logo/amp_publisher_news.gif' + ] ], 'description' => 'さくらインターネットが、2025年3月期第2四半期累計(24年4月〜9月)の連結決算を発表した。売上高は132億7100万円(前年同期比28.3%増)、営業利益は12億9500万円(同419.4%増)だった。生成AI需要によってGPUクラウドサービスが伸長したという。', 'articleSection' => 'ニュース', - 'keywords' => '速報,AI,生成AIニュース,クラウドユーザー,ロボット・AI', + 'keywords' => '速報,AI,生成AIニュース,クラウドユーザー,ロボット・AI' ] - ], + ] ]; diff --git a/tests/fixtures/www.kickstarter.com.projects-1452363698-good-seed-craft-veggie-burgers.php b/tests/fixtures/www.kickstarter.com.projects-1452363698-good-seed-craft-veggie-burgers.php index f7a7dcae..968da7ef 100644 --- a/tests/fixtures/www.kickstarter.com.projects-1452363698-good-seed-craft-veggie-burgers.php +++ b/tests/fixtures/www.kickstarter.com.projects-1452363698-good-seed-craft-veggie-burgers.php @@ -2,45 +2,26 @@ declare(strict_types = 1); return [ - 'authorName' => 'Oliver Ponce and Erin Shotwell', - 'authorUrl' => 'https://www.kickstarter.com/profile/1452363698', + 'authorName' => null, + 'authorUrl' => null, 'cms' => null, - 'code' => [ - 'html' => '', - 'width' => 480, - 'height' => 270, - 'ratio' => 56.25 - ], - 'description' => 'Nutrient-dense Real Food! Good Seed hemp patties transform the veggie burger with bold flavor blends & sprouted superfood ingredients.', + 'code' => null, + 'description' => null, 'favicon' => 'https://www.kickstarter.com/favicon.ico', 'feeds' => [], 'icon' => null, - 'image' => 'https://ksr-ugc.imgix.net/assets/011/647/643/9e80f11b5b125b5b50d1694cbf7f0ba0_original.jpg?ixlib=rb-2.1.0&crop=faces&w=560&h=315&fit=crop&v=1463686200&auto=format&frame=1&q=92&s=551e5f9430e497f3954a21ab81b30c6a', + 'image' => null, 'keywords' => [], - 'language' => 'en', + 'language' => 'en-US', 'languages' => [], 'license' => null, 'providerName' => 'Kickstarter', - 'providerUrl' => 'https://www.kickstarter.com/', + 'providerUrl' => 'https://www.kickstarter.com', 'publishedTime' => null, 'redirect' => null, - 'title' => 'Good Seed Craft Veggie Burgers', + 'title' => 'Just a moment...', 'url' => 'https://www.kickstarter.com/projects/1452363698/good-seed-craft-veggie-burgers', 'linkedData' => [], - 'oEmbed' => [ - 'version' => '1.0', - 'type' => 'rich', - 'provider_name' => 'Kickstarter', - 'provider_url' => 'https://www.kickstarter.com/', - 'title' => 'Good Seed Craft Veggie Burgers', - 'author_name' => 'Oliver Ponce and Erin Shotwell', - 'author_url' => 'https://www.kickstarter.com/profile/1452363698', - 'thumbnail_url' => 'https://ksr-ugc.imgix.net/assets/011/647/643/9e80f11b5b125b5b50d1694cbf7f0ba0_original.jpg?ixlib=rb-2.1.0&crop=faces&w=560&h=315&fit=crop&v=1463686200&auto=format&frame=1&q=92&s=551e5f9430e497f3954a21ab81b30c6a', - 'thumbnail_width' => 560, - 'thumbnail_height' => 315.0, - 'width' => 480, - 'height' => 270.0, - 'html' => '' - ], + 'oEmbed' => [], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/www.meetup.com.es-gpul-labs-events-248885422.php b/tests/fixtures/www.meetup.com.es-gpul-labs-events-248885422.php index 49957c45..abfe9cee 100644 --- a/tests/fixtures/www.meetup.com.es-gpul-labs-events-248885422.php +++ b/tests/fixtures/www.meetup.com.es-gpul-labs-events-248885422.php @@ -6,163 +6,47 @@ 'authorUrl' => null, 'cms' => null, 'code' => null, - 'description' => 'jue., 24 may. 2018 19:00: Fernando Souto - MOBGENDesde que Google anunció en el MWC que Flutter entraba en oficialmente beta, el interés por esta plataforma se ha multiplicado.Flutter es un SDK en len', - 'favicon' => 'https://www.meetup.com/mu_static/es/favicon.a6878039.ico', + 'description' => 'Not a Meetup member yet? Log in and find groups that host online or in person events and meet people in your local community who share your interests.', + 'favicon' => 'https://secure.meetupstatic.com/next/images/general/favicon.ico', 'feeds' => [], - 'icon' => 'https://www.meetup.com/mu_static/es/m_swarm_120x120.4f86abc9.png', - 'image' => 'https://secure.meetupstatic.com/photos/event/d/4/6/e/600_485634382.jpeg', + 'icon' => 'https://secure.meetupstatic.com/next/images/general/m_swarm_120x120.png', + 'image' => 'https://secure.meetupstatic.com/next/images/shared/meetup-logo.jpg', 'keywords' => [], - 'language' => 'es', - 'languages' => [ - 'en' => 'https://www.meetup.com/GPUL-Labs/events/248885422/', - 'de-DE' => 'https://www.meetup.com/de-DE/GPUL-Labs/events/248885422/', - 'de' => 'https://www.meetup.com/de-DE/GPUL-Labs/events/248885422/', - 'en-AU' => 'https://www.meetup.com/en-AU/GPUL-Labs/events/248885422/', - 'es' => 'https://www.meetup.com/es/GPUL-Labs/events/248885422/', - 'es-ES' => 'https://www.meetup.com/es-ES/GPUL-Labs/events/248885422/', - 'fr-FR' => 'https://www.meetup.com/fr-FR/GPUL-Labs/events/248885422/', - 'fr' => 'https://www.meetup.com/fr-FR/GPUL-Labs/events/248885422/', - 'it-IT' => 'https://www.meetup.com/it-IT/GPUL-Labs/events/248885422/', - 'it' => 'https://www.meetup.com/it-IT/GPUL-Labs/events/248885422/', - 'ja-JP' => 'https://www.meetup.com/ja-JP/GPUL-Labs/events/248885422/', - 'ja' => 'https://www.meetup.com/ja-JP/GPUL-Labs/events/248885422/', - 'ko-KR' => 'https://www.meetup.com/ko-KR/GPUL-Labs/events/248885422/', - 'ko' => 'https://www.meetup.com/ko-KR/GPUL-Labs/events/248885422/', - 'nl-NL' => 'https://www.meetup.com/nl-NL/GPUL-Labs/events/248885422/', - 'nl' => 'https://www.meetup.com/nl-NL/GPUL-Labs/events/248885422/', - 'pl-PL' => 'https://www.meetup.com/pl-PL/GPUL-Labs/events/248885422/', - 'pl' => 'https://www.meetup.com/pl-PL/GPUL-Labs/events/248885422/', - 'pt-BR' => 'https://www.meetup.com/pt-BR/GPUL-Labs/events/248885422/', - 'pt' => 'https://www.meetup.com/pt-BR/GPUL-Labs/events/248885422/', - 'ru-RU' => 'https://www.meetup.com/ru-RU/GPUL-Labs/events/248885422/', - 'ru' => 'https://www.meetup.com/ru-RU/GPUL-Labs/events/248885422/', - 'th-TH' => 'https://www.meetup.com/th-TH/GPUL-Labs/events/248885422/', - 'th' => 'https://www.meetup.com/th-TH/GPUL-Labs/events/248885422/', - 'tr-TR' => 'https://www.meetup.com/tr-TR/GPUL-Labs/events/248885422/', - 'tr' => 'https://www.meetup.com/tr-TR/GPUL-Labs/events/248885422/', - 'x-default' => 'https://www.meetup.com/GPUL-Labs/events/248885422/' - ], + 'language' => 'en-US', + 'languages' => [], 'license' => null, 'providerName' => 'Meetup', 'providerUrl' => 'https://www.meetup.com', 'publishedTime' => null, 'redirect' => null, - 'title' => 'Introducción a Flutter', - 'url' => 'https://www.meetup.com/es/GPUL-Labs/events/248885422/', + 'title' => 'Login to Meetup | Meetup', + 'url' => 'https://www.meetup.com/login/?returnUri=https%3A%2F%2Fwww.meetup.com%2Fgpul-labs%2Fevents%2F248885422%2F', 'linkedData' => [ - '@context' => 'http://schema.org', - '@type' => 'Event', - 'name' => 'Introducción a Flutter', - 'url' => 'https://www.meetup.com/es/GPUL-Labs/events/248885422/', - 'description' => 'Fernando Souto - MOBGEN - -Desde que Google anunció en el MWC que Flutter entraba en oficialmente beta, el interés por esta plataforma se ha multiplicado. - -Flutter es un SDK en lenguaje Dart para generar apps nativas multiplataforma, que permite un desarrollo eficaz, una interfaz de usuario expresiva y un buen rendimiento. - -Si estás interesado en saber más y aprender sobre Flutter apúntate. Haremos una introducción práctica al funcionamiento de Flutter desarollando un ejemplo práctico aplicando un', - 'startDate' => '2018-05-24T19:00+02:00', - 'endDate' => '2018-05-24T22:00+02:00', - 'eventStatus' => 'https://schema.org/EventScheduled', - 'eventAttendanceMode' => 'https://schema.org/OfflineEventAttendanceMode', - 'location' => [ - '@type' => 'Place', - 'name' => 'Vivero de empresas Accede Papagaio', - 'address' => [ - '@type' => 'PostalAddress', - 'addressLocality' => 'A Coruña', - 'addressCountry' => 'España', - 'streetAddress' => 'Rúa Hospital, 19, 15002' - ], - 'geo' => [ - '@type' => 'GeoCoordinates', - 'latitude' => 43.37335968017578, - 'longitude' => -8.399272918701172 - ] - ], - 'offers' => [ - '@type' => 'Offer', - 'price' => '0', - 'priceCurrency' => 'USD' - ], - 'organizer' => [ - '@type' => 'Organization', - 'name' => 'GPUL', - 'url' => 'https://www.meetup.com/es/GPUL-Labs/' + '@type' => 'Organization', + '@context' => 'https://schema.org', + 'url' => 'https://www.meetup.com/login/', + 'name' => 'Meetup', + 'logo' => 'https://secure.meetupstatic.com/next/images/general/m_swarm_630x630.png', + 'sameAs' => [ + 'https://www.facebook.com/meetup/', + 'https://twitter.com/Meetup/', + 'https://www.youtube.com/meetup', + 'https://www.instagram.com/meetup/' ] ], 'oEmbed' => [], 'allLinkedData' => [ [ - '@context' => 'http://schema.org', - '@type' => 'Event', - 'name' => 'Introducción a Flutter', - 'url' => 'https://www.meetup.com/es/GPUL-Labs/events/248885422/', - 'description' => 'Fernando Souto - MOBGEN - -Desde que Google anunció en el MWC que Flutter entraba en oficialmente beta, el interés por esta plataforma se ha multiplicado. - -Flutter es un SDK en lenguaje Dart para generar apps nativas multiplataforma, que permite un desarrollo eficaz, una interfaz de usuario expresiva y un buen rendimiento. - -Si estás interesado en saber más y aprender sobre Flutter apúntate. Haremos una introducción práctica al funcionamiento de Flutter desarollando un ejemplo práctico aplicando un', - 'startDate' => '2018-05-24T19:00+02:00', - 'endDate' => '2018-05-24T22:00+02:00', - 'eventStatus' => 'https://schema.org/EventScheduled', - 'eventAttendanceMode' => 'https://schema.org/OfflineEventAttendanceMode', - 'location' => [ - '@type' => 'Place', - 'name' => 'Vivero de empresas Accede Papagaio', - 'address' => [ - '@type' => 'PostalAddress', - 'addressLocality' => 'A Coruña', - 'addressCountry' => 'España', - 'streetAddress' => 'Rúa Hospital, 19, 15002' - ], - 'geo' => [ - '@type' => 'GeoCoordinates', - 'latitude' => 43.37335968017578, - 'longitude' => -8.399272918701172 - ] - ], - 'offers' => [ - '@type' => 'Offer', - 'price' => '0', - 'priceCurrency' => 'USD' - ], - 'organizer' => [ - '@type' => 'Organization', - 'name' => 'GPUL', - 'url' => 'https://www.meetup.com/es/GPUL-Labs/' - ] - ], - [ - '@context' => 'http://schema.org', - '@type' => 'BreadcrumbList', - 'itemListElement' => [ - [ - '@type' => 'ListItem', - 'position' => 1, - 'item' => [ - '@id' => 'https://www.meetup.com/es/GPUL-Labs/', - 'name' => 'GPUL' - ] - ], - [ - '@type' => 'ListItem', - 'position' => 2, - 'item' => [ - '@id' => 'https://www.meetup.com/es/GPUL-Labs/events/', - 'name' => 'Events' - ] - ], - [ - '@type' => 'ListItem', - 'position' => 3, - 'item' => [ - '@id' => 'https://www.meetup.com/es/GPUL-Labs/events/248885422/', - 'name' => 'Introducción a Flutter' - ] - ] + '@type' => 'Organization', + '@context' => 'https://schema.org', + 'url' => 'https://www.meetup.com/login/', + 'name' => 'Meetup', + 'logo' => 'https://secure.meetupstatic.com/next/images/general/m_swarm_630x630.png', + 'sameAs' => [ + 'https://www.facebook.com/meetup/', + 'https://twitter.com/Meetup/', + 'https://www.youtube.com/meetup', + 'https://www.instagram.com/meetup/' ] ] ] diff --git a/tests/fixtures/www.pinterest.com.pin-106890191127977979.php b/tests/fixtures/www.pinterest.com.pin-106890191127977979.php index d7475d9e..a29ccc4f 100644 --- a/tests/fixtures/www.pinterest.com.pin-106890191127977979.php +++ b/tests/fixtures/www.pinterest.com.pin-106890191127977979.php @@ -11,11 +11,11 @@ 'height' => 663, 'ratio' => 147.333 ], - 'description' => 'Apr 21, 2012 - This Pin was discovered by Leslie Carruthers. Discover (and save!) your own Pins on Pinterest', - 'favicon' => 'https://s.pinimg.com/webapp/favicon-54a5b2af.png', + 'description' => null, + 'favicon' => 'https://s.pinimg.com/webapp/favicon_48x48-7470a30d.png', 'feeds' => [], - 'icon' => 'https://s.pinimg.com/webapp/logo_trans_144x144-5e37c0c6.png', - 'image' => 'https://i.pinimg.com/236x/b5/34/13/b53413787f3a42c8b9d5b08a6fbb2124--jack-nicholson-jack-oconnell.jpg', + 'icon' => 'https://s.pinimg.com/webapp/logo_transparent_144x144-3da7a67b.png', + 'image' => 'https://i.pinimg.com/236x/b5/34/13/b53413787f3a42c8b9d5b08a6fbb2124.jpg', 'keywords' => [], 'language' => 'en', 'languages' => [], @@ -38,7 +38,7 @@ 'author_name' => 'Leslie Carruthers', 'author_url' => 'https://www.pinterest.com/thesearchguru/', 'html' => '', - 'thumbnail_url' => 'https://i.pinimg.com/236x/b5/34/13/b53413787f3a42c8b9d5b08a6fbb2124--jack-nicholson-jack-oconnell.jpg', + 'thumbnail_url' => 'https://i.pinimg.com/236x/b5/34/13/b53413787f3a42c8b9d5b08a6fbb2124.jpg', 'thumbnail_width' => 236, 'thumbnail_height' => 295 ], diff --git a/tests/fixtures/www.politico.com.story-2013-12-presidents-barack-obama-george-w-bush-second-term-101314.html.php b/tests/fixtures/www.politico.com.story-2013-12-presidents-barack-obama-george-w-bush-second-term-101314.html.php index 8be52d6e..14839b25 100644 --- a/tests/fixtures/www.politico.com.story-2013-12-presidents-barack-obama-george-w-bush-second-term-101314.html.php +++ b/tests/fixtures/www.politico.com.story-2013-12-presidents-barack-obama-george-w-bush-second-term-101314.html.php @@ -2,41 +2,26 @@ declare(strict_types = 1); return [ - 'authorName' => '@politico', - 'authorUrl' => 'https://twitter.com/politico', + 'authorName' => null, + 'authorUrl' => null, 'cms' => null, - 'code' => [ - 'html' => '', - 'width' => null, - 'height' => null, - 'ratio' => null - ], - 'description' => 'To veterans of the Bush White House, what Obama is going through is all too predictable.', - 'favicon' => 'https://static.politico.com/cf/05/ee684a274496b04fa20ba2978da1/politico.png', + 'code' => null, + 'description' => null, + 'favicon' => 'https://www.politico.com/favicon.ico', 'feeds' => [], - 'icon' => 'https://static.politico.com/dims4/default/bd69088/2147483647/legacy_thumbnail/144x144/quality/90/?url=https%3A%2F%2Fstatic.politico.com%2Fcf%2F05%2Fee684a274496b04fa20ba2978da1%2Fpolitico.png', - 'image' => 'http://s3-origin-images.politico.com/2013/12/18/131218_george_w_bush_barack_obama_ap_605.jpg', - 'keywords' => [ - 'presidents (110)', - 'barack obama (11857)', - 'white house (2586)', - 'second term (10)', - 'george w. bush (753)' - ], - 'language' => 'en', + 'icon' => null, + 'image' => null, + 'keywords' => [], + 'language' => 'en-US', 'languages' => [], 'license' => null, - 'providerName' => 'POLITICO', + 'providerName' => 'Politico', 'providerUrl' => 'https://www.politico.com', 'publishedTime' => null, 'redirect' => null, - 'title' => 'Obama 2nd term: Echoes of Bush', - 'url' => 'https://www.politico.com/story/2013/12/presidents-barack-obama-george-w-bush-second-term-101314', + 'title' => 'Just a moment...', + 'url' => 'https://www.politico.com/story/2013/12/presidents-barack-obama-george-w-bush-second-term-101314.html', 'linkedData' => [], - 'oEmbed' => [ - 'version' => '1.0', - 'type' => 'rich', - 'html' => '' - ], + 'oEmbed' => [], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/www.reddit.com.r-investing-comments-7pfpeq-buffett_on_cyrptocurrencies_i_can_say_almost_with.php b/tests/fixtures/www.reddit.com.r-investing-comments-7pfpeq-buffett_on_cyrptocurrencies_i_can_say_almost_with.php index 43a086b7..edf0bab8 100644 --- a/tests/fixtures/www.reddit.com.r-investing-comments-7pfpeq-buffett_on_cyrptocurrencies_i_can_say_almost_with.php +++ b/tests/fixtures/www.reddit.com.r-investing-comments-7pfpeq-buffett_on_cyrptocurrencies_i_can_say_almost_with.php @@ -6,16 +6,16 @@ 'authorUrl' => null, 'cms' => null, 'code' => [ - 'html' => '
Buffett on cyrptocurrencies: \'I can say almost with certainty that they will come to a bad ending\' from investing
', + 'html' => '
Buffett on cyrptocurrencies: \'I can say almost with certainty that they will come to a bad ending\'
by u/dennisrieves in investing
', 'width' => null, - 'height' => null, + 'height' => 316, 'ratio' => null ], - 'description' => '1,937 votes and 1,263 comments so far on Reddit', - 'favicon' => 'https://www.redditstatic.com/desktop2x/img/favicon/android-icon-192x192.png', + 'description' => null, + 'favicon' => 'https://www.reddit.com/favicon.ico', 'feeds' => [], - 'icon' => 'https://www.redditstatic.com/desktop2x/img/favicon/apple-icon-57x57.png', - 'image' => 'https://external-preview.redd.it/0e1ORiHMjPTnSvzC-f3SC1-3IfjyQHqzqkk5nJPlDts.jpg?auto=webp&s=ef51b1d9b2ec99ba5dc8031fd134011376932b38', + 'icon' => 'https://www.redditstatic.com/shreddit/assets/favicon/76x76.png', + 'image' => null, 'keywords' => [], 'language' => 'en-US', 'languages' => [], @@ -25,22 +25,21 @@ 'publishedTime' => null, 'redirect' => null, 'title' => 'Buffett on cyrptocurrencies: \'I can say almost with certainty that they will come to a bad ending\'', - 'url' => 'https://www.reddit.com/r/investing/comments/7pfpeq/buffett_on_cyrptocurrencies_i_can_say_almost_with/', + 'url' => 'https://www.reddit.com/r/investing/comments/7pfpeq/buffett_on_cyrptocurrencies_i_can_say_almost_with/?rdt=46144', 'linkedData' => [], 'oEmbed' => [ - 'provider_url' => 'https://www.reddit.com/', - 'version' => '1.0', - 'title' => 'Buffett on cyrptocurrencies: \'I can say almost with certainty that they will come to a bad ending\'', + 'author_name' => 'dennisrieves', + 'html' => '
+Buffett on cyrptocurrencies: \'I can say almost with certainty that they will come to a bad ending\'
by +u/dennisrieves in +investing +
+', 'provider_name' => 'reddit', + 'provider_url' => 'https://www.reddit.com', + 'title' => 'Buffett on cyrptocurrencies: \'I can say almost with certainty that they will come to a bad ending\'', 'type' => 'rich', - 'html' => ' -
- Buffett on cyrptocurrencies: \'I can say almost with certainty that they will come to a bad ending\' from - investing -
- -', - 'author_name' => 'dennisrieves' + 'height' => 316 ], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/www.scribd.com.doc-110799637-synthesis-of-knowledge-effects-of-fire-and-thinning-treatments-on-understory-vegetation-in-dry-u-s-forests.php b/tests/fixtures/www.scribd.com.doc-110799637-synthesis-of-knowledge-effects-of-fire-and-thinning-treatments-on-understory-vegetation-in-dry-u-s-forests.php index 5efb10e7..9fcabe2e 100644 --- a/tests/fixtures/www.scribd.com.doc-110799637-synthesis-of-knowledge-effects-of-fire-and-thinning-treatments-on-understory-vegetation-in-dry-u-s-forests.php +++ b/tests/fixtures/www.scribd.com.doc-110799637-synthesis-of-knowledge-effects-of-fire-and-thinning-treatments-on-understory-vegetation-in-dry-u-s-forests.php @@ -12,16 +12,15 @@ 'ratio' => null ], 'description' => 'A review of current literature on studies that address effects of prescribed fire and mechanical thinning treatments on understory vegetation, and the effects of prescribed burning on rare, threatened and endangered species. The studies\' outcomes are presented in two sets of tables: (1) functional group results, and (2) species-specific results.', - 'favicon' => 'https://s-f.scribdassets.com/favicon.ico?10f70579d?v=4', + 'favicon' => 'https://s-f.scribdassets.com/scribd.ico?64c27a3f1?v=5', 'feeds' => [], 'icon' => null, - 'image' => 'https://imgv2-2-f.scribdassets.com/img/document/110799637/111x142/9fc8621525/1617346919?v=1', + 'image' => 'https://imgv2-2-f.scribdassets.com/img/document/110799637/111x142/9fc8621525/1718028294?v=1', 'keywords' => [], 'language' => 'en', 'languages' => [ 'x-default' => 'https://www.scribd.com/document/110799637/Synthesis-of-Knowledge-Effects-of-Fire-and-Thinning-Treatments-on-Understory-Vegetation-in-Dry-U-S-Forests', - 'en' => 'https://www.scribd.com/document/110799637/Synthesis-of-Knowledge-Effects-of-Fire-and-Thinning-Treatments-on-Understory-Vegetation-in-Dry-U-S-Forests', - 'es' => 'https://es.scribd.com/document/110799637/Synthesis-of-Knowledge-Effects-of-Fire-and-Thinning-Treatments-on-Understory-Vegetation-in-Dry-U-S-Forests' + 'en' => 'https://www.scribd.com/document/110799637/Synthesis-of-Knowledge-Effects-of-Fire-and-Thinning-Treatments-on-Understory-Vegetation-in-Dry-U-S-Forests' ], 'license' => null, 'providerName' => 'Scribd', @@ -29,7 +28,7 @@ 'publishedTime' => null, 'redirect' => null, 'title' => 'Synthesis of Knowledge: Effects of Fire and Thinning Treatments on Understory Vegetation in Dry U.S. Forests', - 'url' => 'https://www.scribd.com/document/110799637/Synthesis-of-Knowledge-Effects-of-Fire-and-Thinning-Treatments-on-Understory-Vegetation-in-Dry-U-S-Forests', + 'url' => 'http://www.scribd.com/doc/110799637/Synthesis-of-Knowledge-Effects-of-Fire-and-Thinning-Treatments-on-Understory-Vegetation-in-Dry-U-S-Forests', 'linkedData' => [ '@context' => 'http://schema.org', '@type' => 'MediaObject', @@ -37,17 +36,21 @@ 'name' => 'Synthesis of Knowledge: Effects of Fire and Thinning Treatments on Understory Vegetation in Dry U.S. Forests', 'image' => [ 'https://imgv2-1-f.scribdassets.com/img/document/110799637/149x198/ca766a678b/1521312948?v=1', - 'https://imgv2-2-f.scribdassets.com/img/document/110799637/298x396/889f159160/1521312948?v=1' + 'https://imgv2-1-f.scribdassets.com/img/document/110799637/298x396/889f159160/1521312948?v=1' ], 'aggregateRating' => [ '@type' => 'AggregateRating', 'ratingValue' => 5.0, 'ratingCount' => 1 ], - 'isAccessibleForFree' => 'False', + 'author' => [ + '@type' => 'Person', + 'name' => 'Joint Fire Science Program' + ], + 'description' => 'A review of current literature on studies that address effects of prescribed fire and mechanical thinning treatments on understory vegetation, and the effects of prescribed burning on rare, threatened and endangered species. The studies\' outcomes are presented in two sets of tables: (1) functional group results, and (2) species-specific results.', + 'url' => 'https://www.scribd.com/document/110799637/Synthesis-of-Knowledge-Effects-of-Fire-and-Thinning-Treatments-on-Understory-Vegetation-in-Dry-U-S-Forests', 'hasPart' => [ '@type' => 'WebPageElement', - 'isAccessibleForFree' => 'False', 'cssSelector' => '.blurred_page' ] ], @@ -60,12 +63,13 @@ 'title' => 'Synthesis of Knowledge: Effects of Fire and Thinning Treatments on Understory Vegetation in Dry U.S. Forests', 'author_name' => 'Joint Fire Science Program', 'author_url' => 'https://www.scribd.com/user/151878975/Joint-Fire-Science-Program', - 'thumbnail_url' => 'https://imgv2-2-f.scribdassets.com/img/document/110799637/111x142/9fc8621525/1617346919?v=1', + 'thumbnail_url' => 'https://imgv2-2-f.scribdassets.com/img/document/110799637/111x142/9fc8621525/1718028294?v=1', 'thumbnail_width' => 164, 'thumbnail_height' => 212, 'html' => '' + ', + 'url' => 'http://www.scribd.com/doc/110799637/Synthesis-of-Knowledge-Effects-of-Fire-and-Thinning-Treatments-on-Understory-Vegetation-in-Dry-U-S-Forests' ], 'allLinkedData' => [ [ @@ -75,17 +79,21 @@ 'name' => 'Synthesis of Knowledge: Effects of Fire and Thinning Treatments on Understory Vegetation in Dry U.S. Forests', 'image' => [ 'https://imgv2-1-f.scribdassets.com/img/document/110799637/149x198/ca766a678b/1521312948?v=1', - 'https://imgv2-2-f.scribdassets.com/img/document/110799637/298x396/889f159160/1521312948?v=1' + 'https://imgv2-1-f.scribdassets.com/img/document/110799637/298x396/889f159160/1521312948?v=1' ], 'aggregateRating' => [ '@type' => 'AggregateRating', 'ratingValue' => 5.0, 'ratingCount' => 1 ], - 'isAccessibleForFree' => 'False', + 'author' => [ + '@type' => 'Person', + 'name' => 'Joint Fire Science Program' + ], + 'description' => 'A review of current literature on studies that address effects of prescribed fire and mechanical thinning treatments on understory vegetation, and the effects of prescribed burning on rare, threatened and endangered species. The studies\' outcomes are presented in two sets of tables: (1) functional group results, and (2) species-specific results.', + 'url' => 'https://www.scribd.com/document/110799637/Synthesis-of-Knowledge-Effects-of-Fire-and-Thinning-Treatments-on-Understory-Vegetation-in-Dry-U-S-Forests', 'hasPart' => [ '@type' => 'WebPageElement', - 'isAccessibleForFree' => 'False', 'cssSelector' => '.blurred_page' ] ] diff --git a/tests/fixtures/www.spreaker.com.user-angelclark-angel-clark-ice-cream-tocos.php b/tests/fixtures/www.spreaker.com.user-angelclark-angel-clark-ice-cream-tocos.php index 48334058..4cbbb80f 100644 --- a/tests/fixtures/www.spreaker.com.user-angelclark-angel-clark-ice-cream-tocos.php +++ b/tests/fixtures/www.spreaker.com.user-angelclark-angel-clark-ice-cream-tocos.php @@ -2,43 +2,35 @@ declare(strict_types = 1); return [ - 'authorName' => 'Radio Freedom .us', - 'authorUrl' => 'https://www.spreaker.com/user/angelclark', + 'authorName' => null, + 'authorUrl' => null, 'cms' => null, - 'code' => [ - 'html' => '', - 'width' => 640, - 'height' => 480, - 'ratio' => 75.0 - ], + 'code' => null, 'description' => 'Listen in to popular podcasts and radio shows from around the world or start your own with Spreaker!', - 'favicon' => 'https://d1sojsgu0jwtb7.cloudfront.net/images/favicons/favicon.ico', + 'favicon' => 'https://d1sojsgu0jwtb7.cloudfront.net/images/favicons/spreaker/favicon.ico', 'feeds' => [], - 'icon' => 'https://d1sojsgu0jwtb7.cloudfront.net/images/favicons/apple_touch_114.png', - 'image' => 'https://d3wo5wojvuv7l.cloudfront.net/t_widget_player_cover_medium/images.spreaker.com/original/07176451fd3625d19c5e5d88d9f1bfbb.jpg', + 'icon' => 'https://d1sojsgu0jwtb7.cloudfront.net/images/favicons/spreaker/apple-touch-icon.png', + 'image' => 'https://d1sojsgu0jwtb7.cloudfront.net/images/og_image.jpg', 'keywords' => [], 'language' => 'en', 'languages' => [], 'license' => null, 'providerName' => 'Spreaker', - 'providerUrl' => 'https://www.spreaker.com/', + 'providerUrl' => 'https://www.spreaker.com', 'publishedTime' => null, 'redirect' => null, - 'title' => 'Angel Clark: Ice Cream Toco\'s - Angel Clark Show', + 'title' => 'Spreaker', 'url' => 'https://www.spreaker.com/user/angelclark/angel-clark-ice-cream-tocos', 'linkedData' => [], 'oEmbed' => [ - 'type' => 'rich', - 'version' => '1.0', - 'provider_name' => 'Spreaker', - 'provider_url' => 'https://www.spreaker.com/', - 'title' => 'Angel Clark: Ice Cream Toco\'s - Angel Clark Show', - 'author_name' => 'Radio Freedom .us', - 'author_url' => 'https://www.spreaker.com/user/angelclark', - 'html' => '', - 'width' => 640, - 'height' => 480, - 'thumbnail_url' => 'https://d3wo5wojvuv7l.cloudfront.net/t_widget_player_cover_medium/images.spreaker.com/original/07176451fd3625d19c5e5d88d9f1bfbb.jpg' + 'response' => [ + 'error' => [ + 'messages' => [ + 'The input URL does not match any supported resource on Spreaker.' + ], + 'code' => 404 + ] + ] ], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/www.ted.com.talks-george_monbiot_for_more_wonder_rewild_the_world.07a0d4dfc7fba14d52025577270bbe9c.php b/tests/fixtures/www.ted.com.talks-george_monbiot_for_more_wonder_rewild_the_world.07a0d4dfc7fba14d52025577270bbe9c.php index 7feb53ee..311495ee 100644 --- a/tests/fixtures/www.ted.com.talks-george_monbiot_for_more_wonder_rewild_the_world.07a0d4dfc7fba14d52025577270bbe9c.php +++ b/tests/fixtures/www.ted.com.talks-george_monbiot_for_more_wonder_rewild_the_world.07a0d4dfc7fba14d52025577270bbe9c.php @@ -12,27 +12,27 @@ 'ratio' => 56.429 ], 'description' => 'Wolves were once native to the US\' Yellowstone National Park -- until hunting wiped them out. But when, in 1995, the wolves began to come back (thanks to an aggressive management program), something interesting happened: the rest of the park began to find a new, more healthful balance. In a bold thought experiment, George Monbiot imagines a wilder world in which humans work to restore the complex, lost natural food chains that once surrounded us.', - 'favicon' => 'https://pa.tedcdn.com/favicon.ico', + 'favicon' => 'https://www.ted.com/favicon.ico', 'feeds' => [], 'icon' => 'https://pa.tedcdn.com/apple-touch-icon-precomposed.png', 'image' => 'https://pi.tedcdn.com/r/pe.tedcdn.com/images/ted/b4a648817c1a2102dc53dc2147bb1af2785067f0_1600x1200.jpg?h=316&w=560', 'keywords' => [ 'ted', 'talks', + 'climate change', + 'environment', + 'science', + 'sustainability', 'animals', + 'community', + 'activism', 'biodiversity', 'deextinction', - 'science', + 'conservation', 'ecology', - 'environment', - 'activism', - 'oceans', - 'community', - 'climate change', - 'sustainability', - 'conservation' + 'ocean' ], - 'language' => 'en', + 'language' => null, 'languages' => [ 'x-default' => 'https://www.ted.com/talks/george_monbiot_for_more_wonder_rewild_the_world', 'el' => 'https://www.ted.com/talks/george_monbiot_for_more_wonder_rewild_the_world?language=el', diff --git a/tests/fixtures/www.tiktok.com.a3noticias-video-6806030056956251397.php b/tests/fixtures/www.tiktok.com.a3noticias-video-6806030056956251397.php index 1a660f53..08ae7f45 100644 --- a/tests/fixtures/www.tiktok.com.a3noticias-video-6806030056956251397.php +++ b/tests/fixtures/www.tiktok.com.a3noticias-video-6806030056956251397.php @@ -6,80 +6,19 @@ 'authorUrl' => 'https://www.tiktok.com/@a3noticias', 'cms' => null, 'code' => [ - 'html' => '
@a3noticias

Nuestro #AplausoSanitario más sincero. Para todos. Y recuerda, #QuédateEnCasa

♬ Resistiré - Duo Dinamico
', + 'html' => '
@a3noticias

Nuestro #AplausoSanitario más sincero. Para todos. Y recuerda, #QuédateEnCasa

♬ Resistiré - Duo Dinamico
', 'width' => null, 'height' => null, 'ratio' => null ], - 'description' => 'Antena 3 Noticias (@a3noticias) ha creado un video corto en TikTok con la música Resistiré. | Nuestro #AplausoSanitario más sincero. Para todos. Y recuerda, #QuédateEnCasa', - 'favicon' => 'https://s16.tiktokcdn.com/musical/resource/mtact/static/images/logo_144c91a.png?v=2', + 'description' => null, + 'favicon' => 'https://www.tiktok.com/favicon.ico', 'feeds' => [], - 'icon' => 'https://s16.tiktokcdn.com/musical/resource/mtact/static/pwa/icon_128x128.png', - 'image' => 'https://p77-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/165175e006354d838501378cfb325b79_1584652367?x-expires=1617570000&x-signature=ug9Nat5BeroYU6zWPlDXVO6EDak%3D', - 'keywords' => [ - 'antena 3 noticias', - 'a3noticias', - 'aplausosanitario', - 'quédateencasa', - 'tiktok', - 'ティックトック', - 'tik tok', - 'tick tock', - 'tic tok', - 'tic toc', - 'tictok', - 'тик ток', - 'ticktock' - ], + 'icon' => null, + 'image' => 'https://p77-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/165175e006354d838501378cfb325b79_1584652367?x-expires=1718204400&x-signature=weANzY8AXzX93c5j7jm34neMhWY%3D', + 'keywords' => [], 'language' => 'en', - 'languages' => [ - 'x-default' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397', - 'en' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=en', - 'ar' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=ar', - 'de-DE' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=de-DE', - 'es' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=es', - 'fi-FI' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=fi-FI', - 'fr' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=fr', - 'id-ID' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=id-ID', - 'ja-JP' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=ja-JP', - 'ko-KR' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=ko-KR', - 'ms-MY' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=ms-MY', - 'ru-RU' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=ru-RU', - 'th-TH' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=th-TH', - 'tr-TR' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=tr-TR', - 'vi-VN' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=vi-VN', - 'zh-Hant' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=zh-Hant-TW', - 'af-ZA' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=af-ZA', - 'he-IL' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=he-IL', - 'jv-ID' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=jv-ID', - 'ceb-PH' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=ceb-PH', - 'cs-CZ' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=cs-CZ', - 'it-IT' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=it-IT', - 'hu-HU' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=hu-HU', - 'nl-NL' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=nl-NL', - 'pl-PL' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=pl-PL', - 'pt-BR' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=pt-BR', - 'ro-RO' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=ro-RO', - 'sv-SE' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=sv-SE', - 'sw' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=sw', - 'fil-PH' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=fil-PH', - 'el-GR' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=el-GR', - 'zu-ZA' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=zu-ZA', - 'uk-UA' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=uk-UA', - 'ur' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=ur', - 'mr-IN' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=mr-IN', - 'hi-IN' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=hi-IN', - 'bn-IN' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=bn-IN', - 'pa-IN' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=pa-IN', - 'gu-IN' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=gu-IN', - 'or-IN' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=or-IN', - 'ta-IN' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=ta-IN', - 'te-IN' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=te-IN', - 'kn-IN' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=kn-IN', - 'ml-IN' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=ml-IN', - 'my-MM' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=my-MM', - 'km-KH' => 'https://www.tiktok.com/@a3noticias/video/6806030056956251397?lang=km-KH' - ], + 'languages' => [], 'license' => null, 'providerName' => 'TikTok', 'providerUrl' => 'https://www.tiktok.com/', @@ -96,12 +35,15 @@ 'author_name' => 'Antena 3 Noticias', 'width' => '100%', 'height' => '100%', - 'html' => '
@a3noticias

Nuestro #AplausoSanitario más sincero. Para todos. Y recuerda, #QuédateEnCasa

♬ Resistiré - Duo Dinamico
', + 'html' => '
@a3noticias

Nuestro #AplausoSanitario más sincero. Para todos. Y recuerda, #QuédateEnCasa

♬ Resistiré - Duo Dinamico
', 'thumbnail_width' => 480, 'thumbnail_height' => 848, - 'thumbnail_url' => 'https://p77-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/165175e006354d838501378cfb325b79_1584652367?x-expires=1617570000&x-signature=ug9Nat5BeroYU6zWPlDXVO6EDak%3D', + 'thumbnail_url' => 'https://p77-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/165175e006354d838501378cfb325b79_1584652367?x-expires=1718204400&x-signature=weANzY8AXzX93c5j7jm34neMhWY%3D', 'provider_url' => 'https://www.tiktok.com', - 'provider_name' => 'TikTok' + 'provider_name' => 'TikTok', + 'author_unique_id' => 'a3noticias', + 'embed_product_id' => '6806030056956251397', + 'embed_type' => 'video' ], 'allLinkedData' => [] ]; diff --git a/tests/fixtures/www.twitch.tv.videos-72749628.php b/tests/fixtures/www.twitch.tv.videos-72749628.php index c85d41ca..6c465004 100644 --- a/tests/fixtures/www.twitch.tv.videos-72749628.php +++ b/tests/fixtures/www.twitch.tv.videos-72749628.php @@ -6,31 +6,102 @@ 'authorUrl' => null, 'cms' => null, 'code' => [ - 'html' => '
- -', - 'width' => 620, - 'height' => 378, - 'ratio' => 60.968 + 'html' => '', + 'width' => 640, + 'height' => 360, + 'ratio' => 56.25 ], - 'description' => 'Twitch is the world\'s leading video platform and community for gamers.', - 'favicon' => 'https://static.twitchcdn.net/assets/favicon-32-e29e246c157142c94346.png', + 'description' => 'riotgames went live on Twitch. Catch up on their League of Legends VOD now.', + 'favicon' => 'https://assets.twitch.tv/assets/favicon-32-e29e246c157142c94346.png', 'feeds' => [], 'icon' => null, - 'image' => 'https://static-cdn.jtvnw.net/ttv-static-metadata/twitch_logo3.jpg', + 'image' => 'https://static-cdn.jtvnw.net/cf_vods/d2nvs31859zcd8/d44201bac3_riotgames_21876932832_470478938/thumb/thumb0-640x360.jpg', 'keywords' => [], 'language' => null, - 'languages' => [], + 'languages' => [ + 'bg' => 'https://www.twitch.tv/videos/72749628?lang=bg', + 'cs' => 'https://www.twitch.tv/videos/72749628?lang=cs', + 'da' => 'https://www.twitch.tv/videos/72749628?lang=da', + 'de' => 'https://www.twitch.tv/videos/72749628?lang=de', + 'el' => 'https://www.twitch.tv/videos/72749628?lang=el', + 'es' => 'https://www.twitch.tv/videos/72749628?lang=es', + 'es-mx' => 'https://www.twitch.tv/videos/72749628?lang=es-mx', + 'fi' => 'https://www.twitch.tv/videos/72749628?lang=fi', + 'fr' => 'https://www.twitch.tv/videos/72749628?lang=fr', + 'hu' => 'https://www.twitch.tv/videos/72749628?lang=hu', + 'it' => 'https://www.twitch.tv/videos/72749628?lang=it', + 'ja' => 'https://www.twitch.tv/videos/72749628?lang=ja', + 'ko' => 'https://www.twitch.tv/videos/72749628?lang=ko', + 'nl' => 'https://www.twitch.tv/videos/72749628?lang=nl', + 'no' => 'https://www.twitch.tv/videos/72749628?lang=no', + 'pl' => 'https://www.twitch.tv/videos/72749628?lang=pl', + 'pt' => 'https://www.twitch.tv/videos/72749628?lang=pt', + 'pt-br' => 'https://www.twitch.tv/videos/72749628?lang=pt-br', + 'ro' => 'https://www.twitch.tv/videos/72749628?lang=ro', + 'ru' => 'https://www.twitch.tv/videos/72749628?lang=ru', + 'sk' => 'https://www.twitch.tv/videos/72749628?lang=sk', + 'sv' => 'https://www.twitch.tv/videos/72749628?lang=sv', + 'th' => 'https://www.twitch.tv/videos/72749628?lang=th', + 'tr' => 'https://www.twitch.tv/videos/72749628?lang=tr', + 'vi' => 'https://www.twitch.tv/videos/72749628?lang=vi', + 'zh-cn' => 'https://www.twitch.tv/videos/72749628?lang=zh-cn', + 'zh-tw' => 'https://www.twitch.tv/videos/72749628?lang=zh-tw', + 'x-default' => 'https://www.twitch.tv/videos/72749628' + ], 'license' => null, 'providerName' => 'Twitch', 'providerUrl' => 'https://www.twitch.tv', 'publishedTime' => null, 'redirect' => null, - 'title' => 'Twitch', + 'title' => 'EU LCS Summer - Week 3 Day 1: FNC vs. S04 | H2K vs. G2 (EULCS2) - riotgames on Twitch', 'url' => 'https://www.twitch.tv/videos/72749628', - 'linkedData' => [], + 'linkedData' => [ + '@context' => 'http://schema.org', + '@graph' => [ + [ + '@type' => 'VideoObject', + 'description' => 'riotgames went live on Twitch. Catch up on their League of Legends VOD now.', + 'embedUrl' => 'https://www.twitch.tv/videos/72749628', + 'name' => 'EU LCS Summer - Week 3 Day 1: FNC vs. S04 | H2K vs. G2 (EULCS2)', + 'thumbnailUrl' => [ + 'https://static-cdn.jtvnw.net/cf_vods/d2nvs31859zcd8/d44201bac3_riotgames_21876932832_470478938/thumb/thumb0-640x360.jpg' + ], + 'uploadDate' => '2016-06-16T13:30:57Z', + 'duration' => 'PT2180S', + 'interactionStatistic' => [ + '@type' => 'InteractionCounter', + 'interactionType' => [ + '@type' => 'http://schema.org/WatchAction' + ], + 'userInteractionCount' => 1589 + ] + ] + ] + ], 'oEmbed' => [], - 'allLinkedData' => [] + 'allLinkedData' => [ + [ + '@context' => 'http://schema.org', + '@graph' => [ + [ + '@type' => 'VideoObject', + 'description' => 'riotgames went live on Twitch. Catch up on their League of Legends VOD now.', + 'embedUrl' => 'https://www.twitch.tv/videos/72749628', + 'name' => 'EU LCS Summer - Week 3 Day 1: FNC vs. S04 | H2K vs. G2 (EULCS2)', + 'thumbnailUrl' => [ + 'https://static-cdn.jtvnw.net/cf_vods/d2nvs31859zcd8/d44201bac3_riotgames_21876932832_470478938/thumb/thumb0-640x360.jpg' + ], + 'uploadDate' => '2016-06-16T13:30:57Z', + 'duration' => 'PT2180S', + 'interactionStatistic' => [ + '@type' => 'InteractionCounter', + 'interactionType' => [ + '@type' => 'http://schema.org/WatchAction' + ], + 'userInteractionCount' => 1589 + ] + ] + ] + ] + ] ]; diff --git a/tests/fixtures/www.ustream.tv.channel-red-shoes-billiards-60803-camera-1.php b/tests/fixtures/www.ustream.tv.channel-red-shoes-billiards-60803-camera-1.php index 1ce5754c..394c0920 100644 --- a/tests/fixtures/www.ustream.tv.channel-red-shoes-billiards-60803-camera-1.php +++ b/tests/fixtures/www.ustream.tv.channel-red-shoes-billiards-60803-camera-1.php @@ -6,21 +6,29 @@ 'authorUrl' => 'http://www.ustream.tv/', 'cms' => null, 'code' => [ - 'html' => '
Live Streaming Api', + 'html' => '
Live Video Streaming', 'width' => 400, 'height' => 320, 'ratio' => 80.0 ], - 'description' => 'Red Shoes Billiards 12009 S. Pulaski Road Alsip, Illinois 60803 708 388-3700 http://www.redshoesbilliards.com pool, billiards, 8 ball, 9 ball, 10 ball, straight pool, 14.1, banks, one pocket, APA, leagues, tournaments, 9 foot tables, bar tables, bar box, ACS,', + 'description' => 'End to end video platform for media & enterprises. Live streaming, video hosting, transcoding, monetization, distribution & delivery services for businesses.', 'favicon' => 'http://static-cdn2.ustream.tv/favicon.ico', 'feeds' => [], 'icon' => 'http://static-cdn2.ustream.tv/apple-touch-icon.png', 'image' => 'https://ustvstaticcdn2-a.akamaihd.net/i/channel/picture/1/1/9/5/11958409/11958409,120x90,r:2.jpg', - 'keywords' => [], + 'keywords' => [ + 'streaming', + 'streaming video', + 'video platform', + 'video hosting', + 'streaming video platform', + 'streaming video services', + 'video hosting services' + ], 'language' => 'es', 'languages' => [], 'license' => null, - 'providerName' => 'IBM Watson Media', + 'providerName' => 'IBM Video Streaming', 'providerUrl' => 'http://www.ustream.tv/', 'publishedTime' => null, 'redirect' => null, @@ -29,7 +37,7 @@ 'linkedData' => [], 'oEmbed' => [ 'provider_url' => 'http://www.ustream.tv/', - 'html' => '
Live Streaming Api', + 'html' => '
Live Video Streaming', 'title' => 'Red Shoes Billiards 1pkt,banks camera 1', 'author_name' => 'redsh0es', 'height' => 320, diff --git a/tests/fixtures/www.viddler.com.v-bdce8c7.php b/tests/fixtures/www.viddler.com.v-bdce8c7.php index eceb4b1f..7b06740b 100644 --- a/tests/fixtures/www.viddler.com.v-bdce8c7.php +++ b/tests/fixtures/www.viddler.com.v-bdce8c7.php @@ -14,7 +14,7 @@ 'description' => 'Viddler is a powerful and easy-to-use video platform that services both small and large companies. We give you the tools to connect with your audience in a unique way and we also help you leverage your videos so that you can make money.', 'favicon' => 'https://static1.cdn-ec.viddler.com/rails/assets/layout/favicon-a9c9c887d52b5d9a765dd3468a2285b8.png', 'feeds' => [ - 'http://www.viddler.com/rss/viddler' + 'https://www.viddler.com/rss/viddler' ], 'icon' => null, 'image' => 'https://thumbs.cdn-ec.viddler.com/thumbnail_2_bdce8c7_v8.jpg', diff --git a/tests/fixtures/www.wired.com..1202600986b37d2c6a30336f82c671f8.php b/tests/fixtures/www.wired.com..1202600986b37d2c6a30336f82c671f8.php index 00d6c0b3..dc201b3e 100644 --- a/tests/fixtures/www.wired.com..1202600986b37d2c6a30336f82c671f8.php +++ b/tests/fixtures/www.wired.com..1202600986b37d2c6a30336f82c671f8.php @@ -2,673 +2,215 @@ declare(strict_types = 1); return [ - 'authorName' => 'Wired Staff', - 'authorUrl' => 'https://www.wired.com/author/wired-staff/', + 'authorName' => 'Condé Nast', + 'authorUrl' => 'https://twitter.com/wired', 'cms' => null, - 'code' => [ - 'html' => '
Review: Yi 4K Action Camera
', - 'width' => 600, - 'height' => 338, - 'ratio' => 56.333 - ], - 'description' => 'For nearly two years GoPro’s Hero4 Black has been the standard-bearer for action cameras. But there\'s a new contender.', - 'favicon' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/favicon.ico', + 'code' => null, + 'description' => 'We bring you the future as it happens. From the latest in science and technology to the big stories in business and culture, we\'ve got you covered.', + 'favicon' => 'https://www.wired.com/verso/static/wired-us/assets/favicon.ico', 'feeds' => [ - 'https://www.wired.com/feed/' - ], - 'icon' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/apple-touch-icon.png', - 'image' => 'https://www.wired.com/wp-content/uploads/2016/07/Yi-4KHP.jpg', - 'keywords' => [ - '4k' + 'https://www.wired.com/feed/rss' ], + 'icon' => 'https://www.wired.com/verso/static/wired-us/assets/favicon.ico', + 'image' => 'https://media.wired.com/photos/615e2b8ce78a8cd913bbaa76/16:9/w_1280,c_limit/wired_bug.jpg', + 'keywords' => [], 'language' => 'en-US', 'languages' => [], - 'license' => null, + 'license' => 'Copyright (c) Condé Nast 2025', 'providerName' => 'WIRED', - 'providerUrl' => 'https://www.wired.com/', - 'publishedTime' => '2016-07-29 07:00:38', + 'providerUrl' => 'https://www.wired.com', + 'publishedTime' => null, 'redirect' => null, - 'title' => 'Review: Yi 4K Action Camera', + 'title' => 'WIRED - The Latest in Technology, Science, Culture and Business', 'url' => 'https://www.wired.com/?p=2064839', 'linkedData' => [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2016/07/review-yi-4k-action-camera/' - ], - 'headline' => 'Review: Yi 4K Action Camera', - 'datePublished' => '2016-07-29T07:00:38+00:00', - 'dateModified' => '2017-06-12T18:27:35+00:00', - 'keywords' => [ - '4k' - ], - 'url' => 'https://www.wired.com/2016/07/review-yi-4k-action-camera/', - 'articleSection' => 'gear', - 'author' => [ - '@type' => 'Person', - 'name' => 'Brent Rose' - ], - 'description' => 'For nearly two years GoPro’s Hero4 Black has been the standard-bearer for action cameras. But there\'s a new contender.', - 'image' => [ + '@context' => 'https://schema.org', + '@type' => 'Organization', + 'name' => 'WIRED', + 'logo' => [ '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2016/07/Yi-4KHP.jpg', - 'height' => 2680, - 'width' => 1507 - ] - ], - 'oEmbed' => [ - 'version' => '1.0', - 'provider_name' => 'WIRED', - 'provider_url' => 'https://www.wired.com', - 'author_name' => 'Wired Staff', - 'author_url' => 'https://www.wired.com/author/wired-staff/', - 'title' => 'Review: Yi 4K Action Camera', - 'type' => 'rich', - 'width' => 600, - 'height' => 338, - 'html' => '
Review: Yi 4K Action Camera
-', - 'thumbnail_url' => 'https://www.wired.com/wp-content/uploads/2016/07/Yi-4KHP.jpg', - 'thumbnail_width' => 600, - 'thumbnail_height' => 337 + 'url' => 'https://www.wired.com/verso/static/wired-us/assets/newsletter-signup-hub.jpg', + 'width' => '500px', + 'height' => '100px' + ], + 'url' => 'https://www.wired.com' ], + 'oEmbed' => [], 'allLinkedData' => [ [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2016/07/review-yi-4k-action-camera/' - ], - 'headline' => 'Review: Yi 4K Action Camera', - 'datePublished' => '2016-07-29T07:00:38+00:00', - 'dateModified' => '2017-06-12T18:27:35+00:00', - 'keywords' => [ - '4k' - ], - 'url' => 'https://www.wired.com/2016/07/review-yi-4k-action-camera/', - 'articleSection' => 'gear', - 'author' => [ - '@type' => 'Person', - 'name' => 'Brent Rose' - ], - 'description' => 'For nearly two years GoPro’s Hero4 Black has been the standard-bearer for action cameras. But there\'s a new contender.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2016/07/Yi-4KHP.jpg', - 'height' => 2680, - 'width' => 1507 - ] - ], - [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2021/04/geeks-guide-the-dispossessed/' - ], - 'headline' => '‘The Dispossessed’ Is Still One of Sci-Fi’s Smartest Books', - 'datePublished' => '2021-04-02T11:36:39+00:00', - 'dateModified' => '2021-04-02T11:36:39+00:00', - 'keywords' => [ - 'geeks-guide-to-the-galaxy' - ], - 'url' => 'https://www.wired.com/2021/04/geeks-guide-the-dispossessed/', - 'articleSection' => 'culture', - 'author' => [ - '@type' => 'Person', - 'name' => 'Geek\'s Guide to the Galaxy' - ], - 'description' => 'Ursula K. Le Guin\'s 1974 novel, about a society with no government or laws, remains a thoughtful exploration of politics and economics decades later.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2021/04/Science_geeksguide_2CDFJY0.jpg', - 'height' => 2400, - 'width' => 1600 - ] - ], - [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2021/03/geeks-guide-adventure-games/' - ], - 'headline' => 'It’s Never Been Easier to Make an Adventure Game', - 'datePublished' => '2021-03-26T11:40:18+00:00', - 'dateModified' => '2021-03-26T14:24:40+00:00', - 'keywords' => [ - 'geeks-guide-to-the-galaxy' - ], - 'url' => 'https://www.wired.com/2021/03/geeks-guide-adventure-games/', - 'articleSection' => 'culture', - 'author' => [ - '@type' => 'Person', - 'name' => 'Geek\'s Guide to the Galaxy' - ], - 'description' => 'The story-driven genre is enjoying an indie renaissance, thanks to new tools that are making the coding process simpler than ever.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2021/03/Culture_geeksguide_clip_boathouse_larger.jpg', - 'height' => 1200, - 'width' => 800 - ] - ], - [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2021/03/geeks-guide-griffin-mcelroy/' - ], - 'headline' => 'Starting a Podcast Is Harder Than It Looks', - 'datePublished' => '2021-03-19T11:38:11+00:00', - 'dateModified' => '2021-03-19T11:38:11+00:00', - 'keywords' => [ - 'geeks-guide-to-the-galaxy' - ], - 'url' => 'https://www.wired.com/2021/03/geeks-guide-griffin-mcelroy/', - 'articleSection' => 'culture', - 'author' => [ - '@type' => 'Person', - 'name' => 'Geek\'s Guide to the Galaxy' - ], - 'description' => 'But Everybody Has a Podcast (Except You) author Griffin McElroy can help.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2021/03/Culture_podcast_1250239031.jpg', - 'height' => 2400, - 'width' => 1800 - ] - ], - [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2021/03/geeks-guide-amazon-upload/' - ], - 'headline' => 'What If the Afterlife Had In-App Purchases?', - 'datePublished' => '2021-03-12T11:48:08+00:00', - 'dateModified' => '2021-03-12T11:48:08+00:00', - 'keywords' => [ - 'geeks-guide-to-the-galaxy' - ], - 'url' => 'https://www.wired.com/2021/03/geeks-guide-amazon-upload/', - 'articleSection' => 'culture', - 'author' => [ - '@type' => 'Person', - 'name' => 'Geek\'s Guide to the Galaxy' - ], - 'description' => 'The Amazon series Upload shows a comedic—and probably more realistic—version of what would happen if humans could live online after they die.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2021/03/Science_upload-UPLO_S1_Unit_108_0110RC_FNL_rgb.jpg', - 'height' => 2400, - 'width' => 1600 - ] - ], - [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2021/03/geeks-guide-drawing-chainmail/' - ], - 'headline' => 'Yes, Chainmail Is Really Hard to Draw', - 'datePublished' => '2021-03-05T11:30:34+00:00', - 'dateModified' => '2021-03-05T11:30:34+00:00', - 'keywords' => [ - 'geeks-guide-to-the-galaxy' - ], - 'url' => 'https://www.wired.com/2021/03/geeks-guide-drawing-chainmail/', - 'articleSection' => 'culture', - 'author' => [ - '@type' => 'Person', - 'name' => 'Geek\'s Guide to the Galaxy' - ], - 'description' => 'Ultima artist Denis Loubet knows this for a fact.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2021/03/Culture_GeeksGuide_2BXF8JA-1.png', - 'height' => 2400, - 'width' => 1800 - ] - ], - [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2021/02/geeks-guide-blood-of-zeus/' - ], - 'headline' => '‘Blood of Zeus’ Combines Myth With Saturday Morning Cartoons', - 'datePublished' => '2021-02-26T11:34:19+00:00', - 'dateModified' => '2021-02-26T11:34:19+00:00', - 'keywords' => [ - 'geeks-guide-to-the-galaxy', - 'netflix' - ], - 'url' => 'https://www.wired.com/2021/02/geeks-guide-blood-of-zeus/', - 'articleSection' => 'culture', - 'author' => [ - '@type' => 'Person', - 'name' => 'Geek\'s Guide to the Galaxy' - ], - 'description' => 'The new Netflix animated series puts a new spin on old tales.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2021/02/culture_BloodofZeus_Season1_Episode1_00_13_58_20.jpg', - 'height' => 1620, - 'width' => 1080 - ] - ], - [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2021/02/geeks-guide-settle-alien-worlds/' - ], - 'headline' => 'What Would It Take to Actually Settle an Alien World?', - 'datePublished' => '2021-02-19T11:59:02+00:00', - 'dateModified' => '2021-02-19T12:13:00+00:00', - 'keywords' => [ - 'geeks-guide-to-the-galaxy' - ], - 'url' => 'https://www.wired.com/2021/02/geeks-guide-settle-alien-worlds/', - 'articleSection' => 'culture', - 'author' => [ - '@type' => 'Person', - 'name' => 'Geek\'s Guide to the Galaxy' - ], - 'description' => 'David Gerrold\'s new novel Hella is about a low-gravity planet inhabited by dinosaur-like aliens. It does deep on the logistics of settling an alien planet.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2021/02/geeks-guide-alien-planet.jpg', - 'height' => 2400, - 'width' => 1350 - ] - ], - [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2021/02/geeks-guide-his-dark-materials-2/' - ], - 'headline' => 'Mrs. Coulter Is One of the Best Villains on TV', - 'datePublished' => '2021-02-12T11:52:57+00:00', - 'dateModified' => '2021-02-12T11:52:57+00:00', - 'keywords' => [ - 'geeks-guide-to-the-galaxy' - ], - 'url' => 'https://www.wired.com/2021/02/geeks-guide-his-dark-materials-2/', - 'articleSection' => 'culture', - 'author' => [ - '@type' => 'Person', - 'name' => 'Geek\'s Guide to the Galaxy' - ], - 'description' => 'Ruth Wilson\'s character is the highlight of HBO\'s His Dark Materials.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2021/02/Culture_GeeksGuide_ruth-wilson_1.jpg', - 'height' => 1444, - 'width' => 963 - ] - ], - [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2019/09/apple-iphone-event-liveblog/' - ], - 'headline' => 'Apple Event Liveblog: All the iPhone News as It Happens', - 'datePublished' => '2019-09-10T06:00:17+00:00', - 'dateModified' => '2019-09-10T12:00:57+00:00', - 'keywords' => [ - 'apple', - 'iphone' - ], - 'url' => 'https://www.wired.com/2019/09/apple-iphone-event-liveblog/', - 'articleSection' => 'gear', - 'author' => [ - '@type' => 'Person', - 'name' => 'Michael Calore' - ], - 'description' => 'Join us for live commentary beginning at 1 pm Eastern, 10 am Pacific.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2019/09/Gear-Apple-Liveblog-FA-1157988242.jpg', - 'height' => 2400, - 'width' => 1679 - ] - ], - [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2019/06/apple-wwdc-2019-liveblog/' - ], - 'headline' => 'WWDC 2019 Liveblog: All the Apple News as It Happens', - 'datePublished' => '2019-06-03T06:01:54+00:00', - 'dateModified' => '2019-06-03T14:09:28+00:00', - 'keywords' => [ - 'apple', - 'liveblog', - 'wwdc' - ], - 'url' => 'https://www.wired.com/2019/06/apple-wwdc-2019-liveblog/', - 'articleSection' => 'gear', - 'author' => [ - '@type' => 'Person', - 'name' => 'Wired Staff' - ], - 'description' => 'Apple\'s developer conference kicks off June 3 at 10 am Pacific. Follow along with us for analysis and commentary from WIRED\'s editors.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2019/06/gear_wwdc_967230896.jpg', - 'height' => 2400, - 'width' => 1800 - ] - ], - [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2021/03/geeks-guide-adventure-games/' - ], - 'headline' => 'It’s Never Been Easier to Make an Adventure Game', - 'datePublished' => '2021-03-26T11:40:18+00:00', - 'dateModified' => '2021-03-26T14:24:40+00:00', - 'keywords' => [ - 'geeks-guide-to-the-galaxy' - ], - 'url' => 'https://www.wired.com/2021/03/geeks-guide-adventure-games/', - 'articleSection' => 'culture', - 'author' => [ - '@type' => 'Person', - 'name' => 'Geek\'s Guide to the Galaxy' - ], - 'description' => 'The story-driven genre is enjoying an indie renaissance, thanks to new tools that are making the coding process simpler than ever.', - 'image' => [ + '@context' => 'https://schema.org', + '@type' => 'Organization', + 'name' => 'WIRED', + 'logo' => [ '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2021/03/Culture_geeksguide_clip_boathouse_larger.jpg', - 'height' => 1200, - 'width' => 800 - ] - ], - [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2019/04/facebook-f8-liveblog-2019/' + 'url' => 'https://www.wired.com/verso/static/wired-us/assets/newsletter-signup-hub.jpg', + 'width' => '500px', + 'height' => '100px' ], - 'headline' => 'F8 Liveblog: All the Facebook News as It Happens', - 'datePublished' => '2019-04-30T11:39:17+00:00', - 'dateModified' => '2019-04-30T13:03:50+00:00', - 'keywords' => [ - 'f8', - 'facebook', - 'liveblog' - ], - 'url' => 'https://www.wired.com/2019/04/facebook-f8-liveblog-2019/', - 'articleSection' => 'gear', - 'author' => [ - '@type' => 'Person', - 'name' => 'Wired Staff' - ], - 'description' => 'When Facebook kicks off its annual developer conference with a keynote address on Tuesday morning, we\'ll be liveblogging it right here.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2019/04/f8-953545066.jpg', - 'height' => 2399, - 'width' => 1823 - ] + 'url' => 'https://www.wired.com' ], [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' - ] - ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', - 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2019/03/apple-media-event-liveblog/' - ], - 'headline' => 'Apple Event Liveblog: All the News, as It Happens', - 'datePublished' => '2019-03-25T06:00:04+00:00', - 'dateModified' => '2019-03-25T12:05:50+00:00', - 'keywords' => [ - 'apple', - 'apple-event' - ], - 'url' => 'https://www.wired.com/2019/03/apple-media-event-liveblog/', - 'articleSection' => 'gear', - 'author' => [ - '@type' => 'Person', - 'name' => 'Wired Staff' - ], - 'description' => 'On Monday morning, Apple hosts an event to outline plans for its news distribution and media streaming services. Our live coverage starts at 9 am Pacific.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2019/03/timcook-967356920.jpg', - 'height' => 2400, - 'width' => 1800 - ] - ], - [ - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'WIRED', - 'logo' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/themes/Phoenix/assets/images/article-icon.jpg', - 'width' => '1024 px', - 'height' => '1024 px' + '@context' => 'https://schema.org', + '@type' => 'WebSite', + 'url' => 'https://www.wired.com', + 'name' => 'WIRED', + 'headline' => 'WIRED - The Latest in Technology, Science, Culture and Business', + 'potentialAction' => [ + '@type' => 'SearchAction', + 'target' => 'https://www.wired.com/search?q={search_term_string}', + 'query-input' => 'required name=search_term_string' + ] + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ItemList', + 'itemListElement' => [ + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'Sam Altman Says the GPT-5 Haters Got It All Wrong', + 'url' => 'https://www.wired.com/story/sam-altman-says-the-gpt-5-haters-got-it-all-wrong/', + 'position' => 1 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'ICE Wants to Build Out a 24/7 Social Media Surveillance Team', + 'url' => 'https://www.wired.com/story/ice-social-media-surveillance-24-7-contract/', + 'position' => 2 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'At a Conspiracy Conference in Rural Ireland, Charlie Kirk Was the Star', + 'url' => 'https://www.wired.com/story/conspiracy-conference-rural-ireland-charlie-kirk-quantum-clones/', + 'position' => 3 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'Government Workers Say Their Out-of-Office Replies Were Forcibly Changed to Blame Democrats for Shutdown', + 'url' => 'https://www.wired.com/story/government-workers-say-their-out-of-office-replies-were-forcibly-changed-to-blame-democrats-for-shutdown/', + 'position' => 4 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'Inside the Contentious World of Luigi Mangione Supporters', + 'url' => 'https://www.wired.com/story/inside-the-contentious-world-of-luigi-mangione-supporters/', + 'position' => 5 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'Why Are Car Software Updates Still So Bad?', + 'url' => 'https://www.wired.com/story/why-are-car-software-updates-still-so-bad/', + 'position' => 6 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'Watch Our Livestream Replay: Tech Went All In on Trump. Now What?', + 'url' => 'https://www.wired.com/story/livestream-tech-went-all-in-on-trump-now-what/', + 'position' => 7 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'Gear News of the Week: Adobe Premiere Lands on iPhone, and Nothing Lets You Design Your Own Widgets', + 'url' => 'https://www.wired.com/story/gear-news-of-the-week-adobe-premiere-lands-on-iphone-and-nothing-lets-you-design-your-own-widgets/', + 'position' => 8 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'The Vision Pro Was an Expensive Misstep. Now Apple Has to Catch Up With Smart Glasses', + 'url' => 'https://www.wired.com/story/the-vision-pro-was-an-expensive-misstep-now-apple-has-to-catch-up-with-smart-glasses/', + 'position' => 9 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'Lenovo’s Streamlined Gaming Handheld Is $120 Off', + 'url' => 'https://www.wired.com/story/legion-go-s-deal-1025/', + 'position' => 10 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'Bad Bunny Has MAGA All Worked Up', + 'url' => 'https://www.wired.com/story/bad-bunny-has-maga-all-worked-up/', + 'position' => 11 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'China Is Leading the World in the Clean Energy Transition. Here\'s What That Looks Like', + 'url' => 'https://www.wired.com/story/china-clean-energy-un-climate-summit-goals/', + 'position' => 12 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'China Rolls Out Its First Talent Visa as the US Retreats on H-1Bs', + 'url' => 'https://www.wired.com/story/china-talent-immigration-visa-h1-b-policy/', + 'position' => 13 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'Chatbots Play With Your Emotions to Avoid Saying Goodbye', + 'url' => 'https://www.wired.com/story/chatbots-play-with-emotions-to-avoid-saying-goodbye/', + 'position' => 14 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'The Post–Chuck Schumer Era', + 'url' => 'https://www.wired.com/story/chuck-schumer-government-shutdown/', + 'position' => 15 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'OpenAI Is Preparing to Launch a Social App for AI-Generated Videos', + 'url' => 'https://www.wired.com/story/openai-launches-sora-2-tiktok-like-app/', + 'position' => 16 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'Broadcast TV Is a \'Melting Ice Cube.’ Kimmel Just Turned Up the Heat', + 'url' => 'https://www.wired.com/story/broadcast-tv-fcc-carr-kimmel/', + 'position' => 17 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'The Trump Administration Is Coming for Nonprofits. They\'re Getting Ready', + 'url' => 'https://www.wired.com/story/the-trump-administration-is-coming-for-nonprofits-theyre-getting-ready/', + 'position' => 18 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'Federal Workers Are Being Told to Blame Democrats for the Shutdown', + 'url' => 'https://www.wired.com/story/government-employees-out-of-office-email-replies/', + 'position' => 19 + ], + [ + '@context' => 'https://schema.org', + '@type' => 'ListItem', + 'name' => 'Armed Guards and Muscle Milk: Senate Investigation Reveals DOGE Takeover Details', + 'url' => 'https://www.wired.com/story/armed-guards-muscle-milk-senate-investigation-doge-takeover-details/', + 'position' => 20 ] ], - '@context' => 'http://schema.org', - '@type' => 'NewsArticle', + 'numberOfItems' => 20, 'mainEntityOfPage' => [ - '@type' => 'WebPage', - '@id' => 'https://www.wired.com/2021/04/geeks-guide-the-dispossessed/' - ], - 'headline' => '‘The Dispossessed’ Is Still One of Sci-Fi’s Smartest Books', - 'datePublished' => '2021-04-02T11:36:39+00:00', - 'dateModified' => '2021-04-02T11:36:39+00:00', - 'keywords' => [ - 'geeks-guide-to-the-galaxy' - ], - 'url' => 'https://www.wired.com/2021/04/geeks-guide-the-dispossessed/', - 'articleSection' => 'culture', - 'author' => [ - '@type' => 'Person', - 'name' => 'Geek\'s Guide to the Galaxy' - ], - 'description' => 'Ursula K. Le Guin\'s 1974 novel, about a society with no government or laws, remains a thoughtful exploration of politics and economics decades later.', - 'image' => [ - '@type' => 'ImageObject', - 'url' => 'https://www.wired.com/wp-content/uploads/2021/04/Science_geeksguide_2CDFJY0.jpg', - 'height' => 2400, - 'width' => 1600 - ] - ], - [ - '@context' => 'http://schema.org/', - '@type' => 'Review', - 'itemReviewed' => [ - '@type' => 'Thing', - 'name' => 'Yi 4K Action Camera' - ], - 'author' => [ - '@type' => 'Person', - 'name' => 'Brent Rose' - ], - 'reviewRating' => [ - '@type' => 'Rating', - 'ratingValue' => '8', - 'bestRating' => '10' - ], - 'publisher' => [ - '@type' => 'Organization', - 'name' => 'Wired' + '@context' => 'https://schema.org', + '@type' => 'CollectionPage', + '@id' => 'https://www.wired.com' ] ] ] diff --git a/tests/fixtures/www.youtube.com.watch.878f5352b6f0e632a763ebf05fb48b16.php b/tests/fixtures/www.youtube.com.watch.878f5352b6f0e632a763ebf05fb48b16.php index 4a7ab12d..f0ff1f73 100644 --- a/tests/fixtures/www.youtube.com.watch.878f5352b6f0e632a763ebf05fb48b16.php +++ b/tests/fixtures/www.youtube.com.watch.878f5352b6f0e632a763ebf05fb48b16.php @@ -3,34 +3,76 @@ return [ 'authorName' => 'smshdchrb', - 'authorUrl' => 'https://www.youtube.com/user/smshdchrb', + 'authorUrl' => 'https://www.youtube.com/@smshdchrb', 'cms' => null, 'code' => [ - 'html' => '', + 'html' => '', 'width' => 200, 'height' => 150, 'ratio' => 75.0 ], - 'description' => null, - 'favicon' => 'https://www.google.com/favicon.ico', + 'description' => '7 week old fostered kittens waiting on their dinner being prepared. They had been ill with cat flu and were just starting to get their appetite back. Because...', + 'favicon' => 'https://www.youtube.com/s/desktop/ae4ecf92/img/favicon.ico', 'feeds' => [], - 'icon' => null, + 'icon' => 'https://www.youtube.com/s/desktop/ae4ecf92/img/favicon_144x144.png', 'image' => 'https://i.ytimg.com/vi/eiHXASgRTcA/hqdefault.jpg', - 'keywords' => [], - 'language' => 'es', + 'keywords' => [ + 'kittens', + 'cats', + 'hungry', + 'cat', + 'baby', + 'eight', + 'weeks', + 'old', + 'noisy', + 'meow', + 'funny', + 'kitties', + 'feline', + 'dinner', + 'excited', + 'loud', + 'cute', + 'pet', + 'food', + 'time', + 'felix', + 'kitty', + 'catz', + 'lolcat', + 'kitten', + 'talking', + 'adorable', + 'climbing' + ], + 'language' => 'es-ES', 'languages' => [], 'license' => null, 'providerName' => 'YouTube', 'providerUrl' => 'https://www.youtube.com/', - 'publishedTime' => null, - 'redirect' => 'https://consent.youtube.com/ml?continue=https://www.youtube.com/watch?v%3DeiHXASgRTcA&gl=ES&hl=es&pc=yt&uxe=23983172&src=1&rffu=true', + 'publishedTime' => '2008-10-04 13:06:22', + 'redirect' => null, 'title' => 'Noisy kittens waiting for dinner!', - 'url' => 'http://www.youtube.com/watch?v=eiHXASgRTcA', - 'linkedData' => [], + 'url' => 'https://www.youtube.com/watch?v=eiHXASgRTcA', + 'linkedData' => [ + '@context' => 'http://schema.org', + '@type' => 'BreadcrumbList', + 'itemListElement' => [ + [ + '@type' => 'ListItem', + 'position' => 1, + 'item' => [ + '@id' => 'http://www.youtube.com/@smshdchrb', + 'name' => 'smshdchrb' + ] + ] + ] + ], 'oEmbed' => [ 'title' => 'Noisy kittens waiting for dinner!', 'author_name' => 'smshdchrb', - 'author_url' => 'https://www.youtube.com/user/smshdchrb', + 'author_url' => 'https://www.youtube.com/@smshdchrb', 'type' => 'video', 'height' => 150, 'width' => 200, @@ -40,8 +82,22 @@ 'thumbnail_height' => 360, 'thumbnail_width' => 480, 'thumbnail_url' => 'https://i.ytimg.com/vi/eiHXASgRTcA/hqdefault.jpg', - 'html' => '', - 'url' => 'http://www.youtube.com/watch?v=eiHXASgRTcA' + 'html' => '' ], - 'allLinkedData' => [] + 'allLinkedData' => [ + [ + '@context' => 'http://schema.org', + '@type' => 'BreadcrumbList', + 'itemListElement' => [ + [ + '@type' => 'ListItem', + 'position' => 1, + 'item' => [ + '@id' => 'http://www.youtube.com/@smshdchrb', + 'name' => 'smshdchrb' + ] + ] + ] + ] + ] ];