diff --git a/composer.json b/composer.json index 179770fbef..66ac9361d1 100644 --- a/composer.json +++ b/composer.json @@ -126,7 +126,8 @@ }, "require-dev": { "mockery/mockery": "^1.3", - "mikey179/vfsstream": "^1.6" + "mikey179/vfsstream": "^1.6", + "phpunit/phpunit": "^8.5" }, "scripts": { "test": "phpunit", diff --git a/composer.lock b/composer.lock index fde685617e..facde6d0bd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "67d9153800314dadee1f838ad722c148", + "content-hash": "6dda88f5629c38f3d07b31e13ded57aa", "packages": [ { "name": "asika/simple-console", @@ -4517,6 +4517,71 @@ } ], "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" + }, { "name": "hamcrest/hamcrest-php", "version": "v2.0.1", @@ -4674,6 +4739,1599 @@ "testing" ], "time": "2021-02-24T09:51:00+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220", + "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "replace": { + "myclabs/deep-copy": "self.version" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^7.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2020-11-13T09:40:50+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "bae7c545bef187884426f042434e561ab1ddb182" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/bae7c545bef187884426f042434e561ab1ddb182", + "reference": "bae7c545bef187884426f042434e561ab1ddb182", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2021-02-23T14:00:09+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.13.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/be1996ed8adc35c3fd795488a653f4b518be70ea", + "reference": "be1996ed8adc35c3fd795488a653f4b518be70ea", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.2", + "php": "^7.2 || ~8.0, <8.1", + "phpdocumentor/reflection-docblock": "^5.2", + "sebastian/comparator": "^3.0 || ^4.0", + "sebastian/recursion-context": "^3.0 || ^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^6.0", + "phpunit/phpunit": "^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2021-03-17T13:42:18+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "7.0.15", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "819f92bba8b001d4363065928088de22f25a3a48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/819f92bba8b001d4363065928088de22f25a3a48", + "reference": "819f92bba8b001d4363065928088de22f25a3a48", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": ">=7.2", + "phpunit/php-file-iterator": "^2.0.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^3.1.3 || ^4.0", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^4.2.2", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1.3" + }, + "require-dev": { + "phpunit/phpunit": "^8.2.2" + }, + "suggest": { + "ext-xdebug": "^2.7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-07-26T12:20:09+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "28af674ff175d0768a5a978e6de83f697d4a7f05" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/28af674ff175d0768a5a978e6de83f697d4a7f05", + "reference": "28af674ff175d0768a5a978e6de83f697d4a7f05", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-07-19T06:46:01+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "reference": "2454ae1765516d20c4ffe103d85a58a9a3bd5662", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:20:02+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "3.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "9c1da83261628cb24b6a6df371b6e312b3954768" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9c1da83261628cb24b6a6df371b6e312b3954768", + "reference": "9c1da83261628cb24b6a6df371b6e312b3954768", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "abandoned": true, + "time": "2021-07-26T12:15:06+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "8.5.19", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "496281b64ec781856ed0a583483b5923b4033722" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/496281b64ec781856ed0a583483b5923b4033722", + "reference": "496281b64ec781856ed0a583483b5923b4033722", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.0", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.2", + "phpspec/prophecy": "^1.10.3", + "phpunit/php-code-coverage": "^7.0.12", + "phpunit/php-file-iterator": "^2.0.4", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.2", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.3", + "sebastian/exporter": "^3.1.2", + "sebastian/global-state": "^3.0.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.3", + "sebastian/version": "^2.0.1" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*", + "phpunit/php-invoker": "^2.0.0" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "8.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "funding": [ + { + "url": "https://phpunit.de/donate.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-07-31T15:15:06+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:15:22+00:00" + }, + { + "name": "sebastian/comparator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "1071dfcef776a57013124ff35e1fc41ccd294758" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1071dfcef776a57013124ff35e1fc41ccd294758", + "reference": "1071dfcef776a57013124ff35e1fc41ccd294758", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "sebastian/diff": "^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T08:04:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", + "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:59:04+00:00" + }, + { + "name": "sebastian/environment", + "version": "4.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "reference": "d47bbbad83711771f167c72d4e3f25f7fcc1f8b0", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:53:42+00:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/6b853149eab67d4da22291d36f5b0631c0fd856e", + "reference": "6b853149eab67d4da22291d36f5b0631c0fd856e", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:47:53+00:00" + }, + { + "name": "sebastian/global-state", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/474fb9edb7ab891665d3bfc6317f42a0a150454b", + "reference": "474fb9edb7ab891665d3bfc6317f42a0a150454b", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^8.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:43:24+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "reference": "e67f6d32ebd0c749cf9d1dbd9f226c727043cdf2", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:40:27+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "reference": "9b8772b9cbd456ab45d4a598d2dd1a1bced6363d", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:37:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/367dcba38d6e1977be014dc4b22f47a484dac7fb", + "reference": "367dcba38d6e1977be014dc4b22f47a484dac7fb", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:34:24+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "reference": "31d35ca87926450c44eae7e2611d45a7a65ea8b3", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:30:19+00:00" + }, + { + "name": "sebastian/type", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "reference": "0150cfbc4495ed2df3872fb31b26781e4e077eb4", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-30T07:25:11+00:00" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "reference": "46cd95797e9df938fdd2b03693b5fca5e64b01ce", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-19T12:13:01+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2021-03-09T10:59:23+00:00" } ], "aliases": [], diff --git a/doc/AddonStorageBackend.md b/doc/AddonStorageBackend.md index 950a2ad88d..e54960252b 100644 --- a/doc/AddonStorageBackend.md +++ b/doc/AddonStorageBackend.md @@ -10,12 +10,12 @@ A storage backend is implemented as a class, and the plugin register the class t The class must live in `Friendica\Addon\youraddonname` namespace, where `youraddonname` the folder name of your addon. -The class must implement `Friendica\Model\Storage\IStorage` interface. All method in the interface must be implemented: +The class must implement `Friendica\Model\Storage\IWritableStorage` interface. All method in the interface must be implemented: -namespace Friendica\Model\Storage; +namespace Friendica\Model\IWritableStorage; ```php -interface IStorage +interface IWritableStorage { public function get(string $reference); public function put(string $data, string $reference = ''); @@ -79,7 +79,7 @@ Each label should be translatable ]; -See doxygen documentation of `IStorage` interface for details about each method. +See doxygen documentation of `IWritableStorage` interface for details about each method. ## Register a storage backend class @@ -105,8 +105,9 @@ Each new Storage class should be added to the test-environment at [Storage Tests Add a new test class which's naming convention is `StorageClassTest`, which extend the `StorageTest` in the same directory. Override the two necessary instances: + ```php -use Friendica\Model\Storage\IStorage; +use Friendica\Model\Storage\IWritableStorage; abstract class StorageTest { @@ -114,13 +115,48 @@ abstract class StorageTest abstract protected function getInstance(); // Assertion for the option array you return for your new StorageClass - abstract protected function assertOption(IStorage $storage); + abstract protected function assertOption(IWritableStorage $storage); +} +``` + +## Exception handling + +There are two intended types of exceptions for storages + +### `ReferenceStorageExecption` + +This storage exception should be used in case the caller tries to use an invalid references. +This could happen in case the caller tries to delete or update an unknown reference. +The implementation of the storage backend must not ignore invalid references. + +Avoid throwing the common `StorageExecption` instead of the `ReferenceStorageException` at this particular situation! + +### `StorageException` + +This is the common exception in case unexpected errors happen using the storage backend. +If there's a predecessor to this exception (e.g. you caught an exception and are throwing this execption), you should add the predecessor for transparency reasons. + +Example: + +```php +use Friendica\Model\Storage\IWritableStorage; + +class ExampleStorage implements IWritableStorage +{ + public function get(string $reference) : string + { + try { + throw new Exception('a real bad exception'); + } catch (Exception $exception) { + throw new \Friendica\Model\Storage\StorageException(sprintf('The Example Storage throws an exception for reference %s', $reference), 500, $exception); + } + } } ``` ## Example -Here an hypotetical addon which register an unusefull storage backend. +Here an hypotetical addon which register a useless storage backend. Let's call it `samplestorage`. This backend will discard all data we try to save and will return always the same image when we ask for some data. @@ -133,12 +169,12 @@ The file will be `addon/samplestorage/SampleStorageBackend.php`: assertEquals([ 'filename' => [ diff --git a/mod/photos.php b/mod/photos.php index 4512921074..bd56fbbc6a 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -65,6 +65,9 @@ function photos_init(App $a) { if (DI::args()->getArgc() > 1) { $owner = User::getOwnerDataByNick(DI::args()->getArgv()[1]); + if (!$owner) { + throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.')); + } $is_owner = (local_user() && (local_user() == $owner['uid'])); diff --git a/src/Console/Storage.php b/src/Console/Storage.php index 93bbb26579..3377f33ddf 100644 --- a/src/Console/Storage.php +++ b/src/Console/Storage.php @@ -23,6 +23,7 @@ namespace Friendica\Console; use Asika\SimpleConsole\CommandArgsException; use Friendica\Core\StorageManager; +use Friendica\Model\Storage\ReferenceStorageException; use Friendica\Model\Storage\StorageException; /** @@ -105,7 +106,7 @@ HELP; $this->out(sprintf($rowfmt, 'Sel', 'Name')); $this->out('-----------------------'); $isregisterd = false; - foreach ($this->storageManager->listBackends() as $name => $class) { + foreach ($this->storageManager->listBackends() as $name) { $issel = ' '; if ($current && $current::getName() == $name) { $issel = '*'; @@ -127,23 +128,23 @@ HELP; protected function doSet() { - if (count($this->args) !== 2) { + if (count($this->args) !== 2 || empty($this->args[1])) { throw new CommandArgsException('Invalid arguments'); } $name = $this->args[1]; - $class = $this->storageManager->getByName($name); + try { + $class = $this->storageManager->getWritableStorageByName($name); - if ($class === '') { + if (!$this->storageManager->setBackend($class)) { + $this->out($class . ' is not a valid backend storage class.'); + return -1; + } + } catch (ReferenceStorageException $exception) { $this->out($name . ' is not a registered backend.'); return -1; } - if (!$this->storageManager->setBackend($class)) { - $this->out($class . ' is not a valid backend storage class.'); - return -1; - } - return 0; } diff --git a/src/Core/Installer.php b/src/Core/Installer.php index b2b84c6182..0417718125 100644 --- a/src/Core/Installer.php +++ b/src/Core/Installer.php @@ -129,6 +129,10 @@ class Installer $returnVal = false; } + if (!$this->checkTLS()) { + $returnVal = false; + } + if (!$this->checkKeys()) { $returnVal = false; } @@ -580,6 +584,38 @@ class Installer return $status; } + /** + * TLS Check + * + * Tries to determine whether the connection to the server is secured + * by TLS or not. If not the user will be warned that it is higly + * encuraged to use TLS. + * + * @return bool (true) as TLS is not mandatory + */ + public function checkTLS() + { + $tls = false; + + if (isset($_SERVER['HTTPS'])) { + if (($_SERVER['HTTPS'] == 1) || ($_SERVER['HTTPS'] == 'on')) { + $tls = true; + } + } + + if (!$tls) { + $help = DI::l10n()->t('The detection of TLS to secure the communication between the browser and the new Friendica server failed.'); + $help .= ' ' . DI::l10n()->t('It is highly encouraged to use Friendica only over a secure connection as sensitive information like passwords will be transmitted.'); + $help .= ' ' . DI::l10n()->t('Please ensure that the connection to the server is secure.'); + $this->addCheck(DI::l10n()->t('No TLS detected'), $tls, false, $help); + } else { + $this->addCheck(DI::l10n()->t('TLS detected'), $tls, false, ''); + } + + // TLS is not required + return true; + } + /** * Imagick Check * diff --git a/src/Core/StorageManager.php b/src/Core/StorageManager.php index 64e53c10b9..f206868885 100644 --- a/src/Core/StorageManager.php +++ b/src/Core/StorageManager.php @@ -25,10 +25,9 @@ use Exception; use Friendica\Core\Config\IConfig; use Friendica\Database\Database; use Friendica\Model\Storage; -use Friendica\Network\IHTTPRequest; +use Friendica\Network\HTTPException\InternalServerErrorException; use Psr\Log\LoggerInterface; - /** * Manage storage backends * @@ -41,12 +40,14 @@ class StorageManager const TABLES = ['photo', 'attach']; // Default storage backends + /** @var string[] */ const DEFAULT_BACKENDS = [ - Storage\Filesystem::NAME => Storage\Filesystem::class, - Storage\Database::NAME => Storage\Database::class, + Storage\Filesystem::NAME, + Storage\Database::NAME, ]; - private $backends = []; + /** @var string[] List of valid backend classes */ + private $validBackends; /** * @var Storage\IStorage[] A local cache for storage instances @@ -61,10 +62,8 @@ class StorageManager private $logger; /** @var L10n */ private $l10n; - /** @var IHTTPRequest */ - private $httpRequest; - /** @var Storage\IStorage */ + /** @var Storage\IWritableStorage */ private $currentBackend; /** @@ -72,82 +71,106 @@ class StorageManager * @param IConfig $config * @param LoggerInterface $logger * @param L10n $l10n + * + * @throws Storage\InvalidClassStorageException in case the active backend class is invalid + * @throws Storage\StorageException in case of unexpected errors during the active backend class loading */ - public function __construct(Database $dba, IConfig $config, LoggerInterface $logger, L10n $l10n, IHTTPRequest $httpRequest) + public function __construct(Database $dba, IConfig $config, LoggerInterface $logger, L10n $l10n) { - $this->dba = $dba; - $this->config = $config; - $this->logger = $logger; - $this->l10n = $l10n; - $this->httpRequest = $httpRequest; - $this->backends = $config->get('storage', 'backends', self::DEFAULT_BACKENDS); + $this->dba = $dba; + $this->config = $config; + $this->logger = $logger; + $this->l10n = $l10n; + $this->validBackends = $config->get('storage', 'backends', self::DEFAULT_BACKENDS); - $currentName = $this->config->get('storage', 'name', ''); + $currentName = $this->config->get('storage', 'name'); // you can only use user backends as a "default" backend, so the second parameter is true - $this->currentBackend = $this->getByName($currentName, true); + $this->currentBackend = $this->getWritableStorageByName($currentName); } /** * Return current storage backend class * - * @return Storage\IStorage|null + * @return Storage\IWritableStorage */ public function getBackend() { return $this->currentBackend; } + /** + * Returns a writable storage backend class by registered name + * + * @param string $name Backend name + * + * @return Storage\IWritableStorage + * + * @throws Storage\InvalidClassStorageException in case there's no backend class for the name + * @throws Storage\StorageException in case of an unexpected failure during the hook call + */ + public function getWritableStorageByName(string $name): Storage\IWritableStorage + { + $storage = $this->getByName($name, $this->validBackends); + if (!$storage instanceof Storage\IWritableStorage) { + throw new Storage\InvalidClassStorageException(sprintf('Backend %s is not writable', $name)); + } + + return $storage; + } + /** * Return storage backend class by registered name * - * @param string|null $name Backend name - * @param boolean $onlyUserBackend True, if just user specific instances should be returrned (e.g. not SystemResource) + * @param string $name Backend name + * @param string[]|null $validBackends possible, manual override of the valid backends * - * @return Storage\IStorage|null null if no backend registered at $name + * @return Storage\IStorage * - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws Storage\InvalidClassStorageException in case there's no backend class for the name + * @throws Storage\StorageException in case of an unexpected failure during the hook call */ - public function getByName(string $name = null, $onlyUserBackend = false) + public function getByName(string $name, array $validBackends = null): Storage\IStorage { - // @todo 2020.09 Remove this call after 2 releases - $name = $this->checkLegacyBackend($name); - // If there's no cached instance create a new instance if (!isset($this->backendInstances[$name])) { // If the current name isn't a valid backend (or the SystemResource instance) create it - if ($this->isValidBackend($name, $onlyUserBackend)) { - switch ($name) { - // Try the filesystem backend - case Storage\Filesystem::getName(): - $this->backendInstances[$name] = new Storage\Filesystem($this->config, $this->logger, $this->l10n); - break; - // try the database backend - case Storage\Database::getName(): - $this->backendInstances[$name] = new Storage\Database($this->dba, $this->logger, $this->l10n); - break; - // at least, try if there's an addon for the backend - case Storage\SystemResource::getName(): - $this->backendInstances[$name] = new Storage\SystemResource(); - break; - case Storage\ExternalResource::getName(): - $this->backendInstances[$name] = new Storage\ExternalResource($this->httpRequest); - break; - default: - $data = [ - 'name' => $name, - 'storage' => null, - ]; + if (!$this->isValidBackend($name, $validBackends)) { + throw new Storage\InvalidClassStorageException(sprintf('Backend %s is not valid', $name)); + } + + switch ($name) { + // Try the filesystem backend + case Storage\Filesystem::getName(): + $this->backendInstances[$name] = new Storage\Filesystem($this->config, $this->l10n); + break; + // try the database backend + case Storage\Database::getName(): + $this->backendInstances[$name] = new Storage\Database($this->dba); + break; + // at least, try if there's an addon for the backend + case Storage\SystemResource::getName(): + $this->backendInstances[$name] = new Storage\SystemResource(); + break; + case Storage\ExternalResource::getName(): + $this->backendInstances[$name] = new Storage\ExternalResource(); + break; + default: + $data = [ + 'name' => $name, + 'storage' => null, + ]; + try { Hook::callAll('storage_instance', $data); - if (($data['storage'] ?? null) instanceof Storage\IStorage) { - $this->backendInstances[$data['name'] ?? $name] = $data['storage']; - } else { - return null; + if (!($data['storage'] ?? null) instanceof Storage\IStorage) { + throw new Storage\InvalidClassStorageException(sprintf('Backend %s was not found', $name)); } - break; - } - } else { - return null; + + $this->backendInstances[$data['name'] ?? $name] = $data['storage']; + } catch (InternalServerErrorException $exception) { + throw new Storage\StorageException(sprintf('Failed calling hook::storage_instance for backend %s', $name), $exception); + } + break; } } @@ -157,51 +180,32 @@ class StorageManager /** * Checks, if the storage is a valid backend * - * @param string|null $name The name or class of the backend - * @param boolean $onlyUserBackend True, if just user backend should get returned (e.g. not SystemResource) + * @param string|null $name The name or class of the backend + * @param string[]|null $validBackends Possible, valid backends to check * * @return boolean True, if the backend is a valid backend */ - public function isValidBackend(string $name = null, bool $onlyUserBackend = false) + public function isValidBackend(string $name = null, array $validBackends = null): bool { - return array_key_exists($name, $this->backends) || - (!$onlyUserBackend && in_array($name, [Storage\SystemResource::getName(), Storage\ExternalResource::getName()])); - } - - /** - * Check for legacy backend storage class names (= full model class name) - * - * @todo 2020.09 Remove this function after 2 releases, because there shouldn't be any legacy backend classes left - * - * @param string|null $name a potential, legacy storage name ("Friendica\Model\Storage\...") - * - * @return string|null The current storage name - */ - private function checkLegacyBackend(string $name = null) - { - if (stristr($name, 'Friendica\Model\Storage\\')) { - $this->logger->notice('Using deprecated storage class value', ['name' => $name]); - return substr($name, 24); - } - - return $name; + $validBackends = $validBackends ?? array_merge($this->validBackends, + [ + Storage\SystemResource::getName(), + Storage\ExternalResource::getName(), + ]); + return in_array($name, $validBackends); } /** * Set current storage backend class * - * @param string $name Backend class name + * @param Storage\IWritableStorage $storage The storage class * * @return boolean True, if the set was successful */ - public function setBackend(string $name = null) + public function setBackend(Storage\IWritableStorage $storage): bool { - if (!$this->isValidBackend($name, false)) { - return false; - } - - if ($this->config->set('storage', 'name', $name)) { - $this->currentBackend = $this->getByName($name, false); + if ($this->config->set('storage', 'name', $storage::getName())) { + $this->currentBackend = $storage; return true; } else { return false; @@ -211,11 +215,11 @@ class StorageManager /** * Get registered backends * - * @return array + * @return string[] */ - public function listBackends() + public function listBackends(): array { - return $this->backends; + return $this->validBackends; } /** @@ -227,16 +231,20 @@ class StorageManager * * @return boolean True, if the registration was successful */ - public function register(string $class) + public function register(string $class): bool { if (is_subclass_of($class, Storage\IStorage::class)) { /** @var Storage\IStorage $class */ - $backends = $this->backends; - $backends[$class::getName()] = $class; + if ($this->isValidBackend($class::getName(), $this->validBackends)) { + return true; + } + + $backends = $this->validBackends; + $backends[] = $class::getName(); if ($this->config->set('storage', 'backends', $backends)) { - $this->backends = $backends; + $this->validBackends = $backends; return true; } else { return false; @@ -252,20 +260,33 @@ class StorageManager * @param string $class Backend class name * * @return boolean True, if unregistering was successful + * + * @throws Storage\StorageException */ - public function unregister(string $class) + public function unregister(string $class): bool { if (is_subclass_of($class, Storage\IStorage::class)) { /** @var Storage\IStorage $class */ - unset($this->backends[$class::getName()]); - - if ($this->currentBackend instanceof $class) { - $this->config->set('storage', 'name', null); - $this->currentBackend = null; + if ($this->currentBackend::getName() == $class::getName()) { + throw new Storage\StorageException(sprintf('Cannot unregister %s, because it\'s currently active.', $class::getName())); } - return $this->config->set('storage', 'backends', $this->backends); + $key = array_search($class::getName(), $this->validBackends); + + if ($key !== false) { + $backends = $this->validBackends; + unset($backends[$key]); + $backends = array_values($backends); + if ($this->config->set('storage', 'backends', $backends)) { + $this->validBackends = $backends; + return true; + } else { + return false; + } + } else { + return true; + } } else { return false; } @@ -277,17 +298,17 @@ class StorageManager * Copy existing data to destination storage and delete from source. * This method cannot move to legacy in-table `data` field. * - * @param Storage\IStorage $destination Destination storage class name - * @param array $tables Tables to look in for resources. Optional, defaults to ['photo', 'attach'] - * @param int $limit Limit of the process batch size, defaults to 5000 + * @param Storage\IWritableStorage $destination Destination storage class name + * @param array $tables Tables to look in for resources. Optional, defaults to ['photo', 'attach'] + * @param int $limit Limit of the process batch size, defaults to 5000 * * @return int Number of moved resources * @throws Storage\StorageException * @throws Exception */ - public function move(Storage\IStorage $destination, array $tables = self::TABLES, int $limit = 5000) + public function move(Storage\IWritableStorage $destination, array $tables = self::TABLES, int $limit = 5000): int { - if (!$this->isValidBackend($destination, true)) { + if (!$this->isValidBackend($destination, $this->validBackends)) { throw new Storage\StorageException(sprintf("Can't move to storage backend '%s'", $destination::getName())); } @@ -303,13 +324,19 @@ class StorageManager while ($resource = $this->dba->fetch($resources)) { $id = $resource['id']; - $data = $resource['data']; - $source = $this->getByName($resource['backend-class']); $sourceRef = $resource['backend-ref']; + $source = null; - if (!empty($source)) { + try { + $source = $this->getWritableStorageByName($resource['backend-class'] ?? ''); $this->logger->info('Get data from old backend.', ['oldBackend' => $source, 'oldReference' => $sourceRef]); $data = $source->get($sourceRef); + } catch (Storage\InvalidClassStorageException $exception) { + $this->logger->info('Get data from DB resource field.', ['oldReference' => $sourceRef]); + $data = $resource['data']; + } catch (Storage\ReferenceStorageException $exception) { + $this->logger->info('Invalid source reference.', ['oldBackend' => $source, 'oldReference' => $sourceRef]); + continue; } $this->logger->info('Save data to new backend.', ['newBackend' => $destination::getName()]); diff --git a/src/DI.php b/src/DI.php index 02620ea11e..28ad130b4d 100644 --- a/src/DI.php +++ b/src/DI.php @@ -387,11 +387,11 @@ abstract class DI } /** - * @return Model\Storage\IStorage + * @return Model\Storage\IWritableStorage */ public static function storage() { - return self::$dice->create(Model\Storage\IStorage::class); + return self::$dice->create(Model\Storage\IWritableStorage::class); } // diff --git a/src/Model/Attach.php b/src/Model/Attach.php index 6182727c97..e11fd01bc3 100644 --- a/src/Model/Attach.php +++ b/src/Model/Attach.php @@ -25,6 +25,8 @@ use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\Database\DBStructure; use Friendica\DI; +use Friendica\Model\Storage\InvalidClassStorageException; +use Friendica\Model\Storage\ReferenceStorageException; use Friendica\Object\Image; use Friendica\Util\DateTimeFormat; use Friendica\Util\Mimetype; @@ -163,17 +165,20 @@ class Attach return $item['data']; } - $backendClass = DI::storageManager()->getByName($item['backend-class'] ?? ''); - if (empty($backendClass)) { + try { + $backendClass = DI::storageManager()->getByName($item['backend-class'] ?? ''); + $backendRef = $item['backend-ref']; + return $backendClass->get($backendRef); + } catch (InvalidClassStorageException $storageException) { // legacy data storage in 'data' column $i = self::selectFirst(['data'], ['id' => $item['id']]); if ($i === false) { return null; } return $i['data']; - } else { - $backendRef = $item['backend-ref']; - return $backendClass->get($backendRef); + } catch (ReferenceStorageException $referenceStorageException) { + DI::logger()->debug('No data found for item', ['item' => $item, 'exception' => $referenceStorageException]); + return ''; } } @@ -278,11 +283,13 @@ class Attach $items = self::selectToArray(['backend-class','backend-ref'], $conditions); foreach($items as $item) { - $backend_class = DI::storageManager()->getByName($item['backend-class'] ?? ''); - if (!empty($backend_class)) { + try { + $backend_class = DI::storageManager()->getWritableStorageByName($item['backend-class'] ?? ''); $fields['backend-ref'] = $backend_class->put($img->asString(), $item['backend-ref'] ?? ''); - } else { - $fields['data'] = $img->asString(); + } catch (InvalidClassStorageException $storageException) { + DI::logger()->debug('Storage class not found.', ['conditions' => $conditions, 'exception' => $storageException]); + } catch (ReferenceStorageException $referenceStorageException) { + DI::logger()->debug('Item doesn\'t exist.', ['conditions' => $conditions, 'exception' => $referenceStorageException]); } } } @@ -310,9 +317,13 @@ class Attach $items = self::selectToArray(['backend-class','backend-ref'], $conditions); foreach($items as $item) { - $backend_class = DI::storageManager()->getByName($item['backend-class'] ?? ''); - if (!empty($backend_class)) { + try { + $backend_class = DI::storageManager()->getWritableStorageByName($item['backend-class'] ?? ''); $backend_class->delete($item['backend-ref'] ?? ''); + } catch (InvalidClassStorageException $storageException) { + DI::logger()->debug('Storage class not found.', ['conditions' => $conditions, 'exception' => $storageException]); + } catch (ReferenceStorageException $referenceStorageException) { + DI::logger()->debug('Item doesn\'t exist.', ['conditions' => $conditions, 'exception' => $referenceStorageException]); } } diff --git a/src/Model/Item.php b/src/Model/Item.php index beb7db5764..c683e10e78 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -2757,6 +2757,8 @@ class Item $filter_reasons[] = DI::l10n()->t('Content warning: %s', $item['content-warning']); } + $item['attachments'] = $attachments; + $hook_data = [ 'item' => $item, 'filter_reasons' => $filter_reasons diff --git a/src/Model/Photo.php b/src/Model/Photo.php index 26369a3540..a59c30aca2 100644 --- a/src/Model/Photo.php +++ b/src/Model/Photo.php @@ -28,6 +28,9 @@ use Friendica\Database\DBA; use Friendica\Database\DBStructure; use Friendica\DI; use Friendica\Model\Storage\ExternalResource; +use Friendica\Model\Storage\InvalidClassStorageException; +use Friendica\Model\Storage\ReferenceStorageException; +use Friendica\Model\Storage\StorageException; use Friendica\Model\Storage\SystemResource; use Friendica\Object\Image; use Friendica\Util\DateTimeFormat; @@ -184,8 +187,6 @@ class Photo * @param array $photo Photo data. Needs at least 'id', 'type', 'backend-class', 'backend-ref' * * @return \Friendica\Object\Image - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - * @throws \ImagickException */ public static function getImageDataForPhoto(array $photo) { @@ -193,19 +194,31 @@ class Photo return $photo['data']; } - $backendClass = DI::storageManager()->getByName($photo['backend-class'] ?? ''); - if (empty($backendClass)) { - // legacy data storage in "data" column - $i = self::selectFirst(['data'], ['id' => $photo['id']]); - if ($i === false) { - return null; + try { + $backendClass = DI::storageManager()->getByName($photo['backend-class'] ?? ''); + /// @todo refactoring this returning, because the storage returns a "string" which is casted in different ways - a check "instanceof Image" will fail! + return $backendClass->get($photo['backend-ref'] ?? ''); + } catch (InvalidClassStorageException $storageException) { + try { + // legacy data storage in "data" column + $i = self::selectFirst(['data'], ['id' => $photo['id']]); + if ($i !== false) { + return $i['data']; + } else { + DI::logger()->info('Stored legacy data is empty', ['photo' => $photo]); + } + } catch (\Exception $exception) { + DI::logger()->info('Unexpected database exception', ['photo' => $photo, 'exception' => $exception]); } - $data = $i['data']; - } else { - $backendRef = $photo['backend-ref'] ?? ''; - $data = $backendClass->get($backendRef); + } catch (ReferenceStorageException $referenceStorageException) { + DI::logger()->debug('Invalid reference for photo', ['photo' => $photo, 'exception' => $referenceStorageException]); + } catch (StorageException $storageException) { + DI::logger()->info('Unexpected storage exception', ['photo' => $photo, 'exception' => $storageException]); + } catch (\ImagickException $imagickException) { + DI::logger()->info('Unexpected imagick exception', ['photo' => $photo, 'exception' => $imagickException]); } - return $data; + + return null; } /** @@ -217,14 +230,9 @@ class Photo * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - public static function getImageForPhoto(array $photo) + public static function getImageForPhoto(array $photo): Image { - $data = self::getImageDataForPhoto($photo); - if (empty($data)) { - return null; - } - - return new Image($data, $photo['type']); + return new Image(self::getImageDataForPhoto($photo), $photo['type']); } /** @@ -334,20 +342,20 @@ class Photo // Get defined storage backend. // if no storage backend, we use old "data" column in photo table. // if is an existing photo, reuse same backend - $data = ""; + $data = ""; $backend_ref = ""; + $storage = ""; - if (DBA::isResult($existing_photo)) { - $backend_ref = (string)$existing_photo["backend-ref"]; - $storage = DI::storageManager()->getByName($existing_photo["backend-class"] ?? ''); - } else { - $storage = DI::storage(); - } - - if (empty($storage)) { - $data = $Image->asString(); - } else { + try { + if (DBA::isResult($existing_photo)) { + $backend_ref = (string)$existing_photo["backend-ref"]; + $storage = DI::storageManager()->getWritableStorageByName($existing_photo["backend-class"] ?? ''); + } else { + $storage = DI::storage(); + } $backend_ref = $storage->put($Image->asString(), $backend_ref); + } catch (InvalidClassStorageException $storageException) { + $data = $Image->asString(); } $fields = [ @@ -403,12 +411,15 @@ class Photo $photos = DBA::select('photo', ['id', 'backend-class', 'backend-ref'], $conditions); while ($photo = DBA::fetch($photos)) { - $backend_class = DI::storageManager()->getByName($photo['backend-class'] ?? ''); - if (!empty($backend_class)) { - if ($backend_class->delete($photo["backend-ref"] ?? '')) { - // Delete the photos after they had been deleted successfully - DBA::delete("photo", ['id' => $photo['id']]); - } + try { + $backend_class = DI::storageManager()->getWritableStorageByName($photo['backend-class'] ?? ''); + $backend_class->delete($item['backend-ref'] ?? ''); + // Delete the photos after they had been deleted successfully + DBA::delete("photo", ['id' => $photo['id']]); + } catch (InvalidClassStorageException $storageException) { + DI::logger()->debug('Storage class not found.', ['conditions' => $conditions, 'exception' => $storageException]); + } catch (ReferenceStorageException $referenceStorageException) { + DI::logger()->debug('Photo doesn\'t exist.', ['conditions' => $conditions, 'exception' => $referenceStorageException]); } } @@ -437,10 +448,10 @@ class Photo $photos = self::selectToArray(['backend-class', 'backend-ref'], $conditions); foreach($photos as $photo) { - $backend_class = DI::storageManager()->getByName($photo['backend-class'] ?? ''); - if (!empty($backend_class)) { + try { + $backend_class = DI::storageManager()->getWritableStorageByName($photo['backend-class'] ?? ''); $fields["backend-ref"] = $backend_class->put($img->asString(), $photo['backend-ref']); - } else { + } catch (InvalidClassStorageException $storageException) { $fields["data"] = $img->asString(); } } diff --git a/src/Model/Post/Media.php b/src/Model/Post/Media.php index 801f192e9d..fae71a9536 100644 --- a/src/Model/Post/Media.php +++ b/src/Model/Post/Media.php @@ -534,7 +534,7 @@ class Media * * @param int $uri_id * @param string $guid - * @param array $links ist of links that shouldn't be added + * @param array $links list of links that shouldn't be added * @return array attachments */ public static function splitAttachments(int $uri_id, string $guid = '', array $links = []) diff --git a/src/Model/Storage/Database.php b/src/Model/Storage/Database.php index 4eaa813ca6..3457fa4a30 100644 --- a/src/Model/Storage/Database.php +++ b/src/Model/Storage/Database.php @@ -21,8 +21,7 @@ namespace Friendica\Model\Storage; -use Friendica\Core\L10n; -use Psr\Log\LoggerInterface; +use Exception; use Friendica\Database\Database as DBA; /** @@ -30,7 +29,7 @@ use Friendica\Database\Database as DBA; * * This class manage data stored in database table. */ -class Database extends AbstractStorage +class Database implements IWritableStorage { const NAME = 'Database'; @@ -39,47 +38,57 @@ class Database extends AbstractStorage /** * @param DBA $dba - * @param LoggerInterface $logger - * @param L10n $l10n */ - public function __construct(DBA $dba, LoggerInterface $logger, L10n $l10n) + public function __construct(DBA $dba) { - parent::__construct($l10n, $logger); - $this->dba = $dba; } /** * @inheritDoc */ - public function get(string $reference) + public function get(string $reference): string { - $result = $this->dba->selectFirst('storage', ['data'], ['id' => $reference]); - if (!$this->dba->isResult($result)) { - return ''; - } + try { + $result = $this->dba->selectFirst('storage', ['data'], ['id' => $reference]); + if (!$this->dba->isResult($result)) { + throw new ReferenceStorageException(sprintf('Database storage cannot find data for reference %s', $reference)); + } - return $result['data']; + return $result['data']; + } catch (Exception $exception) { + if ($exception instanceof ReferenceStorageException) { + throw $exception; + } else { + throw new StorageException(sprintf('Database storage failed to get %s', $reference), $exception->getCode(), $exception); + } + } } /** * @inheritDoc */ - public function put(string $data, string $reference = '') + public function put(string $data, string $reference = ''): string { if ($reference !== '') { - $result = $this->dba->update('storage', ['data' => $data], ['id' => $reference]); + try { + $result = $this->dba->update('storage', ['data' => $data], ['id' => $reference]); + } catch (Exception $exception) { + throw new StorageException(sprintf('Database storage failed to update %s', $reference), $exception->getCode(), $exception); + } if ($result === false) { - $this->logger->warning('Failed to update data.', ['id' => $reference, 'errorCode' => $this->dba->errorNo(), 'errorMessage' => $this->dba->errorMessage()]); - throw new StorageException($this->l10n->t('Database storage failed to update %s', $reference)); + throw new StorageException(sprintf('Database storage failed to update %s', $reference), 500, new Exception($this->dba->errorMessage(), $this->dba->errorNo())); } return $reference; } else { - $result = $this->dba->insert('storage', ['data' => $data]); + try { + $result = $this->dba->insert('storage', ['data' => $data]); + } catch (Exception $exception) { + throw new StorageException(sprintf('Database storage failed to insert %s', $reference), $exception->getCode(), $exception); + } if ($result === false) { - $this->logger->warning('Failed to insert data.', ['errorCode' => $this->dba->errorNo(), 'errorMessage' => $this->dba->errorMessage()]); - throw new StorageException($this->l10n->t('Database storage failed to insert data')); + throw new StorageException(sprintf('Database storage failed to update %s', $reference), 500, new Exception($this->dba->errorMessage(), $this->dba->errorNo())); } return $this->dba->lastInsertId(); @@ -91,13 +100,23 @@ class Database extends AbstractStorage */ public function delete(string $reference) { - return $this->dba->delete('storage', ['id' => $reference]); + try { + if (!$this->dba->delete('storage', ['id' => $reference]) || $this->dba->affectedRows() === 0) { + throw new ReferenceStorageException(sprintf('Database storage failed to delete %s', $reference)); + } + } catch (Exception $exception) { + if ($exception instanceof ReferenceStorageException) { + throw $exception; + } else { + throw new StorageException(sprintf('Database storage failed to delete %s', $reference), $exception->getCode(), $exception); + } + } } /** * @inheritDoc */ - public function getOptions() + public function getOptions(): array { return []; } @@ -105,7 +124,7 @@ class Database extends AbstractStorage /** * @inheritDoc */ - public function saveOptions(array $data) + public function saveOptions(array $data): array { return []; } @@ -113,8 +132,13 @@ class Database extends AbstractStorage /** * @inheritDoc */ - public static function getName() + public static function getName(): string { return self::NAME; } + + public function __toString() + { + return self::getName(); + } } diff --git a/src/Model/Storage/ExternalResource.php b/src/Model/Storage/ExternalResource.php index 0e758c7062..918bcf8ac3 100644 --- a/src/Model/Storage/ExternalResource.php +++ b/src/Model/Storage/ExternalResource.php @@ -21,9 +21,8 @@ namespace Friendica\Model\Storage; -use BadMethodCallException; +use Exception; use Friendica\Util\HTTPSignature; -use Friendica\Network\IHTTPRequest; /** * External resource storage class @@ -35,66 +34,33 @@ class ExternalResource implements IStorage { const NAME = 'ExternalResource'; - /** @var IHTTPRequest */ - private $httpRequest; - - public function __construct(IHTTPRequest $httpRequest) - { - $this->httpRequest = $httpRequest; - } - /** * @inheritDoc */ - public function get(string $reference) + public function get(string $reference): string { $data = json_decode($reference); if (empty($data->url)) { - return ""; + throw new ReferenceStorageException(sprintf('Invalid reference %s, cannot retrieve URL', $reference)); } $parts = parse_url($data->url); if (empty($parts['scheme']) || empty($parts['host'])) { - return ""; + throw new ReferenceStorageException(sprintf('Invalid reference %s, cannot extract scheme and host', $reference)); } - $fetchResult = HTTPSignature::fetchRaw($data->url, $data->uid, ['accept_content' => '']); + try { + $fetchResult = HTTPSignature::fetchRaw($data->url, $data->uid, ['accept_content' => '']); + } catch (Exception $exception) { + throw new ReferenceStorageException(sprintf('External resource failed to get %s', $reference), $exception->getCode(), $exception); + } if ($fetchResult->isSuccess()) { return $fetchResult->getBody(); } else { - return ""; + throw new ReferenceStorageException(sprintf('External resource failed to get %s', $reference), $fetchResult->getReturnCode(), new Exception($fetchResult->getBody())); } } - /** - * @inheritDoc - */ - public function put(string $data, string $reference = '') - { - throw new BadMethodCallException(); - } - - public function delete(string $reference) - { - throw new BadMethodCallException(); - } - - /** - * @inheritDoc - */ - public function getOptions() - { - return []; - } - - /** - * @inheritDoc - */ - public function saveOptions(array $data) - { - return []; - } - /** * @inheritDoc */ @@ -106,7 +72,7 @@ class ExternalResource implements IStorage /** * @inheritDoc */ - public static function getName() + public static function getName(): string { return self::NAME; } diff --git a/src/Model/Storage/Filesystem.php b/src/Model/Storage/Filesystem.php index dfe0b812ea..c6c939bd46 100644 --- a/src/Model/Storage/Filesystem.php +++ b/src/Model/Storage/Filesystem.php @@ -21,10 +21,10 @@ namespace Friendica\Model\Storage; +use Exception; use Friendica\Core\Config\IConfig; use Friendica\Core\L10n; use Friendica\Util\Strings; -use Psr\Log\LoggerInterface; /** * Filesystem based storage backend @@ -36,7 +36,7 @@ use Psr\Log\LoggerInterface; * Each new resource gets a value as reference and is saved in a * folder tree stucture created from that value. */ -class Filesystem extends AbstractStorage +class Filesystem implements IWritableStorage { const NAME = 'Filesystem'; @@ -49,18 +49,19 @@ class Filesystem extends AbstractStorage /** @var string */ private $basePath; + /** @var L10n */ + private $l10n; + /** * Filesystem constructor. * * @param IConfig $config - * @param LoggerInterface $logger * @param L10n $l10n */ - public function __construct(IConfig $config, LoggerInterface $logger, L10n $l10n) + public function __construct(IConfig $config, L10n $l10n) { - parent::__construct($l10n, $logger); - $this->config = $config; + $this->l10n = $l10n; $path = $this->config->get('storage', 'filesystem_path', self::DEFAULT_BASE_FOLDER); $this->basePath = rtrim($path, '/'); @@ -73,7 +74,7 @@ class Filesystem extends AbstractStorage * * @return string */ - private function pathForRef(string $reference) + private function pathForRef(string $reference): string { $fold1 = substr($reference, 0, 2); $fold2 = substr($reference, 2, 2); @@ -84,7 +85,7 @@ class Filesystem extends AbstractStorage /** - * Create dirctory tree to store file, with .htaccess and index.html files + * Create directory tree to store file, with .htaccess and index.html files * * @param string $file Path and filename * @@ -96,8 +97,7 @@ class Filesystem extends AbstractStorage if (!is_dir($path)) { if (!mkdir($path, 0770, true)) { - $this->logger->warning('Failed to create dir.', ['path' => $path]); - throw new StorageException($this->l10n->t('Filesystem storage failed to create "%s". Check you write permissions.', $path)); + throw new StorageException(sprintf('Filesystem storage failed to create "%s". Check you write permissions.', $path)); } } @@ -118,23 +118,33 @@ class Filesystem extends AbstractStorage /** * @inheritDoc */ - public function get(string $reference) + public function get(string $reference): string { $file = $this->pathForRef($reference); if (!is_file($file)) { - return ''; + throw new ReferenceStorageException(sprintf('Filesystem storage failed to get the file %s, The file is invalid', $reference)); } - return file_get_contents($file); + $result = file_get_contents($file); + + if ($result === false) { + throw new StorageException(sprintf('Filesystem storage failed to get data to "%s". Check your write permissions', $file)); + } + + return $result; } /** * @inheritDoc */ - public function put(string $data, string $reference = '') + public function put(string $data, string $reference = ''): string { if ($reference === '') { - $reference = Strings::getRandomHex(); + try { + $reference = Strings::getRandomHex(); + } catch (Exception $exception) { + throw new StorageException('Filesystem storage failed to generate a random hex', $exception->getCode(), $exception); + } } $file = $this->pathForRef($reference); @@ -144,8 +154,7 @@ class Filesystem extends AbstractStorage // just in case the result is REALLY false, not zero or empty or anything else, throw the exception if ($result === false) { - $this->logger->warning('Failed to write data.', ['file' => $file]); - throw new StorageException($this->l10n->t('Filesystem storage failed to save data to "%s". Check your write permissions', $file)); + throw new StorageException(sprintf('Filesystem storage failed to save data to "%s". Check your write permissions', $file)); } chmod($file, 0660); @@ -158,17 +167,19 @@ class Filesystem extends AbstractStorage public function delete(string $reference) { $file = $this->pathForRef($reference); - // return true if file doesn't exists. we want to delete it: success with zero work! if (!is_file($file)) { - return true; + throw new ReferenceStorageException(sprintf('File with reference "%s" doesn\'t exist', $reference)); + } + + if (!unlink($file)) { + throw new StorageException(sprintf('Cannot delete with file with reference "%s"', $reference)); } - return unlink($file); } /** * @inheritDoc */ - public function getOptions() + public function getOptions(): array { return [ 'storagepath' => [ @@ -183,7 +194,7 @@ class Filesystem extends AbstractStorage /** * @inheritDoc */ - public function saveOptions(array $data) + public function saveOptions(array $data): array { $storagePath = $data['storagepath'] ?? ''; if ($storagePath === '' || !is_dir($storagePath)) { @@ -199,8 +210,13 @@ class Filesystem extends AbstractStorage /** * @inheritDoc */ - public static function getName() + public static function getName(): string { return self::NAME; } + + public function __toString() + { + return self::getName(); + } } diff --git a/src/Model/Storage/IStorage.php b/src/Model/Storage/IStorage.php index 0a0f589587..8841487412 100644 --- a/src/Model/Storage/IStorage.php +++ b/src/Model/Storage/IStorage.php @@ -22,9 +22,7 @@ namespace Friendica\Model\Storage; /** - * Interface for storage backends - * - * @todo Split this interface into "IStorage" for get() operations (including Resource fetching) and "IUserStorage" for real user backends including put/delete/options + * Interface for basic storage backends */ interface IStorage { @@ -34,77 +32,11 @@ interface IStorage * @param string $reference Data reference * * @return string + * + * @throws StorageException in case there's an unexpected error + * @throws ReferenceStorageException in case the reference doesn't exist */ - public function get(string $reference); - - /** - * Put data in backend as $ref. If $ref is not defined a new reference is created. - * - * @param string $data Data to save - * @param string $reference Data reference. Optional. - * - * @return string Saved data reference - */ - public function put(string $data, string $reference = ""); - - /** - * Remove data from backend - * - * @param string $reference Data reference - * - * @return boolean True on success - */ - public function delete(string $reference); - - /** - * Get info about storage options - * - * @return array - * - * This method return an array with informations about storage options - * from which the form presented to the user is build. - * - * The returned array is: - * - * [ - * 'option1name' => [ ..info.. ], - * 'option2name' => [ ..info.. ], - * ... - * ] - * - * An empty array can be returned if backend doesn't have any options - * - * The info array for each option MUST be as follows: - * - * [ - * 'type', // define the field used in form, and the type of data. - * // one of 'checkbox', 'combobox', 'custom', 'datetime', - * // 'input', 'intcheckbox', 'password', 'radio', 'richtext' - * // 'select', 'select_raw', 'textarea' - * - * 'label', // Translatable label of the field - * 'value', // Current value - * 'help text', // Translatable description for the field - * extra data // Optional. Depends on 'type': - * // select: array [ value => label ] of choices - * // intcheckbox: value of input element - * // select_raw: prebuild html string of < option > tags - * ] - * - * See https://github.com/friendica/friendica/wiki/Quick-Template-Guide - */ - public function getOptions(); - - /** - * Validate and save options - * - * @param array $data Array [optionname => value] to be saved - * - * @return array Validation errors: [optionname => error message] - * - * Return array must be empty if no error. - */ - public function saveOptions(array $data); + public function get(string $reference): string; /** * The name of the backend @@ -118,5 +50,5 @@ interface IStorage * * @return string */ - public static function getName(); + public static function getName(): string; } diff --git a/src/Model/Storage/IWritableStorage.php b/src/Model/Storage/IWritableStorage.php new file mode 100644 index 0000000000..ee0001a669 --- /dev/null +++ b/src/Model/Storage/IWritableStorage.php @@ -0,0 +1,103 @@ +. + * + */ + +namespace Friendica\Model\Storage; + +/** + * Interface for writable storage backends + * + * Used for storages with CRUD functionality, mainly used for user data (e.g. photos, attachements). + * There's only one active writable storage possible. This type of storage is selectable by the current administrator. + */ +interface IWritableStorage extends IStorage +{ + /** + * Put data in backend as $ref. If $ref is not defined a new reference is created. + * + * @param string $data Data to save + * @param string $reference Data reference. Optional. + * + * @return string Saved data reference + * + * @throws StorageException in case there's an unexpected error + */ + public function put(string $data, string $reference = ""): string; + + /** + * Remove data from backend + * + * @param string $reference Data reference + * + * @throws StorageException in case there's an unexpected error + * @throws ReferenceStorageException in case the reference doesn't exist + */ + public function delete(string $reference); + + /** + * Get info about storage options + * + * @return array + * + * This method return an array with informations about storage options + * from which the form presented to the user is build. + * + * The returned array is: + * + * [ + * 'option1name' => [ ..info.. ], + * 'option2name' => [ ..info.. ], + * ... + * ] + * + * An empty array can be returned if backend doesn't have any options + * + * The info array for each option MUST be as follows: + * + * [ + * 'type', // define the field used in form, and the type of data. + * // one of 'checkbox', 'combobox', 'custom', 'datetime', + * // 'input', 'intcheckbox', 'password', 'radio', 'richtext' + * // 'select', 'select_raw', 'textarea' + * + * 'label', // Translatable label of the field + * 'value', // Current value + * 'help text', // Translatable description for the field + * extra data // Optional. Depends on 'type': + * // select: array [ value => label ] of choices + * // intcheckbox: value of input element + * // select_raw: prebuild html string of < option > tags + * ] + * + * See https://github.com/friendica/friendica/wiki/Quick-Template-Guide + */ + public function getOptions(): array; + + /** + * Validate and save options + * + * @param array $data Array [optionname => value] to be saved + * + * @return array Validation errors: [optionname => error message] + * + * Return array must be empty if no error. + */ + public function saveOptions(array $data): array; +} diff --git a/src/Model/Storage/AbstractStorage.php b/src/Model/Storage/InvalidClassStorageException.php similarity index 60% rename from src/Model/Storage/AbstractStorage.php rename to src/Model/Storage/InvalidClassStorageException.php index 97152ddd06..9c39b3a60c 100644 --- a/src/Model/Storage/AbstractStorage.php +++ b/src/Model/Storage/InvalidClassStorageException.php @@ -21,31 +21,9 @@ namespace Friendica\Model\Storage; -use Friendica\Core\L10n; -use Psr\Log\LoggerInterface; - /** - * A general storage class which loads common dependencies and implements common methods + * Storage Exception in case of invalid storage class */ -abstract class AbstractStorage implements IStorage +class InvalidClassStorageException extends StorageException { - /** @var L10n */ - protected $l10n; - /** @var LoggerInterface */ - protected $logger; - - /** - * @param L10n $l10n - * @param LoggerInterface $logger - */ - public function __construct(L10n $l10n, LoggerInterface $logger) - { - $this->l10n = $l10n; - $this->logger = $logger; - } - - public function __toString() - { - return static::getName(); - } } diff --git a/src/Model/Storage/ReferenceStorageException.php b/src/Model/Storage/ReferenceStorageException.php new file mode 100644 index 0000000000..fcfd3ab59d --- /dev/null +++ b/src/Model/Storage/ReferenceStorageException.php @@ -0,0 +1,29 @@ +. + * + */ + +namespace Friendica\Model\Storage; + +/** + * Storage Exception in case of invalid references + */ +class ReferenceStorageException extends StorageException +{ +} diff --git a/src/Model/Storage/StorageException.php b/src/Model/Storage/StorageException.php index 4287f403a1..34a09d57bc 100644 --- a/src/Model/Storage/StorageException.php +++ b/src/Model/Storage/StorageException.php @@ -21,9 +21,11 @@ namespace Friendica\Model\Storage; +use Exception; + /** - * Storage Exception + * Storage Exception for unexpected failures */ -class StorageException extends \Exception +class StorageException extends Exception { } diff --git a/src/Model/Storage/SystemResource.php b/src/Model/Storage/SystemResource.php index c7699c2b79..39bc0a0245 100644 --- a/src/Model/Storage/SystemResource.php +++ b/src/Model/Storage/SystemResource.php @@ -21,8 +21,6 @@ namespace Friendica\Model\Storage; -use \BadMethodCallException; - /** * System resource storage class * @@ -39,45 +37,22 @@ class SystemResource implements IStorage /** * @inheritDoc */ - public function get(string $filename) + public function get(string $reference): string { - $folder = dirname($filename); + $folder = dirname($reference); if (!in_array($folder, self::VALID_FOLDERS)) { - return ""; + throw new ReferenceStorageException(sprintf('System Resource is invalid for reference %s, no valid folder found', $reference)); } - if (!file_exists($filename)) { - return ""; + if (!file_exists($reference)) { + throw new StorageException(sprintf('System Resource is invalid for reference %s, the file doesn\'t exist', $reference)); } - return file_get_contents($filename); - } + $content = file_get_contents($reference); - /** - * @inheritDoc - */ - public function put(string $data, string $filename = '') - { - throw new BadMethodCallException(); - } + if ($content === false) { + throw new StorageException(sprintf('Cannot get content for reference %s', $reference)); + } - public function delete(string $filename) - { - throw new BadMethodCallException(); - } - - /** - * @inheritDoc - */ - public function getOptions() - { - return []; - } - - /** - * @inheritDoc - */ - public function saveOptions(array $data) - { - return []; + return $content; } /** @@ -91,7 +66,7 @@ class SystemResource implements IStorage /** * @inheritDoc */ - public static function getName() + public static function getName(): string { return self::NAME; } diff --git a/src/Module/Admin/Storage.php b/src/Module/Admin/Storage.php index 1010707124..a2172503f5 100644 --- a/src/Module/Admin/Storage.php +++ b/src/Module/Admin/Storage.php @@ -23,7 +23,8 @@ namespace Friendica\Module\Admin; use Friendica\Core\Renderer; use Friendica\DI; -use Friendica\Model\Storage\IStorage; +use Friendica\Model\Storage\InvalidClassStorageException; +use Friendica\Model\Storage\IWritableStorage; use Friendica\Module\BaseAdmin; use Friendica\Util\Strings; @@ -37,8 +38,13 @@ class Storage extends BaseAdmin $storagebackend = Strings::escapeTags(trim($parameters['name'] ?? '')); - /** @var IStorage $newstorage */ - $newstorage = DI::storageManager()->getByName($storagebackend); + try { + /** @var IWritableStorage $newstorage */ + $newstorage = DI::storageManager()->getWritableStorageByName($storagebackend); + } catch (InvalidClassStorageException $storageException) { + notice(DI::l10n()->t('Storage backend, %s is invalid.', $storagebackend)); + DI::baseUrl()->redirect('admin/storage'); + } // save storage backend form $storage_opts = $newstorage->getOptions(); @@ -62,13 +68,20 @@ class Storage extends BaseAdmin $storage_form_errors = $newstorage->saveOptions($storage_opts_data); if (count($storage_form_errors)) { foreach ($storage_form_errors as $name => $err) { - notice('Storage backend, ' . $storage_opts[$name][1] . ': ' . $err); + notice(DI::l10n()->t('Storage backend %s error: %s', $storage_opts[$name][1], $err)); } DI::baseUrl()->redirect('admin/storage'); } if (!empty($_POST['submit_save_set'])) { - if (empty($storagebackend) || !DI::storageManager()->setBackend($storagebackend)) { + try { + /** @var IWritableStorage $newstorage */ + $newstorage = DI::storageManager()->getWritableStorageByName($storagebackend); + + if (!DI::storageManager()->setBackend($newstorage)) { + notice(DI::l10n()->t('Invalid storage backend setting value.')); + } + } catch (InvalidClassStorageException $storageException) { notice(DI::l10n()->t('Invalid storage backend setting value.')); } } @@ -83,13 +96,13 @@ class Storage extends BaseAdmin $current_storage_backend = DI::storage(); $available_storage_forms = []; - foreach (DI::storageManager()->listBackends() as $name => $class) { + foreach (DI::storageManager()->listBackends() as $name) { // build storage config form, $storage_form_prefix = preg_replace('|[^a-zA-Z0-9]|', '', $name); $storage_form = []; - foreach (DI::storageManager()->getByName($name)->getOptions() as $option => $info) { + foreach (DI::storageManager()->getWritableStorageByName($name)->getOptions() as $option => $info) { $type = $info[0]; // Backward compatibilty with yesno field description if ($type == 'yesno') { @@ -108,7 +121,7 @@ class Storage extends BaseAdmin 'name' => $name, 'prefix' => $storage_form_prefix, 'form' => $storage_form, - 'active' => $current_storage_backend instanceof IStorage && $name === $current_storage_backend::getName(), + 'active' => $current_storage_backend instanceof IWritableStorage && $name === $current_storage_backend::getName(), ]; } @@ -124,7 +137,7 @@ class Storage extends BaseAdmin '$noconfig' => DI::l10n()->t('This backend doesn\'t have custom settings'), '$baseurl' => DI::baseUrl()->get(true), '$form_security_token' => self::getFormSecurityToken("admin_storage"), - '$storagebackend' => $current_storage_backend instanceof IStorage ? $current_storage_backend::getName() : DI::l10n()->t('Database (legacy)'), + '$storagebackend' => $current_storage_backend instanceof IWritableStorage ? $current_storage_backend::getName() : DI::l10n()->t('Database (legacy)'), '$availablestorageforms' => $available_storage_forms, ]); } diff --git a/src/Module/Photo.php b/src/Module/Photo.php index 06a369df77..9d2d31b45d 100644 --- a/src/Module/Photo.php +++ b/src/Module/Photo.php @@ -30,7 +30,11 @@ use Friendica\Model\Photo as MPhoto; use Friendica\Model\Post; use Friendica\Model\Profile; use Friendica\Model\Storage\ExternalResource; +use Friendica\Model\Storage\ReferenceStorageException; +use Friendica\Model\Storage\StorageException; use Friendica\Model\Storage\SystemResource; +use Friendica\Network\HTTPException\InternalServerErrorException; +use Friendica\Network\HTTPException\NotFoundException; use Friendica\Util\Proxy; use Friendica\Object\Image; use Friendica\Util\Images; @@ -105,7 +109,11 @@ class Photo extends BaseModule $cacheable = ($photo["allow_cid"] . $photo["allow_gid"] . $photo["deny_cid"] . $photo["deny_gid"] === "") && (isset($photo["cacheable"]) ? $photo["cacheable"] : true); $stamp = microtime(true); + $imgdata = MPhoto::getImageDataForPhoto($photo); + if (empty($imgdata)) { + throw new NotFoundException(); + } // The mimetype for an external or system resource can only be known reliably after it had been fetched if (in_array($photo['backend-class'], [ExternalResource::NAME, SystemResource::NAME])) { diff --git a/src/Module/RemoteFollow.php b/src/Module/RemoteFollow.php index 0950a057a5..f1e653f1ce 100644 --- a/src/Module/RemoteFollow.php +++ b/src/Module/RemoteFollow.php @@ -32,6 +32,7 @@ use Friendica\Core\System; use Friendica\Model\Contact; use Friendica\Model\Profile; use Friendica\Model\User; +use Friendica\Network\HTTPException; use Friendica\Network\Probe; /** @@ -44,6 +45,9 @@ class RemoteFollow extends BaseModule public static function init(array $parameters = []) { self::$owner = User::getOwnerDataByNick($parameters['profile']); + if (!self::$owner) { + throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.')); + } DI::page()['aside'] = Widget\VCard::getHTML(self::$owner); } diff --git a/src/Module/Settings/Profile/Photo/Crop.php b/src/Module/Settings/Profile/Photo/Crop.php index 83722c8661..ba96e0032f 100644 --- a/src/Module/Settings/Profile/Photo/Crop.php +++ b/src/Module/Settings/Profile/Photo/Crop.php @@ -61,6 +61,10 @@ class Crop extends BaseSettings $base_image = Photo::selectFirst([], ['resource-id' => $resource_id, 'uid' => local_user(), 'scale' => $scale]); if (DBA::isResult($base_image)) { $Image = Photo::getImageForPhoto($base_image); + if (empty($Image)) { + throw new HTTPException\InternalServerErrorException(); + } + if ($Image->isValid()) { // If setting for the default profile, unset the profile photo flag from any other photos I own DBA::update('photo', ['profile' => 0], ['uid' => local_user()]); @@ -188,6 +192,9 @@ class Crop extends BaseSettings } $Image = Photo::getImageForPhoto($photos[0]); + if (empty($Image)) { + throw new HTTPException\InternalServerErrorException(); + } $imagecrop = [ 'resource-id' => $resource_id, diff --git a/src/Network/Probe.php b/src/Network/Probe.php index 1a4c47a452..8cee679400 100644 --- a/src/Network/Probe.php +++ b/src/Network/Probe.php @@ -2167,7 +2167,7 @@ class Probe { // Search for the newest entry in the feed $curlResult = DI::httpRequest()->get($data['poll']); - if (!$curlResult->isSuccess()) { + if (!$curlResult->isSuccess() || !$curlResult->getBody()) { return ''; } diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php index 3980b7fba3..e2de810a60 100644 --- a/src/Util/HTTPSignature.php +++ b/src/Util/HTTPSignature.php @@ -28,6 +28,7 @@ use Friendica\DI; use Friendica\Model\APContact; use Friendica\Model\Contact; use Friendica\Model\User; +use Friendica\Network\CurlResult; /** * Implements HTTP Signatures per draft-cavage-http-signatures-07. @@ -408,7 +409,7 @@ class HTTPSignature * 'nobody' => only return the header * 'cookiejar' => path to cookie jar file * - * @return object CurlResult + * @return CurlResult CurlResult * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ public static function fetchRaw($request, $uid = 0, $opts = ['accept_content' => 'application/activity+json, application/ld+json']) diff --git a/src/Worker/MoveStorage.php b/src/Worker/MoveStorage.php index ac70c63545..34ecb5c147 100644 --- a/src/Worker/MoveStorage.php +++ b/src/Worker/MoveStorage.php @@ -34,7 +34,7 @@ class MoveStorage public static function execute() { $current = DI::storage(); - $moved = DI::storageManager()->move($current); + $moved = DI::storageManager()->move($current); if ($moved) { Worker::add(PRIORITY_LOW, 'MoveStorage'); diff --git a/static/dependencies.config.php b/static/dependencies.config.php index 90fa13684c..5efe78ddf3 100644 --- a/static/dependencies.config.php +++ b/static/dependencies.config.php @@ -44,7 +44,7 @@ use Friendica\Core\Session\ISession; use Friendica\Core\StorageManager; use Friendica\Database\Database; use Friendica\Factory; -use Friendica\Model\Storage\IStorage; +use Friendica\Model\Storage\IWritableStorage; use Friendica\Model\User\Cookie; use Friendica\Network; use Friendica\Util; @@ -213,7 +213,7 @@ return [ $_SERVER, $_COOKIE ], ], - IStorage::class => [ + IWritableStorage::class => [ 'instanceOf' => StorageManager::class, 'call' => [ ['getBackend', [], Dice::CHAIN_CALL], diff --git a/static/settings.config.php b/static/settings.config.php index e8c6d09f81..96ae421ff0 100644 --- a/static/settings.config.php +++ b/static/settings.config.php @@ -203,4 +203,11 @@ return [ // Used in the admin settings to lock certain features 'featurelock' => [ ], + + // Storage backend configuration + 'storage' => [ + // name (String) + // The name of the current used backend (default is Database) + 'name' => 'Database', + ], ]; diff --git a/tests/Util/SampleStorageBackend.php b/tests/Util/SampleStorageBackend.php index 3d5789b0de..1185a25646 100644 --- a/tests/Util/SampleStorageBackend.php +++ b/tests/Util/SampleStorageBackend.php @@ -22,14 +22,14 @@ namespace Friendica\Test\Util; use Friendica\Core\Hook; -use Friendica\Model\Storage\IStorage; +use Friendica\Model\Storage\IWritableStorage; use Friendica\Core\L10n; /** * A backend storage example class */ -class SampleStorageBackend implements IStorage +class SampleStorageBackend implements IWritableStorage { const NAME = 'Sample Storage'; @@ -62,14 +62,14 @@ class SampleStorageBackend implements IStorage $this->l10n = $l10n; } - public function get(string $reference) + public function get(string $reference): string { // we return always the same image data. Which file we load is defined by // a config key - return $this->data[$reference] ?? null; + return $this->data[$reference] ?? ''; } - public function put(string $data, string $reference = '') + public function put(string $data, string $reference = ''): string { if ($reference === '') { $reference = 'sample'; @@ -89,12 +89,12 @@ class SampleStorageBackend implements IStorage return true; } - public function getOptions() + public function getOptions(): array { return $this->options; } - public function saveOptions(array $data) + public function saveOptions(array $data): array { $this->options = $data; @@ -107,7 +107,7 @@ class SampleStorageBackend implements IStorage return self::NAME; } - public static function getName() + public static function getName(): string { return self::NAME; } @@ -120,4 +120,3 @@ class SampleStorageBackend implements IStorage Hook::register('storage_instance', __DIR__ . '/SampleStorageBackendInstance.php', 'create_instance'); } } - diff --git a/tests/src/Core/StorageManagerTest.php b/tests/src/Core/StorageManagerTest.php index deb9c4b11f..9e8e3aa2c2 100644 --- a/tests/src/Core/StorageManagerTest.php +++ b/tests/src/Core/StorageManagerTest.php @@ -34,7 +34,6 @@ use Friendica\Factory\ConfigFactory; use Friendica\Model\Config\Config; use Friendica\Model\Storage; use Friendica\Core\Session; -use Friendica\Model\Storage\StorageException; use Friendica\Network\HTTPRequest; use Friendica\Test\DatabaseTest; use Friendica\Test\Util\Database\StaticDatabase; @@ -47,6 +46,7 @@ use Friendica\Test\Util\SampleStorageBackend; class StorageManagerTest extends DatabaseTest { + use VFSTrait; /** @var Database */ private $dba; /** @var IConfig */ @@ -58,8 +58,6 @@ class StorageManagerTest extends DatabaseTest /** @var HTTPRequest */ private $httpRequest; - use VFSTrait; - protected function setUp(): void { parent::setUp(); @@ -82,6 +80,7 @@ class StorageManagerTest extends DatabaseTest $configModel = new Config($this->dba); $this->config = new PreloadConfig($configCache, $configModel); + $this->config->set('storage', 'name', 'Database'); $this->l10n = \Mockery::mock(L10n::class); @@ -101,34 +100,38 @@ class StorageManagerTest extends DatabaseTest public function dataStorages() { return [ - 'empty' => [ - 'name' => '', - 'assert' => null, - 'assertName' => '', - 'userBackend' => false, + 'empty' => [ + 'name' => '', + 'valid' => false, + 'interface' => Storage\IStorage::class, + 'assert' => null, + 'assertName' => '', ], - 'database' => [ - 'name' => Storage\Database::NAME, - 'assert' => Storage\Database::class, - 'assertName' => Storage\Database::NAME, - 'userBackend' => true, + 'database' => [ + 'name' => Storage\Database::NAME, + 'valid' => true, + 'interface' => Storage\IWritableStorage::class, + 'assert' => Storage\Database::class, + 'assertName' => Storage\Database::NAME, ], - 'filesystem' => [ - 'name' => Storage\Filesystem::NAME, - 'assert' => Storage\Filesystem::class, - 'assertName' => Storage\Filesystem::NAME, - 'userBackend' => true, + 'filesystem' => [ + 'name' => Storage\Filesystem::NAME, + 'valid' => true, + 'interface' => Storage\IWritableStorage::class, + 'assert' => Storage\Filesystem::class, + 'assertName' => Storage\Filesystem::NAME, ], 'systemresource' => [ - 'name' => Storage\SystemResource::NAME, - 'assert' => Storage\SystemResource::class, - 'assertName' => Storage\SystemResource::NAME, - // false here, because SystemResource isn't meant to be a user backend, - // it's for system resources only - 'userBackend' => false, + 'name' => Storage\SystemResource::NAME, + 'valid' => true, + 'interface' => Storage\IStorage::class, + 'assert' => Storage\SystemResource::class, + 'assertName' => Storage\SystemResource::NAME, ], - 'invalid' => [ + 'invalid' => [ 'name' => 'invalid', + 'valid' => false, + 'interface' => null, 'assert' => null, 'assertName' => '', 'userBackend' => false, @@ -136,55 +139,31 @@ class StorageManagerTest extends DatabaseTest ]; } - /** - * Data array for legacy backends - * - * @todo 2020.09 After 2 releases, remove the legacy functionality and these data array with it - * - * @return array - */ - public function dataLegacyBackends() - { - return [ - 'legacyDatabase' => [ - 'name' => 'Friendica\Model\Storage\Database', - 'assert' => Storage\Database::class, - 'assertName' => Storage\Database::NAME, - 'userBackend' => true, - ], - 'legacyFilesystem' => [ - 'name' => 'Friendica\Model\Storage\Filesystem', - 'assert' => Storage\Filesystem::class, - 'assertName' => Storage\Filesystem::NAME, - 'userBackend' => true, - ], - 'legacySystemResource' => [ - 'name' => 'Friendica\Model\Storage\SystemResource', - 'assert' => Storage\SystemResource::class, - 'assertName' => Storage\SystemResource::NAME, - 'userBackend' => false, - ], - ]; - } - /** * Test the getByName() method * * @dataProvider dataStorages - * @dataProvider dataLegacyBackends */ - public function testGetByName($name, $assert, $assertName, $userBackend) + public function testGetByName($name, $valid, $interface, $assert, $assertName) { - $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest); - - $storage = $storageManager->getByName($name, $userBackend); - - if (!empty($assert)) { - self::assertInstanceOf(Storage\IStorage::class, $storage); - self::assertInstanceOf($assert, $storage); - } else { - self::assertNull($storage); + if (!$valid) { + $this->expectException(Storage\InvalidClassStorageException::class); } + + if ($interface === Storage\IWritableStorage::class) { + $this->config->set('storage', 'name', $name); + } + + $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n); + + if ($interface === Storage\IWritableStorage::class) { + $storage = $storageManager->getWritableStorageByName($name); + } else { + $storage = $storageManager->getByName($name); + } + + self::assertInstanceOf($interface, $storage); + self::assertInstanceOf($assert, $storage); self::assertEquals($assertName, $storage); } @@ -193,15 +172,15 @@ class StorageManagerTest extends DatabaseTest * * @dataProvider dataStorages */ - public function testIsValidBackend($name, $assert, $assertName, $userBackend) + public function testIsValidBackend($name, $valid, $interface, $assert, $assertName) { - $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest); + $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n); // true in every of the backends self::assertEquals(!empty($assertName), $storageManager->isValidBackend($name)); - // if userBackend is set to true, filter out e.g. SystemRessource - self::assertEquals($userBackend, $storageManager->isValidBackend($name, true)); + // if it's a IWritableStorage, the valid backend should return true, otherwise false + self::assertEquals($interface === Storage\IWritableStorage::class, $storageManager->isValidBackend($name, StorageManager::DEFAULT_BACKENDS)); } /** @@ -209,7 +188,7 @@ class StorageManagerTest extends DatabaseTest */ public function testListBackends() { - $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest); + $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n); self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends()); } @@ -219,36 +198,35 @@ class StorageManagerTest extends DatabaseTest * * @dataProvider dataStorages */ - public function testGetBackend($name, $assert, $assertName, $userBackend) + public function testGetBackend($name, $valid, $interface, $assert, $assertName) { - $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest); - - self::assertNull($storageManager->getBackend()); - - if ($userBackend) { - $storageManager->setBackend($name); - - self::assertInstanceOf($assert, $storageManager->getBackend()); + if ($interface !== Storage\IWritableStorage::class) { + static::markTestSkipped('only works for IWritableStorage'); } + + $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n); + + $selBackend = $storageManager->getWritableStorageByName($name); + $storageManager->setBackend($selBackend); + + self::assertInstanceOf($assert, $storageManager->getBackend()); } /** * Test the method getBackend() with a pre-configured backend * * @dataProvider dataStorages - * @dataProvider dataLegacyBackends */ - public function testPresetBackend($name, $assert, $assertName, $userBackend) + public function testPresetBackend($name, $valid, $interface, $assert, $assertName) { $this->config->set('storage', 'name', $name); - - $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest); - - if ($userBackend) { - self::assertInstanceOf($assert, $storageManager->getBackend()); - } else { - self::assertNull($storageManager->getBackend()); + if ($interface !== Storage\IWritableStorage::class) { + $this->expectException(Storage\InvalidClassStorageException::class); } + + $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n); + + self::assertInstanceOf($assert, $storageManager->getBackend()); } /** @@ -261,38 +239,64 @@ class StorageManagerTest extends DatabaseTest public function testRegisterUnregisterBackends() { /// @todo Remove dice once "Hook" is dynamic and mockable - $dice = (new Dice()) + $dice = (new Dice()) ->addRules(include __DIR__ . '/../../../static/dependencies.config.php') ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true]) ->addRule(ISession::class, ['instanceOf' => Session\Memory::class, 'shared' => true, 'call' => null]); DI::init($dice); - $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest); + $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n); self::assertTrue($storageManager->register(SampleStorageBackend::class)); self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [ - SampleStorageBackend::getName() => SampleStorageBackend::class, + SampleStorageBackend::getName(), ]), $storageManager->listBackends()); self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [ - SampleStorageBackend::getName() => SampleStorageBackend::class, + SampleStorageBackend::getName() + ]), $this->config->get('storage', 'backends')); + + self::assertTrue($storageManager->unregister(SampleStorageBackend::class)); + self::assertEquals(StorageManager::DEFAULT_BACKENDS, $this->config->get('storage', 'backends')); + self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends()); + } + + /** + * tests that an active backend cannot get unregistered + */ + public function testUnregisterActiveBackend() + { + /// @todo Remove dice once "Hook" is dynamic and mockable + $dice = (new Dice()) + ->addRules(include __DIR__ . '/../../../static/dependencies.config.php') + ->addRule(Database::class, ['instanceOf' => StaticDatabase::class, 'shared' => true]) + ->addRule(ISession::class, ['instanceOf' => Session\Memory::class, 'shared' => true, 'call' => null]); + DI::init($dice); + + $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n); + + self::assertTrue($storageManager->register(SampleStorageBackend::class)); + + self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [ + SampleStorageBackend::getName(), + ]), $storageManager->listBackends()); + self::assertEquals(array_merge(StorageManager::DEFAULT_BACKENDS, [ + SampleStorageBackend::getName() ]), $this->config->get('storage', 'backends')); // inline call to register own class as hook (testing purpose only) SampleStorageBackend::registerHook(); Hook::loadHooks(); - self::assertTrue($storageManager->setBackend(SampleStorageBackend::NAME)); + self::assertTrue($storageManager->setBackend($storageManager->getWritableStorageByName(SampleStorageBackend::NAME))); self::assertEquals(SampleStorageBackend::NAME, $this->config->get('storage', 'name')); self::assertInstanceOf(SampleStorageBackend::class, $storageManager->getBackend()); - self::assertTrue($storageManager->unregister(SampleStorageBackend::class)); - self::assertEquals(StorageManager::DEFAULT_BACKENDS, $this->config->get('storage', 'backends')); - self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends()); + self::expectException(Storage\StorageException::class); + self::expectExceptionMessage('Cannot unregister Sample Storage, because it\'s currently active.'); - self::assertNull($storageManager->getBackend()); - self::assertNull($this->config->get('storage', 'name')); + $storageManager->unregister(SampleStorageBackend::class); } /** @@ -300,26 +304,25 @@ class StorageManagerTest extends DatabaseTest * * @dataProvider dataStorages */ - public function testMoveStorage($name, $assert, $assertName, $userBackend) + public function testMoveStorage($name, $valid, $interface, $assert, $assertName) { - if (!$userBackend) { + if ($interface !== Storage\IWritableStorage::class) { self::markTestSkipped("No user backend"); } $this->loadFixture(__DIR__ . '/../../datasets/storage/database.fixture.php', $this->dba); - $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest); - $storage = $storageManager->getByName($name); + $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n); + $storage = $storageManager->getWritableStorageByName($name); $storageManager->move($storage); $photos = $this->dba->select('photo', ['backend-ref', 'backend-class', 'id', 'data']); while ($photo = $this->dba->fetch($photos)) { - self::assertEmpty($photo['data']); $storage = $storageManager->getByName($photo['backend-class']); - $data = $storage->get($photo['backend-ref']); + $data = $storage->get($photo['backend-ref']); self::assertNotEmpty($data); } @@ -328,13 +331,13 @@ class StorageManagerTest extends DatabaseTest /** * Test moving data to a WRONG storage */ - public function testMoveStorageWrong() + public function testWrongWritableStorage() { - $this->expectExceptionMessage("Can't move to storage backend 'SystemResource'"); - $this->expectException(StorageException::class); + $this->expectException(Storage\InvalidClassStorageException::class); + $this->expectExceptionMessage('Backend SystemResource is not valid'); - $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n, $this->httpRequest); - $storage = $storageManager->getByName(Storage\SystemResource::getName()); + $storageManager = new StorageManager($this->dba, $this->config, $this->logger, $this->l10n); + $storage = $storageManager->getWritableStorageByName(Storage\SystemResource::getName()); $storageManager->move($storage); } } diff --git a/tests/src/Model/Storage/DatabaseStorageTest.php b/tests/src/Model/Storage/DatabaseStorageTest.php index 21d6e18652..d7b810c1f8 100644 --- a/tests/src/Model/Storage/DatabaseStorageTest.php +++ b/tests/src/Model/Storage/DatabaseStorageTest.php @@ -21,16 +21,14 @@ namespace Friendica\Test\src\Model\Storage; -use Friendica\Core\L10n; use Friendica\Factory\ConfigFactory; use Friendica\Model\Storage\Database; -use Friendica\Model\Storage\IStorage; +use Friendica\Model\Storage\IWritableStorage; use Friendica\Test\DatabaseTestTrait; use Friendica\Test\Util\Database\StaticDatabase; use Friendica\Test\Util\VFSTrait; use Friendica\Util\ConfigFileLoader; use Friendica\Util\Profiler; -use Mockery\MockInterface; use Psr\Log\NullLogger; class DatabaseStorageTest extends StorageTest @@ -62,13 +60,10 @@ class DatabaseStorageTest extends StorageTest $dba = new StaticDatabase($configCache, $profiler, $logger); - /** @var MockInterface|L10n $l10n */ - $l10n = \Mockery::mock(L10n::class)->makePartial(); - - return new Database($dba, $logger, $l10n); + return new Database($dba); } - protected function assertOption(IStorage $storage) + protected function assertOption(IWritableStorage $storage) { self::assertEmpty($storage->getOptions()); } diff --git a/tests/src/Model/Storage/FilesystemStorageTest.php b/tests/src/Model/Storage/FilesystemStorageTest.php index ebe29f0c4c..45a7264163 100644 --- a/tests/src/Model/Storage/FilesystemStorageTest.php +++ b/tests/src/Model/Storage/FilesystemStorageTest.php @@ -24,13 +24,12 @@ namespace Friendica\Test\src\Model\Storage; use Friendica\Core\Config\IConfig; use Friendica\Core\L10n; use Friendica\Model\Storage\Filesystem; -use Friendica\Model\Storage\IStorage; +use Friendica\Model\Storage\IWritableStorage; use Friendica\Model\Storage\StorageException; use Friendica\Test\Util\VFSTrait; use Friendica\Util\Profiler; use Mockery\MockInterface; use org\bovigo\vfs\vfsStream; -use Psr\Log\NullLogger; class FilesystemStorageTest extends StorageTest { @@ -50,7 +49,6 @@ class FilesystemStorageTest extends StorageTest protected function getInstance() { - $logger = new NullLogger(); $profiler = \Mockery::mock(Profiler::class); $profiler->shouldReceive('startRecording'); $profiler->shouldReceive('stopRecording'); @@ -63,10 +61,10 @@ class FilesystemStorageTest extends StorageTest ->with('storage', 'filesystem_path', Filesystem::DEFAULT_BASE_FOLDER) ->andReturn($this->root->getChild('storage')->url()); - return new Filesystem($this->config, $logger, $l10n); + return new Filesystem($this->config, $l10n); } - protected function assertOption(IStorage $storage) + protected function assertOption(IWritableStorage $storage) { self::assertEquals([ 'storagepath' => [ diff --git a/tests/src/Model/Storage/StorageTest.php b/tests/src/Model/Storage/StorageTest.php index d978f013d9..340aee8bfd 100644 --- a/tests/src/Model/Storage/StorageTest.php +++ b/tests/src/Model/Storage/StorageTest.php @@ -21,15 +21,17 @@ namespace Friendica\Test\src\Model\Storage; +use Friendica\Model\Storage\IWritableStorage; use Friendica\Model\Storage\IStorage; +use Friendica\Model\Storage\ReferenceStorageException; use Friendica\Test\MockedTest; abstract class StorageTest extends MockedTest { - /** @return IStorage */ + /** @return IWritableStorage */ abstract protected function getInstance(); - abstract protected function assertOption(IStorage $storage); + abstract protected function assertOption(IWritableStorage $storage); /** * Test if the instance is "really" implementing the interface @@ -62,7 +64,7 @@ abstract class StorageTest extends MockedTest self::assertEquals('data12345', $instance->get($ref)); - self::assertTrue($instance->delete($ref)); + $instance->delete($ref); } /** @@ -70,10 +72,11 @@ abstract class StorageTest extends MockedTest */ public function testInvalidDelete() { + self::expectException(ReferenceStorageException::class); + $instance = $this->getInstance(); - // Even deleting not existing references should return "true" - self::assertTrue($instance->delete(-1234456)); + $instance->delete(-1234456); } /** @@ -81,10 +84,11 @@ abstract class StorageTest extends MockedTest */ public function testInvalidGet() { + self::expectException(ReferenceStorageException::class); + $instance = $this->getInstance(); - // Invalid references return an empty string - self::assertEmpty($instance->get(-123456)); + $instance->get(-123456); } /** diff --git a/update.php b/update.php index 842fefca88..536610a00e 100644 --- a/update.php +++ b/update.php @@ -981,3 +981,20 @@ function update_1429() return Update::SUCCESS; } + +function update_1434() +{ + $name = DI::config()->get('storage', 'name'); + + // in case of an empty config, set "Database" as default storage backend + if (empty($name)) { + DI::config()->set('storage', 'name', Storage\Database::getName()); + } + + // In case of a Using deprecated storage class value, set the right name for it + if (stristr($name, 'Friendica\Model\Storage\\')) { + DI::config()->set('storage', 'name', substr($name, 24)); + } + + return Update::SUCCESS; +} diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po index f7fb3aa405..197981f7c2 100644 --- a/view/lang/C/messages.po +++ b/view/lang/C/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 2021.09-dev\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-08-17 22:57+0000\n" +"POT-Creation-Date: 2021-08-17 08:39+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -40,10 +40,10 @@ msgstr "" #: include/api.php:4437 mod/photos.php:86 mod/photos.php:195 mod/photos.php:623 #: mod/photos.php:1031 mod/photos.php:1048 mod/photos.php:1594 #: src/Model/User.php:1112 src/Model/User.php:1120 src/Model/User.php:1128 -#: src/Module/Settings/Profile/Photo/Crop.php:97 -#: src/Module/Settings/Profile/Photo/Crop.php:113 -#: src/Module/Settings/Profile/Photo/Crop.php:129 -#: src/Module/Settings/Profile/Photo/Crop.php:175 +#: src/Module/Settings/Profile/Photo/Crop.php:101 +#: src/Module/Settings/Profile/Photo/Crop.php:117 +#: src/Module/Settings/Profile/Photo/Crop.php:133 +#: src/Module/Settings/Profile/Photo/Crop.php:179 #: src/Module/Settings/Profile/Photo/Index.php:95 #: src/Module/Settings/Profile/Photo/Index.php:101 msgid "Profile Photos" @@ -851,7 +851,7 @@ msgstr "" #: src/Module/Search/Directory.php:38 src/Module/Settings/Delegation.php:42 #: src/Module/Settings/Delegation.php:70 src/Module/Settings/Display.php:43 #: src/Module/Settings/Display.php:121 -#: src/Module/Settings/Profile/Photo/Crop.php:154 +#: src/Module/Settings/Profile/Photo/Crop.php:158 #: src/Module/Settings/Profile/Photo/Index.php:112 #: src/Module/Settings/UserExport.php:58 src/Module/Settings/UserExport.php:93 #: src/Module/Settings/UserExport.php:199 @@ -969,7 +969,7 @@ msgid "Edit post" msgstr "" #: mod/editpost.php:91 mod/notes.php:56 src/Content/Text/HTML.php:885 -#: src/Module/Admin/Storage.php:120 src/Module/Filer/SaveTag.php:69 +#: src/Module/Admin/Storage.php:133 src/Module/Filer/SaveTag.php:69 msgid "Save" msgstr "" @@ -2716,7 +2716,7 @@ msgstr "" msgid "File upload failed." msgstr "" -#: mod/wall_upload.php:233 src/Model/Photo.php:1002 +#: mod/wall_upload.php:233 src/Model/Photo.php:1013 msgid "Wall Photos" msgstr "" @@ -3679,29 +3679,29 @@ msgstr "" msgid "Connectors" msgstr "" -#: src/Core/Installer.php:179 +#: src/Core/Installer.php:183 msgid "" "The database configuration file \"config/local.config.php\" could not be " "written. Please use the enclosed text to create a configuration file in your " "web server root." msgstr "" -#: src/Core/Installer.php:198 +#: src/Core/Installer.php:202 msgid "" "You may need to import the file \"database.sql\" manually using phpmyadmin " "or mysql." msgstr "" -#: src/Core/Installer.php:199 src/Module/Install.php:206 +#: src/Core/Installer.php:203 src/Module/Install.php:206 #: src/Module/Install.php:365 msgid "Please see the file \"doc/INSTALL.md\"." msgstr "" -#: src/Core/Installer.php:260 +#: src/Core/Installer.php:264 msgid "Could not find a command line version of PHP in the web server PATH." msgstr "" -#: src/Core/Installer.php:261 +#: src/Core/Installer.php:265 msgid "" "If you don't have a command line version of PHP installed on your server, " "you will not be able to run the background processing. See 'Setup the worker'" msgstr "" -#: src/Core/Installer.php:266 +#: src/Core/Installer.php:270 msgid "PHP executable path" msgstr "" -#: src/Core/Installer.php:266 +#: src/Core/Installer.php:270 msgid "" "Enter full path to php executable. You can leave this blank to continue the " "installation." msgstr "" -#: src/Core/Installer.php:271 +#: src/Core/Installer.php:275 msgid "Command line PHP" msgstr "" -#: src/Core/Installer.php:280 +#: src/Core/Installer.php:284 msgid "PHP executable is not the php cli binary (could be cgi-fgci version)" msgstr "" -#: src/Core/Installer.php:281 +#: src/Core/Installer.php:285 msgid "Found PHP version: " msgstr "" -#: src/Core/Installer.php:283 +#: src/Core/Installer.php:287 msgid "PHP cli binary" msgstr "" -#: src/Core/Installer.php:296 +#: src/Core/Installer.php:300 msgid "" "The command line version of PHP on your system does not have " "\"register_argc_argv\" enabled." msgstr "" -#: src/Core/Installer.php:297 +#: src/Core/Installer.php:301 msgid "This is required for message delivery to work." msgstr "" -#: src/Core/Installer.php:302 +#: src/Core/Installer.php:306 msgid "PHP register_argc_argv" msgstr "" -#: src/Core/Installer.php:334 +#: src/Core/Installer.php:338 msgid "" "Error: the \"openssl_pkey_new\" function on this system is not able to " "generate encryption keys" msgstr "" -#: src/Core/Installer.php:335 +#: src/Core/Installer.php:339 msgid "" "If running under Windows, please see \"http://www.php.net/manual/en/openssl." "installation.php\"." msgstr "" -#: src/Core/Installer.php:338 +#: src/Core/Installer.php:342 msgid "Generate encryption keys" msgstr "" -#: src/Core/Installer.php:390 +#: src/Core/Installer.php:394 msgid "" "Error: Apache webserver mod-rewrite module is required but not installed." msgstr "" -#: src/Core/Installer.php:395 +#: src/Core/Installer.php:399 msgid "Apache mod_rewrite module" msgstr "" -#: src/Core/Installer.php:401 +#: src/Core/Installer.php:405 msgid "Error: PDO or MySQLi PHP module required but not installed." msgstr "" -#: src/Core/Installer.php:406 +#: src/Core/Installer.php:410 msgid "Error: The MySQL driver for PDO is not installed." msgstr "" -#: src/Core/Installer.php:410 +#: src/Core/Installer.php:414 msgid "PDO or MySQLi PHP module" msgstr "" -#: src/Core/Installer.php:418 +#: src/Core/Installer.php:422 msgid "Error, XML PHP module required but not installed." msgstr "" -#: src/Core/Installer.php:422 +#: src/Core/Installer.php:426 msgid "XML PHP module" msgstr "" -#: src/Core/Installer.php:425 +#: src/Core/Installer.php:429 msgid "libCurl PHP module" msgstr "" -#: src/Core/Installer.php:426 +#: src/Core/Installer.php:430 msgid "Error: libCURL PHP module required but not installed." msgstr "" -#: src/Core/Installer.php:432 +#: src/Core/Installer.php:436 msgid "GD graphics PHP module" msgstr "" -#: src/Core/Installer.php:433 +#: src/Core/Installer.php:437 msgid "" "Error: GD graphics PHP module with JPEG support required but not installed." msgstr "" -#: src/Core/Installer.php:439 +#: src/Core/Installer.php:443 msgid "OpenSSL PHP module" msgstr "" -#: src/Core/Installer.php:440 +#: src/Core/Installer.php:444 msgid "Error: openssl PHP module required but not installed." msgstr "" -#: src/Core/Installer.php:446 +#: src/Core/Installer.php:450 msgid "mb_string PHP module" msgstr "" -#: src/Core/Installer.php:447 +#: src/Core/Installer.php:451 msgid "Error: mb_string PHP module required but not installed." msgstr "" -#: src/Core/Installer.php:453 +#: src/Core/Installer.php:457 msgid "iconv PHP module" msgstr "" -#: src/Core/Installer.php:454 +#: src/Core/Installer.php:458 msgid "Error: iconv PHP module required but not installed." msgstr "" -#: src/Core/Installer.php:460 +#: src/Core/Installer.php:464 msgid "POSIX PHP module" msgstr "" -#: src/Core/Installer.php:461 +#: src/Core/Installer.php:465 msgid "Error: POSIX PHP module required but not installed." msgstr "" -#: src/Core/Installer.php:467 +#: src/Core/Installer.php:471 msgid "Program execution functions" msgstr "" -#: src/Core/Installer.php:468 +#: src/Core/Installer.php:472 msgid "" "Error: Program execution functions (proc_open) required but not enabled." msgstr "" -#: src/Core/Installer.php:474 +#: src/Core/Installer.php:478 msgid "JSON PHP module" msgstr "" -#: src/Core/Installer.php:475 +#: src/Core/Installer.php:479 msgid "Error: JSON PHP module required but not installed." msgstr "" -#: src/Core/Installer.php:481 +#: src/Core/Installer.php:485 msgid "File Information PHP module" msgstr "" -#: src/Core/Installer.php:482 +#: src/Core/Installer.php:486 msgid "Error: File Information PHP module required but not installed." msgstr "" -#: src/Core/Installer.php:505 +#: src/Core/Installer.php:509 msgid "" "The web installer needs to be able to create a file called \"local.config.php" "\" in the \"config\" folder of your web server and it is unable to do so." msgstr "" -#: src/Core/Installer.php:506 +#: src/Core/Installer.php:510 msgid "" "This is most often a permission setting, as the web server may not be able " "to write files in your folder - even if you can." msgstr "" -#: src/Core/Installer.php:507 +#: src/Core/Installer.php:511 msgid "" "At the end of this procedure, we will give you a text to save in a file " "named local.config.php in your Friendica \"config\" folder." msgstr "" -#: src/Core/Installer.php:508 +#: src/Core/Installer.php:512 msgid "" "You can alternatively skip this procedure and perform a manual installation. " "Please see the file \"doc/INSTALL.md\" for instructions." msgstr "" -#: src/Core/Installer.php:511 +#: src/Core/Installer.php:515 msgid "config/local.config.php is writable" msgstr "" -#: src/Core/Installer.php:531 +#: src/Core/Installer.php:535 msgid "" "Friendica uses the Smarty3 template engine to render its web views. Smarty3 " "compiles templates to PHP to speed up rendering." msgstr "" -#: src/Core/Installer.php:532 +#: src/Core/Installer.php:536 msgid "" "In order to store these compiled templates, the web server needs to have " "write access to the directory view/smarty3/ under the Friendica top level " "folder." msgstr "" -#: src/Core/Installer.php:533 +#: src/Core/Installer.php:537 msgid "" "Please ensure that the user that your web server runs as (e.g. www-data) has " "write access to this folder." msgstr "" -#: src/Core/Installer.php:534 +#: src/Core/Installer.php:538 msgid "" "Note: as a security measure, you should give the web server write access to " "view/smarty3/ only--not the template files (.tpl) that it contains." msgstr "" -#: src/Core/Installer.php:537 +#: src/Core/Installer.php:541 msgid "view/smarty3 is writable" msgstr "" -#: src/Core/Installer.php:565 +#: src/Core/Installer.php:569 msgid "" "Url rewrite in .htaccess seems not working. Make sure you copied .htaccess-" "dist to .htaccess." msgstr "" -#: src/Core/Installer.php:566 +#: src/Core/Installer.php:570 msgid "" "In some circumstances (like running inside containers), you can skip this " "error." msgstr "" -#: src/Core/Installer.php:568 +#: src/Core/Installer.php:572 msgid "Error message from Curl when fetching" msgstr "" -#: src/Core/Installer.php:574 +#: src/Core/Installer.php:578 msgid "Url rewrite is working" msgstr "" -#: src/Core/Installer.php:603 +#: src/Core/Installer.php:607 +msgid "" +"The detection of TLS to secure the communication between the browser and the " +"new Friendica server failed." +msgstr "" + +#: src/Core/Installer.php:608 +msgid "" +"It is highly encouraged to use Friendica only over a secure connection as " +"sensitive information like passwords will be transmitted." +msgstr "" + +#: src/Core/Installer.php:609 +msgid "Please ensure that the connection to the server is secure." +msgstr "" + +#: src/Core/Installer.php:610 +msgid "No TLS detected" +msgstr "" + +#: src/Core/Installer.php:612 +msgid "TLS detected" +msgstr "" + +#: src/Core/Installer.php:639 msgid "ImageMagick PHP extension is not installed" msgstr "" -#: src/Core/Installer.php:605 +#: src/Core/Installer.php:641 msgid "ImageMagick PHP extension is installed" msgstr "" -#: src/Core/Installer.php:607 +#: src/Core/Installer.php:643 msgid "ImageMagick supports GIF" msgstr "" -#: src/Core/Installer.php:629 +#: src/Core/Installer.php:665 msgid "Database already in use." msgstr "" -#: src/Core/Installer.php:634 +#: src/Core/Installer.php:670 msgid "Could not connect to database." msgstr "" @@ -4689,39 +4713,17 @@ msgstr "" msgid "OpenWebAuth: %1$s welcomes %2$s" msgstr "" -#: src/Model/Storage/Database.php:74 -#, php-format -msgid "Database storage failed to update %s" -msgstr "" - -#: src/Model/Storage/Database.php:82 -msgid "Database storage failed to insert data" -msgstr "" - -#: src/Model/Storage/Filesystem.php:100 -#, php-format -msgid "" -"Filesystem storage failed to create \"%s\". Check you write permissions." -msgstr "" - -#: src/Model/Storage/Filesystem.php:148 -#, php-format -msgid "" -"Filesystem storage failed to save data to \"%s\". Check your write " -"permissions" -msgstr "" - -#: src/Model/Storage/Filesystem.php:176 +#: src/Model/Storage/Filesystem.php:187 msgid "Storage base path" msgstr "" -#: src/Model/Storage/Filesystem.php:178 +#: src/Model/Storage/Filesystem.php:189 msgid "" "Folder where uploaded files are saved. For maximum security, This should be " "a path outside web server folder tree" msgstr "" -#: src/Model/Storage/Filesystem.php:191 +#: src/Model/Storage/Filesystem.php:202 msgid "Enter a valid existing folder" msgstr "" @@ -5004,7 +5006,7 @@ msgstr "" #: src/Module/Admin/Blocklist/Server.php:88 src/Module/Admin/Federation.php:159 #: src/Module/Admin/Item/Delete.php:65 src/Module/Admin/Logs/Settings.php:80 #: src/Module/Admin/Logs/View.php:64 src/Module/Admin/Queue.php:72 -#: src/Module/Admin/Site.php:498 src/Module/Admin/Storage.php:118 +#: src/Module/Admin/Site.php:498 src/Module/Admin/Storage.php:131 #: src/Module/Admin/Summary.php:232 src/Module/Admin/Themes/Details.php:90 #: src/Module/Admin/Themes/Index.php:111 src/Module/Admin/Tos.php:58 #: src/Module/Admin/Users/Active.php:136 src/Module/Admin/Users/Blocked.php:137 @@ -6463,31 +6465,41 @@ msgstr "" msgid "Start Relocation" msgstr "" -#: src/Module/Admin/Storage.php:72 +#: src/Module/Admin/Storage.php:45 +#, php-format +msgid "Storage backend, %s is invalid." +msgstr "" + +#: src/Module/Admin/Storage.php:71 +#, php-format +msgid "Storage backend %s error: %s" +msgstr "" + +#: src/Module/Admin/Storage.php:82 src/Module/Admin/Storage.php:85 msgid "Invalid storage backend setting value." msgstr "" -#: src/Module/Admin/Storage.php:119 src/Module/BaseAdmin.php:91 +#: src/Module/Admin/Storage.php:132 src/Module/BaseAdmin.php:91 msgid "Storage" msgstr "" -#: src/Module/Admin/Storage.php:121 +#: src/Module/Admin/Storage.php:134 msgid "Save & Activate" msgstr "" -#: src/Module/Admin/Storage.php:122 +#: src/Module/Admin/Storage.php:135 msgid "Activate" msgstr "" -#: src/Module/Admin/Storage.php:123 +#: src/Module/Admin/Storage.php:136 msgid "Save & Reload" msgstr "" -#: src/Module/Admin/Storage.php:124 +#: src/Module/Admin/Storage.php:137 msgid "This backend doesn't have custom settings" msgstr "" -#: src/Module/Admin/Storage.php:127 +#: src/Module/Admin/Storage.php:140 msgid "Database (legacy)" msgstr "" @@ -8700,17 +8712,17 @@ msgstr "" msgid "Visible to:" msgstr "" -#: src/Module/Photo.php:94 +#: src/Module/Photo.php:98 #, php-format msgid "The Photo with id %s is not available." msgstr "" -#: src/Module/Photo.php:124 +#: src/Module/Photo.php:132 #, php-format msgid "Invalid external resource with url %s." msgstr "" -#: src/Module/Photo.php:126 +#: src/Module/Photo.php:134 #, php-format msgid "Invalid photo with id %s." msgstr "" @@ -9465,42 +9477,42 @@ msgid "" "contacts or the Friendica contacts in the selected groups.

" msgstr "" -#: src/Module/Settings/Profile/Photo/Crop.php:102 -#: src/Module/Settings/Profile/Photo/Crop.php:118 -#: src/Module/Settings/Profile/Photo/Crop.php:134 +#: src/Module/Settings/Profile/Photo/Crop.php:106 +#: src/Module/Settings/Profile/Photo/Crop.php:122 +#: src/Module/Settings/Profile/Photo/Crop.php:138 #: src/Module/Settings/Profile/Photo/Index.php:102 #, php-format msgid "Image size reduction [%s] failed." msgstr "" -#: src/Module/Settings/Profile/Photo/Crop.php:139 +#: src/Module/Settings/Profile/Photo/Crop.php:143 msgid "" "Shift-reload the page or clear browser cache if the new photo does not " "display immediately." msgstr "" -#: src/Module/Settings/Profile/Photo/Crop.php:144 +#: src/Module/Settings/Profile/Photo/Crop.php:148 msgid "Unable to process image" msgstr "" -#: src/Module/Settings/Profile/Photo/Crop.php:163 +#: src/Module/Settings/Profile/Photo/Crop.php:167 msgid "Photo not found." msgstr "" -#: src/Module/Settings/Profile/Photo/Crop.php:185 +#: src/Module/Settings/Profile/Photo/Crop.php:189 msgid "Profile picture successfully updated." msgstr "" -#: src/Module/Settings/Profile/Photo/Crop.php:208 -#: src/Module/Settings/Profile/Photo/Crop.php:212 +#: src/Module/Settings/Profile/Photo/Crop.php:215 +#: src/Module/Settings/Profile/Photo/Crop.php:219 msgid "Crop Image" msgstr "" -#: src/Module/Settings/Profile/Photo/Crop.php:209 +#: src/Module/Settings/Profile/Photo/Crop.php:216 msgid "Please adjust the image cropping for optimum viewing." msgstr "" -#: src/Module/Settings/Profile/Photo/Crop.php:211 +#: src/Module/Settings/Profile/Photo/Crop.php:218 msgid "Use Image As Is" msgstr ""