mirror of
https://github.com/friendica/friendica
synced 2024-12-23 05:20:15 +00:00
Merge pull request #13246 from nupplaphil/feat/addons
Introduce dynamic hook loading
This commit is contained in:
commit
aee420152f
49 changed files with 1871 additions and 821 deletions
|
@ -7,9 +7,9 @@ matrix:
|
||||||
- PHP_MAJOR_VERSION: 8.0
|
- PHP_MAJOR_VERSION: 8.0
|
||||||
PHP_VERSION: 8.0.29
|
PHP_VERSION: 8.0.29
|
||||||
- PHP_MAJOR_VERSION: 8.1
|
- PHP_MAJOR_VERSION: 8.1
|
||||||
PHP_VERSION: 8.1.20
|
PHP_VERSION: 8.1.21
|
||||||
- PHP_MAJOR_VERSION: 8.2
|
- 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...)
|
# This forces PHP Unit executions at the "opensocial" labeled location (because of much more power...)
|
||||||
labels:
|
labels:
|
||||||
|
|
|
@ -58,6 +58,7 @@ if (php_sapi_name() !== 'cli') {
|
||||||
|
|
||||||
use Dice\Dice;
|
use Dice\Dice;
|
||||||
use Friendica\App\Mode;
|
use Friendica\App\Mode;
|
||||||
|
use Friendica\Core\Logger\Capabilities\LogChannel;
|
||||||
use Friendica\Security\ExAuth;
|
use Friendica\Security\ExAuth;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
@ -78,7 +79,10 @@ chdir($directory);
|
||||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
$dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.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\DI::init($dice);
|
||||||
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));
|
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));
|
||||||
|
|
|
@ -26,13 +26,17 @@ if (php_sapi_name() !== 'cli') {
|
||||||
}
|
}
|
||||||
|
|
||||||
use Dice\Dice;
|
use Dice\Dice;
|
||||||
|
use Friendica\Core\Logger\Capabilities\LogChannel;
|
||||||
use Friendica\DI;
|
use Friendica\DI;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
$dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.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
|
/// @fixme Necessary until Hooks inside the Logger can get loaded without the DI-class
|
||||||
DI::init($dice);
|
DI::init($dice);
|
||||||
|
|
|
@ -60,7 +60,10 @@ if (!file_exists('index.php') && (sizeof($_SERVER['argv']) != 0)) {
|
||||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
$dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.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);
|
DI::init($dice);
|
||||||
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));
|
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));
|
||||||
|
|
|
@ -29,6 +29,7 @@ if (php_sapi_name() !== 'cli') {
|
||||||
use Dice\Dice;
|
use Dice\Dice;
|
||||||
use Friendica\App;
|
use Friendica\App;
|
||||||
use Friendica\App\Mode;
|
use Friendica\App\Mode;
|
||||||
|
use Friendica\Core\Logger\Capabilities\LogChannel;
|
||||||
use Friendica\Core\Update;
|
use Friendica\Core\Update;
|
||||||
use Friendica\Core\Worker;
|
use Friendica\Core\Worker;
|
||||||
use Friendica\DI;
|
use Friendica\DI;
|
||||||
|
@ -54,7 +55,10 @@ if (!file_exists("index.php") && (sizeof($_SERVER["argv"]) != 0)) {
|
||||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||||
|
|
||||||
$dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.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);
|
DI::init($dice);
|
||||||
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));
|
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));
|
||||||
|
|
|
@ -134,6 +134,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "phpunit",
|
"test": "phpunit",
|
||||||
"lint": "find . -name \\*.php -not -path './vendor/*' -not -path './view/asset/*' -print0 | xargs -0 -n1 php -l",
|
"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:install": "@composer install --working-dir=bin/dev/php-cs-fixer",
|
||||||
"cs:check": [
|
"cs:check": [
|
||||||
"@cs:install",
|
"@cs:install",
|
||||||
|
|
89
doc/StrategyHooks.md
Normal file
89
doc/StrategyHooks.md
Normal file
|
@ -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.
|
|
@ -30,6 +30,9 @@ if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
|
||||||
require __DIR__ . '/vendor/autoload.php';
|
require __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
$dice = (new Dice())->addRules(include __DIR__ . '/static/dependencies.config.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]]]);
|
$dice = $dice->addRule(Friendica\App\Mode::class, ['call' => [['determineRunMode', [false, $_SERVER], Dice::CHAIN_CALL]]]);
|
||||||
|
|
||||||
\Friendica\DI::init($dice);
|
\Friendica\DI::init($dice);
|
||||||
|
|
37
src/Core/Addon/Capabilities/ICanLoadAddons.php
Normal file
37
src/Core/Addon/Capabilities/ICanLoadAddons.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
35
src/Core/Addon/Exception/AddonInvalidConfigFileException.php
Normal file
35
src/Core/Addon/Exception/AddonInvalidConfigFileException.php
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
70
src/Core/Addon/Model/AddonLoader.php
Normal file
70
src/Core/Addon/Model/AddonLoader.php
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
37
src/Core/Hooks/Capabilities/BehavioralHookType.php
Normal file
37
src/Core/Hooks/Capabilities/BehavioralHookType.php
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
41
src/Core/Hooks/Capabilities/ICanCreateInstances.php
Normal file
41
src/Core/Hooks/Capabilities/ICanCreateInstances.php
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
|
@ -1,81 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* @copyright Copyright (C) 2010-2023, the Friendica project
|
|
||||||
*
|
|
||||||
* @license GNU AGPL version 3 or any later version
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
46
src/Core/Hooks/Capabilities/ICanRegisterStrategies.php
Normal file
46
src/Core/Hooks/Capabilities/ICanRegisterStrategies.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
|
@ -19,13 +19,11 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Friendica\Core\Logger\Exception;
|
namespace Friendica\Core\Hooks\Exceptions;
|
||||||
|
|
||||||
use Throwable;
|
class HookConfigException extends \RuntimeException
|
||||||
|
|
||||||
class LoggerInvalidException extends \RuntimeException
|
|
||||||
{
|
{
|
||||||
public function __construct($message = "", Throwable $previous = null)
|
public function __construct($message = '', \Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct($message, 500, $previous);
|
parent::__construct($message, 500, $previous);
|
||||||
}
|
}
|
70
src/Core/Hooks/Model/DiceInstanceManager.php
Normal file
70
src/Core/Hooks/Model/DiceInstanceManager.php
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,104 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* @copyright Copyright (C) 2010-2023, the Friendica project
|
|
||||||
*
|
|
||||||
* @license GNU AGPL version 3 or any later version
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
95
src/Core/Hooks/Util/StrategiesFileManager.php
Normal file
95
src/Core/Hooks/Util/StrategiesFileManager.php
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
42
src/Core/Logger/Capabilities/ICheckLoggerSettings.php
Normal file
42
src/Core/Logger/Capabilities/ICheckLoggerSettings.php
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
43
src/Core/Logger/Capabilities/LogChannel.php
Normal file
43
src/Core/Logger/Capabilities/LogChannel.php
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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';
|
||||||
|
}
|
|
@ -23,9 +23,12 @@ namespace Friendica\Core\Logger\Exception;
|
||||||
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception in case the loglevel isn't set or isn't valid
|
||||||
|
*/
|
||||||
class LogLevelException extends \InvalidArgumentException
|
class LogLevelException extends \InvalidArgumentException
|
||||||
{
|
{
|
||||||
public function __construct($message = "", Throwable $previous = null)
|
public function __construct($message = '', Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct($message, 500, $previous);
|
parent::__construct($message, 500, $previous);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,12 @@ namespace Friendica\Core\Logger\Exception;
|
||||||
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception in case an argument of a logger class isn't valid
|
||||||
|
*/
|
||||||
class LoggerArgumentException extends \InvalidArgumentException
|
class LoggerArgumentException extends \InvalidArgumentException
|
||||||
{
|
{
|
||||||
public function __construct($message = "", Throwable $previous = null)
|
public function __construct($message = '', Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct($message, 500, $previous);
|
parent::__construct($message, 500, $previous);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,12 @@ namespace Friendica\Core\Logger\Exception;
|
||||||
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic exception of the logging namespace
|
||||||
|
*/
|
||||||
class LoggerException extends \Exception
|
class LoggerException extends \Exception
|
||||||
{
|
{
|
||||||
public function __construct($message = "", Throwable $previous = null)
|
public function __construct($message = '', Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct($message, 500, $previous);
|
parent::__construct($message, 500, $previous);
|
||||||
}
|
}
|
||||||
|
|
35
src/Core/Logger/Exception/LoggerUnusableException.php
Normal file
35
src/Core/Logger/Exception/LoggerUnusableException.php
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
80
src/Core/Logger/Factory/AbstractLoggerTypeFactory.php
Normal file
80
src/Core/Logger/Factory/AbstractLoggerTypeFactory.php
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,134 +22,46 @@
|
||||||
namespace Friendica\Core\Logger\Factory;
|
namespace Friendica\Core\Logger\Factory;
|
||||||
|
|
||||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||||
use Friendica\Core\Hooks\Capabilities\ICanManageInstances;
|
use Friendica\Core\Hooks\Capabilities\ICanCreateInstances;
|
||||||
use Friendica\Core\Logger\Exception\LogLevelException;
|
use Friendica\Core\Logger\Capabilities\LogChannel;
|
||||||
use Friendica\Core\Logger\Type\ProfilerLogger;
|
use Friendica\Core\Logger\Type\ProfilerLogger as ProfilerLoggerClass;
|
||||||
use Friendica\Core\Logger\Type\StreamLogger;
|
use Friendica\Util\Profiler;
|
||||||
use Friendica\Core\Logger\Type\SyslogLogger;
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Psr\Log\LogLevel;
|
|
||||||
use Psr\Log\NullLogger;
|
use Psr\Log\NullLogger;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A logger factory
|
* The logger factory for the core logging instances
|
||||||
*/
|
*/
|
||||||
class Logger
|
class Logger
|
||||||
{
|
{
|
||||||
const DEV_CHANNEL = 'dev';
|
/** @var string The channel */
|
||||||
|
|
||||||
/** @var string The log-channel (app, worker, ...) */
|
|
||||||
protected $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->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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function create(ICanCreateInstances $instanceCreator, IManageConfigValues $config, Profiler $profiler): LoggerInterface
|
||||||
* 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
|
|
||||||
{
|
{
|
||||||
if (empty($this->config->get('system', 'debugging') ?? false)) {
|
if (empty($config->get('system', 'debugging') ?? false)) {
|
||||||
return new NullLogger();
|
return new NullLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
$loglevel = $loglevel ?? static::mapLegacyConfigDebugLevel($this->config->get('system', 'loglevel'));
|
$name = $config->get('system', 'logger_config') ?? '';
|
||||||
$name = $this->config->get('system', 'logger_config') ?? 'stream';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
/** @var LoggerInterface */
|
/** @var LoggerInterface $logger */
|
||||||
return $this->instanceManager->getInstance(LoggerInterface::class, $name, [$this->channel, $loglevel]);
|
$logger = $instanceCreator->create(LoggerInterface::class, $name, [$this->channel]);
|
||||||
} catch (LogLevelException $exception) {
|
if ($config->get('system', 'profiling') ?? false) {
|
||||||
// If there's a wrong config value for loglevel, try again with standard
|
return new ProfilerLoggerClass($logger, $profiler);
|
||||||
$logger = $this->create(LogLevel::NOTICE);
|
} else {
|
||||||
$logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
|
|
||||||
return $logger;
|
return $logger;
|
||||||
} catch (\Throwable $e) {
|
}
|
||||||
|
} catch (Throwable $e) {
|
||||||
// No logger ...
|
// No logger ...
|
||||||
return new NullLogger();
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
100
src/Core/Logger/Factory/StreamLogger.php
Normal file
100
src/Core/Logger/Factory/StreamLogger.php
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
60
src/Core/Logger/Factory/SyslogLogger.php
Normal file
60
src/Core/Logger/Factory/SyslogLogger.php
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
|
|
@ -21,20 +21,16 @@
|
||||||
|
|
||||||
namespace Friendica\Core\Logger\Type;
|
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\Capabilities\IHaveCallIntrospections;
|
||||||
use Friendica\Core\Logger\Exception\LoggerArgumentException;
|
|
||||||
use Friendica\Core\Logger\Exception\LoggerException;
|
use Friendica\Core\Logger\Exception\LoggerException;
|
||||||
use Friendica\Core\Logger\Exception\LogLevelException;
|
use Friendica\Core\Logger\Exception\LogLevelException;
|
||||||
use Friendica\Util\DateTimeFormat;
|
use Friendica\Util\DateTimeFormat;
|
||||||
use Friendica\Util\FileSystem;
|
|
||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Logger instance for logging into a stream (file, stdout, stderr)
|
* 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
|
* The minimum loglevel at which this logger will be triggered
|
||||||
|
@ -42,12 +38,6 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy
|
||||||
*/
|
*/
|
||||||
private $logLevel;
|
private $logLevel;
|
||||||
|
|
||||||
/**
|
|
||||||
* The file URL of the stream (if needed)
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $url;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The stream, where the current logger is writing into
|
* The stream, where the current logger is writing into
|
||||||
* @var resource
|
* @var resource
|
||||||
|
@ -60,16 +50,11 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy
|
||||||
*/
|
*/
|
||||||
private $pid;
|
private $pid;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var FileSystem
|
|
||||||
*/
|
|
||||||
private $fileSystem;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates LogLevel log levels to integer values
|
* Translates LogLevel log levels to integer values
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $levelToInt = [
|
public const levelToInt = [
|
||||||
LogLevel::EMERGENCY => 0,
|
LogLevel::EMERGENCY => 0,
|
||||||
LogLevel::ALERT => 1,
|
LogLevel::ALERT => 1,
|
||||||
LogLevel::CRITICAL => 2,
|
LogLevel::CRITICAL => 2,
|
||||||
|
@ -84,41 +69,20 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
* @param string $level The minimum loglevel at which this logger will be triggered
|
* @param string $level The minimum loglevel at which this logger will be triggered
|
||||||
*
|
*
|
||||||
* @throws LoggerArgumentException
|
* @throws LoggerException
|
||||||
* @throws LogLevelException
|
|
||||||
*/
|
*/
|
||||||
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);
|
parent::__construct($channel, $introspection);
|
||||||
|
|
||||||
if (is_resource($stream)) {
|
|
||||||
$this->stream = $stream;
|
$this->stream = $stream;
|
||||||
} elseif (is_string($stream)) {
|
$this->pid = $pid;
|
||||||
$this->url = $stream;
|
$this->logLevel = $logLevel;
|
||||||
} 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function close()
|
public function close()
|
||||||
{
|
{
|
||||||
if ($this->url && is_resource($this->stream)) {
|
if (is_resource($this->stream)) {
|
||||||
fclose($this->stream);
|
fclose($this->stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,18 +103,16 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy
|
||||||
*/
|
*/
|
||||||
protected function addEntry($level, string $message, array $context = [])
|
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));
|
throw new LogLevelException(sprintf('The level "%s" is not valid.', $level));
|
||||||
}
|
}
|
||||||
|
|
||||||
$logLevel = $this->levelToInt[$level];
|
$logLevel = static::levelToInt[$level];
|
||||||
|
|
||||||
if ($logLevel > $this->logLevel) {
|
if ($logLevel > $this->logLevel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->checkStream();
|
|
||||||
|
|
||||||
$formattedLog = $this->formatLog($level, $message, $context);
|
$formattedLog = $this->formatLog($level, $message, $context);
|
||||||
fwrite($this->stream, $formattedLog);
|
fwrite($this->stream, $formattedLog);
|
||||||
}
|
}
|
||||||
|
@ -185,27 +147,4 @@ class StreamLogger extends AbstractLogger implements IAmAStrategy
|
||||||
|
|
||||||
return $logMessage;
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,6 @@
|
||||||
|
|
||||||
namespace Friendica\Core\Logger\Type;
|
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\Capabilities\IHaveCallIntrospections;
|
||||||
use Friendica\Core\Logger\Exception\LoggerException;
|
use Friendica\Core\Logger\Exception\LoggerException;
|
||||||
use Friendica\Core\Logger\Exception\LogLevelException;
|
use Friendica\Core\Logger\Exception\LogLevelException;
|
||||||
|
@ -32,7 +30,7 @@ use Psr\Log\LogLevel;
|
||||||
* A Logger instance for syslogging (fast, but simple)
|
* A Logger instance for syslogging (fast, but simple)
|
||||||
* @see http://php.net/manual/en/function.syslog.php
|
* @see http://php.net/manual/en/function.syslog.php
|
||||||
*/
|
*/
|
||||||
class SyslogLogger extends AbstractLogger implements IAmAStrategy
|
class SyslogLogger extends AbstractLogger
|
||||||
{
|
{
|
||||||
const IDENT = 'Friendica';
|
const IDENT = 'Friendica';
|
||||||
|
|
||||||
|
@ -45,7 +43,7 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy
|
||||||
* Translates LogLevel log levels to syslog log priorities.
|
* Translates LogLevel log levels to syslog log priorities.
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $logLevels = [
|
public const logLevels = [
|
||||||
LogLevel::DEBUG => LOG_DEBUG,
|
LogLevel::DEBUG => LOG_DEBUG,
|
||||||
LogLevel::INFO => LOG_INFO,
|
LogLevel::INFO => LOG_INFO,
|
||||||
LogLevel::NOTICE => LOG_NOTICE,
|
LogLevel::NOTICE => LOG_NOTICE,
|
||||||
|
@ -60,7 +58,7 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy
|
||||||
* Translates log priorities to string outputs
|
* Translates log priorities to string outputs
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private $logToString = [
|
protected const logToString = [
|
||||||
LOG_DEBUG => 'DEBUG',
|
LOG_DEBUG => 'DEBUG',
|
||||||
LOG_INFO => 'INFO',
|
LOG_INFO => 'INFO',
|
||||||
LOG_NOTICE => 'NOTICE',
|
LOG_NOTICE => 'NOTICE',
|
||||||
|
@ -101,19 +99,18 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
* @param string $level The minimum loglevel at which this logger will be triggered
|
|
||||||
*
|
*
|
||||||
* @throws LogLevelException
|
* @param string $logLevel The minimum loglevel at which this logger will be triggered
|
||||||
* @throws LoggerException
|
* @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);
|
parent::__construct($channel, $introspection);
|
||||||
|
|
||||||
$this->logOpts = $config->get('system', 'syslog_flags') ?? static::DEFAULT_FLAGS;
|
$this->logOpts = $logOptions;
|
||||||
$this->logFacility = $config->get('system', 'syslog_facility') ?? static::DEFAULT_FACILITY;
|
$this->logFacility = $logFacility;
|
||||||
$this->logLevel = $this->mapLevelToPriority($level);
|
$this->logLevel = $logLevel;
|
||||||
$this->introspection->addClasses([self::class]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -149,11 +146,11 @@ class SyslogLogger extends AbstractLogger implements IAmAStrategy
|
||||||
*/
|
*/
|
||||||
public function mapLevelToPriority(string $level): int
|
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));
|
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]);
|
$record = array_merge($record, ['uid' => $this->logUid]);
|
||||||
|
|
||||||
$logMessage = $this->channel . ' ';
|
$logMessage = $this->channel . ' ';
|
||||||
$logMessage .= '[' . $this->logToString[$level] . ']: ';
|
$logMessage .= '[' . static::logToString[$level] . ']: ';
|
||||||
$logMessage .= $this->psrInterpolate($message, $context) . ' ';
|
$logMessage .= $this->psrInterpolate($message, $context) . ' ';
|
||||||
$logMessage .= $this->jsonEncodeArray($context) . ' - ';
|
$logMessage .= $this->jsonEncodeArray($context) . ' - ';
|
||||||
$logMessage .= $this->jsonEncodeArray($record);
|
$logMessage .= $this->jsonEncodeArray($record);
|
||||||
|
|
|
@ -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
|
class FileSystem
|
||||||
{
|
{
|
||||||
|
@ -37,8 +39,10 @@ class FileSystem
|
||||||
* @param string $file The file
|
* @param string $file The file
|
||||||
*
|
*
|
||||||
* @return string The directory name (empty if no directory is found, like urls)
|
* @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;
|
$dirname = null;
|
||||||
$pos = strpos($file, '://');
|
$pos = strpos($file, '://');
|
||||||
|
@ -57,7 +61,7 @@ class FileSystem
|
||||||
restore_error_handler();
|
restore_error_handler();
|
||||||
|
|
||||||
if (!$status && !is_dir($dirname)) {
|
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;
|
return $dirname;
|
||||||
|
@ -75,7 +79,7 @@ class FileSystem
|
||||||
*
|
*
|
||||||
* @return resource the open stream resource
|
* @return resource the open stream resource
|
||||||
*
|
*
|
||||||
* @throws \UnexpectedValueException
|
* @throws LoggerUnusableException
|
||||||
*/
|
*/
|
||||||
public function createStream(string $url)
|
public function createStream(string $url)
|
||||||
{
|
{
|
||||||
|
@ -89,7 +93,7 @@ class FileSystem
|
||||||
restore_error_handler();
|
restore_error_handler();
|
||||||
|
|
||||||
if (!is_resource($stream)) {
|
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;
|
return $stream;
|
91
src/Core/Logger/Util/LoggerSettingsCheck.php
Normal file
91
src/Core/Logger/Util/LoggerSettingsCheck.php
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
15
src/DI.php
15
src/DI.php
|
@ -22,6 +22,8 @@
|
||||||
namespace Friendica;
|
namespace Friendica;
|
||||||
|
|
||||||
use Dice\Dice;
|
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\IHandleSessions;
|
||||||
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
||||||
use Friendica\Navigation\SystemMessages;
|
use Friendica\Navigation\SystemMessages;
|
||||||
|
@ -295,6 +297,11 @@ abstract class DI
|
||||||
static::init($flushDice);
|
static::init($flushDice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function logCheck(): ICheckLoggerSettings
|
||||||
|
{
|
||||||
|
return self::$dice->create(LoggerSettingsCheck::class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return LoggerInterface
|
* @return LoggerInterface
|
||||||
*/
|
*/
|
||||||
|
@ -692,14 +699,6 @@ abstract class DI
|
||||||
return self::$dice->create(Util\DateTimeFormat::class);
|
return self::$dice->create(Util\DateTimeFormat::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Util\FileSystem
|
|
||||||
*/
|
|
||||||
public static function fs()
|
|
||||||
{
|
|
||||||
return self::$dice->create(Util\FileSystem::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Util\Profiler
|
* @return Util\Profiler
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -126,35 +126,11 @@ class Summary extends BaseAdmin
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check logfile permission
|
// Check logfile permission
|
||||||
if (DI::config()->get('system', 'debugging')) {
|
if (($return = DI::logCheck()->checkLogfile()) !== null) {
|
||||||
$file = DI::config()->get('system', 'logfile');
|
$warningtext[] = $return;
|
||||||
|
|
||||||
$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()->checkDebugLogfile()) !== null) {
|
||||||
|
$warningtext[] = $return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check legacy basepath settings
|
// check legacy basepath settings
|
||||||
|
|
|
@ -37,8 +37,9 @@ use Dice\Dice;
|
||||||
use Friendica\App;
|
use Friendica\App;
|
||||||
use Friendica\Core\Cache;
|
use Friendica\Core\Cache;
|
||||||
use Friendica\Core\Config;
|
use Friendica\Core\Config;
|
||||||
use Friendica\Core\Hooks\Capabilities\ICanManageInstances;
|
use Friendica\Core\Hooks\Capabilities\ICanCreateInstances;
|
||||||
use Friendica\Core\Hooks\Model\InstanceManager;
|
use Friendica\Core\Hooks\Capabilities\ICanRegisterStrategies;
|
||||||
|
use Friendica\Core\Hooks\Model\DiceInstanceManager;
|
||||||
use Friendica\Core\PConfig;
|
use Friendica\Core\PConfig;
|
||||||
use Friendica\Core\L10n;
|
use Friendica\Core\L10n;
|
||||||
use Friendica\Core\Lock;
|
use Friendica\Core\Lock;
|
||||||
|
@ -62,6 +63,13 @@ return [
|
||||||
// one instance for the whole execution
|
// one instance for the whole execution
|
||||||
'shared' => true,
|
'shared' => true,
|
||||||
],
|
],
|
||||||
|
\Friendica\Core\Addon\Capabilities\ICanLoadAddons::class => [
|
||||||
|
'instanceOf' => \Friendica\Core\Addon\Model\AddonLoader::class,
|
||||||
|
'constructParams' => [
|
||||||
|
[Dice::INSTANCE => '$basepath'],
|
||||||
|
[Dice::INSTANCE => Dice::SELF],
|
||||||
|
],
|
||||||
|
],
|
||||||
'$basepath' => [
|
'$basepath' => [
|
||||||
'instanceOf' => Util\BasePath::class,
|
'instanceOf' => Util\BasePath::class,
|
||||||
'call' => [
|
'call' => [
|
||||||
|
@ -78,8 +86,27 @@ return [
|
||||||
$_SERVER
|
$_SERVER
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
ICanManageInstances::class => [
|
DiceInstanceManager::class => [
|
||||||
'instanceOf' => InstanceManager::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' => [
|
'constructParams' => [
|
||||||
[Dice::INSTANCE => Dice::SELF],
|
[Dice::INSTANCE => Dice::SELF],
|
||||||
],
|
],
|
||||||
|
@ -156,40 +183,34 @@ return [
|
||||||
[Dice::INSTANCE => '$basepath'],
|
[Dice::INSTANCE => '$basepath'],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
/**
|
\Psr\Log\LoggerInterface::class => [
|
||||||
* 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 => [
|
|
||||||
'instanceOf' => \Friendica\Core\Logger\Factory\Logger::class,
|
'instanceOf' => \Friendica\Core\Logger\Factory\Logger::class,
|
||||||
'constructParams' => [
|
|
||||||
'index',
|
|
||||||
],
|
|
||||||
'call' => [
|
'call' => [
|
||||||
['create', [], Dice::CHAIN_CALL],
|
['create', [], Dice::CHAIN_CALL],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'$devLogger' => [
|
\Friendica\Core\Logger\Type\SyslogLogger::class => [
|
||||||
'instanceOf' => \Friendica\Core\Logger\Factory\Logger::class,
|
'instanceOf' => \Friendica\Core\Logger\Factory\SyslogLogger::class,
|
||||||
'constructParams' => [
|
|
||||||
'dev',
|
|
||||||
],
|
|
||||||
'call' => [
|
'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 => [
|
\Friendica\Core\Logger\Capabilities\IHaveCallIntrospections::class => [
|
||||||
'instanceOf' => \Friendica\Core\Logger\Util\Introspection::class,
|
'instanceOf' => \Friendica\Core\Logger\Util\Introspection::class,
|
||||||
'constructParams' => [
|
'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 => [
|
Cache\Capability\ICanCache::class => [
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
/**
|
return [
|
||||||
* All classes, implementing this interface are valid Strategies for Hook calls
|
Log\LoggerInterface::class => [
|
||||||
*/
|
Log\NullLogger::class => [''],
|
||||||
interface IAmAStrategy
|
Type\SyslogLogger::class => ['syslog'],
|
||||||
{
|
Type\StreamLogger::class => ['stream'],
|
||||||
}
|
],
|
||||||
|
];
|
|
@ -21,9 +21,7 @@
|
||||||
|
|
||||||
namespace Friendica\Test\Util\Hooks\InstanceMocks;
|
namespace Friendica\Test\Util\Hooks\InstanceMocks;
|
||||||
|
|
||||||
use Friendica\Core\Hooks\Capabilities\IAmAStrategy;
|
class FakeInstance implements IAmADecoratedInterface
|
||||||
|
|
||||||
class FakeInstance implements IAmADecoratedInterface, IAmAStrategy
|
|
||||||
{
|
{
|
||||||
protected $aText = null;
|
protected $aText = null;
|
||||||
protected $cBool = null;
|
protected $cBool = null;
|
||||||
|
@ -41,6 +39,8 @@ class FakeInstance implements IAmADecoratedInterface, IAmAStrategy
|
||||||
$this->aText = $aText;
|
$this->aText = $aText;
|
||||||
$this->cBool = $cBool;
|
$this->cBool = $cBool;
|
||||||
$this->bText = $bText;
|
$this->bText = $bText;
|
||||||
|
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAText(): ?string
|
public function getAText(): ?string
|
||||||
|
|
|
@ -25,14 +25,14 @@ class FakeInstanceDecorator implements IAmADecoratedInterface
|
||||||
{
|
{
|
||||||
public static $countInstance = 0;
|
public static $countInstance = 0;
|
||||||
|
|
||||||
|
const PREFIX = 'prefix1';
|
||||||
|
|
||||||
/** @var IAmADecoratedInterface */
|
/** @var IAmADecoratedInterface */
|
||||||
protected $orig;
|
protected $orig;
|
||||||
protected $prefix = '';
|
|
||||||
|
|
||||||
public function __construct(IAmADecoratedInterface $orig, string $prefix = '')
|
public function __construct(IAmADecoratedInterface $orig)
|
||||||
{
|
{
|
||||||
$this->orig = $orig;
|
$this->orig = $orig;
|
||||||
$this->prefix = $prefix;
|
|
||||||
|
|
||||||
self::$countInstance++;
|
self::$countInstance++;
|
||||||
}
|
}
|
||||||
|
@ -44,16 +44,16 @@ class FakeInstanceDecorator implements IAmADecoratedInterface
|
||||||
|
|
||||||
public function getAText(): ?string
|
public function getAText(): ?string
|
||||||
{
|
{
|
||||||
return $this->prefix . $this->orig->getAText();
|
return static::PREFIX . $this->orig->getAText();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getBText(): ?string
|
public function getBText(): ?string
|
||||||
{
|
{
|
||||||
return $this->prefix . $this->orig->getBText();
|
return static::PREFIX . $this->orig->getBText();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCBool(): ?bool
|
public function getCBool(): ?bool
|
||||||
{
|
{
|
||||||
return $this->prefix . $this->orig->getCBool();
|
return static::PREFIX . $this->orig->getCBool();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,7 @@ class DependencyCheckTest extends FixtureTest
|
||||||
$config->set('system', 'dlogfile', $this->root->url() . '/friendica.log');
|
$config->set('system', 'dlogfile', $this->root->url() . '/friendica.log');
|
||||||
|
|
||||||
/** @var LoggerInterface $logger */
|
/** @var LoggerInterface $logger */
|
||||||
$logger = $this->dice->create('$devLogger', [['$channel' => 'dev']]);
|
$logger = $this->dice->create('$devLogger', ['dev']);
|
||||||
|
|
||||||
self::assertInstanceOf(LoggerInterface::class, $logger);
|
self::assertInstanceOf(LoggerInterface::class, $logger);
|
||||||
}
|
}
|
||||||
|
|
212
tests/src/Core/Addon/Model/AddonLoaderTest.php
Normal file
212
tests/src/Core/Addon/Model/AddonLoaderTest.php
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 = <<<EOF
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
\Friendica\Core\Hooks\Capabilities\BehavioralHookType::STRATEGY => [
|
||||||
|
\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 = "<php return 'wrong';";
|
||||||
|
|
||||||
|
vfsStream::create($this->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 = "<php return 'wrong';";
|
||||||
|
|
||||||
|
vfsStream::create($this->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'));
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,25 +22,27 @@
|
||||||
namespace Friendica\Test\src\Core\Hooks\Model;
|
namespace Friendica\Test\src\Core\Hooks\Model;
|
||||||
|
|
||||||
use Dice\Dice;
|
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\MockedTest;
|
||||||
use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstance;
|
use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstance;
|
||||||
use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstanceDecorator;
|
use Friendica\Test\Util\Hooks\InstanceMocks\FakeInstanceDecorator;
|
||||||
use Friendica\Test\Util\Hooks\InstanceMocks\IAmADecoratedInterface;
|
use Friendica\Test\Util\Hooks\InstanceMocks\IAmADecoratedInterface;
|
||||||
|
use Mockery\MockInterface;
|
||||||
|
|
||||||
class InstanceManagerTest extends MockedTest
|
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);
|
$this->hookFileManager = \Mockery::mock(StrategiesFileManager::class);
|
||||||
|
$this->hookFileManager->shouldReceive('setupStrategies')->withAnyArgs();
|
||||||
$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake');
|
|
||||||
$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake');
|
|
||||||
|
|
||||||
self::assertEquals($getInstanceA, $getInstanceB);
|
|
||||||
self::assertNotSame($getInstanceA, $getInstanceB);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
|
@ -50,6 +52,19 @@ class InstanceManagerTest extends MockedTest
|
||||||
parent::tearDown();
|
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
|
public function dataTests(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -79,9 +94,9 @@ class InstanceManagerTest extends MockedTest
|
||||||
/**
|
/**
|
||||||
* @dataProvider dataTests
|
* @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 = [];
|
$args = [];
|
||||||
|
|
||||||
|
@ -95,45 +110,12 @@ class InstanceManagerTest extends MockedTest
|
||||||
$args[] = $cBool;
|
$args[] = $cBool;
|
||||||
}
|
}
|
||||||
|
|
||||||
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args);
|
$instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake');
|
||||||
|
|
||||||
/** @var IAmADecoratedInterface $getInstanceA */
|
/** @var IAmADecoratedInterface $getInstanceA */
|
||||||
$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake');
|
$getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake', $args);
|
||||||
/** @var IAmADecoratedInterface $getInstanceB */
|
/** @var IAmADecoratedInterface $getInstanceB */
|
||||||
$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake');
|
$getInstanceB = $instance->create(IAmADecoratedInterface::class, 'fake', $args);
|
||||||
|
|
||||||
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]);
|
|
||||||
|
|
||||||
self::assertEquals($getInstanceA, $getInstanceB);
|
self::assertEquals($getInstanceA, $getInstanceB);
|
||||||
self::assertNotSame($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)
|
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 = [];
|
$args = [];
|
||||||
|
|
||||||
if (isset($aString)) {
|
if (isset($aString)) {
|
||||||
$args[] = $aString;
|
$args[] = $aString;
|
||||||
}
|
}
|
||||||
|
if (isset($bString)) {
|
||||||
|
$args[] = $bString;
|
||||||
|
}
|
||||||
if (isset($cBool)) {
|
if (isset($cBool)) {
|
||||||
$args[] = $cBool;
|
$args[] = $cBool;
|
||||||
}
|
}
|
||||||
|
|
||||||
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args);
|
$instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake');
|
||||||
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args);
|
$instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake23');
|
||||||
|
|
||||||
/** @var IAmADecoratedInterface $getInstanceA */
|
/** @var IAmADecoratedInterface $getInstanceA */
|
||||||
$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]);
|
$getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake', $args);
|
||||||
/** @var IAmADecoratedInterface $getInstanceB */
|
/** @var IAmADecoratedInterface $getInstanceB */
|
||||||
$getInstanceB = $instance->getInstance(IAmADecoratedInterface::class, 'fake23', [$bString]);
|
$getInstanceB = $instance->create(IAmADecoratedInterface::class, 'fake23', $args);
|
||||||
|
|
||||||
self::assertEquals($getInstanceA, $getInstanceB);
|
self::assertEquals($getInstanceA, $getInstanceB);
|
||||||
self::assertNotSame($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 = [];
|
$instance = new DiceInstanceManager(new Dice(), $this->hookFileManager);
|
||||||
|
$instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake');
|
||||||
if (isset($aString)) {
|
$instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake');
|
||||||
$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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 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
|
* @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 = [];
|
$args = [];
|
||||||
|
|
||||||
if (isset($aString)) {
|
if (isset($aString)) {
|
||||||
$args[] = $aString;
|
$args[] = $aString;
|
||||||
}
|
}
|
||||||
|
if (isset($bString)) {
|
||||||
|
$args[] = $bString;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dice = (new Dice())->addRules([
|
||||||
|
FakeInstance::class => [
|
||||||
|
'constructParams' => $args,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$args = [];
|
||||||
|
|
||||||
if (isset($cBool)) {
|
if (isset($cBool)) {
|
||||||
$args[] = $cBool;
|
$args[] = $cBool;
|
||||||
}
|
}
|
||||||
|
|
||||||
$prefix = 'prefix1';
|
$instance = new DiceInstanceManager($dice, $this->hookFileManager);
|
||||||
|
|
||||||
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake', FakeInstance::class, $args);
|
$instance->registerStrategy(IAmADecoratedInterface::class, FakeInstance::class, 'fake');
|
||||||
$instance->registerStrategy(IAmADecoratedInterface::class, 'fake23', FakeInstance::class, $args);
|
|
||||||
$instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class, [$prefix]);
|
|
||||||
$instance->registerDecorator(IAmADecoratedInterface::class, FakeInstanceDecorator::class);
|
|
||||||
|
|
||||||
/** @var IAmADecoratedInterface $getInstanceA */
|
/** @var IAmADecoratedInterface $getInstanceA */
|
||||||
$getInstanceA = $instance->getInstance(IAmADecoratedInterface::class, 'fake', [$bString]);
|
$getInstanceA = $instance->create(IAmADecoratedInterface::class, 'fake', $args);
|
||||||
/** @var IAmADecoratedInterface $getInstanceB */
|
/** @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::assertEquals($getInstanceA, $getInstanceB);
|
||||||
self::assertNotSame($getInstanceA, $getInstanceB);
|
self::assertNotSame($getInstanceA, $getInstanceB);
|
||||||
self::assertEquals($prefix . $aString, $getInstanceA->getAText());
|
self::assertEquals($aString, $getInstanceA->getAText());
|
||||||
self::assertEquals($prefix . $aString, $getInstanceB->getAText());
|
self::assertEquals($aString, $getInstanceB->getAText());
|
||||||
self::assertEquals($prefix . $bString, $getInstanceA->getBText());
|
self::assertEquals($bString, $getInstanceA->getBText());
|
||||||
self::assertEquals($prefix . $bString, $getInstanceB->getBText());
|
self::assertEquals($bString, $getInstanceB->getBText());
|
||||||
self::assertEquals($prefix . $cBool, $getInstanceA->getCBool());
|
self::assertEquals($cBool, $getInstanceA->getCBool());
|
||||||
self::assertEquals($prefix . $cBool, $getInstanceB->getCBool());
|
self::assertEquals($cBool, $getInstanceB->getCBool());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
202
tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php
Normal file
202
tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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' => <<<EOF
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
\Psr\Log\LoggerInterface::class => [
|
||||||
|
\Psr\Log\NullLogger::class => [''],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
EOF,
|
||||||
|
'addonsArray' => [],
|
||||||
|
'assertStrategies' => [
|
||||||
|
[LoggerInterface::class, NullLogger::class, ''],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'normalWithString' => [
|
||||||
|
'content' => <<<EOF
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
\Psr\Log\LoggerInterface::class => [
|
||||||
|
\Psr\Log\NullLogger::class => '',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
EOF,
|
||||||
|
'addonsArray' => [],
|
||||||
|
'assertStrategies' => [
|
||||||
|
[LoggerInterface::class, NullLogger::class, ''],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'withAddons' => [
|
||||||
|
'content' => <<<EOF
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
\Psr\Log\LoggerInterface::class => [
|
||||||
|
\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' => <<<EOF
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
\Psr\Log\LoggerInterface::class => [
|
||||||
|
\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' => <<<EOF
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
\Psr\Log\LoggerInterface::class => [
|
||||||
|
\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("<php return 'WRONG_CONTENT';")
|
||||||
|
->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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,9 +22,7 @@
|
||||||
namespace Friendica\Test\src\Core\Logger;
|
namespace Friendica\Test\src\Core\Logger;
|
||||||
|
|
||||||
use Friendica\Core\Logger\Exception\LoggerArgumentException;
|
use Friendica\Core\Logger\Exception\LoggerArgumentException;
|
||||||
use Friendica\Core\Logger\Exception\LoggerException;
|
|
||||||
use Friendica\Core\Logger\Exception\LogLevelException;
|
use Friendica\Core\Logger\Exception\LogLevelException;
|
||||||
use Friendica\Util\FileSystem;
|
|
||||||
use Friendica\Test\Util\VFSTrait;
|
use Friendica\Test\Util\VFSTrait;
|
||||||
use Friendica\Core\Logger\Type\StreamLogger;
|
use Friendica\Core\Logger\Type\StreamLogger;
|
||||||
use org\bovigo\vfs\vfsStream;
|
use org\bovigo\vfs\vfsStream;
|
||||||
|
@ -40,33 +38,26 @@ class StreamLoggerTest extends AbstractLoggerTest
|
||||||
*/
|
*/
|
||||||
private $logfile;
|
private $logfile;
|
||||||
|
|
||||||
/**
|
|
||||||
* @var Filesystem
|
|
||||||
*/
|
|
||||||
private $fileSystem;
|
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
$this->setUpVfsDir();
|
$this->setUpVfsDir();
|
||||||
|
|
||||||
$this->fileSystem = new FileSystem();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@@inheritdoc}
|
* {@@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);
|
->at($this->root);
|
||||||
|
|
||||||
$this->config->shouldReceive('get')->with('system', 'logfile')->andReturn($this->logfile->url())->once();
|
$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);
|
$loggerFactory = new \Friendica\Core\Logger\Factory\StreamLogger($this->introspection, 'test');
|
||||||
|
return $loggerFactory->create($this->config);
|
||||||
return $logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,11 +74,12 @@ class StreamLoggerTest extends AbstractLoggerTest
|
||||||
public function testNoUrl()
|
public function testNoUrl()
|
||||||
{
|
{
|
||||||
$this->expectException(LoggerArgumentException::class);
|
$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();
|
$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');
|
$logger->emergency('not working');
|
||||||
}
|
}
|
||||||
|
@ -104,7 +96,8 @@ class StreamLoggerTest extends AbstractLoggerTest
|
||||||
|
|
||||||
$this->config->shouldReceive('get')->with('system', 'logfile')->andReturn($logfile->url())->once();
|
$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');
|
$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');
|
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');
|
$logger->emergency('not working');
|
||||||
}
|
}
|
||||||
|
@ -132,9 +126,7 @@ class StreamLoggerTest extends AbstractLoggerTest
|
||||||
$this->expectException(LogLevelException::class);
|
$this->expectException(LogLevelException::class);
|
||||||
$this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
|
$this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
|
||||||
|
|
||||||
$this->config->shouldReceive('get')->with('system', 'logfile')->andReturn('file.text')->once();
|
$logger = $this->getInstance('NOPE');
|
||||||
|
|
||||||
$logger = new StreamLogger('test', $this->config, $this->introspection, $this->fileSystem, 'NOPE');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -145,29 +137,11 @@ class StreamLoggerTest extends AbstractLoggerTest
|
||||||
$this->expectException(LogLevelException::class);
|
$this->expectException(LogLevelException::class);
|
||||||
$this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
|
$this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
|
||||||
|
|
||||||
$logfile = vfsStream::newFile('friendica.log')
|
$logger = $this->getInstance('NOPE');
|
||||||
->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->log('NOPE', 'a test');
|
$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
|
* Test a relative path
|
||||||
* @doesNotPerformAssertions
|
* @doesNotPerformAssertions
|
||||||
|
|
46
tests/src/Core/Logger/SyslogLoggerFactoryWrapper.php
Normal file
46
tests/src/Core/Logger/SyslogLoggerFactoryWrapper.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (C) 2010-2023, the Friendica project
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,8 +21,6 @@
|
||||||
|
|
||||||
namespace Friendica\Test\src\Core\Logger;
|
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\Exception\LogLevelException;
|
||||||
use Friendica\Core\Logger\Type\SyslogLogger;
|
use Friendica\Core\Logger\Type\SyslogLogger;
|
||||||
use Psr\Log\LogLevel;
|
use Psr\Log\LogLevel;
|
||||||
|
@ -58,7 +56,10 @@ class SyslogLoggerTest extends AbstractLoggerTest
|
||||||
*/
|
*/
|
||||||
protected function getInstance($level = LogLevel::DEBUG)
|
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;
|
return $this->logger;
|
||||||
}
|
}
|
||||||
|
@ -72,7 +73,7 @@ class SyslogLoggerTest extends AbstractLoggerTest
|
||||||
$this->expectException(LogLevelException::class);
|
$this->expectException(LogLevelException::class);
|
||||||
$this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
|
$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->expectException(LogLevelException::class);
|
||||||
$this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
|
$this->expectExceptionMessageMatches("/The level \".*\" is not valid./");
|
||||||
|
|
||||||
$logger = new SyslogLoggerWrapper('test', $this->config, $this->introspection);
|
$logger = $this->getInstance();
|
||||||
|
|
||||||
$logger->log('NOPE', 'a test');
|
$logger->log('NOPE', 'a test');
|
||||||
}
|
}
|
||||||
|
@ -94,7 +95,7 @@ class SyslogLoggerTest extends AbstractLoggerTest
|
||||||
*/
|
*/
|
||||||
public function testClose()
|
public function testClose()
|
||||||
{
|
{
|
||||||
$logger = new SyslogLoggerWrapper('test', $this->config, $this->introspection);
|
$logger = $this->getInstance();
|
||||||
$logger->emergency('test');
|
$logger->emergency('test');
|
||||||
$logger->close();
|
$logger->close();
|
||||||
// Reopened itself
|
// Reopened itself
|
||||||
|
|
|
@ -21,10 +21,8 @@
|
||||||
|
|
||||||
namespace Friendica\Test\src\Core\Logger;
|
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\Type\SyslogLogger;
|
||||||
use Friendica\Core\Logger\Util\Introspection;
|
|
||||||
use Psr\Log\LogLevel;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps the SyslogLogger for replacing the syslog call with a string field.
|
* Wraps the SyslogLogger for replacing the syslog call with a string field.
|
||||||
|
@ -33,9 +31,9 @@ class SyslogLoggerWrapper extends SyslogLogger
|
||||||
{
|
{
|
||||||
private $content;
|
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 = '';
|
$this->content = '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: 2023.09-dev\n"
|
"Project-Id-Version: 2023.09-dev\n"
|
||||||
"Report-Msgid-Bugs-To: \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"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -292,9 +292,9 @@ msgid "Insert web link"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: mod/message.php:201 mod/message.php:357 mod/photos.php:1301
|
#: 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/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"
|
msgid "Please wait"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -315,7 +315,7 @@ msgstr ""
|
||||||
#: src/Module/Moderation/Report/Create.php:211
|
#: src/Module/Moderation/Report/Create.php:211
|
||||||
#: src/Module/Moderation/Report/Create.php:263
|
#: src/Module/Moderation/Report/Create.php:263
|
||||||
#: src/Module/Profile/Profile.php:274 src/Module/Profile/UnkMail.php:155
|
#: 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/duepuntozero/config.php:85 view/theme/frio/config.php:171
|
||||||
#: view/theme/quattro/config.php:87 view/theme/vier/config.php:135
|
#: view/theme/quattro/config.php:87 view/theme/vier/config.php:135
|
||||||
msgid "Submit"
|
msgid "Submit"
|
||||||
|
@ -600,33 +600,33 @@ msgstr ""
|
||||||
|
|
||||||
#: mod/photos.php:1139 mod/photos.php:1195 mod/photos.php:1275
|
#: mod/photos.php:1139 mod/photos.php:1195 mod/photos.php:1275
|
||||||
#: src/Module/Contact.php:619 src/Module/Item/Compose.php:188
|
#: 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"
|
msgid "This is you"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: mod/photos.php:1141 mod/photos.php:1197 mod/photos.php:1277
|
#: 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"
|
msgid "Comment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: mod/photos.php:1143 mod/photos.php:1199 mod/photos.php:1279
|
#: 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/Content/Conversation.php:407 src/Module/Calendar/Event/Form.php:248
|
||||||
#: src/Module/Item/Compose.php:201 src/Module/Post/Edit.php:165
|
#: src/Module/Item/Compose.php:201 src/Module/Post/Edit.php:165
|
||||||
#: src/Object/Post.php:1103
|
#: src/Object/Post.php:1100
|
||||||
msgid "Preview"
|
msgid "Preview"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: mod/photos.php:1144 src/Content/Conversation.php:360
|
#: 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..."
|
msgid "Loading..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: mod/photos.php:1236 src/Content/Conversation.php:1422
|
#: mod/photos.php:1236 src/Content/Conversation.php:1424
|
||||||
#: src/Object/Post.php:263
|
#: src/Object/Post.php:260
|
||||||
msgid "Select"
|
msgid "Select"
|
||||||
msgstr ""
|
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/Active.php:136
|
||||||
#: src/Module/Moderation/Users/Blocked.php:136
|
#: src/Module/Moderation/Users/Blocked.php:136
|
||||||
#: src/Module/Moderation/Users/Index.php:151
|
#: src/Module/Moderation/Users/Index.php:151
|
||||||
|
@ -634,19 +634,19 @@ msgstr ""
|
||||||
msgid "Delete"
|
msgid "Delete"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: mod/photos.php:1298 src/Object/Post.php:405
|
#: mod/photos.php:1298 src/Object/Post.php:402
|
||||||
msgid "Like"
|
msgid "Like"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: mod/photos.php:1299 src/Object/Post.php:405
|
#: mod/photos.php:1299 src/Object/Post.php:402
|
||||||
msgid "I like this (toggle)"
|
msgid "I like this (toggle)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: mod/photos.php:1300 src/Object/Post.php:406
|
#: mod/photos.php:1300 src/Object/Post.php:403
|
||||||
msgid "Dislike"
|
msgid "Dislike"
|
||||||
msgstr ""
|
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)"
|
msgid "I don't like this (toggle)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -662,97 +662,97 @@ msgstr ""
|
||||||
msgid "Apologies but the website is unavailable at the moment."
|
msgid "Apologies but the website is unavailable at the moment."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:247
|
#: src/App/Page.php:248
|
||||||
msgid "Delete this item?"
|
msgid "Delete this item?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:248
|
#: src/App/Page.php:249
|
||||||
msgid ""
|
msgid ""
|
||||||
"Block this author? They won't be able to follow you nor see your public "
|
"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."
|
"posts, and you won't be able to see their posts and their notifications."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:249
|
#: src/App/Page.php:250
|
||||||
msgid ""
|
msgid ""
|
||||||
"Ignore this author? You won't be able to see their posts and their "
|
"Ignore this author? You won't be able to see their posts and their "
|
||||||
"notifications."
|
"notifications."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:250
|
#: src/App/Page.php:251
|
||||||
msgid "Collapse this author's posts?"
|
msgid "Collapse this author's posts?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:252
|
#: src/App/Page.php:253
|
||||||
msgid "Like not successful"
|
msgid "Like not successful"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:253
|
#: src/App/Page.php:254
|
||||||
msgid "Dislike not successful"
|
msgid "Dislike not successful"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:254
|
#: src/App/Page.php:255
|
||||||
msgid "Sharing not successful"
|
msgid "Sharing not successful"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:255
|
#: src/App/Page.php:256
|
||||||
msgid "Attendance unsuccessful"
|
msgid "Attendance unsuccessful"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:256
|
#: src/App/Page.php:257
|
||||||
msgid "Backend error"
|
msgid "Backend error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:257
|
#: src/App/Page.php:258
|
||||||
msgid "Network error"
|
msgid "Network error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:260
|
#: src/App/Page.php:261
|
||||||
msgid "Drop files here to upload"
|
msgid "Drop files here to upload"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:261
|
#: src/App/Page.php:262
|
||||||
msgid "Your browser does not support drag and drop file uploads."
|
msgid "Your browser does not support drag and drop file uploads."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:262
|
#: src/App/Page.php:263
|
||||||
msgid ""
|
msgid ""
|
||||||
"Please use the fallback form below to upload your files like in the olden "
|
"Please use the fallback form below to upload your files like in the olden "
|
||||||
"days."
|
"days."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:263
|
#: src/App/Page.php:264
|
||||||
msgid "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB."
|
msgid "File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:264
|
#: src/App/Page.php:265
|
||||||
msgid "You can't upload files of this type."
|
msgid "You can't upload files of this type."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:265
|
#: src/App/Page.php:266
|
||||||
msgid "Server responded with {{statusCode}} code."
|
msgid "Server responded with {{statusCode}} code."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:266
|
#: src/App/Page.php:267
|
||||||
msgid "Cancel upload"
|
msgid "Cancel upload"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:267
|
#: src/App/Page.php:268
|
||||||
msgid "Upload canceled."
|
msgid "Upload canceled."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:268
|
#: src/App/Page.php:269
|
||||||
msgid "Are you sure you want to cancel this upload?"
|
msgid "Are you sure you want to cancel this upload?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:269
|
#: src/App/Page.php:270
|
||||||
msgid "Remove file"
|
msgid "Remove file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:270
|
#: src/App/Page.php:271
|
||||||
msgid "You can't upload any more files."
|
msgid "You can't upload any more files."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/App/Page.php:348
|
#: src/App/Page.php:349
|
||||||
msgid "toggle mobile"
|
msgid "toggle mobile"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -769,31 +769,31 @@ msgstr ""
|
||||||
msgid "You must be logged in to use addons. "
|
msgid "You must be logged in to use addons. "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/BaseModule.php:400
|
#: src/BaseModule.php:401
|
||||||
msgid ""
|
msgid ""
|
||||||
"The form security token was not correct. This probably happened because the "
|
"The form security token was not correct. This probably happened because the "
|
||||||
"form has been opened for too long (>3 hours) before submitting it."
|
"form has been opened for too long (>3 hours) before submitting it."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/BaseModule.php:427
|
#: src/BaseModule.php:428
|
||||||
msgid "All contacts"
|
msgid "All contacts"
|
||||||
msgstr ""
|
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/Contact.php:415 src/Module/PermissionTooltip.php:127
|
||||||
#: src/Module/PermissionTooltip.php:149
|
#: src/Module/PermissionTooltip.php:149
|
||||||
msgid "Followers"
|
msgid "Followers"
|
||||||
msgstr ""
|
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"
|
msgid "Following"
|
||||||
msgstr ""
|
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"
|
msgid "Mutual friends"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/BaseModule.php:450
|
#: src/BaseModule.php:451
|
||||||
msgid "Common"
|
msgid "Common"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -1226,7 +1226,7 @@ msgid "Visible to <strong>everybody</strong>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:330 src/Module/Item/Compose.php:200
|
#: 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:"
|
msgid "Please enter a image/video/audio/webpage URL:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -1271,52 +1271,52 @@ msgid "attach file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:365 src/Module/Item/Compose.php:190
|
#: 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"
|
msgid "Bold"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:366 src/Module/Item/Compose.php:191
|
#: 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"
|
msgid "Italic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:367 src/Module/Item/Compose.php:192
|
#: 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"
|
msgid "Underline"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:368 src/Module/Item/Compose.php:193
|
#: 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"
|
msgid "Quote"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:369 src/Module/Item/Compose.php:194
|
#: 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"
|
msgid "Add emojis"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:370 src/Module/Item/Compose.php:195
|
#: src/Content/Conversation.php:370 src/Module/Item/Compose.php:195
|
||||||
#: src/Object/Post.php:1095
|
#: src/Object/Post.php:1092
|
||||||
msgid "Content Warning"
|
msgid "Content Warning"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:371 src/Module/Item/Compose.php:196
|
#: 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"
|
msgid "Code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:372 src/Module/Item/Compose.php:197
|
#: src/Content/Conversation.php:372 src/Module/Item/Compose.php:197
|
||||||
#: src/Object/Post.php:1099
|
#: src/Object/Post.php:1096
|
||||||
msgid "Image"
|
msgid "Image"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:373 src/Module/Item/Compose.php:198
|
#: 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"
|
msgid "Link"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:374 src/Module/Item/Compose.php:199
|
#: 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"
|
msgid "Link or Media"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -1386,111 +1386,111 @@ msgstr ""
|
||||||
msgid "Delete Selected Items"
|
msgid "Delete Selected Items"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:724 src/Content/Conversation.php:727
|
#: src/Content/Conversation.php:728 src/Content/Conversation.php:731
|
||||||
#: src/Content/Conversation.php:730 src/Content/Conversation.php:733
|
#: src/Content/Conversation.php:734 src/Content/Conversation.php:737
|
||||||
#: src/Content/Conversation.php:736
|
#: src/Content/Conversation.php:740
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "You had been addressed (%s)."
|
msgid "You had been addressed (%s)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:739
|
#: src/Content/Conversation.php:743
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "You are following %s."
|
msgid "You are following %s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:742
|
#: src/Content/Conversation.php:746
|
||||||
msgid "You subscribed to one or more tags in this post."
|
msgid "You subscribed to one or more tags in this post."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:761
|
#: src/Content/Conversation.php:765
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "%s reshared this."
|
msgid "%s reshared this."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:763
|
#: src/Content/Conversation.php:767
|
||||||
msgid "Reshared"
|
msgid "Reshared"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:763
|
#: src/Content/Conversation.php:767
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Reshared by %s <%s>"
|
msgid "Reshared by %s <%s>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:766
|
#: src/Content/Conversation.php:770
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "%s is participating in this thread."
|
msgid "%s is participating in this thread."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:769
|
#: src/Content/Conversation.php:773
|
||||||
msgid "Stored for general reasons"
|
msgid "Stored for general reasons"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:772
|
#: src/Content/Conversation.php:776
|
||||||
msgid "Global post"
|
msgid "Global post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:775
|
#: src/Content/Conversation.php:779
|
||||||
msgid "Sent via an relay server"
|
msgid "Sent via an relay server"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:775
|
#: src/Content/Conversation.php:779
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Sent via the relay server %s <%s>"
|
msgid "Sent via the relay server %s <%s>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:778
|
#: src/Content/Conversation.php:782
|
||||||
msgid "Fetched"
|
msgid "Fetched"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:778
|
#: src/Content/Conversation.php:782
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Fetched because of %s <%s>"
|
msgid "Fetched because of %s <%s>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:781
|
#: src/Content/Conversation.php:785
|
||||||
msgid "Stored because of a child post to complete this thread."
|
msgid "Stored because of a child post to complete this thread."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:784
|
#: src/Content/Conversation.php:788
|
||||||
msgid "Local delivery"
|
msgid "Local delivery"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:787
|
#: src/Content/Conversation.php:791
|
||||||
msgid "Stored because of your activity (like, comment, star, ...)"
|
msgid "Stored because of your activity (like, comment, star, ...)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:790
|
#: src/Content/Conversation.php:794
|
||||||
msgid "Distributed"
|
msgid "Distributed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:793
|
#: src/Content/Conversation.php:797
|
||||||
msgid "Pushed to us"
|
msgid "Pushed to us"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:1450 src/Object/Post.php:248
|
#: src/Content/Conversation.php:1452 src/Object/Post.php:248
|
||||||
msgid "Pinned item"
|
msgid "Pinned item"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:1466 src/Object/Post.php:518
|
#: src/Content/Conversation.php:1468 src/Object/Post.php:515
|
||||||
#: src/Object/Post.php:519
|
#: src/Object/Post.php:516
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "View %s's profile @ %s"
|
msgid "View %s's profile @ %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:1479 src/Object/Post.php:506
|
#: src/Content/Conversation.php:1481 src/Object/Post.php:503
|
||||||
msgid "Categories:"
|
msgid "Categories:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:1480 src/Object/Post.php:507
|
#: src/Content/Conversation.php:1482 src/Object/Post.php:504
|
||||||
msgid "Filed under:"
|
msgid "Filed under:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:1488 src/Object/Post.php:532
|
#: src/Content/Conversation.php:1490 src/Object/Post.php:529
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "%s from %s"
|
msgid "%s from %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Conversation.php:1504
|
#: src/Content/Conversation.php:1506
|
||||||
msgid "View in context"
|
msgid "View in context"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -1700,7 +1700,7 @@ msgstr ""
|
||||||
msgid "Collapse"
|
msgid "Collapse"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Content/Item.php:434 src/Object/Post.php:487
|
#: src/Content/Item.php:434 src/Object/Post.php:484
|
||||||
msgid "Languages"
|
msgid "Languages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -2833,6 +2833,16 @@ msgstr ""
|
||||||
msgid "Dec"
|
msgid "Dec"
|
||||||
msgstr ""
|
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:89 src/Core/Renderer.php:118 src/Core/Renderer.php:147
|
||||||
#: src/Core/Renderer.php:181 src/Render/FriendicaSmartyEngine.php:60
|
#: src/Core/Renderer.php:181 src/Render/FriendicaSmartyEngine.php:60
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -3407,7 +3417,7 @@ msgstr ""
|
||||||
msgid "Title/Description:"
|
msgid "Title/Description:"
|
||||||
msgstr ""
|
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/Report/Create.php:280
|
||||||
#: src/Module/Moderation/Summary.php:77
|
#: src/Module/Moderation/Summary.php:77
|
||||||
msgid "Summary"
|
msgid "Summary"
|
||||||
|
@ -3735,7 +3745,7 @@ msgstr ""
|
||||||
#: src/Module/Admin/Federation.php:210 src/Module/Admin/Logs/Settings.php:85
|
#: 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/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/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/Admin/Themes/Index.php:111 src/Module/Admin/Tos.php:77
|
||||||
#: src/Module/Moderation/Users/Create.php:61
|
#: src/Module/Moderation/Users/Create.php:61
|
||||||
#: src/Module/Moderation/Users/Pending.php:96
|
#: src/Module/Moderation/Users/Pending.php:96
|
||||||
|
@ -5224,50 +5234,40 @@ msgid ""
|
||||||
"href=\"%s\">the installation page</a> for help."
|
"href=\"%s\">the installation page</a> for help."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Summary.php:142
|
#: src/Module/Admin/Summary.php:148
|
||||||
#, 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
|
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Friendica's system.basepath was updated from '%s' to '%s'. Please remove the "
|
"Friendica's system.basepath was updated from '%s' to '%s'. Please remove the "
|
||||||
"system.basepath from your db to avoid differences."
|
"system.basepath from your db to avoid differences."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Summary.php:180
|
#: src/Module/Admin/Summary.php:156
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Friendica's current system.basepath '%s' is wrong and the config file '%s' "
|
"Friendica's current system.basepath '%s' is wrong and the config file '%s' "
|
||||||
"isn't used."
|
"isn't used."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Summary.php:188
|
#: src/Module/Admin/Summary.php:164
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Friendica's current system.basepath '%s' is not equal to the config file "
|
"Friendica's current system.basepath '%s' is not equal to the config file "
|
||||||
"'%s'. Please fix your configuration."
|
"'%s'. Please fix your configuration."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Summary.php:199
|
#: src/Module/Admin/Summary.php:175
|
||||||
msgid "Message queues"
|
msgid "Message queues"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Summary.php:205
|
#: src/Module/Admin/Summary.php:181
|
||||||
msgid "Server Settings"
|
msgid "Server Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Summary.php:223
|
#: src/Module/Admin/Summary.php:199
|
||||||
msgid "Version"
|
msgid "Version"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Admin/Summary.php:227
|
#: src/Module/Admin/Summary.php:203
|
||||||
msgid "Active addons"
|
msgid "Active addons"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -5878,7 +5878,7 @@ msgid "Only show blocked contacts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Module/Contact.php:369 src/Module/Contact.php:441
|
#: src/Module/Contact.php:369 src/Module/Contact.php:441
|
||||||
#: src/Object/Post.php:365
|
#: src/Object/Post.php:362
|
||||||
msgid "Ignored"
|
msgid "Ignored"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -6562,7 +6562,7 @@ msgstr ""
|
||||||
msgid "Posts that mention or involve you"
|
msgid "Posts that mention or involve you"
|
||||||
msgstr ""
|
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"
|
msgid "Starred"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -11500,234 +11500,234 @@ msgstr ""
|
||||||
msgid "Edit"
|
msgid "Edit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:252
|
#: src/Object/Post.php:261
|
||||||
msgid "Delete globally"
|
msgid "Delete globally"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:252
|
#: src/Object/Post.php:261
|
||||||
msgid "Remove locally"
|
msgid "Remove locally"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:271
|
#: src/Object/Post.php:268
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Block %s"
|
msgid "Block %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:276
|
#: src/Object/Post.php:273
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Ignore %s"
|
msgid "Ignore %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:281
|
#: src/Object/Post.php:278
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Collapse %s"
|
msgid "Collapse %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:285
|
#: src/Object/Post.php:282
|
||||||
msgid "Report post"
|
msgid "Report post"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:290
|
#: src/Object/Post.php:287
|
||||||
msgid "Save to folder"
|
msgid "Save to folder"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:330
|
#: src/Object/Post.php:327
|
||||||
msgid "I will attend"
|
msgid "I will attend"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:330
|
#: src/Object/Post.php:327
|
||||||
msgid "I will not attend"
|
msgid "I will not attend"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:330
|
#: src/Object/Post.php:327
|
||||||
msgid "I might attend"
|
msgid "I might attend"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:360
|
#: src/Object/Post.php:357
|
||||||
msgid "Ignore thread"
|
msgid "Ignore thread"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:361
|
#: src/Object/Post.php:358
|
||||||
msgid "Unignore thread"
|
msgid "Unignore thread"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:362
|
#: src/Object/Post.php:359
|
||||||
msgid "Toggle ignore status"
|
msgid "Toggle ignore status"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:372
|
#: src/Object/Post.php:369
|
||||||
msgid "Add star"
|
msgid "Add star"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:373
|
#: src/Object/Post.php:370
|
||||||
msgid "Remove star"
|
msgid "Remove star"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:374
|
#: src/Object/Post.php:371
|
||||||
msgid "Toggle star status"
|
msgid "Toggle star status"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:385
|
#: src/Object/Post.php:382
|
||||||
msgid "Pin"
|
msgid "Pin"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:386
|
#: src/Object/Post.php:383
|
||||||
msgid "Unpin"
|
msgid "Unpin"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:387
|
#: src/Object/Post.php:384
|
||||||
msgid "Toggle pin status"
|
msgid "Toggle pin status"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:390
|
#: src/Object/Post.php:387
|
||||||
msgid "Pinned"
|
msgid "Pinned"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:395
|
#: src/Object/Post.php:392
|
||||||
msgid "Add tag"
|
msgid "Add tag"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:408
|
#: src/Object/Post.php:405
|
||||||
msgid "Quote share this"
|
msgid "Quote share this"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:408
|
#: src/Object/Post.php:405
|
||||||
msgid "Quote Share"
|
msgid "Quote Share"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:411
|
#: src/Object/Post.php:408
|
||||||
msgid "Reshare this"
|
msgid "Reshare this"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:411
|
#: src/Object/Post.php:408
|
||||||
msgid "Reshare"
|
msgid "Reshare"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:412
|
#: src/Object/Post.php:409
|
||||||
msgid "Cancel your Reshare"
|
msgid "Cancel your Reshare"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:412
|
#: src/Object/Post.php:409
|
||||||
msgid "Unshare"
|
msgid "Unshare"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:463
|
#: src/Object/Post.php:460
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "%s (Received %s)"
|
msgid "%s (Received %s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:469
|
#: src/Object/Post.php:466
|
||||||
msgid "Comment this item on your system"
|
msgid "Comment this item on your system"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:469
|
#: src/Object/Post.php:466
|
||||||
msgid "Remote comment"
|
msgid "Remote comment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:491
|
#: src/Object/Post.php:488
|
||||||
msgid "Share via ..."
|
msgid "Share via ..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:491
|
#: src/Object/Post.php:488
|
||||||
msgid "Share via external services"
|
msgid "Share via external services"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:520
|
#: src/Object/Post.php:517
|
||||||
msgid "to"
|
msgid "to"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:521
|
#: src/Object/Post.php:518
|
||||||
msgid "via"
|
msgid "via"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:522
|
#: src/Object/Post.php:519
|
||||||
msgid "Wall-to-Wall"
|
msgid "Wall-to-Wall"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:523
|
#: src/Object/Post.php:520
|
||||||
msgid "via Wall-To-Wall:"
|
msgid "via Wall-To-Wall:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:569
|
#: src/Object/Post.php:566
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Reply to %s"
|
msgid "Reply to %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:572
|
#: src/Object/Post.php:569
|
||||||
msgid "More"
|
msgid "More"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:590
|
#: src/Object/Post.php:587
|
||||||
msgid "Notifier task is pending"
|
msgid "Notifier task is pending"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:591
|
#: src/Object/Post.php:588
|
||||||
msgid "Delivery to remote servers is pending"
|
msgid "Delivery to remote servers is pending"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:592
|
#: src/Object/Post.php:589
|
||||||
msgid "Delivery to remote servers is underway"
|
msgid "Delivery to remote servers is underway"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:593
|
#: src/Object/Post.php:590
|
||||||
msgid "Delivery to remote servers is mostly done"
|
msgid "Delivery to remote servers is mostly done"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:594
|
#: src/Object/Post.php:591
|
||||||
msgid "Delivery to remote servers is done"
|
msgid "Delivery to remote servers is done"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:614
|
#: src/Object/Post.php:611
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "%d comment"
|
msgid "%d comment"
|
||||||
msgid_plural "%d comments"
|
msgid_plural "%d comments"
|
||||||
msgstr[0] ""
|
msgstr[0] ""
|
||||||
msgstr[1] ""
|
msgstr[1] ""
|
||||||
|
|
||||||
#: src/Object/Post.php:615
|
#: src/Object/Post.php:612
|
||||||
msgid "Show more"
|
msgid "Show more"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:616
|
#: src/Object/Post.php:613
|
||||||
msgid "Show fewer"
|
msgid "Show fewer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:652
|
#: src/Object/Post.php:649
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Reshared by: %s"
|
msgid "Reshared by: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:657
|
#: src/Object/Post.php:654
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Viewed by: %s"
|
msgid "Viewed by: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:662
|
#: src/Object/Post.php:659
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Liked by: %s"
|
msgid "Liked by: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:667
|
#: src/Object/Post.php:664
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Disliked by: %s"
|
msgid "Disliked by: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:672
|
#: src/Object/Post.php:669
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Attended by: %s"
|
msgid "Attended by: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:677
|
#: src/Object/Post.php:674
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Maybe attended by: %s"
|
msgid "Maybe attended by: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:682
|
#: src/Object/Post.php:679
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Not attended by: %s"
|
msgid "Not attended by: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: src/Object/Post.php:687
|
#: src/Object/Post.php:684
|
||||||
#, php-format
|
#, php-format
|
||||||
msgid "Reacted with %s by: %s"
|
msgid "Reacted with %s by: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
Loading…
Reference in a new issue