diff --git a/.woodpecker/.phpunit.yml b/.woodpecker/.phpunit.yml index a2f8f45d7..8f89e4b5b 100644 --- a/.woodpecker/.phpunit.yml +++ b/.woodpecker/.phpunit.yml @@ -7,9 +7,9 @@ matrix: - PHP_MAJOR_VERSION: 8.0 PHP_VERSION: 8.0.29 - PHP_MAJOR_VERSION: 8.1 - PHP_VERSION: 8.1.20 + PHP_VERSION: 8.1.21 - PHP_MAJOR_VERSION: 8.2 - PHP_VERSION: 8.2.7 + PHP_VERSION: 8.2.8 # This forces PHP Unit executions at the "opensocial" labeled location (because of much more power...) labels: diff --git a/bin/auth_ejabberd.php b/bin/auth_ejabberd.php index 40e7d3b97..b923009e6 100755 --- a/bin/auth_ejabberd.php +++ b/bin/auth_ejabberd.php @@ -58,6 +58,7 @@ if (php_sapi_name() !== 'cli') { use Dice\Dice; use Friendica\App\Mode; +use Friendica\Core\Logger\Capabilities\LogChannel; use Friendica\Security\ExAuth; use Psr\Log\LoggerInterface; @@ -78,7 +79,10 @@ chdir($directory); require dirname(__DIR__) . '/vendor/autoload.php'; $dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.php'); -$dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['auth_ejabberd']]); +/** @var \Friendica\Core\Addon\Capabilities\ICanLoadAddons $addonLoader */ +$addonLoader = $dice->create(\Friendica\Core\Addon\Capabilities\ICanLoadAddons::class); +$dice = $dice->addRules($addonLoader->getActiveAddonConfig('dependencies')); +$dice = $dice->addRule(LoggerInterface::class,['constructParams' => [LogChannel::AUTH_JABBERED]]); \Friendica\DI::init($dice); \Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class)); diff --git a/bin/console.php b/bin/console.php index e1bc6b498..b2cde1082 100755 --- a/bin/console.php +++ b/bin/console.php @@ -26,13 +26,17 @@ if (php_sapi_name() !== 'cli') { } use Dice\Dice; +use Friendica\Core\Logger\Capabilities\LogChannel; use Friendica\DI; use Psr\Log\LoggerInterface; require dirname(__DIR__) . '/vendor/autoload.php'; $dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.php'); -$dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['console']]); +/** @var \Friendica\Core\Addon\Capabilities\ICanLoadAddons $addonLoader */ +$addonLoader = $dice->create(\Friendica\Core\Addon\Capabilities\ICanLoadAddons::class); +$dice = $dice->addRules($addonLoader->getActiveAddonConfig('dependencies')); +$dice = $dice->addRule(LoggerInterface::class, ['constructParams' => [LogChannel::CONSOLE]]); /// @fixme Necessary until Hooks inside the Logger can get loaded without the DI-class DI::init($dice); diff --git a/bin/daemon.php b/bin/daemon.php index e550aea89..11bad2b11 100755 --- a/bin/daemon.php +++ b/bin/daemon.php @@ -60,7 +60,10 @@ if (!file_exists('index.php') && (sizeof($_SERVER['argv']) != 0)) { require dirname(__DIR__) . '/vendor/autoload.php'; $dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.php'); -$dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['daemon']]); +/** @var \Friendica\Core\Addon\Capabilities\ICanLoadAddons $addonLoader */ +$addonLoader = $dice->create(\Friendica\Core\Addon\Capabilities\ICanLoadAddons::class); +$dice = $dice->addRules($addonLoader->getActiveAddonConfig('dependencies')); +$dice = $dice->addRule(LoggerInterface::class, ['constructParams' => [Logger\Capabilities\LogChannel::DAEMON]]); DI::init($dice); \Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class)); diff --git a/bin/worker.php b/bin/worker.php index a74213248..42a8f533d 100755 --- a/bin/worker.php +++ b/bin/worker.php @@ -29,6 +29,7 @@ if (php_sapi_name() !== 'cli') { use Dice\Dice; use Friendica\App; use Friendica\App\Mode; +use Friendica\Core\Logger\Capabilities\LogChannel; use Friendica\Core\Update; use Friendica\Core\Worker; use Friendica\DI; @@ -54,7 +55,10 @@ if (!file_exists("index.php") && (sizeof($_SERVER["argv"]) != 0)) { require dirname(__DIR__) . '/vendor/autoload.php'; $dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.php'); -$dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['worker']]); +/** @var \Friendica\Core\Addon\Capabilities\ICanLoadAddons $addonLoader */ +$addonLoader = $dice->create(\Friendica\Core\Addon\Capabilities\ICanLoadAddons::class); +$dice = $dice->addRules($addonLoader->getActiveAddonConfig('dependencies')); +$dice = $dice->addRule(LoggerInterface::class, ['constructParams' => [LogChannel::WORKER]]); DI::init($dice); \Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class)); diff --git a/composer.json b/composer.json index 6e0d9f984..60094d5bd 100644 --- a/composer.json +++ b/composer.json @@ -134,6 +134,7 @@ "scripts": { "test": "phpunit", "lint": "find . -name \\*.php -not -path './vendor/*' -not -path './view/asset/*' -print0 | xargs -0 -n1 php -l", + "docker:translate": "docker run --rm -v $PWD:/data -w /data friendicaci/transifex bin/run_xgettext.sh", "cs:install": "@composer install --working-dir=bin/dev/php-cs-fixer", "cs:check": [ "@cs:install", diff --git a/doc/StrategyHooks.md b/doc/StrategyHooks.md new file mode 100644 index 000000000..39fc1bd8d --- /dev/null +++ b/doc/StrategyHooks.md @@ -0,0 +1,89 @@ +Friendica strategy Hooks +=========================================== + +* [Home](help) + +## Strategy hooks + +This type of hook is based on the [Strategy Design Pattern](https://refactoring.guru/design-patterns/strategy). + +A strategy class defines a possible implementation of a given interface based on a unique name. +Every name is possible as long as it's unique and not `null`. +Using an empty name (`''`) is possible as well and should be used as the "default" implementation. +To register a strategy, use the [`ICanRegisterInstance`](../src/Core/Hooks/Capabilities/ICanRegisterInstances.php) interface. + +After registration, a caller can automatically create this instance with the [`ICanCreateInstances`](../src/Core/Hooks/Capabilities/ICanCreateInstances.php) interface and the chosen name. + +This is useful in case there are different, possible implementations for the same purpose, like for logging, locking, caching, ... + +Normally, a config entry is used to choose the right implementation at runtime. +And if no config entry is set, the "default" implementation should be used. + +### Example + +```php +interface ExampleInterface +{ + public function testMethod(); +} + +public class ConcreteClassA implements ExampleInterface +{ + public function testMethod() + { + echo "concrete class A"; + } +} + +public class ConcreteClassB implements ExampleInterface +{ + public function testMethod() + { + echo "concrete class B"; + } +} + +/** @var \Friendica\Core\Hooks\Capabilities\ICanRegisterStrategies $instanceRegister */ +$instanceRegister->registerStrategy(ExampleInterface::class, ConcreteClassA::class, 'A'); +$instanceRegister->registerStrategy(ExampleInterface::class, ConcreteClassB::class, 'B'); + +/** @var \Friendica\Core\Hooks\Capabilities\ICanCreateInstances $instanceManager */ +/** @var ConcreteClassA $concreteClass */ +$concreteClass = $instanceManager->create(ExampleInterface::class, 'A'); + +$concreteClass->testMethod(); +// output: +// "concrete class A"; +``` + +## hooks.config.php + +To avoid registering all strategies manually inside the code, Friendica introduced the [`hooks.config.php`](../static/hooks.config.php) file. + +There, you can register all kind of strategies in one file. + +### [`HookType::STRATEGY`](../src/Core/Hooks/Capabilities/HookType.php) + +For each given interface, a list of key-value pairs can be set, where the key is the concrete implementation class and the value is an array of unique names. + +### Example + +```php +use Friendica\Core\Hooks\Capabilities\BehavioralHookType as H; + +return [ + H::STRATEGY => [ + ExampleInterface::class => [ + ConcreteClassA::class => ['A'], + ConcreteClassB::class => ['B'], + ], + ], +]; +``` + +## Addons + +The hook logic is useful for decoupling the Friendica core logic, but its primary goal is to modularize Friendica in creating addons. + +Therefor you can either use the interfaces directly as shown above, or you can place your own `hooks.config.php` file inside a `static` directory directly under your addon core directory. +Friendica will automatically search these config files for each **activated** addon and register the given hooks. diff --git a/index.php b/index.php index 90df9c00e..ce82470b6 100644 --- a/index.php +++ b/index.php @@ -30,6 +30,9 @@ if (!file_exists(__DIR__ . '/vendor/autoload.php')) { require __DIR__ . '/vendor/autoload.php'; $dice = (new Dice())->addRules(include __DIR__ . '/static/dependencies.config.php'); +/** @var \Friendica\Core\Addon\Capabilities\ICanLoadAddons $addonLoader */ +$addonLoader = $dice->create(\Friendica\Core\Addon\Capabilities\ICanLoadAddons::class); +$dice = $dice->addRules($addonLoader->getActiveAddonConfig('dependencies')); $dice = $dice->addRule(Friendica\App\Mode::class, ['call' => [['determineRunMode', [false, $_SERVER], Dice::CHAIN_CALL]]]); \Friendica\DI::init($dice); diff --git a/src/Core/Addon/Capabilities/ICanLoadAddons.php b/src/Core/Addon/Capabilities/ICanLoadAddons.php new file mode 100644 index 000000000..9c9d1e841 --- /dev/null +++ b/src/Core/Addon/Capabilities/ICanLoadAddons.php @@ -0,0 +1,37 @@ +. + * + */ + +namespace Friendica\Core\Addon\Capabilities; + +/** + * Interface for loading Addons specific content + */ +interface ICanLoadAddons +{ + /** + * Returns a merged config array of all active addons for a given config-name + * + * @param string $configName The config-name (config-file at the static directory, like 'hooks' => '{addon}/static/hooks.config.php) + * + * @return array the merged array + */ + public function getActiveAddonConfig(string $configName): array; +} diff --git a/src/Core/Addon/Exception/AddonInvalidConfigFileException.php b/src/Core/Addon/Exception/AddonInvalidConfigFileException.php new file mode 100644 index 000000000..bf173d0a4 --- /dev/null +++ b/src/Core/Addon/Exception/AddonInvalidConfigFileException.php @@ -0,0 +1,35 @@ +. + * + */ + +namespace Friendica\Core\Addon\Exception; + +use Throwable; + +/** + * Exception in case one or more config files of the addons are invalid + */ +class AddonInvalidConfigFileException extends \RuntimeException +{ + public function __construct($message = '', $code = 0, Throwable $previous = null) + { + parent::__construct($message, 500, $previous); + } +} diff --git a/src/Core/Addon/Model/AddonLoader.php b/src/Core/Addon/Model/AddonLoader.php new file mode 100644 index 000000000..952246af0 --- /dev/null +++ b/src/Core/Addon/Model/AddonLoader.php @@ -0,0 +1,70 @@ +. + * + */ + +namespace Friendica\Core\Addon\Model; + +use Friendica\Core\Addon\Capabilities\ICanLoadAddons; +use Friendica\Core\Addon\Exception\AddonInvalidConfigFileException; +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Util\Strings; + +class AddonLoader implements ICanLoadAddons +{ + const STATIC_PATH = 'static'; + /** @var string */ + protected $basePath; + /** @var IManageConfigValues */ + protected $config; + + public function __construct(string $basePath, IManageConfigValues $config) + { + $this->basePath = $basePath; + $this->config = $config; + } + + /** {@inheritDoc} */ + public function getActiveAddonConfig(string $configName): array + { + $addons = array_keys(array_filter($this->config->get('addons') ?? [])); + $returnConfig = []; + + foreach ($addons as $addon) { + $addonName = Strings::sanitizeFilePathItem(trim($addon)); + + $configFile = $this->basePath . '/addon/' . $addonName . '/' . static::STATIC_PATH . '/' . $configName . '.config.php'; + + if (!file_exists($configFile)) { + // Addon unmodified, skipping + continue; + } + + $config = include $configFile; + + if (!is_array($config)) { + throw new AddonInvalidConfigFileException('Error loading config file ' . $configFile); + } + + $returnConfig = array_merge_recursive($returnConfig, $config); + } + + return $returnConfig; + } +} diff --git a/src/Core/Hooks/Capabilities/BehavioralHookType.php b/src/Core/Hooks/Capabilities/BehavioralHookType.php new file mode 100644 index 000000000..6336406e6 --- /dev/null +++ b/src/Core/Hooks/Capabilities/BehavioralHookType.php @@ -0,0 +1,37 @@ +. + * + */ + +namespace Friendica\Core\Hooks\Capabilities; + +/** + * An enum of hook types, based on behavioral design patterns + * @see https://refactoring.guru/design-patterns/behavioral-patterns + */ +interface BehavioralHookType +{ + /** + * Defines the key for the list of strategy-hooks. + * + * @see https://refactoring.guru/design-patterns/strategy + */ + const STRATEGY = 'strategy'; + const EVENT = 'event'; +} diff --git a/src/Core/Hooks/Capabilities/ICanCreateInstances.php b/src/Core/Hooks/Capabilities/ICanCreateInstances.php new file mode 100644 index 000000000..77d4c4b36 --- /dev/null +++ b/src/Core/Hooks/Capabilities/ICanCreateInstances.php @@ -0,0 +1,41 @@ +. + * + */ + +namespace Friendica\Core\Hooks\Capabilities; + +/** + * creates special instances for given classes + */ +interface ICanCreateInstances +{ + /** + * Returns a new instance of a given class for the corresponding name + * + * The instance will be build based on the registered strategy and the (unique) name + * + * @param string $class The fully-qualified name of the given class or interface which will get returned + * @param string $strategy An arbitrary identifier to find a concrete instance strategy. + * @param array $arguments Additional arguments, which can be passed to the constructor of "$class" at runtime + * + * @return object The concrete instance of the type "$class" + */ + public function create(string $class, string $strategy, array $arguments = []): object; +} diff --git a/src/Core/Hooks/Capabilities/ICanManageInstances.php b/src/Core/Hooks/Capabilities/ICanManageInstances.php deleted file mode 100644 index bdad1b923..000000000 --- a/src/Core/Hooks/Capabilities/ICanManageInstances.php +++ /dev/null @@ -1,81 +0,0 @@ -. - * - */ - -namespace Friendica\Core\Hooks\Capabilities; - -use Friendica\Core\Hooks\Exceptions\HookInstanceException; -use Friendica\Core\Hooks\Exceptions\HookRegisterArgumentException; - -/** - * Managing special instance and decorator treatments for classes - */ -interface ICanManageInstances -{ - /** - * Register a class(strategy) for a given interface with a unique name. - * - * @see https://refactoring.guru/design-patterns/strategy - * - * @param string $interface The interface, which the given class implements - * @param string $name An arbitrary identifier for the given class, which will be used for factories, dependency injections etc. - * @param string $class The fully-qualified given class name - * @param ?array $arguments Additional arguments, which can be passed to the constructor - * - * @return $this This interface for chain-calls - * - * @throws HookRegisterArgumentException in case the given class for the interface isn't valid or already set - */ - public function registerStrategy(string $interface, string $name, string $class, array $arguments = null): self; - - /** - * Register a new decorator for a given class or interface - * @see https://refactoring.guru/design-patterns/decorator - * - * @note Decorator attach new behaviors to classes without changing them or without letting them know about it. - * - * @param string $class The fully-qualified class or interface name, which gets decorated by a class - * @param string $decoratorClass The fully-qualified name of the class which mimics the given class or interface and adds new functionality - * @param array $arguments Additional arguments, which can be passed to the constructor of "decoratorClass" - * - * @return $this This interface for chain-calls - * - * @throws HookRegisterArgumentException in case the given class for the class or interface isn't valid - */ - public function registerDecorator(string $class, string $decoratorClass, array $arguments = []): self; - - /** - * Returns a new instance of a given class for the corresponding name - * - * The instance will be build based on the registered strategy and the (unique) name - * - * In case, there are registered decorators for this class as well, all decorators of the list will be wrapped - * around the instance before returning it - * - * @param string $class The fully-qualified name of the given class or interface which will get returned - * @param string $name An arbitrary identifier to find a concrete instance strategy. - * @param array $arguments Additional arguments, which can be passed to the constructor of "$class" at runtime - * - * @return object The concrete instance of the type "$class" - * - * @throws HookInstanceException In case the class cannot get created - */ - public function getInstance(string $class, string $name, array $arguments = []): object; -} diff --git a/src/Core/Hooks/Capabilities/ICanRegisterStrategies.php b/src/Core/Hooks/Capabilities/ICanRegisterStrategies.php new file mode 100644 index 000000000..911eb3499 --- /dev/null +++ b/src/Core/Hooks/Capabilities/ICanRegisterStrategies.php @@ -0,0 +1,46 @@ +. + * + */ + +namespace Friendica\Core\Hooks\Capabilities; + +use Friendica\Core\Hooks\Exceptions\HookRegisterArgumentException; + +/** + * Register strategies for given classes + */ +interface ICanRegisterStrategies +{ + /** + * Register a class(strategy) for a given interface with a unique name. + * + * @see https://refactoring.guru/design-patterns/strategy + * + * @param string $interface The interface, which the given class implements + * @param string $class The fully-qualified given class name + * A placeholder for dependencies is possible as well + * @param ?string $name An arbitrary identifier for the given strategy, which will be used for factories, dependency injections etc. + * + * @return $this This interface for chain-calls + * + * @throws HookRegisterArgumentException in case the given class for the interface isn't valid or already set + */ + public function registerStrategy(string $interface, string $class, ?string $name = null): self; +} diff --git a/src/Core/Logger/Exception/LoggerInvalidException.php b/src/Core/Hooks/Exceptions/HookConfigException.php similarity index 82% rename from src/Core/Logger/Exception/LoggerInvalidException.php rename to src/Core/Hooks/Exceptions/HookConfigException.php index db6ecb78c..6588787eb 100644 --- a/src/Core/Logger/Exception/LoggerInvalidException.php +++ b/src/Core/Hooks/Exceptions/HookConfigException.php @@ -19,13 +19,11 @@ * */ -namespace Friendica\Core\Logger\Exception; +namespace Friendica\Core\Hooks\Exceptions; -use Throwable; - -class LoggerInvalidException extends \RuntimeException +class HookConfigException extends \RuntimeException { - public function __construct($message = "", Throwable $previous = null) + public function __construct($message = '', \Throwable $previous = null) { parent::__construct($message, 500, $previous); } diff --git a/src/Core/Hooks/Model/DiceInstanceManager.php b/src/Core/Hooks/Model/DiceInstanceManager.php new file mode 100644 index 000000000..a8b0b540c --- /dev/null +++ b/src/Core/Hooks/Model/DiceInstanceManager.php @@ -0,0 +1,70 @@ +. + * + */ + +namespace Friendica\Core\Hooks\Model; + +use Dice\Dice; +use Friendica\Core\Hooks\Capabilities\ICanCreateInstances; +use Friendica\Core\Hooks\Capabilities\ICanRegisterStrategies; +use Friendica\Core\Hooks\Exceptions\HookInstanceException; +use Friendica\Core\Hooks\Exceptions\HookRegisterArgumentException; +use Friendica\Core\Hooks\Util\StrategiesFileManager; + +/** + * This class represents an instance register, which uses Dice for creation + * + * @see Dice + */ +class DiceInstanceManager implements ICanCreateInstances, ICanRegisterStrategies +{ + protected $instance = []; + + /** @var Dice */ + protected $dice; + + public function __construct(Dice $dice, StrategiesFileManager $strategiesFileManager) + { + $this->dice = $dice; + $strategiesFileManager->setupStrategies($this); + } + + /** {@inheritDoc} */ + public function registerStrategy(string $interface, string $class, ?string $name = null): ICanRegisterStrategies + { + if (!empty($this->instance[$interface][$name])) { + throw new HookRegisterArgumentException(sprintf('A class with the name %s is already set for the interface %s', $name, $interface)); + } + + $this->instance[$interface][$name] = $class; + + return $this; + } + + /** {@inheritDoc} */ + public function create(string $class, string $strategy, array $arguments = []): object + { + if (empty($this->instance[$class][$strategy])) { + throw new HookInstanceException(sprintf('The class with the name %s isn\'t registered for the class or interface %s', $strategy, $class)); + } + + return $this->dice->create($this->instance[$class][$strategy], $arguments); + } +} diff --git a/src/Core/Hooks/Model/InstanceManager.php b/src/Core/Hooks/Model/InstanceManager.php deleted file mode 100644 index 7bfcfa420..000000000 --- a/src/Core/Hooks/Model/InstanceManager.php +++ /dev/null @@ -1,104 +0,0 @@ -. - * - */ - -namespace Friendica\Core\Hooks\Model; - -use Dice\Dice; -use Friendica\Core\Hooks\Capabilities\IAmAStrategy; -use Friendica\Core\Hooks\Capabilities\ICanManageInstances; -use Friendica\Core\Hooks\Exceptions\HookInstanceException; -use Friendica\Core\Hooks\Exceptions\HookRegisterArgumentException; - -/** {@inheritDoc} */ -class InstanceManager implements ICanManageInstances -{ - protected $instance = []; - protected $instanceArguments = []; - protected $decorator = []; - - /** @var Dice */ - protected $dice; - - public function __construct(Dice $dice) - { - $this->dice = $dice; - } - - /** {@inheritDoc} */ - public function registerStrategy(string $interface, string $name, string $class, array $arguments = null): ICanManageInstances - { - if (!is_a($class, $interface, true)) { - throw new HookRegisterArgumentException(sprintf('%s is not a valid class for the interface %s', $class, $interface)); - } - - if (!is_a($class, IAmAStrategy::class, true)) { - throw new HookRegisterArgumentException(sprintf('%s does not inherit from the marker interface %s', $class, IAmAStrategy::class)); - } - - if (!empty($this->instance[$interface][$name])) { - throw new HookRegisterArgumentException(sprintf('A class with the name %s is already set for the interface %s', $name, $interface)); - } - - $this->instance[$interface][$name] = $class; - $this->instanceArguments[$interface][$name] = $arguments; - - return $this; - } - - /** {@inheritDoc} */ - public function registerDecorator(string $class, string $decoratorClass, array $arguments = []): ICanManageInstances - { - if (!is_a($decoratorClass, $class, true)) { - throw new HookRegisterArgumentException(sprintf('%s is not a valid substitution for the given class or interface %s', $decoratorClass, $class)); - } - - $this->decorator[$class][] = [ - 'class' => $decoratorClass, - 'arguments' => $arguments, - ]; - - return $this; - } - - /** {@inheritDoc} */ - public function getInstance(string $class, string $name, array $arguments = []): object - { - if (empty($this->instance[$class][$name])) { - throw new HookInstanceException(sprintf('The class with the name %s isn\'t registered for the class or interface %s', $name, $class)); - } - - $instance = $this->dice->create($this->instance[$class][$name], array_merge($this->instanceArguments[$class][$name] ?? [], $arguments)); - - foreach ($this->decorator[$class] ?? [] as $decorator) { - $this->dice = $this->dice->addRule($class, [ - 'instanceOf' => $decorator['class'], - 'constructParams' => empty($decorator['arguments']) ? null : $decorator['arguments'], - /// @todo maybe support call structures for hooks as well in a later stage - could make factory calls easier - 'call' => null, - 'substitutions' => [$class => $instance], - ]); - - $instance = $this->dice->create($class); - } - - return $instance; - } -} diff --git a/src/Core/Hooks/Util/StrategiesFileManager.php b/src/Core/Hooks/Util/StrategiesFileManager.php new file mode 100644 index 000000000..700c401f2 --- /dev/null +++ b/src/Core/Hooks/Util/StrategiesFileManager.php @@ -0,0 +1,95 @@ +. + * + */ + +namespace Friendica\Core\Hooks\Util; + +use Friendica\Core\Addon\Capabilities\ICanLoadAddons; +use Friendica\Core\Hooks\Capabilities\ICanRegisterStrategies; +use Friendica\Core\Hooks\Exceptions\HookConfigException; + +/** + * Manage all strategies.config.php files + */ +class StrategiesFileManager +{ + const STATIC_DIR = 'static'; + const CONFIG_NAME = 'strategies'; + + /** @var ICanLoadAddons */ + protected $addonLoader; + /** @var array */ + protected $config = []; + /** @var string */ + protected $basePath; + + public function __construct(string $basePath, ICanLoadAddons $addonLoader) + { + $this->basePath = $basePath; + $this->addonLoader = $addonLoader; + } + + /** + * Loads all kinds of hooks and registers the corresponding instances + * + * @param ICanRegisterStrategies $instanceRegister The instance register + * + * @return void + */ + public function setupStrategies(ICanRegisterStrategies $instanceRegister) + { + foreach ($this->config as $interface => $strategy) { + foreach ($strategy as $dependencyName => $names) { + if (is_array($names)) { + foreach ($names as $name) { + $instanceRegister->registerStrategy($interface, $dependencyName, $name); + } + } else { + $instanceRegister->registerStrategy($interface, $dependencyName, $names); + } + } + } + } + + /** + * Reloads all hook config files into the config cache for later usage + * + * Merges all hook configs from every addon - if present - as well + * + * @return void + */ + public function loadConfig() + { + // load core hook config + $configFile = $this->basePath . '/' . static::STATIC_DIR . '/' . static::CONFIG_NAME . '.config.php'; + + if (!file_exists($configFile)) { + throw new HookConfigException(sprintf('config file %s does not exist.', $configFile)); + } + + $config = include $configFile; + + if (!is_array($config)) { + throw new HookConfigException(sprintf('Error loading config file %s.', $configFile)); + } + + $this->config = array_merge_recursive($config, $this->addonLoader->getActiveAddonConfig(static::CONFIG_NAME)); + } +} diff --git a/src/Core/Logger/Capabilities/ICheckLoggerSettings.php b/src/Core/Logger/Capabilities/ICheckLoggerSettings.php new file mode 100644 index 000000000..fb93d18e8 --- /dev/null +++ b/src/Core/Logger/Capabilities/ICheckLoggerSettings.php @@ -0,0 +1,42 @@ +. + * + */ + +namespace Friendica\Core\Logger\Capabilities; + +/** + * Whenever a logging specific check is necessary, use this interface to encapsulate and centralize this logic + */ +interface ICheckLoggerSettings +{ + /** + * Checks if the logfile is set and usable + * + * @return string|null null in case everything is ok, otherwise returns the error + */ + public function checkLogfile(): ?string; + + /** + * Checks if the debugging logfile is usable in case it is set! + * + * @return string|null null in case everything is ok, otherwise returns the error + */ + public function checkDebugLogfile(): ?string; +} diff --git a/src/Core/Logger/Capabilities/LogChannel.php b/src/Core/Logger/Capabilities/LogChannel.php new file mode 100644 index 000000000..31915168b --- /dev/null +++ b/src/Core/Logger/Capabilities/LogChannel.php @@ -0,0 +1,43 @@ +. + * + */ + +namespace Friendica\Core\Logger\Capabilities; + +/** + * An enum class for the Log channels + */ +interface LogChannel +{ + /** @var string channel for the auth_ejabbered script */ + public const AUTH_JABBERED = 'auth_ejabberd'; + /** @var string Default channel in case it isn't set explicit */ + public const DEFAULT = self::APP; + /** @var string channel for console execution */ + public const CONSOLE = 'console'; + /** @var string channel for developer focused logging */ + public const DEV = 'dev'; + /** @var string channel for daemon executions */ + public const DAEMON = 'daemon'; + /** @var string channel for worker execution */ + public const WORKER = 'worker'; + /** @var string channel for frontend app executions */ + public const APP = 'app'; +} diff --git a/src/Core/Logger/Exception/LogLevelException.php b/src/Core/Logger/Exception/LogLevelException.php index 36b2c31cf..9cf649a86 100644 --- a/src/Core/Logger/Exception/LogLevelException.php +++ b/src/Core/Logger/Exception/LogLevelException.php @@ -23,9 +23,12 @@ namespace Friendica\Core\Logger\Exception; use Throwable; +/** + * Exception in case the loglevel isn't set or isn't valid + */ class LogLevelException extends \InvalidArgumentException { - public function __construct($message = "", Throwable $previous = null) + public function __construct($message = '', Throwable $previous = null) { parent::__construct($message, 500, $previous); } diff --git a/src/Core/Logger/Exception/LoggerArgumentException.php b/src/Core/Logger/Exception/LoggerArgumentException.php index a0b9949b0..6925be4c7 100644 --- a/src/Core/Logger/Exception/LoggerArgumentException.php +++ b/src/Core/Logger/Exception/LoggerArgumentException.php @@ -23,9 +23,12 @@ namespace Friendica\Core\Logger\Exception; use Throwable; +/** + * Exception in case an argument of a logger class isn't valid + */ class LoggerArgumentException extends \InvalidArgumentException { - public function __construct($message = "", Throwable $previous = null) + public function __construct($message = '', Throwable $previous = null) { parent::__construct($message, 500, $previous); } diff --git a/src/Core/Logger/Exception/LoggerException.php b/src/Core/Logger/Exception/LoggerException.php index 7e3637327..7529f6e78 100644 --- a/src/Core/Logger/Exception/LoggerException.php +++ b/src/Core/Logger/Exception/LoggerException.php @@ -23,9 +23,12 @@ namespace Friendica\Core\Logger\Exception; use Throwable; +/** + * A generic exception of the logging namespace + */ class LoggerException extends \Exception { - public function __construct($message = "", Throwable $previous = null) + public function __construct($message = '', Throwable $previous = null) { parent::__construct($message, 500, $previous); } diff --git a/src/Core/Logger/Exception/LoggerUnusableException.php b/src/Core/Logger/Exception/LoggerUnusableException.php new file mode 100644 index 000000000..401a63482 --- /dev/null +++ b/src/Core/Logger/Exception/LoggerUnusableException.php @@ -0,0 +1,35 @@ +. + * + */ + +namespace Friendica\Core\Logger\Exception; + +use Throwable; + +/** + * Exception in case the used logging instance is unusable because of some circumstances + */ +class LoggerUnusableException extends \RuntimeException +{ + public function __construct($message = '', Throwable $previous = null) + { + parent::__construct($message, 500, $previous); + } +} diff --git a/src/Core/Logger/Factory/AbstractLoggerTypeFactory.php b/src/Core/Logger/Factory/AbstractLoggerTypeFactory.php new file mode 100644 index 000000000..402176d8e --- /dev/null +++ b/src/Core/Logger/Factory/AbstractLoggerTypeFactory.php @@ -0,0 +1,80 @@ +. + * + */ + +namespace Friendica\Core\Logger\Factory; + +use Friendica\Core\Logger\Capabilities\IHaveCallIntrospections; +use Psr\Log\LogLevel; + +/** + * Abstract class for creating logger types, which includes common necessary logic/content + */ +abstract class AbstractLoggerTypeFactory +{ + /** @var string */ + protected $channel; + /** @var IHaveCallIntrospections */ + protected $introspection; + + /** + * @param string $channel The channel for the logger + */ + public function __construct(IHaveCallIntrospections $introspection, string $channel) + { + $this->channel = $channel; + $this->introspection = $introspection; + } + + /** + * Mapping a legacy level to the PSR-3 compliant levels + * + * @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#5-psrlogloglevel + * + * @param string $level the level to be mapped + * + * @return string the PSR-3 compliant level + */ + protected static function mapLegacyConfigDebugLevel(string $level): string + { + switch ($level) { + // legacy WARNING + case "0": + return LogLevel::ERROR; + // legacy INFO + case "1": + return LogLevel::WARNING; + // legacy TRACE + case "2": + return LogLevel::NOTICE; + // legacy DEBUG + case "3": + return LogLevel::INFO; + // legacy DATA + case "4": + // legacy ALL + case "5": + return LogLevel::DEBUG; + // default if nothing set + default: + return $level; + } + } +} diff --git a/src/Core/Logger/Factory/Logger.php b/src/Core/Logger/Factory/Logger.php index 2821a813c..c370cc4dd 100644 --- a/src/Core/Logger/Factory/Logger.php +++ b/src/Core/Logger/Factory/Logger.php @@ -22,134 +22,46 @@ namespace Friendica\Core\Logger\Factory; use Friendica\Core\Config\Capability\IManageConfigValues; -use Friendica\Core\Hooks\Capabilities\ICanManageInstances; -use Friendica\Core\Logger\Exception\LogLevelException; -use Friendica\Core\Logger\Type\ProfilerLogger; -use Friendica\Core\Logger\Type\StreamLogger; -use Friendica\Core\Logger\Type\SyslogLogger; +use Friendica\Core\Hooks\Capabilities\ICanCreateInstances; +use Friendica\Core\Logger\Capabilities\LogChannel; +use Friendica\Core\Logger\Type\ProfilerLogger as ProfilerLoggerClass; +use Friendica\Util\Profiler; use Psr\Log\LoggerInterface; -use Psr\Log\LogLevel; use Psr\Log\NullLogger; +use Throwable; /** - * A logger factory + * The logger factory for the core logging instances */ class Logger { - const DEV_CHANNEL = 'dev'; - - /** @var string The log-channel (app, worker, ...) */ + /** @var string The channel */ protected $channel; - /** @var ICanManageInstances */ - protected $instanceManager; - /** @var IManageConfigValues */ - protected $config; - public function __construct(string $channel, ICanManageInstances $instanceManager, IManageConfigValues $config, string $logfile = null) + public function __construct(string $channel = LogChannel::DEFAULT) { - $this->channel = $channel; - $this->instanceManager = $instanceManager; - $this->config = $config; - - $this->instanceManager - ->registerStrategy(LoggerInterface::class, 'syslog', SyslogLogger::class) - ->registerStrategy(LoggerInterface::class, 'stream', StreamLogger::class, isset($logfile) ? [$logfile] : null); - - if ($this->config->get('system', 'profiling') ?? false) { - $this->instanceManager->registerDecorator(LoggerInterface::class, ProfilerLogger::class); - } + $this->channel = $channel; } - /** - * Creates a new PSR-3 compliant logger instances - * - * @param string|null $loglevel (optional) A given loglevel in case the loglevel in the config isn't applicable - * - * @return LoggerInterface The PSR-3 compliant logger instance - */ - public function create(string $loglevel = null): LoggerInterface + public function create(ICanCreateInstances $instanceCreator, IManageConfigValues $config, Profiler $profiler): LoggerInterface { - if (empty($this->config->get('system', 'debugging') ?? false)) { + if (empty($config->get('system', 'debugging') ?? false)) { return new NullLogger(); } - $loglevel = $loglevel ?? static::mapLegacyConfigDebugLevel($this->config->get('system', 'loglevel')); - $name = $this->config->get('system', 'logger_config') ?? 'stream'; + $name = $config->get('system', 'logger_config') ?? ''; try { - /** @var LoggerInterface */ - return $this->instanceManager->getInstance(LoggerInterface::class, $name, [$this->channel, $loglevel]); - } catch (LogLevelException $exception) { - // If there's a wrong config value for loglevel, try again with standard - $logger = $this->create(LogLevel::NOTICE); - $logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]); - return $logger; - } catch (\Throwable $e) { + /** @var LoggerInterface $logger */ + $logger = $instanceCreator->create(LoggerInterface::class, $name, [$this->channel]); + if ($config->get('system', 'profiling') ?? false) { + return new ProfilerLoggerClass($logger, $profiler); + } else { + return $logger; + } + } catch (Throwable $e) { // No logger ... return new NullLogger(); } } - - /** - * Creates a new PSR-3 compliant develop logger - * - * If you want to debug only interactions from your IP or the IP of a remote server for federation debug, - * you'll use this logger instance for the duration of your work. - * - * It should never get filled during normal usage of Friendica - * - * @return LoggerInterface The PSR-3 compliant logger instance - * @throws \Exception - */ - public function createDev() - { - $debugging = $this->config->get('system', 'debugging'); - $stream = $this->config->get('system', 'dlogfile'); - $developerIp = $this->config->get('system', 'dlogip'); - - if ((!isset($developerIp) || !$debugging) && - (!is_file($stream) || is_writable($stream))) { - return new NullLogger(); - } - - $name = $this->config->get('system', 'logger_config') ?? 'stream'; - - /** @var LoggerInterface */ - return $this->instanceManager->getInstance(LoggerInterface::class, $name, [self::DEV_CHANNEL, LogLevel::DEBUG, $stream]); - } - - /** - * Mapping a legacy level to the PSR-3 compliant levels - * - * @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#5-psrlogloglevel - * - * @param string $level the level to be mapped - * - * @return string the PSR-3 compliant level - */ - private static function mapLegacyConfigDebugLevel(string $level): string - { - switch ($level) { - // legacy WARNING - case "0": - return LogLevel::ERROR; - // legacy INFO - case "1": - return LogLevel::WARNING; - // legacy TRACE - case "2": - return LogLevel::NOTICE; - // legacy DEBUG - case "3": - return LogLevel::INFO; - // legacy DATA - case "4": - // legacy ALL - case "5": - return LogLevel::DEBUG; - // default if nothing set - default: - return $level; - } - } } diff --git a/src/Core/Logger/Factory/StreamLogger.php b/src/Core/Logger/Factory/StreamLogger.php new file mode 100644 index 000000000..caad78e3b --- /dev/null +++ b/src/Core/Logger/Factory/StreamLogger.php @@ -0,0 +1,100 @@ +. + * + */ + +namespace Friendica\Core\Logger\Factory; + +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\Logger\Capabilities\LogChannel; +use Friendica\Core\Logger\Exception\LoggerArgumentException; +use Friendica\Core\Logger\Exception\LoggerException; +use Friendica\Core\Logger\Exception\LogLevelException; +use Friendica\Core\Logger\Type\StreamLogger as StreamLoggerClass; +use Friendica\Core\Logger\Util\FileSystem; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; + +/** + * The logger factory for the StreamLogger instance + * + * @see StreamLoggerClass + */ +class StreamLogger extends AbstractLoggerTypeFactory +{ + /** + * Creates a new PSR-3 compliant stream logger instance + * + * @param IManageConfigValues $config The system configuration + * @param string|null $logfile (optional) A given logfile which should be used as stream (e.g. in case of + * developer logging) + * @param string|null $channel (optional) A given channel in case it is different from the default + * + * @return LoggerInterface The PSR-3 compliant logger instance + * + * @throws LoggerException in case the logger cannot get created + */ + public function create(IManageConfigValues $config, string $logfile = null, string $channel = null): LoggerInterface + { + $fileSystem = new FileSystem(); + + $logfile = $logfile ?? $config->get('system', 'logfile'); + if (!@file_exists($logfile) || !@is_writable($logfile)) { + throw new LoggerArgumentException(sprintf('%s is not a valid logfile', $logfile)); + } + + $loglevel = static::mapLegacyConfigDebugLevel($config->get('system', 'loglevel')); + + if (array_key_exists($loglevel, StreamLoggerClass::levelToInt)) { + $loglevel = StreamLoggerClass::levelToInt[$loglevel]; + } else { + throw new LogLevelException(sprintf('The level "%s" is not valid.', $loglevel)); + } + + $stream = $fileSystem->createStream($logfile); + + return new StreamLoggerClass($channel ?? $this->channel, $this->introspection, $stream, $loglevel, getmypid()); + } + + /** + * Creates a new PSR-3 compliant develop logger + * + * If you want to debug only interactions from your IP or the IP of a remote server for federation debug, + * you'll use this logger instance for the duration of your work. + * + * It should never get filled during normal usage of Friendica + * + * @return LoggerInterface The PSR-3 compliant logger instance + * + * @throws LoggerException + */ + public function createDev(IManageConfigValues $config) + { + $debugging = $config->get('system', 'debugging'); + $logfile = $config->get('system', 'dlogfile'); + $developerIp = $config->get('system', 'dlogip'); + + if ((!isset($developerIp) || !$debugging) && + (!is_file($logfile) || is_writable($logfile))) { + return new NullLogger(); + } + + return $this->create($config, $logfile, LogChannel::DEV); + } +} diff --git a/src/Core/Logger/Factory/SyslogLogger.php b/src/Core/Logger/Factory/SyslogLogger.php new file mode 100644 index 000000000..7b1712344 --- /dev/null +++ b/src/Core/Logger/Factory/SyslogLogger.php @@ -0,0 +1,60 @@ +. + * + */ + +namespace Friendica\Core\Logger\Factory; + +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\Logger\Exception\LoggerException; +use Friendica\Core\Logger\Exception\LogLevelException; +use Friendica\Core\Logger\Type\SyslogLogger as SyslogLoggerClass; +use Psr\Log\LoggerInterface; + +/** + * The logger factory for the SyslogLogger instance + * + * @see SyslogLoggerClass + */ +class SyslogLogger extends AbstractLoggerTypeFactory +{ + /** + * Creates a new PSR-3 compliant syslog logger instance + * + * @param IManageConfigValues $config The system configuration + * + * @return LoggerInterface The PSR-3 compliant logger instance + * + * @throws LoggerException in case the logger cannot get created + */ + public function create(IManageConfigValues $config): LoggerInterface + { + $logOpts = $config->get('system', 'syslog_flags') ?? SyslogLoggerClass::DEFAULT_FLAGS; + $logFacility = $config->get('system', 'syslog_facility') ?? SyslogLoggerClass::DEFAULT_FACILITY; + $loglevel = SyslogLogger::mapLegacyConfigDebugLevel($config->get('system', 'loglevel')); + + if (array_key_exists($loglevel, SyslogLoggerClass::logLevels)) { + $loglevel = SyslogLoggerClass::logLevels[$loglevel]; + } else { + throw new LogLevelException(sprintf('The level "%s" is not valid.', $loglevel)); + } + + return new SyslogLoggerClass($this->channel, $this->introspection, $loglevel, $logOpts, $logFacility); + } +} diff --git a/src/Core/Logger/Type/README.md b/src/Core/Logger/Type/README.md deleted file mode 100644 index b204353c0..000000000 --- a/src/Core/Logger/Type/README.md +++ /dev/null @@ -1,26 +0,0 @@ -## Friendica\Util\Logger - -This namespace contains the different implementations of a Logger. - -### Configuration guideline - -The following settings are possible for `logger_config`: -- [`stream`](StreamLogger.php): A small logger for files or streams -- [`syslog`](SyslogLogger.php): Prints the logging output into the syslog - -[`VoidLogger`](VoidLogger.php) is a fallback logger without any function if no debugging is enabled. - -[`ProfilerLogger`](ProfilerLogger.php) is a wrapper around an existing logger in case profiling is enabled for Friendica. -Every log call will be saved to the `Profiler` with a timestamp. - -### Implementation guideline - -Each logging implementation should pe capable of printing at least the following information: -- An unique ID for each Request/Call -- The process ID (PID) -- A timestamp of the logging entry -- The critically of the log entry -- A log message -- A context of the log message (f.e which user) - -If possible, a Logger should extend [`AbstractLogger`](AbstractLogger.php), because it contains additional, Friendica specific business logic for each logging call. diff --git a/src/Core/Logger/Type/StreamLogger.php b/src/Core/Logger/Type/StreamLogger.php index d56f5b20e..87f1a3937 100644 --- a/src/Core/Logger/Type/StreamLogger.php +++ b/src/Core/Logger/Type/StreamLogger.php @@ -21,20 +21,16 @@ namespace Friendica\Core\Logger\Type; -use Friendica\Core\Config\Capability\IManageConfigValues; -use Friendica\Core\Hooks\Capabilities\IAmAStrategy; use Friendica\Core\Logger\Capabilities\IHaveCallIntrospections; -use Friendica\Core\Logger\Exception\LoggerArgumentException; use Friendica\Core\Logger\Exception\LoggerException; use Friendica\Core\Logger\Exception\LogLevelException; use Friendica\Util\DateTimeFormat; -use Friendica\Util\FileSystem; use Psr\Log\LogLevel; /** * A Logger instance for logging into a stream (file, stdout, stderr) */ -class StreamLogger extends AbstractLogger implements IAmAStrategy +class StreamLogger extends AbstractLogger { /** * The minimum loglevel at which this logger will be triggered @@ -42,12 +38,6 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy */ private $logLevel; - /** - * The file URL of the stream (if needed) - * @var string - */ - private $url; - /** * The stream, where the current logger is writing into * @var resource @@ -60,16 +50,11 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy */ private $pid; - /** - * @var FileSystem - */ - private $fileSystem; - /** * Translates LogLevel log levels to integer values * @var array */ - private $levelToInt = [ + public const levelToInt = [ LogLevel::EMERGENCY => 0, LogLevel::ALERT => 1, LogLevel::CRITICAL => 2, @@ -84,41 +69,20 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy * {@inheritdoc} * @param string $level The minimum loglevel at which this logger will be triggered * - * @throws LoggerArgumentException - * @throws LogLevelException + * @throws LoggerException */ - public function __construct(string $channel, IManageConfigValues $config, IHaveCallIntrospections $introspection, FileSystem $fileSystem, string $level = LogLevel::DEBUG) + public function __construct(string $channel, IHaveCallIntrospections $introspection, $stream, int $logLevel, int $pid) { - $this->fileSystem = $fileSystem; - - $stream = $this->logfile ?? $config->get('system', 'logfile'); - if ((@file_exists($stream) && !@is_writable($stream)) && !@is_writable(basename($stream))) { - throw new LoggerArgumentException(sprintf('%s is not a valid logfile', $stream)); - } - parent::__construct($channel, $introspection); - if (is_resource($stream)) { - $this->stream = $stream; - } elseif (is_string($stream)) { - $this->url = $stream; - } else { - throw new LoggerArgumentException('A stream must either be a resource or a string.'); - } - - $this->pid = getmypid(); - if (array_key_exists($level, $this->levelToInt)) { - $this->logLevel = $this->levelToInt[$level]; - } else { - throw new LogLevelException(sprintf('The level "%s" is not valid.', $level)); - } - - $this->checkStream(); + $this->stream = $stream; + $this->pid = $pid; + $this->logLevel = $logLevel; } public function close() { - if ($this->url && is_resource($this->stream)) { + if (is_resource($this->stream)) { fclose($this->stream); } @@ -139,18 +103,16 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy */ protected function addEntry($level, string $message, array $context = []) { - if (!array_key_exists($level, $this->levelToInt)) { + if (!array_key_exists($level, static::levelToInt)) { throw new LogLevelException(sprintf('The level "%s" is not valid.', $level)); } - $logLevel = $this->levelToInt[$level]; + $logLevel = static::levelToInt[$level]; if ($logLevel > $this->logLevel) { return; } - $this->checkStream(); - $formattedLog = $this->formatLog($level, $message, $context); fwrite($this->stream, $formattedLog); } @@ -185,27 +147,4 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy return $logMessage; } - - /** - * Checks the current stream - * - * @throws LoggerException - * @throws LoggerArgumentException - */ - private function checkStream() - { - if (is_resource($this->stream)) { - return; - } - - if (empty($this->url)) { - throw new LoggerArgumentException('Missing stream URL.'); - } - - try { - $this->stream = $this->fileSystem->createStream($this->url); - } catch (\UnexpectedValueException $exception) { - throw new LoggerException('Cannot create stream.', $exception); - } - } } diff --git a/src/Core/Logger/Type/SyslogLogger.php b/src/Core/Logger/Type/SyslogLogger.php index 3c9ab581a..88dc1964d 100644 --- a/src/Core/Logger/Type/SyslogLogger.php +++ b/src/Core/Logger/Type/SyslogLogger.php @@ -21,8 +21,6 @@ namespace Friendica\Core\Logger\Type; -use Friendica\Core\Config\Capability\IManageConfigValues; -use Friendica\Core\Hooks\Capabilities\IAmAStrategy; use Friendica\Core\Logger\Capabilities\IHaveCallIntrospections; use Friendica\Core\Logger\Exception\LoggerException; use Friendica\Core\Logger\Exception\LogLevelException; @@ -32,7 +30,7 @@ use Psr\Log\LogLevel; * A Logger instance for syslogging (fast, but simple) * @see http://php.net/manual/en/function.syslog.php */ -class SyslogLogger extends AbstractLogger implements IAmAStrategy +class SyslogLogger extends AbstractLogger { const IDENT = 'Friendica'; @@ -45,7 +43,7 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy * Translates LogLevel log levels to syslog log priorities. * @var array */ - private $logLevels = [ + public const logLevels = [ LogLevel::DEBUG => LOG_DEBUG, LogLevel::INFO => LOG_INFO, LogLevel::NOTICE => LOG_NOTICE, @@ -60,7 +58,7 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy * Translates log priorities to string outputs * @var array */ - private $logToString = [ + protected const logToString = [ LOG_DEBUG => 'DEBUG', LOG_INFO => 'INFO', LOG_NOTICE => 'NOTICE', @@ -101,19 +99,18 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy /** * {@inheritdoc} - * @param string $level The minimum loglevel at which this logger will be triggered * - * @throws LogLevelException - * @throws LoggerException + * @param string $logLevel The minimum loglevel at which this logger will be triggered + * @param string $logOptions + * @param string $logFacility */ - public function __construct(string $channel, IManageConfigValues $config, IHaveCallIntrospections $introspection, string $level = LogLevel::NOTICE) + public function __construct(string $channel, IHaveCallIntrospections $introspection, string $logLevel, string $logOptions, string $logFacility) { parent::__construct($channel, $introspection); - $this->logOpts = $config->get('system', 'syslog_flags') ?? static::DEFAULT_FLAGS; - $this->logFacility = $config->get('system', 'syslog_facility') ?? static::DEFAULT_FACILITY; - $this->logLevel = $this->mapLevelToPriority($level); - $this->introspection->addClasses([self::class]); + $this->logOpts = $logOptions; + $this->logFacility = $logFacility; + $this->logLevel = $logLevel; } /** @@ -149,11 +146,11 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy */ public function mapLevelToPriority(string $level): int { - if (!array_key_exists($level, $this->logLevels)) { + if (!array_key_exists($level, static::logLevels)) { throw new LogLevelException(sprintf('The level "%s" is not valid.', $level)); } - return $this->logLevels[$level]; + return static::logLevels[$level]; } /** @@ -202,7 +199,7 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy $record = array_merge($record, ['uid' => $this->logUid]); $logMessage = $this->channel . ' '; - $logMessage .= '[' . $this->logToString[$level] . ']: '; + $logMessage .= '[' . static::logToString[$level] . ']: '; $logMessage .= $this->psrInterpolate($message, $context) . ' '; $logMessage .= $this->jsonEncodeArray($context) . ' - '; $logMessage .= $this->jsonEncodeArray($record); diff --git a/src/Util/FileSystem.php b/src/Core/Logger/Util/FileSystem.php similarity index 81% rename from src/Util/FileSystem.php rename to src/Core/Logger/Util/FileSystem.php index a21e7fb60..41bb423b4 100644 --- a/src/Util/FileSystem.php +++ b/src/Core/Logger/Util/FileSystem.php @@ -19,10 +19,12 @@ * */ -namespace Friendica\Util; +namespace Friendica\Core\Logger\Util; + +use Friendica\Core\Logger\Exception\LoggerUnusableException; /** - * Util class for filesystem manipulation + * Util class for filesystem manipulation for Logger classes */ class FileSystem { @@ -37,8 +39,10 @@ class FileSystem * @param string $file The file * * @return string The directory name (empty if no directory is found, like urls) + * + * @throws LoggerUnusableException */ - public function createDir(string $file) + public function createDir(string $file): string { $dirname = null; $pos = strpos($file, '://'); @@ -57,7 +61,7 @@ class FileSystem restore_error_handler(); if (!$status && !is_dir($dirname)) { - throw new \UnexpectedValueException(sprintf('Directory "%s" cannot get created: ' . $this->errorMessage, $dirname)); + throw new LoggerUnusableException(sprintf('Directory "%s" cannot get created: ' . $this->errorMessage, $dirname)); } return $dirname; @@ -75,7 +79,7 @@ class FileSystem * * @return resource the open stream resource * - * @throws \UnexpectedValueException + * @throws LoggerUnusableException */ public function createStream(string $url) { @@ -89,7 +93,7 @@ class FileSystem restore_error_handler(); if (!is_resource($stream)) { - throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: ' . $this->errorMessage, $url)); + throw new LoggerUnusableException(sprintf('The stream or file "%s" could not be opened: ' . $this->errorMessage, $url)); } return $stream; diff --git a/src/Core/Logger/Util/LoggerSettingsCheck.php b/src/Core/Logger/Util/LoggerSettingsCheck.php new file mode 100644 index 000000000..7782216da --- /dev/null +++ b/src/Core/Logger/Util/LoggerSettingsCheck.php @@ -0,0 +1,91 @@ +. + * + */ + +namespace Friendica\Core\Logger\Util; + +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\L10n; +use Friendica\Core\Logger\Capabilities\ICheckLoggerSettings; +use Friendica\Core\Logger\Exception\LoggerUnusableException; + +/** {@inheritDoc} */ +class LoggerSettingsCheck implements ICheckLoggerSettings +{ + /** @var IManageConfigValues */ + protected $config; + /** @var $fileSystem */ + protected $fileSystem; + /** @var L10n */ + protected $l10n; + + public function __construct(IManageConfigValues $config, FileSystem $fileSystem, L10n $l10n) + { + $this->config = $config; + $this->fileSystem = $fileSystem; + $this->l10n = $l10n; + } + + /** {@inheritDoc} */ + public function checkLogfile(): ?string + { + // Check logfile permission + if ($this->config->get('system', 'debugging')) { + $file = $this->config->get('system', 'logfile'); + + try { + $stream = $this->fileSystem->createStream($file); + + if (!isset($stream)) { + throw new LoggerUnusableException('Stream is null.'); + } + } catch (\Throwable $exception) { + return $this->l10n->t('The logfile \'%s\' is not usable. No logging possible (error: \'%s\')', $file, $exception->getMessage()); + } + } + + return null; + } + + /** {@inheritDoc} */ + public function checkDebugLogfile(): ?string + { + // Check logfile permission + if ($this->config->get('system', 'debugging')) { + $file = $this->config->get('system', 'dlogfile'); + + if (empty($file)) { + return null; + } + + try { + $stream = $this->fileSystem->createStream($file); + + if (!isset($stream)) { + throw new LoggerUnusableException('Stream is null.'); + } + } catch (\Throwable $exception) { + return $this->l10n->t('The debug logfile \'%s\' is not usable. No logging possible (error: \'%s\')', $file, $exception->getMessage()); + } + } + + return null; + } +} diff --git a/src/DI.php b/src/DI.php index 8d706ed37..1917f710d 100644 --- a/src/DI.php +++ b/src/DI.php @@ -22,6 +22,8 @@ namespace Friendica; use Dice\Dice; +use Friendica\Core\Logger\Capabilities\ICheckLoggerSettings; +use Friendica\Core\Logger\Util\LoggerSettingsCheck; use Friendica\Core\Session\Capability\IHandleSessions; use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Navigation\SystemMessages; @@ -295,6 +297,11 @@ abstract class DI static::init($flushDice); } + public static function logCheck(): ICheckLoggerSettings + { + return self::$dice->create(LoggerSettingsCheck::class); + } + /** * @return LoggerInterface */ @@ -692,14 +699,6 @@ abstract class DI return self::$dice->create(Util\DateTimeFormat::class); } - /** - * @return Util\FileSystem - */ - public static function fs() - { - return self::$dice->create(Util\FileSystem::class); - } - /** * @return Util\Profiler */ diff --git a/src/Module/Admin/Summary.php b/src/Module/Admin/Summary.php index d872f4825..99d0e3325 100644 --- a/src/Module/Admin/Summary.php +++ b/src/Module/Admin/Summary.php @@ -79,7 +79,7 @@ class Summary extends BaseAdmin // Check if github.com/friendica/stable/VERSION is higher then // the local version of Friendica. Check is opt-in, source may be stable or develop branch if (DI::config()->get('system', 'check_new_version_url', 'none') != 'none') { - $gitversion = DI::keyValue()->get('git_friendica_version') ?? ''; + $gitversion = DI::keyValue()->get('git_friendica_version') ?? ''; if (version_compare(App::VERSION, $gitversion) < 0) { $warningtext[] = DI::l10n()->t('There is a new version of Friendica available for download. Your current version is %1$s, upstream version is %2$s', App::VERSION, $gitversion); @@ -126,35 +126,11 @@ class Summary extends BaseAdmin } // Check logfile permission - if (DI::config()->get('system', 'debugging')) { - $file = DI::config()->get('system', 'logfile'); - - $fileSystem = DI::fs(); - - try { - $stream = $fileSystem->createStream($file); - - if (!isset($stream)) { - throw new ServiceUnavailableException('Stream is null.'); - } - - } catch (\Throwable $exception) { - $warningtext[] = DI::l10n()->t('The logfile \'%s\' is not usable. No logging possible (error: \'%s\')', $file, $exception->getMessage()); - } - - $file = DI::config()->get('system', 'dlogfile'); - - try { - if (!empty($file)) { - $stream = $fileSystem->createStream($file); - - if (!isset($stream)) { - throw new ServiceUnavailableException('Stream is null.'); - } - } - } catch (\Throwable $exception) { - $warningtext[] = DI::l10n()->t('The debug logfile \'%s\' is not usable. No logging possible (error: \'%s\')', $file, $exception->getMessage()); - } + if (($return = DI::logCheck()->checkLogfile()) !== null) { + $warningtext[] = $return; + } + if (($return = DI::logCheck()->checkDebugLogfile()) !== null) { + $warningtext[] = $return; } // check legacy basepath settings diff --git a/static/dependencies.config.php b/static/dependencies.config.php index 089116642..98a9f3077 100644 --- a/static/dependencies.config.php +++ b/static/dependencies.config.php @@ -37,8 +37,9 @@ use Dice\Dice; use Friendica\App; use Friendica\Core\Cache; use Friendica\Core\Config; -use Friendica\Core\Hooks\Capabilities\ICanManageInstances; -use Friendica\Core\Hooks\Model\InstanceManager; +use Friendica\Core\Hooks\Capabilities\ICanCreateInstances; +use Friendica\Core\Hooks\Capabilities\ICanRegisterStrategies; +use Friendica\Core\Hooks\Model\DiceInstanceManager; use Friendica\Core\PConfig; use Friendica\Core\L10n; use Friendica\Core\Lock; @@ -62,6 +63,13 @@ return [ // one instance for the whole execution 'shared' => true, ], + \Friendica\Core\Addon\Capabilities\ICanLoadAddons::class => [ + 'instanceOf' => \Friendica\Core\Addon\Model\AddonLoader::class, + 'constructParams' => [ + [Dice::INSTANCE => '$basepath'], + [Dice::INSTANCE => Dice::SELF], + ], + ], '$basepath' => [ 'instanceOf' => Util\BasePath::class, 'call' => [ @@ -78,8 +86,27 @@ return [ $_SERVER ] ], - ICanManageInstances::class => [ - 'instanceOf' => InstanceManager::class, + DiceInstanceManager::class => [ + 'constructParams' => [ + [Dice::INSTANCE => Dice::SELF], + ] + ], + \Friendica\Core\Hooks\Util\StrategiesFileManager::class => [ + 'constructParams' => [ + [Dice::INSTANCE => '$basepath'], + ], + 'call' => [ + ['loadConfig'], + ], + ], + ICanRegisterStrategies::class => [ + 'instanceOf' => DiceInstanceManager::class, + 'constructParams' => [ + [Dice::INSTANCE => Dice::SELF], + ], + ], + ICanCreateInstances::class => [ + 'instanceOf' => DiceInstanceManager::class, 'constructParams' => [ [Dice::INSTANCE => Dice::SELF], ], @@ -156,40 +183,34 @@ return [ [Dice::INSTANCE => '$basepath'], ], ], - /** - * Create a Logger, which implements the LoggerInterface - * - * Same as: - * $loggerFactory = new Factory\LoggerFactory(); - * $logger = $loggerFactory->create($channel, $configuration, $profiler); - * - * Attention1: We can use DICE for detecting dependencies inside "chained" calls too - * Attention2: The variable "$channel" is passed inside the creation of the dependencies per: - * $app = $dice->create(App::class, [], ['$channel' => 'index']); - * and is automatically passed as an argument with the same name - */ - LoggerInterface::class => [ + \Psr\Log\LoggerInterface::class => [ 'instanceOf' => \Friendica\Core\Logger\Factory\Logger::class, - 'constructParams' => [ - 'index', - ], 'call' => [ ['create', [], Dice::CHAIN_CALL], ], ], - '$devLogger' => [ - 'instanceOf' => \Friendica\Core\Logger\Factory\Logger::class, - 'constructParams' => [ - 'dev', - ], + \Friendica\Core\Logger\Type\SyslogLogger::class => [ + 'instanceOf' => \Friendica\Core\Logger\Factory\SyslogLogger::class, 'call' => [ - ['createDev', [], Dice::CHAIN_CALL], - ] + ['create', [], Dice::CHAIN_CALL], + ], + ], + \Friendica\Core\Logger\Type\StreamLogger::class => [ + 'instanceOf' => \Friendica\Core\Logger\Factory\StreamLogger::class, + 'call' => [ + ['create', [], Dice::CHAIN_CALL], + ], ], \Friendica\Core\Logger\Capabilities\IHaveCallIntrospections::class => [ - 'instanceOf' => \Friendica\Core\Logger\Util\Introspection::class, + 'instanceOf' => \Friendica\Core\Logger\Util\Introspection::class, 'constructParams' => [ - \Friendica\Core\Logger\Util\Introspection::IGNORE_CLASS_LIST, + \Friendica\Core\Logger\Capabilities\IHaveCallIntrospections::IGNORE_CLASS_LIST, + ], + ], + '$devLogger' => [ + 'instanceOf' => \Friendica\Core\Logger\Factory\StreamLogger::class, + 'call' => [ + ['createDev', [], Dice::CHAIN_CALL], ], ], Cache\Capability\ICanCache::class => [ diff --git a/src/Core/Hooks/Capabilities/IAmAStrategy.php b/static/strategies.config.php similarity index 74% rename from src/Core/Hooks/Capabilities/IAmAStrategy.php rename to static/strategies.config.php index 017cb56c4..bb3598c36 100644 --- a/src/Core/Hooks/Capabilities/IAmAStrategy.php +++ b/static/strategies.config.php @@ -19,11 +19,14 @@ * */ -namespace Friendica\Core\Hooks\Capabilities; +use Friendica\Core\Hooks\Capabilities\BehavioralHookType as H; +use Friendica\Core\Logger\Type; +use Psr\Log; -/** - * All classes, implementing this interface are valid Strategies for Hook calls - */ -interface IAmAStrategy -{ -} +return [ + Log\LoggerInterface::class => [ + Log\NullLogger::class => [''], + Type\SyslogLogger::class => ['syslog'], + Type\StreamLogger::class => ['stream'], + ], +]; diff --git a/tests/Util/Hooks/InstanceMocks/FakeInstance.php b/tests/Util/Hooks/InstanceMocks/FakeInstance.php index ff99002f7..64fe2c5ad 100644 --- a/tests/Util/Hooks/InstanceMocks/FakeInstance.php +++ b/tests/Util/Hooks/InstanceMocks/FakeInstance.php @@ -21,9 +21,7 @@ namespace Friendica\Test\Util\Hooks\InstanceMocks; -use Friendica\Core\Hooks\Capabilities\IAmAStrategy; - -class FakeInstance implements IAmADecoratedInterface, IAmAStrategy +class FakeInstance implements IAmADecoratedInterface { protected $aText = null; protected $cBool = null; @@ -41,6 +39,8 @@ class FakeInstance implements IAmADecoratedInterface, IAmAStrategy $this->aText = $aText; $this->cBool = $cBool; $this->bText = $bText; + + return ''; } public function getAText(): ?string diff --git a/tests/Util/Hooks/InstanceMocks/FakeInstanceDecorator.php b/tests/Util/Hooks/InstanceMocks/FakeInstanceDecorator.php index af4db96c4..40ab78a25 100644 --- a/tests/Util/Hooks/InstanceMocks/FakeInstanceDecorator.php +++ b/tests/Util/Hooks/InstanceMocks/FakeInstanceDecorator.php @@ -25,14 +25,14 @@ class FakeInstanceDecorator implements IAmADecoratedInterface { public static $countInstance = 0; + const PREFIX = 'prefix1'; + /** @var IAmADecoratedInterface */ protected $orig; - protected $prefix = ''; - public function __construct(IAmADecoratedInterface $orig, string $prefix = '') + public function __construct(IAmADecoratedInterface $orig) { $this->orig = $orig; - $this->prefix = $prefix; self::$countInstance++; } @@ -44,16 +44,16 @@ class FakeInstanceDecorator implements IAmADecoratedInterface public function getAText(): ?string { - return $this->prefix . $this->orig->getAText(); + return static::PREFIX . $this->orig->getAText(); } public function getBText(): ?string { - return $this->prefix . $this->orig->getBText(); + return static::PREFIX . $this->orig->getBText(); } public function getCBool(): ?bool { - return $this->prefix . $this->orig->getCBool(); + return static::PREFIX . $this->orig->getCBool(); } } diff --git a/tests/functional/DependencyCheckTest.php b/tests/functional/DependencyCheckTest.php index 89b8f79a5..4488fe586 100644 --- a/tests/functional/DependencyCheckTest.php +++ b/tests/functional/DependencyCheckTest.php @@ -145,7 +145,7 @@ class DependencyCheckTest extends FixtureTest $config->set('system', 'dlogfile', $this->root->url() . '/friendica.log'); /** @var LoggerInterface $logger */ - $logger = $this->dice->create('$devLogger', [['$channel' => 'dev']]); + $logger = $this->dice->create('$devLogger', ['dev']); self::assertInstanceOf(LoggerInterface::class, $logger); } diff --git a/tests/src/Core/Addon/Model/AddonLoaderTest.php b/tests/src/Core/Addon/Model/AddonLoaderTest.php new file mode 100644 index 000000000..189110fde --- /dev/null +++ b/tests/src/Core/Addon/Model/AddonLoaderTest.php @@ -0,0 +1,212 @@ +. + * + */ + +namespace Friendica\Test\src\Core\Addon\Model; + +use Friendica\Core\Addon\Exception\AddonInvalidConfigFileException; +use Friendica\Core\Addon\Model\AddonLoader; +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Test\MockedTest; +use Friendica\Test\Util\VFSTrait; +use org\bovigo\vfs\vfsStream; + +class AddonLoaderTest extends MockedTest +{ + use VFSTrait; + + protected $structure = [ + 'addon' => [ + 'testaddon1' => [ + 'static' => [], + ], + 'testaddon2' => [ + 'static' => [], + ], + 'testaddon3' => [], + ] + ]; + + protected $addons = [ + 'testaddon1', + 'testaddon2', + 'testaddon3', + ]; + + protected $content = << [ + \Psr\Log\LoggerInterface::class => [ + \Psr\Log\NullLogger::class => [''], + ], + ], +]; +EOF; + + protected function setUp(): void + { + parent::setUp(); + + $this->setUpVfsDir(); + } + + public function dataHooks(): array + { + return [ + 'normal' => [ + 'structure' => $this->structure, + 'enabled' => $this->addons, + 'files' => [ + 'addon/testaddon1/static/hooks.config.php' => $this->content, + ], + 'assertion' => [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ + \Psr\Log\LoggerInterface::class => [ + \Psr\Log\NullLogger::class => [''], + ], + ], + ], + ], + 'double' => [ + 'structure' => $this->structure, + 'enabled' => $this->addons, + 'files' => [ + 'addon/testaddon1/static/hooks.config.php' => $this->content, + 'addon/testaddon2/static/hooks.config.php' => $this->content, + ], + 'assertion' => [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ + \Psr\Log\LoggerInterface::class => [ + \Psr\Log\NullLogger::class => ['', ''], + ], + ], + ], + ], + 'wrongName' => [ + 'structure' => $this->structure, + 'enabled' => $this->addons, + 'files' => [ + 'addon/testaddon1/static/wrong.config.php' => $this->content, + ], + 'assertion' => [ + ], + ], + 'doubleNutOnlyOneEnabled' => [ + 'structure' => $this->structure, + 'enabled' => ['testaddon1'], + 'files' => [ + 'addon/testaddon1/static/hooks.config.php' => $this->content, + 'addon/testaddon2/static/hooks.config.php' => $this->content, + ], + 'assertion' => [ + \Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [ + \Psr\Log\LoggerInterface::class => [ + \Psr\Log\NullLogger::class => [''], + ], + ], + ], + ] + ]; + } + + /** + * @dataProvider dataHooks + */ + public function testAddonLoader(array $structure, array $enabledAddons, array $files, array $assertion) + { + vfsStream::create($structure)->at($this->root); + + foreach ($files as $file => $content) { + vfsStream::newFile($file) + ->withContent($content) + ->at($this->root); + } + + $configArray = []; + foreach ($enabledAddons as $enabledAddon) { + $configArray[$enabledAddon] = ['test' => []]; + } + + $config = \Mockery::mock(IManageConfigValues::class); + $config->shouldReceive('get')->with('addons')->andReturn($configArray)->once(); + + $addonLoader = new AddonLoader($this->root->url(), $config); + + self::assertEquals($assertion, $addonLoader->getActiveAddonConfig('hooks')); + } + + /** + * Test the exception in case of a wrong addon content + */ + public function testWrongContent() + { + $filename = 'addon/testaddon1/static/hooks.config.php'; + $wrongContent = "structure)->at($this->root); + + vfsStream::newFile($filename) + ->withContent($wrongContent) + ->at($this->root); + + $configArray = []; + foreach ($this->addons as $enabledAddon) { + $configArray[$enabledAddon] = ['test' => []]; + } + + $config = \Mockery::mock(IManageConfigValues::class); + $config->shouldReceive('get')->with('addons')->andReturn($configArray)->once(); + + $addonLoader = new AddonLoader($this->root->url(), $config); + + self::expectException(AddonInvalidConfigFileException::class); + self::expectExceptionMessage(sprintf('Error loading config file %s', $this->root->getChild($filename)->url())); + + $addonLoader->getActiveAddonConfig('hooks'); + } + + /** + * Test that nothing happens in case there are wrong addons files, but they're not used + */ + public function testNoHooksConfig() + { + $filename = 'addon/testaddon1/static/hooks.config.php'; + $wrongContent = "structure)->at($this->root); + + vfsStream::newFile($filename) + ->withContent($wrongContent) + ->at($this->root); + + $configArray = []; + foreach ($this->addons as $enabledAddon) { + $configArray[$enabledAddon] = ['test' => []]; + } + + $config = \Mockery::mock(IManageConfigValues::class); + $config->shouldReceive('get')->with('addons')->andReturn($configArray)->once(); + + $addonLoader = new AddonLoader($this->root->url(), $config); + self::assertEmpty($addonLoader->getActiveAddonConfig('anythingElse')); + } +} diff --git a/tests/src/Core/Hooks/Model/InstanceManagerTest.php b/tests/src/Core/Hooks/Model/InstanceManagerTest.php index 4e4c0135c..44459eb2e 100644 --- a/tests/src/Core/Hooks/Model/InstanceManagerTest.php +++ b/tests/src/Core/Hooks/Model/InstanceManagerTest.php @@ -22,25 +22,27 @@ namespace Friendica\Test\src\Core\Hooks\Model; use Dice\Dice; -use Friendica\Core\Hooks\Model\InstanceManager; +use Friendica\Core\Hooks\Exceptions\HookInstanceException; +use Friendica\Core\Hooks\Exceptions\HookRegisterArgumentException; +use Friendica\Core\Hooks\Model\DiceInstanceManager; +use Friendica\Core\Hooks\Util\StrategiesFileManager; use Friendica\Test\MockedTest; use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstance; use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstanceDecorator; use Friendica\Test\Util\Hooks\InstanceMocks\IAmADecoratedInterface; +use Mockery\MockInterface; class InstanceManagerTest extends MockedTest { - public function testEqualButNotSameInstance() + /** @var StrategiesFileManager|MockInterface */ + protected $hookFileManager; + + protected function setUp(): void { - $instance = new InstanceManager(new Dice()); + parent::setUp(); - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class); - - $getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake'); - $getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake'); - - self::assertEquals($getInstanceA, $getInstanceB); - self::assertNotSame($getInstanceA, $getInstanceB); + $this->hookFileManager = \Mockery::mock(StrategiesFileManager::class); + $this->hookFileManager->shouldReceive('setupStrategies')->withAnyArgs(); } protected function tearDown(): void @@ -50,6 +52,19 @@ class InstanceManagerTest extends MockedTest parent::tearDown(); } + public function testEqualButNotSameInstance() + { + $instance = new DiceInstanceManager(new Dice(), $this->hookFileManager); + + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake'); + + $getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake'); + $getInstanceB = $instance->create(IAmADecoratedInterface::class, 'fake'); + + self::assertEquals($getInstanceA, $getInstanceB); + self::assertNotSame($getInstanceA, $getInstanceB); + } + public function dataTests(): array { return [ @@ -79,9 +94,9 @@ class InstanceManagerTest extends MockedTest /** * @dataProvider dataTests */ - public function testInstanceWithConstructorAnonymArgs(string $aString = null, bool $cBool = null, string $bString = null) + public function testInstanceWithArgs(string $aString = null, bool $cBool = null, string $bString = null) { - $instance = new InstanceManager(new Dice()); + $instance = new DiceInstanceManager(new Dice(), $this->hookFileManager); $args = []; @@ -95,45 +110,12 @@ class InstanceManagerTest extends MockedTest $args[] = $cBool; } - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args); + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake'); /** @var IAmADecoratedInterface $getInstanceA */ - $getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake'); + $getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake', $args); /** @var IAmADecoratedInterface $getInstanceB */ - $getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake'); - - self::assertEquals($getInstanceA, $getInstanceB); - self::assertNotSame($getInstanceA, $getInstanceB); - self::assertEquals($aString, $getInstanceA->getAText()); - self::assertEquals($aString, $getInstanceB->getAText()); - self::assertEquals($bString, $getInstanceA->getBText()); - self::assertEquals($bString, $getInstanceB->getBText()); - self::assertEquals($cBool, $getInstanceA->getCBool()); - self::assertEquals($cBool, $getInstanceB->getCBool()); - } - - /** - * @dataProvider dataTests - */ - public function testInstanceConstructorAndGetInstanceWithNamedArgs(string $aString = null, bool $cBool = null, string $bString = null) - { - $instance = new InstanceManager(new Dice()); - - $args = []; - - if (isset($aString)) { - $args[] = $aString; - } - if (isset($cBool)) { - $args[] = $cBool; - } - - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args); - - /** @var IAmADecoratedInterface $getInstanceA */ - $getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]); - /** @var IAmADecoratedInterface $getInstanceB */ - $getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]); + $getInstanceB = $instance->create(IAmADecoratedInterface::class, 'fake', $args); self::assertEquals($getInstanceA, $getInstanceB); self::assertNotSame($getInstanceA, $getInstanceB); @@ -150,24 +132,27 @@ class InstanceManagerTest extends MockedTest */ public function testInstanceWithTwoStrategies(string $aString = null, bool $cBool = null, string $bString = null) { - $instance = new InstanceManager(new Dice()); + $instance = new DiceInstanceManager(new Dice(), $this->hookFileManager); $args = []; if (isset($aString)) { $args[] = $aString; } + if (isset($bString)) { + $args[] = $bString; + } if (isset($cBool)) { $args[] = $cBool; } - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args); - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args); + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake'); + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake23'); /** @var IAmADecoratedInterface $getInstanceA */ - $getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]); + $getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake', $args); /** @var IAmADecoratedInterface $getInstanceB */ - $getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake23', [$bString]); + $getInstanceB = $instance->create(IAmADecoratedInterface::class, 'fake23', $args); self::assertEquals($getInstanceA, $getInstanceB); self::assertNotSame($getInstanceA, $getInstanceB); @@ -180,79 +165,74 @@ class InstanceManagerTest extends MockedTest } /** - * @dataProvider dataTests + * Test the exception in case the interface was already registered */ - public function testDecorator(string $aString = null, bool $cBool = null, string $bString = null) + public function testDoubleRegister() { - $instance = new InstanceManager(new Dice()); + self::expectException(HookRegisterArgumentException::class); + self::expectExceptionMessage(sprintf('A class with the name %s is already set for the interface %s', 'fake', IAmADecoratedInterface::class)); - $args = []; - - if (isset($aString)) { - $args[] = $aString; - } - if (isset($cBool)) { - $args[] = $cBool; - } - - $prefix = 'prefix1'; - - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args); - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args); - $instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class, [$prefix]); - - /** @var IAmADecoratedInterface $getInstanceA */ - $getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]); - /** @var IAmADecoratedInterface $getInstanceB */ - $getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake23', [$bString]); - - self::assertEquals(2, FakeInstanceDecorator::$countInstance); - self::assertEquals($getInstanceA, $getInstanceB); - self::assertNotSame($getInstanceA, $getInstanceB); - self::assertEquals($prefix . $aString, $getInstanceA->getAText()); - self::assertEquals($prefix . $aString, $getInstanceB->getAText()); - self::assertEquals($prefix . $bString, $getInstanceA->getBText()); - self::assertEquals($prefix . $bString, $getInstanceB->getBText()); - self::assertEquals($prefix . $cBool, $getInstanceA->getCBool()); - self::assertEquals($prefix . $cBool, $getInstanceB->getCBool()); + $instance = new DiceInstanceManager(new Dice(), $this->hookFileManager); + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake'); + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake'); } /** + * Test the exception in case the name of the instance isn't registered + */ + public function testWrongInstanceName() + { + self::expectException(HookInstanceException::class ); + self::expectExceptionMessage(sprintf('The class with the name %s isn\'t registered for the class or interface %s', 'fake', IAmADecoratedInterface::class)); + + $instance = new DiceInstanceManager(new Dice(), $this->hookFileManager); + $instance->create(IAmADecoratedInterface::class, 'fake'); + } + + /** + * Test in case there are already some rules + * * @dataProvider dataTests */ - public function testTwoDecoratorWithPrefix(string $aString = null, bool $cBool = null, string $bString = null) + public function testWithGivenRules(string $aString = null, bool $cBool = null, string $bString = null) { - $instance = new InstanceManager(new Dice()); - $args = []; if (isset($aString)) { $args[] = $aString; } + if (isset($bString)) { + $args[] = $bString; + } + + $dice = (new Dice())->addRules([ + FakeInstance::class => [ + 'constructParams' => $args, + ], + ]); + + $args = []; + if (isset($cBool)) { $args[] = $cBool; } - $prefix = 'prefix1'; + $instance = new DiceInstanceManager($dice, $this->hookFileManager); - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args); - $instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args); - $instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class, [$prefix]); - $instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class); + $instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake'); /** @var IAmADecoratedInterface $getInstanceA */ - $getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]); + $getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake', $args); /** @var IAmADecoratedInterface $getInstanceB */ - $getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake23', [$bString]); + $getInstanceB = $instance->create(IAmADecoratedInterface::class, 'fake', $args); - self::assertEquals(4, FakeInstanceDecorator::$countInstance); self::assertEquals($getInstanceA, $getInstanceB); self::assertNotSame($getInstanceA, $getInstanceB); - self::assertEquals($prefix . $aString, $getInstanceA->getAText()); - self::assertEquals($prefix . $aString, $getInstanceB->getAText()); - self::assertEquals($prefix . $bString, $getInstanceA->getBText()); - self::assertEquals($prefix . $bString, $getInstanceB->getBText()); - self::assertEquals($prefix . $cBool, $getInstanceA->getCBool()); - self::assertEquals($prefix . $cBool, $getInstanceB->getCBool()); + self::assertEquals($aString, $getInstanceA->getAText()); + self::assertEquals($aString, $getInstanceB->getAText()); + self::assertEquals($bString, $getInstanceA->getBText()); + self::assertEquals($bString, $getInstanceB->getBText()); + self::assertEquals($cBool, $getInstanceA->getCBool()); + self::assertEquals($cBool, $getInstanceB->getCBool()); } } diff --git a/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php b/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php new file mode 100644 index 000000000..592f6f1e0 --- /dev/null +++ b/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php @@ -0,0 +1,202 @@ +. + * + */ + +namespace Friendica\Test\src\Core\Hooks\Util; + +use Friendica\Core\Addon\Capabilities\ICanLoadAddons; +use Friendica\Core\Hooks\Capabilities\ICanRegisterStrategies; +use Friendica\Core\Hooks\Exceptions\HookConfigException; +use Friendica\Core\Hooks\Util\StrategiesFileManager; +use Friendica\Test\MockedTest; +use Friendica\Test\Util\VFSTrait; +use org\bovigo\vfs\vfsStream; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; + +class StrategiesFileManagerTest extends MockedTest +{ + use VFSTrait; + + protected function setUp(): void + { + parent::setUp(); + + $this->setUpVfsDir(); + } + + public function dataHooks(): array + { + return [ + 'normal' => [ + 'content' => << [ + \Psr\Log\NullLogger::class => [''], + ], +]; +EOF, + 'addonsArray' => [], + 'assertStrategies' => [ + [LoggerInterface::class, NullLogger::class, ''], + ], + ], + 'normalWithString' => [ + 'content' => << [ + \Psr\Log\NullLogger::class => '', + ], +]; +EOF, + 'addonsArray' => [], + 'assertStrategies' => [ + [LoggerInterface::class, NullLogger::class, ''], + ], + ], + 'withAddons' => [ + 'content' => << [ + \Psr\Log\NullLogger::class => [''], + ], +]; +EOF, + 'addonsArray' => [ + \Psr\Log\LoggerInterface::class => [ + \Psr\Log\NullLogger::class => ['null'], + ], + ], + 'assertStrategies' => [ + [LoggerInterface::class, NullLogger::class, ''], + [LoggerInterface::class, NullLogger::class, 'null'], + ], + ], + 'withAddonsWithString' => [ + 'content' => << [ + \Psr\Log\NullLogger::class => [''], + ], +]; +EOF, + 'addonsArray' => [ + \Psr\Log\LoggerInterface::class => [ + \Psr\Log\NullLogger::class => 'null', + ], + ], + 'assertStrategies' => [ + [LoggerInterface::class, NullLogger::class, ''], + [LoggerInterface::class, NullLogger::class, 'null'], + ], + ], + // This should work because unique name convention is part of the instance manager logic, not of the file-infrastructure layer + 'withAddonsDoubleNamed' => [ + 'content' => << [ + \Psr\Log\NullLogger::class => [''], + ], +]; +EOF, + 'addonsArray' => [ + \Psr\Log\LoggerInterface::class => [ + \Psr\Log\NullLogger::class => [''], + ], + ], + 'assertStrategies' => [ + [LoggerInterface::class, NullLogger::class, ''], + [LoggerInterface::class, NullLogger::class, ''], + ], + ], + ]; + } + + /** + * @dataProvider dataHooks + */ + public function testSetupHooks(string $content, array $addonsArray, array $assertStrategies) + { + vfsStream::newFile(StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php') + ->withContent($content) + ->at($this->root); + + $addonLoader = \Mockery::mock(ICanLoadAddons::class); + $addonLoader->shouldReceive('getActiveAddonConfig')->andReturn($addonsArray)->once(); + + $hookFileManager = new StrategiesFileManager($this->root->url(), $addonLoader); + + $instanceManager = \Mockery::mock(ICanRegisterStrategies::class); + foreach ($assertStrategies as $assertStrategy) { + $instanceManager->shouldReceive('registerStrategy')->withArgs($assertStrategy)->once(); + } + + $hookFileManager->loadConfig(); + $hookFileManager->setupStrategies($instanceManager); + + self::expectNotToPerformAssertions(); + } + + /** + * Test the exception in case the strategies.config.php file is missing + */ + public function testMissingStrategiesFile() + { + $addonLoader = \Mockery::mock(ICanLoadAddons::class); + $instanceManager = \Mockery::mock(ICanRegisterStrategies::class); + $hookFileManager = new StrategiesFileManager($this->root->url(), $addonLoader); + + self::expectException(HookConfigException::class); + self::expectExceptionMessage(sprintf('config file %s does not exist.', + $this->root->url() . '/' . StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php')); + + $hookFileManager->loadConfig(); + } + + /** + * Test the exception in case the strategies.config.php file is wrong + */ + public function testWrongStrategiesFile() + { + $addonLoader = \Mockery::mock(ICanLoadAddons::class); + $instanceManager = \Mockery::mock(ICanRegisterStrategies::class); + $hookFileManager = new StrategiesFileManager($this->root->url(), $addonLoader); + + vfsStream::newFile(StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php') + ->withContent("at($this->root); + + self::expectException(HookConfigException::class); + self::expectExceptionMessage(sprintf('Error loading config file %s.', + $this->root->url() . '/' . StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php')); + + $hookFileManager->loadConfig(); + } +} diff --git a/tests/src/Core/Logger/StreamLoggerTest.php b/tests/src/Core/Logger/StreamLoggerTest.php index 1ddddf4c1..a1e0af5bb 100644 --- a/tests/src/Core/Logger/StreamLoggerTest.php +++ b/tests/src/Core/Logger/StreamLoggerTest.php @@ -22,9 +22,7 @@ namespace Friendica\Test\src\Core\Logger; use Friendica\Core\Logger\Exception\LoggerArgumentException; -use Friendica\Core\Logger\Exception\LoggerException; use Friendica\Core\Logger\Exception\LogLevelException; -use Friendica\Util\FileSystem; use Friendica\Test\Util\VFSTrait; use Friendica\Core\Logger\Type\StreamLogger; use org\bovigo\vfs\vfsStream; @@ -40,33 +38,26 @@ class StreamLoggerTest extends AbstractLoggerTest */ private $logfile; - /** - * @var Filesystem - */ - private $fileSystem; - protected function setUp(): void { parent::setUp(); $this->setUpVfsDir(); - - $this->fileSystem = new FileSystem(); } /** * {@@inheritdoc} */ - protected function getInstance($level = LogLevel::DEBUG) + protected function getInstance($level = LogLevel::DEBUG, $logfile = 'friendica.log') { - $this->logfile = vfsStream::newFile('friendica.log') + $this->logfile = vfsStream::newFile($logfile) ->at($this->root); $this->config->shouldReceive('get')->with('system', 'logfile')->andReturn($this->logfile->url())->once(); + $this->config->shouldReceive('get')->with('system', 'loglevel')->andReturn($level)->once(); - $logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem, $level); - - return $logger; + $loggerFactory = new \Friendica\Core\Logger\Factory\StreamLogger($this->introspection, 'test'); + return $loggerFactory->create($this->config); } /** @@ -83,11 +74,12 @@ class StreamLoggerTest extends AbstractLoggerTest public function testNoUrl() { $this->expectException(LoggerArgumentException::class); - $this->expectExceptionMessage("Missing stream URL."); + $this->expectExceptionMessage(' is not a valid logfile'); $this->config->shouldReceive('get')->with('system', 'logfile')->andReturn('')->once(); - $logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem); + $loggerFactory = new \Friendica\Core\Logger\Factory\StreamLogger($this->introspection, 'test'); + $logger = $loggerFactory->create($this->config); $logger->emergency('not working'); } @@ -104,7 +96,8 @@ class StreamLoggerTest extends AbstractLoggerTest $this->config->shouldReceive('get')->with('system', 'logfile')->andReturn($logfile->url())->once(); - $logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem); + $loggerFactory = new \Friendica\Core\Logger\Factory\StreamLogger($this->introspection, 'test'); + $logger = $loggerFactory->create($this->config); $logger->emergency('not working'); } @@ -119,7 +112,8 @@ class StreamLoggerTest extends AbstractLoggerTest static::markTestIncomplete('We need a platform independent way to set directory to readonly'); - $logger = new StreamLogger('test', '/$%/wrong/directory/file.txt', $this->introspection, $this->fileSystem); + $loggerFactory = new \Friendica\Core\Logger\Factory\StreamLogger($this->introspection, 'test'); + $logger = $loggerFactory->create($this->config); $logger->emergency('not working'); } @@ -132,9 +126,7 @@ class StreamLoggerTest extends AbstractLoggerTest $this->expectException(LogLevelException::class); $this->expectExceptionMessageMatches("/The level \".*\" is not valid./"); - $this->config->shouldReceive('get')->with('system', 'logfile')->andReturn('file.text')->once(); - - $logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem, 'NOPE'); + $logger = $this->getInstance('NOPE'); } /** @@ -145,29 +137,11 @@ class StreamLoggerTest extends AbstractLoggerTest $this->expectException(LogLevelException::class); $this->expectExceptionMessageMatches("/The level \".*\" is not valid./"); - $logfile = vfsStream::newFile('friendica.log') - ->at($this->root); - - $this->config->shouldReceive('get')->with('system', 'logfile')->andReturn($logfile->url())->once(); - - $logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem); + $logger = $this->getInstance('NOPE'); $logger->log('NOPE', 'a test'); } - /** - * Test when the file is null - */ - public function testWrongFile() - { - $this->expectException(LoggerArgumentException::class); - $this->expectExceptionMessage("A stream must either be a resource or a string."); - - $this->config->shouldReceive('get')->with('system', 'logfile')->andReturn(null)->once(); - - $logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem); - } - /** * Test a relative path * @doesNotPerformAssertions diff --git a/tests/src/Core/Logger/SyslogLoggerFactoryWrapper.php b/tests/src/Core/Logger/SyslogLoggerFactoryWrapper.php new file mode 100644 index 000000000..63209cad3 --- /dev/null +++ b/tests/src/Core/Logger/SyslogLoggerFactoryWrapper.php @@ -0,0 +1,46 @@ +. + * + */ + +namespace Friendica\Test\src\Core\Logger; + +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\Logger\Exception\LogLevelException; +use Friendica\Core\Logger\Factory\SyslogLogger; +use Friendica\Core\Logger\Type\SyslogLogger as SyslogLoggerClass; +use Psr\Log\LoggerInterface; + +class SyslogLoggerFactoryWrapper extends SyslogLogger +{ + public function create(IManageConfigValues $config): LoggerInterface + { + $logOpts = $config->get('system', 'syslog_flags') ?? SyslogLoggerClass::DEFAULT_FLAGS; + $logFacility = $config->get('system', 'syslog_facility') ?? SyslogLoggerClass::DEFAULT_FACILITY; + $loglevel = SyslogLogger::mapLegacyConfigDebugLevel($config->get('system', 'loglevel')); + + if (array_key_exists($loglevel, SyslogLoggerClass::logLevels)) { + $loglevel = SyslogLoggerClass::logLevels[$loglevel]; + } else { + throw new LogLevelException(sprintf('The level "%s" is not valid.', $loglevel)); + } + + return new SyslogLoggerWrapper($this->channel, $this->introspection, $loglevel, $logOpts, $logFacility); + } +} diff --git a/tests/src/Core/Logger/SyslogLoggerTest.php b/tests/src/Core/Logger/SyslogLoggerTest.php index 53ace79c0..c22aecc10 100644 --- a/tests/src/Core/Logger/SyslogLoggerTest.php +++ b/tests/src/Core/Logger/SyslogLoggerTest.php @@ -21,8 +21,6 @@ namespace Friendica\Test\src\Core\Logger; -use Friendica\Core\Logger\Exception\LoggerArgumentException; -use Friendica\Core\Logger\Exception\LoggerException; use Friendica\Core\Logger\Exception\LogLevelException; use Friendica\Core\Logger\Type\SyslogLogger; use Psr\Log\LogLevel; @@ -58,7 +56,10 @@ class SyslogLoggerTest extends AbstractLoggerTest */ protected function getInstance($level = LogLevel::DEBUG) { - $this->logger = new SyslogLoggerWrapper('test', $this->config, $this->introspection, $level); + $this->config->shouldReceive('get')->with('system', 'loglevel')->andReturn($level); + + $loggerFactory = new SyslogLoggerFactoryWrapper($this->introspection, 'test'); + $this->logger = $loggerFactory->create($this->config); return $this->logger; } @@ -71,8 +72,8 @@ class SyslogLoggerTest extends AbstractLoggerTest { $this->expectException(LogLevelException::class); $this->expectExceptionMessageMatches("/The level \".*\" is not valid./"); - - $logger = new SyslogLoggerWrapper('test', $this->config, $this->introspection, 'NOPE'); + + $logger = $this->getInstance('NOPE'); } /** @@ -83,7 +84,7 @@ class SyslogLoggerTest extends AbstractLoggerTest $this->expectException(LogLevelException::class); $this->expectExceptionMessageMatches("/The level \".*\" is not valid./"); - $logger = new SyslogLoggerWrapper('test', $this->config, $this->introspection); + $logger = $this->getInstance(); $logger->log('NOPE', 'a test'); } @@ -94,7 +95,7 @@ class SyslogLoggerTest extends AbstractLoggerTest */ public function testClose() { - $logger = new SyslogLoggerWrapper('test', $this->config, $this->introspection); + $logger = $this->getInstance(); $logger->emergency('test'); $logger->close(); // Reopened itself diff --git a/tests/src/Core/Logger/SyslogLoggerWrapper.php b/tests/src/Core/Logger/SyslogLoggerWrapper.php index 9fd16706a..1e0c4535f 100644 --- a/tests/src/Core/Logger/SyslogLoggerWrapper.php +++ b/tests/src/Core/Logger/SyslogLoggerWrapper.php @@ -21,10 +21,8 @@ namespace Friendica\Test\src\Core\Logger; -use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\Logger\Capabilities\IHaveCallIntrospections; use Friendica\Core\Logger\Type\SyslogLogger; -use Friendica\Core\Logger\Util\Introspection; -use Psr\Log\LogLevel; /** * Wraps the SyslogLogger for replacing the syslog call with a string field. @@ -33,9 +31,9 @@ class SyslogLoggerWrapper extends SyslogLogger { private $content; - public function __construct($channel, IManageConfigValues $config, Introspection $introspection, $level = LogLevel::NOTICE) + public function __construct(string $channel, IHaveCallIntrospections $introspection, string $logLevel, string $logOptions, string $logFacility) { - parent::__construct($channel, $config, $introspection, $level); + parent::__construct($channel, $introspection, $logLevel, $logOptions, $logFacility); $this->content = ''; } diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po index 445ffcbd3..a017e59dc 100644 --- a/view/lang/C/messages.po +++ b/view/lang/C/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 2023.09-dev\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-07-09 18:36-0400\n" +"POT-Creation-Date: 2023-07-16 16:40+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -292,9 +292,9 @@ msgid "Insert web link" msgstr "" #: mod/message.php:201 mod/message.php:357 mod/photos.php:1301 -#: src/Content/Conversation.php:392 src/Content/Conversation.php:1506 +#: src/Content/Conversation.php:392 src/Content/Conversation.php:1508 #: src/Module/Item/Compose.php:206 src/Module/Post/Edit.php:145 -#: src/Module/Profile/UnkMail.php:154 src/Object/Post.php:574 +#: src/Module/Profile/UnkMail.php:154 src/Object/Post.php:571 msgid "Please wait" msgstr "" @@ -315,7 +315,7 @@ msgstr "" #: src/Module/Moderation/Report/Create.php:211 #: src/Module/Moderation/Report/Create.php:263 #: src/Module/Profile/Profile.php:274 src/Module/Profile/UnkMail.php:155 -#: src/Module/Settings/Profile/Index.php:230 src/Object/Post.php:1090 +#: src/Module/Settings/Profile/Index.php:230 src/Object/Post.php:1087 #: view/theme/duepuntozero/config.php:85 view/theme/frio/config.php:171 #: view/theme/quattro/config.php:87 view/theme/vier/config.php:135 msgid "Submit" @@ -600,33 +600,33 @@ msgstr "" #: mod/photos.php:1139 mod/photos.php:1195 mod/photos.php:1275 #: src/Module/Contact.php:619 src/Module/Item/Compose.php:188 -#: src/Object/Post.php:1087 +#: src/Object/Post.php:1084 msgid "This is you" msgstr "" #: mod/photos.php:1141 mod/photos.php:1197 mod/photos.php:1277 -#: src/Object/Post.php:568 src/Object/Post.php:1089 +#: src/Object/Post.php:565 src/Object/Post.php:1086 msgid "Comment" msgstr "" #: mod/photos.php:1143 mod/photos.php:1199 mod/photos.php:1279 #: src/Content/Conversation.php:407 src/Module/Calendar/Event/Form.php:248 #: src/Module/Item/Compose.php:201 src/Module/Post/Edit.php:165 -#: src/Object/Post.php:1103 +#: src/Object/Post.php:1100 msgid "Preview" msgstr "" #: mod/photos.php:1144 src/Content/Conversation.php:360 -#: src/Module/Post/Edit.php:130 src/Object/Post.php:1091 +#: src/Module/Post/Edit.php:130 src/Object/Post.php:1088 msgid "Loading..." msgstr "" -#: mod/photos.php:1236 src/Content/Conversation.php:1422 -#: src/Object/Post.php:263 +#: mod/photos.php:1236 src/Content/Conversation.php:1424 +#: src/Object/Post.php:260 msgid "Select" msgstr "" -#: mod/photos.php:1237 src/Content/Conversation.php:1423 +#: mod/photos.php:1237 src/Content/Conversation.php:1425 #: src/Module/Moderation/Users/Active.php:136 #: src/Module/Moderation/Users/Blocked.php:136 #: src/Module/Moderation/Users/Index.php:151 @@ -634,19 +634,19 @@ msgstr "" msgid "Delete" msgstr "" -#: mod/photos.php:1298 src/Object/Post.php:405 +#: mod/photos.php:1298 src/Object/Post.php:402 msgid "Like" msgstr "" -#: mod/photos.php:1299 src/Object/Post.php:405 +#: mod/photos.php:1299 src/Object/Post.php:402 msgid "I like this (toggle)" msgstr "" -#: mod/photos.php:1300 src/Object/Post.php:406 +#: mod/photos.php:1300 src/Object/Post.php:403 msgid "Dislike" msgstr "" -#: mod/photos.php:1302 src/Object/Post.php:406 +#: mod/photos.php:1302 src/Object/Post.php:403 msgid "I don't like this (toggle)" msgstr "" @@ -662,97 +662,97 @@ msgstr "" msgid "Apologies but the website is unavailable at the moment." msgstr "" -#: src/App/Page.php:247 +#: src/App/Page.php:248 msgid "Delete this item?" msgstr "" -#: src/App/Page.php:248 +#: src/App/Page.php:249 msgid "" "Block this author? They won't be able to follow you nor see your public " "posts, and you won't be able to see their posts and their notifications." msgstr "" -#: src/App/Page.php:249 +#: src/App/Page.php:250 msgid "" "Ignore this author? You won't be able to see their posts and their " "notifications." msgstr "" -#: src/App/Page.php:250 +#: src/App/Page.php:251 msgid "Collapse this author's posts?" msgstr "" -#: src/App/Page.php:252 +#: src/App/Page.php:253 msgid "Like not successful" msgstr "" -#: src/App/Page.php:253 +#: src/App/Page.php:254 msgid "Dislike not successful" msgstr "" -#: src/App/Page.php:254 +#: src/App/Page.php:255 msgid "Sharing not successful" msgstr "" -#: src/App/Page.php:255 +#: src/App/Page.php:256 msgid "Attendance unsuccessful" msgstr "" -#: src/App/Page.php:256 +#: src/App/Page.php:257 msgid "Backend error" msgstr "" -#: src/App/Page.php:257 +#: src/App/Page.php:258 msgid "Network error" msgstr "" -#: src/App/Page.php:260 +#: src/App/Page.php:261 msgid "Drop files here to upload" msgstr "" -#: src/App/Page.php:261 +#: src/App/Page.php:262 msgid "Your browser does not support drag and drop file uploads." msgstr "" -#: src/App/Page.php:262 +#: src/App/Page.php:263 msgid "" "Please use the fallback form below to upload your files like in the olden " "days." msgstr "" -#: src/App/Page.php:263 +#: src/App/Page.php:264 msgid "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB." msgstr "" -#: src/App/Page.php:264 +#: src/App/Page.php:265 msgid "You can't upload files of this type." msgstr "" -#: src/App/Page.php:265 +#: src/App/Page.php:266 msgid "Server responded with {{statusCode}} code." msgstr "" -#: src/App/Page.php:266 +#: src/App/Page.php:267 msgid "Cancel upload" msgstr "" -#: src/App/Page.php:267 +#: src/App/Page.php:268 msgid "Upload canceled." msgstr "" -#: src/App/Page.php:268 +#: src/App/Page.php:269 msgid "Are you sure you want to cancel this upload?" msgstr "" -#: src/App/Page.php:269 +#: src/App/Page.php:270 msgid "Remove file" msgstr "" -#: src/App/Page.php:270 +#: src/App/Page.php:271 msgid "You can't upload any more files." msgstr "" -#: src/App/Page.php:348 +#: src/App/Page.php:349 msgid "toggle mobile" msgstr "" @@ -769,31 +769,31 @@ msgstr "" msgid "You must be logged in to use addons. " msgstr "" -#: src/BaseModule.php:400 +#: src/BaseModule.php:401 msgid "" "The form security token was not correct. This probably happened because the " "form has been opened for too long (>3 hours) before submitting it." msgstr "" -#: src/BaseModule.php:427 +#: src/BaseModule.php:428 msgid "All contacts" msgstr "" -#: src/BaseModule.php:432 src/Content/Widget.php:243 src/Core/ACL.php:195 +#: src/BaseModule.php:433 src/Content/Widget.php:243 src/Core/ACL.php:195 #: src/Module/Contact.php:415 src/Module/PermissionTooltip.php:127 #: src/Module/PermissionTooltip.php:149 msgid "Followers" msgstr "" -#: src/BaseModule.php:437 src/Content/Widget.php:244 src/Module/Contact.php:418 +#: src/BaseModule.php:438 src/Content/Widget.php:244 src/Module/Contact.php:418 msgid "Following" msgstr "" -#: src/BaseModule.php:442 src/Content/Widget.php:245 src/Module/Contact.php:421 +#: src/BaseModule.php:443 src/Content/Widget.php:245 src/Module/Contact.php:421 msgid "Mutual friends" msgstr "" -#: src/BaseModule.php:450 +#: src/BaseModule.php:451 msgid "Common" msgstr "" @@ -1226,7 +1226,7 @@ msgid "Visible to everybody" msgstr "" #: src/Content/Conversation.php:330 src/Module/Item/Compose.php:200 -#: src/Object/Post.php:1102 +#: src/Object/Post.php:1099 msgid "Please enter a image/video/audio/webpage URL:" msgstr "" @@ -1271,52 +1271,52 @@ msgid "attach file" msgstr "" #: src/Content/Conversation.php:365 src/Module/Item/Compose.php:190 -#: src/Module/Post/Edit.php:171 src/Object/Post.php:1092 +#: src/Module/Post/Edit.php:171 src/Object/Post.php:1089 msgid "Bold" msgstr "" #: src/Content/Conversation.php:366 src/Module/Item/Compose.php:191 -#: src/Module/Post/Edit.php:172 src/Object/Post.php:1093 +#: src/Module/Post/Edit.php:172 src/Object/Post.php:1090 msgid "Italic" msgstr "" #: src/Content/Conversation.php:367 src/Module/Item/Compose.php:192 -#: src/Module/Post/Edit.php:173 src/Object/Post.php:1094 +#: src/Module/Post/Edit.php:173 src/Object/Post.php:1091 msgid "Underline" msgstr "" #: src/Content/Conversation.php:368 src/Module/Item/Compose.php:193 -#: src/Module/Post/Edit.php:174 src/Object/Post.php:1096 +#: src/Module/Post/Edit.php:174 src/Object/Post.php:1093 msgid "Quote" msgstr "" #: src/Content/Conversation.php:369 src/Module/Item/Compose.php:194 -#: src/Module/Post/Edit.php:175 src/Object/Post.php:1097 +#: src/Module/Post/Edit.php:175 src/Object/Post.php:1094 msgid "Add emojis" msgstr "" #: src/Content/Conversation.php:370 src/Module/Item/Compose.php:195 -#: src/Object/Post.php:1095 +#: src/Object/Post.php:1092 msgid "Content Warning" msgstr "" #: src/Content/Conversation.php:371 src/Module/Item/Compose.php:196 -#: src/Module/Post/Edit.php:176 src/Object/Post.php:1098 +#: src/Module/Post/Edit.php:176 src/Object/Post.php:1095 msgid "Code" msgstr "" #: src/Content/Conversation.php:372 src/Module/Item/Compose.php:197 -#: src/Object/Post.php:1099 +#: src/Object/Post.php:1096 msgid "Image" msgstr "" #: src/Content/Conversation.php:373 src/Module/Item/Compose.php:198 -#: src/Module/Post/Edit.php:177 src/Object/Post.php:1100 +#: src/Module/Post/Edit.php:177 src/Object/Post.php:1097 msgid "Link" msgstr "" #: src/Content/Conversation.php:374 src/Module/Item/Compose.php:199 -#: src/Module/Post/Edit.php:178 src/Object/Post.php:1101 +#: src/Module/Post/Edit.php:178 src/Object/Post.php:1098 msgid "Link or Media" msgstr "" @@ -1386,111 +1386,111 @@ msgstr "" msgid "Delete Selected Items" msgstr "" -#: src/Content/Conversation.php:724 src/Content/Conversation.php:727 -#: src/Content/Conversation.php:730 src/Content/Conversation.php:733 -#: src/Content/Conversation.php:736 +#: src/Content/Conversation.php:728 src/Content/Conversation.php:731 +#: src/Content/Conversation.php:734 src/Content/Conversation.php:737 +#: src/Content/Conversation.php:740 #, php-format msgid "You had been addressed (%s)." msgstr "" -#: src/Content/Conversation.php:739 +#: src/Content/Conversation.php:743 #, php-format msgid "You are following %s." msgstr "" -#: src/Content/Conversation.php:742 +#: src/Content/Conversation.php:746 msgid "You subscribed to one or more tags in this post." msgstr "" -#: src/Content/Conversation.php:761 +#: src/Content/Conversation.php:765 #, php-format msgid "%s reshared this." msgstr "" -#: src/Content/Conversation.php:763 +#: src/Content/Conversation.php:767 msgid "Reshared" msgstr "" -#: src/Content/Conversation.php:763 +#: src/Content/Conversation.php:767 #, php-format msgid "Reshared by %s <%s>" msgstr "" -#: src/Content/Conversation.php:766 +#: src/Content/Conversation.php:770 #, php-format msgid "%s is participating in this thread." msgstr "" -#: src/Content/Conversation.php:769 +#: src/Content/Conversation.php:773 msgid "Stored for general reasons" msgstr "" -#: src/Content/Conversation.php:772 +#: src/Content/Conversation.php:776 msgid "Global post" msgstr "" -#: src/Content/Conversation.php:775 +#: src/Content/Conversation.php:779 msgid "Sent via an relay server" msgstr "" -#: src/Content/Conversation.php:775 +#: src/Content/Conversation.php:779 #, php-format msgid "Sent via the relay server %s <%s>" msgstr "" -#: src/Content/Conversation.php:778 +#: src/Content/Conversation.php:782 msgid "Fetched" msgstr "" -#: src/Content/Conversation.php:778 +#: src/Content/Conversation.php:782 #, php-format msgid "Fetched because of %s <%s>" msgstr "" -#: src/Content/Conversation.php:781 +#: src/Content/Conversation.php:785 msgid "Stored because of a child post to complete this thread." msgstr "" -#: src/Content/Conversation.php:784 +#: src/Content/Conversation.php:788 msgid "Local delivery" msgstr "" -#: src/Content/Conversation.php:787 +#: src/Content/Conversation.php:791 msgid "Stored because of your activity (like, comment, star, ...)" msgstr "" -#: src/Content/Conversation.php:790 +#: src/Content/Conversation.php:794 msgid "Distributed" msgstr "" -#: src/Content/Conversation.php:793 +#: src/Content/Conversation.php:797 msgid "Pushed to us" msgstr "" -#: src/Content/Conversation.php:1450 src/Object/Post.php:248 +#: src/Content/Conversation.php:1452 src/Object/Post.php:248 msgid "Pinned item" msgstr "" -#: src/Content/Conversation.php:1466 src/Object/Post.php:518 -#: src/Object/Post.php:519 +#: src/Content/Conversation.php:1468 src/Object/Post.php:515 +#: src/Object/Post.php:516 #, php-format msgid "View %s's profile @ %s" msgstr "" -#: src/Content/Conversation.php:1479 src/Object/Post.php:506 +#: src/Content/Conversation.php:1481 src/Object/Post.php:503 msgid "Categories:" msgstr "" -#: src/Content/Conversation.php:1480 src/Object/Post.php:507 +#: src/Content/Conversation.php:1482 src/Object/Post.php:504 msgid "Filed under:" msgstr "" -#: src/Content/Conversation.php:1488 src/Object/Post.php:532 +#: src/Content/Conversation.php:1490 src/Object/Post.php:529 #, php-format msgid "%s from %s" msgstr "" -#: src/Content/Conversation.php:1504 +#: src/Content/Conversation.php:1506 msgid "View in context" msgstr "" @@ -1700,7 +1700,7 @@ msgstr "" msgid "Collapse" msgstr "" -#: src/Content/Item.php:434 src/Object/Post.php:487 +#: src/Content/Item.php:434 src/Object/Post.php:484 msgid "Languages" msgstr "" @@ -2833,6 +2833,16 @@ msgstr "" msgid "Dec" msgstr "" +#: src/Core/Logger/Util/LoggerSettingsCheck.php:60 +#, php-format +msgid "The logfile '%s' is not usable. No logging possible (error: '%s')" +msgstr "" + +#: src/Core/Logger/Util/LoggerSettingsCheck.php:85 +#, php-format +msgid "The debug logfile '%s' is not usable. No logging possible (error: '%s')" +msgstr "" + #: src/Core/Renderer.php:89 src/Core/Renderer.php:118 src/Core/Renderer.php:147 #: src/Core/Renderer.php:181 src/Render/FriendicaSmartyEngine.php:60 msgid "" @@ -3407,7 +3417,7 @@ msgstr "" msgid "Title/Description:" msgstr "" -#: src/Model/Profile.php:1025 src/Module/Admin/Summary.php:221 +#: src/Model/Profile.php:1025 src/Module/Admin/Summary.php:197 #: src/Module/Moderation/Report/Create.php:280 #: src/Module/Moderation/Summary.php:77 msgid "Summary" @@ -3735,7 +3745,7 @@ msgstr "" #: src/Module/Admin/Federation.php:210 src/Module/Admin/Logs/Settings.php:85 #: src/Module/Admin/Logs/View.php:83 src/Module/Admin/Queue.php:72 #: src/Module/Admin/Site.php:398 src/Module/Admin/Storage.php:138 -#: src/Module/Admin/Summary.php:220 src/Module/Admin/Themes/Details.php:90 +#: src/Module/Admin/Summary.php:196 src/Module/Admin/Themes/Details.php:90 #: src/Module/Admin/Themes/Index.php:111 src/Module/Admin/Tos.php:77 #: src/Module/Moderation/Users/Create.php:61 #: src/Module/Moderation/Users/Pending.php:96 @@ -5224,50 +5234,40 @@ msgid "" "href=\"%s\">the installation page for help." msgstr "" -#: src/Module/Admin/Summary.php:142 -#, php-format -msgid "The logfile '%s' is not usable. No logging possible (error: '%s')" -msgstr "" - -#: src/Module/Admin/Summary.php:156 -#, php-format -msgid "The debug logfile '%s' is not usable. No logging possible (error: '%s')" -msgstr "" - -#: src/Module/Admin/Summary.php:172 +#: src/Module/Admin/Summary.php:148 #, php-format msgid "" "Friendica's system.basepath was updated from '%s' to '%s'. Please remove the " "system.basepath from your db to avoid differences." msgstr "" -#: src/Module/Admin/Summary.php:180 +#: src/Module/Admin/Summary.php:156 #, php-format msgid "" "Friendica's current system.basepath '%s' is wrong and the config file '%s' " "isn't used." msgstr "" -#: src/Module/Admin/Summary.php:188 +#: src/Module/Admin/Summary.php:164 #, php-format msgid "" "Friendica's current system.basepath '%s' is not equal to the config file " "'%s'. Please fix your configuration." msgstr "" -#: src/Module/Admin/Summary.php:199 +#: src/Module/Admin/Summary.php:175 msgid "Message queues" msgstr "" -#: src/Module/Admin/Summary.php:205 +#: src/Module/Admin/Summary.php:181 msgid "Server Settings" msgstr "" -#: src/Module/Admin/Summary.php:223 +#: src/Module/Admin/Summary.php:199 msgid "Version" msgstr "" -#: src/Module/Admin/Summary.php:227 +#: src/Module/Admin/Summary.php:203 msgid "Active addons" msgstr "" @@ -5878,7 +5878,7 @@ msgid "Only show blocked contacts" msgstr "" #: src/Module/Contact.php:369 src/Module/Contact.php:441 -#: src/Object/Post.php:365 +#: src/Object/Post.php:362 msgid "Ignored" msgstr "" @@ -6562,7 +6562,7 @@ msgstr "" msgid "Posts that mention or involve you" msgstr "" -#: src/Module/Conversation/Network.php:289 src/Object/Post.php:377 +#: src/Module/Conversation/Network.php:289 src/Object/Post.php:374 msgid "Starred" msgstr "" @@ -11500,234 +11500,234 @@ msgstr "" msgid "Edit" msgstr "" -#: src/Object/Post.php:252 +#: src/Object/Post.php:261 msgid "Delete globally" msgstr "" -#: src/Object/Post.php:252 +#: src/Object/Post.php:261 msgid "Remove locally" msgstr "" -#: src/Object/Post.php:271 +#: src/Object/Post.php:268 #, php-format msgid "Block %s" msgstr "" -#: src/Object/Post.php:276 +#: src/Object/Post.php:273 #, php-format msgid "Ignore %s" msgstr "" -#: src/Object/Post.php:281 +#: src/Object/Post.php:278 #, php-format msgid "Collapse %s" msgstr "" -#: src/Object/Post.php:285 +#: src/Object/Post.php:282 msgid "Report post" msgstr "" -#: src/Object/Post.php:290 +#: src/Object/Post.php:287 msgid "Save to folder" msgstr "" -#: src/Object/Post.php:330 +#: src/Object/Post.php:327 msgid "I will attend" msgstr "" -#: src/Object/Post.php:330 +#: src/Object/Post.php:327 msgid "I will not attend" msgstr "" -#: src/Object/Post.php:330 +#: src/Object/Post.php:327 msgid "I might attend" msgstr "" -#: src/Object/Post.php:360 +#: src/Object/Post.php:357 msgid "Ignore thread" msgstr "" -#: src/Object/Post.php:361 +#: src/Object/Post.php:358 msgid "Unignore thread" msgstr "" -#: src/Object/Post.php:362 +#: src/Object/Post.php:359 msgid "Toggle ignore status" msgstr "" -#: src/Object/Post.php:372 +#: src/Object/Post.php:369 msgid "Add star" msgstr "" -#: src/Object/Post.php:373 +#: src/Object/Post.php:370 msgid "Remove star" msgstr "" -#: src/Object/Post.php:374 +#: src/Object/Post.php:371 msgid "Toggle star status" msgstr "" -#: src/Object/Post.php:385 +#: src/Object/Post.php:382 msgid "Pin" msgstr "" -#: src/Object/Post.php:386 +#: src/Object/Post.php:383 msgid "Unpin" msgstr "" -#: src/Object/Post.php:387 +#: src/Object/Post.php:384 msgid "Toggle pin status" msgstr "" -#: src/Object/Post.php:390 +#: src/Object/Post.php:387 msgid "Pinned" msgstr "" -#: src/Object/Post.php:395 +#: src/Object/Post.php:392 msgid "Add tag" msgstr "" -#: src/Object/Post.php:408 +#: src/Object/Post.php:405 msgid "Quote share this" msgstr "" -#: src/Object/Post.php:408 +#: src/Object/Post.php:405 msgid "Quote Share" msgstr "" -#: src/Object/Post.php:411 +#: src/Object/Post.php:408 msgid "Reshare this" msgstr "" -#: src/Object/Post.php:411 +#: src/Object/Post.php:408 msgid "Reshare" msgstr "" -#: src/Object/Post.php:412 +#: src/Object/Post.php:409 msgid "Cancel your Reshare" msgstr "" -#: src/Object/Post.php:412 +#: src/Object/Post.php:409 msgid "Unshare" msgstr "" -#: src/Object/Post.php:463 +#: src/Object/Post.php:460 #, php-format msgid "%s (Received %s)" msgstr "" -#: src/Object/Post.php:469 +#: src/Object/Post.php:466 msgid "Comment this item on your system" msgstr "" -#: src/Object/Post.php:469 +#: src/Object/Post.php:466 msgid "Remote comment" msgstr "" -#: src/Object/Post.php:491 +#: src/Object/Post.php:488 msgid "Share via ..." msgstr "" -#: src/Object/Post.php:491 +#: src/Object/Post.php:488 msgid "Share via external services" msgstr "" -#: src/Object/Post.php:520 +#: src/Object/Post.php:517 msgid "to" msgstr "" -#: src/Object/Post.php:521 +#: src/Object/Post.php:518 msgid "via" msgstr "" -#: src/Object/Post.php:522 +#: src/Object/Post.php:519 msgid "Wall-to-Wall" msgstr "" -#: src/Object/Post.php:523 +#: src/Object/Post.php:520 msgid "via Wall-To-Wall:" msgstr "" -#: src/Object/Post.php:569 +#: src/Object/Post.php:566 #, php-format msgid "Reply to %s" msgstr "" -#: src/Object/Post.php:572 +#: src/Object/Post.php:569 msgid "More" msgstr "" -#: src/Object/Post.php:590 +#: src/Object/Post.php:587 msgid "Notifier task is pending" msgstr "" -#: src/Object/Post.php:591 +#: src/Object/Post.php:588 msgid "Delivery to remote servers is pending" msgstr "" -#: src/Object/Post.php:592 +#: src/Object/Post.php:589 msgid "Delivery to remote servers is underway" msgstr "" -#: src/Object/Post.php:593 +#: src/Object/Post.php:590 msgid "Delivery to remote servers is mostly done" msgstr "" -#: src/Object/Post.php:594 +#: src/Object/Post.php:591 msgid "Delivery to remote servers is done" msgstr "" -#: src/Object/Post.php:614 +#: src/Object/Post.php:611 #, php-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "" msgstr[1] "" -#: src/Object/Post.php:615 +#: src/Object/Post.php:612 msgid "Show more" msgstr "" -#: src/Object/Post.php:616 +#: src/Object/Post.php:613 msgid "Show fewer" msgstr "" -#: src/Object/Post.php:652 +#: src/Object/Post.php:649 #, php-format msgid "Reshared by: %s" msgstr "" -#: src/Object/Post.php:657 +#: src/Object/Post.php:654 #, php-format msgid "Viewed by: %s" msgstr "" -#: src/Object/Post.php:662 +#: src/Object/Post.php:659 #, php-format msgid "Liked by: %s" msgstr "" -#: src/Object/Post.php:667 +#: src/Object/Post.php:664 #, php-format msgid "Disliked by: %s" msgstr "" -#: src/Object/Post.php:672 +#: src/Object/Post.php:669 #, php-format msgid "Attended by: %s" msgstr "" -#: src/Object/Post.php:677 +#: src/Object/Post.php:674 #, php-format msgid "Maybe attended by: %s" msgstr "" -#: src/Object/Post.php:682 +#: src/Object/Post.php:679 #, php-format msgid "Not attended by: %s" msgstr "" -#: src/Object/Post.php:687 +#: src/Object/Post.php:684 #, php-format msgid "Reacted with %s by: %s" msgstr ""