diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b004717 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/vendor/ +/.idea +/.phpunit.result.cache +error.log diff --git a/README.md b/README.md index 58cd595..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? @@ -12,7 +32,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 @@ -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? @@ -43,7 +66,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/'; @@ -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 : -http://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 + + + + + + + + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b92702d --- /dev/null +++ b/composer.json @@ -0,0 +1,27 @@ +{ + "name": "shikiryu/shikiryurss", + "description": "A RSS reader and writer", + "minimum-stability": "stable", + "license": "proprietary", + "authors": [ + { + "name": "Shikiryu", + "email": "projets@shiki.fr", + "homepage": "https://shikiryu.com", + "role": "Developer" + } + ], + "autoload": { + "psr-4": { + "Shikiryu\\SRSS\\": "src/" + } + }, + "require-dev": { + "phpunit/phpunit": "^9" + }, + "require": { + "php": ">=8.0", + "ext-dom": "*", + "ext-libxml": "*" + } +} 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/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..3edcd2e --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,23 @@ + + + + + + + tests + + + + + + + + \ No newline at end of file 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 new file mode 100644 index 0000000..c62ddc4 --- /dev/null +++ b/src/Builder/SRSSBuilder.php @@ -0,0 +1,65 @@ +createElement('rss'); + + $root->setAttribute('version', '2.0'); + + $srss->channel->generator = 'Shikiryu RSS'; + + $channel_builder = new ChannelBuilder($this); + $channel = $channel_builder->build($srss->channel); + + $item_builder = new ItemBuilder($this); + foreach ($srss->items as $item) { + $channel->appendChild($item_builder->build($item)); + } + + $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; + } + + /** + * @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): bool|string + { + return $this + ->buildRSS($srss) + ->saveXml(); + } +} \ No newline at end of file diff --git a/src/Entity/Channel.php b/src/Entity/Channel.php new file mode 100644 index 0000000..59aee30 --- /dev/null +++ b/src/Entity/Channel.php @@ -0,0 +1,112 @@ + 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 (new Validator())->isObjectValid($this); + } + + /** + * @return array + */ + public function toArray(): array + { + $vars = get_object_vars($this); + unset($vars['validated']); + return $vars; + } +} \ No newline at end of file diff --git a/src/Entity/Channel/Category.php b/src/Entity/Channel/Category.php new file mode 100644 index 0000000..6923c47 --- /dev/null +++ b/src/Entity/Channel/Category.php @@ -0,0 +1,34 @@ +isObjectValid($this); + } catch (ReflectionException) { + 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..9d379f3 --- /dev/null +++ b/src/Entity/Channel/Cloud.php @@ -0,0 +1,46 @@ +isObjectValid($this); + } catch (ReflectionException) { + return false; + } + } + + public function toArray(): array + { + return get_object_vars($this); + } +} \ No newline at end of file diff --git a/src/Entity/Channel/Image.php b/src/Entity/Channel/Image.php new file mode 100644 index 0000000..a4dfaaa --- /dev/null +++ b/src/Entity/Channel/Image.php @@ -0,0 +1,53 @@ +isObjectValid($this); + } catch (ReflectionException) { + 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 new file mode 100644 index 0000000..3faa0fd --- /dev/null +++ b/src/Entity/Item.php @@ -0,0 +1,79 @@ +isObjectValid($this); + } catch (ReflectionException) { + return false; + } + } + + public function toArray(): array + { + $vars = get_object_vars($this); + unset($vars['validated']); + return $vars; + } +} \ No newline at end of file diff --git a/src/Entity/Item/Category.php b/src/Entity/Item/Category.php new file mode 100644 index 0000000..fe9b493 --- /dev/null +++ b/src/Entity/Item/Category.php @@ -0,0 +1,34 @@ +isObjectValid($this); + } catch (ReflectionException) { + 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..fa9d8fb --- /dev/null +++ b/src/Entity/Item/Enclosure.php @@ -0,0 +1,40 @@ +isObjectValid($this); + } catch (ReflectionException) { + 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..355a1e0 --- /dev/null +++ b/src/Entity/Item/Source.php @@ -0,0 +1,35 @@ +isObjectValid($this); + } catch (ReflectionException) { + return false; + } + } + + 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 new file mode 100644 index 0000000..0cbacdc --- /dev/null +++ b/src/Entity/Media/Content.php @@ -0,0 +1,82 @@ +isObjectValid($this); + } catch (ReflectionException) { + return false; + } + } + + public function toArray(): array + { + return get_object_vars($this); + } +} \ No newline at end of file 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 @@ + not found', $file)); + } + +} \ No newline at end of file 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/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 @@ +Message : ' . $this->getMessage() . ' à la ligne : ' . $this->getLine(); + } +} \ No newline at end of file diff --git a/src/Exception/UnreadableRSSException.php b/src/Exception/UnreadableRSSException.php new file mode 100644 index 0000000..ef9e3a7 --- /dev/null +++ b/src/Exception/UnreadableRSSException.php @@ -0,0 +1,12 @@ +childNodes as $child) { + if ($child->nodeType === XML_ELEMENT_NODE && $child->nodeName !== 'item') { + 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); + } + } + } + } + + /** + * @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; + } +} \ No newline at end of file diff --git a/src/Parser/MediaContentParser.php b/src/Parser/MediaContentParser.php new file mode 100644 index 0000000..9179a76 --- /dev/null +++ b/src/Parser/MediaContentParser.php @@ -0,0 +1,62 @@ +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/Parser/SRSSParser.php b/src/Parser/SRSSParser.php new file mode 100644 index 0000000..684d450 --- /dev/null +++ b/src/Parser/SRSSParser.php @@ -0,0 +1,135 @@ +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 SRSS + * @throws ChannelNotFoundInRSSException + * @throws SRSSException + * @throws UnreadableRSSException + */ + 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. + $channel = $this->getElementsByTagName('channel'); + if($channel->length === 1){ // Good URL and good RSS + $this->_parseChannel(); // loading channel properties + $this->_parseItems(); // loading all items + + return $this->doc; + } + + throw new ChannelNotFoundInRSSException($link); + } + + throw new UnreadableRSSException($link); + } + + /** + * @throws SRSSException + */ + private function _parseItems(): void + { + $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] = ItemParser::read($items->item($i)); + } + } + } + + /** + * putting all RSS attributes into the object + * @throws SRSSException + */ + private function _parseChannel(): 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->{$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; + } + } + } + } + + + /** + * getter of current RSS channel + * @return DOMNode + * @throws SRSSException + */ + private function _getChannel(): DOMNode + { + $channel = $this->getElementsByTagName('channel'); + if($channel->length !== 1) { + throw new ChannelNotFoundInRSSException('channel node not created, or too many channel nodes'); + } + + return $channel->item(0); + } +} \ No newline at end of file diff --git a/src/SRSS.php b/src/SRSS.php new file mode 100644 index 0000000..082fc54 --- /dev/null +++ b/src/SRSS.php @@ -0,0 +1,291 @@ +items = []; + $this->position = 0; + } + + /** + * @param string $link url of the rss + * + * @return SRSS + * @throws ChannelNotFoundInRSSException + * @throws UnreadableRSSException + * @throws SRSSException + */ + public static function read(string $link): SRSS + { + return (new SRSSParser())->parse($link); + } + + /** + * @return SRSS + */ + public static function create(): SRSS + { + $doc = new SRSS; + + $doc->channel = new Channel(); + $doc->items = []; + + return $doc; + } + + /** + * check if current RSS is a valid one (based on specifications) + * @return bool + */ + public function isValid(): bool + { + try { + $valid = true; + foreach ($this->getItems() as $item) { + if ($item->isValid() === false) { + $valid = false; + } + } + + return ($valid && $this->channel->isValid()); + } catch (ReflectionException) { + return false; + } + } + + /** + * @param $name + * + * @return bool + */ + public function __isset($name) + { + return isset($this->channel->{$name}); + } + + /** + * setter of others attributes + * + * @param $name + * @param $val + * + * @throws SRSSException + */ + public function __set($name, $val) + { + if (!property_exists(Channel::class, $name)) { + throw new PropertyNotFoundException(Channel::class, $name); + } + if ((new Validator())->isValidValueForObjectProperty($this->channel, $name, $val)) { + if (SRSSTools::getPropertyType(Channel::class, $name) === 'array') { + $this->channel->{$name}[] = $val; + } else { + $this->channel->{$name} = $val; + } + } + } + + /** + * getter of others attributes + * + * @param $name + * + * @return null|string + */ + public function __get($name) + { + return $this->channel->{$name} ?? null; + } + + /** + * rewind from Iterator + */ + public function rewind(): void + { + $this->position = 0; + } + + /** + * current from Iterator + */ + public function current(): Item + { + return $this->items[$this->position]; + } + + /** + * key from Iterator + */ + public function key(): int + { + return $this->position; + } + + /** + * next from Iterator + */ + public function next(): void + { + ++$this->position; + } + + /** + * valid from Iterator + */ + public function valid(): bool + { + return isset($this->items[$this->position]); + } + + /** + * getter of 1st item + * @return Item|null + */ + public function getFirst(): ?Item + { + return $this->getItem(1); + } + + /** + * getter of last item + * @return Item + */ + public function getLast(): Item + { + $items = $this->getItems(); + + return $items[array_key_last($items)]; + } + + /** + * getter of an item + * + * @param $i int + * + * @return Item|null + */ + public function getItem(int $i): ?Item + { + $i--; + + return $this->items[$i] ?? null; + } + + /** + * getter of all items + * @return Item[] + */ + public function getItems(): array + { + return $this->items; + } + + /** + * transform current object into an array + * @return array + */ + public function toArray(): array + { + $doc = $this->channel->toArray(); + + foreach ($this->getItems() as $item) { + $doc['items'][] = $item->toArray(); + } + + return array_filter($doc); + } + + /** + * @param Item $rssItem + * + * @return array|Item[] + */ + public function addItem(Item $rssItem): array + { + $this->items[] = $rssItem; + + return $this->items; + } + + /** + * @param Item $firstItem + * + * @return array|Item[] + */ + public function addItemBefore(Item $firstItem): array + { + array_unshift($this->items, $firstItem); + + return $this->items; + } + + /** + * @param string $path + * + * @return void + * @throws \Shikiryu\SRSS\Exception\DOMBuilderException + */ + public function save(string $path): void + { + (new SRSSBuilder('1.0', 'UTF-8'))->build($this, $path); + } + + /** + * @return false|string + * @throws \Shikiryu\SRSS\Exception\DOMBuilderException + */ + public function show(): bool|string + { + return (new SRSSBuilder('1.0', 'UTF-8'))->show($this); + } + +} diff --git a/src/SRSSTools.php b/src/SRSSTools.php new file mode 100644 index 0000000..5ab55f1 --- /dev/null +++ b/src/SRSSTools.php @@ -0,0 +1,211 @@ +getType()?->getName(); + } + public static function check($check, $flag) + { + 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), + '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 + * + * @param $format string wanted format + * @param $date string RSS date + * + * @return string 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(string $date, string $format = ''): string + { + $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 $datetime->format(DATE_RSS); + } + + if (strtotime($date) !==false ) { + 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)); + } + + /** + * 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(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 + */ + public static function HTML4XML(string $check): string + { + return sprintf('', htmlspecialchars($check)); + } + + /** + * delete html tags + * + * @param $check string to format + * + * @return string formatted string + */ + 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(string $check): string + { + $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(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(string $check): bool|string + { + $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(int $check): bool|int + { + return filter_var($check, FILTER_VALIDATE_INT); + } + + /** + * @param $check + * + * @return mixed + */ + public static function checkMediaType($check): mixed + { + return $check; + } + + /** + * @param $check + * + * @return mixed|null + */ + public static function checkMediaMedium($check): ?string + { + return in_array($check, ['image', 'audio', 'video', 'document', 'executable']) ? $check : null; + } + + /** + * @param $check + * + * @return mixed|null + */ + public static function checkBool($check): ?string + { + return in_array($check, ['true', 'false']) ? $check : null; + } + + /** + * @param $check + * + * @return mixed|null + */ + public static function checkMediumExpression($check): ?string + { + return in_array($check, ['sample', 'full', 'nonstop']) ? $check : null; + } +} \ No newline at end of file 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 @@ +_getClassProperties(get_class($object)), + static fn($p) => $p->getName() === $property + ); + + if (count($properties) !== 1) { + return null; + } + + return current($properties); + } + + /** + * @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[$property->name] = $this->_validateProperty($annotation, $value); + } + + 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}); + } + + /** + * @throws ReflectionException + */ + public function isObjectValid($object): bool + { + if (!$object->validated) { + $object = $this->validateObject($object); + } + + return !in_array(false, $object->validated, true); + } + + /** + * @throws ReflectionException + */ + 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']}; + + if (empty($propertyValue) && !in_array('required', $property['rules'], true)) { + continue; + } + + foreach ($property['rules'] as $propertyAnnotation) { + $annotation = explode(' ', $propertyAnnotation); + + $object->validated[$property['name']] = $this->_validateProperty($annotation, $propertyValue); + } + } + + return $object; + } + + private function _validateProperty(array $annotation, $property): bool + { + if ($annotation[0] === 'var') { + return true; + } + + 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 + { + return (new ReflectionClass($class))->getProperties(); + } + + private function _getPropertyAnnotations(ReflectionProperty $property): array + { + preg_match_all('#@(.*?)\n#s', $property->getDocComment(), $annotations); + + return array_map(static 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)); + } + + 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 + * 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'] + ); + } + + private function _validateContentMedia($value): bool + { + 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']); + } + + private function _validateEmail($value): bool + { + return filter_var($value, FILTER_VALIDATE_EMAIL); + } +} \ 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/BasicBuilderTest.php b/tests/BasicBuilderTest.php new file mode 100644 index 0000000..9a84ddd --- /dev/null +++ b/tests/BasicBuilderTest.php @@ -0,0 +1,56 @@ +saved)) { + unlink($this->saved); + } + } + public function testCreateBasicRSS(): void + { + $title = 'My Blog'; + $description = 'is the best'; + $link = 'http://shikiryu.com/devblog/'; + $srss = SRSS::create(); + $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'], + ['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()); + + self::assertEquals($title, $srss->title); + self::assertEquals($description, $srss->description); + self::assertEquals($link, $srss->link); + + $builder = new SRSSBuilder(); + $builder->build($srss, $this->saved); + + self::assertFileExists($this->saved); + + self::assertIsString($srss->show()); + } +} \ No newline at end of file diff --git a/tests/BasicReaderTest.php b/tests/BasicReaderTest.php new file mode 100644 index 0000000..1f987c5 --- /dev/null +++ b/tests/BasicReaderTest.php @@ -0,0 +1,99 @@ +title); + $first_item = $rss->getFirst(); + self::assertNotNull($first_item); + self::assertEquals('RSS Tutorial', $first_item->title); + + self::assertTrue($rss->isValid()); + } + + public function testSpecificationExampleRSS(): void + { + $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); + + self::assertTrue($rss->isValid()); + } + + public function testChannelImage(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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(): void + { + $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/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 diff --git a/tests/ExceptionTest.php b/tests/ExceptionTest.php new file mode 100644 index 0000000..19fca5a --- /dev/null +++ b/tests/ExceptionTest.php @@ -0,0 +1,29 @@ +expectException(PropertyNotFoundException::class); + $srss->notfound = 'true'; + } + + public function testRssNotFound(): void + { + $this->expectException(UnreadableRSSException::class); + SRSS::read('not_found.xml'); + } + + public function testMissingChannel(): void + { + $this->expectException(ChannelNotFoundInRSSException::class); + SRSS::read(__DIR__ . '/resources/invalid-no-channel.xml'); + } +} \ No newline at end of file diff --git a/tests/MediaTest.php b/tests/MediaTest.php new file mode 100644 index 0000000..0abb611 --- /dev/null +++ b/tests/MediaTest.php @@ -0,0 +1,31 @@ +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->isValid(), var_export($rss->channel->validated, true)); + } + + public function testMusicVideo(): void + { + $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->isValid()); + } +} \ No newline at end of file diff --git a/tests/OriginalReaderSRSSTest.php b/tests/OriginalReaderSRSSTest.php new file mode 100644 index 0000000..0b1bd7a --- /dev/null +++ b/tests/OriginalReaderSRSSTest.php @@ -0,0 +1,41 @@ +saved)) { + unlink($this->saved); + } + } + + public function testOriginalReader(): void + { + $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..c57cc73 --- /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){ + $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(); + $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 diff --git a/tests/resources/basic.xml b/tests/resources/basic.xml new file mode 100644 index 0000000..d2329e1 --- /dev/null +++ b/tests/resources/basic.xml @@ -0,0 +1,22 @@ + + + + + test Home Page + https://www.test.com + 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 + + + + \ 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 + + + 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 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 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 diff --git a/tests/resources/tmp/build/.gitkeep b/tests/resources/tmp/build/.gitkeep new file mode 100644 index 0000000..e69de29