From a55b05d734b3b278e2e49aa7b197688ce32be4d7 Mon Sep 17 00:00:00 2001 From: Clement Desmidt Date: Wed, 5 Apr 2023 14:44:57 +0200 Subject: [PATCH 01/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Begins=20to=20refact?= =?UTF-8?q?or=20for=20media?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For #4 --- .gitignore | 2 + composer.json | 23 + composer.lock | 1752 +++++++++++++++++++++++++ src/Media/Content.php | 114 ++ src/Media/Group.php | 8 + src/SRSS.php | 478 +++++++ src/SRSSException.php | 18 + src/SRSSItem.php | 199 +++ src/SRSSTools.php | 190 +++ srss.php | 817 ------------ tests/BasicReader.php | 22 + tests/MediaTest.php | 18 + tests/resources/basic.xml | 20 + tests/resources/media/cnn.xml | 1383 +++++++++++++++++++ tests/resources/media/music-video.xml | 32 + tests/resources/media/trailer.xml | 19 + 16 files changed, 4278 insertions(+), 817 deletions(-) create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 src/Media/Content.php create mode 100644 src/Media/Group.php create mode 100644 src/SRSS.php create mode 100644 src/SRSSException.php create mode 100644 src/SRSSItem.php create mode 100644 src/SRSSTools.php delete mode 100644 srss.php create mode 100644 tests/BasicReader.php create mode 100644 tests/MediaTest.php create mode 100644 tests/resources/basic.xml create mode 100644 tests/resources/media/cnn.xml create mode 100644 tests/resources/media/music-video.xml create mode 100644 tests/resources/media/trailer.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f45219c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +/.idea \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c49cd62 --- /dev/null +++ b/composer.json @@ -0,0 +1,23 @@ +{ + "name": "shikiryu/shikiryurss", + "description": "A RSS reader and writer", + "minimum-stability": "stable", + "license": "proprietary", + "authors": [ + { + "name": "clement", + "email": "email@example.com" + } + ], + "autoload": { + "psr-4": { + "Shikiryu\\SRSS\\": "src/" + } + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "require": { + "ext-dom": "*" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..1454f64 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1752 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "f9098bb855d1eaf15a0ec531f0903ae9", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^11", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-12-30T00:15:36+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.15.4", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" + }, + "time": "2023-03-05T19:49:14+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.26", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.15", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-03-06T12:58:08+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b65d59a059d3004a040c16a82e07bbdf6cfdd115", + "reference": "b65d59a059d3004a040c16a82e07bbdf6cfdd115", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.6" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2023-03-27T11:43:46+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T06:03:37+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-02-14T08:28:10+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "ext-dom": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.1.0" +} diff --git a/src/Media/Content.php b/src/Media/Content.php new file mode 100644 index 0000000..f518b53 --- /dev/null +++ b/src/Media/Content.php @@ -0,0 +1,114 @@ + 'link', + 'fileSize' => 'int', // TODO + 'type' => 'media_type', // TODO + 'medium' => 'media_medium', // TODO + 'isDefault' => 'bool', // TODO + 'expression' => 'medium_expression', // TODO + 'bitrate' => 'int', + 'framerate' => 'int', + 'samplingrate' => 'float', + 'channels' => 'int', + 'duration' => 'int', + 'height' => 'int', + 'width' => 'int', + 'lang' => '', + ]; + private array $attr = []; + + /** + * Constructor + * + * @param DomNode $node + */ + public function __construct(?\DOMNode $node = null) + { + parent::__construct('media:content'); + $this->_loadAttributes(); + } + + /** + * @return void + */ + private function _loadAttributes() + { + foreach ($this->attributes as $attributes) { + if (array_key_exists($attributes->name, $this->possibilities)) { + $this->{$attributes->name} = $attributes->value; + } + } + } + + + /** + * main getter for properties + * + * @param $name + * + * @return null|string + * @throws SRSSException + */ + public function __get($name) + { + if (array_key_exists($name, $this->attr)) { + return $this->attr[$name]; + } + + if (array_key_exists($name, $this->possibilities)) { + $tmp = $this->node->getElementsByTagName($name); + if ($tmp->length !== 1) { + return null; + } + + return $tmp->item(0)->nodeValue; + } + + throw new SRSSException(sprintf('%s is not a possible item (%s)', $name, implode(', ', array_keys($this->possibilities)))); + } + + /** + * @param $name + * + * @return bool + */ + public function __isset($name) + { + return isset($this->attr[$name]); + } + + /** + * main setter for properties + * + * @param $name + * @param $val + * + * @throws SRSSException + */ + public function __set($name, $val) + { + if (!array_key_exists($name, $this->possibilities)) { + throw new SRSSException(sprintf('%s is not a possible item (%s)', $name, implode(', ', array_keys($this->possibilities)))); + } + + $flag = $this->possibilities[$name]; + if ($flag !== '') + $val = SRSSTools::check($val, $flag); + if (!empty($val)) { + if ($this->$name === null) { + $this->node->appendChild(new DomElement($name, $val)); + } + $this->attr[$name] = $val; + } + } +} \ No newline at end of file diff --git a/src/Media/Group.php b/src/Media/Group.php new file mode 100644 index 0000000..f096468 --- /dev/null +++ b/src/Media/Group.php @@ -0,0 +1,8 @@ + 'nohtml', + 'link' => 'link', + 'description' => 'html', + 'language' => '', + //'language' => 'lang', + 'copyright' => 'nohtml', + 'pubDate' => 'date', + 'lastBuildDate' => 'date', + 'category' => 'nohtml', + 'docs' => 'link', + 'cloud' => '', + 'generator' => 'nohtml', + 'managingEditor' => 'email', + 'webMaster' => 'email', + 'ttl' => 'int', + 'image' => '', + 'rating' => 'nohtml', + //'textInput' => '', + 'skipHours' => 'hour', + 'skipDays' => 'day', + ]; + + /** + * Constructor + */ + public function __construct() + { + libxml_use_internal_errors(true); + parent::__construct(); + $this->xpath = new DOMXpath($this); + $this->attr = array(); + $this->items = array(); + $this->position = 0; + $this->formatOutput = true; + $this->preserveWhiteSpace = false; + } + + /** + * Destructor + * manage of libxml errors + */ + public function __destruct() + { + foreach (libxml_get_errors() as $error) { + error_log($error->message, 3, 'error.log'); + } + libxml_clear_errors(); + } + + /** + * @param $link string url of the rss + * @throws SRSSException + * @return SRSS + */ + public static function read($link): SRSS + { + $doc = new SRSS; + if(@$doc->load($link)) // We don't want the warning in case of bad XML. Let's manage it with an exception. + { + $channel = $doc->getElementsByTagName('channel'); + if($channel->length == 1){ // Good URL and good RSS + $doc->_loadAttributes(); // loading channel properties + $doc->getItems(); // loading all items + return $doc; + } + + throw new SRSSException('invalid file '.$link); + } + + throw new SRSSException('Can not open file '.$link); + } + + /** + * @return SRSS + */ + public static function create() + { + $doc = new SRSS; + $root = $doc->createElement('rss'); + $root->setAttribute('version', '2.0'); + $channel = $doc->createElement('channel'); + $root->appendChild($channel); + $doc->appendChild($root); + $doc->encoding = "UTF-8"; + $doc->generator = 'Shikiryu RSS'; + // $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html'; + return $doc; + } + + /** + * getter of "image"'s channel attributes + * @return string|array + */ + public function image() + { + $args = func_get_args(); + if(func_num_args() == 0) $args[0] = 'url'; + $img = $this->xpath->query('//channel/image'); + if($img->length != 1) return null; // is not in channel + $img = $img->item(0); + $r = array(); + foreach($img->childNodes as $child) + { + if($child->nodeType == XML_ELEMENT_NODE && in_array($child->nodeName, $args)) + { + $r[$child->nodeName] = $child->nodeValue; + } + } + return (func_num_args() > 1) ? $r : $r[$args[0]]; + } + + /** + * setter of "image"'s channel attributes + * @param $url string picture's url + * @param $title string picture's title + * @param $link string link on the picture + * @param $width int width + * @param $height int height + * @param $description string description + */ + public function setImage($url, $title, $link, $width = 0, $height = 0, $description = '') + { + $channel = $this->_getChannel(); + $array = []; + $url = SRSSTools::checkLink($url); + $array['url'] = $url; + $title = SRSSTools::noHTML($title); + $array['title'] = $title; + $link = SRSSTools::checkLink($link); + $array['link'] = $link; + if($width != 0) + { + $width = SRSSTools::checkInt($width); + $array['width'] = $width; + } + if($height != 0) + { + $height = SRSSTools::checkInt($height); + $array['height'] = $height; + } + if($description != 0) + { + $description = SRSSTools::noHTML($description); + $array['description'] = $description; + } + if($this->image == null) + { + $node = $this->createElement('image'); + $urlNode = $this->createElement('url', $url); + $titleNode = $this->createElement('title', $title); + $linkNode = $this->createElement('link', $link); + $node->appendChild($urlNode); + $node->appendChild($titleNode); + $node->appendChild($linkNode); + if($width != 0) + { + $widthNode = $this->createElement('width', $width); + $node->appendChild($widthNode); + } + if($height != 0) + { + $heightNode = $this->createElement('height', $height); + $node->appendChild($heightNode); + } + if($description != '') + { + $descNode = $this->createElement('description', $description); + $node->appendChild($descNode); + } + $channel->appendChild($node); + } + $this->attr['image'] = $array; + } + + /** + * setter of "cloud"'s channel attributes + * @param $domain string domain + * @param $port int port + * @param $path string path + * @param $registerProcedure string register procedure + * @param $protocol string protocol + */ + public function setCloud($domain, $port, $path, $registerProcedure, $protocol) + { + $channel = $this->_getChannel(); + $array = array(); + $domain = SRSSTools::noHTML($domain); + $array['domain'] = $domain; + $port = SRSSTools::checkInt($port); + $array['port'] = $port; + $path = SRSSTools::noHTML($path); + $array['path'] = $path; + $registerProcedure = SRSSTools::noHTML($registerProcedure); + $array['registerProcedure'] = $registerProcedure; + $protocol = SRSSTools::noHTML($protocol); + $array['protocol'] = $protocol; + if($this->cloud == null) + { + $node = $this->createElement('cloud'); + $node->setAttribute('domain', $domain); + $node->setAttribute('port', $port); + $node->setAttribute('path', $path); + $node->setAttribute('registerProcedure', $registerProcedure); + $node->setAttribute('protocol', $protocol); + $channel->appendChild($node); + } + $this->attr['cloud'] = $array; + } + + /** + * check if current RSS is a valid one (based on specifications) + * @return bool + */ + public function isValid() + { + $valid = true; + $items = $this->getItems(); + $invalidItems = array(); + $i = 1; + foreach($items as $item){ + if($item->isValid() === false){ + $invalidItems[] = $i; + $valid = false; + } + $i++; + } + return ($valid && $this->title != null && $this->link != null && $this->description != null); + } + + /** + * getter of current RSS channel + * @return DOMElement + * @throws SRSSException + */ + private function _getChannel() + { + $channel = $this->getElementsByTagName('channel'); + if($channel->length != 1) throw new SRSSException('channel node not created, or too many channel nodes'); + return $channel->item(0); + } + + /** + * @param $name + * + * @return bool + */ + public function __isset($name) + { + return isset($this->attr[$name]); + } + + /** + * setter of others attributes + * @param $name + * @param $val + * @throws SRSSException + */ + public function __set($name, $val) + { + $channel = $this->_getChannel(); + if(array_key_exists($name, $this->possibleAttr)){ + $flag = $this->possibleAttr[$name]; + $val = SRSSTools::check($val, $flag); + if(!empty($val)){ + if($this->$name == null){ + $node = $this->createElement($name, $val); + $channel->appendChild($node); + } + $this->attr[$name] = $val; + } + }else{ + throw new SRSSException($name.' is not a possible item'); + } + } + + /** + * getter of others attributes + * @param $name + * @return null|string + * @throws SRSSException + */ + public function __get($name) + { + if(isset($this->attr[$name])) + return $this->attr[$name]; +// $channel = $this->_getChannel(); + if(array_key_exists($name, $this->possibleAttr)){ + $tmp = $this->xpath->query('//channel/'.$name); + if($tmp->length != 1) return null; + return $tmp->item(0)->nodeValue; + }else{ + throw new SRSSException($name.' is not a possible value.'); + } + } + + /** + * add a SRSS Item as an item into current RSS as first item + * @param SRSSItem $item + */ + public function addItemBefore(SRSSItem $item) + { + $node = $this->importNode($item->getItem(), true); + $items = $this->getElementsByTagName('item'); + if($items->length != 0){ + $firstNode = $items->item(0); + if($firstNode != null) + $firstNode->parentNode->insertBefore($node, $firstNode); + else + $this->addItem($item); + } + else + $this->addItem($item); + } + + /** + * add a SRSS Item as an item into current RSS + * @param SRSSItem $item + */ + public function addItem(SRSSItem $item) + { + $node = $this->importNode($item->getItem(), true); + $channel = $this->_getChannel(); + $channel->appendChild($node); + } + + /** + * rewind from Iterator + */ + public function rewind() { + $this->position = 0; + } + + /** + * current from Iterator + */ + public function current() { + return $this->items[$this->position]; + } + + /** + * key from Iterator + */ + public function key() { + return $this->position; + } + + /** + * next from Iterator + */ + public function next() { + ++$this->position; + } + + /** + * valid from Iterator + */ + public function valid() { + return isset($this->items[$this->position]); + } + + /** + * getter of 1st item + * @return SRSSItem + */ + public function getFirst() + { + return $this->getItem(1); + } + + /** + * getter of last item + * @return SRSSItem + */ + public function getLast() + { + $items = $this->getItems(); + return $items[count($items)-1]; + } + + /** + * getter of an item + * @param $i int + * @return SRSSItem + */ + public function getItem($i) + { + $i--; + return isset($this->items[$i]) ? $this->items[$i] : null; + } + + /** + * getter of all items + * @return SRSSItem[] + * @throws SRSSException + */ + public function getItems() + { + $channel = $this->_getChannel(); + $item = $channel->getElementsByTagName('item'); + $length = $item->length; + $this->items = []; + if($length > 0){ + for($i = 0; $i < $length; $i++) + { + $this->items[$i] = new SRSSItem($item->item($i)); + } + } + return $this->items; + } + + /** + * display XML + * see DomDocument's docs + */ + public function show() + { + return $this->saveXml(); + } + + + /** + * putting all RSS attributes into the object + */ + private function _loadAttributes() + { + $channel = $this->_getChannel(); + foreach($channel->childNodes as $child) + { + if($child->nodeType == XML_ELEMENT_NODE && $child->nodeName != 'item') + { + if($child->nodeName == 'image'){ + foreach($child->childNodes as $children) + { + if($children->nodeType == XML_ELEMENT_NODE) + $this->attr['image'][$children->nodeName] = $children->nodeValue; + } + } + else + $this->attr[$child->nodeName] = $child->nodeValue; + } + } + } + + /** + * transform current object into an array + * @return array + */ + public function toArray() + { + $doc = array(); + foreach($this->attr as $attrName => $attrVal) + { + $doc[$attrName] = $attrVal; + } + foreach($this->getItems() as $item) + { + $doc['items'][] = $item->toArray(); + } + return $doc; + } +} \ No newline at end of file diff --git a/src/SRSSException.php b/src/SRSSException.php new file mode 100644 index 0000000..eca3cc8 --- /dev/null +++ b/src/SRSSException.php @@ -0,0 +1,18 @@ +Message : ' . $this->getMessage() . ' à la ligne : ' . $this->getLine(); + } +} \ No newline at end of file diff --git a/src/SRSSItem.php b/src/SRSSItem.php new file mode 100644 index 0000000..09af81a --- /dev/null +++ b/src/SRSSItem.php @@ -0,0 +1,199 @@ + 'nohtml', + 'link' => 'link', + 'description' => 'html', + 'author' => 'email', + 'category' => 'nohtml', + 'comments' => 'link', + 'enclosure' => '', + 'guid' => 'nohtml', + 'pubDate' => 'date', + 'source' => 'link', + 'media:group' => 'folder', + 'media:content' => '', + ]; + + /** + * Constructor + * + * @param DomNode $node + */ + public function __construct($node = null) + { + parent::__construct(); + if ($node instanceof DOMElement) $this->node = $this->importNode($node, true); + else $this->node = $this->importNode(new DomElement('item')); + $this->_loadAttributes(); + } + + /** + * putting all item attributes into the object + */ + private function _loadAttributes(): void + { + $this->_loadChildAttributes($this->node->childNodes); + } + + /** + * @param $nodes + * + * @return void + */ + private function _loadChildAttributes($nodes): void + { + foreach ($nodes as $child) { + if ($child->nodeType === XML_ELEMENT_NODE && $child->nodeName !== 'item') { + if (array_key_exists($child->nodeName, $this->possibilities) && $this->possibilities[$child->nodeName] === 'folder') { + $this->_loadChildAttributes($child->childNodes); + } + if ($child->nodeName === 'media:content') { + $this->{$child->nodeName} = new Content($child); + } else { + $this->{$child->nodeName} = $child->nodeValue; + } + } + } + } + + /** + * getter of item DomElement + */ + public function getItem() + { + $this->appendChild($this->node); + + return $this->getElementsByTagName('item')->item(0); + } + + /** + * setter for enclosure's properties + * + * @param $url string url + * @param $length int length + * @param $type string type + */ + public function setEnclosure($url, $length, $type) + { + $array = []; + $url = SRSSTools::checkLink($url); + $array['url'] = $url; + $length = SRSSTools::checkInt($length); + $array['length'] = $length; + $type = SRSSTools::noHTML($type); + $array['type'] = $type; + if ($this->enclosure == null) { + $node = $this->createElement('enclosure'); + $node->setAttribute('url', $url); + $node->setAttribute('length', $length); + $node->setAttribute('type', $type); + $this->node->appendChild($node); + } + $this->attr['enclosure'] = $array; + } + + /** + * check if current item is valid (following specifications) + * @return bool + */ + public function isValid() + { + return $this->description != null ? true : false; + } + + /** + * @param $name + * + * @return bool + */ + public function __isset($name) + { + return isset($this->attr[$name]); + } + + /** + * main setter for properties + * + * @param $name + * @param $val + * + * @throws SRSSException + */ + public function __set($name, $val) + { + if (!array_key_exists($name, $this->possibilities)) { + throw new SRSSException(sprintf('%s is not a possible item (%s)', $name, implode(', ', array_keys($this->possibilities)))); + } + + $flag = $this->possibilities[$name]; + if ($flag !== '') + $val = SRSSTools::check($val, $flag); + if (!empty($val)) { + if ($this->$name == null) { + $this->node->appendChild(new DomElement($name, $val)); + } + $this->attr[$name] = $val; + } + } + + /** + * main getter for properties + * + * @param $name + * + * @return null|string + * @throws SRSSException + */ + public function __get($name) + { + if (isset($this->attr[$name])) + return $this->attr[$name]; + if (array_key_exists($name, $this->possibilities)) { + $tmp = $this->node->getElementsByTagName($name); + if ($tmp->length != 1) return null; + + return $tmp->item(0)->nodeValue; + } + + throw new SRSSException(sprintf('%s is not a possible item (%s)', $name, implode(', ', array_keys($this->possibilities)))); + } + + /** + * display item's XML + * see DomDocument's docs + */ + public function show() + { + return $this->saveXml(); + } + + /** + * transform current item's object into an array + * @return array + */ + public function toArray() + { + $infos = []; + foreach ($this->attr as $attrName => $attrVal) { + $infos[$attrName] = $attrVal; + } + + return $infos; + } +} \ No newline at end of file diff --git a/src/SRSSTools.php b/src/SRSSTools.php new file mode 100644 index 0000000..1a2e2a9 --- /dev/null +++ b/src/SRSSTools.php @@ -0,0 +1,190 @@ + 2 ? '20'.substr($dateArray['tm_year'], -2) : '19'.$dateArray['tm_year']; + $date = $annee.'-'.$mois.'-'.$dateArray['tm_mday']; + return date("D, d M Y H:i:s T", strtotime($date)); + } + return ''; + } + + if(strtotime($date) !==false ){ + return date("D, d M Y H:i:s T", strtotime($date)); + } + + [$j, $m, $a] = explode('/', $date); + + return date("D, d M Y H:i:s T", strtotime($a.'-'.$m.'-'.$j)); + } + + /** + * check if it's an url + * @param $check string to check + * @return string|boolean the filtered data, or FALSE if the filter fails. + */ + public static function checkLink($check) + { + return filter_var($check, FILTER_VALIDATE_URL); + } + + /** + * make a string XML-compatible + * @param $check string to format + * @return string formatted string + * TODO CDATA ? + */ + public static function HTML4XML($check) + { + return htmlspecialchars($check); + } + + /** + * delete html tags + * @param $check string to format + * @return string formatted string + */ + public static function noHTML($check) + { + return strip_tags($check); + } + + /** + * check if it's a day (in RSS terms) + * @param $check string to check + * @return string the day, or empty string + */ + public static function checkDay($check) + { + $possibleDay = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']; + return in_array(strtolower($check), $possibleDay) ? $check : ''; + } + + /** + * check if it's an email + * @param $check string to check + * @return string|boolean the filtered data, or FALSE if the filter fails. + */ + public static function checkEmail($check) + { + return filter_var($check, FILTER_VALIDATE_EMAIL); + } + + /** + * check if it's an hour (in RSS terms) + * @param $check string to check + * @return string|boolean the filtered data, or FALSE if the filter fails. + */ + public static function checkHour($check) + { + $options = [ + 'options' => [ + 'default' => 0, + 'min_range' => 0, + 'max_range' => 23 + ] + ]; + return filter_var($check, FILTER_VALIDATE_INT, $options); + } + + /** + * check if it's an int + * @param $check int to check + * @return int|boolean the filtered data, or FALSE if the filter fails. + */ + public static function checkInt($check) + { + return filter_var($check, FILTER_VALIDATE_INT); + } + + /** + * @param $check + * + * @return mixed + */ + private static function checkMediaType($check) + { + return $check; + } + + /** + * @param $check + * + * @return mixed|null + */ + private static function checkMediaMedium($check) + { + return in_array($check, ['image', 'audio', 'video', 'document', 'executable']) ? $check : null; + } + + /** + * @param $check + * + * @return mixed|null + */ + private static function checkBool($check) + { + return in_array($check, ['true', 'false']) ? $check : null; + } + + /** + * @param $check + * + * @return mixed|null + */ + private static function checkMediumExpression($check) + { + return in_array($check, ['sample', 'full', 'nonstop']) ? $check : null; + } +} \ No newline at end of file diff --git a/srss.php b/srss.php deleted file mode 100644 index 2d3fdeb..0000000 --- a/srss.php +++ /dev/null @@ -1,817 +0,0 @@ - 'nohtml', - 'link' => 'link', - 'description' => 'html', - 'language' => '', - //'language' => 'lang', - 'copyright' => 'nohtml', - 'pubDate' => 'date', - 'lastBuildDate' => 'date', - 'category' => 'nohtml', - 'docs' => 'link', - 'cloud' => '', - 'generator' => 'nohtml', - 'managingEditor' => 'email', - 'webMaster' => 'email', - 'ttl' => 'int', - 'image' => '', - 'rating' => 'nohtml', - //'textInput' => '', - 'skipHours' => 'hour', - 'skipDays' => 'day', - ); - - /** - * Constructor - */ - public function __construct() - { - libxml_use_internal_errors(true); - parent::__construct(); - $this->xpath = new DOMXpath($this); - $this->attr = array(); - $this->items = array(); - $this->position = 0; - $this->formatOutput = true; - $this->preserveWhiteSpace = false; - } - - /** - * Destructor - * manage of libxml errors - */ - public function __destruct() - { - foreach (libxml_get_errors() as $error) { - error_log($error->message, 3, 'error.log'); - } - libxml_clear_errors(); - } - - /** - * @param $link string url of the rss - * @throws SRSSException - * @return SRSS - */ - public static function read($link) - { - $doc = new SRSS; - if(@$doc->load($link)) // We don't want the warning in case of bad XML. Let's manage it with an exception. - { - $channel = $doc->getElementsByTagName('channel'); - if($channel->length == 1){ // Good URL and good RSS - $doc->_loadAttributes(); // loading channel properties - $doc->getItems(); // loading all items - return $doc; - } - else - { - throw new SRSSException('invalid file '.$link); - } - } - else - { - throw new SRSSException('Can not open file '.$link); - } - } - - /** - * @return SRSS - */ - public static function create() - { - $doc = new SRSS; - $root = $doc->createElement('rss'); - $root->setAttribute('version', '2.0'); - $channel = $doc->createElement('channel'); - $root->appendChild($channel); - $doc->appendChild($root); - $doc->encoding = "UTF-8"; - $doc->generator = 'Shikiryu RSS'; - // $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html'; - return $doc; - } - - /** - * getter of "image"'s channel attributes - * @return string|array - */ - public function image() - { - $args = func_get_args(); - if(func_num_args() == 0) $args[0] = 'url'; - $img = $this->xpath->query('//channel/image'); - if($img->length != 1) return null; // is not in channel - $img = $img->item(0); - $r = array(); - foreach($img->childNodes as $child) - { - if($child->nodeType == XML_ELEMENT_NODE && in_array($child->nodeName, $args)) - { - $r[$child->nodeName] = $child->nodeValue; - } - } - return (func_num_args() > 1) ? $r : $r[$args[0]]; - } - - /** - * setter of "image"'s channel attributes - * @param $url string picture's url - * @param $title string picture's title - * @param $link string link on the picture - * @param $width int width - * @param $height int height - * @param $description string description - */ - public function setImage($url, $title, $link, $width = 0, $height = 0, $description = '') - { - $channel = $this->_getChannel(); - $array = array(); - $url = SRSSTools::checkLink($url); - $array['url'] = $url; - $title = SRSSTools::noHTML($title); - $array['title'] = $title; - $link = SRSSTools::checkLink($link); - $array['link'] = $link; - if($width != 0) - { - $width = SRSSTools::checkInt($width); - $array['width'] = $width; - } - if($height != 0) - { - $height = SRSSTools::checkInt($height); - $array['height'] = $height; - } - if($description != 0) - { - $description = SRSSTools::noHTML($description); - $array['description'] = $description; - } - if($this->image == null) - { - $node = $this->createElement('image'); - $urlNode = $this->createElement('url', $url); - $titleNode = $this->createElement('title', $title); - $linkNode = $this->createElement('link', $link); - $node->appendChild($urlNode); - $node->appendChild($titleNode); - $node->appendChild($linkNode); - if($width != 0) - { - $widthNode = $this->createElement('width', $width); - $node->appendChild($widthNode); - } - if($height != 0) - { - $heightNode = $this->createElement('height', $height); - $node->appendChild($heightNode); - } - if($description != '') - { - $descNode = $this->createElement('description', $description); - $node->appendChild($descNode); - } - $channel->appendChild($node); - } - $this->attr['image'] = $array; - } - - /** - * setter of "cloud"'s channel attributes - * @param $domain string domain - * @param $port int port - * @param $path string path - * @param $registerProcedure string register procedure - * @param $protocol string protocol - */ - public function setCloud($domain, $port, $path, $registerProcedure, $protocol) - { - $channel = $this->_getChannel(); - $array = array(); - $domain = SRSSTools::noHTML($domain); - $array['domain'] = $domain; - $port = SRSSTools::checkInt($port); - $array['port'] = $port; - $path = SRSSTools::noHTML($path); - $array['path'] = $path; - $registerProcedure = SRSSTools::noHTML($registerProcedure); - $array['registerProcedure'] = $registerProcedure; - $protocol = SRSSTools::noHTML($protocol); - $array['protocol'] = $protocol; - if($this->cloud == null) - { - $node = $this->createElement('cloud'); - $node->setAttribute('domain', $domain); - $node->setAttribute('port', $port); - $node->setAttribute('path', $path); - $node->setAttribute('registerProcedure', $registerProcedure); - $node->setAttribute('protocol', $protocol); - $channel->appendChild($node); - } - $this->attr['cloud'] = $array; - } - - /** - * check if current RSS is a valid one (based on specifications) - * @return bool - */ - public function isValid() - { - $valid = true; - $items = $this->getItems(); - $invalidItems = array(); - $i = 1; - foreach($items as $item){ - if($item->isValid() === false){ - $invalidItems[] = $i; - $valid = false; - } - $i++; - } - return ($valid && $this->title != null && $this->link != null && $this->description != null); - } - - /** - * getter of current RSS channel - * @return DOMElement - * @throws SRSSException - */ - private function _getChannel() - { - $channel = $this->getElementsByTagName('channel'); - if($channel->length != 1) throw new SRSSException('channel node not created, or too many channel nodes'); - return $channel->item(0); - } - - /** - * setter of others attributes - * @param $name - * @param $val - * @throws SRSSException - */ - public function __set($name, $val) - { - $channel = $this->_getChannel(); - if(array_key_exists($name, $this->possibleAttr)){ - $flag = $this->possibleAttr[$name]; - $val = SRSSTools::check($val, $flag); - if(!empty($val)){ - if($this->$name == null){ - $node = $this->createElement($name, $val); - $channel->appendChild($node); - } - $this->attr[$name] = $val; - } - }else{ - throw new SRSSException($name.' is not a possible item'); - } - } - - /** - * getter of others attributes - * @param $name - * @return null|string - * @throws SRSSException - */ - public function __get($name) - { - if(isset($this->attr[$name])) - return $this->attr[$name]; -// $channel = $this->_getChannel(); - if(array_key_exists($name, $this->possibleAttr)){ - $tmp = $this->xpath->query('//channel/'.$name); - if($tmp->length != 1) return null; - return $tmp->item(0)->nodeValue; - }else{ - throw new SRSSException($name.' is not a possible value.'); - } - } - - /** - * add a SRSS Item as an item into current RSS as first item - * @param SRSSItem $item - */ - public function addItemBefore(SRSSItem $item) - { - $node = $this->importNode($item->getItem(), true); - $items = $this->getElementsByTagName('item'); - if($items->length != 0){ - $firstNode = $items->item(0); - if($firstNode != null) - $firstNode->parentNode->insertBefore($node, $firstNode); - else - $this->addItem($item); - } - else - $this->addItem($item); - } - - /** - * add a SRSS Item as an item into current RSS - * @param SRSSItem $item - */ - public function addItem(SRSSItem $item) - { - $node = $this->importNode($item->getItem(), true); - $channel = $this->_getChannel(); - $channel->appendChild($node); - } - - /** - * rewind from Iterator - */ - public function rewind() { - $this->position = 0; - } - - /** - * current from Iterator - */ - public function current() { - return $this->items[$this->position]; - } - - /** - * key from Iterator - */ - public function key() { - return $this->position; - } - - /** - * next from Iterator - */ - public function next() { - ++$this->position; - } - - /** - * valid from Iterator - */ - public function valid() { - return isset($this->items[$this->position]); - } - - /** - * getter of 1st item - * @return SRSSItem - */ - public function getFirst() - { - return $this->getItem(1); - } - - /** - * getter of last item - * @return SRSSItem - */ - public function getLast() - { - $items = $this->getItems(); - return $items[count($items)-1]; - } - - /** - * getter of an item - * @param $i int - * @return SRSSItem - */ - public function getItem($i) - { - $i--; - return isset($this->items[$i]) ? $this->items[$i] : null; - } - - /** - * getter of all items - * @return SRSSItem[] - * @throws SRSSException - */ - public function getItems() - { - $channel = $this->_getChannel(); - $item = $channel->getElementsByTagName('item'); - $length = $item->length; - $this->items = array(); - if($length > 0){ - for($i = 0; $i < $length; $i++) - { - $this->items[$i] = new SRSSItem($item->item($i)); - } - } - return $this->items; - } - - /** - * display XML - * see DomDocument's docs - */ - public function show() - { - return $this->saveXml(); - } - - - /** - * putting all RSS attributes into the object - */ - private function _loadAttributes() - { - $channel = $this->_getChannel(); - foreach($channel->childNodes as $child) - { - if($child->nodeType == XML_ELEMENT_NODE && $child->nodeName != 'item') - { - if($child->nodeName == 'image'){ - foreach($child->childNodes as $children) - { - if($children->nodeType == XML_ELEMENT_NODE) - $this->attr['image'][$children->nodeName] = $children->nodeValue; - } - } - else - $this->attr[$child->nodeName] = $child->nodeValue; - } - } - } - - /** - * transform current object into an array - * @return array - */ - public function toArray() - { - $doc = array(); - foreach($this->attr as $attrName => $attrVal) - { - $doc[$attrName] = $attrVal; - } - foreach($this->getItems() as $item) - { - $doc['items'][] = $item->toArray(); - } - return $doc; - } -} - -/** - * @property null|string enclosure - * @property null|string description - */ -class SRSSItem extends DomDocument -{ - - /** - * @var DOMElement - */ - protected $node; // item node - protected $attr; // item's properties - - // possible properties' names - protected $possibilities = array( - 'title' => 'nohtml', - 'link' => 'link', - 'description' => 'html', - 'author' => 'email', - 'category' => 'nohtml', - 'comments' => 'link', - 'enclosure' => '', - 'guid' => 'nohtml', - 'pubDate' => 'date', - 'source' => 'link', - ); - - /** - * Constructor - * @param DomNode $node - */ - public function __construct($node = null) - { - parent::__construct(); - if($node instanceof DOMElement) $this->node = $this->importNode($node, true); - else $this->node = $this->importNode(new DomElement('item')); - $this->_loadAttributes(); - } - - /** - * putting all item attributes into the object - */ - private function _loadAttributes() - { - foreach($this->node->childNodes as $child) - { - if($child->nodeType == XML_ELEMENT_NODE && $child->nodeName != 'item') - { - $this->{$child->nodeName} = $child->nodeValue; - } - } - } - - /** - * getter of item DomElement - */ - public function getItem() - { - $this->appendChild($this->node); - return $this->getElementsByTagName('item')->item(0); - } - - /** - * setter for enclosure's properties - * @param $url string url - * @param $length int length - * @param $type string type - */ - public function setEnclosure($url, $length, $type) - { - $array = array(); - $url = SRSSTools::checkLink($url); - $array['url'] = $url; - $length = SRSSTools::checkInt($length); - $array['length'] = $length; - $type = SRSSTools::noHTML($type); - $array['type'] = $type; - if($this->enclosure == null) - { - $node = $this->createElement('enclosure'); - $node->setAttribute('url', $url); - $node->setAttribute('length', $length); - $node->setAttribute('type', $type); - $this->node->appendChild($node); - } - $this->attr['enclosure'] = $array; - } - - /** - * check if current item is valid (following specifications) - * @return bool - */ - public function isValid() - { - return $this->description != null ? true : false; - } - - /** - * main setter for properties - * @param $name - * @param $val - * @throws SRSSException - */ - public function __set($name, $val) - { - if(array_key_exists($name, $this->possibilities)) - { - $flag = $this->possibilities[$name]; - if($flag != '') - $val = SRSSTools::check($val, $flag); - if(!empty($val)){ - if($this->$name == null){ - $this->node->appendChild(new DomElement($name, $val)); - } - $this->attr[$name] = $val; - } - } - else - { - throw New SRSSException($name.' is not a possible item : '.print_r($this->possibilities)); - } - } - - /** - * main getter for properties - * @param $name - * @return null|string - * @throws SRSSException - */ - public function __get($name) - { - if(isset($this->attr[$name])) - return $this->attr[$name]; - if(array_key_exists($name, $this->possibilities)) - { - $tmp = $this->node->getElementsByTagName($name); - if($tmp->length != 1) return null; - return $tmp->item(0)->nodeValue; - } - else - { - throw New SRSSException($name.' is not a possible item : '.print_r($this->possibilities)); - } - } - - /** - * display item's XML - * see DomDocument's docs - */ - public function show() - { - return $this->saveXml(); - } - - /** - * transform current item's object into an array - * @return array - */ - public function toArray() - { - $infos = array(); - foreach($this->attr as $attrName => $attrVal) - { - $infos[$attrName] = $attrVal; - } - return $infos; - } -} - -class SRSSException extends Exception -{ - public function __construct($msg) - { - parent :: __construct($msg); - } - - public function getError() - { - $return = 'Une exception a été générée : Message : ' . $this->getMessage() . ' à la ligne : ' . $this->getLine(); - return $return; - } -} - -class SRSSTools -{ - public static function check($check, $flag) - { - switch($flag){ - case 'nohtml': - return self::noHTML($check); - break; - case 'link': - return self::checkLink($check); - break; - case 'html': - return self::HTML4XML($check); - break; - /*case 'lang': - return self::noHTML($check); - break;*/ - case 'date': - return self::getRSSDate($check); - break; - case 'email': - return self::checkEmail($check); - break; - case 'int': - return self::checkInt($check); - break; - case 'hour': - return self::checkHour($check); - break; - case 'day': - return self::checkDay($check); - break; - case '': - return $check; - break; - default: - throw new SRSSEXception('flag '.$flag.' does not exist.'); - } - } - - /** - * format the RSS to the wanted format - * @param $format string wanted format - * @param $date string RSS date - * @return string date - */ - public static function formatDate($format, $date) - { - return date($format, strtotime($date)); - } - - /** - * format a date for RSS format - * @param string $date date to format - * @param string $format - * @return string - */ - public static function getRSSDate($date, $format='') - { - $datepos = 'dDjlNSwzWFmMntLoYyaABgGhHisueIOPTZcrU'; - if($format != '' && preg_match('~^(['.$datepos.']{1})(-|/)(['.$datepos.']{1})(-|/)(['.$datepos.']{1})$~', $format, $match)){ - $sep = $match[2]; - $format = '%'.$match[1].$sep.'%'.$match[3].$sep.'%'.$match[5]; - if($dateArray = strptime($date, $format)){ - $mois = intval($dateArray['tm_mon']) + 1; - $annee = strlen($dateArray['tm_year']) > 2 ? '20'.substr($dateArray['tm_year'], -2) : '19'.$dateArray['tm_year']; - $date = $annee.'-'.$mois.'-'.$dateArray['tm_mday']; - return date("D, d M Y H:i:s T", strtotime($date)); - } - return ''; - } - else if(strtotime($date) !==false ){ - return date("D, d M Y H:i:s T", strtotime($date)); - } - else - { - list($j, $m, $a) = explode('/', $date); - return date("D, d M Y H:i:s T", strtotime($a.'-'.$m.'-'.$j)); - } - } - - /** - * check if it's an url - * @param $check string to check - * @return string|boolean the filtered data, or FALSE if the filter fails. - */ - public static function checkLink($check) - { - return filter_var($check, FILTER_VALIDATE_URL); - } - - /** - * make a string XML-compatible - * @param $check string to format - * @return string formatted string - * TODO CDATA ? - */ - public static function HTML4XML($check) - { - return htmlspecialchars($check); - } - - /** - * delete html tags - * @param $check string to format - * @return string formatted string - */ - public static function noHTML($check) - { - return strip_tags($check); - } - - /** - * check if it's a day (in RSS terms) - * @param $check string to check - * @return string the day, or empty string - */ - public static function checkDay($check) - { - $possibleDay = array('monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'); - return in_array(strtolower($check), $possibleDay) ? $check : ''; - } - - /** - * check if it's an email - * @param $check string to check - * @return string|boolean the filtered data, or FALSE if the filter fails. - */ - public static function checkEmail($check) - { - return filter_var($check, FILTER_VALIDATE_EMAIL); - } - - /** - * check if it's an hour (in RSS terms) - * @param $check string to check - * @return string|boolean the filtered data, or FALSE if the filter fails. - */ - public static function checkHour($check) - { - $options = array( - 'options' => array( - 'default' => 0, - 'min_range' => 0, - 'max_range' => 23 - ) - ); - return filter_var($check, FILTER_VALIDATE_INT, $options); - } - - /** - * check if it's an int - * @param $check int to check - * @return int|boolean the filtered data, or FALSE if the filter fails. - */ - public static function checkInt($check) - { - return filter_var($check, FILTER_VALIDATE_INT); - } -} \ No newline at end of file diff --git a/tests/BasicReader.php b/tests/BasicReader.php new file mode 100644 index 0000000..36d9468 --- /dev/null +++ b/tests/BasicReader.php @@ -0,0 +1,22 @@ +title); + $first_item = $rss->getFirst(); + self::assertEquals('RSS Tutorial', $first_item->title); + } + + public function testRssNotFound() + { + $this->expectException(SRSSException::class); + $rss = SRSS::read('not_found.xml'); + } +} \ No newline at end of file diff --git a/tests/MediaTest.php b/tests/MediaTest.php new file mode 100644 index 0000000..fec2729 --- /dev/null +++ b/tests/MediaTest.php @@ -0,0 +1,18 @@ +title); + + $first_item = $rss->getFirst(); + self::assertEquals('Kirstie Alley, \'Cheers\' and \'Veronica\'s Closet\' star, dead at 71', $first_item->title); + + self::assertEquals('https://cdn.cnn.com/cnnnext/dam/assets/221205172141-kirstie-alley-2005-super-169.jpg', $first_item->{'media:group'}->{'media:content'}->url); + } +} \ No newline at end of file diff --git a/tests/resources/basic.xml b/tests/resources/basic.xml new file mode 100644 index 0000000..11dd2f7 --- /dev/null +++ b/tests/resources/basic.xml @@ -0,0 +1,20 @@ + + + + + test Home Page + https://www.test.com + Free web building tutorials + + RSS Tutorial + https://www.test.com/xml/xml_rss.asp + New RSS tutorial on test + + + XML Tutorial + https://www.test.com/xml + New XML tutorial on test + + + + \ No newline at end of file diff --git a/tests/resources/media/cnn.xml b/tests/resources/media/cnn.xml new file mode 100644 index 0000000..f0ad899 --- /dev/null +++ b/tests/resources/media/cnn.xml @@ -0,0 +1,1383 @@ + + + + <![CDATA[CNN.com - RSS Channel - Entertainment]]> + + + https://www.cnn.com/entertainment/index.html + + http://i2.cdn.turner.com/cnn/2015/images/09/24/cnn.digital.png + CNN.com - RSS Channel - Entertainment + https://www.cnn.com/entertainment/index.html + + coredev-bumblebee + Wed, 05 Apr 2023 08:57:36 GMT + Tue, 06 Dec 2022 20:13:31 GMT + + + 10 + + <![CDATA[Kirstie Alley, 'Cheers' and 'Veronica's Closet' star, dead at 71]]> + + + https://www.cnn.com/2022/12/05/entertainment/kirstie-alley-obit/index.html + https://www.cnn.com/2022/12/05/entertainment/kirstie-alley-obit/index.html + Tue, 06 Dec 2022 12:59:28 GMT + + + + + + + + + + + + + + + + <![CDATA[John Travolta and Kirstie Alley: A love story]]> + + + https://www.cnn.com/2022/12/06/entertainment/john-travolta-kirstie-alley/index.html + + https://www.cnn.com/2022/12/06/entertainment/john-travolta-kirstie-alley/index.html + + Tue, 06 Dec 2022 14:22:24 GMT + + + + + + + + + + + + + + + + + <![CDATA[Chelsea Handler, Leslie Jones and John Leguizamo among guest hosts to step in for Trevor Noah on 'The Daily Show']]> + + + https://www.cnn.com/2022/12/06/entertainment/chelsea-handler-leslie-jones-daily-show/index.html + + https://www.cnn.com/2022/12/06/entertainment/chelsea-handler-leslie-jones-daily-show/index.html + + Tue, 06 Dec 2022 20:30:42 GMT + + + + + + + + + + + + + + + + <![CDATA[Ashton Kutcher and twin Michael talk health, guilt and rift between them]]> + + + https://www.cnn.com/2022/12/06/entertainment/ashton-kutcher-michael/index.html + https://www.cnn.com/2022/12/06/entertainment/ashton-kutcher-michael/index.html + + Tue, 06 Dec 2022 15:42:43 GMT + + + + + + + + + + + + + + + + <![CDATA[Bong Joon Ho's 'Mickey 17' gets trailer and release date]]> + + + https://www.cnn.com/2022/12/06/entertainment/bong-joon-ho-mickey-17-trailer-new-movie/index.html + + + https://www.cnn.com/2022/12/06/entertainment/bong-joon-ho-mickey-17-trailer-new-movie/index.html + + Tue, 06 Dec 2022 17:01:45 GMT + + + + + + + + + + + + + + + + <![CDATA[K-pop band Blackpink selected as Time Entertainer of the Year 2022]]> + + + + https://www.cnn.com/2022/12/06/entertainment/blackpink-time-entertainer-of-the-year-2022-intl-scli/index.html + + + https://www.cnn.com/2022/12/06/entertainment/blackpink-time-entertainer-of-the-year-2022-intl-scli/index.html + + Tue, 06 Dec 2022 11:18:19 GMT + + + + + + + + + + + + + + + + <![CDATA[Gabourey Sidibe reveals she's been secretly married for over a year]]> + + + https://www.cnn.com/2022/12/05/entertainment/gabourey-sidibe-married-brandon-frankel/index.html + + https://www.cnn.com/2022/12/05/entertainment/gabourey-sidibe-married-brandon-frankel/index.html + + Mon, 05 Dec 2022 21:53:05 GMT + + + + + + + + + + + + + + + + <![CDATA['Harry & Meghan' series gets release date and new trailer ]]> + + + https://www.cnn.com/2022/12/05/entertainment/harry-meghan-netflix-date/index.html + https://www.cnn.com/2022/12/05/entertainment/harry-meghan-netflix-date/index.html + + Mon, 05 Dec 2022 14:13:14 GMT + + + + + + + + + + + + + + + + + <![CDATA[Jill Scott announces 'Who is Jill Scott? Words & Sounds Vol. 1' 23rd anniversary tour]]> + + + https://www.cnn.com/2022/12/05/entertainment/jill-scott-tour-music/index.html + https://www.cnn.com/2022/12/05/entertainment/jill-scott-tour-music/index.html + + Mon, 05 Dec 2022 18:20:24 GMT + + + + + + + + + + + + + + + + <![CDATA[Adam Sandler still gets emotional singing sweet Chris Farley song]]> + + https://www.cnn.com/2022/12/05/entertainment/adam-sandler-chris-farley-song-snl/index.html + + https://www.cnn.com/2022/12/05/entertainment/adam-sandler-chris-farley-song-snl/index.html + + Mon, 05 Dec 2022 15:04:40 GMT + + + + + + + + + + + + + + + + + <![CDATA[Neil Diamond surprises audience with 'Sweet Caroline' performance at Broadway opening of 'A Beautiful Noise']]> + + + https://www.cnn.com/2022/12/05/entertainment/neil-diamond-sweet-caroline-broadway-sing/index.html + + + https://www.cnn.com/2022/12/05/entertainment/neil-diamond-sweet-caroline-broadway-sing/index.html + + Tue, 06 Dec 2022 13:41:03 GMT + + + + + + + + + + + + + + + + <![CDATA[George Clooney has a simple strategy for being a star in the age of social media]]> + + + https://www.cnn.com/2022/12/04/entertainment/george-clooney-star-social-media/index.html + + https://www.cnn.com/2022/12/04/entertainment/george-clooney-star-social-media/index.html + + Sun, 04 Dec 2022 23:42:59 GMT + + + + + + + + + + + + + + + + + <![CDATA[Ben Affleck says Netflix's 'assembly line' approach to making quality films is 'an impossible job']]> + + + https://www.cnn.com/2022/12/04/entertainment/ben-affleck-netflix-assembly-line/index.html + + https://www.cnn.com/2022/12/04/entertainment/ben-affleck-netflix-assembly-line/index.html + + Mon, 05 Dec 2022 02:21:54 GMT + + + + + + + + + + + + + + + + + <![CDATA[Keke Palmer reveals baby bump as part of her 'Saturday Night Live' opening monologue]]> + + + https://www.cnn.com/2022/12/04/entertainment/keke-palmer-baby-bump-snl-monologue/index.html + + https://www.cnn.com/2022/12/04/entertainment/keke-palmer-baby-bump-snl-monologue/index.html + + Sun, 04 Dec 2022 18:33:52 GMT + + + + + + + + + + + + + + + + <![CDATA[Christine McVie's music: 5 songs to listen to in her honor]]> + + + https://www.cnn.com/2022/12/01/entertainment/christine-mcvie-music/index.html + https://www.cnn.com/2022/12/01/entertainment/christine-mcvie-music/index.html + + Thu, 01 Dec 2022 19:08:38 GMT + + + + + + + + + + + + + + + + <![CDATA[Axl Rose will stop tossing mic after a fan was reportedly injured ]]> + + https://www.cnn.com/2022/12/05/entertainment/axl-rose-microphone/index.html + https://www.cnn.com/2022/12/05/entertainment/axl-rose-microphone/index.html + Mon, 05 Dec 2022 14:15:44 GMT + + + + + + + + + + + + + + + + + <![CDATA[Harrison Ford cracks the whip in teaser trailer for 'Indiana Jones and the Dial of Destiny']]> + + + https://www.cnn.com/2022/12/01/entertainment/indiana-jones-dial-of-destiny-teaser-trailer/index.html + + + https://www.cnn.com/2022/12/01/entertainment/indiana-jones-dial-of-destiny-teaser-trailer/index.html + + Fri, 02 Dec 2022 03:04:00 GMT + + + + + + + + + + + + + + + + + <![CDATA['Willow' revives the Lucasfilm fantasy with a more contemporary streaming adventure]]> + + + https://www.cnn.com/2022/11/30/entertainment/willow-review/index.html + https://www.cnn.com/2022/11/30/entertainment/willow-review/index.html + Wed, 30 Nov 2022 17:23:24 GMT + + + + + + + + + + + + + + + + <![CDATA['Love Actually' director feels 'a bit stupid' about movie's lack of diversity]]> + + + + https://www.cnn.com/2022/12/01/entertainment/love-actually-richard-curtis-diversity-scli-intl/index.html + + + https://www.cnn.com/2022/12/01/entertainment/love-actually-richard-curtis-diversity-scli-intl/index.html + + Thu, 01 Dec 2022 14:24:40 GMT + + + + + + + + + + + + + + + + + <![CDATA[Joe Pesci says playing Harry in the 'Home Alone' films came with some 'serious' pain]]> + + + https://www.cnn.com/2022/11/30/entertainment/joe-pesci-burns-home-alone-2/index.html + + https://www.cnn.com/2022/11/30/entertainment/joe-pesci-burns-home-alone-2/index.html + + Thu, 01 Dec 2022 01:02:12 GMT + + + + + + + + + + + + + + + + <![CDATA[Inside Christine McVie's and Stevie Nicks' decades-long friendship]]> + + + https://www.cnn.com/2022/12/01/entertainment/stevie-nicks-christine-mcvie-friendship-cec/index.html + + + https://www.cnn.com/2022/12/01/entertainment/stevie-nicks-christine-mcvie-friendship-cec/index.html + + Thu, 01 Dec 2022 17:54:53 GMT + + + + + + + + + + + + + + + + + <![CDATA[Will Smith, opening up about Oscars slap, tells Trevor Noah 'hurt people hurt people']]> + + + https://www.cnn.com/2022/11/29/entertainment/will-smith-daily-show/index.html + https://www.cnn.com/2022/11/29/entertainment/will-smith-daily-show/index.html + + Tue, 29 Nov 2022 17:02:36 GMT + + + + + + + + + + + + + + + + <![CDATA['Super Mario Bros. Movie' trailer shows being a hero isn't all fun and games]]> + + + https://www.cnn.com/2022/11/30/entertainment/super-mario-bros-movie-trailer-two/index.html + + https://www.cnn.com/2022/11/30/entertainment/super-mario-bros-movie-trailer-two/index.html + + Wed, 30 Nov 2022 17:10:09 GMT + + + + + + + + + + + + + + + + + <![CDATA['The Phantom of the Opera' extends Broadway run for eight weeks due to high demand]]> + + + + https://www.cnn.com/2022/11/29/entertainment/the-phantom-of-the-opera-broadway-longer-extension/index.html + + + https://www.cnn.com/2022/11/29/entertainment/the-phantom-of-the-opera-broadway-longer-extension/index.html + + Tue, 29 Nov 2022 18:40:18 GMT + + + + + + + + + + + + + + + + <![CDATA[Kim Kardashian and Kanye West reach divorce settlement]]> + + + https://www.cnn.com/2022/11/29/entertainment/kim-kardashian-kanye-west-divorce/index.html + + https://www.cnn.com/2022/11/29/entertainment/kim-kardashian-kanye-west-divorce/index.html + + Wed, 30 Nov 2022 15:56:36 GMT + + + + + + + + + + + + + + + + <![CDATA[Marsai Martin and Omari Harwick play to win in 'Fantasy Football']]> + + + + https://www.cnn.com/2022/11/23/entertainment/marsai-martin-omari-hardwick-fantasy-football/index.html + + + https://www.cnn.com/2022/11/23/entertainment/marsai-martin-omari-hardwick-fantasy-football/index.html + + Wed, 23 Nov 2022 14:10:45 GMT + + + + + + + + + + + + + + + + + <![CDATA[In 'Wednesday,' Jenna Ortega makes Netflix's Addams Family series look like a snap]]> + + + https://www.cnn.com/2022/11/22/entertainment/wednesday-review/index.html + https://www.cnn.com/2022/11/22/entertainment/wednesday-review/index.html + Tue, 22 Nov 2022 14:45:05 GMT + + + + + + + + + + + + + + + + <![CDATA['Glass Onion' sharpens the 'Knives Out' formula in a polished Netflix sequel]]> + + + https://www.cnn.com/2022/11/23/entertainment/glass-onion-a-knives-out-mystery-review/index.html + + https://www.cnn.com/2022/11/23/entertainment/glass-onion-a-knives-out-mystery-review/index.html + + Wed, 23 Nov 2022 14:00:36 GMT + + + + + + + + + + + + + + + + <![CDATA['Love Actually' cast to reunite for 20th anniversary TV special]]> + + + https://www.cnn.com/2022/11/22/entertainment/love-actually-20th-anniversary-abc-special/index.html + + + https://www.cnn.com/2022/11/22/entertainment/love-actually-20th-anniversary-abc-special/index.html + + Wed, 23 Nov 2022 23:02:35 GMT + + + + + + + + + + + + + + + + + <![CDATA[James Cameron almost didn't choose Leonardo DiCaprio or Kate Winslet to star in 'Titanic']]> + + + + https://www.cnn.com/2022/11/22/entertainment/james-cameron-leonardo-dicaprio-kate-winslet-titanic/index.html + + + https://www.cnn.com/2022/11/22/entertainment/james-cameron-leonardo-dicaprio-kate-winslet-titanic/index.html + + Wed, 23 Nov 2022 15:24:00 GMT + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/resources/media/music-video.xml b/tests/resources/media/music-video.xml new file mode 100644 index 0000000..9563047 --- /dev/null +++ b/tests/resources/media/music-video.xml @@ -0,0 +1,32 @@ + + + + Music Videos 101 + http://www.foo.com + Discussions of great videos + + The latest video from an artist + http://www.foo.com/item1.htm + + + dfdec888b72151965a34b4b59031290a + producer's name + artist's name + + music/artistname/album/song + + + Oh, say, can you see, by the dawn's early light + + nonadult + + start=2002-10-13T09:00+01:00; + end=2002-10-17T17:00+01:00; + scheme=W3C-DTF + + + + + \ No newline at end of file diff --git a/tests/resources/media/trailer.xml b/tests/resources/media/trailer.xml new file mode 100644 index 0000000..91dd506 --- /dev/null +++ b/tests/resources/media/trailer.xml @@ -0,0 +1,19 @@ + + + + My Movie Review Site + http://www.foo.com + I review movies. + + Movie Title: Is this a good movie? + http://www.foo.com/item1.htm + + + http://www.creativecommons.org/licenses/by-nc/1.0 + + nonadult + + + \ No newline at end of file From 4783124a1db2246901f031623d68f6df54760127 Mon Sep 17 00:00:00 2001 From: Shikiryu Date: Thu, 6 Apr 2023 00:38:26 +0200 Subject: [PATCH 02/16] :sparkles: Starts refactoring reader Still missing media:group --- src/Entity/Channel.php | 31 ++++++ src/Entity/Channel/Image.php | 15 +++ src/Entity/Item.php | 19 ++++ src/{ => Entity}/Media/Content.php | 23 ++-- src/Entity/Media/Group.php | 8 ++ src/Media/Group.php | 8 -- src/SRSS.php | 165 +++++++++++++++++------------ src/SRSSBuilder.php | 8 ++ src/SRSSItem.php | 78 ++++++++------ tests/BasicReader.php | 1 + tests/MediaTest.php | 2 +- tests/resources/harvard.xml | 41 +++++++ 12 files changed, 288 insertions(+), 111 deletions(-) create mode 100644 src/Entity/Channel.php create mode 100644 src/Entity/Channel/Image.php create mode 100644 src/Entity/Item.php rename src/{ => Entity}/Media/Content.php (84%) create mode 100644 src/Entity/Media/Group.php delete mode 100644 src/Media/Group.php create mode 100644 src/SRSSBuilder.php create mode 100644 tests/resources/harvard.xml diff --git a/src/Entity/Channel.php b/src/Entity/Channel.php new file mode 100644 index 0000000..176237e --- /dev/null +++ b/src/Entity/Channel.php @@ -0,0 +1,31 @@ + 'link', @@ -27,6 +28,8 @@ class Content extends DOMElement ]; private array $attr = []; + private DOMNode $node; + /** * Constructor * @@ -34,16 +37,21 @@ class Content extends DOMElement */ public function __construct(?\DOMNode $node = null) { - parent::__construct('media:content'); + parent::__construct(); + if ($node instanceof DOMElement) { + $this->node = $this->importNode($node, true); + } else { + $this->node = $this->importNode(new DomElement('item')); + } $this->_loadAttributes(); } /** * @return void */ - private function _loadAttributes() + private function _loadAttributes(): void { - foreach ($this->attributes as $attributes) { + foreach ($this->node->attributes as $attributes) { if (array_key_exists($attributes->name, $this->possibilities)) { $this->{$attributes->name} = $attributes->value; } @@ -102,8 +110,11 @@ class Content extends DOMElement } $flag = $this->possibilities[$name]; - if ($flag !== '') + + if ($flag !== '') { $val = SRSSTools::check($val, $flag); + } + if (!empty($val)) { if ($this->$name === null) { $this->node->appendChild(new DomElement($name, $val)); diff --git a/src/Entity/Media/Group.php b/src/Entity/Media/Group.php new file mode 100644 index 0000000..64ca71c --- /dev/null +++ b/src/Entity/Media/Group.php @@ -0,0 +1,8 @@ + 'nohtml', @@ -45,8 +52,8 @@ class SRSS extends DomDocument implements Iterator libxml_use_internal_errors(true); parent::__construct(); $this->xpath = new DOMXpath($this); - $this->attr = array(); - $this->items = array(); + $this->attr = []; + $this->items = []; $this->position = 0; $this->formatOutput = true; $this->preserveWhiteSpace = false; @@ -65,19 +72,19 @@ class SRSS extends DomDocument implements Iterator } /** - * @param $link string url of the rss + * @param string $link url of the rss * @throws SRSSException * @return SRSS */ - public static function read($link): SRSS + public static function read(string $link): SRSS { $doc = new SRSS; - if(@$doc->load($link)) // We don't want the warning in case of bad XML. Let's manage it with an exception. - { + if(@$doc->load($link)) { // We don't want the warning in case of bad XML. Let's manage it with an exception. $channel = $doc->getElementsByTagName('channel'); if($channel->length == 1){ // Good URL and good RSS $doc->_loadAttributes(); // loading channel properties $doc->getItems(); // loading all items + return $doc; } @@ -89,6 +96,7 @@ class SRSS extends DomDocument implements Iterator /** * @return SRSS + * @throws \DOMException */ public static function create() { @@ -101,25 +109,31 @@ class SRSS extends DomDocument implements Iterator $doc->encoding = "UTF-8"; $doc->generator = 'Shikiryu RSS'; // $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html'; + $doc->channel = new Channel(); + $doc->items = []; + return $doc; } /** * getter of "image"'s channel attributes * @return string|array + * TODO */ public function image() { $args = func_get_args(); - if(func_num_args() == 0) $args[0] = 'url'; + if (func_num_args() == 0) { + $args[0] = 'url'; + } $img = $this->xpath->query('//channel/image'); - if($img->length != 1) return null; // is not in channel + if($img->length != 1) { // is not in channel + return null; + } $img = $img->item(0); - $r = array(); - foreach($img->childNodes as $child) - { - if($child->nodeType == XML_ELEMENT_NODE && in_array($child->nodeName, $args)) - { + $r = []; + foreach($img->childNodes as $child) { + if($child->nodeType == XML_ELEMENT_NODE && in_array($child->nodeName, $args)) { $r[$child->nodeName] = $child->nodeValue; } } @@ -134,6 +148,7 @@ class SRSS extends DomDocument implements Iterator * @param $width int width * @param $height int height * @param $description string description + * TODO */ public function setImage($url, $title, $link, $width = 0, $height = 0, $description = '') { @@ -196,6 +211,7 @@ class SRSS extends DomDocument implements Iterator * @param $path string path * @param $registerProcedure string register procedure * @param $protocol string protocol + * TODO */ public function setCloud($domain, $port, $path, $registerProcedure, $protocol) { @@ -227,12 +243,13 @@ class SRSS extends DomDocument implements Iterator /** * check if current RSS is a valid one (based on specifications) * @return bool + * TODO use required */ public function isValid() { $valid = true; $items = $this->getItems(); - $invalidItems = array(); + $invalidItems = []; $i = 1; foreach($items as $item){ if($item->isValid() === false){ @@ -246,13 +263,16 @@ class SRSS extends DomDocument implements Iterator /** * getter of current RSS channel - * @return DOMElement + * @return \DOMNode * @throws SRSSException */ - private function _getChannel() + private function _getChannel(): \DOMNode { $channel = $this->getElementsByTagName('channel'); - if($channel->length != 1) throw new SRSSException('channel node not created, or too many channel nodes'); + if($channel->length != 1) { + throw new SRSSException('channel node not created, or too many channel nodes'); + } + return $channel->item(0); } @@ -298,16 +318,19 @@ class SRSS extends DomDocument implements Iterator */ public function __get($name) { - if(isset($this->attr[$name])) - return $this->attr[$name]; + if (isset($this->channel->{$name})) { + return $this->channel->{$name}; + } // $channel = $this->_getChannel(); if(array_key_exists($name, $this->possibleAttr)){ $tmp = $this->xpath->query('//channel/'.$name); - if($tmp->length != 1) return null; + if($tmp->length != 1) { + return null; + } return $tmp->item(0)->nodeValue; - }else{ - throw new SRSSException($name.' is not a possible value.'); } + + throw new SRSSException($name.' is not a possible value.'); } /** @@ -357,71 +380,80 @@ class SRSS extends DomDocument implements Iterator /** * key from Iterator */ - public function key() { + #[ReturnTypeWillChange] public function key(): int + { return $this->position; } /** * next from Iterator */ - public function next() { + #[ReturnTypeWillChange] public function next(): void + { ++$this->position; } /** * valid from Iterator */ - public function valid() { + #[ReturnTypeWillChange] public function valid(): bool + { return isset($this->items[$this->position]); } /** * getter of 1st item - * @return SRSSItem + * @return Item */ - public function getFirst() + public function getFirst(): ?Item { return $this->getItem(1); } /** * getter of last item - * @return SRSSItem + * @return Item */ - public function getLast() + public function getLast(): Item { $items = $this->getItems(); - return $items[count($items)-1]; + return $items[array_key_last($items)]; } /** * getter of an item * @param $i int - * @return SRSSItem + * + * @return Item|null */ - public function getItem($i) + public function getItem(int $i): ?Item { $i--; - return isset($this->items[$i]) ? $this->items[$i] : null; + return $this->items[$i] ?? null; } /** * getter of all items - * @return SRSSItem[] + * @return Item[] * @throws SRSSException */ - public function getItems() + public function getItems(): array { + if (!empty($this->items)) { + return $this->items; + } + $channel = $this->_getChannel(); - $item = $channel->getElementsByTagName('item'); - $length = $item->length; + /** @var DOMNodeList $items */ + $items = $channel->getElementsByTagName('item'); + $length = $items->length; $this->items = []; - if($length > 0){ - for($i = 0; $i < $length; $i++) - { - $this->items[$i] = new SRSSItem($item->item($i)); + if ($length > 0) { + for($i = 0; $i < $length; $i++) { + $this->items[$i] = SRSSItem::read($items->item($i)); } } + return $this->items; } @@ -429,31 +461,36 @@ class SRSS extends DomDocument implements Iterator * display XML * see DomDocument's docs */ - public function show() + public function show(): bool|string { + // TODO build return $this->saveXml(); } /** * putting all RSS attributes into the object + * @throws SRSSException */ - private function _loadAttributes() + private function _loadAttributes(): void { - $channel = $this->_getChannel(); - foreach($channel->childNodes as $child) - { - if($child->nodeType == XML_ELEMENT_NODE && $child->nodeName != 'item') - { - if($child->nodeName == 'image'){ - foreach($child->childNodes as $children) - { - if($children->nodeType == XML_ELEMENT_NODE) - $this->attr['image'][$children->nodeName] = $children->nodeValue; + $node_channel = $this->_getChannel(); + $this->channel = new Channel(); + + foreach($node_channel->childNodes as $child) { + if($child->nodeType == XML_ELEMENT_NODE && $child->nodeName !== 'item') { + if($child->nodeName == 'image') { + $image = new Image(); + foreach($child->childNodes as $children) { + if($children->nodeType == XML_ELEMENT_NODE) { + $image->{$child->nodeName} = $children->nodeValue; + } } + $this->channel->image = $image; + + } else { + $this->channel->{$child->nodeName} = $child->nodeValue; } - else - $this->attr[$child->nodeName] = $child->nodeValue; } } } @@ -461,18 +498,16 @@ class SRSS extends DomDocument implements Iterator /** * transform current object into an array * @return array + * @throws SRSSException */ - public function toArray() + public function toArray(): array { - $doc = array(); - foreach($this->attr as $attrName => $attrVal) - { - $doc[$attrName] = $attrVal; - } - foreach($this->getItems() as $item) - { + $doc = $this->channel->toArray(); + + foreach($this->getItems() as $item) { $doc['items'][] = $item->toArray(); } + return $doc; } } \ No newline at end of file diff --git a/src/SRSSBuilder.php b/src/SRSSBuilder.php new file mode 100644 index 0000000..6612563 --- /dev/null +++ b/src/SRSSBuilder.php @@ -0,0 +1,8 @@ + 'nohtml', 'link' => 'link', 'description' => 'html', @@ -38,44 +42,53 @@ class SRSSItem extends DomDocument public function __construct($node = null) { parent::__construct(); - if ($node instanceof DOMElement) $this->node = $this->importNode($node, true); - else $this->node = $this->importNode(new DomElement('item')); - $this->_loadAttributes(); - } - - /** - * putting all item attributes into the object - */ - private function _loadAttributes(): void - { - $this->_loadChildAttributes($this->node->childNodes); +// if ($node instanceof DOMElement) $this->node = $this->importNode($node, true); +// else $this->node = $this->importNode(new DomElement('item')); +// $this->_loadAttributes(); } /** + * @param Item $item * @param $nodes * * @return void */ - private function _loadChildAttributes($nodes): void + private static function _loadChildAttributes(Item $item, $nodes): void { - foreach ($nodes as $child) { + foreach ($nodes->childNodes as $child) { if ($child->nodeType === XML_ELEMENT_NODE && $child->nodeName !== 'item') { - if (array_key_exists($child->nodeName, $this->possibilities) && $this->possibilities[$child->nodeName] === 'folder') { - $this->_loadChildAttributes($child->childNodes); - } - if ($child->nodeName === 'media:content') { - $this->{$child->nodeName} = new Content($child); + if (array_key_exists($child->nodeName, self::$possibilities) && self::$possibilities[$child->nodeName] === 'folder') { + self::_loadChildAttributes($item, $child); + } elseif ($child->nodeName === 'media:group') { + // TODO + } elseif ($child->nodeName === 'media:content') { + $item->{$child->nodeName} = new Content($child); } else { - $this->{$child->nodeName} = $child->nodeValue; + $item->{$child->nodeName} = $child->nodeValue; } } } } + /** + * @param DOMNode|null $node + * + * @return Item + */ + public static function read(?DOMNode $node = null): Item + { + $item = new Item(); + if ($node instanceof DOMNode) { + self::_loadChildAttributes($item, $node); + } + + return $item; + } + /** * getter of item DomElement */ - public function getItem() + public function getItem(): ?\DOMNode { $this->appendChild($this->node); @@ -88,8 +101,9 @@ class SRSSItem extends DomDocument * @param $url string url * @param $length int length * @param $type string type + * @throws DOMException */ - public function setEnclosure($url, $length, $type) + public function setEnclosure(string $url, int $length, string $type): void { $array = []; $url = SRSSTools::checkLink($url); @@ -112,9 +126,9 @@ class SRSSItem extends DomDocument * check if current item is valid (following specifications) * @return bool */ - public function isValid() + public function isValid(): bool { - return $this->description != null ? true : false; + return $this->description != null; } /** @@ -133,7 +147,7 @@ class SRSSItem extends DomDocument * @param $name * @param $val * - * @throws SRSSException + * @throws SRSSException|DOMException */ public function __set($name, $val) { @@ -145,7 +159,9 @@ class SRSSItem extends DomDocument if ($flag !== '') $val = SRSSTools::check($val, $flag); if (!empty($val)) { - if ($this->$name == null) { + if ($val instanceof DOMElement) { + $this->node->appendChild($val); + } elseif ($this->$name == null) { $this->node->appendChild(new DomElement($name, $val)); } $this->attr[$name] = $val; diff --git a/tests/BasicReader.php b/tests/BasicReader.php index 36d9468..400eabb 100644 --- a/tests/BasicReader.php +++ b/tests/BasicReader.php @@ -11,6 +11,7 @@ class BasicReader extends TestCase $rss = SRSS::read(__DIR__.'/resources/basic.xml'); self::assertEquals('test Home Page', $rss->title); $first_item = $rss->getFirst(); + self::assertNotNull($first_item); self::assertEquals('RSS Tutorial', $first_item->title); } diff --git a/tests/MediaTest.php b/tests/MediaTest.php index fec2729..655f04f 100644 --- a/tests/MediaTest.php +++ b/tests/MediaTest.php @@ -13,6 +13,6 @@ class MediaTest extends TestCase $first_item = $rss->getFirst(); self::assertEquals('Kirstie Alley, \'Cheers\' and \'Veronica\'s Closet\' star, dead at 71', $first_item->title); - self::assertEquals('https://cdn.cnn.com/cnnnext/dam/assets/221205172141-kirstie-alley-2005-super-169.jpg', $first_item->{'media:group'}->{'media:content'}->url); + self::assertEquals('https://cdn.cnn.com/cnnnext/dam/assets/221205172141-kirstie-alley-2005-super-169.jpg', $first_item->{'media:content'}->url); } } \ No newline at end of file diff --git a/tests/resources/harvard.xml b/tests/resources/harvard.xml new file mode 100644 index 0000000..48dcb54 --- /dev/null +++ b/tests/resources/harvard.xml @@ -0,0 +1,41 @@ + + + + Liftoff News + http://liftoff.msfc.nasa.gov/ + Liftoff to Space Exploration. + en-us + Tue, 10 Jun 2003 04:00:00 GMT + Tue, 10 Jun 2003 09:41:01 GMT + http://blogs.law.harvard.edu/tech/rss + Weblog Editor 2.0 + editor@example.com + webmaster@example.com + + Star City + http://liftoff.msfc.nasa.gov/news/2003/news-starcity.asp + How do Americans get ready to work with Russians aboard the International Space Station? They take a crash course in culture, language and protocol at Russia's <a href="http://howe.iki.rssi.ru/GCTC/gctc_e.htm">Star City</a>. + Tue, 03 Jun 2003 09:39:21 GMT + http://liftoff.msfc.nasa.gov/2003/06/03.html#item573 + + + Sky watchers in Europe, Asia, and parts of Alaska and Canada will experience a <a href="http://science.nasa.gov/headlines/y2003/30may_solareclipse.htm">partial eclipse of the Sun</a> on Saturday, May 31st. + Fri, 30 May 2003 11:06:42 GMT + http://liftoff.msfc.nasa.gov/2003/05/30.html#item572 + + + The Engine That Does More + http://liftoff.msfc.nasa.gov/news/2003/news-VASIMR.asp + Before man travels to Mars, NASA hopes to design new engines that will let us fly through the Solar System more quickly. The proposed VASIMR engine would do that. + Tue, 27 May 2003 08:37:32 GMT + http://liftoff.msfc.nasa.gov/2003/05/27.html#item571 + + + Astronauts' Dirty Laundry + http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp + Compared to earlier spacecraft, the International Space Station has many luxuries, but laundry facilities are not one of them. Instead, astronauts have other options. + Tue, 20 May 2003 08:56:02 GMT + http://liftoff.msfc.nasa.gov/2003/05/20.html#item570 + + + \ No newline at end of file From bc0e818bbced742610247c9d3a47ab5999500bbb Mon Sep 17 00:00:00 2001 From: Clement Desmidt Date: Thu, 6 Apr 2023 11:07:06 +0200 Subject: [PATCH 03/16] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactores=20parser?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For #4 --- composer.json | 3 +- src/Entity/Channel.php | 18 +- src/Entity/Item.php | 17 +- src/Entity/Media/Content.php | 1 - src/Entity/SRSSElement.php | 16 ++ src/SRSS.php | 380 +++++------------------------------ src/SRSSBuilder.php | 158 ++++++++++++++- src/SRSSItem.php | 4 +- src/SRSSParser.php | 146 ++++++++++++++ tests/BasicReader.php | 19 ++ tests/MediaTest.php | 13 +- 11 files changed, 438 insertions(+), 337 deletions(-) create mode 100644 src/Entity/SRSSElement.php create mode 100644 src/SRSSParser.php diff --git a/composer.json b/composer.json index c49cd62..48d4820 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ "phpunit/phpunit": "^9" }, "require": { - "ext-dom": "*" + "ext-dom": "*", + "ext-libxml": "*" } } diff --git a/src/Entity/Channel.php b/src/Entity/Channel.php index 176237e..c0bae45 100644 --- a/src/Entity/Channel.php +++ b/src/Entity/Channel.php @@ -4,7 +4,7 @@ namespace Shikiryu\SRSS\Entity; use Shikiryu\SRSS\Entity\Channel\Image; -class Channel +class Channel implements SRSSElement { public string $title; public string $link; @@ -28,4 +28,20 @@ class Channel public ?string $skipDays; public array $required = ['title', 'link', 'description']; + + /** + * @return bool + */ + public function isValid(): bool + { + return count(array_filter($this->required, fn($field) => !empty($this->{$field}))) === 0; + } + + /** + * @return array + */ + public function toArray(): array + { + return get_object_vars($this); + } } \ No newline at end of file diff --git a/src/Entity/Item.php b/src/Entity/Item.php index a39ecc4..e3cd66b 100644 --- a/src/Entity/Item.php +++ b/src/Entity/Item.php @@ -2,7 +2,7 @@ namespace Shikiryu\SRSS\Entity; -class Item +class Item implements SRSSElement { public ?string $title; public ?string $link; @@ -15,5 +15,20 @@ class Item public ?string $pubDate; public ?string $source; + /** + * @var \Shikiryu\SRSS\Entity\Media\Content[] + */ + public array $medias = []; + public array $required = ['description']; + + public function isValid(): bool + { + return count(array_filter($this->required, fn($field) => !empty($this->{$field}))) === 0; + } + + public function toArray(): array + { + return get_object_vars($this); + } } \ No newline at end of file diff --git a/src/Entity/Media/Content.php b/src/Entity/Media/Content.php index 05a045f..1e09671 100644 --- a/src/Entity/Media/Content.php +++ b/src/Entity/Media/Content.php @@ -58,7 +58,6 @@ class Content extends DomDocument } } - /** * main getter for properties * diff --git a/src/Entity/SRSSElement.php b/src/Entity/SRSSElement.php new file mode 100644 index 0000000..607d3b3 --- /dev/null +++ b/src/Entity/SRSSElement.php @@ -0,0 +1,16 @@ + 'nohtml', - 'link' => 'link', - 'description' => 'html', - 'language' => '', + 'title' => 'nohtml', + 'link' => 'link', + 'description' => 'html', + 'language' => '', //'language' => 'lang', - 'copyright' => 'nohtml', - 'pubDate' => 'date', - 'lastBuildDate' => 'date', - 'category' => 'nohtml', - 'docs' => 'link', - 'cloud' => '', - 'generator' => 'nohtml', - 'managingEditor' => 'email', - 'webMaster' => 'email', - 'ttl' => 'int', - 'image' => '', - 'rating' => 'nohtml', + 'copyright' => 'nohtml', + 'pubDate' => 'date', + 'lastBuildDate' => 'date', + 'category' => 'nohtml', + 'docs' => 'link', + 'cloud' => '', + 'generator' => 'nohtml', + 'managingEditor' => 'email', + 'webMaster' => 'email', + 'ttl' => 'int', + 'image' => '', + 'rating' => 'nohtml', //'textInput' => '', - 'skipHours' => 'hour', - 'skipDays' => 'day', + 'skipHours' => 'hour', + 'skipDays' => 'day', ]; /** @@ -49,49 +43,20 @@ class SRSS extends DomDocument implements Iterator */ public function __construct() { - libxml_use_internal_errors(true); - parent::__construct(); - $this->xpath = new DOMXpath($this); $this->attr = []; $this->items = []; $this->position = 0; - $this->formatOutput = true; - $this->preserveWhiteSpace = false; - } - - /** - * Destructor - * manage of libxml errors - */ - public function __destruct() - { - foreach (libxml_get_errors() as $error) { - error_log($error->message, 3, 'error.log'); - } - libxml_clear_errors(); } /** * @param string $link url of the rss - * @throws SRSSException + * * @return SRSS + * @throws SRSSException */ public static function read(string $link): SRSS { - $doc = new SRSS; - if(@$doc->load($link)) { // We don't want the warning in case of bad XML. Let's manage it with an exception. - $channel = $doc->getElementsByTagName('channel'); - if($channel->length == 1){ // Good URL and good RSS - $doc->_loadAttributes(); // loading channel properties - $doc->getItems(); // loading all items - - return $doc; - } - - throw new SRSSException('invalid file '.$link); - } - - throw new SRSSException('Can not open file '.$link); + return (new SRSSParser())->parse($link); } /** @@ -101,145 +66,13 @@ class SRSS extends DomDocument implements Iterator public static function create() { $doc = new SRSS; - $root = $doc->createElement('rss'); - $root->setAttribute('version', '2.0'); - $channel = $doc->createElement('channel'); - $root->appendChild($channel); - $doc->appendChild($root); - $doc->encoding = "UTF-8"; - $doc->generator = 'Shikiryu RSS'; - // $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html'; + $doc->channel = new Channel(); $doc->items = []; return $doc; } - /** - * getter of "image"'s channel attributes - * @return string|array - * TODO - */ - public function image() - { - $args = func_get_args(); - if (func_num_args() == 0) { - $args[0] = 'url'; - } - $img = $this->xpath->query('//channel/image'); - if($img->length != 1) { // is not in channel - return null; - } - $img = $img->item(0); - $r = []; - foreach($img->childNodes as $child) { - if($child->nodeType == XML_ELEMENT_NODE && in_array($child->nodeName, $args)) { - $r[$child->nodeName] = $child->nodeValue; - } - } - return (func_num_args() > 1) ? $r : $r[$args[0]]; - } - - /** - * setter of "image"'s channel attributes - * @param $url string picture's url - * @param $title string picture's title - * @param $link string link on the picture - * @param $width int width - * @param $height int height - * @param $description string description - * TODO - */ - public function setImage($url, $title, $link, $width = 0, $height = 0, $description = '') - { - $channel = $this->_getChannel(); - $array = []; - $url = SRSSTools::checkLink($url); - $array['url'] = $url; - $title = SRSSTools::noHTML($title); - $array['title'] = $title; - $link = SRSSTools::checkLink($link); - $array['link'] = $link; - if($width != 0) - { - $width = SRSSTools::checkInt($width); - $array['width'] = $width; - } - if($height != 0) - { - $height = SRSSTools::checkInt($height); - $array['height'] = $height; - } - if($description != 0) - { - $description = SRSSTools::noHTML($description); - $array['description'] = $description; - } - if($this->image == null) - { - $node = $this->createElement('image'); - $urlNode = $this->createElement('url', $url); - $titleNode = $this->createElement('title', $title); - $linkNode = $this->createElement('link', $link); - $node->appendChild($urlNode); - $node->appendChild($titleNode); - $node->appendChild($linkNode); - if($width != 0) - { - $widthNode = $this->createElement('width', $width); - $node->appendChild($widthNode); - } - if($height != 0) - { - $heightNode = $this->createElement('height', $height); - $node->appendChild($heightNode); - } - if($description != '') - { - $descNode = $this->createElement('description', $description); - $node->appendChild($descNode); - } - $channel->appendChild($node); - } - $this->attr['image'] = $array; - } - - /** - * setter of "cloud"'s channel attributes - * @param $domain string domain - * @param $port int port - * @param $path string path - * @param $registerProcedure string register procedure - * @param $protocol string protocol - * TODO - */ - public function setCloud($domain, $port, $path, $registerProcedure, $protocol) - { - $channel = $this->_getChannel(); - $array = array(); - $domain = SRSSTools::noHTML($domain); - $array['domain'] = $domain; - $port = SRSSTools::checkInt($port); - $array['port'] = $port; - $path = SRSSTools::noHTML($path); - $array['path'] = $path; - $registerProcedure = SRSSTools::noHTML($registerProcedure); - $array['registerProcedure'] = $registerProcedure; - $protocol = SRSSTools::noHTML($protocol); - $array['protocol'] = $protocol; - if($this->cloud == null) - { - $node = $this->createElement('cloud'); - $node->setAttribute('domain', $domain); - $node->setAttribute('port', $port); - $node->setAttribute('path', $path); - $node->setAttribute('registerProcedure', $registerProcedure); - $node->setAttribute('protocol', $protocol); - $channel->appendChild($node); - } - $this->attr['cloud'] = $array; - } - /** * check if current RSS is a valid one (based on specifications) * @return bool @@ -251,29 +84,15 @@ class SRSS extends DomDocument implements Iterator $items = $this->getItems(); $invalidItems = []; $i = 1; - foreach($items as $item){ - if($item->isValid() === false){ + foreach ($items as $item) { + if ($item->isValid() === false) { $invalidItems[] = $i; $valid = false; } $i++; } - return ($valid && $this->title != null && $this->link != null && $this->description != null); - } - /** - * getter of current RSS channel - * @return \DOMNode - * @throws SRSSException - */ - private function _getChannel(): \DOMNode - { - $channel = $this->getElementsByTagName('channel'); - if($channel->length != 1) { - throw new SRSSException('channel node not created, or too many channel nodes'); - } - - return $channel->item(0); + return ($valid && $this->channel->isValid()); } /** @@ -283,104 +102,61 @@ class SRSS extends DomDocument implements Iterator */ public function __isset($name) { - return isset($this->attr[$name]); + return isset($this->channel->{$name}); } /** * setter of others attributes + * * @param $name * @param $val + * * @throws SRSSException */ public function __set($name, $val) { - $channel = $this->_getChannel(); - if(array_key_exists($name, $this->possibleAttr)){ - $flag = $this->possibleAttr[$name]; - $val = SRSSTools::check($val, $flag); - if(!empty($val)){ - if($this->$name == null){ - $node = $this->createElement($name, $val); - $channel->appendChild($node); - } - $this->attr[$name] = $val; - } - }else{ - throw new SRSSException($name.' is not a possible item'); + if (!array_key_exists($name, $this->possibleAttr)) { + throw new SRSSException($name . ' is not a possible item'); + } + $flag = $this->possibleAttr[$name]; + $val = SRSSTools::check($val, $flag); + if (!empty($val)) { + $this->channel->{$name} = $val; } } /** * getter of others attributes + * * @param $name + * * @return null|string - * @throws SRSSException */ public function __get($name) { - if (isset($this->channel->{$name})) { - return $this->channel->{$name}; - } -// $channel = $this->_getChannel(); - if(array_key_exists($name, $this->possibleAttr)){ - $tmp = $this->xpath->query('//channel/'.$name); - if($tmp->length != 1) { - return null; - } - return $tmp->item(0)->nodeValue; - } - - throw new SRSSException($name.' is not a possible value.'); - } - - /** - * add a SRSS Item as an item into current RSS as first item - * @param SRSSItem $item - */ - public function addItemBefore(SRSSItem $item) - { - $node = $this->importNode($item->getItem(), true); - $items = $this->getElementsByTagName('item'); - if($items->length != 0){ - $firstNode = $items->item(0); - if($firstNode != null) - $firstNode->parentNode->insertBefore($node, $firstNode); - else - $this->addItem($item); - } - else - $this->addItem($item); - } - - /** - * add a SRSS Item as an item into current RSS - * @param SRSSItem $item - */ - public function addItem(SRSSItem $item) - { - $node = $this->importNode($item->getItem(), true); - $channel = $this->_getChannel(); - $channel->appendChild($node); + return $this->channel->{$name} ?? null; } /** * rewind from Iterator */ - public function rewind() { + public function rewind(): void + { $this->position = 0; } /** * current from Iterator */ - public function current() { + public function current() + { return $this->items[$this->position]; } /** * key from Iterator */ - #[ReturnTypeWillChange] public function key(): int + public function key(): int { return $this->position; } @@ -388,7 +164,7 @@ class SRSS extends DomDocument implements Iterator /** * next from Iterator */ - #[ReturnTypeWillChange] public function next(): void + public function next(): void { ++$this->position; } @@ -396,7 +172,7 @@ class SRSS extends DomDocument implements Iterator /** * valid from Iterator */ - #[ReturnTypeWillChange] public function valid(): bool + public function valid(): bool { return isset($this->items[$this->position]); } @@ -417,11 +193,13 @@ class SRSS extends DomDocument implements Iterator public function getLast(): Item { $items = $this->getItems(); + return $items[array_key_last($items)]; } /** * getter of an item + * * @param $i int * * @return Item|null @@ -429,6 +207,7 @@ class SRSS extends DomDocument implements Iterator public function getItem(int $i): ?Item { $i--; + return $this->items[$i] ?? null; } @@ -439,62 +218,9 @@ class SRSS extends DomDocument implements Iterator */ public function getItems(): array { - if (!empty($this->items)) { - return $this->items; - } - - $channel = $this->_getChannel(); - /** @var DOMNodeList $items */ - $items = $channel->getElementsByTagName('item'); - $length = $items->length; - $this->items = []; - if ($length > 0) { - for($i = 0; $i < $length; $i++) { - $this->items[$i] = SRSSItem::read($items->item($i)); - } - } - return $this->items; } - /** - * display XML - * see DomDocument's docs - */ - public function show(): bool|string - { - // TODO build - return $this->saveXml(); - } - - - /** - * putting all RSS attributes into the object - * @throws SRSSException - */ - private function _loadAttributes(): void - { - $node_channel = $this->_getChannel(); - $this->channel = new Channel(); - - foreach($node_channel->childNodes as $child) { - if($child->nodeType == XML_ELEMENT_NODE && $child->nodeName !== 'item') { - if($child->nodeName == 'image') { - $image = new Image(); - foreach($child->childNodes as $children) { - if($children->nodeType == XML_ELEMENT_NODE) { - $image->{$child->nodeName} = $children->nodeValue; - } - } - $this->channel->image = $image; - - } else { - $this->channel->{$child->nodeName} = $child->nodeValue; - } - } - } - } - /** * transform current object into an array * @return array @@ -504,10 +230,10 @@ class SRSS extends DomDocument implements Iterator { $doc = $this->channel->toArray(); - foreach($this->getItems() as $item) { + foreach ($this->getItems() as $item) { $doc['items'][] = $item->toArray(); } return $doc; } -} \ No newline at end of file +} diff --git a/src/SRSSBuilder.php b/src/SRSSBuilder.php index 6612563..9d89403 100644 --- a/src/SRSSBuilder.php +++ b/src/SRSSBuilder.php @@ -2,7 +2,161 @@ namespace Shikiryu\SRSS; -class SRSSBuilder -{ +use DOMDocument; +class SRSSBuilder extends DomDocument +{ + public function build() + { + $root = $this->createElement('rss'); + $root->setAttribute('version', '2.0'); + $channel = $this->createElement('channel'); + $root->appendChild($channel); + $this->appendChild($root); + $this->encoding = 'UTF-8'; + $this->generator = 'Shikiryu RSS'; + $this->formatOutput = true; + $this->preserveWhiteSpace = false; + // $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html'; + } + + /** + * add a SRSS Item as an item into current RSS as first item + * @param SRSSItem $item + */ + public function addItemBefore(SRSSItem $item) + { + $node = $this->importNode($item->getItem(), true); + $items = $this->getElementsByTagName('item'); + if($items->length != 0){ + $firstNode = $items->item(0); + if($firstNode != null) + $firstNode->parentNode->insertBefore($node, $firstNode); + else + $this->addItem($item); + } + else + $this->addItem($item); + } + + /** + * add a SRSS Item as an item into current RSS + * @param SRSSItem $item + */ + public function addItem(SRSSItem $item) + { + $node = $this->importNode($item->getItem(), true); + $channel = $this->_getChannel(); + $channel->appendChild($node); + } + + /** + * display XML + * see DomDocument's docs + */ + public function show(): bool|string + { + // TODO build + return $this->saveXml(); + } + + /** + * setter of "image"'s channel attributes + * @param $url string picture's url + * @param $title string picture's title + * @param $link string link on the picture + * @param $width int width + * @param $height int height + * @param $description string description + * TODO + */ + public function setImage($url, $title, $link, $width = 0, $height = 0, $description = '') + { + $channel = $this->_getChannel(); + $array = []; + $url = SRSSTools::checkLink($url); + $array['url'] = $url; + $title = SRSSTools::noHTML($title); + $array['title'] = $title; + $link = SRSSTools::checkLink($link); + $array['link'] = $link; + if($width != 0) + { + $width = SRSSTools::checkInt($width); + $array['width'] = $width; + } + if($height != 0) + { + $height = SRSSTools::checkInt($height); + $array['height'] = $height; + } + if($description != 0) + { + $description = SRSSTools::noHTML($description); + $array['description'] = $description; + } + if($this->image == null) + { + $node = $this->createElement('image'); + $urlNode = $this->createElement('url', $url); + $titleNode = $this->createElement('title', $title); + $linkNode = $this->createElement('link', $link); + $node->appendChild($urlNode); + $node->appendChild($titleNode); + $node->appendChild($linkNode); + if($width != 0) + { + $widthNode = $this->createElement('width', $width); + $node->appendChild($widthNode); + } + if($height != 0) + { + $heightNode = $this->createElement('height', $height); + $node->appendChild($heightNode); + } + if($description != '') + { + $descNode = $this->createElement('description', $description); + $node->appendChild($descNode); + } + $channel->appendChild($node); + } + $this->attr['image'] = $array; + } + + /** + * setter of "cloud"'s channel attributes + * @param $domain string domain + * @param $port int port + * @param $path string path + * @param $registerProcedure string register procedure + * @param $protocol string protocol + * TODO + */ + public function setCloud($domain, $port, $path, $registerProcedure, $protocol) + { + $channel = $this->_getChannel(); + $array = array(); + $domain = SRSSTools::noHTML($domain); + $array['domain'] = $domain; + $port = SRSSTools::checkInt($port); + $array['port'] = $port; + $path = SRSSTools::noHTML($path); + $array['path'] = $path; + $registerProcedure = SRSSTools::noHTML($registerProcedure); + $array['registerProcedure'] = $registerProcedure; + $protocol = SRSSTools::noHTML($protocol); + $array['protocol'] = $protocol; + if($this->cloud == null) + { + $node = $this->createElement('cloud'); + $node->setAttribute('domain', $domain); + $node->setAttribute('port', $port); + $node->setAttribute('path', $path); + $node->setAttribute('registerProcedure', $registerProcedure); + $node->setAttribute('protocol', $protocol); + $channel->appendChild($node); + } + $this->attr['cloud'] = $array; + } } \ No newline at end of file diff --git a/src/SRSSItem.php b/src/SRSSItem.php index e940e19..4c52bd5 100644 --- a/src/SRSSItem.php +++ b/src/SRSSItem.php @@ -59,10 +59,8 @@ class SRSSItem extends DomDocument if ($child->nodeType === XML_ELEMENT_NODE && $child->nodeName !== 'item') { if (array_key_exists($child->nodeName, self::$possibilities) && self::$possibilities[$child->nodeName] === 'folder') { self::_loadChildAttributes($item, $child); - } elseif ($child->nodeName === 'media:group') { - // TODO } elseif ($child->nodeName === 'media:content') { - $item->{$child->nodeName} = new Content($child); + $item->medias[] = new Content($child); } else { $item->{$child->nodeName} = $child->nodeValue; } diff --git a/src/SRSSParser.php b/src/SRSSParser.php new file mode 100644 index 0000000..7aa83e3 --- /dev/null +++ b/src/SRSSParser.php @@ -0,0 +1,146 @@ +xpath = new DOMXpath($this); + $this->doc = new SRSS(); + } + + /** + * Destructor + * manage of libxml errors + */ + public function __destruct() + { + foreach (libxml_get_errors() as $error) { + error_log($error->message, 3, 'error.log'); + } + libxml_clear_errors(); + } + + /** + * @param string $link + * + * @return \Shikiryu\SRSS\SRSS + * @throws \Shikiryu\SRSS\SRSSException + */ + public function parse(string $link) + { + if(@$this->load($link)) { // We don't want the warning in case of bad XML. Let's manage it with an exception. + $channel = $this->getElementsByTagName('channel'); + if($channel->length === 1){ // Good URL and good RSS + $this->_loadAttributes(); // loading channel properties + $this->getItems(); // loading all items + + return $this->doc; + } + + throw new SRSSException('invalid file '.$link); + } + + throw new SRSSException('Can not open file '.$link); + } + + /** + * @return array|mixed + * @throws \Shikiryu\SRSS\SRSSException + */ + private function getItems() + { + $channel = $this->_getChannel(); + /** @var DOMNodeList $items */ + $items = $channel->getElementsByTagName('item'); + $length = $items->length; + $this->doc->items = []; + if ($length > 0) { + for($i = 0; $i < $length; $i++) { + $this->doc->items[$i] = SRSSItem::read($items->item($i)); + } + } + + return $this->doc->items; + } + /** + * putting all RSS attributes into the object + * @throws SRSSException + */ + private function _loadAttributes(): void + { + $node_channel = $this->_getChannel(); + $this->doc->channel = new Channel(); + + foreach($node_channel->childNodes as $child) { + if ($child->nodeType === XML_ELEMENT_NODE && $child->nodeName !== 'item') { + if($child->nodeName === 'image') { + $image = new Image(); + foreach($child->childNodes as $children) { + if($children->nodeType == XML_ELEMENT_NODE) { + $image->{$child->nodeName} = $children->nodeValue; + } + } + $this->doc->channel->image = $image; + + } else { + $this->doc->channel->{$child->nodeName} = $child->nodeValue; + } + } + } + } + + + /** + * getter of current RSS channel + * @return \DOMNode + * @throws SRSSException + */ + private function _getChannel(): \DOMNode + { + $channel = $this->getElementsByTagName('channel'); + if($channel->length != 1) { + throw new SRSSException('channel node not created, or too many channel nodes'); + } + + return $channel->item(0); + } + + /** + * getter of "image"'s channel attributes + * @return string|array + * TODO + */ + public function image() + { + $args = func_get_args(); + if (func_num_args() == 0) { + $args[0] = 'url'; + } + $img = $this->xpath->query('//channel/image'); + if ($img->length != 1) { // is not in channel + return null; + } + $img = $img->item(0); + $r = []; + foreach ($img->childNodes as $child) { + if ($child->nodeType == XML_ELEMENT_NODE && in_array($child->nodeName, $args)) { + $r[$child->nodeName] = $child->nodeValue; + } + } + + return (func_num_args() > 1) ? $r : $r[$args[0]]; + } +} \ No newline at end of file diff --git a/tests/BasicReader.php b/tests/BasicReader.php index 400eabb..1ce19a4 100644 --- a/tests/BasicReader.php +++ b/tests/BasicReader.php @@ -20,4 +20,23 @@ class BasicReader extends TestCase $this->expectException(SRSSException::class); $rss = SRSS::read('not_found.xml'); } + + public function testSpecificationExampleRSS() + { + $rss = SRSS::read(__DIR__.'/resources/harvard.xml'); + self::assertEquals('Liftoff News', $rss->title); + self::assertEquals('http://liftoff.msfc.nasa.gov/', $rss->link); + self::assertEquals('Liftoff to Space Exploration.', $rss->description); + self::assertEquals('en-us', $rss->language); + self::assertEquals('Tue, 10 Jun 2003 04:00:00 GMT', $rss->pubDate); + self::assertEquals('Tue, 10 Jun 2003 09:41:01 GMT', $rss->lastBuildDate); + self::assertEquals('http://blogs.law.harvard.edu/tech/rss', $rss->docs); + self::assertEquals('Weblog Editor 2.0', $rss->generator); + self::assertEquals('editor@example.com', $rss->managingEditor); + self::assertEquals('webmaster@example.com', $rss->webMaster); + self::assertCount(4, $rss->items); + self::assertEquals('Star City', $rss->getFirst()->title); + self::assertEquals('http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp', $rss->getLast()->link); + self::assertEquals('Fri, 30 May 2003 11:06:42 GMT', $rss->getItem(2)->pubDate); + } } \ No newline at end of file diff --git a/tests/MediaTest.php b/tests/MediaTest.php index 655f04f..ae1e96a 100644 --- a/tests/MediaTest.php +++ b/tests/MediaTest.php @@ -13,6 +13,17 @@ class MediaTest extends TestCase $first_item = $rss->getFirst(); self::assertEquals('Kirstie Alley, \'Cheers\' and \'Veronica\'s Closet\' star, dead at 71', $first_item->title); - self::assertEquals('https://cdn.cnn.com/cnnnext/dam/assets/221205172141-kirstie-alley-2005-super-169.jpg', $first_item->{'media:content'}->url); + self::assertEquals('https://cdn.cnn.com/cnnnext/dam/assets/221205172141-kirstie-alley-2005-super-169.jpg', $first_item->medias[0]->url); + } + + public function testMusicVideo() + { + $rss = SRSS::read('resources/media/music-video.xml'); + self::assertEquals('Music Videos 101', $rss->title); + + self::assertCount(1, $rss->items); + + $first_item = $rss->getFirst(); + self::assertEquals('http://www.foo.com/movie.mov', $first_item->medias[0]->url); } } \ No newline at end of file From fec8c122e393cd75f7808b1a0fcbea20af654758 Mon Sep 17 00:00:00 2001 From: Shikiryu Date: Fri, 7 Apr 2023 00:35:20 +0200 Subject: [PATCH 04/16] =?UTF-8?q?=F0=9F=A6=BA=20Add=20validator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- src/Entity/Channel.php | 104 ++++++++++++++++++----- src/SRSS.php | 19 ++--- src/SRSSParser.php | 5 +- src/SRSSTools.php | 37 ++++----- src/Validator/HasValidator.php | 11 +++ src/Validator/Validator.php | 146 +++++++++++++++++++++++++++++++++ tests/BasicReader.php | 4 + tests/MediaTest.php | 2 + 9 files changed, 276 insertions(+), 55 deletions(-) create mode 100644 src/Validator/HasValidator.php create mode 100644 src/Validator/Validator.php diff --git a/.gitignore b/.gitignore index f45219c..4eb89f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor/ -/.idea \ No newline at end of file +/.idea +/tests/error.log diff --git a/src/Entity/Channel.php b/src/Entity/Channel.php index c0bae45..9700bad 100644 --- a/src/Entity/Channel.php +++ b/src/Entity/Channel.php @@ -2,39 +2,103 @@ namespace Shikiryu\SRSS\Entity; +use ReflectionException; use Shikiryu\SRSS\Entity\Channel\Image; +use Shikiryu\SRSS\Validator\HasValidator; +use Shikiryu\SRSS\Validator\Validator; -class Channel implements SRSSElement +/** + * https://cyber.harvard.edu/rss/rss.html#requiredChannelElements + */ +class Channel extends HasValidator implements SRSSElement { + /** + * @required + * @nohtml + */ public string $title; + + /** + * @required + * @url + */ public string $link; + + /** + * @required + */ public string $description; - public ?string $language; - public ?string $copyright; - public ?string $managingEditor; - public ?string $webMaster; - public ?string $pubDate; - public ?string $lastBuildDate; - public ?string $category; - public ?string $generator; - public ?string $docs; - public ?string $cloud; - public ?string $ttl; - public ?Image $image; - public ?string $rating; - public ?string $textInput; - public ?string $skipHours; - public ?string $skipDays; - - public array $required = ['title', 'link', 'description']; + /** + * @lang + */ + public ?string $language = null; + /** + * @nohtml + */ + public ?string $copyright = null; + /** + * @nohtml + */ + public ?string $managingEditor = null; + /** + * @nohtml + */ + public ?string $webMaster = null; + /** + * @date + */ + public ?string $pubDate = null; + /** + * @date + */ + public ?string $lastBuildDate = null; + /** + * TODO should be an array + */ + public ?string $category = null; + /** + * @nohtml + */ + public ?string $generator = null; + /** + * @url + */ + public ?string $docs = null; + /** + * @var string|null + * TODO validator + */ + public ?string $cloud = null; + /** + * @int + */ + public ?string $ttl = null; + public ?Image $image = null; + public ?string $rating = null; + /** + * @var string|null + * The purpose of the element is something of a mystery. You can use it to specify a search engine box. Or to allow a reader to provide feedback. Most aggregators ignore it. + */ + public ?string $textInput = null; + /** + * @hour + */ + public ?string $skipHours = null; + /** + * @day + */ + public ?string $skipDays = null; /** * @return bool + * @throws ReflectionException */ public function isValid(): bool { - return count(array_filter($this->required, fn($field) => !empty($this->{$field}))) === 0; + $annotation_validation = new Validator(); + + return $annotation_validation->isObjectValid($this); } /** diff --git a/src/SRSS.php b/src/SRSS.php index addc078..954f5bb 100644 --- a/src/SRSS.php +++ b/src/SRSS.php @@ -8,11 +8,10 @@ use Shikiryu\SRSS\Entity\Item; class SRSS implements Iterator { - public array $items; // array of SRSSItems - protected $attr; // array of RSS attributes - private $position; // Iterator position - public Channel $channel; + public array $items; // array of SRSSItems + + private int $position; // Iterator position // lists of possible attributes for RSS protected $possibleAttr = [ @@ -43,7 +42,6 @@ class SRSS implements Iterator */ public function __construct() { - $this->attr = []; $this->items = []; $this->position = 0; } @@ -76,9 +74,8 @@ class SRSS implements Iterator /** * check if current RSS is a valid one (based on specifications) * @return bool - * TODO use required */ - public function isValid() + public function isValid(): bool { $valid = true; $items = $this->getItems(); @@ -115,7 +112,7 @@ class SRSS implements Iterator */ public function __set($name, $val) { - if (!array_key_exists($name, $this->possibleAttr)) { + if (!property_exists(Channel::class, $name)) { throw new SRSSException($name . ' is not a possible item'); } $flag = $this->possibleAttr[$name]; @@ -148,7 +145,7 @@ class SRSS implements Iterator /** * current from Iterator */ - public function current() + public function current(): mixed { return $this->items[$this->position]; } @@ -179,7 +176,7 @@ class SRSS implements Iterator /** * getter of 1st item - * @return Item + * @return Item|null */ public function getFirst(): ?Item { @@ -214,7 +211,6 @@ class SRSS implements Iterator /** * getter of all items * @return Item[] - * @throws SRSSException */ public function getItems(): array { @@ -224,7 +220,6 @@ class SRSS implements Iterator /** * transform current object into an array * @return array - * @throws SRSSException */ public function toArray(): array { diff --git a/src/SRSSParser.php b/src/SRSSParser.php index 7aa83e3..70e5a04 100644 --- a/src/SRSSParser.php +++ b/src/SRSSParser.php @@ -7,6 +7,7 @@ use DOMNodeList; use DOMXPath; use Shikiryu\SRSS\Entity\Channel; use Shikiryu\SRSS\Entity\Channel\Image; +use Shikiryu\SRSS\Entity\Item; class SRSSParser extends DomDocument { @@ -57,10 +58,10 @@ class SRSSParser extends DomDocument } /** - * @return array|mixed + * @return Item[] * @throws \Shikiryu\SRSS\SRSSException */ - private function getItems() + private function getItems(): mixed { $channel = $this->_getChannel(); /** @var DOMNodeList $items */ diff --git a/src/SRSSTools.php b/src/SRSSTools.php index 1a2e2a9..e7d42a4 100644 --- a/src/SRSSTools.php +++ b/src/SRSSTools.php @@ -6,26 +6,23 @@ class SRSSTools { public static function check($check, $flag) { - switch($flag){ - case 'nohtml': return self::noHTML($check); - case 'link': return self::checkLink($check); - case 'html': return self::HTML4XML($check); - /*case 'lang': - return self::noHTML($check); - */ - case 'date': return self::getRSSDate($check); - case 'email': return self::checkEmail($check); - case 'int': return self::checkInt($check); - case 'hour': return self::checkHour($check); - case 'day': return self::checkDay($check); - case 'folder': return []; - case 'media_type': return self::checkMediaType($check); - case 'media_medium': return self::checkMediaMedium($check); - case 'bool': return self::checkBool($check); - case 'medium_expression': return self::checkMediumExpression($check); - case '': return $check; - default: throw new SRSSException('flag '.$flag.' does not exist.'); - } + return match ($flag) { + 'nohtml' => self::noHTML($check), + 'link' => self::checkLink($check), + 'html' => self::HTML4XML($check), + 'date' => self::getRSSDate($check), + 'email' => self::checkEmail($check), + 'int' => self::checkInt($check), + 'hour' => self::checkHour($check), + 'day' => self::checkDay($check), + 'folder' => [], + 'media_type' => self::checkMediaType($check), + 'media_medium' => self::checkMediaMedium($check), + 'bool' => self::checkBool($check), + 'medium_expression' => self::checkMediumExpression($check), + '' => $check, + default => throw new SRSSException('flag ' . $flag . ' does not exist.'), + }; } /** diff --git a/src/Validator/HasValidator.php b/src/Validator/HasValidator.php new file mode 100644 index 0000000..96f7de3 --- /dev/null +++ b/src/Validator/HasValidator.php @@ -0,0 +1,11 @@ +validated) { + $object = $this->validateObject($object); + } + + return !in_array(false, $object->validated); + } + + /** + * @throws ReflectionException + */ + public function validateObject($object) + { + $properties = $this->_getClassProperties(get_class($object)); + + foreach ($properties as $property) { + $propertyValue = $object->{$property->name}; + $propertyAnnotations = $this->_getPropertyAnnotations($property, get_class($object)); + + if (!in_array('required', $propertyAnnotations) && empty($propertyValue)) { + continue; + } + + foreach ($propertyAnnotations as $propertyAnnotation) { + $annotation = explode(' ', $propertyAnnotation); + + $object->validated[$property->name] = $this->_validateProperty($annotation, $propertyValue); + } + } + + return $object; + } + + private function _validateProperty(array $annotation, $property): bool + { + if (count($annotation) === 1) { + return call_user_func([$this, sprintf('_validate%s', ucfirst($annotation[0]))], $property); + } + + return true; // TODO check + } + + /** + * @throws ReflectionException + */ + private function _getClassProperties($class): array + { + $ReflectionClass = new ReflectionClass($class); + + return $ReflectionClass->getProperties(); + } + + private function _getPropertyAnnotations($property, $className): array + { + preg_match_all('#@(.*?)\n#s', $property->getDocComment(), $annotations); + + return array_map(fn($annotation) => trim($annotation), $annotations[1]); + } + + private function _validateString($value): bool + { + return is_string($value); + } + + private function _validateInt($value): bool + { + return is_numeric($value); + } + + private function _validateRequired($value): bool + { + return !empty(trim($value)); + } + + /** + * @param $value + * @return bool + * https://cyber.harvard.edu/rss/languages.html + */ + private function _validateLang($value): bool + { + return in_array(strtolower($value), [ + 'af','sq','eu','be','bg','ca','zh-cn','zh-tw','hr','cs','da','nl','nl-be','nl-nl','en','en-au','en-bz', + 'en-ca','en-ie','en-jm','en-nz','en-ph','en-za','en-tt','en-gb','en-us','en-zw','et','fo','fi','fr','fr-be', + 'fr-ca','fr-fr','fr-lu','fr-mc','fr-ch','gl','gd','de','de-at','de-de','de-li','de-lu','de-ch','el','haw', + 'hu','is','in','ga','it','it-it','it-ch','ja','ko','mk','no','pl','pt','pt-br','pt-pt','ro','ro-mo','ro-ro', + 'ru','ru-mo','ru-ru','sr','sk','sl','es','es-ar','es-bo','es-cl','es-co','es-cr','es-do','es-ec','es-sv', + 'es-gt','es-hn','es-mx','es-ni','es-pa','es-py','es-pe','es-pr','es-es','es-uy','es-ve','sv','sv-fi','sv-se', + 'tr','uk', + ]); + } + + /** + * @param $value + * @return bool + */ + private function _validateNoHtml($value): bool + { + return strip_tags($value) === $value; + } + + private function _validateUrl($value): bool + { + return filter_var($value, FILTER_VALIDATE_URL) !== false; + } + + private function _validateDate($value): bool + { + return \DateTime::createFromFormat(DateTimeInterface::RSS, $value) !== false; + } + + private function _validateHour($value): bool + { + $options = [ + 'options' => [ + 'default' => 0, + 'min_range' => 0, + 'max_range' => 23 + ] + ]; + return filter_var($value, FILTER_VALIDATE_INT, $options) !== false; + } + + private function _validateDay($value): bool + { + return in_array( + strtolower($value), + ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] + ); + } +} \ No newline at end of file diff --git a/tests/BasicReader.php b/tests/BasicReader.php index 1ce19a4..8e4834e 100644 --- a/tests/BasicReader.php +++ b/tests/BasicReader.php @@ -13,6 +13,8 @@ class BasicReader extends TestCase $first_item = $rss->getFirst(); self::assertNotNull($first_item); self::assertEquals('RSS Tutorial', $first_item->title); + + self::assertTrue($rss->channel->isValid()); } public function testRssNotFound() @@ -38,5 +40,7 @@ class BasicReader extends TestCase self::assertEquals('Star City', $rss->getFirst()->title); self::assertEquals('http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp', $rss->getLast()->link); self::assertEquals('Fri, 30 May 2003 11:06:42 GMT', $rss->getItem(2)->pubDate); + + self::assertTrue($rss->channel->isValid()); } } \ No newline at end of file diff --git a/tests/MediaTest.php b/tests/MediaTest.php index ae1e96a..67511d0 100644 --- a/tests/MediaTest.php +++ b/tests/MediaTest.php @@ -14,6 +14,7 @@ class MediaTest extends TestCase self::assertEquals('Kirstie Alley, \'Cheers\' and \'Veronica\'s Closet\' star, dead at 71', $first_item->title); self::assertEquals('https://cdn.cnn.com/cnnnext/dam/assets/221205172141-kirstie-alley-2005-super-169.jpg', $first_item->medias[0]->url); + self::assertTrue($rss->channel->isValid(), var_export($rss->channel->validated, true)); } public function testMusicVideo() @@ -25,5 +26,6 @@ class MediaTest extends TestCase $first_item = $rss->getFirst(); self::assertEquals('http://www.foo.com/movie.mov', $first_item->medias[0]->url); + self::assertTrue($rss->channel->isValid()); } } \ No newline at end of file From 63d0b03b50425adf19b6737caacf3b89c8f76261 Mon Sep 17 00:00:00 2001 From: Clement Desmidt Date: Fri, 7 Apr 2023 17:57:35 +0200 Subject: [PATCH 05/16] =?UTF-8?q?=F0=9F=9A=A7=20Continues=20tests,=20parse?= =?UTF-8?q?r=20and=20starts=20builder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For #4 --- .gitignore | 1 + phpunit.xml | 30 +++ src/{ => Builder}/SRSSBuilder.php | 54 +++++- src/Entity/Channel.php | 14 +- src/Entity/Channel/Image.php | 41 ++++- src/Entity/Item.php | 65 +++++-- src/Entity/Media/Content.php | 173 +++++++----------- src/Entity/Media/Group.php | 8 - src/{ => Exception}/SRSSException.php | 2 +- src/{SRSSItem.php => Parser/ItemParser.php} | 23 +-- src/Parser/MediaContentParser.php | 79 ++++++++ src/{ => Parser}/SRSSParser.php | 12 +- src/SRSS.php | 46 ++--- src/SRSSTools.php | 14 +- src/Validator/Validator.php | 110 +++++++++-- tests/BasicBuilderTest.php | 40 ++++ .../{BasicReader.php => BasicReaderTest.php} | 8 +- tests/MediaTest.php | 8 +- 18 files changed, 494 insertions(+), 234 deletions(-) create mode 100644 phpunit.xml rename src/{ => Builder}/SRSSBuilder.php (76%) delete mode 100644 src/Entity/Media/Group.php rename src/{ => Exception}/SRSSException.php (90%) rename src/{SRSSItem.php => Parser/ItemParser.php} (89%) create mode 100644 src/Parser/MediaContentParser.php rename src/{ => Parser}/SRSSParser.php (92%) create mode 100644 tests/BasicBuilderTest.php rename tests/{BasicReader.php => BasicReaderTest.php} (90%) diff --git a/.gitignore b/.gitignore index 4eb89f8..2d769b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /vendor/ /.idea /tests/error.log +/.phpunit.result.cache diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..e340815 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,30 @@ + + + + + + + tests + + + + + + src/ + + + + + + + + \ No newline at end of file diff --git a/src/SRSSBuilder.php b/src/Builder/SRSSBuilder.php similarity index 76% rename from src/SRSSBuilder.php rename to src/Builder/SRSSBuilder.php index 9d89403..5a6f1ae 100644 --- a/src/SRSSBuilder.php +++ b/src/Builder/SRSSBuilder.php @@ -1,16 +1,27 @@ createElement('rss'); $root->setAttribute('version', '2.0'); $channel = $this->createElement('channel'); + + $this->appendChannelToDom($srss->channel, $channel); + + $this->appendItemsToDom($srss->items, $channel); + $root->appendChild($channel); $this->appendChild($root); $this->encoding = 'UTF-8'; @@ -18,13 +29,16 @@ class SRSSBuilder extends DomDocument $this->formatOutput = true; $this->preserveWhiteSpace = false; // $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html'; + + $this->save($filepath); } /** * add a SRSS Item as an item into current RSS as first item - * @param SRSSItem $item + * + * @param ItemParser $item */ - public function addItemBefore(SRSSItem $item) + public function addItemBefore(ItemParser $item) { $node = $this->importNode($item->getItem(), true); $items = $this->getElementsByTagName('item'); @@ -41,9 +55,10 @@ class SRSSBuilder extends DomDocument /** * add a SRSS Item as an item into current RSS - * @param SRSSItem $item + * + * @param ItemParser $item */ - public function addItem(SRSSItem $item) + public function addItem(ItemParser $item) { $node = $this->importNode($item->getItem(), true); $channel = $this->_getChannel(); @@ -54,7 +69,7 @@ class SRSSBuilder extends DomDocument * display XML * see DomDocument's docs */ - public function show(): bool|string + public function show() { // TODO build return $this->saveXml(); @@ -159,4 +174,29 @@ class SRSSBuilder extends DomDocument } $this->attr['cloud'] = $array; } + + private function appendChannelToDom(Channel $channel, DOMElement $node) + { + foreach (array_filter($channel->toArray(), fn($el) => !empty($el)) as $name => $value) { + $new_node = $this->createElement($name, $value); + $node->appendChild($new_node); + } + } + + private function appendItemsToDom(array $items, DOMElement $channel) + { + foreach ($items as $item) { + $this->appendItemToDom($item, $channel); + } + } + + private function appendItemToDom(Item $item, DOMElement $channel) + { + $itemNode = $this->createElement('item'); + foreach (array_filter($item->toArray()) as $name => $value) { + $new_node = $this->createElement($name, $value); + $itemNode->appendChild($new_node); + } + $channel->appendChild($itemNode); + } } \ No newline at end of file diff --git a/src/Entity/Channel.php b/src/Entity/Channel.php index 9700bad..d4d958f 100644 --- a/src/Entity/Channel.php +++ b/src/Entity/Channel.php @@ -16,18 +16,18 @@ class Channel extends HasValidator implements SRSSElement * @required * @nohtml */ - public string $title; + public string $title = ''; /** * @required * @url */ - public string $link; + public string $link = ''; /** * @required */ - public string $description; + public string $description = ''; /** * @lang @@ -96,9 +96,7 @@ class Channel extends HasValidator implements SRSSElement */ public function isValid(): bool { - $annotation_validation = new Validator(); - - return $annotation_validation->isObjectValid($this); + return (new Validator())->isObjectValid($this); } /** @@ -106,6 +104,8 @@ class Channel extends HasValidator implements SRSSElement */ public function toArray(): array { - return get_object_vars($this); + $vars = get_object_vars($this); + unset($vars['validated']); + return $vars; } } \ No newline at end of file diff --git a/src/Entity/Channel/Image.php b/src/Entity/Channel/Image.php index d794078..f4fac3a 100644 --- a/src/Entity/Channel/Image.php +++ b/src/Entity/Channel/Image.php @@ -2,14 +2,47 @@ namespace Shikiryu\SRSS\Entity\Channel; -class Image +use Shikiryu\SRSS\Entity\SRSSElement; +use Shikiryu\SRSS\Validator\HasValidator; +use Shikiryu\SRSS\Validator\Validator; + +class Image extends HasValidator implements SRSSElement { + /** + * @required + * @url + */ public string $url; + /** + * @required + * @nohtml + */ public string $title; + /** + * @required + * @url + */ public string $link; - public int $width; // Maximum value for width is 144, default value is 88. - public int $height; //Maximum value for height is 400, default value is 31. + /** + * @int + * @max 144 + */ + public int $width = 88; // Maximum value for width is 144, default value is 88. + /** + * @int + * @max 400 + */ + public int $height = 31; //Maximum value for height is 400, default value is 31. + public string $description; - public array $required = ['url', 'title', 'link']; + public function isValid(): bool + { + return (new Validator())->isObjectValid($this); + } + + public function toArray(): array + { + return get_object_vars($this); + } } \ No newline at end of file diff --git a/src/Entity/Item.php b/src/Entity/Item.php index e3cd66b..2831f2f 100644 --- a/src/Entity/Item.php +++ b/src/Entity/Item.php @@ -2,33 +2,68 @@ namespace Shikiryu\SRSS\Entity; -class Item implements SRSSElement +use Shikiryu\SRSS\Validator\HasValidator; +use Shikiryu\SRSS\Validator\Validator; + +/** + * https://cyber.harvard.edu/rss/rss.html#hrelementsOfLtitemgt + */ +class Item extends HasValidator implements SRSSElement { - public ?string $title; - public ?string $link; - public ?string $description; - public ?string $author; - public ?string $category; - public ?string $comments; - public ?string $enclosure; - public ?string $guid; - public ?string $pubDate; - public ?string $source; + /** + * @requiredOr description + * @nohtml + */ + public ?string $title = null; + /** + * @url + */ + public ?string $link = null; + /** + * @requiredOr title + */ + public ?string $description = null; + /** + * @email + */ + public ?string $author = null; + /* + * TODO can be multiple with attributes and all + */ + public ?string $category = null; + /** + * @url + */ + public ?string $comments = null; + /* + * TODO 1 attributes and 1 value + */ + public ?string $enclosure = null; + public ?string $guid = null; + /** + * @date + */ + public ?string $pubDate = null; + /* + * TODO 1 attributes and 1 value + */ + public ?string $source = null; /** * @var \Shikiryu\SRSS\Entity\Media\Content[] + * @contentMedia */ public array $medias = []; - public array $required = ['description']; - public function isValid(): bool { - return count(array_filter($this->required, fn($field) => !empty($this->{$field}))) === 0; + return (new Validator())->isObjectValid($this); } public function toArray(): array { - return get_object_vars($this); + $vars = get_object_vars($this); + unset($vars['validated']); + return $vars; } } \ No newline at end of file diff --git a/src/Entity/Media/Content.php b/src/Entity/Media/Content.php index 1e09671..bfbd14a 100644 --- a/src/Entity/Media/Content.php +++ b/src/Entity/Media/Content.php @@ -2,123 +2,76 @@ namespace Shikiryu\SRSS\Entity\Media; -use DOMDocument; -use DOMElement; -use DOMNode; -use Shikiryu\SRSS\SRSSException; -use Shikiryu\SRSS\SRSSTools; +use Shikiryu\SRSS\Entity\SRSSElement; +use Shikiryu\SRSS\Validator\HasValidator; +use Shikiryu\SRSS\Validator\Validator; -class Content extends DomDocument +class Content extends HasValidator implements SRSSElement { - protected array $possibilities = [ - 'url' => 'link', - 'fileSize' => 'int', // TODO - 'type' => 'media_type', // TODO - 'medium' => 'media_medium', // TODO - 'isDefault' => 'bool', // TODO - 'expression' => 'medium_expression', // TODO - 'bitrate' => 'int', - 'framerate' => 'int', - 'samplingrate' => 'float', - 'channels' => 'int', - 'duration' => 'int', - 'height' => 'int', - 'width' => 'int', - 'lang' => '', - ]; - private array $attr = []; - - private DOMNode $node; - /** - * Constructor - * - * @param DomNode $node + * @url */ - public function __construct(?\DOMNode $node = null) + public ?string $url = null; + /** + * @int + */ + public ?int $filesize = null; + /** + * @mediaType + */ + public ?string $type = null; + /** + * @mediaMedium + */ + public ?string $medium = null; + /** + * @bool + */ + public ?bool $isDefault = null; + /** + * @mediaExpression + */ + public ?string $expression = null; + /** + * @int + */ + public ?int $bitrate = null; + /** + * @int + */ + public ?int $framerate = null; + /** + * @float + */ + public ?float $samplerate = null; + /** + * @int + */ + public ?int $channels = null; + /** + * @int + */ + public ?int $duration = null; + /** + * @int + */ + public ?int $height = null; + /** + * @int + */ + public ?int $width = null; + /** + * @lang + */ + public ?string $lang = null; + + public function isValid(): bool { - parent::__construct(); - if ($node instanceof DOMElement) { - $this->node = $this->importNode($node, true); - } else { - $this->node = $this->importNode(new DomElement('item')); - } - $this->_loadAttributes(); + return (new Validator())->isObjectValid($this); } - /** - * @return void - */ - private function _loadAttributes(): void + public function toArray(): array { - foreach ($this->node->attributes as $attributes) { - if (array_key_exists($attributes->name, $this->possibilities)) { - $this->{$attributes->name} = $attributes->value; - } - } - } - - /** - * main getter for properties - * - * @param $name - * - * @return null|string - * @throws SRSSException - */ - public function __get($name) - { - if (array_key_exists($name, $this->attr)) { - return $this->attr[$name]; - } - - if (array_key_exists($name, $this->possibilities)) { - $tmp = $this->node->getElementsByTagName($name); - if ($tmp->length !== 1) { - return null; - } - - return $tmp->item(0)->nodeValue; - } - - throw new SRSSException(sprintf('%s is not a possible item (%s)', $name, implode(', ', array_keys($this->possibilities)))); - } - - /** - * @param $name - * - * @return bool - */ - public function __isset($name) - { - return isset($this->attr[$name]); - } - - /** - * main setter for properties - * - * @param $name - * @param $val - * - * @throws SRSSException - */ - public function __set($name, $val) - { - if (!array_key_exists($name, $this->possibilities)) { - throw new SRSSException(sprintf('%s is not a possible item (%s)', $name, implode(', ', array_keys($this->possibilities)))); - } - - $flag = $this->possibilities[$name]; - - if ($flag !== '') { - $val = SRSSTools::check($val, $flag); - } - - if (!empty($val)) { - if ($this->$name === null) { - $this->node->appendChild(new DomElement($name, $val)); - } - $this->attr[$name] = $val; - } + return get_object_vars($this); } } \ No newline at end of file diff --git a/src/Entity/Media/Group.php b/src/Entity/Media/Group.php deleted file mode 100644 index 64ca71c..0000000 --- a/src/Entity/Media/Group.php +++ /dev/null @@ -1,8 +0,0 @@ -node = $this->importNode($node, true); -// else $this->node = $this->importNode(new DomElement('item')); -// $this->_loadAttributes(); } /** @@ -60,9 +58,9 @@ class SRSSItem extends DomDocument if (array_key_exists($child->nodeName, self::$possibilities) && self::$possibilities[$child->nodeName] === 'folder') { self::_loadChildAttributes($item, $child); } elseif ($child->nodeName === 'media:content') { - $item->medias[] = new Content($child); + $item->medias[] = MediaContentParser::read($child); } else { - $item->{$child->nodeName} = $child->nodeValue; + $item->{$child->nodeName} = trim($child->nodeValue); } } } @@ -188,15 +186,6 @@ class SRSSItem extends DomDocument throw new SRSSException(sprintf('%s is not a possible item (%s)', $name, implode(', ', array_keys($this->possibilities)))); } - /** - * display item's XML - * see DomDocument's docs - */ - public function show() - { - return $this->saveXml(); - } - /** * transform current item's object into an array * @return array diff --git a/src/Parser/MediaContentParser.php b/src/Parser/MediaContentParser.php new file mode 100644 index 0000000..4896cf5 --- /dev/null +++ b/src/Parser/MediaContentParser.php @@ -0,0 +1,79 @@ + 'nohtml', + 'link' => 'link', + 'description' => 'html', + 'author' => 'email', + 'category' => 'nohtml', + 'comments' => 'link', + 'enclosure' => '', + 'guid' => 'nohtml', + 'pubDate' => 'date', + 'source' => 'link', + 'media:group' => 'folder', + 'media:content' => '', + ]; + + /** + * Constructor + * + * @param DomNode $node + */ + public function __construct($node = null) + { + parent::__construct(); + } + + /** + * @param Content $item + * @param $nodes + * + * @return void + */ + private static function _loadChildAttributes(Content $item, $nodes): void + { + foreach ($nodes->attributes as $attribute) { + + if (property_exists(Content::class, $attribute->name)) { + $item->{$attribute->name} = $attribute->value; + } + + } + } + + /** + * @param DOMNode|null $node + * + * @return Content + */ + public static function read(?DOMNode $node = null): Content + { + $content = new Content(); + if ($node instanceof DOMNode) { + self::_loadChildAttributes($content, $node); + } + + return $content; + } +} \ No newline at end of file diff --git a/src/SRSSParser.php b/src/Parser/SRSSParser.php similarity index 92% rename from src/SRSSParser.php rename to src/Parser/SRSSParser.php index 70e5a04..a7f9076 100644 --- a/src/SRSSParser.php +++ b/src/Parser/SRSSParser.php @@ -1,6 +1,6 @@ _getChannel(); /** @var DOMNodeList $items */ @@ -70,7 +72,7 @@ class SRSSParser extends DomDocument $this->doc->items = []; if ($length > 0) { for($i = 0; $i < $length; $i++) { - $this->doc->items[$i] = SRSSItem::read($items->item($i)); + $this->doc->items[$i] = ItemParser::read($items->item($i)); } } diff --git a/src/SRSS.php b/src/SRSS.php index 954f5bb..4eb353d 100644 --- a/src/SRSS.php +++ b/src/SRSS.php @@ -5,38 +5,17 @@ namespace Shikiryu\SRSS; use Iterator; use Shikiryu\SRSS\Entity\Channel; use Shikiryu\SRSS\Entity\Item; +use Shikiryu\SRSS\Exception\SRSSException; +use Shikiryu\SRSS\Parser\SRSSParser; class SRSS implements Iterator { public Channel $channel; + /** @var Item[] */ public array $items; // array of SRSSItems private int $position; // Iterator position - // lists of possible attributes for RSS - protected $possibleAttr = [ - 'title' => 'nohtml', - 'link' => 'link', - 'description' => 'html', - 'language' => '', - //'language' => 'lang', - 'copyright' => 'nohtml', - 'pubDate' => 'date', - 'lastBuildDate' => 'date', - 'category' => 'nohtml', - 'docs' => 'link', - 'cloud' => '', - 'generator' => 'nohtml', - 'managingEditor' => 'email', - 'webMaster' => 'email', - 'ttl' => 'int', - 'image' => '', - 'rating' => 'nohtml', - //'textInput' => '', - 'skipHours' => 'hour', - 'skipDays' => 'day', - ]; - /** * Constructor */ @@ -78,15 +57,10 @@ class SRSS implements Iterator public function isValid(): bool { $valid = true; - $items = $this->getItems(); - $invalidItems = []; - $i = 1; - foreach ($items as $item) { + foreach ($this->getItems() as $item) { if ($item->isValid() === false) { - $invalidItems[] = $i; $valid = false; } - $i++; } return ($valid && $this->channel->isValid()); @@ -115,11 +89,10 @@ class SRSS implements Iterator if (!property_exists(Channel::class, $name)) { throw new SRSSException($name . ' is not a possible item'); } - $flag = $this->possibleAttr[$name]; - $val = SRSSTools::check($val, $flag); - if (!empty($val)) { + // TODO add validator ? +// if ((new Validator())->isPropertyValid($this->channel, $name)) { $this->channel->{$name} = $val; - } +// } } /** @@ -231,4 +204,9 @@ class SRSS implements Iterator return $doc; } + + public function addItem(Item $rssItem) + { + $this->items[] = $rssItem; + } } diff --git a/src/SRSSTools.php b/src/SRSSTools.php index e7d42a4..9d7ee3e 100644 --- a/src/SRSSTools.php +++ b/src/SRSSTools.php @@ -2,9 +2,11 @@ namespace Shikiryu\SRSS; +use DateTimeInterface; + class SRSSTools { - public static function check($check, $flag) + /*public static function check($check, $flag) { return match ($flag) { 'nohtml' => self::noHTML($check), @@ -19,11 +21,9 @@ class SRSSTools 'media_type' => self::checkMediaType($check), 'media_medium' => self::checkMediaMedium($check), 'bool' => self::checkBool($check), - 'medium_expression' => self::checkMediumExpression($check), - '' => $check, - default => throw new SRSSException('flag ' . $flag . ' does not exist.'), + 'medium_expression' => self::checkMediumExpression($check) }; - } + }*/ /** * format the RSS to the wanted format @@ -61,6 +61,10 @@ class SRSSTools return date("D, d M Y H:i:s T", strtotime($date)); } + if (count(explode(' ', $date)) === 2) { + return \DateTime::createFromFormat('Y-m-d H:i:s', $date)->format(DateTimeInterface::RSS); + } + [$j, $m, $a] = explode('/', $date); return date("D, d M Y H:i:s T", strtotime($a.'-'.$m.'-'.$j)); diff --git a/src/Validator/Validator.php b/src/Validator/Validator.php index 2161006..c16f1de 100644 --- a/src/Validator/Validator.php +++ b/src/Validator/Validator.php @@ -5,9 +5,36 @@ namespace Shikiryu\SRSS\Validator; use DateTimeInterface; use ReflectionClass; use ReflectionException; +use ReflectionProperty; +use Shikiryu\SRSS\Entity\Media\Content; class Validator { + protected ?object $object = null; + /** + * @throws \ReflectionException + */ + public function isPropertyValid($object, $property) + { + $properties = array_filter($this->_getClassProperties(get_class($object)), fn($p) => $p->getName() === $property); + if (count($properties) !== 1) { + return false; + } + + $properties = current($properties); + $propertyValue = $object->{$properties->name}; + $propertyAnnotations = $this->_getPropertyAnnotations($properties); + + if (!in_array('required', $propertyAnnotations, true) && empty($propertyValue)) { + return true; + } + + foreach ($propertyAnnotations as $propertyAnnotation) { + $annotation = explode(' ', $propertyAnnotation); + + $object->validated[$properties->name] = $this->_validateProperty($annotation, $propertyValue); + } + } /** * @throws ReflectionException @@ -18,7 +45,7 @@ class Validator $object = $this->validateObject($object); } - return !in_array(false, $object->validated); + return !in_array(false, $object->validated, true); } /** @@ -26,20 +53,25 @@ class Validator */ public function validateObject($object) { + $this->object = $object; $properties = $this->_getClassProperties(get_class($object)); + $properties = array_map(fn($property) => array_merge( + ['name' => $property->name], + ['rules' => $this->_getPropertyAnnotations($property)] + ), $properties); foreach ($properties as $property) { - $propertyValue = $object->{$property->name}; - $propertyAnnotations = $this->_getPropertyAnnotations($property, get_class($object)); + $propertyValue = $object->{$property['name']}; +// $propertyAnnotations = $this->_getPropertyAnnotations($property, get_class($object)); - if (!in_array('required', $propertyAnnotations) && empty($propertyValue)) { + if (!in_array('required', $property['rules'], true) && empty($propertyValue)) { continue; } - foreach ($propertyAnnotations as $propertyAnnotation) { + foreach ($property['rules'] as $propertyAnnotation) { $annotation = explode(' ', $propertyAnnotation); - $object->validated[$property->name] = $this->_validateProperty($annotation, $propertyValue); + $object->validated[$property['name']] = $this->_validateProperty($annotation, $propertyValue); } } @@ -48,24 +80,29 @@ class Validator private function _validateProperty(array $annotation, $property): bool { - if (count($annotation) === 1) { - return call_user_func([$this, sprintf('_validate%s', ucfirst($annotation[0]))], $property); + if ($annotation[0] === 'var') { + return true; } - return true; // TODO check + if (count($annotation) === 1) { + return $this->{sprintf('_validate%s', ucfirst($annotation[0]))}($property); + } + + $args_annotation = array_splice($annotation, 1); + + return $this->{sprintf('_validate%s', ucfirst($annotation[0]))}($property, ...$args_annotation); } /** + * @return ReflectionProperty[] * @throws ReflectionException */ private function _getClassProperties($class): array { - $ReflectionClass = new ReflectionClass($class); - - return $ReflectionClass->getProperties(); + return (new ReflectionClass($class))->getProperties(); } - private function _getPropertyAnnotations($property, $className): array + private function _getPropertyAnnotations(ReflectionProperty $property): array { preg_match_all('#@(.*?)\n#s', $property->getDocComment(), $annotations); @@ -87,6 +124,21 @@ class Validator return !empty(trim($value)); } + private function _validateRequiredOr($value, $other_values): bool + { + if (!empty($value)) { + return true; + } + + foreach ($other_values as $other_value) { + if (!empty($this->object->$other_value)) { + return true; + } + } + + return false; + } + /** * @param $value * @return bool @@ -143,4 +195,36 @@ class Validator ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] ); } + + private function _validateContentMedia($value) + { + if (is_array($value)) { + foreach ($value as $content) { + if (!$content->isValid()) { + return false; + } + } + return true; + } + + if ($value instanceof Content) { + return $value->isValid(); + } + + return false; + } + + private function _validateMediaType($value): bool + { + return true; + } + private function _validateMediaMedium($value): bool + { + return in_array($value, ['image', 'audio', 'video', 'document', 'executable']); + } + + private function _validateMediaExpression($value): bool + { + return in_array($value, ['sample', 'full', 'nonstop']); + } } \ No newline at end of file diff --git a/tests/BasicBuilderTest.php b/tests/BasicBuilderTest.php new file mode 100644 index 0000000..d3be8ed --- /dev/null +++ b/tests/BasicBuilderTest.php @@ -0,0 +1,40 @@ +title = 'My Blog'; + $srss->description = 'is the best'; + $srss->link = 'http://shikiryu.com/devblog/'; + $items = [ + ['title' => 'title 1', 'link' => 'http://shikiryu.com/devblog/article-1', 'pubDate' => SRSSTools::getRSSDate('2012-03-05 12:02:01'), 'description' => 'description 1'], + ['title' => 'title 2', 'link' => 'http://shikiryu.com/devblog/article-2', 'pubDate' => SRSSTools::getRSSDate('2022-03-05 22:02:02'), 'description' => 'description 2'], + ['title' => 'title 3', 'link' => 'http://shikiryu.com/devblog/article-3', 'pubDate' => SRSSTools::getRSSDate('2032-03-05 32:02:03'), 'description' => 'description 3'], + ['title' => 'title 4', 'link' => 'http://shikiryu.com/devblog/article-4', 'pubDate' => SRSSTools::getRSSDate('2042-03-05 42:02:04'), 'description' => 'description 4'], + ]; + foreach ($items as $item) { + $rssItem = new Item(); + $rssItem->title = $item['title']; + $rssItem->link = $item['link']; + $rssItem->pubDate = $item['pubDate']; + $rssItem->description = $item['description']; + $srss->addItem($rssItem); + } + + self::assertTrue($srss->isValid()); + + $filepath = __DIR__.'/resources/tmp/build/testCreateBasicRSS.rss'; + $builder = new SRSSBuilder(); + $builder->build($srss, $filepath); + + self::assertFileExists($filepath); + } +} \ No newline at end of file diff --git a/tests/BasicReader.php b/tests/BasicReaderTest.php similarity index 90% rename from tests/BasicReader.php rename to tests/BasicReaderTest.php index 8e4834e..331ecde 100644 --- a/tests/BasicReader.php +++ b/tests/BasicReaderTest.php @@ -1,10 +1,10 @@ title); - self::assertTrue($rss->channel->isValid()); + self::assertTrue($rss->isValid()); } public function testRssNotFound() @@ -41,6 +41,6 @@ class BasicReader extends TestCase self::assertEquals('http://liftoff.msfc.nasa.gov/news/2003/news-laundry.asp', $rss->getLast()->link); self::assertEquals('Fri, 30 May 2003 11:06:42 GMT', $rss->getItem(2)->pubDate); - self::assertTrue($rss->channel->isValid()); + self::assertTrue($rss->isValid()); } } \ No newline at end of file diff --git a/tests/MediaTest.php b/tests/MediaTest.php index 67511d0..0e88c10 100644 --- a/tests/MediaTest.php +++ b/tests/MediaTest.php @@ -7,25 +7,25 @@ class MediaTest extends TestCase { public function testImages() { - $rss = SRSS::read('resources/media/cnn.xml'); + $rss = SRSS::read(__DIR__.'/resources/media/cnn.xml'); self::assertEquals('CNN.com - RSS Channel - Entertainment', $rss->title); $first_item = $rss->getFirst(); self::assertEquals('Kirstie Alley, \'Cheers\' and \'Veronica\'s Closet\' star, dead at 71', $first_item->title); self::assertEquals('https://cdn.cnn.com/cnnnext/dam/assets/221205172141-kirstie-alley-2005-super-169.jpg', $first_item->medias[0]->url); - self::assertTrue($rss->channel->isValid(), var_export($rss->channel->validated, true)); + self::assertTrue($rss->isValid(), var_export($rss->channel->validated, true)); } public function testMusicVideo() { - $rss = SRSS::read('resources/media/music-video.xml'); + $rss = SRSS::read(__DIR__.'/resources/media/music-video.xml'); self::assertEquals('Music Videos 101', $rss->title); self::assertCount(1, $rss->items); $first_item = $rss->getFirst(); self::assertEquals('http://www.foo.com/movie.mov', $first_item->medias[0]->url); - self::assertTrue($rss->channel->isValid()); + self::assertTrue($rss->isValid()); } } \ No newline at end of file From 5de5993e2bbe181fdb6646bbb5cc473e755e2791 Mon Sep 17 00:00:00 2001 From: Clement Desmidt Date: Tue, 11 Apr 2023 14:07:13 +0200 Subject: [PATCH 06/16] =?UTF-8?q?=E2=9C=85=20Add=20original=20cases?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- phpunit.xml | 9 +----- src/Builder/SRSSBuilder.php | 49 +++++++------------------------- src/SRSS.php | 45 +++++++++++++++++++++++++++-- tests/BasicBuilderTest.php | 15 ++++++++-- tests/OriginalReaderSRSSTest.php | 41 ++++++++++++++++++++++++++ tests/OriginalWriterSRSSTest.php | 45 +++++++++++++++++++++++++++++ 6 files changed, 152 insertions(+), 52 deletions(-) create mode 100644 tests/OriginalReaderSRSSTest.php create mode 100644 tests/OriginalWriterSRSSTest.php diff --git a/phpunit.xml b/phpunit.xml index e340815..3edcd2e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -8,8 +8,7 @@ convertNoticesToExceptions = "true" convertWarningsToExceptions = "true" processIsolation = "false" - stopOnFailure = "false" - syntaxCheck = "false"> + stopOnFailure = "false"> @@ -17,12 +16,6 @@ - - - src/ - - - diff --git a/src/Builder/SRSSBuilder.php b/src/Builder/SRSSBuilder.php index 5a6f1ae..19ac73d 100644 --- a/src/Builder/SRSSBuilder.php +++ b/src/Builder/SRSSBuilder.php @@ -6,13 +6,12 @@ use DOMDocument; use DOMElement; use Shikiryu\SRSS\Entity\Channel; use Shikiryu\SRSS\Entity\Item; -use Shikiryu\SRSS\Parser\ItemParser; use Shikiryu\SRSS\SRSS; use Shikiryu\SRSS\SRSSTools; class SRSSBuilder extends DomDocument { - public function build(SRSS $srss, string $filepath) + private function buildRSS(SRSS $srss) { $root = $this->createElement('rss'); $root->setAttribute('version', '2.0'); @@ -25,53 +24,27 @@ class SRSSBuilder extends DomDocument $root->appendChild($channel); $this->appendChild($root); $this->encoding = 'UTF-8'; - $this->generator = 'Shikiryu RSS'; + $srss->generator = 'Shikiryu RSS'; $this->formatOutput = true; $this->preserveWhiteSpace = false; // $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html'; + return $this; + } + public function build(SRSS $srss, string $filepath) + { + $this->buildRSS($srss); + $this->save($filepath); } /** - * add a SRSS Item as an item into current RSS as first item - * - * @param ItemParser $item + * @return false|string */ - public function addItemBefore(ItemParser $item) + public function show(SRSS $srss) { - $node = $this->importNode($item->getItem(), true); - $items = $this->getElementsByTagName('item'); - if($items->length != 0){ - $firstNode = $items->item(0); - if($firstNode != null) - $firstNode->parentNode->insertBefore($node, $firstNode); - else - $this->addItem($item); - } - else - $this->addItem($item); - } + $this->buildRSS($srss); - /** - * add a SRSS Item as an item into current RSS - * - * @param ItemParser $item - */ - public function addItem(ItemParser $item) - { - $node = $this->importNode($item->getItem(), true); - $channel = $this->_getChannel(); - $channel->appendChild($node); - } - - /** - * display XML - * see DomDocument's docs - */ - public function show() - { - // TODO build return $this->saveXml(); } diff --git a/src/SRSS.php b/src/SRSS.php index 4eb353d..3e047ca 100644 --- a/src/SRSS.php +++ b/src/SRSS.php @@ -3,6 +3,7 @@ namespace Shikiryu\SRSS; use Iterator; +use Shikiryu\SRSS\Builder\SRSSBuilder; use Shikiryu\SRSS\Entity\Channel; use Shikiryu\SRSS\Entity\Item; use Shikiryu\SRSS\Exception\SRSSException; @@ -118,7 +119,7 @@ class SRSS implements Iterator /** * current from Iterator */ - public function current(): mixed + public function current(): Item { return $this->items[$this->position]; } @@ -202,11 +203,49 @@ class SRSS implements Iterator $doc['items'][] = $item->toArray(); } - return $doc; + return array_filter($doc); } - public function addItem(Item $rssItem) + /** + * @param \Shikiryu\SRSS\Entity\Item $rssItem + * + * @return array|\Shikiryu\SRSS\Entity\Item[] + */ + public function addItem(Item $rssItem): array { $this->items[] = $rssItem; + + return $this->items; } + + /** + * @param \Shikiryu\SRSS\Entity\Item $firstItem + * + * @return array|\Shikiryu\SRSS\Entity\Item[] + */ + public function addItemBefore(Item $firstItem): array + { + array_unshift($this->items, $firstItem); + + return $this->items; + } + + /** + * @param string $path + * + * @return void + */ + public function save(string $path): void + { + (new SRSSBuilder('1.0', 'UTF-8'))->build($this, $path); + } + + /** + * @return false|string + */ + public function show() + { + return (new SRSSBuilder('1.0', 'UTF-8'))->show($this); + } + } diff --git a/tests/BasicBuilderTest.php b/tests/BasicBuilderTest.php index d3be8ed..b3a433c 100644 --- a/tests/BasicBuilderTest.php +++ b/tests/BasicBuilderTest.php @@ -8,6 +8,14 @@ use Shikiryu\SRSS\SRSSTools; class BasicBuilderTest extends TestCase { + private string $saved = __DIR__.'/resources/tmp/build/testCreateBasicRSS.rss'; + protected function tearDown(): void + { + parent::tearDown(); + if (file_exists($this->saved)) { + unlink($this->saved); + } + } public function testCreateBasicRSS() { $srss = SRSS::create(); @@ -31,10 +39,11 @@ class BasicBuilderTest extends TestCase self::assertTrue($srss->isValid()); - $filepath = __DIR__.'/resources/tmp/build/testCreateBasicRSS.rss'; $builder = new SRSSBuilder(); - $builder->build($srss, $filepath); + $builder->build($srss, $this->saved); - self::assertFileExists($filepath); + self::assertFileExists($this->saved); + + self::assertIsString($srss->show()); } } \ No newline at end of file diff --git a/tests/OriginalReaderSRSSTest.php b/tests/OriginalReaderSRSSTest.php new file mode 100644 index 0000000..d702121 --- /dev/null +++ b/tests/OriginalReaderSRSSTest.php @@ -0,0 +1,41 @@ +saved)) { + unlink($this->saved); + } + } + + public function testOriginalReader() + { + $rss = SRSS::read($this->original); + self::assertEquals('Liftoff News', $rss->title); + + $article1 = $rss->getItem(1); + $articleFirst = $rss->getFirst(); + self::assertEquals($article1, $articleFirst); + + $links = []; + foreach($rss as $article) { + $links[] = sprintf('%s %s', $article->link, SRSSTools::formatDate('d/m/y', $article->pubDate), $article->title); + } + self::assertCount(4, $links, var_export($links, true)); + + $rssArray = $rss->toArray(); + self::assertCount(11, $rssArray, var_export($rssArray, true)); // 11 elements in RSS + + $rss->save($this->saved); + + self::assertFileExists($this->saved); + } +} \ No newline at end of file diff --git a/tests/OriginalWriterSRSSTest.php b/tests/OriginalWriterSRSSTest.php new file mode 100644 index 0000000..40b1f36 --- /dev/null +++ b/tests/OriginalWriterSRSSTest.php @@ -0,0 +1,45 @@ +title = 'My Awesome Blog'; + $rss->link = 'http://shikiryu.com/devblog/'; + $rss->description = 'is awesome'; + + $items = [ + ['title' => 'title 1', 'link' => 'http://shikiryu.com/devblog/article-1', 'pubDate' => SRSSTools::getRSSDate('2012-03-05 12:02:01'), 'description' => 'description 1'], + ['title' => 'title 2', 'link' => 'http://shikiryu.com/devblog/article-2', 'pubDate' => SRSSTools::getRSSDate('2022-03-05 22:02:02'), 'description' => 'description 2'], + ['title' => 'title 3', 'link' => 'http://shikiryu.com/devblog/article-3', 'pubDate' => SRSSTools::getRSSDate('2032-03-05 32:02:03'), 'description' => 'description 3'], + ['title' => 'title 4', 'link' => 'http://shikiryu.com/devblog/article-4', 'pubDate' => SRSSTools::getRSSDate('2042-03-05 42:02:04'), 'description' => 'description 4'], + ]; + + foreach($items as $item){ + $rssitem = new Item(); + $rssitem->title = $item["title"]; + $rssitem->link = $item['link']; + $rssitem->pubDate = $item["pubDate"]; + $rssitem->description = $item["description"]; + $rss->addItem($rssitem); + } + + $firstItem = new Item(); + $firstItem->title = 'title 0'; + $firstItem->link = 'http://shikiryu.com/devblog/article-0'; + $firstItem->pubDate = SRSSTools::getRSSDate('2011-03-05 12:02:01'); + $firstItem->description = 'description 0'; + $rss->addItemBefore($firstItem); + + self::assertCount(5, $rss->items, var_export($rss->items, true)); + self::assertEquals('title 0', $rss->getFirst()->title, var_export($rss->items, true)); + self::assertEquals('title 1', $rss->getItem(2)->title, var_export($rss->items, true)); + + } +} \ No newline at end of file From f1816dec0c0ffed4766932e776b6fc042b55cb73 Mon Sep 17 00:00:00 2001 From: Shikiryu Date: Tue, 11 Apr 2023 23:49:03 +0200 Subject: [PATCH 07/16] :construction: Add some validations and corrections --- README.md | 6 +++--- src/Builder/SRSSBuilder.php | 3 ++- src/Entity/Channel/Image.php | 6 +++++- src/Entity/Item.php | 9 +++++++-- src/Entity/Media/Content.php | 6 +++++- src/Parser/ItemParser.php | 2 +- src/Parser/SRSSParser.php | 15 ++++++++------- src/SRSS.php | 17 ++++++++++------- src/SRSSTools.php | 3 ++- src/Validator/Validator.php | 7 +++++-- 10 files changed, 48 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 58cd595..eb53ea0 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ First, we need to load the RSS : $rss = SRSS::read('http://exemple.com/rss.xml'); -Easy, right? Then you can extract general informations : +Easy, right? Then you can extract general information : echo $rss->title; // will display blog title @@ -43,7 +43,7 @@ First, we need to initialize the RSS : $rss = SRSS::create(); -Easy, right? Then you can add general informations : +Easy, right? Then you can add general information : $rss->title = 'My Awesome Blog'; $rss->link = 'http://shikiryu.com/devblog/'; @@ -73,4 +73,4 @@ The other one does the opposite and add the next item in top of your RSS ---------------------------------- Contact : -http://shikiryu.com/contact +https://shikiryu.com/contact diff --git a/src/Builder/SRSSBuilder.php b/src/Builder/SRSSBuilder.php index 19ac73d..e1a1b6c 100644 --- a/src/Builder/SRSSBuilder.php +++ b/src/Builder/SRSSBuilder.php @@ -17,6 +17,8 @@ class SRSSBuilder extends DomDocument $root->setAttribute('version', '2.0'); $channel = $this->createElement('channel'); + $srss->channel->generator = 'Shikiryu RSS'; + $this->appendChannelToDom($srss->channel, $channel); $this->appendItemsToDom($srss->items, $channel); @@ -24,7 +26,6 @@ class SRSSBuilder extends DomDocument $root->appendChild($channel); $this->appendChild($root); $this->encoding = 'UTF-8'; - $srss->generator = 'Shikiryu RSS'; $this->formatOutput = true; $this->preserveWhiteSpace = false; // $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html'; diff --git a/src/Entity/Channel/Image.php b/src/Entity/Channel/Image.php index f4fac3a..e7f1757 100644 --- a/src/Entity/Channel/Image.php +++ b/src/Entity/Channel/Image.php @@ -38,7 +38,11 @@ class Image extends HasValidator implements SRSSElement public function isValid(): bool { - return (new Validator())->isObjectValid($this); + try { + return (new Validator())->isObjectValid($this); + } catch (\ReflectionException $e) { + return false; + } } public function toArray(): array diff --git a/src/Entity/Item.php b/src/Entity/Item.php index 2831f2f..6b795b6 100644 --- a/src/Entity/Item.php +++ b/src/Entity/Item.php @@ -2,6 +2,7 @@ namespace Shikiryu\SRSS\Entity; +use Shikiryu\SRSS\Entity\Media\Content; use Shikiryu\SRSS\Validator\HasValidator; use Shikiryu\SRSS\Validator\Validator; @@ -50,14 +51,18 @@ class Item extends HasValidator implements SRSSElement public ?string $source = null; /** - * @var \Shikiryu\SRSS\Entity\Media\Content[] + * @var Content[] * @contentMedia */ public array $medias = []; public function isValid(): bool { - return (new Validator())->isObjectValid($this); + try { + return (new Validator())->isObjectValid($this); + } catch (\ReflectionException $e) { + return false; + } } public function toArray(): array diff --git a/src/Entity/Media/Content.php b/src/Entity/Media/Content.php index bfbd14a..a776957 100644 --- a/src/Entity/Media/Content.php +++ b/src/Entity/Media/Content.php @@ -67,7 +67,11 @@ class Content extends HasValidator implements SRSSElement public function isValid(): bool { - return (new Validator())->isObjectValid($this); + try { + return (new Validator())->isObjectValid($this); + } catch (\ReflectionException $e) { + return false; + } } public function toArray(): array diff --git a/src/Parser/ItemParser.php b/src/Parser/ItemParser.php index f65649c..e40f063 100644 --- a/src/Parser/ItemParser.php +++ b/src/Parser/ItemParser.php @@ -84,7 +84,7 @@ class ItemParser extends DomDocument /** * getter of item DomElement */ - public function getItem(): ?\DOMNode + public function getItem(): ?DOMNode { $this->appendChild($this->node); diff --git a/src/Parser/SRSSParser.php b/src/Parser/SRSSParser.php index a7f9076..2936ad8 100644 --- a/src/Parser/SRSSParser.php +++ b/src/Parser/SRSSParser.php @@ -3,6 +3,7 @@ namespace Shikiryu\SRSS\Parser; use DOMDocument; +use DOMNode; use DOMNodeList; use DOMXPath; use Shikiryu\SRSS\Entity\Channel; @@ -39,8 +40,8 @@ class SRSSParser extends DomDocument /** * @param string $link * - * @return \Shikiryu\SRSS\SRSS - * @throws \Shikiryu\SRSS\Exception\SRSSException + * @return SRSS + * @throws SRSSException */ public function parse(string $link) { @@ -48,7 +49,7 @@ class SRSSParser extends DomDocument $channel = $this->getElementsByTagName('channel'); if($channel->length === 1){ // Good URL and good RSS $this->_loadAttributes(); // loading channel properties - $this->getItems(); // loading all items + $this->parseItems(); // loading all items return $this->doc; } @@ -61,9 +62,9 @@ class SRSSParser extends DomDocument /** * @return Item[] - * @throws \Shikiryu\SRSS\Exception\SRSSException + * @throws SRSSException */ - private function getItems(): array + private function parseItems(): array { $channel = $this->_getChannel(); /** @var DOMNodeList $items */ @@ -108,10 +109,10 @@ class SRSSParser extends DomDocument /** * getter of current RSS channel - * @return \DOMNode + * @return DOMNode * @throws SRSSException */ - private function _getChannel(): \DOMNode + private function _getChannel(): DOMNode { $channel = $this->getElementsByTagName('channel'); if($channel->length != 1) { diff --git a/src/SRSS.php b/src/SRSS.php index 3e047ca..18da1d8 100644 --- a/src/SRSS.php +++ b/src/SRSS.php @@ -39,7 +39,6 @@ class SRSS implements Iterator /** * @return SRSS - * @throws \DOMException */ public static function create() { @@ -57,14 +56,18 @@ class SRSS implements Iterator */ public function isValid(): bool { - $valid = true; - foreach ($this->getItems() as $item) { - if ($item->isValid() === false) { - $valid = false; + try { + $valid = true; + foreach ($this->getItems() as $item) { + if ($item->isValid() === false) { + $valid = false; + } } - } - return ($valid && $this->channel->isValid()); + return ($valid && $this->channel->isValid()); + } catch (\ReflectionException $e) { + return false; + } } /** diff --git a/src/SRSSTools.php b/src/SRSSTools.php index 9d7ee3e..f677047 100644 --- a/src/SRSSTools.php +++ b/src/SRSSTools.php @@ -2,6 +2,7 @@ namespace Shikiryu\SRSS; +use DateTime; use DateTimeInterface; class SRSSTools @@ -62,7 +63,7 @@ class SRSSTools } if (count(explode(' ', $date)) === 2) { - return \DateTime::createFromFormat('Y-m-d H:i:s', $date)->format(DateTimeInterface::RSS); + return DateTime::createFromFormat('Y-m-d H:i:s', $date)->format(DateTimeInterface::RSS); } [$j, $m, $a] = explode('/', $date); diff --git a/src/Validator/Validator.php b/src/Validator/Validator.php index c16f1de..4652d28 100644 --- a/src/Validator/Validator.php +++ b/src/Validator/Validator.php @@ -2,6 +2,7 @@ namespace Shikiryu\SRSS\Validator; +use DateTime; use DateTimeInterface; use ReflectionClass; use ReflectionException; @@ -12,7 +13,7 @@ class Validator { protected ?object $object = null; /** - * @throws \ReflectionException + * @throws ReflectionException */ public function isPropertyValid($object, $property) { @@ -34,6 +35,8 @@ class Validator $object->validated[$properties->name] = $this->_validateProperty($annotation, $propertyValue); } + + return false; } /** @@ -173,7 +176,7 @@ class Validator private function _validateDate($value): bool { - return \DateTime::createFromFormat(DateTimeInterface::RSS, $value) !== false; + return DateTime::createFromFormat(DateTimeInterface::RSS, $value) !== false; } private function _validateHour($value): bool From cb6fff0daedb6304ab8baa018824b43c8c1b7c8f Mon Sep 17 00:00:00 2001 From: Shikiryu Date: Wed, 12 Apr 2023 00:27:35 +0200 Subject: [PATCH 08/16] =?UTF-8?q?=E2=9C=85=20Add=20new=20exceptions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ChannelNotFoundInRSSException.php | 12 ++++++++ src/Exception/PropertyNotFoundException.php | 15 ++++++++++ src/Exception/UnreadableRSSException.php | 12 ++++++++ src/Parser/SRSSParser.php | 12 +++++--- src/SRSS.php | 21 +++++++++----- tests/BasicReaderTest.php | 7 +---- tests/ExceptionTest.php | 29 +++++++++++++++++++ tests/resources/invalid-no-channel.xml | 18 ++++++++++++ 8 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 src/Exception/ChannelNotFoundInRSSException.php create mode 100644 src/Exception/PropertyNotFoundException.php create mode 100644 src/Exception/UnreadableRSSException.php create mode 100644 tests/ExceptionTest.php create mode 100644 tests/resources/invalid-no-channel.xml diff --git a/src/Exception/ChannelNotFoundInRSSException.php b/src/Exception/ChannelNotFoundInRSSException.php new file mode 100644 index 0000000..9f4479f --- /dev/null +++ b/src/Exception/ChannelNotFoundInRSSException.php @@ -0,0 +1,12 @@ + not found', $file)); + } + +} \ No newline at end of file diff --git a/src/Exception/PropertyNotFoundException.php b/src/Exception/PropertyNotFoundException.php new file mode 100644 index 0000000..58b39b4 --- /dev/null +++ b/src/Exception/PropertyNotFoundException.php @@ -0,0 +1,15 @@ +load($link)) { // We don't want the warning in case of bad XML. Let's manage it with an exception. $channel = $this->getElementsByTagName('channel'); @@ -54,10 +58,10 @@ class SRSSParser extends DomDocument return $this->doc; } - throw new SRSSException('invalid file '.$link); + throw new ChannelNotFoundInRSSException($link); } - throw new SRSSException('Can not open file '.$link); + throw new UnreadableRSSException($link); } /** @@ -116,7 +120,7 @@ class SRSSParser extends DomDocument { $channel = $this->getElementsByTagName('channel'); if($channel->length != 1) { - throw new SRSSException('channel node not created, or too many channel nodes'); + throw new ChannelNotFoundInRSSException('channel node not created, or too many channel nodes'); } return $channel->item(0); diff --git a/src/SRSS.php b/src/SRSS.php index 18da1d8..fad830a 100644 --- a/src/SRSS.php +++ b/src/SRSS.php @@ -3,15 +3,20 @@ namespace Shikiryu\SRSS; use Iterator; +use ReflectionException; use Shikiryu\SRSS\Builder\SRSSBuilder; use Shikiryu\SRSS\Entity\Channel; use Shikiryu\SRSS\Entity\Item; +use Shikiryu\SRSS\Exception\ChannelNotFoundInRSSException; +use Shikiryu\SRSS\Exception\PropertyNotFoundException; use Shikiryu\SRSS\Exception\SRSSException; +use Shikiryu\SRSS\Exception\UnreadableRSSException; use Shikiryu\SRSS\Parser\SRSSParser; class SRSS implements Iterator { public Channel $channel; + /** @var Item[] */ public array $items; // array of SRSSItems @@ -30,6 +35,8 @@ class SRSS implements Iterator * @param string $link url of the rss * * @return SRSS + * @throws ChannelNotFoundInRSSException + * @throws UnreadableRSSException * @throws SRSSException */ public static function read(string $link): SRSS @@ -40,7 +47,7 @@ class SRSS implements Iterator /** * @return SRSS */ - public static function create() + public static function create(): SRSS { $doc = new SRSS; @@ -65,7 +72,7 @@ class SRSS implements Iterator } return ($valid && $this->channel->isValid()); - } catch (\ReflectionException $e) { + } catch (ReflectionException $e) { return false; } } @@ -91,7 +98,7 @@ class SRSS implements Iterator public function __set($name, $val) { if (!property_exists(Channel::class, $name)) { - throw new SRSSException($name . ' is not a possible item'); + throw new PropertyNotFoundException(Channel::class, $name); } // TODO add validator ? // if ((new Validator())->isPropertyValid($this->channel, $name)) { @@ -210,9 +217,9 @@ class SRSS implements Iterator } /** - * @param \Shikiryu\SRSS\Entity\Item $rssItem + * @param Item $rssItem * - * @return array|\Shikiryu\SRSS\Entity\Item[] + * @return array|Item[] */ public function addItem(Item $rssItem): array { @@ -222,9 +229,9 @@ class SRSS implements Iterator } /** - * @param \Shikiryu\SRSS\Entity\Item $firstItem + * @param Item $firstItem * - * @return array|\Shikiryu\SRSS\Entity\Item[] + * @return array|Item[] */ public function addItemBefore(Item $firstItem): array { diff --git a/tests/BasicReaderTest.php b/tests/BasicReaderTest.php index 331ecde..e6444f7 100644 --- a/tests/BasicReaderTest.php +++ b/tests/BasicReaderTest.php @@ -2,6 +2,7 @@ use PHPUnit\Framework\TestCase; use Shikiryu\SRSS\Exception\SRSSException; +use Shikiryu\SRSS\Exception\UnreadableRSSException; use Shikiryu\SRSS\SRSS; class BasicReaderTest extends TestCase @@ -17,12 +18,6 @@ class BasicReaderTest extends TestCase self::assertTrue($rss->isValid()); } - public function testRssNotFound() - { - $this->expectException(SRSSException::class); - $rss = SRSS::read('not_found.xml'); - } - public function testSpecificationExampleRSS() { $rss = SRSS::read(__DIR__.'/resources/harvard.xml'); diff --git a/tests/ExceptionTest.php b/tests/ExceptionTest.php new file mode 100644 index 0000000..004f4dc --- /dev/null +++ b/tests/ExceptionTest.php @@ -0,0 +1,29 @@ +expectException(PropertyNotFoundException::class); + $srss->notfound = 'true'; + } + + public function testRssNotFound() + { + $this->expectException(UnreadableRSSException::class); + $rss = SRSS::read('not_found.xml'); + } + + public function testMissingChannel() + { + $this->expectException(ChannelNotFoundInRSSException::class); + $rss = SRSS::read(__DIR__ . '/resources/invalid-no-channel.xml'); + } +} \ No newline at end of file diff --git a/tests/resources/invalid-no-channel.xml b/tests/resources/invalid-no-channel.xml new file mode 100644 index 0000000..e7cf96f --- /dev/null +++ b/tests/resources/invalid-no-channel.xml @@ -0,0 +1,18 @@ + + + + test Home Page + https://www.test.com + Free web building tutorials + + RSS Tutorial + https://www.test.com/xml/xml_rss.asp + New RSS tutorial on test + + + XML Tutorial + https://www.test.com/xml + New XML tutorial on test + + + \ No newline at end of file From cc7a1ec8315a2f355e19bb9bafab8bc5fe30fd48 Mon Sep 17 00:00:00 2001 From: Shikiryu Date: Wed, 12 Apr 2023 00:28:14 +0200 Subject: [PATCH 09/16] :construction: Add complex types from docs --- src/Entity/Channel.php | 11 ++--- src/Entity/Channel/Category.php | 33 ++++++++++++++ src/Entity/Channel/Cloud.php | 47 ++++++++++++++++++++ src/Entity/Item.php | 22 ++++++---- src/Entity/Item/Category.php | 33 ++++++++++++++ src/Entity/Item/Enclosure.php | 39 +++++++++++++++++ src/Entity/Item/Source.php | 34 ++++++++++++++ tests/resources/cloud.xml | 78 +++++++++++++++++++++++++++++++++ 8 files changed, 283 insertions(+), 14 deletions(-) create mode 100644 src/Entity/Channel/Category.php create mode 100644 src/Entity/Channel/Cloud.php create mode 100644 src/Entity/Item/Category.php create mode 100644 src/Entity/Item/Enclosure.php create mode 100644 src/Entity/Item/Source.php create mode 100644 tests/resources/cloud.xml diff --git a/src/Entity/Channel.php b/src/Entity/Channel.php index d4d958f..59aee30 100644 --- a/src/Entity/Channel.php +++ b/src/Entity/Channel.php @@ -3,6 +3,8 @@ namespace Shikiryu\SRSS\Entity; use ReflectionException; +use Shikiryu\SRSS\Entity\Channel\Category; +use Shikiryu\SRSS\Entity\Channel\Cloud; use Shikiryu\SRSS\Entity\Channel\Image; use Shikiryu\SRSS\Validator\HasValidator; use Shikiryu\SRSS\Validator\Validator; @@ -54,9 +56,9 @@ class Channel extends HasValidator implements SRSSElement */ public ?string $lastBuildDate = null; /** - * TODO should be an array + * @var Category[] */ - public ?string $category = null; + public ?array $category = null; /** * @nohtml */ @@ -66,10 +68,9 @@ class Channel extends HasValidator implements SRSSElement */ public ?string $docs = null; /** - * @var string|null - * TODO validator + * @var Cloud|null */ - public ?string $cloud = null; + public ?Cloud $cloud = null; /** * @int */ diff --git a/src/Entity/Channel/Category.php b/src/Entity/Channel/Category.php new file mode 100644 index 0000000..acfc4f1 --- /dev/null +++ b/src/Entity/Channel/Category.php @@ -0,0 +1,33 @@ +isObjectValid($this); + } catch (\ReflectionException $e) { + return false; + } + } + + public function toArray(): array + { + return get_object_vars($this); + } +} \ No newline at end of file diff --git a/src/Entity/Channel/Cloud.php b/src/Entity/Channel/Cloud.php new file mode 100644 index 0000000..95f991f --- /dev/null +++ b/src/Entity/Channel/Cloud.php @@ -0,0 +1,47 @@ + + + public function isValid(): bool + { + try { + return (new Validator())->isObjectValid($this); + } catch (\ReflectionException $e) { + return false; + } + } + + public function toArray(): array + { + return get_object_vars($this); + } +} \ No newline at end of file diff --git a/src/Entity/Item.php b/src/Entity/Item.php index 6b795b6..1149870 100644 --- a/src/Entity/Item.php +++ b/src/Entity/Item.php @@ -2,6 +2,9 @@ namespace Shikiryu\SRSS\Entity; +use Shikiryu\SRSS\Entity\Item\Category; +use Shikiryu\SRSS\Entity\Item\Enclosure; +use Shikiryu\SRSS\Entity\Item\Source; use Shikiryu\SRSS\Entity\Media\Content; use Shikiryu\SRSS\Validator\HasValidator; use Shikiryu\SRSS\Validator\Validator; @@ -28,27 +31,28 @@ class Item extends HasValidator implements SRSSElement * @email */ public ?string $author = null; - /* - * TODO can be multiple with attributes and all + /** + * @var Category[] */ - public ?string $category = null; + public ?array $category = null; /** * @url */ public ?string $comments = null; - /* - * TODO 1 attributes and 1 value + /** + * @var Enclosure|null */ - public ?string $enclosure = null; + public ?Enclosure $enclosure = null; public ?string $guid = null; /** * @date */ public ?string $pubDate = null; - /* - * TODO 1 attributes and 1 value + + /** + * @var Source|null */ - public ?string $source = null; + public ?Source $source = null; /** * @var Content[] diff --git a/src/Entity/Item/Category.php b/src/Entity/Item/Category.php new file mode 100644 index 0000000..6d8c318 --- /dev/null +++ b/src/Entity/Item/Category.php @@ -0,0 +1,33 @@ +isObjectValid($this); + } catch (\ReflectionException $e) { + return false; + } + } + + public function toArray(): array + { + return get_object_vars($this); + } +} \ No newline at end of file diff --git a/src/Entity/Item/Enclosure.php b/src/Entity/Item/Enclosure.php new file mode 100644 index 0000000..4590b23 --- /dev/null +++ b/src/Entity/Item/Enclosure.php @@ -0,0 +1,39 @@ +isObjectValid($this); + } catch (\ReflectionException $e) { + return false; + } + } + + public function toArray(): array + { + return get_object_vars($this); + } +} \ No newline at end of file diff --git a/src/Entity/Item/Source.php b/src/Entity/Item/Source.php new file mode 100644 index 0000000..ddbaf55 --- /dev/null +++ b/src/Entity/Item/Source.php @@ -0,0 +1,34 @@ +isObjectValid($this); + } catch (\ReflectionException $e) { + return false; + } + } + + public function toArray(): array + { + return get_object_vars($this); + } +} \ No newline at end of file diff --git a/tests/resources/cloud.xml b/tests/resources/cloud.xml new file mode 100644 index 0000000..2c4a12e --- /dev/null +++ b/tests/resources/cloud.xml @@ -0,0 +1,78 @@ + + + + + Dave's Handsome Radio Blog! + http://radio.weblogs.com/0001015/ + A non-smoking weblog since June 14, 2002. + en-us + Copyright 2003 Dave Winer + Mon, 13 Oct 2003 18:54:10 GMT + http://backend.userland.com/rss + Radio UserLand v8.0.5 + dave@userland.com + dave@userland.com + rssUpdates + + 0 + 23 + 1 + 2 + 3 + 22 + 17 + 11 + + + 60 + + http://radio.weblogs.com/0001015/2003/10/13.html#a1866 + <A href="http://andrea.editthispage.com/"><IMG height=100 alt="A picture named andrea.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/13/andrea.jpg" width=45 align=right vspace=5 border=0></A><A href="http://andrea.editthispage.com/"><IMG height=100 alt="A picture named andrea.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/13/andrea.jpg" width=45 align=right vspace=5 border=0></A><A href="http://andrea.editthispage.com/"><IMG height=100 alt="A picture named andrea.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/13/andrea.jpg" width=45 align=right vspace=5 border=0></A><A href="http://andrea.editthispage.com/"><IMG height=100 alt="A picture named andrea.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/13/andrea.jpg" width=45 align=right vspace=5 border=0></A><A href="http://andrea.editthispage.com/"><IMG height=100 alt="A picture named andrea.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/13/andrea.jpg" width=45 align=right vspace=5 border=0></A> + http://radio.weblogs.com/0001015/2003/10/13.html#a1866 + Mon, 13 Oct 2003 18:54:09 GMT + http://blogs.law.harvard.edu/comments?u=1015&amp;p=1866&amp;link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F10%2F13.html%23a1866 + + + http://radio.weblogs.com/0001015/2003/10/12.html#a1865 + <A href="http://blogs.law.harvard.edu/crimson1/pictures/viewer$747"><IMG height=89 alt="A picture named cousinJoey.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/12/cousinJoey.jpg" width=65 align=right vspace=5 border=0></A><A href="http://blogs.law.harvard.edu/crimson1/pictures/viewer$747"><IMG height=89 alt="A picture named cousinJoey.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/12/cousinJoey.jpg" width=65 align=right vspace=5 border=0></A><A href="http://blogs.law.harvard.edu/crimson1/pictures/viewer$747"><IMG height=89 alt="A picture named cousinJoey.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/12/cousinJoey.jpg" width=65 align=right vspace=5 border=0></A><A href="http://blogs.law.harvard.edu/crimson1/pictures/viewer$747"><IMG height=89 alt="A picture named cousinJoey.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/12/cousinJoey.jpg" width=65 align=right vspace=5 border=0></A> + http://radio.weblogs.com/0001015/2003/10/12.html#a1865 + Sun, 12 Oct 2003 20:42:30 GMT + http://blogs.law.harvard.edu/comments?u=1015&amp;p=1865&amp;link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F10%2F12.html%23a1865 + + + http://radio.weblogs.com/0001015/2003/10/08.html#a1864 + <A href="http://radio.weblogs.com/0001015/images/2002/06/04/yourarespecial.gif"><IMG height=52 alt="You are so special." hspace=15 src="http://radio.weblogs.com/0001015/images/2002/06/04/mrrogers.gif" width=45 align=right vspace=5 border=0></A><A href="http://radio.weblogs.com/0001015/images/2002/06/04/yourarespecial.gif"><IMG height=52 alt="You are so special." hspace=15 src="http://radio.weblogs.com/0001015/images/2002/06/04/mrrogers.gif" width=45 align=right vspace=5 border=0></A><A href="http://radio.weblogs.com/0001015/images/2002/06/04/yourarespecial.gif"><IMG height=52 alt="You are so special." hspace=15 src="http://radio.weblogs.com/0001015/images/2002/06/04/mrrogers.gif" width=45 align=right vspace=5 border=0></A><A href="http://radio.weblogs.com/0001015/images/2002/06/04/yourarespecial.gif"><IMG height=52 alt="You are so special." hspace=15 src="http://radio.weblogs.com/0001015/images/2002/06/04/mrrogers.gif" width=45 align=right vspace=5 border=0></A><A href="http://radio.weblogs.com/0001015/images/2002/06/04/yourarespecial.gif"><IMG height=52 alt="You are so special." hspace=15 src="http://radio.weblogs.com/0001015/images/2002/06/04/mrrogers.gif" width=45 align=right vspace=5 border=0></A> + http://radio.weblogs.com/0001015/2003/10/08.html#a1864 + Wed, 08 Oct 2003 13:22:16 GMT + http://blogs.law.harvard.edu/comments?u=1015&amp;p=1864&amp;link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F10%2F08.html%23a1864 + + + http://radio.weblogs.com/0001015/2003/10/06.html#a1863 + <A href="http://www.state.gov/secretary/rm/2003/17300.htm"><IMG height=51 alt="A picture named powell.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/06/powell.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.state.gov/secretary/rm/2003/17300.htm"><IMG height=51 alt="A picture named powell.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/06/powell.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.state.gov/secretary/rm/2003/17300.htm"><IMG height=51 alt="A picture named powell.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/06/powell.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.state.gov/secretary/rm/2003/17300.htm"><IMG height=51 alt="A picture named powell.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/06/powell.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.state.gov/secretary/rm/2003/17300.htm"><IMG height=51 alt="A picture named powell.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/06/powell.jpg" width=45 align=right vspace=5 border=0></A> + http://radio.weblogs.com/0001015/2003/10/06.html#a1863 + Mon, 06 Oct 2003 13:37:40 GMT + http://blogs.law.harvard.edu/comments?u=1015&amp;p=1863&amp;link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F10%2F06.html%23a1863 + + + http://radio.weblogs.com/0001015/2003/10/01.html#a1862 + <A href="http://127.0.0.1:5335/xxx"><IMG height=80 alt="A picture named retard.gif" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/01/retard.gif" width=53 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=80 alt="A picture named retard.gif" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/01/retard.gif" width=53 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=80 alt="A picture named retard.gif" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/01/retard.gif" width=53 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=80 alt="A picture named retard.gif" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/01/retard.gif" width=53 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=80 alt="A picture named retard.gif" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/10/01/retard.gif" width=53 align=right vspace=5 border=0></A> + http://radio.weblogs.com/0001015/2003/10/01.html#a1862 + Wed, 01 Oct 2003 18:26:11 GMT + http://blogs.law.harvard.edu/comments?u=1015&amp;p=1862&amp;link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F10%2F01.html%23a1862 + + + http://radio.weblogs.com/0001015/2003/09/26.html#a1861 + <A href="http://127.0.0.1:5335/xxx"><IMG height=77 alt="A picture named allen.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/26/allen.jpg" width=65 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=77 alt="A picture named allen.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/26/allen.jpg" width=65 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=77 alt="A picture named allen.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/26/allen.jpg" width=65 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=77 alt="A picture named allen.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/26/allen.jpg" width=65 align=right vspace=5 border=0></A><A href="http://127.0.0.1:5335/xxx"><IMG height=77 alt="A picture named allen.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/26/allen.jpg" width=65 align=right vspace=5 border=0></A> + http://radio.weblogs.com/0001015/2003/09/26.html#a1861 + Fri, 26 Sep 2003 13:29:36 GMT + http://blogs.law.harvard.edu/comments?u=1015&amp;p=1861&amp;link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F09%2F26.html%23a1861 + + + http://radio.weblogs.com/0001015/2003/09/25.html#a1860 + <A href="http://www.command-post.org/2004/2_archives/008524.html"><IMG height=61 alt="A picture named clark.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/25/clark.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.command-post.org/2004/2_archives/008524.html"><IMG height=61 alt="A picture named clark.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/25/clark.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.command-post.org/2004/2_archives/008524.html"><IMG height=61 alt="A picture named clark.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/25/clark.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.command-post.org/2004/2_archives/008524.html"><IMG height=61 alt="A picture named clark.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/25/clark.jpg" width=45 align=right vspace=5 border=0></A><A href="http://www.command-post.org/2004/2_archives/008524.html"><IMG height=61 alt="A picture named clark.jpg" hspace=15 src="http://radio.weblogs.com/0001015/images/2003/09/25/clark.jpg" width=45 align=right vspace=5 border=0></A> + http://radio.weblogs.com/0001015/2003/09/25.html#a1860 + Thu, 25 Sep 2003 14:08:26 GMT + http://blogs.law.harvard.edu/comments?u=1015&amp;p=1860&amp;link=http%3A%2F%2Fradio.weblogs.com%2F0001015%2F2003%2F09%2F25.html%23a1860 + + + From 4194420980db5c1a8f45f1ca781221ddae03c4c8 Mon Sep 17 00:00:00 2001 From: Shikiryu Date: Wed, 12 Apr 2023 00:29:12 +0200 Subject: [PATCH 10/16] =?UTF-8?q?=F0=9F=99=88=20Add=20details=20for=20giti?= =?UTF-8?q?gnore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- tests/resources/tmp/build/.gitkeep | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tests/resources/tmp/build/.gitkeep diff --git a/.gitignore b/.gitignore index 2d769b6..b004717 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /vendor/ /.idea -/tests/error.log /.phpunit.result.cache +error.log diff --git a/tests/resources/tmp/build/.gitkeep b/tests/resources/tmp/build/.gitkeep new file mode 100644 index 0000000..e69de29 From 08138992fd3713ef046d8150f3da9beef299d0da Mon Sep 17 00:00:00 2001 From: Clement Desmidt Date: Wed, 12 Apr 2023 14:41:45 +0200 Subject: [PATCH 11/16] :sparkles: Add specific elements --- src/Entity/Channel/Category.php | 4 +-- src/Entity/Channel/Cloud.php | 10 +++--- src/Entity/Channel/Image.php | 6 ++-- src/Entity/Item/Enclosure.php | 6 ++-- src/Entity/Item/Source.php | 4 +-- src/Parser/ItemParser.php | 38 +++++++++++--------- src/Parser/SRSSParser.php | 27 ++++++++++---- tests/BasicReaderTest.php | 62 +++++++++++++++++++++++++++++++-- tests/resources/basic.xml | 2 ++ 9 files changed, 118 insertions(+), 41 deletions(-) diff --git a/src/Entity/Channel/Category.php b/src/Entity/Channel/Category.php index acfc4f1..7bc4557 100644 --- a/src/Entity/Channel/Category.php +++ b/src/Entity/Channel/Category.php @@ -11,11 +11,11 @@ class Category extends HasValidator implements SRSSElement /** * @string */ - public string $domain; + public ?string $domain = null; /** * @string */ - public string $value; + public ?string $value = null; public function isValid(): bool { diff --git a/src/Entity/Channel/Cloud.php b/src/Entity/Channel/Cloud.php index 95f991f..71b6875 100644 --- a/src/Entity/Channel/Cloud.php +++ b/src/Entity/Channel/Cloud.php @@ -11,23 +11,23 @@ class Cloud extends HasValidator implements SRSSElement /** * @string */ - public string $domain; + public ?string $domain = null; /** * @int */ - public int $port; + public ?int $port = null; /** * @string */ - public string $path; + public ?string $path = null; /** * @string */ - public string $registerProcedure; + public ?string $registerProcedure = null; /** * @string */ - public string $protocol; + public ?string $protocol = null; // diff --git a/src/Entity/Channel/Image.php b/src/Entity/Channel/Image.php index e7f1757..b45b9d0 100644 --- a/src/Entity/Channel/Image.php +++ b/src/Entity/Channel/Image.php @@ -12,17 +12,17 @@ class Image extends HasValidator implements SRSSElement * @required * @url */ - public string $url; + public ?string $url = null; /** * @required * @nohtml */ - public string $title; + public ?string $title = null; /** * @required * @url */ - public string $link; + public ?string $link = null; /** * @int * @max 144 diff --git a/src/Entity/Item/Enclosure.php b/src/Entity/Item/Enclosure.php index 4590b23..1ec2169 100644 --- a/src/Entity/Item/Enclosure.php +++ b/src/Entity/Item/Enclosure.php @@ -11,17 +11,17 @@ class Enclosure extends HasValidator implements SRSSElement /** * @url */ - public string $url; + public ?string $url = null; /** * @int */ - public int $length; + public ?int $length = null; /** * @mediaType */ - public string $type; + public ?string $type = null; public function isValid(): bool { diff --git a/src/Entity/Item/Source.php b/src/Entity/Item/Source.php index ddbaf55..1d75ebc 100644 --- a/src/Entity/Item/Source.php +++ b/src/Entity/Item/Source.php @@ -11,12 +11,12 @@ class Source extends HasValidator implements SRSSElement /** * @url */ - public string $url; + public ?string $url = null; /** * @nohtml */ - public string $source; + public ?string $value = null; public function isValid(): bool { diff --git a/src/Parser/ItemParser.php b/src/Parser/ItemParser.php index e40f063..0fe7471 100644 --- a/src/Parser/ItemParser.php +++ b/src/Parser/ItemParser.php @@ -19,22 +19,6 @@ class ItemParser extends DomDocument protected DOMNode $node; // item node protected $attr; // item's properties - // possible properties' names - protected static $possibilities = [ - 'title' => 'nohtml', - 'link' => 'link', - 'description' => 'html', - 'author' => 'email', - 'category' => 'nohtml', - 'comments' => 'link', - 'enclosure' => '', - 'guid' => 'nohtml', - 'pubDate' => 'date', - 'source' => 'link', - 'media:group' => 'folder', - 'media:content' => '', - ]; - /** * Constructor * @@ -55,10 +39,30 @@ class ItemParser extends DomDocument { foreach ($nodes->childNodes as $child) { if ($child->nodeType === XML_ELEMENT_NODE && $child->nodeName !== 'item') { - if (array_key_exists($child->nodeName, self::$possibilities) && self::$possibilities[$child->nodeName] === 'folder') { + if ($child->nodeName === 'media:group') { self::_loadChildAttributes($item, $child); } elseif ($child->nodeName === 'media:content') { $item->medias[] = MediaContentParser::read($child); + } elseif ($child->nodeName === 'category') { + $category = new Item\Category(); + foreach($child->attributes as $attribute) { + $category->{$attribute->name} = $attribute->value; + } + $category->value = $child->nodeValue; + $item->category[] = $category; + } elseif ($child->nodeName === 'enclosure') { + $enclosure = new Item\Enclosure(); + foreach($child->attributes as $attribute) { + $enclosure->{$attribute->name} = $attribute->value; + } + $item->enclosure = $enclosure; + } elseif ($child->nodeName === 'source') { + $source = new Item\Source(); + foreach($child->attributes as $attribute) { + $source->{$attribute->name} = $attribute->value; + } + $source->value = $child->nodeValue; + $item->source = $source; } else { $item->{$child->nodeName} = trim($child->nodeValue); } diff --git a/src/Parser/SRSSParser.php b/src/Parser/SRSSParser.php index 10571c3..4d8b066 100644 --- a/src/Parser/SRSSParser.php +++ b/src/Parser/SRSSParser.php @@ -49,11 +49,11 @@ class SRSSParser extends DomDocument */ public function parse(string $link): SRSS { - if(@$this->load($link)) { // We don't want the warning in case of bad XML. Let's manage it with an exception. + if (@$this->load($link)) { // We don't want the warning in case of bad XML. Let's manage it with an exception. $channel = $this->getElementsByTagName('channel'); if($channel->length === 1){ // Good URL and good RSS - $this->_loadAttributes(); // loading channel properties - $this->parseItems(); // loading all items + $this->_parseChannel(); // loading channel properties + $this->_parseItems(); // loading all items return $this->doc; } @@ -68,7 +68,7 @@ class SRSSParser extends DomDocument * @return Item[] * @throws SRSSException */ - private function parseItems(): array + private function _parseItems(): array { $channel = $this->_getChannel(); /** @var DOMNodeList $items */ @@ -87,7 +87,7 @@ class SRSSParser extends DomDocument * putting all RSS attributes into the object * @throws SRSSException */ - private function _loadAttributes(): void + private function _parseChannel(): void { $node_channel = $this->_getChannel(); $this->doc->channel = new Channel(); @@ -97,12 +97,25 @@ class SRSSParser extends DomDocument if($child->nodeName === 'image') { $image = new Image(); foreach($child->childNodes as $children) { - if($children->nodeType == XML_ELEMENT_NODE) { - $image->{$child->nodeName} = $children->nodeValue; + if($children->nodeType === XML_ELEMENT_NODE) { + $image->{$children->nodeName} = $children->nodeValue; } } $this->doc->channel->image = $image; + } elseif($child->nodeName === 'cloud') { + $cloud = new Channel\Cloud(); + foreach($child->attributes as $attribute) { + $cloud->{$attribute->name} = $attribute->value; + } + $this->doc->channel->cloud = $cloud; + } elseif($child->nodeName === 'category') { + $category = new Channel\Category(); + foreach($child->attributes as $attribute) { + $category->{$attribute->name} = $attribute->value; + } + $category->value = $child->nodeValue; + $this->doc->channel->category[] = $category; } else { $this->doc->channel->{$child->nodeName} = $child->nodeValue; } diff --git a/tests/BasicReaderTest.php b/tests/BasicReaderTest.php index e6444f7..3660a01 100644 --- a/tests/BasicReaderTest.php +++ b/tests/BasicReaderTest.php @@ -1,8 +1,10 @@ isValid()); } + + public function testChannelImage() + { + $rss = SRSS::read(__DIR__.'/resources/media/cnn.xml'); + $image = $rss->image; + self::assertInstanceOf(Image::class, $image); + self::assertEquals('http://i2.cdn.turner.com/cnn/2015/images/09/24/cnn.digital.png', $image->url, var_export($image, true)); + self::assertEquals('CNN.com - RSS Channel - Entertainment', $image->title, var_export($image, true)); + self::assertEquals('https://www.cnn.com/entertainment/index.html', $image->link, var_export($image, true)); + } + + public function testChannelCategory() + { + $rss = SRSS::read(__DIR__.'/resources/cloud.xml'); + $categories = $rss->category; + self::assertCount(1, $categories); + $category = $categories[0]; + self::assertInstanceOf(Category::class, $category); + self::assertEquals('http://www.weblogs.com/rssUpdates/changes.xml', $category->domain, var_export($category, true)); + self::assertEquals('rssUpdates', $category->value, var_export($category, true)); + } + + public function testCloud() + { + $rss = SRSS::read(__DIR__.'/resources/cloud.xml'); + $cloud = $rss->cloud; + self::assertInstanceOf(Cloud::class, $cloud); + self::assertEquals('radio.xmlstoragesystem.com', $cloud->domain, var_export($cloud, true)); + self::assertEquals('80', $cloud->port, var_export($cloud, true)); + self::assertEquals('/RPC2', $cloud->path, var_export($cloud, true)); + self::assertEquals('xmlStorageSystem.rssPleaseNotify', $cloud->registerProcedure, var_export($cloud, true)); + self::assertEquals('xml-rpc', $cloud->protocol, var_export($cloud, true)); + } + + public function testSource() + { + $rss = SRSS::read(__DIR__.'/resources/basic.xml'); + $firstItem = $rss->getFirst(); + self::assertInstanceOf(Item::class, $firstItem); + $source = $firstItem->source; + self::assertInstanceOf(Item\Source::class, $source); + self::assertEquals('http://www.tomalak.org/links2.xml', $source->url); + self::assertEquals('Tomalak\'s Realm', $source->value); + } + + public function testEnclosure() + { + $rss = SRSS::read(__DIR__.'/resources/basic.xml'); + $item = $rss->getItem(2); + self::assertInstanceOf(Item::class, $item); + $enclosure = $item->enclosure; + self::assertInstanceOf(Item\Enclosure::class, $enclosure); + self::assertEquals('http://www.scripting.com/mp3s/touchOfGrey.mp3', $enclosure->url); + self::assertEquals('5588242', $enclosure->length); + self::assertEquals('audio/mpeg', $enclosure->type); + } } \ No newline at end of file diff --git a/tests/resources/basic.xml b/tests/resources/basic.xml index 11dd2f7..d2329e1 100644 --- a/tests/resources/basic.xml +++ b/tests/resources/basic.xml @@ -7,11 +7,13 @@ Free web building tutorials RSS Tutorial + Tomalak's Realm https://www.test.com/xml/xml_rss.asp New RSS tutorial on test XML Tutorial + https://www.test.com/xml New XML tutorial on test From 3419e7d46c388972c41bdc6d231bd7d7230a8479 Mon Sep 17 00:00:00 2001 From: Clement Desmidt Date: Wed, 12 Apr 2023 15:28:38 +0200 Subject: [PATCH 12/16] =?UTF-8?q?=F0=9F=9A=A8=20Clean=20code=20for=20linte?= =?UTF-8?q?rs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 1 + src/Builder/SRSSBuilder.php | 166 +++++++------------------- src/Entity/Channel/Category.php | 3 +- src/Entity/Channel/Cloud.php | 5 +- src/Entity/Channel/Image.php | 3 +- src/Entity/Item.php | 3 +- src/Entity/Item/Category.php | 3 +- src/Entity/Item/Enclosure.php | 3 +- src/Entity/Item/Source.php | 3 +- src/Entity/Media/Content.php | 3 +- src/Exception/DOMBuilderException.php | 12 ++ src/Exception/SRSSException.php | 5 +- src/Parser/ItemParser.php | 44 ++----- src/Parser/SRSSParser.php | 38 +----- src/SRSS.php | 30 ++++- src/SRSSTools.php | 65 ++++++---- src/Validator/Validator.php | 12 +- tests/BasicBuilderTest.php | 2 +- tests/BasicReaderTest.php | 14 +-- tests/ExceptionTest.php | 10 +- tests/MediaTest.php | 4 +- tests/OriginalReaderSRSSTest.php | 2 +- tests/OriginalWriterSRSSTest.php | 14 +-- 23 files changed, 185 insertions(+), 260 deletions(-) create mode 100644 src/Exception/DOMBuilderException.php diff --git a/composer.json b/composer.json index 48d4820..0638d56 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ "phpunit/phpunit": "^9" }, "require": { + "php": ">=8.0", "ext-dom": "*", "ext-libxml": "*" } diff --git a/src/Builder/SRSSBuilder.php b/src/Builder/SRSSBuilder.php index e1a1b6c..11f1fd3 100644 --- a/src/Builder/SRSSBuilder.php +++ b/src/Builder/SRSSBuilder.php @@ -6,165 +6,79 @@ use DOMDocument; use DOMElement; use Shikiryu\SRSS\Entity\Channel; use Shikiryu\SRSS\Entity\Item; +use Shikiryu\SRSS\Exception\DOMBuilderException; use Shikiryu\SRSS\SRSS; -use Shikiryu\SRSS\SRSSTools; class SRSSBuilder extends DomDocument { - private function buildRSS(SRSS $srss) + /** + * @throws \Shikiryu\SRSS\Exception\DOMBuilderException + */ + private function buildRSS(SRSS $srss): SRSSBuilder { - $root = $this->createElement('rss'); - $root->setAttribute('version', '2.0'); - $channel = $this->createElement('channel'); + try { + $root = $this->createElement('rss'); - $srss->channel->generator = 'Shikiryu RSS'; + $root->setAttribute('version', '2.0'); + $channel = $this->createElement('channel'); - $this->appendChannelToDom($srss->channel, $channel); + $srss->channel->generator = 'Shikiryu RSS'; - $this->appendItemsToDom($srss->items, $channel); + $this->appendChannelToDom($srss->channel, $channel); - $root->appendChild($channel); - $this->appendChild($root); - $this->encoding = 'UTF-8'; - $this->formatOutput = true; - $this->preserveWhiteSpace = false; - // $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html'; + $this->appendItemsToDom($srss->items, $channel); + + $root->appendChild($channel); + $this->appendChild($root); + $this->encoding = 'UTF-8'; + $this->formatOutput = true; + $this->preserveWhiteSpace = false; + // $docs = 'http://www.scriptol.fr/rss/RSS-2.0.html'; + + } catch (\DOMException $e) { + throw new DOMBuilderException($e); + } return $this; } - public function build(SRSS $srss, string $filepath) - { - $this->buildRSS($srss); - $this->save($filepath); + /** + * @throws \Shikiryu\SRSS\Exception\DOMBuilderException + */ + public function build(SRSS $srss, string $filepath): void + { + $this + ->buildRSS($srss) + ->save($filepath); } /** * @return false|string + * @throws \Shikiryu\SRSS\Exception\DOMBuilderException */ - public function show(SRSS $srss) + public function show(SRSS $srss): bool|string { - $this->buildRSS($srss); - - return $this->saveXml(); + return $this + ->buildRSS($srss) + ->saveXml(); } - /** - * setter of "image"'s channel attributes - * @param $url string picture's url - * @param $title string picture's title - * @param $link string link on the picture - * @param $width int width - * @param $height int height - * @param $description string description - * TODO - */ - public function setImage($url, $title, $link, $width = 0, $height = 0, $description = '') + private function appendChannelToDom(Channel $channel, DOMElement $node): void { - $channel = $this->_getChannel(); - $array = []; - $url = SRSSTools::checkLink($url); - $array['url'] = $url; - $title = SRSSTools::noHTML($title); - $array['title'] = $title; - $link = SRSSTools::checkLink($link); - $array['link'] = $link; - if($width != 0) - { - $width = SRSSTools::checkInt($width); - $array['width'] = $width; - } - if($height != 0) - { - $height = SRSSTools::checkInt($height); - $array['height'] = $height; - } - if($description != 0) - { - $description = SRSSTools::noHTML($description); - $array['description'] = $description; - } - if($this->image == null) - { - $node = $this->createElement('image'); - $urlNode = $this->createElement('url', $url); - $titleNode = $this->createElement('title', $title); - $linkNode = $this->createElement('link', $link); - $node->appendChild($urlNode); - $node->appendChild($titleNode); - $node->appendChild($linkNode); - if($width != 0) - { - $widthNode = $this->createElement('width', $width); - $node->appendChild($widthNode); - } - if($height != 0) - { - $heightNode = $this->createElement('height', $height); - $node->appendChild($heightNode); - } - if($description != '') - { - $descNode = $this->createElement('description', $description); - $node->appendChild($descNode); - } - $channel->appendChild($node); - } - $this->attr['image'] = $array; - } - - /** - * setter of "cloud"'s channel attributes - * @param $domain string domain - * @param $port int port - * @param $path string path - * @param $registerProcedure string register procedure - * @param $protocol string protocol - * TODO - */ - public function setCloud($domain, $port, $path, $registerProcedure, $protocol) - { - $channel = $this->_getChannel(); - $array = array(); - $domain = SRSSTools::noHTML($domain); - $array['domain'] = $domain; - $port = SRSSTools::checkInt($port); - $array['port'] = $port; - $path = SRSSTools::noHTML($path); - $array['path'] = $path; - $registerProcedure = SRSSTools::noHTML($registerProcedure); - $array['registerProcedure'] = $registerProcedure; - $protocol = SRSSTools::noHTML($protocol); - $array['protocol'] = $protocol; - if($this->cloud == null) - { - $node = $this->createElement('cloud'); - $node->setAttribute('domain', $domain); - $node->setAttribute('port', $port); - $node->setAttribute('path', $path); - $node->setAttribute('registerProcedure', $registerProcedure); - $node->setAttribute('protocol', $protocol); - $channel->appendChild($node); - } - $this->attr['cloud'] = $array; - } - - private function appendChannelToDom(Channel $channel, DOMElement $node) - { - foreach (array_filter($channel->toArray(), fn($el) => !empty($el)) as $name => $value) { + foreach (array_filter($channel->toArray(), static fn($el) => !empty($el)) as $name => $value) { $new_node = $this->createElement($name, $value); $node->appendChild($new_node); } } - private function appendItemsToDom(array $items, DOMElement $channel) + private function appendItemsToDom(array $items, DOMElement $channel): void { foreach ($items as $item) { $this->appendItemToDom($item, $channel); } } - private function appendItemToDom(Item $item, DOMElement $channel) + private function appendItemToDom(Item $item, DOMElement $channel): void { $itemNode = $this->createElement('item'); foreach (array_filter($item->toArray()) as $name => $value) { diff --git a/src/Entity/Channel/Category.php b/src/Entity/Channel/Category.php index 7bc4557..6923c47 100644 --- a/src/Entity/Channel/Category.php +++ b/src/Entity/Channel/Category.php @@ -2,6 +2,7 @@ namespace Shikiryu\SRSS\Entity\Channel; +use ReflectionException; use Shikiryu\SRSS\Entity\SRSSElement; use Shikiryu\SRSS\Validator\HasValidator; use Shikiryu\SRSS\Validator\Validator; @@ -21,7 +22,7 @@ class Category extends HasValidator implements SRSSElement { try { return (new Validator())->isObjectValid($this); - } catch (\ReflectionException $e) { + } catch (ReflectionException) { return false; } } diff --git a/src/Entity/Channel/Cloud.php b/src/Entity/Channel/Cloud.php index 71b6875..9d379f3 100644 --- a/src/Entity/Channel/Cloud.php +++ b/src/Entity/Channel/Cloud.php @@ -2,6 +2,7 @@ namespace Shikiryu\SRSS\Entity\Channel; +use ReflectionException; use Shikiryu\SRSS\Entity\SRSSElement; use Shikiryu\SRSS\Validator\HasValidator; use Shikiryu\SRSS\Validator\Validator; @@ -29,13 +30,11 @@ class Cloud extends HasValidator implements SRSSElement */ public ?string $protocol = null; - // - public function isValid(): bool { try { return (new Validator())->isObjectValid($this); - } catch (\ReflectionException $e) { + } catch (ReflectionException) { return false; } } diff --git a/src/Entity/Channel/Image.php b/src/Entity/Channel/Image.php index b45b9d0..a4dfaaa 100644 --- a/src/Entity/Channel/Image.php +++ b/src/Entity/Channel/Image.php @@ -2,6 +2,7 @@ namespace Shikiryu\SRSS\Entity\Channel; +use ReflectionException; use Shikiryu\SRSS\Entity\SRSSElement; use Shikiryu\SRSS\Validator\HasValidator; use Shikiryu\SRSS\Validator\Validator; @@ -40,7 +41,7 @@ class Image extends HasValidator implements SRSSElement { try { return (new Validator())->isObjectValid($this); - } catch (\ReflectionException $e) { + } catch (ReflectionException) { return false; } } diff --git a/src/Entity/Item.php b/src/Entity/Item.php index 1149870..3faa0fd 100644 --- a/src/Entity/Item.php +++ b/src/Entity/Item.php @@ -2,6 +2,7 @@ namespace Shikiryu\SRSS\Entity; +use ReflectionException; use Shikiryu\SRSS\Entity\Item\Category; use Shikiryu\SRSS\Entity\Item\Enclosure; use Shikiryu\SRSS\Entity\Item\Source; @@ -64,7 +65,7 @@ class Item extends HasValidator implements SRSSElement { try { return (new Validator())->isObjectValid($this); - } catch (\ReflectionException $e) { + } catch (ReflectionException) { return false; } } diff --git a/src/Entity/Item/Category.php b/src/Entity/Item/Category.php index 6d8c318..fe9b493 100644 --- a/src/Entity/Item/Category.php +++ b/src/Entity/Item/Category.php @@ -2,6 +2,7 @@ namespace Shikiryu\SRSS\Entity\Item; +use ReflectionException; use Shikiryu\SRSS\Entity\SRSSElement; use Shikiryu\SRSS\Validator\HasValidator; use Shikiryu\SRSS\Validator\Validator; @@ -21,7 +22,7 @@ class Category extends HasValidator implements SRSSElement { try { return (new Validator())->isObjectValid($this); - } catch (\ReflectionException $e) { + } catch (ReflectionException) { return false; } } diff --git a/src/Entity/Item/Enclosure.php b/src/Entity/Item/Enclosure.php index 1ec2169..fa9d8fb 100644 --- a/src/Entity/Item/Enclosure.php +++ b/src/Entity/Item/Enclosure.php @@ -2,6 +2,7 @@ namespace Shikiryu\SRSS\Entity\Item; +use ReflectionException; use Shikiryu\SRSS\Entity\SRSSElement; use Shikiryu\SRSS\Validator\HasValidator; use Shikiryu\SRSS\Validator\Validator; @@ -27,7 +28,7 @@ class Enclosure extends HasValidator implements SRSSElement { try { return (new Validator())->isObjectValid($this); - } catch (\ReflectionException $e) { + } catch (ReflectionException) { return false; } } diff --git a/src/Entity/Item/Source.php b/src/Entity/Item/Source.php index 1d75ebc..355a1e0 100644 --- a/src/Entity/Item/Source.php +++ b/src/Entity/Item/Source.php @@ -2,6 +2,7 @@ namespace Shikiryu\SRSS\Entity\Item; +use ReflectionException; use Shikiryu\SRSS\Entity\SRSSElement; use Shikiryu\SRSS\Validator\HasValidator; use Shikiryu\SRSS\Validator\Validator; @@ -22,7 +23,7 @@ class Source extends HasValidator implements SRSSElement { try { return (new Validator())->isObjectValid($this); - } catch (\ReflectionException $e) { + } catch (ReflectionException) { return false; } } diff --git a/src/Entity/Media/Content.php b/src/Entity/Media/Content.php index a776957..0cbacdc 100644 --- a/src/Entity/Media/Content.php +++ b/src/Entity/Media/Content.php @@ -2,6 +2,7 @@ namespace Shikiryu\SRSS\Entity\Media; +use ReflectionException; use Shikiryu\SRSS\Entity\SRSSElement; use Shikiryu\SRSS\Validator\HasValidator; use Shikiryu\SRSS\Validator\Validator; @@ -69,7 +70,7 @@ class Content extends HasValidator implements SRSSElement { try { return (new Validator())->isObjectValid($this); - } catch (\ReflectionException $e) { + } catch (ReflectionException) { return false; } } diff --git a/src/Exception/DOMBuilderException.php b/src/Exception/DOMBuilderException.php new file mode 100644 index 0000000..8b3fd26 --- /dev/null +++ b/src/Exception/DOMBuilderException.php @@ -0,0 +1,12 @@ +getMessage())); + } + +} \ No newline at end of file diff --git a/src/Exception/SRSSException.php b/src/Exception/SRSSException.php index fd35775..0232a70 100644 --- a/src/Exception/SRSSException.php +++ b/src/Exception/SRSSException.php @@ -11,7 +11,10 @@ class SRSSException extends Exception parent :: __construct($msg); } - public function getError() + /** + * @return string + */ + public function getError(): string { return 'Une exception a été générée : Message : ' . $this->getMessage() . ' à la ligne : ' . $this->getLine(); } diff --git a/src/Parser/ItemParser.php b/src/Parser/ItemParser.php index 0fe7471..0c311ba 100644 --- a/src/Parser/ItemParser.php +++ b/src/Parser/ItemParser.php @@ -95,33 +95,6 @@ class ItemParser extends DomDocument return $this->getElementsByTagName('item')->item(0); } - /** - * setter for enclosure's properties - * - * @param $url string url - * @param $length int length - * @param $type string type - * @throws DOMException - */ - public function setEnclosure(string $url, int $length, string $type): void - { - $array = []; - $url = SRSSTools::checkLink($url); - $array['url'] = $url; - $length = SRSSTools::checkInt($length); - $array['length'] = $length; - $type = SRSSTools::noHTML($type); - $array['type'] = $type; - if ($this->enclosure == null) { - $node = $this->createElement('enclosure'); - $node->setAttribute('url', $url); - $node->setAttribute('length', $length); - $node->setAttribute('type', $type); - $this->node->appendChild($node); - } - $this->attr['enclosure'] = $array; - } - /** * check if current item is valid (following specifications) * @return bool @@ -156,12 +129,13 @@ class ItemParser extends DomDocument } $flag = $this->possibilities[$name]; - if ($flag !== '') + if ($flag !== '') { $val = SRSSTools::check($val, $flag); + } if (!empty($val)) { if ($val instanceof DOMElement) { $this->node->appendChild($val); - } elseif ($this->$name == null) { + } elseif ($this->$name === null) { $this->node->appendChild(new DomElement($name, $val)); } $this->attr[$name] = $val; @@ -178,11 +152,17 @@ class ItemParser extends DomDocument */ public function __get($name) { - if (isset($this->attr[$name])) + if (isset($this->attr[$name])) { return $this->attr[$name]; + } + if (array_key_exists($name, $this->possibilities)) { + $tmp = $this->node->getElementsByTagName($name); - if ($tmp->length != 1) return null; + + if ($tmp->length !== 1) { + return null; + } return $tmp->item(0)->nodeValue; } @@ -194,7 +174,7 @@ class ItemParser extends DomDocument * transform current item's object into an array * @return array */ - public function toArray() + public function toArray(): array { $infos = []; foreach ($this->attr as $attrName => $attrVal) { diff --git a/src/Parser/SRSSParser.php b/src/Parser/SRSSParser.php index 4d8b066..684d450 100644 --- a/src/Parser/SRSSParser.php +++ b/src/Parser/SRSSParser.php @@ -5,10 +5,8 @@ namespace Shikiryu\SRSS\Parser; use DOMDocument; use DOMNode; use DOMNodeList; -use DOMXPath; use Shikiryu\SRSS\Entity\Channel; use Shikiryu\SRSS\Entity\Channel\Image; -use Shikiryu\SRSS\Entity\Item; use Shikiryu\SRSS\Exception\ChannelNotFoundInRSSException; use Shikiryu\SRSS\Exception\SRSSException; use Shikiryu\SRSS\Exception\UnreadableRSSException; @@ -17,13 +15,11 @@ use Shikiryu\SRSS\SRSS; class SRSSParser extends DomDocument { private SRSS $doc; - private DOMXPath $xpath; public function __construct() { libxml_use_internal_errors(true); parent::__construct(); - $this->xpath = new DOMXpath($this); $this->doc = new SRSS(); } @@ -65,10 +61,9 @@ class SRSSParser extends DomDocument } /** - * @return Item[] * @throws SRSSException */ - private function _parseItems(): array + private function _parseItems(): void { $channel = $this->_getChannel(); /** @var DOMNodeList $items */ @@ -80,9 +75,8 @@ class SRSSParser extends DomDocument $this->doc->items[$i] = ItemParser::read($items->item($i)); } } - - return $this->doc->items; } + /** * putting all RSS attributes into the object * @throws SRSSException @@ -132,36 +126,10 @@ class SRSSParser extends DomDocument private function _getChannel(): DOMNode { $channel = $this->getElementsByTagName('channel'); - if($channel->length != 1) { + if($channel->length !== 1) { throw new ChannelNotFoundInRSSException('channel node not created, or too many channel nodes'); } return $channel->item(0); } - - /** - * getter of "image"'s channel attributes - * @return string|array - * TODO - */ - public function image() - { - $args = func_get_args(); - if (func_num_args() == 0) { - $args[0] = 'url'; - } - $img = $this->xpath->query('//channel/image'); - if ($img->length != 1) { // is not in channel - return null; - } - $img = $img->item(0); - $r = []; - foreach ($img->childNodes as $child) { - if ($child->nodeType == XML_ELEMENT_NODE && in_array($child->nodeName, $args)) { - $r[$child->nodeName] = $child->nodeValue; - } - } - - return (func_num_args() > 1) ? $r : $r[$args[0]]; - } } \ No newline at end of file diff --git a/src/SRSS.php b/src/SRSS.php index fad830a..4920aba 100644 --- a/src/SRSS.php +++ b/src/SRSS.php @@ -6,6 +6,9 @@ use Iterator; use ReflectionException; use Shikiryu\SRSS\Builder\SRSSBuilder; use Shikiryu\SRSS\Entity\Channel; +use Shikiryu\SRSS\Entity\Channel\Category; +use Shikiryu\SRSS\Entity\Channel\Cloud; +use Shikiryu\SRSS\Entity\Channel\Image; use Shikiryu\SRSS\Entity\Item; use Shikiryu\SRSS\Exception\ChannelNotFoundInRSSException; use Shikiryu\SRSS\Exception\PropertyNotFoundException; @@ -13,6 +16,27 @@ use Shikiryu\SRSS\Exception\SRSSException; use Shikiryu\SRSS\Exception\UnreadableRSSException; use Shikiryu\SRSS\Parser\SRSSParser; +/** + * @property null|string $title + * @property null|string $link + * @property null|string $description + * @property null|string $language + * @property null|string $copyright + * @property null|string $managingEditor + * @property null|string $webMaster + * @property null|string $pubDate + * @property null|string $lastBuildDate + * @property null|Category[] $category + * @property null|string $generator + * @property null|string $docs + * @property null|Cloud $cloud + * @property null|string $ttl + * @property null|Image $image + * @property null|string $rating + * @property null|string $textInput + * @property null|string $skipHours + * @property null|string $skipDays + */ class SRSS implements Iterator { public Channel $channel; @@ -72,7 +96,7 @@ class SRSS implements Iterator } return ($valid && $this->channel->isValid()); - } catch (ReflectionException $e) { + } catch (ReflectionException) { return false; } } @@ -244,6 +268,7 @@ class SRSS implements Iterator * @param string $path * * @return void + * @throws \Shikiryu\SRSS\Exception\DOMBuilderException */ public function save(string $path): void { @@ -252,8 +277,9 @@ class SRSS implements Iterator /** * @return false|string + * @throws \Shikiryu\SRSS\Exception\DOMBuilderException */ - public function show() + public function show(): bool|string { return (new SRSSBuilder('1.0', 'UTF-8'))->show($this); } diff --git a/src/SRSSTools.php b/src/SRSSTools.php index f677047..b83d714 100644 --- a/src/SRSSTools.php +++ b/src/SRSSTools.php @@ -28,37 +28,38 @@ class SRSSTools /** * format the RSS to the wanted format + * * @param $format string wanted format - * @param $date string RSS date + * @param $date string RSS date + * * @return string date */ - public static function formatDate($format, $date) + public static function formatDate(string $format, string $date): string { return date($format, strtotime($date)); } /** * format a date for RSS format + * * @param string $date date to format * @param string $format + * * @return string */ - public static function getRSSDate($date, $format='') + public static function getRSSDate(string $date, string $format = ''): string { - $datepos = 'dDjlNSwzWFmMntLoYyaABgGhHisueIOPTZcrU'; - if($format != '' && preg_match('~^(['.$datepos.']{1})(-|/)(['.$datepos.']{1})(-|/)(['.$datepos.']{1})$~', $format, $match)){ - $sep = $match[2]; - $format = '%'.$match[1].$sep.'%'.$match[3].$sep.'%'.$match[5]; - if($dateArray = strptime($date, $format)){ - $mois = (int)$dateArray['tm_mon'] + 1; - $annee = strlen($dateArray['tm_year']) > 2 ? '20'.substr($dateArray['tm_year'], -2) : '19'.$dateArray['tm_year']; - $date = $annee.'-'.$mois.'-'.$dateArray['tm_mday']; - return date("D, d M Y H:i:s T", strtotime($date)); + $date_position = 'dDjlNSwzWFmMntLoYyaABgGhHisueIOPTZcrU'; + if($format !== '' && preg_match('~^(['.$date_position.']{1})([-/])(['.$date_position.']{1})([-/])(['.$date_position.']{1})$~', $format)){ + $datetime = DateTime::createFromFormat($format, $date); + if ($datetime === false) { + return ''; } - return ''; + + return $datetime->format(DATE_RSS); } - if(strtotime($date) !==false ){ + if (strtotime($date) !==false ) { return date("D, d M Y H:i:s T", strtotime($date)); } @@ -73,41 +74,49 @@ class SRSSTools /** * check if it's an url + * * @param $check string to check + * * @return string|boolean the filtered data, or FALSE if the filter fails. */ - public static function checkLink($check) + public static function checkLink(string $check): bool|string { return filter_var($check, FILTER_VALIDATE_URL); } /** * make a string XML-compatible + * * @param $check string to format + * * @return string formatted string * TODO CDATA ? */ - public static function HTML4XML($check) + public static function HTML4XML(string $check): string { return htmlspecialchars($check); } /** * delete html tags + * * @param $check string to format + * * @return string formatted string */ - public static function noHTML($check) + public static function noHTML(string $check): string { return strip_tags($check); } /** * check if it's a day (in RSS terms) + * * @param $check string to check + * * @return string the day, or empty string */ - public static function checkDay($check) + public static function checkDay(string $check): string { $possibleDay = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']; return in_array(strtolower($check), $possibleDay) ? $check : ''; @@ -115,20 +124,24 @@ class SRSSTools /** * check if it's an email + * * @param $check string to check + * * @return string|boolean the filtered data, or FALSE if the filter fails. */ - public static function checkEmail($check) + public static function checkEmail(string $check): bool|string { return filter_var($check, FILTER_VALIDATE_EMAIL); } /** * check if it's an hour (in RSS terms) + * * @param $check string to check + * * @return string|boolean the filtered data, or FALSE if the filter fails. */ - public static function checkHour($check) + public static function checkHour(string $check): bool|string { $options = [ 'options' => [ @@ -142,10 +155,12 @@ class SRSSTools /** * check if it's an int + * * @param $check int to check + * * @return int|boolean the filtered data, or FALSE if the filter fails. */ - public static function checkInt($check) + public static function checkInt(int $check): bool|int { return filter_var($check, FILTER_VALIDATE_INT); } @@ -155,7 +170,7 @@ class SRSSTools * * @return mixed */ - private static function checkMediaType($check) + public static function checkMediaType($check): mixed { return $check; } @@ -165,7 +180,7 @@ class SRSSTools * * @return mixed|null */ - private static function checkMediaMedium($check) + public static function checkMediaMedium($check): ?string { return in_array($check, ['image', 'audio', 'video', 'document', 'executable']) ? $check : null; } @@ -175,7 +190,7 @@ class SRSSTools * * @return mixed|null */ - private static function checkBool($check) + public static function checkBool($check): ?string { return in_array($check, ['true', 'false']) ? $check : null; } @@ -185,7 +200,7 @@ class SRSSTools * * @return mixed|null */ - private static function checkMediumExpression($check) + public static function checkMediumExpression($check): ?string { return in_array($check, ['sample', 'full', 'nonstop']) ? $check : null; } diff --git a/src/Validator/Validator.php b/src/Validator/Validator.php index 4652d28..92358fa 100644 --- a/src/Validator/Validator.php +++ b/src/Validator/Validator.php @@ -15,9 +15,9 @@ class Validator /** * @throws ReflectionException */ - public function isPropertyValid($object, $property) + public function isPropertyValid($object, $property): bool { - $properties = array_filter($this->_getClassProperties(get_class($object)), fn($p) => $p->getName() === $property); + $properties = array_filter($this->_getClassProperties(get_class($object)), static fn($p) => $p->getName() === $property); if (count($properties) !== 1) { return false; } @@ -26,7 +26,7 @@ class Validator $propertyValue = $object->{$properties->name}; $propertyAnnotations = $this->_getPropertyAnnotations($properties); - if (!in_array('required', $propertyAnnotations, true) && empty($propertyValue)) { + if (empty($propertyValue) && !in_array('required', $propertyAnnotations, true)) { return true; } @@ -67,7 +67,7 @@ class Validator $propertyValue = $object->{$property['name']}; // $propertyAnnotations = $this->_getPropertyAnnotations($property, get_class($object)); - if (!in_array('required', $property['rules'], true) && empty($propertyValue)) { + if (empty($propertyValue) && !in_array('required', $property['rules'], true)) { continue; } @@ -109,7 +109,7 @@ class Validator { preg_match_all('#@(.*?)\n#s', $property->getDocComment(), $annotations); - return array_map(fn($annotation) => trim($annotation), $annotations[1]); + return array_map(static fn($annotation) => trim($annotation), $annotations[1]); } private function _validateString($value): bool @@ -199,7 +199,7 @@ class Validator ); } - private function _validateContentMedia($value) + private function _validateContentMedia($value): bool { if (is_array($value)) { foreach ($value as $content) { diff --git a/tests/BasicBuilderTest.php b/tests/BasicBuilderTest.php index b3a433c..2297453 100644 --- a/tests/BasicBuilderTest.php +++ b/tests/BasicBuilderTest.php @@ -16,7 +16,7 @@ class BasicBuilderTest extends TestCase unlink($this->saved); } } - public function testCreateBasicRSS() + public function testCreateBasicRSS(): void { $srss = SRSS::create(); $srss->title = 'My Blog'; diff --git a/tests/BasicReaderTest.php b/tests/BasicReaderTest.php index 3660a01..1f987c5 100644 --- a/tests/BasicReaderTest.php +++ b/tests/BasicReaderTest.php @@ -9,7 +9,7 @@ use Shikiryu\SRSS\SRSS; class BasicReaderTest extends TestCase { - public function testReadBasicRSS() + public function testReadBasicRSS(): void { $rss = SRSS::read(__DIR__.'/resources/basic.xml'); self::assertEquals('test Home Page', $rss->title); @@ -20,7 +20,7 @@ class BasicReaderTest extends TestCase self::assertTrue($rss->isValid()); } - public function testSpecificationExampleRSS() + public function testSpecificationExampleRSS(): void { $rss = SRSS::read(__DIR__.'/resources/harvard.xml'); self::assertEquals('Liftoff News', $rss->title); @@ -41,7 +41,7 @@ class BasicReaderTest extends TestCase self::assertTrue($rss->isValid()); } - public function testChannelImage() + public function testChannelImage(): void { $rss = SRSS::read(__DIR__.'/resources/media/cnn.xml'); $image = $rss->image; @@ -51,7 +51,7 @@ class BasicReaderTest extends TestCase self::assertEquals('https://www.cnn.com/entertainment/index.html', $image->link, var_export($image, true)); } - public function testChannelCategory() + public function testChannelCategory(): void { $rss = SRSS::read(__DIR__.'/resources/cloud.xml'); $categories = $rss->category; @@ -62,7 +62,7 @@ class BasicReaderTest extends TestCase self::assertEquals('rssUpdates', $category->value, var_export($category, true)); } - public function testCloud() + public function testCloud(): void { $rss = SRSS::read(__DIR__.'/resources/cloud.xml'); $cloud = $rss->cloud; @@ -74,7 +74,7 @@ class BasicReaderTest extends TestCase self::assertEquals('xml-rpc', $cloud->protocol, var_export($cloud, true)); } - public function testSource() + public function testSource(): void { $rss = SRSS::read(__DIR__.'/resources/basic.xml'); $firstItem = $rss->getFirst(); @@ -85,7 +85,7 @@ class BasicReaderTest extends TestCase self::assertEquals('Tomalak\'s Realm', $source->value); } - public function testEnclosure() + public function testEnclosure(): void { $rss = SRSS::read(__DIR__.'/resources/basic.xml'); $item = $rss->getItem(2); diff --git a/tests/ExceptionTest.php b/tests/ExceptionTest.php index 004f4dc..19fca5a 100644 --- a/tests/ExceptionTest.php +++ b/tests/ExceptionTest.php @@ -8,22 +8,22 @@ use Shikiryu\SRSS\SRSS; class ExceptionTest extends TestCase { - public function testPropertyNotFound() + public function testPropertyNotFound(): void { $srss = new SRSS(); $this->expectException(PropertyNotFoundException::class); $srss->notfound = 'true'; } - public function testRssNotFound() + public function testRssNotFound(): void { $this->expectException(UnreadableRSSException::class); - $rss = SRSS::read('not_found.xml'); + SRSS::read('not_found.xml'); } - public function testMissingChannel() + public function testMissingChannel(): void { $this->expectException(ChannelNotFoundInRSSException::class); - $rss = SRSS::read(__DIR__ . '/resources/invalid-no-channel.xml'); + SRSS::read(__DIR__ . '/resources/invalid-no-channel.xml'); } } \ No newline at end of file diff --git a/tests/MediaTest.php b/tests/MediaTest.php index 0e88c10..0abb611 100644 --- a/tests/MediaTest.php +++ b/tests/MediaTest.php @@ -5,7 +5,7 @@ use Shikiryu\SRSS\SRSS; class MediaTest extends TestCase { - public function testImages() + public function testImages(): void { $rss = SRSS::read(__DIR__.'/resources/media/cnn.xml'); self::assertEquals('CNN.com - RSS Channel - Entertainment', $rss->title); @@ -17,7 +17,7 @@ class MediaTest extends TestCase self::assertTrue($rss->isValid(), var_export($rss->channel->validated, true)); } - public function testMusicVideo() + public function testMusicVideo(): void { $rss = SRSS::read(__DIR__.'/resources/media/music-video.xml'); self::assertEquals('Music Videos 101', $rss->title); diff --git a/tests/OriginalReaderSRSSTest.php b/tests/OriginalReaderSRSSTest.php index d702121..0b1bd7a 100644 --- a/tests/OriginalReaderSRSSTest.php +++ b/tests/OriginalReaderSRSSTest.php @@ -16,7 +16,7 @@ class OriginalReaderSRSSTest extends TestCase } } - public function testOriginalReader() + public function testOriginalReader(): void { $rss = SRSS::read($this->original); self::assertEquals('Liftoff News', $rss->title); diff --git a/tests/OriginalWriterSRSSTest.php b/tests/OriginalWriterSRSSTest.php index 40b1f36..c57cc73 100644 --- a/tests/OriginalWriterSRSSTest.php +++ b/tests/OriginalWriterSRSSTest.php @@ -7,7 +7,7 @@ use Shikiryu\SRSS\SRSSTools; class OriginalWriterSRSSTest extends TestCase { - public function testOriginalWriter() + public function testOriginalWriter(): void { $rss = SRSS::create(); $rss->title = 'My Awesome Blog'; @@ -22,12 +22,12 @@ class OriginalWriterSRSSTest extends TestCase ]; foreach($items as $item){ - $rssitem = new Item(); - $rssitem->title = $item["title"]; - $rssitem->link = $item['link']; - $rssitem->pubDate = $item["pubDate"]; - $rssitem->description = $item["description"]; - $rss->addItem($rssitem); + $rss_item = new Item(); + $rss_item->title = $item["title"]; + $rss_item->link = $item['link']; + $rss_item->pubDate = $item["pubDate"]; + $rss_item->description = $item["description"]; + $rss->addItem($rss_item); } $firstItem = new Item(); From a6a31e18e114b70002fce48fb69f035643be12b6 Mon Sep 17 00:00:00 2001 From: Shikiryu Date: Thu, 13 Apr 2023 01:10:52 +0200 Subject: [PATCH 13/16] :lock: Remove dead code and another way to validate data --- src/Parser/ItemParser.php | 114 ------------------------------------ src/SRSS.php | 6 +- src/SRSSTools.php | 8 +-- src/Validator/Validator.php | 62 ++++++++++++++++---- tests/BasicBuilderTest.php | 13 +++- 5 files changed, 68 insertions(+), 135 deletions(-) diff --git a/src/Parser/ItemParser.php b/src/Parser/ItemParser.php index 0c311ba..e401910 100644 --- a/src/Parser/ItemParser.php +++ b/src/Parser/ItemParser.php @@ -3,12 +3,8 @@ namespace Shikiryu\SRSS\Parser; use DOMDocument; -use DOMElement; -use DOMException; use DOMNode; use Shikiryu\SRSS\Entity\Item; -use Shikiryu\SRSS\Exception\SRSSException; -use Shikiryu\SRSS\SRSSTools; /** * @property string|null $description @@ -17,17 +13,6 @@ class ItemParser extends DomDocument { protected DOMNode $node; // item node - protected $attr; // item's properties - - /** - * Constructor - * - * @param DomNode $node - */ - public function __construct($node = null) - { - parent::__construct(); - } /** * @param Item $item @@ -84,103 +69,4 @@ class ItemParser extends DomDocument return $item; } - - /** - * getter of item DomElement - */ - public function getItem(): ?DOMNode - { - $this->appendChild($this->node); - - return $this->getElementsByTagName('item')->item(0); - } - - /** - * check if current item is valid (following specifications) - * @return bool - */ - public function isValid(): bool - { - return $this->description != null; - } - - /** - * @param $name - * - * @return bool - */ - public function __isset($name) - { - return isset($this->attr[$name]); - } - - /** - * main setter for properties - * - * @param $name - * @param $val - * - * @throws SRSSException|DOMException - */ - public function __set($name, $val) - { - if (!array_key_exists($name, $this->possibilities)) { - throw new SRSSException(sprintf('%s is not a possible item (%s)', $name, implode(', ', array_keys($this->possibilities)))); - } - - $flag = $this->possibilities[$name]; - if ($flag !== '') { - $val = SRSSTools::check($val, $flag); - } - if (!empty($val)) { - if ($val instanceof DOMElement) { - $this->node->appendChild($val); - } elseif ($this->$name === null) { - $this->node->appendChild(new DomElement($name, $val)); - } - $this->attr[$name] = $val; - } - } - - /** - * main getter for properties - * - * @param $name - * - * @return null|string - * @throws SRSSException - */ - public function __get($name) - { - if (isset($this->attr[$name])) { - return $this->attr[$name]; - } - - if (array_key_exists($name, $this->possibilities)) { - - $tmp = $this->node->getElementsByTagName($name); - - if ($tmp->length !== 1) { - return null; - } - - return $tmp->item(0)->nodeValue; - } - - throw new SRSSException(sprintf('%s is not a possible item (%s)', $name, implode(', ', array_keys($this->possibilities)))); - } - - /** - * transform current item's object into an array - * @return array - */ - public function toArray(): array - { - $infos = []; - foreach ($this->attr as $attrName => $attrVal) { - $infos[$attrName] = $attrVal; - } - - return $infos; - } } \ No newline at end of file diff --git a/src/SRSS.php b/src/SRSS.php index 4920aba..37d6213 100644 --- a/src/SRSS.php +++ b/src/SRSS.php @@ -15,6 +15,7 @@ use Shikiryu\SRSS\Exception\PropertyNotFoundException; use Shikiryu\SRSS\Exception\SRSSException; use Shikiryu\SRSS\Exception\UnreadableRSSException; use Shikiryu\SRSS\Parser\SRSSParser; +use Shikiryu\SRSS\Validator\Validator; /** * @property null|string $title @@ -124,10 +125,9 @@ class SRSS implements Iterator if (!property_exists(Channel::class, $name)) { throw new PropertyNotFoundException(Channel::class, $name); } - // TODO add validator ? -// if ((new Validator())->isPropertyValid($this->channel, $name)) { + if ((new Validator())->isValidValueForObjectProperty($this->channel, $name, $val)) { $this->channel->{$name} = $val; -// } + } } /** diff --git a/src/SRSSTools.php b/src/SRSSTools.php index b83d714..2f038c3 100644 --- a/src/SRSSTools.php +++ b/src/SRSSTools.php @@ -7,7 +7,7 @@ use DateTimeInterface; class SRSSTools { - /*public static function check($check, $flag) + public static function check($check, $flag) { return match ($flag) { 'nohtml' => self::noHTML($check), @@ -18,13 +18,12 @@ class SRSSTools 'int' => self::checkInt($check), 'hour' => self::checkHour($check), 'day' => self::checkDay($check), - 'folder' => [], 'media_type' => self::checkMediaType($check), 'media_medium' => self::checkMediaMedium($check), 'bool' => self::checkBool($check), 'medium_expression' => self::checkMediumExpression($check) }; - }*/ + } /** * format the RSS to the wanted format @@ -90,11 +89,10 @@ class SRSSTools * @param $check string to format * * @return string formatted string - * TODO CDATA ? */ public static function HTML4XML(string $check): string { - return htmlspecialchars($check); + return sprintf('', htmlspecialchars($check)); } /** diff --git a/src/Validator/Validator.php b/src/Validator/Validator.php index 92358fa..da63052 100644 --- a/src/Validator/Validator.php +++ b/src/Validator/Validator.php @@ -12,31 +12,74 @@ use Shikiryu\SRSS\Entity\Media\Content; class Validator { protected ?object $object = null; + + /** + * @param $object + * @param $property + * @return ReflectionProperty|null * @throws ReflectionException */ - public function isPropertyValid($object, $property): bool + private function getReflectedProperty($object, $property): ?ReflectionProperty { - $properties = array_filter($this->_getClassProperties(get_class($object)), static fn($p) => $p->getName() === $property); + $properties = array_filter( + $this->_getClassProperties(get_class($object)), + static fn($p) => $p->getName() === $property + ); + if (count($properties) !== 1) { - return false; + return null; } - $properties = current($properties); - $propertyValue = $object->{$properties->name}; - $propertyAnnotations = $this->_getPropertyAnnotations($properties); + return current($properties); + } - if (empty($propertyValue) && !in_array('required', $propertyAnnotations, true)) { + /** + * @param $object + * @param $property + * @param $value + * @return bool + */ + public function isValidValueForObjectProperty($object, $property, $value): bool + { + try { + $property = $this->getReflectedProperty($object, $property); + } catch (ReflectionException) { + return false; + } + $propertyAnnotations = $this->_getPropertyAnnotations($property); + + if (empty($value) && !in_array('required', $propertyAnnotations, true)) { return true; } foreach ($propertyAnnotations as $propertyAnnotation) { $annotation = explode(' ', $propertyAnnotation); - $object->validated[$properties->name] = $this->_validateProperty($annotation, $propertyValue); + $object->validated[$property->name] = $this->_validateProperty($annotation, $value); } - return false; + return count(array_filter($object->validated, static fn($v) => ($v !== null && $v === false))) === 0; + } + + /** + * @param $object + * @param $property + * @return bool + * @throws ReflectionException + */ + private function objectHasProperty($object, $property): bool + { + return $this->getReflectedProperty($object, $property) instanceof ReflectionProperty; + } + + /** + * @throws ReflectionException + */ + public function isPropertyValid($object, $property): bool + { + return $this->objectHasProperty($object, $property) && + $this->isValidValueForObjectProperty($object, $property, $object->{$property}); } /** @@ -65,7 +108,6 @@ class Validator foreach ($properties as $property) { $propertyValue = $object->{$property['name']}; -// $propertyAnnotations = $this->_getPropertyAnnotations($property, get_class($object)); if (empty($propertyValue) && !in_array('required', $property['rules'], true)) { continue; diff --git a/tests/BasicBuilderTest.php b/tests/BasicBuilderTest.php index 2297453..9a84ddd 100644 --- a/tests/BasicBuilderTest.php +++ b/tests/BasicBuilderTest.php @@ -18,10 +18,13 @@ class BasicBuilderTest extends TestCase } public function testCreateBasicRSS(): void { + $title = 'My Blog'; + $description = 'is the best'; + $link = 'http://shikiryu.com/devblog/'; $srss = SRSS::create(); - $srss->title = 'My Blog'; - $srss->description = 'is the best'; - $srss->link = 'http://shikiryu.com/devblog/'; + $srss->title = $title; + $srss->description = $description; + $srss->link = $link; $items = [ ['title' => 'title 1', 'link' => 'http://shikiryu.com/devblog/article-1', 'pubDate' => SRSSTools::getRSSDate('2012-03-05 12:02:01'), 'description' => 'description 1'], ['title' => 'title 2', 'link' => 'http://shikiryu.com/devblog/article-2', 'pubDate' => SRSSTools::getRSSDate('2022-03-05 22:02:02'), 'description' => 'description 2'], @@ -39,6 +42,10 @@ class BasicBuilderTest extends TestCase self::assertTrue($srss->isValid()); + self::assertEquals($title, $srss->title); + self::assertEquals($description, $srss->description); + self::assertEquals($link, $srss->link); + $builder = new SRSSBuilder(); $builder->build($srss, $this->saved); From 317d9e37e86c0f4e70a30fef2235fb3f042432b9 Mon Sep 17 00:00:00 2001 From: Clement Desmidt Date: Thu, 13 Apr 2023 11:24:43 +0200 Subject: [PATCH 14/16] :sparkles: Finishes Builders --- src/Builder/ChannelBuilder.php | 98 ++++++++++++++++++++++++++++++ src/Builder/ItemBuilder.php | 105 +++++++++++++++++++++++++++++++++ src/Builder/SRSSBuilder.php | 37 ++---------- src/SRSS.php | 6 +- src/SRSSTools.php | 6 ++ src/Validator/Validator.php | 5 ++ tests/CompleteBuilderTest.php | 101 +++++++++++++++++++++++++++++++ 7 files changed, 326 insertions(+), 32 deletions(-) create mode 100644 src/Builder/ChannelBuilder.php create mode 100644 src/Builder/ItemBuilder.php create mode 100644 tests/CompleteBuilderTest.php diff --git a/src/Builder/ChannelBuilder.php b/src/Builder/ChannelBuilder.php new file mode 100644 index 0000000..2f57ea1 --- /dev/null +++ b/src/Builder/ChannelBuilder.php @@ -0,0 +1,98 @@ +document = $document; + } + + /** + * @param \Shikiryu\SRSS\Entity\Channel $channel + * + * @return \DOMElement|false + * @throws \DOMException + */ + public function build(Channel $channel) + { + $node = $this->document->createElement('channel'); + foreach (array_filter($channel->toArray(), static fn($el) => !empty($el)) as $name => $value) { + if ($name === 'category') { + /** @var \Shikiryu\SRSS\Entity\Channel\Category $category */ + foreach ($value as $category) { + $node->appendChild($this->buildCategory($category)); + } + } elseif ($name === 'cloud') { + $node->appendChild($this->buildCloud($value)); + } elseif ($name === 'image') { + $node->appendChild($this->buildImage($value)); + } else { + $new_node = $this->document->createElement($name, $value); + $node->appendChild($new_node); + } + } + + return $node; + } + + /** + * @param \Shikiryu\SRSS\Entity\Channel\Category $category + * + * @return bool|\DOMElement + * @throws \DOMException + */ + private function buildCategory(Channel\Category $category): bool|DOMElement + { + $node = $this->document->createElement('category', $category->value); + $node->setAttribute('domain', $category->domain); + + return $node; + } + + /** + * @param \Shikiryu\SRSS\Entity\Channel\Cloud $cloud + * + * @return \DOMElement|false + * @throws \DOMException + */ + private function buildCloud(Channel\Cloud $cloud) + { + $node = $this->document->createElement('cloud'); + foreach (get_object_vars($cloud) as $name => $value) { + if (!is_array($value)) { + $node->setAttribute($name, $value); + } + } + + return $node; + } + + /** + * @param \Shikiryu\SRSS\Entity\Channel\Image $image + * + * @return \DOMElement|false + * @throws \DOMException + */ + private function buildImage(Channel\Image $image) + { + $node = $this->document->createElement('image'); + foreach (get_object_vars($image) as $name => $value) { + if (!is_array($value)) { + $node->appendChild($this->document->createElement($name, $value)); + } + } + + return $node; + } +} \ No newline at end of file diff --git a/src/Builder/ItemBuilder.php b/src/Builder/ItemBuilder.php new file mode 100644 index 0000000..b65de13 --- /dev/null +++ b/src/Builder/ItemBuilder.php @@ -0,0 +1,105 @@ +document = $document; + } + + /** + * @param \Shikiryu\SRSS\Entity\Item $item + * + * @return \DOMElement|false + * @throws \DOMException + */ + public function build(Item $item): bool|\DOMElement + { + $node = $this->document->createElement('item'); + + foreach (array_filter($item->toArray()) as $name => $value) { + if ($name === 'category') { + /** @var \Shikiryu\SRSS\Entity\Item\Category $category */ + foreach ($value as $category) { + $node->appendChild($this->buildCategory($category)); + } + } elseif ($name === 'medias') { + $group = null; + if (count($value) > 1) { + $group = $node->appendChild($this->document->createElement('media:group')); + } + foreach ($value as $media) { + if (null === $group) { + $node->appendChild($this->buildMedia($media)); + } else { + $group->appendChild($this->buildMedia($media)); + } + } + if ($group !== null) { + $node->appendChild($group); + } + } elseif ($name === 'enclosure') { + $node->appendChild($this->buildEnclosure($value)); + } elseif ($name === 'source') { + $node->appendChild($this->buildSource($value)); + } else { + $new_node = $this->document->createElement($name, $value); + $node->appendChild($new_node); + } + } + + return $node; + } + + private function buildCategory(Item\Category $category) + { + $node = $this->document->createElement('category', $category->value); + $node->setAttribute('domain', $category->domain); + + return $node; + } + + private function buildEnclosure(Item\Enclosure $enclosure) + { + $node = $this->document->createElement('enclosure'); + foreach (get_object_vars($enclosure) as $name => $value) { + if (!is_array($value)) { + $node->setAttribute($name, $value); + } + } + + return $node; + } + + private function buildSource(Item\Source $source) + { + $node = $this->document->createElement('source', $source->value); + $node->setAttribute('url', $source->url); + + return $node; + } + + private function buildMedia(Content $media) + { + $node = $this->document->createElement('media:content'); + foreach (get_object_vars($media) as $name => $value) { + if (!is_array($value)) { + $node->setAttribute($name, $value); + } + } + + return $node; + } + + +} \ No newline at end of file diff --git a/src/Builder/SRSSBuilder.php b/src/Builder/SRSSBuilder.php index 11f1fd3..c62ddc4 100644 --- a/src/Builder/SRSSBuilder.php +++ b/src/Builder/SRSSBuilder.php @@ -3,9 +3,6 @@ namespace Shikiryu\SRSS\Builder; use DOMDocument; -use DOMElement; -use Shikiryu\SRSS\Entity\Channel; -use Shikiryu\SRSS\Entity\Item; use Shikiryu\SRSS\Exception\DOMBuilderException; use Shikiryu\SRSS\SRSS; @@ -20,13 +17,16 @@ class SRSSBuilder extends DomDocument $root = $this->createElement('rss'); $root->setAttribute('version', '2.0'); - $channel = $this->createElement('channel'); $srss->channel->generator = 'Shikiryu RSS'; - $this->appendChannelToDom($srss->channel, $channel); + $channel_builder = new ChannelBuilder($this); + $channel = $channel_builder->build($srss->channel); - $this->appendItemsToDom($srss->items, $channel); + $item_builder = new ItemBuilder($this); + foreach ($srss->items as $item) { + $channel->appendChild($item_builder->build($item)); + } $root->appendChild($channel); $this->appendChild($root); @@ -62,29 +62,4 @@ class SRSSBuilder extends DomDocument ->buildRSS($srss) ->saveXml(); } - - private function appendChannelToDom(Channel $channel, DOMElement $node): void - { - foreach (array_filter($channel->toArray(), static fn($el) => !empty($el)) as $name => $value) { - $new_node = $this->createElement($name, $value); - $node->appendChild($new_node); - } - } - - private function appendItemsToDom(array $items, DOMElement $channel): void - { - foreach ($items as $item) { - $this->appendItemToDom($item, $channel); - } - } - - private function appendItemToDom(Item $item, DOMElement $channel): void - { - $itemNode = $this->createElement('item'); - foreach (array_filter($item->toArray()) as $name => $value) { - $new_node = $this->createElement($name, $value); - $itemNode->appendChild($new_node); - } - $channel->appendChild($itemNode); - } } \ No newline at end of file diff --git a/src/SRSS.php b/src/SRSS.php index 37d6213..082fc54 100644 --- a/src/SRSS.php +++ b/src/SRSS.php @@ -126,7 +126,11 @@ class SRSS implements Iterator throw new PropertyNotFoundException(Channel::class, $name); } if ((new Validator())->isValidValueForObjectProperty($this->channel, $name, $val)) { - $this->channel->{$name} = $val; + if (SRSSTools::getPropertyType(Channel::class, $name) === 'array') { + $this->channel->{$name}[] = $val; + } else { + $this->channel->{$name} = $val; + } } } diff --git a/src/SRSSTools.php b/src/SRSSTools.php index 2f038c3..5ab55f1 100644 --- a/src/SRSSTools.php +++ b/src/SRSSTools.php @@ -4,9 +4,15 @@ namespace Shikiryu\SRSS; use DateTime; use DateTimeInterface; +use ReflectionProperty; class SRSSTools { + public static function getPropertyType($object, $property): ?string + { + $rp = new ReflectionProperty($object, $property); + return $rp->getType()?->getName(); + } public static function check($check, $flag) { return match ($flag) { diff --git a/src/Validator/Validator.php b/src/Validator/Validator.php index da63052..dc981f1 100644 --- a/src/Validator/Validator.php +++ b/src/Validator/Validator.php @@ -272,4 +272,9 @@ class Validator { return in_array($value, ['sample', 'full', 'nonstop']); } + + private function _validateEmail($value): bool + { + return filter_var($value, FILTER_VALIDATE_EMAIL); + } } \ No newline at end of file diff --git a/tests/CompleteBuilderTest.php b/tests/CompleteBuilderTest.php new file mode 100644 index 0000000..06c6b17 --- /dev/null +++ b/tests/CompleteBuilderTest.php @@ -0,0 +1,101 @@ +better'; + $language = 'en-us'; + $copyright = 'Shikiryu'; + $managingEditor = 'editor'; + $webMaster = 'Shikiryu'; + $pubDate = (new DateTime())->format(DATE_RSS); + $lastBuildDate = $pubDate; + $category = new Category(); + $category->domain = $link; + $category->value = 'Test Category'; + $generator = 'SRSS'; + $docs = $link; + $cloud = new Cloud(); + $cloud->domain = $link; + $cloud->port = 80; + $cloud->path = '/test'; + $ttl = 3660; + $image = new Image(); + $image->link = $link; + $image->title = 'title of image'; + $rating = 'yes'; + $textInput = 'ignore'; + $skipDays = 'monday'; + $skipHours = '8'; + + $srss = SRSS::create(); + $srss->title = $title; + $srss->link = $link; + $srss->description = $description; + $srss->language = $language; + $srss->copyright = $copyright; + $srss->managingEditor = $managingEditor; + $srss->webMaster = $webMaster; + $srss->pubDate = $pubDate; + $srss->lastBuildDate = $lastBuildDate; + $srss->category = $category; + $srss->generator = $generator; + $srss->docs = $docs; + $srss->cloud = $cloud; + $srss->ttl = $ttl; + $srss->image = $image; + $srss->rating = $rating; + $srss->textInput = $textInput; + $srss->skipDays = $skipDays; + $srss->skipHours = $skipHours; + + $item_title = 'item title'; + $item_link = 'https://example.com'; + $item_description = 'item description'; + $item_author = 'shikiryu@shikiryu.com'; + $item_category = new \Shikiryu\SRSS\Entity\Item\Category(); + $item_category->domain = 'https://shikiryu.com'; + $item_category->value = 'category shikiryu'; + $item_comments = $link.'/comments'; + $item_enclosure = new Enclosure(); + $item_enclosure->url = $item_link; + $item_enclosure->length = 5023; + $item_enclosure->type = 'audio/mp3'; + $item_guid = '123456'; + $item_pubdate = $pubDate; + $item_source = new Source(); + $item_source->url = $item_link; + $item_source->value = 'source'; + $item_media = new Shikiryu\SRSS\Entity\Media\Content(); + $item_media->url = $item_link; + $item_media->type = 'image/jpg'; + $item = new Shikiryu\SRSS\Entity\Item(); + $item->title = $item_title; + $item->link = $item_link; + $item->description = $item_description; + $item->author = $item_author; + $item->category[] = $item_category; + $item->comments = $item_comments; + $item->enclosure = $item_enclosure; + $item->guid = $item_guid; + $item->pubDate = $item_pubdate; + $item->source = $item_source; + $item->medias[] = $item_media; + + $srss->addItem($item); + + self::assertTrue($srss->isValid(), var_export($srss->channel->validated + array_map(static fn ($item) => $item->validated, $srss->items), true)); + } +} \ No newline at end of file From 4eac721473fcb60b0093cb8cc0444104ff779df8 Mon Sep 17 00:00:00 2001 From: Clement Desmidt Date: Thu, 13 Apr 2023 11:44:42 +0200 Subject: [PATCH 15/16] =?UTF-8?q?=F0=9F=93=9D=20update=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index eb53ea0..81a2dd3 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,29 @@ -All documentation @ http://labs.shikiryu.com/SRSS/#_how +# SћíкïяўЦЯSS + +> PHP library reading and creating RSS **disclaimer:** -This class is functionnal. Anyway, use it only if you don't have other choices. +This class is functional. Anyway, use it only if you don't have other choices. For example, [Zend](http://framework.zend.com/manual/current/en/modules/zend.feed.introduction.html) and Symfony got their own RSS factory, don't add another one in. + +## :books: Table of Contents + +- [Installation](#package-installation) +- [Usage](#rocket-usage) +- [Features](#sparkles-features) +- [Support](#hammer_and_wrench-support) +- [Contributing](#memo-contributing) +- [License](#scroll-license) + +## :package: Installation + +```sh +composer install shikiryu/shikiryurss +``` + +## :rocket: Usage + ---------------------------------- How to make it read RSS? @@ -22,8 +42,7 @@ Then, you can take care of articles. You can select a precise article : Or looping them : - foreach($rss as $article) - { + foreach($rss as $article) { echo ''. SRSSTools::formatDate('d/m/y', $item->pubDate).' '.$item->title.''; } @@ -34,7 +53,11 @@ If you like arrays, you can transform the RSS into an array : You can also save it into your server with : $rss->save('/www/rss/rss.xml'); // example - + +Or finally, you can display it with : + + $rss->show(); + ---------------------------------- How to make it create RSS? @@ -53,7 +76,7 @@ Those 3 are mandatory to validate your RSS, other options can be added. Then, you can add articles. Let's imagine $content contains an array from your database. foreach($content as $item){ - $rssitem= new SRSSItem; // we create an item + $rssitem = new Item(); // we create an item $rssitem->title = $item["title"]; // adding title (option) $rssitem->link = $item['link']; // adding link (option) $rssitem->pubDate = $item["date"]; // date automatically transformed into RSS format (option) @@ -72,5 +95,43 @@ The other one does the opposite and add the next item in top of your RSS ---------------------------------- -Contact : -https://shikiryu.com/contact +## :sparkles: Features + +
+
Read every RSS 2.0
+
+ Based on RSS 2.0 specifications. +
+
+ +
+
Write and validate RSS 2.0 file
+
+ Based on RSS 2.0 specifications. +
+
+ +## :hammer_and_wrench: Support + +Please [open an issue](https://github.com/Chouchen/ShikiryuRSS/issues) for support. + + +## :memo: Contributing + +Please contribute using [Github Flow](https://guides.github.com/introduction/flow/). Create a branch, add commits, and [open a pull request](https://github.com/Chouchen/ShikiryuRSS/pulls). + +## :scroll: License + +[Creative Commons Attribution NonCommercial (CC-BY-NC)]() © [Chouchen](https://github.com/Chouchen/) + +All documentation @ http://labs.shikiryu.com/SRSS/#_how. + +Contact : https://shikiryu.com/contact + + + + + + + + From 59b6027819d013bf22306cc83228b8c157645bd1 Mon Sep 17 00:00:00 2001 From: Shikiryu Date: Thu, 13 Apr 2023 21:14:13 +0200 Subject: [PATCH 16/16] =?UTF-8?q?=F0=9F=9A=91=EF=B8=8F=20Corrects=20last?= =?UTF-8?q?=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 6 ++++-- src/Parser/MediaContentParser.php | 17 ----------------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/composer.json b/composer.json index 0638d56..b92702d 100644 --- a/composer.json +++ b/composer.json @@ -5,8 +5,10 @@ "license": "proprietary", "authors": [ { - "name": "clement", - "email": "email@example.com" + "name": "Shikiryu", + "email": "projets@shiki.fr", + "homepage": "https://shikiryu.com", + "role": "Developer" } ], "autoload": { diff --git a/src/Parser/MediaContentParser.php b/src/Parser/MediaContentParser.php index 4896cf5..9179a76 100644 --- a/src/Parser/MediaContentParser.php +++ b/src/Parser/MediaContentParser.php @@ -17,23 +17,6 @@ use Shikiryu\SRSS\SRSSTools; class MediaContentParser extends DomDocument { protected DOMNode $node; // item node - protected $attr; // item's properties - - // possible properties' names - protected static $possibilities = [ - 'title' => 'nohtml', - 'link' => 'link', - 'description' => 'html', - 'author' => 'email', - 'category' => 'nohtml', - 'comments' => 'link', - 'enclosure' => '', - 'guid' => 'nohtml', - 'pubDate' => 'date', - 'source' => 'link', - 'media:group' => 'folder', - 'media:content' => '', - ]; /** * Constructor