diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | composer.json | 15 | ||||
-rw-r--r-- | composer.lock | 325 | ||||
-rw-r--r-- | js/package-lock.json | 668 | ||||
-rw-r--r-- | lib/AppInfo/Application.php | 113 | ||||
-rw-r--r-- | lib/Config/Config.php | 4 | ||||
-rw-r--r-- | lib/Config/FetcherConfig.php | 118 | ||||
-rw-r--r-- | lib/Config/LegacyGuzzleClient.php | 65 | ||||
-rw-r--r-- | lib/Config/LegacyGuzzleResponse.php | 86 | ||||
-rw-r--r-- | lib/Db/Item.php | 14 | ||||
-rw-r--r-- | lib/Fetcher/FeedFetcher.php | 377 | ||||
-rw-r--r-- | lib/Fetcher/Fetcher.php | 45 | ||||
-rw-r--r-- | lib/Fetcher/IFeedFetcher.php | 17 | ||||
-rw-r--r-- | lib/Fetcher/YoutubeFetcher.php | 25 | ||||
-rw-r--r-- | lib/PostProcessor/LWNProcessor.php | 117 | ||||
-rw-r--r-- | lib/Service/FeedService.php | 47 | ||||
-rw-r--r-- | lib/Utility/PicoFeedClientFactory.php | 42 | ||||
-rw-r--r-- | lib/Utility/PicoFeedFaviconFactory.php | 40 | ||||
-rw-r--r-- | lib/Utility/PsrLogger.php | 97 | ||||
-rw-r--r-- | tests/Unit/Config/ConfigTest.php | 2 | ||||
-rw-r--r-- | tests/Unit/Fetcher/FeedFetcherTest.php | 650 | ||||
-rw-r--r-- | tests/Unit/Service/FeedServiceTest.php | 11 |
22 files changed, 1605 insertions, 1277 deletions
@@ -41,10 +41,10 @@ app_name:=$(notdir $(CURDIR)) build_tools_directory:=$(CURDIR)/build/tools -source_build_directory:=$(CURDIR)/build/source/news +source_build_directory:=$(CURDIR)/build/source/$(app_name) source_artifact_directory:=$(CURDIR)/build/artifacts/source source_package_name:=$(source_artifact_directory)/$(app_name) -appstore_build_directory:=$(CURDIR)/build/appstore/news +appstore_build_directory:=$(CURDIR)/build/appstore/$(app_name) appstore_artifact_directory:=$(CURDIR)/build/artifacts/appstore appstore_package_name:=$(appstore_artifact_directory)/$(app_name) npm:=$(shell which npm 2> /dev/null) diff --git a/composer.json b/composer.json index 4a03b86a2..de3b7a028 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,12 @@ "role": "Developer" }, { + "name": "Sean Molenaar", + "email": "sean@seanmolenaar.eu", + "homepage": "https://seanmolenaar.eu", + "role": "Developer" + }, + { "name": "Alessandro Cosentino", "homepage": "http://algorithmsforthekitchen.com/", "email": "cosenal@gmail.com", @@ -33,11 +39,16 @@ "ezyang/htmlpurifier": "4.10.0", "pear/net_url2": "2.2.2", "riimu/kit-pathjoin": "1.2.0", - "nicolus/picofeed": "0.1.35" + "debril/feed-io": "^3.0", + "arthurhoaro/favicon": "^1.2" }, "require-dev": { "phpunit/phpunit": "^6.5", - "squizlabs/php_codesniffer": "^3.3" + "squizlabs/php_codesniffer": "^3.3", + "guzzlehttp/guzzle": "~6.3" + }, + "replace": { + "guzzlehttp/guzzle": "*" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 2c814bcd0..e981b11f9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,93 +4,93 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f4a0d96b7e83ec4d9d232412b9e61566", + "content-hash": "1630b553e70e8245b11922394d4d9f59", "packages": [ { - "name": "ezyang/htmlpurifier", - "version": "v4.10.0", + "name": "arthurhoaro/favicon", + "version": "v1.2.2", "source": { "type": "git", - "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "d85d39da4576a6934b72480be6978fb10c860021" + "url": "https://github.com/ArthurHoaro/favicon.git", + "reference": "50fd2a0f984db13948a69ab120451e03e41979fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/d85d39da4576a6934b72480be6978fb10c860021", - "reference": "d85d39da4576a6934b72480be6978fb10c860021", + "url": "https://api.github.com/repos/ArthurHoaro/favicon/zipball/50fd2a0f984db13948a69ab120451e03e41979fa", + "reference": "50fd2a0f984db13948a69ab120451e03e41979fa", "shasum": "" }, "require": { - "php": ">=5.2" + "php": ">=5.3.3" }, "require-dev": { - "simpletest/simpletest": "^1.1" + "phpunit/phpunit": "~4.8", + "weew/helpers-filesystem": "~1.0" }, "type": "library", "autoload": { - "psr-0": { - "HTMLPurifier": "library/" - }, - "files": [ - "library/HTMLPurifier.composer.php" - ] + "psr-4": { + "Favicon\\": "src/Favicon/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL" + "Apache-2.0" ], "authors": [ { - "name": "Edward Z. Yang", - "email": "admin@htmlpurifier.org", - "homepage": "http://ezyang.com" + "name": "Chris Shiflett", + "homepage": "http://shiflett.org/" + }, + { + "name": "Arthur Hoaro", + "homepage": "http://hoa.ro" } ], - "description": "Standards compliant HTML filter written in PHP", - "homepage": "http://htmlpurifier.org/", + "description": "PHP Library used to discover favicon from given URL", + "homepage": "https://github.com/ArthurHoaro/favicon", "keywords": [ - "html" + "favicon", + "finder", + "icon" ], - "time": "2018-02-23T01:58:20+00:00" + "time": "2018-09-08T09:37:54+00:00" }, { - "name": "nicolus/picofeed", - "version": "v0.1.35", + "name": "debril/feed-io", + "version": "v3.1.1", "source": { "type": "git", - "url": "https://github.com/nicolus/picoFeed.git", - "reference": "3a27b47de31eedec075c719f961783c5db7a7b08" + "url": "https://github.com/alexdebril/feed-io.git", + "reference": "a79a09a915540b5475b12c82effb3dd43c2b2a0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nicolus/picoFeed/zipball/3a27b47de31eedec075c719f961783c5db7a7b08", - "reference": "3a27b47de31eedec075c719f961783c5db7a7b08", + "url": "https://api.github.com/repos/alexdebril/feed-io/zipball/a79a09a915540b5475b12c82effb3dd43c2b2a0b", + "reference": "a79a09a915540b5475b12c82effb3dd43c2b2a0b", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-iconv": "*", - "ext-libxml": "*", - "ext-simplexml": "*", - "ext-xml": "*", - "php": ">=5.3.0", - "zendframework/zendxml": "^1.0" + "guzzlehttp/guzzle": "~6.2", + "php": ">=5.6.0", + "psr/log": "~1.0" }, "require-dev": { - "phpdocumentor/reflection-docblock": "2.0.4", - "phpunit/phpunit": "4.8.26", - "symfony/yaml": "2.8.7" + "friendsofphp/php-cs-fixer": "^2.4", + "monolog/monolog": "1.*", + "phpunit/phpunit": "~5.6.0" }, "suggest": { - "ext-curl": "PicoFeed will use cURL if present" + "monolog/monolog": "Allows to handle logs", + "symfony/console": "Allows to use the command line interface" }, "bin": [ - "picofeed" + "bin/feedio" ], "type": "library", "autoload": { - "psr-0": { - "PicoFeed": "lib/" + "psr-4": { + "FeedIo\\": "src/FeedIo" } }, "notification-url": "https://packagist.org/downloads/", @@ -99,12 +99,69 @@ ], "authors": [ { - "name": "Frédéric Guillot" + "name": "Alexandre Debril", + "email": "alex.debril@gmail.com" } ], - "description": "Modern library to handle RSS/Atom feeds", - "homepage": "https://github.com/miniflux/picoFeed", - "time": "2017-06-20T22:54:47+00:00" + "description": "PHP library built to consume and serve JSONFeed / RSS / Atom feeds", + "homepage": "https://feed-io.net", + "keywords": [ + "atom", + "cli", + "client", + "feed", + "jsonfeed", + "news", + "rss" + ], + "time": "2018-06-18T12:31:47+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.10.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "d85d39da4576a6934b72480be6978fb10c860021" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/d85d39da4576a6934b72480be6978fb10c860021", + "reference": "d85d39da4576a6934b72480be6978fb10c860021", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "require-dev": { + "simpletest/simpletest": "^1.1" + }, + "type": "library", + "autoload": { + "psr-0": { + "HTMLPurifier": "library/" + }, + "files": [ + "library/HTMLPurifier.composer.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "time": "2018-02-23T01:58:20+00:00" }, { "name": "pear/net_url2", @@ -171,6 +228,53 @@ "time": "2017-08-25T06:16:11+00:00" }, { + "name": "psr/log", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2018-11-20T15:27:04+00:00" + }, + { "name": "riimu/kit-pathjoin", "version": "v1.2.0", "source": { @@ -219,52 +323,6 @@ "system" ], "time": "2017-07-09T14:41:04+00:00" - }, - { - "name": "zendframework/zendxml", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/zendframework/ZendXml.git", - "reference": "267db6a2c431a08a8f8ff0f1f4c302a5ba6f5b99" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/zendframework/ZendXml/zipball/267db6a2c431a08a8f8ff0f1f4c302a5ba6f5b99", - "reference": "267db6a2c431a08a8f8ff0f1f4c302a5ba6f5b99", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev", - "dev-develop": "1.2.x-dev" - } - }, - "autoload": { - "psr-4": { - "ZendXml\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Utility library for XML usage, best practices, and security in PHP", - "keywords": [ - "ZendFramework", - "security", - "xml", - "zf" - ], - "time": "2018-04-30T15:11:04+00:00" } ], "packages-dev": [ @@ -1640,16 +1698,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e" + "reference": "379deb987e26c7cd103a7b387aea178baec96e48" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6ad28354c04b364c3c71a34e4a18b629cc3b231e", - "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/379deb987e26c7cd103a7b387aea178baec96e48", + "reference": "379deb987e26c7cd103a7b387aea178baec96e48", "shasum": "" }, "require": { @@ -1687,7 +1745,65 @@ "phpcs", "standards" ], - "time": "2018-09-23T23:08:17+00:00" + "time": "2018-12-19T23:57:18+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "backendtea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2018-08-06T14:22:27+00:00" }, { "name": "theseer/tokenizer", @@ -1731,20 +1847,21 @@ }, { "name": "webmozart/assert", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" }, "require-dev": { "phpunit/phpunit": "^4.6", @@ -1777,7 +1894,7 @@ "check", "validate" ], - "time": "2018-01-29T19:49:41+00:00" + "time": "2018-12-25T11:19:39+00:00" } ], "aliases": [], diff --git a/js/package-lock.json b/js/package-lock.json index 97204fc9b..eb3d2e289 100644 --- a/js/package-lock.json +++ b/js/package-lock.json @@ -31,7 +31,7 @@ }, "acorn": { "version": "2.6.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.6.4.tgz", + "resolved": "http://registry.npmjs.org/acorn/-/acorn-2.6.4.tgz", "integrity": "sha1-6x9FtKQ/ox0DcBpexG87Umc+kO4=", "dev": true }, @@ -89,7 +89,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true + "dev": true, + "optional": true }, "angular": { "version": "1.7.5", @@ -351,7 +352,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "dev": true, + "optional": true }, "assign-symbols": { "version": "1.0.0", @@ -361,7 +363,7 @@ }, "async": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz", "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", "dev": true }, @@ -379,9 +381,9 @@ "optional": true }, "atob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, "aws-sign2": { @@ -392,9 +394,9 @@ "optional": true }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true, "optional": true }, @@ -503,9 +505,9 @@ } }, "binary-extensions": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", - "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", + "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==", "dev": true }, "blob": { @@ -515,9 +517,9 @@ "dev": true }, "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", + "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==", "dev": true }, "body-parser": { @@ -548,9 +550,9 @@ } }, "bowser": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.3.tgz", - "integrity": "sha512-/gp96UlcFw5DbV2KQPCqTqi0Mb9gZRyDAHiDsGEH+4B/KOQjeoE5lM1PxlVX8DQDvfEfitmC1rW2Oy8fk/XBDg==", + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-1.9.4.tgz", + "integrity": "sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==", "dev": true }, "brace-expansion": { @@ -592,6 +594,28 @@ } } }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", + "dev": true + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -616,7 +640,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -710,7 +734,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -800,9 +824,9 @@ }, "dependencies": { "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -872,7 +896,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -920,12 +944,12 @@ } }, "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { - "color-name": "^1.1.1" + "color-name": "1.1.3" } }, "color-name": { @@ -956,22 +980,30 @@ }, "dependencies": { "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true } } }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "dev": true, + "optional": true, "requires": { "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true, + "optional": true + }, "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", @@ -1018,7 +1050,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -1090,7 +1122,7 @@ }, "convert-source-map": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=", "dev": true }, @@ -1129,25 +1161,22 @@ "dev": true }, "css": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.3.tgz", - "integrity": "sha512-0W171WccAjQGGTKLhw4m2nnl0zPHUlTO/I8td4XzJgIB8Hg3ZZx71qT4G4eX8OVsSiaAKiUMy73E3nsbPlg2DQ==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", + "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", "dev": true, "requires": { - "inherits": "^2.0.1", - "source-map": "^0.1.38", - "source-map-resolve": "^0.5.1", + "inherits": "^2.0.3", + "source-map": "^0.6.1", + "source-map-resolve": "^0.5.2", "urix": "^0.1.0" }, "dependencies": { "source-map": { - "version": "0.1.43", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", - "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, @@ -1302,7 +1331,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "dev": true, + "optional": true }, "depd": { "version": "1.1.2", @@ -1334,9 +1364,9 @@ "dev": true }, "detect-node": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz", - "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", "dev": true }, "di": { @@ -1491,7 +1521,7 @@ "dependencies": { "debug": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz", "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", "dev": true, "requires": { @@ -1528,7 +1558,7 @@ "dependencies": { "debug": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz", "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", "dev": true, "requires": { @@ -1570,18 +1600,18 @@ "dev": true }, "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { "is-arrayish": "^0.2.1" } }, "es5-ext": { - "version": "0.10.45", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.45.tgz", - "integrity": "sha512-FkfM6Vxxfmztilbxxz5UKSD4ICMf5tSpRFtDNtkAhOxZ0EKtX6qwmXNyH/sFyIbX2P/nU5AMiA9jilWsUGJzCQ==", + "version": "0.10.46", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", + "integrity": "sha512-24XxRvJXNFwEMpJb3nOkiRJKRoupmjYmOPVlI65Qy2SrtxwOTB+g6ODjBKOtwEHbYrhWRty9xxOWLNdClT2djw==", "dev": true, "requires": { "es6-iterator": "~2.0.3", @@ -1601,9 +1631,9 @@ } }, "es6-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", - "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz", + "integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==", "dev": true, "optional": true }, @@ -1847,9 +1877,9 @@ } }, "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, "extend-shallow": { @@ -1955,7 +1985,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "dev": true, + "optional": true }, "eyes": { "version": "0.1.8", @@ -2129,12 +2160,12 @@ "dev": true }, "follow-redirects": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.0.tgz", - "integrity": "sha512-fdrt472/9qQ6Kgjvb935ig6vJCuofpBUD14f9Vb+SLlm7xIe4Qva5gey8EKtv8lp7ahE1wilg3xL1znpVGtZIA==", + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.8.tgz", + "integrity": "sha512-sy1mXPmv7kLAMKW/8XofG7o9T+6gAjzdZK4AJF6ryqQYUa/hnzgiypoeUecZ53x7XiqKNEpNqLtS97MshW2nxg==", "dev": true, "requires": { - "debug": "^3.1.0" + "debug": "=3.1.0" }, "dependencies": { "debug": { @@ -2180,6 +2211,18 @@ "asynckit": "^0.4.0", "combined-stream": "1.0.6", "mime-types": "^2.1.12" + }, + "dependencies": { + "combined-stream": { + "version": "1.0.6", + "resolved": "http://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "dev": true, + "optional": true, + "requires": { + "delayed-stream": "~1.0.0" + } + } } }, "fragment-cache": { @@ -2268,12 +2311,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2288,17 +2333,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2415,7 +2463,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2427,6 +2476,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2441,6 +2491,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2448,12 +2499,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -2472,6 +2525,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -2552,7 +2606,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2564,6 +2619,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -2685,6 +2741,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2802,6 +2859,17 @@ "inherits": "2", "minimatch": "^2.0.1", "once": "^1.3.0" + }, + "dependencies": { + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "dev": true, + "requires": { + "brace-expansion": "^1.0.0" + } + } } }, "glob-base": { @@ -2871,9 +2939,18 @@ "unique-stream": "^1.0.0" }, "dependencies": { + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "dev": true, + "requires": { + "brace-expansion": "^1.0.0" + } + }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -3066,9 +3143,9 @@ }, "dependencies": { "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true }, "clone-stats": { @@ -3084,9 +3161,9 @@ "dev": true }, "vinyl": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", - "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", "dev": true, "requires": { "clone": "^2.1.1", @@ -3113,9 +3190,9 @@ }, "dependencies": { "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "minimatch": { @@ -3193,7 +3270,7 @@ }, "lodash": { "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "resolved": "http://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", "dev": true }, @@ -3204,9 +3281,9 @@ "dev": true }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -3300,15 +3377,15 @@ } }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -3392,9 +3469,9 @@ }, "dependencies": { "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true } } @@ -3435,30 +3512,47 @@ } }, "handlebars": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", + "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", "dev": true, "requires": { - "async": "^1.4.0", + "async": "^2.5.0", "optimist": "^0.6.1", - "source-map": "^0.4.4", - "uglify-js": "^2.6" + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" }, "dependencies": { "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "dev": true, + "requires": { + "lodash": "^4.17.10" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-js": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", + "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", "dev": true, + "optional": true, "requires": { - "amdefine": ">=0.0.4" + "commander": "~2.17.1", + "source-map": "~0.6.1" } } } @@ -3471,13 +3565,13 @@ "optional": true }, "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", + "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", "dev": true, "optional": true, "requires": { - "ajv": "^5.1.0", + "ajv": "^5.3.0", "har-schema": "^2.0.0" } }, @@ -3585,9 +3679,9 @@ } }, "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", "dev": true }, "htmlparser2": { @@ -3605,7 +3699,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -3755,7 +3849,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { @@ -3875,23 +3969,6 @@ } } }, - "is-odd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", - "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", - "dev": true, - "requires": { - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -3970,10 +4047,13 @@ "dev": true }, "isbinaryfile": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", - "integrity": "sha1-Sj6XTsDLqQBNP8bN5yCeppNopiE=", - "dev": true + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", + "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", + "dev": true, + "requires": { + "buffer-alloc": "^1.2.0" + } }, "isemail": { "version": "1.2.0", @@ -4071,7 +4151,7 @@ }, "joi": { "version": "6.10.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-6.10.1.tgz", + "resolved": "http://registry.npmjs.org/joi/-/joi-6.10.1.tgz", "integrity": "sha1-TVDDGAeRIgAP5fFq8f+OGRe3fgY=", "dev": true, "requires": { @@ -4098,9 +4178,9 @@ }, "dependencies": { "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true } } @@ -4132,9 +4212,9 @@ }, "dependencies": { "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "minimatch": { @@ -4183,7 +4263,7 @@ }, "jsonfile": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "optional": true, @@ -4249,15 +4329,15 @@ }, "dependencies": { "colors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.0.tgz", - "integrity": "sha512-EDpX3a7wHMWFA7PUHWPHNWqOxIIRSJetuwl0AS5Oi/5FMV8kWm69RTlgm00GKjBO1xFHMtBbL49yRtMMdticBw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.2.tgz", + "integrity": "sha512-rhP0JSBGYvpcNQj4s5AdShMeE5ahMop96cTeDl/v9qQQm2fYClE2QXZRi8wLzc+GmXSxdIqqbOIAhyObEXDbfQ==", "dev": true }, "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -4276,7 +4356,7 @@ }, "lodash": { "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "resolved": "http://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", "dev": true }, @@ -4325,9 +4405,9 @@ } }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "minimatch": { @@ -4434,7 +4514,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -4464,7 +4544,7 @@ }, "lodash": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", "dev": true }, @@ -4478,9 +4558,9 @@ }, "dependencies": { "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true } } @@ -4648,7 +4728,7 @@ }, "log4js": { "version": "0.6.38", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", + "resolved": "http://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz", "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=", "dev": true, "requires": { @@ -4658,7 +4738,7 @@ "dependencies": { "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -4693,9 +4773,9 @@ "dev": true }, "make-error": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", - "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", "dev": true }, "make-error-cause": { @@ -4955,18 +5035,18 @@ "dev": true }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", + "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", "dev": true }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", + "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", "dev": true, "requires": { - "mime-db": "~1.33.0" + "mime-db": "~1.36.0" } }, "minimatch": { @@ -4980,7 +5060,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -5007,7 +5087,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -5016,7 +5096,7 @@ "dependencies": { "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true } @@ -5042,22 +5122,22 @@ } }, "mute-stdout": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.0.tgz", - "integrity": "sha1-WzLqB+tDyd7WEwQ0z5JvRrKn/U0=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==", "dev": true }, "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", "dev": true, "optional": true }, "nanomatch": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", - "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -5065,7 +5145,6 @@ "define-property": "^2.0.2", "extend-shallow": "^3.0.2", "fragment-cache": "^0.2.1", - "is-odd": "^2.0.0", "is-windows": "^1.0.2", "kind-of": "^6.0.2", "object.pick": "^1.3.0", @@ -5075,9 +5154,9 @@ } }, "natives": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.4.tgz", - "integrity": "sha512-Q29yeg9aFKwhLVdkTAejM/HvYG0Y1Am1+HUkFQGn5k2j8GS+v60TVmZh6nujpEAj/qql+wGUrlryO8bF+b1jEg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.5.tgz", + "integrity": "sha512-1pJ+02gl2KJgCPFtpZGtuD4lGSJnIZvvFHCQTOeDRMSXjfu2GmYWuhI8NFMA4W2I5NNFRbfy/YCiVt4CgNpP8A==", "dev": true }, "negotiator": { @@ -5125,9 +5204,9 @@ }, "dependencies": { "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", "dev": true } } @@ -5184,9 +5263,9 @@ "dev": true }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true, "optional": true }, @@ -5324,7 +5403,7 @@ "dependencies": { "minimist": { "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true } @@ -5398,7 +5477,7 @@ }, "os-locale": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -5528,9 +5607,9 @@ "dev": true }, "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "path-root": { @@ -5678,7 +5757,7 @@ }, "kind-of": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", "dev": true } @@ -5727,6 +5806,13 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", + "dev": true, + "optional": true + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -5753,7 +5839,7 @@ }, "chalk": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", "dev": true, "requires": { @@ -5764,7 +5850,7 @@ }, "strip-ansi": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", "integrity": "sha1-OeipjQRNFQZgq+SmgIrPcLt7yZE=", "dev": true } @@ -5783,9 +5869,9 @@ "dev": true }, "randomatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", - "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz", + "integrity": "sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ==", "dev": true, "requires": { "is-number": "^4.0.0", @@ -5863,7 +5949,7 @@ }, "readable-stream": { "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { @@ -5874,15 +5960,14 @@ } }, "readdirp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", - "readable-stream": "^2.0.2", - "set-immediate-shim": "^1.0.1" + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" }, "dependencies": { "graceful-fs": { @@ -5897,18 +5982,9 @@ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -5977,9 +6053,9 @@ "dev": true }, "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", "dev": true }, "repeat-string": { @@ -6004,32 +6080,32 @@ "dev": true }, "request": { - "version": "2.87.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", - "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "dev": true, "optional": true, "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "uuid": "^3.3.2" } }, "request-progress": { @@ -6049,9 +6125,9 @@ "dev": true }, "resolve": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", - "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "dev": true, "requires": { "path-parse": "^1.0.5" @@ -6098,9 +6174,9 @@ }, "dependencies": { "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -6164,12 +6240,6 @@ "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=", "dev": true }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true - }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", @@ -6211,9 +6281,9 @@ }, "dependencies": { "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -6389,7 +6459,7 @@ "dependencies": { "debug": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz", "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", "dev": true, "requires": { @@ -6422,7 +6492,7 @@ "dependencies": { "debug": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz", "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", "dev": true, "requires": { @@ -6458,7 +6528,7 @@ "dependencies": { "debug": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.3.3.tgz", "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=", "dev": true, "requires": { @@ -6493,7 +6563,7 @@ }, "debug": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "resolved": "http://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", "dev": true, "requires": { @@ -6540,9 +6610,9 @@ "dev": true }, "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.1.tgz", + "integrity": "sha512-hxSPZbRZvSDuOvADntOElzJpenIR7wXJkuoUcUtS0erbgt2fgeaoPIYretfKpslMhfFDY4k0MZ2F5CUzhBsSvQ==", "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", @@ -6550,9 +6620,9 @@ } }, "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", "dev": true }, "spdx-expression-parse": { @@ -6566,9 +6636,9 @@ } }, "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz", + "integrity": "sha512-TfOfPcYGBB5sDuPn3deByxPhmfegAhpDYKSOXZQN81Oyrrif8ZCodOLzK3AesELnCx03kikhyDwh0pfvvQvF8w==", "dev": true }, "split": { @@ -6690,7 +6760,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -6747,7 +6817,7 @@ }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true, "optional": true @@ -6770,7 +6840,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -6876,12 +6946,13 @@ } }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "dev": true, "optional": true, "requires": { + "psl": "^1.1.24", "punycode": "^1.4.1" } }, @@ -6982,7 +7053,7 @@ }, "yargs": { "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, "requires": { @@ -7131,13 +7202,10 @@ "dev": true }, "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true }, "user-home": { "version": "1.1.1", @@ -7196,9 +7264,9 @@ } }, "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "dev": true, "requires": { "spdx-correct": "^3.0.0", @@ -7252,7 +7320,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -7325,9 +7393,9 @@ "dev": true }, "winston": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.2.tgz", - "integrity": "sha512-4S/Ad4ZfSNl8OccCLxnJmNISWcm2joa6Q0YGDxlxMzH0fgSwWsjMt+SmlNwCqdpaPg3ev1HKkMBsIiXeSUwpbA==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.4.tgz", + "integrity": "sha512-NBo2Pepn4hK4V01UfcWcDlmiVTs7VTB1h7bgnB0rgP146bYhMxX0ypCz3lBOfNxCO4Zuek7yeT+y/zM1OfMw4Q==", "dev": true, "requires": { "async": "~1.0.0", @@ -7346,7 +7414,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { @@ -7412,7 +7480,7 @@ }, "yargs": { "version": "3.32.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", "dev": true, "requires": { diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index d88bbbaec..b2773a224 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -13,27 +13,46 @@ namespace OCA\News\AppInfo; +use Closure; +use FeedIo\FeedIo; use HTMLPurifier; use HTMLPurifier_Config; + +use OCA\News\Config\FetcherConfig; +use OCA\News\Utility\PsrLogger; +use OCP\BackgroundJob\IJobList; + +use OCP\IContainer; +use OCP\INavigationManager; +use OCP\IURLGenerator; +use OCP\IConfig; +use OCP\AppFramework\App; +use OCP\Files\IRootFolder; +use OCP\Files\Node; + + +use OCA\News\Config\AppConfig; use OCA\News\Config\Config; -use OCA\News\Db\ItemMapper; use OCA\News\Db\MapperFactory; +use OCA\News\Db\ItemMapper; use OCA\News\Fetcher\FeedFetcher; use OCA\News\Fetcher\Fetcher; use OCA\News\Fetcher\YoutubeFetcher; use OCA\News\Utility\ProxyConfigParser; -use OCP\AppFramework\App; -use OCP\Files\IRootFolder; -use OCP\Files\Node; -use OCP\IConfig; -use OCP\IContainer; -use OCP\ILogger; -use PicoFeed\Config\Config as PicoFeedConfig; -use PicoFeed\Reader\Reader as PicoFeedReader; +/** + * Class Application + * + * @package OCA\News\AppInfo + */ class Application extends App { + /** + * Application constructor. + * + * @param array $urlParams Parameters + */ public function __construct(array $urlParams = []) { parent::__construct('news', $urlParams); @@ -57,6 +76,21 @@ class Application extends App return $c->query(MapperFactory::class)->build(); }); + + /** + * App config parser. + */ + $container->registerService(AppConfig::class, function (IContainer $c) { + $config = new AppConfig( + $c->query(INavigationManager::class), + $c->query(IURLGenerator::class), + $c->query(IJobList::class) + ); + + $config->loadConfig($c->query('info')); + return $config; + }); + /** * Core */ @@ -79,10 +113,21 @@ class Application extends App } }); + /** + * Logger base + */ + $container->registerService(PsrLogger::class, function (IContainer $c) { + return new PsrLogger( + $c->query('ServerContainer')->getLogger(), + $c->query('AppName') + ); + }); + + $container->registerService(Config::class, function (IContainer $c): Config { $config = new Config( $c->query('ConfigView'), - $c->query(ILogger::class), + $c->query(PsrLogger::class), $c->query('LoggerParameters') ); $config->read($c->query('configFile'), true); @@ -115,55 +160,33 @@ class Application extends App /** * Fetchers */ - $container->registerService(PicoFeedConfig::class, function (IContainer $c): PicoFeedConfig { + $container->registerService(FetcherConfig::class, function (IContainer $c) { // FIXME: move this into a separate class for testing? $config = $c->query(Config::class); - $proxy = $c->query(ProxyConfigParser::class); - - $userAgent = 'NextCloud-News/1.0'; - - $pico = new PicoFeedConfig(); - $pico->setClientUserAgent($userAgent) - ->setClientTimeout($config->getFeedFetcherTimeout()) - ->setMaxRedirections($config->getMaxRedirects()) - ->setMaxBodySize($config->getMaxSize()) - ->setParserHashAlgo('md5'); - - // proxy settings - $proxySettings = $proxy->parse(); - $host = $proxySettings['host']; - $port = $proxySettings['port']; - $user = $proxySettings['user']; - $password = $proxySettings['password']; - - if ($host) { - $pico->setProxyHostname($host); - - if ($port) { - $pico->setProxyPort($port); - } - } + $proxy = $c->query(ProxyConfigParser::class); - if ($user) { - $pico->setProxyUsername($user) - ->setProxyPassword($password); - } + $fConfig = new FetcherConfig(); + $fConfig->setClientTimeout($config->getFeedFetcherTimeout()); + $fConfig->setProxy($proxy); - return $pico; + return $fConfig; }); - $container->registerService(PicoFeedReader::class, function (IContainer $c): PicoFeedReader { - return new PicoFeedReader($c->query(PicoFeedConfig::class)); + $container->registerService(FeedIo::class, function (IContainer $c) { + $config = $c->query(FetcherConfig::class); + return new FeedIo($config->getClient(), $c->query(PsrLogger::class)); }); - $container->registerService(Fetcher::class, function (IContainer $c): Fetcher { + /** + * @noinspection PhpParamsInspection + */ + $container->registerService(Fetcher::class, function (IContainer $c) { $fetcher = new Fetcher(); // register fetchers in order, the most generic fetcher should be // the last one $fetcher->registerFetcher($c->query(YoutubeFetcher::class)); $fetcher->registerFetcher($c->query(FeedFetcher::class)); - return $fetcher; }); } diff --git a/lib/Config/Config.php b/lib/Config/Config.php index 7c5cee74a..dea1f5814 100644 --- a/lib/Config/Config.php +++ b/lib/Config/Config.php @@ -13,7 +13,7 @@ namespace OCA\News\Config; -use OCP\ILogger; +use OCA\News\Utility\PsrLogger; use OCP\Files\Folder; class Config @@ -35,7 +35,7 @@ class Config public function __construct( Folder $fileSystem, - ILogger $logger, + PsrLogger $logger, $LoggerParameters ) { $this->fileSystem = $fileSystem; diff --git a/lib/Config/FetcherConfig.php b/lib/Config/FetcherConfig.php new file mode 100644 index 000000000..55603c47c --- /dev/null +++ b/lib/Config/FetcherConfig.php @@ -0,0 +1,118 @@ +<?php +/** + * Nextcloud - News + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Alessandro Cosentino <cosenal@gmail.com> + * @author Bernhard Posselt <dev@bernhard-posselt.com> + * @copyright 2012 Alessandro Cosentino + * @copyright 2012-2014 Bernhard Posselt + */ + +namespace OCA\News\Config; + +use FeedIo\Adapter\ClientInterface; +use \GuzzleHttp\Client; +use \FeedIo\Adapter\Guzzle\Client as FeedIoClient; + +/** + * Class FetcherConfig + * + * @package OCA\News\Config + */ +class FetcherConfig +{ + protected $client_timeout; + protected $proxy; + + /** + * Configure a guzzle client + * + * @return ClientInterface Legacy client to guzzle. + */ + public function getClient() + { + if (!class_exists('GuzzleHttp\Collection')) { + $config = [ + 'timeout' => $this->getClientTimeout(), + ]; + + if (!empty($this->proxy)) { + $config['proxy'] = $this->proxy; + } + + $guzzle = new Client(); + $client = new FeedIoClient($guzzle); + + return $client; + } + + $config = [ + 'request.options' => [ + 'timeout' => $this->getClientTimeout(), + ], + ]; + + if (!empty($this->proxy)) { + $config['request.options']['proxy'] = $this->proxy; + } + + $guzzle = new Client($config); + return new LegacyGuzzleClient($guzzle); + } + + /** + * Set a timeout for the client + * + * @param int $timeout The timeout + * + * @return self + */ + public function setClientTimeout($timeout) + { + $this->client_timeout = $timeout; + + return $this; + } + + /** + * Get the client timeout. + * + * @return mixed + */ + public function getClientTimeout() + { + return $this->client_timeout; + } + + /** + * Set the proxy + * + * @param \OCA\News\Utility\ProxyConfigParser $proxy The proxy to set. + * + * @return self + */ + public function setProxy($proxy) + { + // proxy settings + $proxySettings = $proxy->parse(); + $host = $proxySettings['host']; + $port = $proxySettings['port']; + $user = $proxySettings['user']; + $password = $proxySettings['password']; + + $proxy_string = 'https://'; + if (!empty($user)) { + $proxy_string .= $user . ':' . $password . '@'; + } + $proxy_string .= $host; + if (!empty($port)) { + $proxy_string .= ':' . $port; + } + $this->proxy = $proxy_string; + + return $this; + } +} diff --git a/lib/Config/LegacyGuzzleClient.php b/lib/Config/LegacyGuzzleClient.php new file mode 100644 index 000000000..bc1364c30 --- /dev/null +++ b/lib/Config/LegacyGuzzleClient.php @@ -0,0 +1,65 @@ +<?php +/** + * Nextcloud - News + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Sean Molenaar <smillernl@me.com> + * @copyright 2018 Sean Molenaar + */ + +namespace OCA\News\Config; + +use FeedIo\Adapter\ClientInterface as FeedIoClientInterface; +use FeedIo\Adapter\NotFoundException; +use FeedIo\Adapter\ServerErrorException; +use Guzzle\Service\ClientInterface; +use GuzzleHttp\Exception\BadResponseException; + +/** + * Guzzle dependent HTTP client + */ +class LegacyGuzzleClient implements FeedIoClientInterface +{ + /** + * @var ClientInterface + */ + protected $guzzleClient; + + /** + * @param ClientInterface $guzzleClient + */ + public function __construct(ClientInterface $guzzleClient) + { + $this->guzzleClient = $guzzleClient; + } + + /** + * @param string $url + * @param \DateTime $modifiedSince + * @throws \FeedIo\Adapter\NotFoundException + * @throws \FeedIo\Adapter\ServerErrorException + * @return \FeedIo\Adapter\ResponseInterface + */ + public function getResponse($url, \DateTime $modifiedSince) + { + try { + $options = [ + 'headers' => [ + 'User-Agent' => 'NextCloud-News/1.0', + 'If-Modified-Since' => $modifiedSince->format(\DateTime::RFC2822) + ] + ]; + + return new LegacyGuzzleResponse($this->guzzleClient->get($url, $options)); + } catch (BadResponseException $e) { + switch ((int) $e->getResponse()->getStatusCode()) { + case 404: + throw new NotFoundException($e->getMessage()); + default: + throw new ServerErrorException($e->getMessage()); + } + } + } +} diff --git a/lib/Config/LegacyGuzzleResponse.php b/lib/Config/LegacyGuzzleResponse.php new file mode 100644 index 000000000..d9f6102ee --- /dev/null +++ b/lib/Config/LegacyGuzzleResponse.php @@ -0,0 +1,86 @@ +<?php +/** + * Nextcloud - News + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Sean Molenaar <smillernl@me.com> + * @copyright 2018 Sean Molenaar + */ + +namespace OCA\News\Config; + +use FeedIo\Adapter\ResponseInterface; +use GuzzleHttp\Message\ResponseInterface as GuzzleResponseInterface; + +/** + * Guzzle dependent HTTP Response + */ +class LegacyGuzzleResponse implements ResponseInterface +{ + const HTTP_LAST_MODIFIED = 'Last-Modified'; + + /** + * @var \GuzzleHttp\Message\ResponseInterface + */ + protected $response; + + /** + * @param \GuzzleHttp\Message\ResponseInterface + */ + public function __construct(GuzzleResponseInterface $psrResponse) + { + $this->response = $psrResponse; + } + + /** + * @return boolean + */ + public function isModified() + { + return $this->response->getStatusCode() !== 304 && $this->response->getBody()->getSize() > 0; + } + + /** + * @return \Psr\Http\Message\StreamInterface + */ + public function getBody() + { + return $this->response->getBody(); + } + + /** + * @return \DateTime|null + */ + public function getLastModified() + { + if ($this->response->hasHeader(static::HTTP_LAST_MODIFIED)) { + $lastModified = \DateTime::createFromFormat( + \DateTime::RFC2822, + $this->getHeader(static::HTTP_LAST_MODIFIED) + ); + + return false === $lastModified ? null : $lastModified; + } + + return; + } + + /** + * @return array + */ + public function getHeaders() + { + return $this->response->getHeaders(); + } + + /** + * @param string $name + * @return string[] + */ + public function getHeader($name) + { + return $this->response->getHeader($name); + } +} diff --git a/lib/Db/Item.php b/lib/Db/Item.php index 1a8d284a2..3a17dd2cb 100644 --- a/lib/Db/Item.php +++ b/lib/Db/Item.php @@ -491,4 +491,18 @@ class Item extends Entity implements IAPI, \JsonSerializable $this->getEnclosureLink() ); } + + /** + * Check if a given mimetype is supported + * + * @param string $mime mimetype to check + * + * @return boolean + */ + public function isSupportedMime($mime) + { + return ( + stripos($mime, 'audio/') !== false || + stripos($mime, 'video/') !== false); + } } diff --git a/lib/Fetcher/FeedFetcher.php b/lib/Fetcher/FeedFetcher.php index 65a4b5526..ae338ca09 100644 --- a/lib/Fetcher/FeedFetcher.php +++ b/lib/Fetcher/FeedFetcher.php @@ -13,29 +13,17 @@ namespace OCA\News\Fetcher; -use Exception; - -use OCA\News\PostProcessor\LWNProcessor; +use DateTime; +use Favicon\Favicon; +use FeedIo\Feed\ItemInterface; +use FeedIo\FeedInterface; +use FeedIo\FeedIo; use OCP\Http\Client\IClientService; -use PicoFeed\Parser\MalFormedXmlException; -use PicoFeed\Reader\Reader; -use PicoFeed\Parser\Parser; -use PicoFeed\Reader\SubscriptionNotFoundException; -use PicoFeed\Reader\UnsupportedFeedFormatException; -use PicoFeed\Client\InvalidCertificateException; -use PicoFeed\Client\InvalidUrlException; -use PicoFeed\Client\MaxRedirectException; -use PicoFeed\Client\MaxSizeException; -use PicoFeed\Client\TimeoutException; -use PicoFeed\Client\ForbiddenException; -use PicoFeed\Client\UnauthorizedException; use OCP\IL10N; use OCA\News\Db\Item; use OCA\News\Db\Feed; -use OCA\News\Utility\PicoFeedFaviconFactory; -use OCA\News\Utility\PicoFeedReaderFactory; use OCA\News\Utility\Time; class FeedFetcher implements IFeedFetcher @@ -48,22 +36,26 @@ class FeedFetcher implements IFeedFetcher private $clientService; public function __construct( - Reader $reader, - PicoFeedFaviconFactory $faviconFactory, + FeedIo $fetcher, + Favicon $favicon, IL10N $l10n, Time $time, IClientService $clientService ) { - $this->faviconFactory = $faviconFactory; - $this->reader = $reader; - $this->time = $time; - $this->l10n = $l10n; - $this->clientService = $clientService; + $this->faviconFactory = $favicon; + $this->reader = $fetcher; + $this->time = $time; + $this->l10n = $l10n; + $this->clientService = $clientService; } /** - * This fetcher handles all the remaining urls therefore always returns true + * This fetcher handles all the remaining urls therefore always returns true. + * + * @param string $url The URL to check + * + * @return bool */ public function canHandle($url) { @@ -74,176 +66,55 @@ class FeedFetcher implements IFeedFetcher /** * Fetch a feed from remote * - * @param string $url remote url of the feed - * @param boolean $getFavicon if the favicon should also be fetched, defaults to true - * @param string $lastModified a last modified value from an http header defaults to false. - * If lastModified matches the http header from the feed no results are fetched - * @param string $etag an etag from an http header. - * If lastModified matches the http header from the feed no results are fetched - * @param bool $fullTextEnabled if true tells the fetcher to enhance the articles by fetching more content - * @param string $basicAuthUser if given, basic auth is set for this feed - * @param string $basicAuthPassword if given, basic auth is set for this feed. Ignored if user is empty + * @param string $url Remote url of the feed + * @param boolean $getFavicon If the favicon should also be fetched, + * defaults to true + * @param string $lastModified A last modified value from an http header + * defaults to false. If lastModified matches + * the header from the feed no results are fetched + * @param string $user If given, basic auth is set for this feed + * @param string $password If given, basic auth is set for this feed. + * Ignored if user is null or an empty string. * - * @throws FetcherException if it fails * @return array an array containing the new feed and its items, first * element being the Feed and second element being an array of Items */ - public function fetch( - $url, - $getFavicon = true, - $lastModified = null, - $etag = null, - $fullTextEnabled = false, - $basicAuthUser = null, - $basicAuthPassword = null - ) { - try { - if ($basicAuthUser !== null && trim($basicAuthUser) !== '') { - $resource = $this->reader->discover( - $url, - $lastModified, - $etag, - $basicAuthUser, - $basicAuthPassword - ); - } else { - $resource = $this->reader->discover($url, $lastModified, $etag); - } - - if (!$resource->isModified()) { - return [null, null]; - } - - $location = $resource->getUrl(); - $etag = $resource->getEtag(); - $content = $resource->getContent(); - $encoding = $resource->getEncoding(); - $lastModified = $resource->getLastModified(); - - $parser = $this->reader->getParser($location, $content, $encoding); - - if ($fullTextEnabled) { - $parser->enableContentGrabber(); - $parser->getItemPostProcessor()->register( - new LWNProcessor( - $basicAuthUser, - $basicAuthPassword, - $this->clientService - ) - ); - } - - $parsedFeed = $parser->execute(); - - $feed = $this->buildFeed( - $parsedFeed, - $url, - $getFavicon, - $lastModified, - $etag, - $location - ); - - $items = []; - foreach ($parsedFeed->getItems() as $item) { - $items[] = $this->buildItem($item, $parsedFeed); - } - - return [$feed, $items]; - } catch (Exception $ex) { - $this->handleError($ex, $url); - } - } - - - private function handleError(Exception $ex, $url) + public function fetch($url, $getFavicon = true, $lastModified = null, $user = null, $password = null) { - $msg = $ex->getMessage(); + if ($user !== null && trim($user) !== '') { + $url = explode('://', $url); + $url = $url[0] . '://' . $user . ':' . $password . '@' . $url[1]; + } + $resource = $this->reader->readSince($url, new DateTime($lastModified)); - if ($ex instanceof MalFormedXmlException) { - $msg = $this->l10n->t('Feed contains invalid XML'); - } elseif ($ex instanceof SubscriptionNotFoundException) { - $msg = $this->l10n->t( - 'Feed not found: Either the website ' . - 'does not provide a feed or blocks access. To rule out ' . - 'blocking, try to download the feed on your server\'s ' . - 'command line using curl: curl ' . $url - ); - } elseif ($ex instanceof UnsupportedFeedFormatException) { - $msg = $this->l10n->t('Detected feed format is not supported'); - } elseif ($ex instanceof InvalidCertificateException) { - $msg = $this->buildCurlSslErrorMessage($ex->getCode()); - } elseif ($ex instanceof InvalidUrlException) { - $msg = $this->l10n->t('Website not found'); - } elseif ($ex instanceof MaxRedirectException) { - $msg = $this->l10n->t('More redirects than allowed, aborting'); - } elseif ($ex instanceof MaxSizeException) { - $msg = $this->l10n->t('Bigger than maximum allowed size'); - } elseif ($ex instanceof TimeoutException) { - $msg = $this->l10n->t('Request timed out'); - } elseif ($ex instanceof UnauthorizedException) { - $msg = $this->l10n->t( - 'Required credentials for feed were ' . - 'either missing or incorrect' - ); - } elseif ($ex instanceof ForbiddenException) { - $msg = $this->l10n->t('Forbidden to access feed'); + if (!$resource->getResponse()->isModified()) { + return [null, null]; } - throw new FetcherException($msg); - } + $location = $resource->getUrl(); + $parsedFeed = $resource->getFeed(); + $feed = $this->buildFeed( + $parsedFeed, + $url, + $getFavicon, + $location + ); - private function buildCurlSslErrorMessage($errorCode) - { - switch ($errorCode) { - case 35: // CURLE_SSL_CONNECT_ERROR - return $this->l10n->t( - 'Certificate error: A problem occurred ' . - 'somewhere in the SSL/TLS handshake. Could be ' . - 'certificates (file formats, paths, permissions), ' . - 'passwords, and others.' - ); - case 51: // CURLE_PEER_FAILED_VERIFICATION - return $this->l10n->t( - 'Certificate error: The remote server\'s SSL ' . - 'certificate or SSH md5 fingerprint was deemed not OK.' - ); - case 58: // CURLE_SSL_CERTPROBLEM - return $this->l10n->t( - 'Certificate error: Problem with the local client ' . - 'certificate.' - ); - case 59: // CURLE_SSL_CIPHER - return $this->l10n->t( - 'Certificate error: Couldn\'t use specified cipher.' - ); - case 60: // CURLE_SSL_CACERT - return $this->l10n->t( - 'Certificate error: Peer certificate cannot be ' . - 'authenticated with known CA certificates.' - ); - case 64: // CURLE_USE_SSL_FAILED - return $this->l10n->t( - 'Certificate error: Requested FTP SSL level failed.' - ); - case 66: // CURLE_SSL_ENGINE_INITFAILED - return $this->l10n->t( - 'Certificate error: Initiating the SSL engine failed.' - ); - case 77: // CURLE_SSL_CACERT_BADFILE - return $this->l10n->t( - 'Certificate error: Problem with reading the SSL CA ' . - 'cert (path? access rights?)' - ); - case 83: // CURLE_SSL_ISSUER_ERROR - return $this->l10n->t( - 'Certificate error: Issuer check failed' - ); - default: - return $this->l10n->t('Unknown SSL certificate error!'); + $items = []; + foreach ($parsedFeed as $item) { + $items[] = $this->buildItem($item, $parsedFeed); } + + return [$feed, $items]; } + /** + * Decode the string twice + * + * @param string $string String to decode + * + * @return string + */ private function decodeTwice($string) { return html_entity_decode( @@ -257,37 +128,73 @@ class FeedFetcher implements IFeedFetcher ); } - - protected function determineRtl($parsedItem, $parsedFeed) + /** + * Check if a feed is RTL or not + * + * @param FeedInterface $parsedFeed The feed that was parsed + * + * @return bool + */ + protected function determineRtl($parsedFeed) { - $itemLang = $parsedItem->getLanguage(); - $feedLang = $parsedFeed->getLanguage(); - - if ($itemLang) { - return Parser::isLanguageRTL($itemLang); - } else { - return Parser::isLanguageRTL($feedLang); + $language = $parsedFeed->getLanguage(); + + $language = strtolower($language); + $rtl_languages = array( + 'ar', // Arabic (ar-**) + 'fa', // Farsi (fa-**) + 'ur', // Urdu (ur-**) + 'ps', // Pashtu (ps-**) + 'syr', // Syriac (syr-**) + 'dv', // Divehi (dv-**) + 'he', // Hebrew (he-**) + 'yi', // Yiddish (yi-**) + ); + foreach ($rtl_languages as $prefix) { + if (strpos($language, $prefix) === 0) { + return true; + } } + return false; } - + /** + * Build an item based on a feed. + * + * @param ItemInterface $parsedItem The item to use + * @param FeedInterface $parsedFeed The feed to use + * + * @return Item + */ protected function buildItem($parsedItem, $parsedFeed) { $item = new Item(); $item->setUnread(true); - $item->setUrl($parsedItem->getUrl()); - $item->setGuid($parsedItem->getId()); + $item->setUrl($parsedItem->getLink()); + $item->setGuid($parsedItem->getPublicId()); $item->setGuidHash($item->getGuid()); - $item->setPubDate($parsedItem->getPublishedDate()->getTimestamp()); - $item->setUpdatedDate($parsedItem->getUpdatedDate()->getTimestamp()); - $item->setRtl($this->determineRtl($parsedItem, $parsedFeed)); + + $pubDT = $parsedItem->getLastModified(); + if ($parsedItem->getValue('pubDate') !== null) { + $pubDT = new DateTime($parsedItem->getValue('pubDate')); + } elseif ($parsedItem->getValue('published') !== null) { + $pubDT = new DateTime($parsedItem->getValue('published')); + } + + $item->setPubDate( + $pubDT->getTimestamp() + ); + $item->setLastModified( + $parsedItem->getLastModified()->getTimestamp() + ); + $item->setRtl($this->determineRtl($parsedFeed)); // unescape content because angularjs helps against XSS $item->setTitle($this->decodeTwice($parsedItem->getTitle())); $item->setAuthor($this->decodeTwice($parsedItem->getAuthor())); // purification is done in the service layer - $body = $parsedItem->getContent(); + $body = $parsedItem->getDescription(); $body = mb_convert_encoding( $body, 'HTML-ENTITIES', @@ -295,14 +202,14 @@ class FeedFetcher implements IFeedFetcher ); $item->setBody($body); - $enclosureUrl = $parsedItem->getEnclosureUrl(); - if ($enclosureUrl) { - $enclosureType = $parsedItem->getEnclosureType(); - if (stripos($enclosureType, 'audio/') !== false - || stripos($enclosureType, 'video/') !== false - ) { - $item->setEnclosureMime($enclosureType); - $item->setEnclosureLink($enclosureUrl); + if ($parsedItem->hasMedia()) { + // TODO: Fix multiple media support + foreach ($parsedItem->getMedias() as $media) { + if (!$item->isSupportedMime($media->getType())) { + continue; + } + $item->setEnclosureMime($media->getType()); + $item->setEnclosureLink($media->getUrl()); } } @@ -311,39 +218,35 @@ class FeedFetcher implements IFeedFetcher return $item; } - - protected function buildFeed( - $parsedFeed, - $url, - $getFavicon, - $modified, - $etag, - $location - ) { - $feed = new Feed(); - - $link = $parsedFeed->getSiteUrl(); - - if (!$link) { - $link = $location; - } + /** + * Build a feed based on provided info + * + * @param FeedInterface $feed Feed to build from + * @param string $url URL to use + * @param bool $getFavicon To get the favicon + * @param string $location String base URL + * + * @return Feed + */ + protected function buildFeed($feed, $url, $getFavicon, $location) + { + $newFeed = new Feed(); // unescape content because angularjs helps against XSS - $title = strip_tags($this->decodeTwice($parsedFeed->getTitle())); - $feed->setTitle($title); - $feed->setUrl($url); // the url used to add the feed - $feed->setLocation($location); // the url where the feed was found - $feed->setLink($link); // <link> attribute in the feed - $feed->setHttpLastModified($modified); - $feed->setHttpEtag($etag); - $feed->setAdded($this->time->getTime()); - - if ($getFavicon) { - $faviconFetcher = $this->faviconFactory->build(); - $favicon = $faviconFetcher->find($feed->getLink()); - $feed->setFaviconLink($favicon); + $title = strip_tags($this->decodeTwice($feed->getTitle())); + $newFeed->setTitle($title); + $newFeed->setUrl($url); // the url used to add the feed + $newFeed->setLocation($location); // the url where the feed was found + $newFeed->setLink($feed->getLink()); // <link> attribute in the feed + $newFeed->setLastModified($feed->getLastModified()->getTimestamp()); + $newFeed->setAdded($this->time->getTime()); + + if (!$getFavicon) { + return $newFeed; } + $favicon = $this->faviconFactory->get($url); + $newFeed->setFaviconLink($favicon); - return $feed; + return $newFeed; } } diff --git a/lib/Fetcher/Fetcher.php b/lib/Fetcher/Fetcher.php index e78da0265..23f5b57f7 100644 --- a/lib/Fetcher/Fetcher.php +++ b/lib/Fetcher/Fetcher.php @@ -16,6 +16,10 @@ namespace OCA\News\Fetcher; class Fetcher { + /** + * List of fetchers. + * @var IFeedFetcher[] + */ private $fetchers; public function __construct() @@ -39,39 +43,28 @@ class Fetcher * * @param string $url remote url of the feed * @param boolean $getFavicon if the favicon should also be fetched, defaults to true - * @param string $lastModified a last modified value from an http header defaults to false. + * @param string $lastModified a last modified value from an http header defaults to false. * If lastModified matches the http header from the feed no results are fetched - * @param string $etag an etag from an http header. - * If lastModified matches the http header from the feed no results are fetched - * @param bool $fullTextEnabled if true tells the fetcher to enhance the articles by fetching more content - * @param string $basicAuthUser if given, basic auth is set for this feed - * @param string $basicAuthPassword if given, basic auth is set for this feed. Ignored if user is empty + * @param string $user if given, basic auth is set for this feed + * @param string $password if given, basic auth is set for this feed. Ignored if user is empty * - * @throws FetcherException if simple pie fails + * @throws FetcherException if FeedIO fails * @return array an array containing the new feed and its items, first * element being the Feed and second element being an array of Items */ - public function fetch( - $url, - $getFavicon = true, - $lastModified = null, - $etag = null, - $fullTextEnabled = false, - $basicAuthUser = null, - $basicAuthPassword = null - ) { + public function fetch($url, $getFavicon = true, $lastModified = null, $user = null, $password = null) + { foreach ($this->fetchers as $fetcher) { - if ($fetcher->canHandle($url)) { - return $fetcher->fetch( - $url, - $getFavicon, - $lastModified, - $etag, - $fullTextEnabled, - $basicAuthUser, - $basicAuthPassword - ); + if (!$fetcher->canHandle($url)) { + continue; } + return $fetcher->fetch( + $url, + $getFavicon, + $lastModified, + $user, + $password + ); } return [null, []]; diff --git a/lib/Fetcher/IFeedFetcher.php b/lib/Fetcher/IFeedFetcher.php index c96bd315b..d5994a076 100644 --- a/lib/Fetcher/IFeedFetcher.php +++ b/lib/Fetcher/IFeedFetcher.php @@ -23,25 +23,14 @@ interface IFeedFetcher * @param boolean $getFavicon if the favicon should also be fetched, defaults to true * @param string $lastModified a last modified value from an http header defaults to false. * If lastModified matches the http header from the feed no results are fetched - * @param string $etag an etag from an http header. - * If lastModified matches the http header from the feed no results are fetched - * @param bool $fullTextEnabled if true tells the fetcher to enhance the articles by fetching more content - * @param string $basicAuthUser if given, basic auth is set for this feed - * @param string $basicAuthPassword if given, basic auth is set for this feed. Ignored if user is empty + * @param string $user if given, basic auth is set for this feed + * @param string $password if given, basic auth is set for this feed. Ignored if user is empty * * @throws FetcherException if the fetcher encounters a problem * @return array an array containing the new feed and its items, first * element being the Feed and second element being an array of Items */ - public function fetch( - $url, - $getFavicon = true, - $lastModified = null, - $etag = null, - $fullTextEnabled = false, - $basicAuthUser = null, - $basicAuthPassword = null - ); + public function fetch($url, $getFavicon = true, $lastModified = null, $user = null, $password = null); /** * Can a fetcher handle a feed. diff --git a/lib/Fetcher/YoutubeFetcher.php b/lib/Fetcher/YoutubeFetcher.php index a47b8fdb8..9ccce4463 100644 --- a/lib/Fetcher/YoutubeFetcher.php +++ b/lib/Fetcher/YoutubeFetcher.php @@ -52,35 +52,24 @@ class YoutubeFetcher implements IFeedFetcher * @param boolean $getFavicon if the favicon should also be fetched, defaults to true * @param string $lastModified a last modified value from an http header defaults to false. * If lastModified matches the http header from the feed no results are fetched - * @param string $etag an etag from an http header. - * If lastModified matches the http header from the feed no results are fetched - * @param bool $fullTextEnabled if true tells the fetcher to enhance the articles by fetching more content - * @param string $basicAuthUser if given, basic auth is set for this feed - * @param string $basicAuthPassword if given, basic auth is set for this feed. Ignored if user is empty + * @param string $user if given, basic auth is set for this feed + * @param string $password if given, basic auth is set for this feed. Ignored if user is empty * * @throws FetcherException if it fails * @return array an array containing the new feed and its items, first * element being the Feed and second element being an array of Items */ - public function fetch( - $url, - $getFavicon = true, - $lastModified = null, - $etag = null, - $fullTextEnabled = false, - $basicAuthUser = null, - $basicAuthPassword = null - ) { + public function fetch($url, $getFavicon = true, $lastModified = null, $user = null, $password = null + ) + { $transformedUrl = $this->buildUrl($url); $result = $this->feedFetcher->fetch( $transformedUrl, $getFavicon, $lastModified, - $etag, - $fullTextEnabled, - $basicAuthUser, - $basicAuthPassword + $user, + $password ); // reset feed url so we know the correct added url for the feed diff --git a/lib/PostProcessor/LWNProcessor.php b/lib/PostProcessor/LWNProcessor.php deleted file mode 100644 index 1028df100..000000000 --- a/lib/PostProcessor/LWNProcessor.php +++ /dev/null @@ -1,117 +0,0 @@ -<?php -/** - * Nextcloud - News - * - * This file is licensed under the Affero General Public License version 3 or - * later. See the COPYING file. - * - * @author Robin Appelman <robin@icewind.nl> - */ - -namespace OCA\News\PostProcessor; - -use GuzzleHttp\Cookie\CookieJar; -use OCP\Http\Client\IClientService; -use PicoFeed\Parser\Feed; -use PicoFeed\Parser\Item; -use PicoFeed\Processor\ItemProcessorInterface; -use PicoFeed\Scraper\RuleParser; - -class LWNProcessor implements ItemProcessorInterface -{ - private $user; - - private $password; - - private $clientService; - - private $cookieJar; - - /** - * @param $user - * @param $password - */ - public function __construct($user, $password, IClientService $clientService) - { - $this->user = $user; - $this->password = $password; - $this->clientService = $clientService; - $this->cookieJar = new CookieJar(); - } - - private function login() - { - if ($this->cookieJar->count() > 0) { - return true; - } - if (!$this->user || !$this->password) { - return false; - } - - $client = $this->clientService->newClient(); - $response = $client->post( - 'https://lwn.net/login', - [ - 'cookies' => $this->cookieJar, - 'body' => [ - 'Username' => $this->user, - 'Password' => $this->password, - 'target' => '/' - ] - ] - ); - return ($response->getStatusCode() === 200 && $this->cookieJar->count() > 0); - } - - private function getBody($url) - { - $client = $this->clientService->newClient(); - $response = $client->get( - $url, - [ - 'cookies' => $this->cookieJar - ] - ); - $parser = new RuleParser( - $response->getBody(), - [ - 'body' => array( - '//div[@class="ArticleText"]', - ), - 'strip' => array( - '//div[@class="FeatureByline"]' - ) - ] - ); - $articleBody = $parser->execute(); - // make all links absolute - return str_replace('href="/', 'href="https://lwn.net/', $articleBody); - } - - private function canHandle($url) - { - $regex = '%(?:https?://|//)?(?:www.)?lwn.net%'; - - return (bool)preg_match($regex, $url); - } - - /** - * Execute Item Processor - * - * @access public - * @param Feed $feed - * @param Item $item - * @return bool - */ - public function execute(Feed $feed, Item $item) - { - if ($this->canHandle($item->getUrl())) { - $loggedIn = $this->login(); - - $item->setUrl(str_replace('/rss', '', $item->getUrl())); - if ($loggedIn) { - $item->setContent($this->getBody($item->getUrl())); - } - } - } -} diff --git a/lib/Service/FeedService.php b/lib/Service/FeedService.php index 2ccbb014b..fade77df8 100644 --- a/lib/Service/FeedService.php +++ b/lib/Service/FeedService.php @@ -58,8 +58,8 @@ class FeedService extends Service $this->logger = $logger; $this->l10n = $l10n; $this->timeFactory = $timeFactory; - $this->autoPurgeMinimumInterval = - $config->getAutoPurgeMinimumInterval(); + $this->autoPurgeMinimumInterval = $config->getAutoPurgeMinimumInterval( + ); $this->purifier = $purifier; $this->feedMapper = $feedMapper; $this->loggerParams = $LoggerParameters; @@ -69,6 +69,7 @@ class FeedService extends Service * Finds all feeds of a user * * @param string $userId the name of the user + * * @return Feed[] */ public function findAll($userId) @@ -96,9 +97,10 @@ class FeedService extends Service * folder * @param string $userId for which user the feed should be created * @param string $title if given, this is used for the opml feed title - * @param string $basicAuthUser if given, basic auth is set for this feed - * @param string $basicAuthPassword if given, basic auth is set for this + * @param string $user if given, basic auth is set for this feed + * @param string $password if given, basic auth is set for this * feed. Ignored if user is null or an empty string + * * @throws ServiceConflictException if the feed exists already * @throws ServiceNotFoundException if the url points to an invalid feed * @return Feed the newly created feed @@ -108,23 +110,21 @@ class FeedService extends Service $folderId, $userId, $title = null, - $basicAuthUser = null, - $basicAuthPassword = null + $user = null, + $password = null ) { // first try if the feed exists already try { /** - * @var Feed $feed + * @var Feed $feed * @var Item[] $items */ list($feed, $items) = $this->feedFetcher->fetch( $feedUrl, true, null, - null, - false, - $basicAuthUser, - $basicAuthPassword + $user, + $password ); // try again if feed exists depending on the reported link @@ -140,8 +140,8 @@ class FeedService extends Service // insert feed $itemCount = count($items); - $feed->setBasicAuthUser($basicAuthUser); - $feed->setBasicAuthPassword($basicAuthPassword); + $feed->setBasicAuthUser($user); + $feed->setBasicAuthPassword($password); $feed->setFolderId($folderId); $feed->setUserId($userId); $feed->setArticlesPerUpdate($itemCount); @@ -213,6 +213,7 @@ class FeedService extends Service * @param int $feedId the id of the feed that should be updated * @param string $userId the id of the user * @param bool $forceUpdate update even if the article exists already + * * @throws ServiceNotFoundException if the feed does not exist * @return Feed the updated feed entity */ @@ -237,8 +238,6 @@ class FeedService extends Service $location, false, $existingFeed->getHttpLastModified(), - $existingFeed->getHttpEtag(), - $existingFeed->getFullTextEnabled(), $existingFeed->getBasicAuthUser(), $existingFeed->getBasicAuthPassword() ); @@ -332,6 +331,7 @@ class FeedService extends Service * * @param array $json the array with json * @param string $userId the username + * * @return Feed if one had to be created for nonexistent feeds */ public function importArticles($json, $userId) @@ -406,6 +406,7 @@ class FeedService extends Service * * @param int $feedId the id of the feed that should be deleted * @param string $userId the name of the user for security reasons + * * @throws ServiceNotFoundException when feed does not exist */ public function markDeleted($feedId, $userId) @@ -421,6 +422,7 @@ class FeedService extends Service * * @param int $feedId the id of the feed that should be restored * @param string $userId the name of the user for security reasons + * * @throws ServiceNotFoundException when feed does not exist */ public function unmarkDeleted($feedId, $userId) @@ -471,13 +473,14 @@ class FeedService extends Service * @param $feedId * @param $userId * @param $diff an array containing the fields to update, e.g.: - * [ - * 'ordering' => 1, - * 'fullTextEnabled' => true, - * 'pinned' => true, - * 'updateMode' => 0, - * 'title' => 'title' - * ] + * [ + * 'ordering' => 1, + * 'fullTextEnabled' => true, + * 'pinned' => true, + * 'updateMode' => 0, + * 'title' => 'title' + * ] + * * @throws ServiceNotFoundException if feed does not exist */ public function patch($feedId, $userId, $diff = []) diff --git a/lib/Utility/PicoFeedClientFactory.php b/lib/Utility/PicoFeedClientFactory.php deleted file mode 100644 index 046224919..000000000 --- a/lib/Utility/PicoFeedClientFactory.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php -/** - * Nextcloud - News - * - * This file is licensed under the Affero General Public License version 3 or - * later. See the COPYING file. - * - * @author Alessandro Cosentino <cosenal@gmail.com> - * @author Bernhard Posselt <dev@bernhard-posselt.com> - * @copyright 2012 Alessandro Cosentino - * @copyright 2012-2014 Bernhard Posselt - */ - - -namespace OCA\News\Utility; - -use \PicoFeed\Config\Config; -use \PicoFeed\Client\Client; - -class PicoFeedClientFactory -{ - - private $config; - - public function __construct(Config $config) - { - $this->config = $config; - } - - - /** - * Returns a new instance of an PicoFeed Http client - * - * @return \PicoFeed\Client instance - */ - public function build() - { - $client = Client::getInstance(); - $client->setConfig($this->config); - return $client; - } -} diff --git a/lib/Utility/PicoFeedFaviconFactory.php b/lib/Utility/PicoFeedFaviconFactory.php deleted file mode 100644 index 09a1b76c8..000000000 --- a/lib/Utility/PicoFeedFaviconFactory.php +++ /dev/null @@ -1,40 +0,0 @@ -<?php -/** - * Nextcloud - News - * - * This file is licensed under the Affero General Public License version 3 or - * later. See the COPYING file. - * - * @author Alessandro Cosentino <cosenal@gmail.com> - * @author Bernhard Posselt <dev@bernhard-posselt.com> - * @copyright 2012 Alessandro Cosentino - * @copyright 2012-2014 Bernhard Posselt - */ - - -namespace OCA\News\Utility; - -use \PicoFeed\Config\Config; -use \PicoFeed\Reader\Favicon; - -class PicoFeedFaviconFactory -{ - - private $config; - - public function __construct(Config $config) - { - $this->config = $config; - } - - - /** - * Returns a new instance of an PicoFeed Http client - * - * @return \PicoFeed\Favicon instance - */ - public function build() - { - return new Favicon($this->config); - } -} diff --git a/lib/Utility/PsrLogger.php b/lib/Utility/PsrLogger.php new file mode 100644 index 000000000..5d9a2529b --- /dev/null +++ b/lib/Utility/PsrLogger.php @@ -0,0 +1,97 @@ +<?php +/** + * Nextcloud - News + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Sean Molenaar <sean@seanmolenaar.eu> + * @copyright 2018 Sean Molenaar + */ + +namespace OCA\News\Utility; + +use \OCP\ILogger; + +/** + * This is a wrapper to make OC\Log conform to Psr\Log\LoggerInterface + * + * @package OCA\News\Utility + */ +class PsrLogger implements \Psr\Log\LoggerInterface +{ + private $logger; + private $appName; + + /** + * PsrLogger constructor. + * + * @param ILogger $logger The logger + * @param string $appName Name of the app + */ + public function __construct(ILogger $logger, $appName) + { + $this->logger = $logger; + $this->appName = $appName; + } + + public function logException($exception, array $context = []) + { + $context['app'] = $this->appName; + $this->logger->logException($exception, $context); + } + + public function emergency($message, array $context = []) + { + $context['app'] = $this->appName; + $this->logger->emergency($message, $context); + } + + public function alert($message, array $context = []) + { + $context['app'] = $this->appName; + $this->logger->alert($message, $context); + } + + public function critical($message, array $context = []) + { + $context['app'] = $this->appName; + $this->logger->critical($message, $context); + } + + public function error($message, array $context = []) + { + $context['app'] = $this->appName; + $this->logger->error($message, $context); + } + + public function warning($message, array $context = []) + { + $context['app'] = $this->appName; + $this->logger->warning($message, $context); + } + + public function notice($message, array $context = []) + { + $context['app'] = $this->appName; + $this->logger->notice($message, $context); + } + + public function info($message, array $context = []) + { + $context['app'] = $this->appName; + $this->logger->info($message, $context); + } + + public function debug($message, array $context = []) + { + $context['app'] = $this->appName; + $this->logger->debug($message, $context); + } + + public function log($level, $message, array $context = []) + { + $context['app'] = $this->appName; + $this->logger->log($level, $message, $context); + } +} diff --git a/tests/Unit/Config/ConfigTest.php b/tests/Unit/Config/ConfigTest.php index 992453cca..1d1bd0ed6 100644 --- a/tests/Unit/Config/ConfigTest.php +++ b/tests/Unit/Config/ConfigTest.php @@ -29,7 +29,7 @@ class ConfigTest extends TestCase public function setUp() { - $this->logger = $this->getMockBuilder(ILogger::class) + $this->logger = $this->getMockBuilder('OCA\News\Utility\PsrLogger') ->disableOriginalConstructor() ->getMock(); $this->fileSystem = $this->getMockBuilder(Folder::class)->getMock(); diff --git a/tests/Unit/Fetcher/FeedFetcherTest.php b/tests/Unit/Fetcher/FeedFetcherTest.php index 36eaefa1e..f6ef47602 100644 --- a/tests/Unit/Fetcher/FeedFetcherTest.php +++ b/tests/Unit/Fetcher/FeedFetcherTest.php @@ -13,13 +13,15 @@ namespace OCA\News\Tests\Unit\Fetcher; -use \OCA\News\Db\Item; +use FeedIo\Feed\Item\MediaInterface; use \OCA\News\Db\Feed; +use \OCA\News\Db\Item; use OCA\News\Fetcher\FeedFetcher; use OCA\News\Utility\PicoFeedFaviconFactory; use OCA\News\Utility\Time; use OCP\Http\Client\IClientService; use OCP\IL10N; + use PHPUnit\Framework\TestCase; use PicoFeed\Client\Client; use PicoFeed\Parser\Parser; @@ -27,403 +29,453 @@ use PicoFeed\Processor\ItemPostProcessor; use PicoFeed\Reader\Favicon; use PicoFeed\Reader\Reader; - +/** + * Class FeedFetcherTest + * + * @package OCA\News\Tests\Unit\Fetcher + */ class FeedFetcherTest extends TestCase { - - private $fetcher; - private $parser; - private $reader; - private $client; - private $faviconFetcher; - private $parsedFeed; - private $faviconFactory; - private $l10n; - private $url; - private $time; - private $item; - private $content; - private $encoding; + /** + * The class to test + * + * @var FeedFetcher + */ + private $_fetcher; + + /** + * Feed reader + * + * @var \FeedIo\FeedIo + */ + private $_reader; + + /** + * Feed reader result + * + * @var \FeedIo\Reader\Result + */ + private $_result; + + /** + * Feed reader result object + * + * @var \FeedIo\Adapter\ResponseInterface + */ + private $_response; + + private $_favicon; + private $_l10n; + private $_url; + private $_time; + private $_item_mock; + private $_feed_mock; + private $_encoding; // items - private $permalink; - private $title; - private $guid; - private $pub; - private $updated; - private $body; - private $author; - private $enclosureLink; - private $rtl; - private $language; + private $_permalink; + private $_title; + private $_guid; + private $_pub; + private $_updated; + private $_body; + private $_author; + private $_enclosure; + private $_rtl; + private $_language; // feed - private $feedTitle; - private $feedLink; - private $feedImage; - private $webFavicon; - private $modified; - private $etag; - private $location; - private $feedLanguage; + private $_feed_title; + private $_feed_link; + private $_feed_image; + private $_web_favicon; + private $_modified; + private $_location; protected function setUp() { - $this->l10n = $this->getMockBuilder(IL10N::class) + $this->_l10n = $this->getMockBuilder(\OCP\IL10N::class) ->disableOriginalConstructor() ->getMock(); - $this->reader = $this->getMockBuilder(Reader::class) + $this->_reader = $this->getMockBuilder(\FeedIo\FeedIo::class) ->disableOriginalConstructor() ->getMock(); - $this->parser = $this->getMockBuilder(Parser::class) + $this->_favicon = $this->getMockBuilder(\Favicon\Favicon::class) ->disableOriginalConstructor() ->getMock(); - $this->client = $this->getMockBuilder(Client::class) + $this->_result = $this->getMockBuilder(\FeedIo\Reader\Result::class) ->disableOriginalConstructor() ->getMock(); - $this->parsedFeed = $this->getMockBuilder(\PicoFeed\Parser\Feed::class) + $this->_response = $this->getMockBuilder(\FeedIo\Adapter\ResponseInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->item = $this->getMockBuilder(\PicoFeed\Parser\Item::class) - ->disableOriginalConstructor() - ->getMock(); - $this->faviconFetcher = $this->getMockBuilder(Favicon::class) + + $this->_item_mock = $this->getMockBuilder(\FeedIo\Feed\ItemInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->faviconFactory = $this->getMockBuilder(PicoFeedFaviconFactory::class) + + $this->_feed_mock = $this->getMockBuilder(\FeedIo\FeedInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->time = 2323; - $timeFactory = $this->getMockBuilder(Time::class) + $this->_time = 2323; + $timeFactory = $this->getMockBuilder(\OCA\News\Utility\Time::class) ->disableOriginalConstructor() ->getMock(); $timeFactory->expects($this->any()) ->method('getTime') - ->will($this->returnValue($this->time)); - $postProcessor = $this->getMockBuilder(ItemPostProcessor::class) + ->will($this->returnValue($this->_time)); + $clientService = $this->getMockBuilder(IClientService::class) ->getMock(); - $this->parser->expects($this->any()) - ->method('getItemPostProcessor') - ->will($this->returnValue($postProcessor)); - $clientService = $this->getMockBuilder(IClientService::class) - ->getMock(); - $this->fetcher = new FeedFetcher( - $this->reader, - $this->faviconFactory, - $this->l10n, + $this->_fetcher = new FeedFetcher( + $this->_reader, + $this->_favicon, + $this->_l10n, $timeFactory, $clientService ); - $this->url = 'http://tests'; - - $this->permalink = 'http://permalink'; - $this->title = 'my&lt;' title'; - $this->guid = 'hey guid here'; - $this->body = 'let the bodies hit the floor <a href="test">test</a>'; - $this->body2 = 'let the bodies hit the floor ' . - '<a target="_blank" href="test">test</a>'; - $this->pub = 23111; - $this->updated = 23444; - $this->author = '<boogieman'; - $this->enclosureLink = 'http://enclosure.you'; - - $this->feedTitle = '<a>&its a</a> title'; - $this->feedLink = 'http://goatse'; - $this->feedImage = '/an/image'; - $this->webFavicon = 'http://anon.google.com'; - $this->authorMail = 'doe@joes.com'; - $this->modified = 3; - $this->etag = 'yo'; - $this->content = 'some content'; - $this->encoding = 'UTF-8'; - $this->language = 'de-DE'; - $this->feedLanguage = 'de-DE'; + $this->_url = 'http://tests'; + + $this->_permalink = 'http://permalink'; + $this->_title = 'my&lt;' title'; + $this->_guid = 'hey guid here'; + $this->_body = 'let the bodies hit the floor <a href="test">test</a>'; + $this->_pub = 23111; + $this->_updated = 23444; + $this->_author = '<boogieman'; + $this->_enclosure = 'http://enclosure.you'; + + $this->_feed_title = '<a>&its a</a> title'; + $this->_feed_link = 'http://tests'; + $this->_feed_image = '/an/image'; + $this->_web_favicon = 'http://anon.google.com'; + $this->_modified = $this->getMockBuilder('\DateTime')->getMock(); + $this->_modified->expects($this->any()) + ->method('getTimestamp') + ->will($this->returnValue(3)); + $this->_encoding = 'UTF-8'; + $this->_language = 'de-DE'; + $this->_rtl = false; } - public function testCanHandle() { $url = 'google.de'; - $this->assertTrue($this->fetcher->canHandle($url)); + $this->assertTrue($this->_fetcher->canHandle($url)); } - private function setUpReader($url='', $modified=true, $noParser=false) + public function testNoFetchIfNotModified() { - $this->reader->expects($this->once()) - ->method('discover') - ->with($this->equalTo($url)) - ->will($this->returnValue($this->client)); - $this->client->expects($this->once()) - ->method('isModified') - ->will($this->returnValue($modified)); + $this->_setUpReader($this->_url, false);; + $result = $this->_fetcher->fetch($this->_url, false); + $this->assertSame([null, null], $result); + } - if (!$modified) { - $this->reader->expects($this->never()) - ->method('getParser'); - } else { - $this->client->expects($this->once()) - ->method('getLastModified') - ->will($this->returnValue($this->modified)); - $this->client->expects($this->once()) - ->method('getEtag') - ->will($this->returnValue($this->etag)); - $this->client->expects($this->once()) - ->method('getUrl') - ->will($this->returnValue($this->location)); - $this->client->expects($this->once()) - ->method('getContent') - ->will($this->returnValue($this->content)); - $this->client->expects($this->once()) - ->method('getEncoding') - ->will($this->returnValue($this->encoding)); - - if ($noParser) { - $this->reader->expects($this->once()) - ->method('getParser') - ->will( - $this->throwException( - new \PicoFeed\Reader\SubscriptionNotFoundException() - ) - ); - } else { - $this->reader->expects($this->once()) - ->method('getParser') - ->with( - $this->equalTo($this->location), - $this->equalTo($this->content), - $this->equalTo($this->encoding) - ) - ->will($this->returnValue($this->parser)); - } - - $this->parser->expects($this->once()) - ->method('execute') - ->will($this->returnValue($this->parsedFeed)); - } + public function testFetch() + { + $this->_setUpReader($this->_url); + $item = $this->_createItem(); + $feed = $this->_createFeed(); + $this->_mockIterator($this->_feed_mock, [$this->_item_mock]); + $result = $this->_fetcher->fetch($this->_url, false); + $this->assertEquals([$feed, [$item]], $result); } - private function expectFeed($method, $return, $count = 1) + public function testAudioEnclosure() { - $this->parsedFeed->expects($this->exactly($count)) - ->method($method) - ->will($this->returnValue($return)); - } + $this->_setUpReader($this->_url); + $item = $this->_createItem('audio/ogg'); + $feed = $this->_createFeed(); + $this->_mockIterator($this->_feed_mock, [$this->_item_mock]); + $result = $this->_fetcher->fetch($this->_url, false); - private function expectItem($method, $return, $count = 1) - { - $this->item->expects($this->exactly($count)) - ->method($method) - ->will($this->returnValue($return)); + $this->assertEquals([$feed, [$item]], $result); } - private function createItem($enclosureType=null) + public function testVideoEnclosure() { - $this->expectItem('getUrl', $this->permalink); - $this->expectItem('getTitle', $this->title); - $this->expectItem('getId', $this->guid); - $this->expectItem('getContent', $this->body); + $this->_setUpReader($this->_url); + $item = $this->_createItem('video/ogg'); + $feed = $this->_createFeed(); + $this->_mockIterator($this->_feed_mock, [$this->_item_mock]); + $result = $this->_fetcher->fetch($this->_url, false); - $item = new Item(); + $this->assertEquals([$feed, [$item]], $result); + } - date_default_timezone_set('America/Los_Angeles'); + public function testFavicon() + { + $this->_setUpReader($this->_url); - $pubdate = \Datetime::createFromFormat('U', $this->pub); - $this->expectItem('getPublishedDate', $pubdate); - $item->setPubDate($this->pub); + $feed = $this->_createFeed('de-DE', true); + $item = $this->_createItem(); + $this->_mockIterator($this->_feed_mock, [$this->_item_mock]); + $result = $this->_fetcher->fetch($this->_url, true); - $update = \Datetime::createFromFormat('U', $this->updated); - $this->expectItem('getUpdatedDate', $update); - $item->setUpdatedDate($this->updated); + $this->assertEquals([$feed, [$item]], $result); + } - $item->setStatus(0); - $item->setUnread(true); - $item->setUrl($this->permalink); - $item->setTitle('my<\' title'); - $item->setGuid($this->guid); - $item->setGuidHash($this->guid); - $item->setBody($this->body); - $item->setRtl(false); + public function testNoFavicon() + { + $this->_setUpReader($this->_url); - $this->expectItem('getAuthor', $this->author); - $item->setAuthor(html_entity_decode($this->author)); + $feed = $this->_createFeed(false); - if($enclosureType === 'audio/ogg' || $enclosureType === 'video/ogg') { - $this->expectItem('getEnclosureUrl', $this->enclosureLink); - $this->expectItem('getEnclosureType', $enclosureType); + $this->_favicon->expects($this->never()) + ->method('get'); - $item->setEnclosureMime($enclosureType); - $item->setEnclosureLink($this->enclosureLink); - } - $item->generateSearchIndex(); + $item = $this->_createItem(); + $this->_mockIterator($this->_feed_mock, [$this->_item_mock]); + $result = $this->_fetcher->fetch($this->_url, false); - return $item; + $this->assertEquals([$feed, [$item]], $result); } + public function testRtl() + { + $this->_setUpReader($this->_url); + $this->_createFeed('he-IL'); + $this->_createItem(); + $this->_mockIterator($this->_feed_mock, [$this->_item_mock]); + list($feed, $items) = $this->_fetcher->fetch( + $this->_url, false + ); + $this->assertTrue($items[0]->getRtl()); + } - private function createFeed($hasFavicon=false) + public function testRssPubDate() { - $this->expectFeed('getTitle', $this->feedTitle); - $this->expectFeed('getSiteUrl', $this->feedLink); + $this->_setUpReader($this->_url); + $this->_createFeed('he-IL'); + $this->_createItem(); - $feed = new Feed(); - $feed->setTitle('&its a title'); - $feed->setUrl($this->url); - $feed->setLink($this->feedLink); - $feed->setAdded($this->time); - $feed->setHttpLastModified($this->modified); - $feed->setHttpEtag($this->etag); - $feed->setLocation($this->location); - - if($hasFavicon) { - $this->faviconFactory->expects($this->once()) - ->method('build') - ->will($this->returnValue($this->faviconFetcher)); - $this->faviconFetcher->expects($this->once()) - ->method('find') - ->with($this->equalTo($this->feedLink)) - ->will($this->returnValue($this->webFavicon)); - $feed->setFaviconLink($this->webFavicon); - } + $this->_item_mock->expects($this->exactly(2)) + ->method('getValue') + ->will($this->returnValueMap([ + ['pubDate', '2018-03-27T19:50:29Z'], + ['published', NULL], + ])); - return $feed; - } - public function testNoFetchIfNotModified() - { - $this->setUpReader($this->url, false);; - $result = $this->fetcher->fetch($this->url, false); + $this->_mockIterator($this->_feed_mock, [$this->_item_mock]); + list($feed, $items) = $this->_fetcher->fetch($this->_url, false); + $this->assertSame($items[0]->getPubDate(), 1522180229); } - public function testFetch() + public function testAtomPubDate() { - $this->setUpReader($this->url); - $item = $this->createItem(); - $feed = $this->createFeed(); - $this->expectFeed('getItems', [$this->item]); - $result = $this->fetcher->fetch($this->url, false); - - $this->assertEquals([$feed, [$item]], $result); - } + $this->_setUpReader($this->_url); + $this->_createFeed('he-IL'); + $this->_createItem(); + $this->_item_mock->expects($this->exactly(3)) + ->method('getValue') + ->will($this->returnValueMap([ + ['pubDate', NULL], + ['published', '2018-02-27T19:50:29Z'], + ])); - public function testAudioEnclosure() - { - $this->setUpReader($this->url); - $item = $this->createItem('audio/ogg'); - $feed = $this->createFeed(); - $this->expectFeed('getItems', [$this->item]); - $result = $this->fetcher->fetch($this->url, false); - $this->assertEquals([$feed, [$item]], $result); + $this->_mockIterator($this->_feed_mock, [$this->_item_mock]); + list($feed, $items) = $this->_fetcher->fetch($this->_url, false); + $this->assertSame($items[0]->getPubDate(), 1519761029); } - - public function testVideoEnclosure() + /** + * Mock an iteration option on an existing mock + * + * @param object $iteratorMock The mock to enhance + * @param array $items The items to make available + * + * @return mixed + */ + private function _mockIterator($iteratorMock, array $items) { - $this->setUpReader($this->url); - $item = $this->createItem('video/ogg'); - $feed = $this->createFeed(); - $this->expectFeed('getItems', [$this->item]); - $result = $this->fetcher->fetch($this->url, false); - - $this->assertEquals([$feed, [$item]], $result); + $iteratorData = new \stdClass(); + $iteratorData->array = $items; + $iteratorData->position = 0; + + $iteratorMock->expects($this->any()) + ->method('rewind') + ->will( + $this->returnCallback( + function () use ($iteratorData) { + $iteratorData->position = 0; + } + ) + ); + + $iteratorMock->expects($this->any()) + ->method('current') + ->will( + $this->returnCallback( + function () use ($iteratorData) { + return $iteratorData->array[$iteratorData->position]; + } + ) + ); + + $iteratorMock->expects($this->any()) + ->method('key') + ->will( + $this->returnCallback( + function () use ($iteratorData) { + return $iteratorData->position; + } + ) + ); + + $iteratorMock->expects($this->any()) + ->method('next') + ->will( + $this->returnCallback( + function () use ($iteratorData) { + $iteratorData->position++; + } + ) + ); + + $iteratorMock->expects($this->any()) + ->method('valid') + ->will( + $this->returnCallback( + function () use ($iteratorData) { + return isset($iteratorData->array[$iteratorData->position]); + } + ) + ); + + $iteratorMock->expects($this->any()) + ->method('count') + ->will( + $this->returnCallback( + function () use ($iteratorData) { + return sizeof($iteratorData->array); + } + ) + ); + + return $iteratorMock; } - - - public function testFavicon() + private function _setUpReader($url='', $modified=true) { - $this->setUpReader($this->url); + $this->_reader->expects($this->once()) + ->method('readSince') + ->with($this->equalTo($url)) + ->will($this->returnValue($this->_result)); + $this->_result->expects($this->once()) + ->method('getResponse') + ->will($this->returnValue($this->_response)); + $this->_response->expects($this->once()) + ->method('isModified') + ->will($this->returnValue($modified)); - $feed = $this->createFeed(true); - $item = $this->createItem(); - $this->expectFeed('getItems', [$this->item]); - $result = $this->fetcher->fetch($this->url); + if (!$modified) { + $this->_result->expects($this->never()) + ->method('getUrl'); + } else { + $this->_result->expects($this->once()) + ->method('getUrl') + ->will($this->returnValue($this->_location)); + $this->_result->expects($this->once()) + ->method('getFeed') + ->will($this->returnValue($this->_feed_mock)); + } - $this->assertEquals([$feed, [$item]], $result); } - public function testFullText() + private function _expectFeed($method, $return, $count = 1) { - $this->setUpReader($this->url); - - $feed = $this->createFeed(); - $item = $this->createItem(); - $this->parser->expects($this->once()) - ->method('enableContentGrabber'); - $this->expectFeed('getItems', [$this->item]); - $this->fetcher->fetch($this->url, false, null, null, true); + $this->_feed_mock->expects($this->exactly($count)) + ->method($method) + ->will($this->returnValue($return)); } + private function _expectItem($method, $return, $count = 1) + { + $this->_item_mock->expects($this->exactly($count)) + ->method($method) + ->will($this->returnValue($return)); + } - public function testNoFavicon() + + private function _createItem($enclosureType=null) { - $this->setUpReader($this->url); + $this->_expectItem('getLink', $this->_permalink); + $this->_expectItem('getTitle', $this->_title); + $this->_expectItem('getPublicId', $this->_guid); + $this->_expectItem('getDescription', $this->_body); + $this->_expectItem('getLastModified', $this->_modified, 2); + $this->_expectItem('getAuthor', $this->_author); - $feed = $this->createFeed(false); + $item = new Item(); - $this->faviconFetcher->expects($this->never()) - ->method('find'); + $item->setStatus(0); + $item->setUnread(true); + $item->setUrl($this->_permalink); + $item->setTitle('my<\' title'); + $item->setGuid($this->_guid); + $item->setGuidHash($this->_guid); + $item->setBody($this->_body); + $item->setRtl(false); + $item->setLastModified(3); + $item->setPubDate(3); + $item->setAuthor(html_entity_decode($this->_author)); + + if ($enclosureType === 'audio/ogg' || $enclosureType === 'video/ogg') { + $media = $this->getMockbuilder(MediaInterface::class)->getMock(); + $media->expects($this->once()) + ->method('getType') + ->will($this->returnValue('sounds')); + $media2 = $this->getMockbuilder(MediaInterface::class)->getMock(); + $media2->expects($this->exactly(2)) + ->method('getType') + ->will($this->returnValue($enclosureType)); + $media2->expects($this->once()) + ->method('getUrl') + ->will($this->returnValue($this->_enclosure)); + $this->_expectItem('hasMedia', true); + $this->_expectItem('getMedias', [$media, $media2]); - $item = $this->createItem(); - $this->expectFeed('getItems', [$this->item]); - $result = $this->fetcher->fetch($this->url, false); + $item->setEnclosureMime($enclosureType); + $item->setEnclosureLink($this->_enclosure); + } + $item->generateSearchIndex(); - $this->assertEquals([$feed, [$item]], $result); + return $item; } - public function testRtl() + private function _createFeed($lang='de-DE', $favicon=false) { - $this->setUpReader($this->url); - $this->expectFeed('getLanguage', 'he-IL'); - $this->expectItem('getLanguage', ''); - $feed = $this->createFeed(); - $item = $this->createItem(null); - $this->expectFeed('getItems', [$this->item]); - list($feed, $items) = $this->fetcher->fetch( - $this->url, false, null, - null, true - ); - $this->assertTrue($items[0]->getRtl()); - } + $this->_expectFeed('getTitle', $this->_feed_title); + $this->_expectFeed('getLink', $this->_feed_link); + $this->_expectFeed('getLastModified', $this->_modified); + $this->_expectFeed('getLanguage', $lang); + $feed = new Feed(); - public function testRtlItemPrecedence() - { - $this->setUpReader($this->url); - $this->expectFeed('getLanguage', 'de-DE'); - $this->expectItem('getLanguage', 'he-IL'); - - $feed = $this->createFeed(); - $item = $this->createItem(null); - $this->expectFeed('getItems', [$this->item]); - list($feed, $items) = $this->fetcher->fetch( - $this->url, false, null, - null, true - ); - $this->assertTrue($items[0]->getRtl()); - } + $feed->setTitle('&its a title'); + $feed->setLink($this->_feed_link); + $feed->setUrl($this->_url); + $feed->setLastModified(3); + $feed->setAdded($this->_time); + if ($favicon) { + $feed->setFaviconLink('http://anon.google.com'); + $this->_favicon->expects($this->exactly(1)) + ->method('get') + ->with($this->equalTo($this->_feed_link)) + ->will($this->returnValue($this->_web_favicon)); + } else { + $this->_favicon->expects($this->never()) + ->method('get'); + } - public function testNegativeRtlItemPrecedence() - { - $this->setUpReader($this->url); - $this->expectFeed('getLanguage', 'he-IL'); - $this->expectItem('getLanguage', 'de-DE'); - - $feed = $this->createFeed(); - $item = $this->createItem(null); - $this->expectFeed('getItems', [$this->item]); - list($feed, $items) = $this->fetcher->fetch( - $this->url, false, null, - null, true - ); - $this->assertFalse($items[0]->getRtl()); + return $feed; } - } diff --git a/tests/Unit/Service/FeedServiceTest.php b/tests/Unit/Service/FeedServiceTest.php index 8cdc50c88..9b0c50b20 100644 --- a/tests/Unit/Service/FeedServiceTest.php +++ b/tests/Unit/Service/FeedServiceTest.php @@ -311,7 +311,8 @@ class FeedServiceTest extends TestCase $this->equalTo('http://test'), $this->equalTo(false), $this->equalTo(3), - $this->equalTo(4) + $this->equalTo(''), + $this->equalTo('') ) ->will($this->returnValue($fetchReturn)); $this->feedMapper->expects($this->at(1)) @@ -377,7 +378,8 @@ class FeedServiceTest extends TestCase $this->equalTo('http://test'), $this->equalTo(false), $this->equalTo(3), - $this->equalTo(4) + $this->equalTo(''), + $this->equalTo('') ) ->will($this->returnValue($fetchReturn)); $this->feedMapper->expects($this->at(1)) @@ -635,7 +637,6 @@ class FeedServiceTest extends TestCase $feed = new Feed(); $feed->setId(3); $feed->setUrl('https://goo.com'); - $feed->setHttpEtag('abc'); $feed->setHttpLastModified(123); $feed->setFullTextEnabled(true); @@ -654,9 +655,7 @@ class FeedServiceTest extends TestCase ->with( $this->equalTo($feed->getUrl()), $this->equalTo(false), - $this->equalTo($feed->getHttpLastModified()), - $this->equalTo($feed->getHttpEtag()), - $this->equalTo($feed->getFullTextEnabled()) + $this->equalTo($feed->getHttpLastModified()) ) ->will($this->throwException($ex)); |