From 694893e2bbd7610b0970fcf822c3617040ae8e6a Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 15 May 2025 12:37:57 +0000 Subject: [PATCH 01/23] Implement getAddonDependencyConfig --- src/Core/Addon/AddonHelper.php | 13 ++++ src/Core/Addon/AddonManagerHelper.php | 29 ++++++++ .../Core/Addon/AddonManagerHelperTest.php | 71 +++++++++++++++++++ 3 files changed, 113 insertions(+) diff --git a/src/Core/Addon/AddonHelper.php b/src/Core/Addon/AddonHelper.php index 03a21232e5..94dd721fa3 100644 --- a/src/Core/Addon/AddonHelper.php +++ b/src/Core/Addon/AddonHelper.php @@ -9,6 +9,8 @@ declare(strict_types=1); namespace Friendica\Core\Addon; +use Friendica\Core\Addon\Exception\AddonInvalidConfigFileException; + /** * Some functions to handle addons */ @@ -64,6 +66,17 @@ interface AddonHelper */ public function getAddonInfo(string $addonId): AddonInfo; + /** + * Returns a dependency config array for a given addon + * + * This will load a potential config-file from the static directory, like `addon/{addonId}/static/dependencies.config.php` + * + * @throws AddonInvalidConfigFileException If the config file doesn't return an array + * + * @return array the config as array or empty array if no config file was found + */ + public function getAddonDependencyConfig(string $addonId): array; + /** * Checks if the provided addon is enabled */ diff --git a/src/Core/Addon/AddonManagerHelper.php b/src/Core/Addon/AddonManagerHelper.php index 21573ab89c..a5b8ccc96e 100644 --- a/src/Core/Addon/AddonManagerHelper.php +++ b/src/Core/Addon/AddonManagerHelper.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace Friendica\Core\Addon; +use Friendica\Core\Addon\Exception\AddonInvalidConfigFileException; use Friendica\Core\Cache\Capability\ICanCache; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Database\Database; @@ -246,6 +247,34 @@ final class AddonManagerHelper implements AddonHelper return AddonInfo::fromString($addonId, $raw); } + /** + * Returns a dependency config array for a given addon + * + * This will load a potential config-file from the static directory, like `addon/{addonId}/static/dependencies.config.php` + * + * @throws AddonInvalidConfigFileException If the config file doesn't return an array + * + * @return array the config as array or empty array if no config file was found + */ + public function getAddonDependencyConfig(string $addonId): array + { + $addonId = Strings::sanitizeFilePathItem(trim($addonId)); + + $configFile = $this->getAddonPath() . '/' . $addonId . '/static/dependencies.config.php'; + + if (!file_exists($configFile)) { + return []; + } + + $config = include($configFile); + + if (!is_array($config)) { + throw new AddonInvalidConfigFileException('Error loading config file ' . $configFile); + } + + return $config; + } + /** * Checks if the provided addon is enabled */ diff --git a/tests/Unit/Core/Addon/AddonManagerHelperTest.php b/tests/Unit/Core/Addon/AddonManagerHelperTest.php index fae0502474..0008f80692 100644 --- a/tests/Unit/Core/Addon/AddonManagerHelperTest.php +++ b/tests/Unit/Core/Addon/AddonManagerHelperTest.php @@ -12,6 +12,7 @@ namespace Friendica\Test\Unit\Core\Addon; use Exception; use Friendica\Core\Addon\AddonInfo; use Friendica\Core\Addon\AddonManagerHelper; +use Friendica\Core\Addon\Exception\AddonInvalidConfigFileException; use Friendica\Core\Cache\Capability\ICanCache; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Database\Database; @@ -54,6 +55,76 @@ class AddonManagerHelperTest extends TestCase $this->assertEquals('Hello Addon', $info->getName()); } + public function testGetAddonDependencyConfigReturnsArray(): void + { + $root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [ + 'helloaddon' => [ + 'static' => [ + 'dependencies.config.php' => << 'bar', + ]; + PHP, + ], + ] + ]); + + $addonManagerHelper = new AddonManagerHelper( + $root->url(), + $this->createStub(Database::class), + $this->createStub(IManageConfigValues::class), + $this->createStub(ICanCache::class), + $this->createStub(LoggerInterface::class), + $this->createStub(Profiler::class) + ); + + $this->assertSame(['foo' => 'bar'], $addonManagerHelper->getAddonDependencyConfig('helloaddon')); + } + + public function testGetAddonDependencyConfigWithoutConfigFileReturnsEmptyArray(): void + { + $root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [ + 'helloaddon' => [] + ]); + + $addonManagerHelper = new AddonManagerHelper( + $root->url(), + $this->createStub(Database::class), + $this->createStub(IManageConfigValues::class), + $this->createStub(ICanCache::class), + $this->createStub(LoggerInterface::class), + $this->createStub(Profiler::class) + ); + + $this->assertSame([], $addonManagerHelper->getAddonDependencyConfig('helloaddon')); + } + + public function testGetAddonDependencyConfigWithoutReturningAnArrayThrowsException(): void + { + $root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [ + 'helloaddon' => [ + 'static' => [ + 'dependencies.config.php' => 'url(), + $this->createStub(Database::class), + $this->createStub(IManageConfigValues::class), + $this->createStub(ICanCache::class), + $this->createStub(LoggerInterface::class), + $this->createStub(Profiler::class) + ); + + $this->expectException(AddonInvalidConfigFileException::class); + $this->expectExceptionMessageMatches('^#Error loading config file .+/helloaddon/static/dependencies\.config\.php#$'); + + $addonManagerHelper->getAddonDependencyConfig('helloaddon'); + } + public function testEnabledAddons(): void { $config = $this->createStub(IManageConfigValues::class); From 203e9642d515dbabb792e5ffc1671d80ac9ffae7 Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 15 May 2025 13:12:17 +0000 Subject: [PATCH 02/23] Inline AddonLoader for strategies into StrategiesFileManager --- src/Core/Hooks/Util/StrategiesFileManager.php | 71 ++++++++++++++++--- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/src/Core/Hooks/Util/StrategiesFileManager.php b/src/Core/Hooks/Util/StrategiesFileManager.php index a876dc832c..15b624b916 100644 --- a/src/Core/Hooks/Util/StrategiesFileManager.php +++ b/src/Core/Hooks/Util/StrategiesFileManager.php @@ -7,9 +7,15 @@ namespace Friendica\Core\Hooks\Util; +use Friendica\Core\Addon\AddonHelper; use Friendica\Core\Addon\Capability\ICanLoadAddons; +use Friendica\Core\Addon\Exception\AddonInvalidConfigFileException; +use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Hooks\Capability\ICanRegisterStrategies; use Friendica\Core\Hooks\Exceptions\HookConfigException; +use Friendica\Core\Logger\Factory\LoggerFactory; +use Friendica\Util\Strings; +use Psr\Log\LoggerInterface; /** * Manage all strategies.config.php files @@ -24,17 +30,15 @@ class StrategiesFileManager const STATIC_DIR = 'static'; const CONFIG_NAME = 'strategies'; - /** @var ICanLoadAddons */ - protected $addonLoader; - /** @var array */ - protected $config = []; + private AddonHelper $addonHelper; + protected array $config = []; /** @var string */ protected $basePath; - public function __construct(string $basePath, ICanLoadAddons $addonLoader) + public function __construct(string $basePath, AddonHelper $addonHelper) { $this->basePath = $basePath; - $this->addonLoader = $addonLoader; + $this->addonHelper = $addonHelper; } /** @@ -69,7 +73,7 @@ class StrategiesFileManager public function loadConfig() { // load core hook config - $configFile = $this->basePath . '/' . static::STATIC_DIR . '/' . static::CONFIG_NAME . '.config.php'; + $configFile = $this->addonHelper->getAddonPath() . '/' . static::STATIC_DIR . '/' . static::CONFIG_NAME . '.config.php'; if (!file_exists($configFile)) { throw new HookConfigException(sprintf('config file %s does not exist.', $configFile)); @@ -84,6 +88,57 @@ class StrategiesFileManager /** * @deprecated 2025.02 Providing strategies via addons is deprecated and will be removed in 5 months. */ - $this->config = array_merge_recursive($config, $this->addonLoader->getActiveAddonConfig(static::CONFIG_NAME)); + $this->config = array_merge_recursive($config, $this->getActiveAddonConfig(static::CONFIG_NAME)); + } + + /** + * @deprecated 2025.02 Providing strategies via addons is deprecated and will be removed in 5 months. + */ + private function getActiveAddonConfig(string $configName): array + { + $this->addonHelper->loadAddons(); + + $addons = $this->addonHelper->getEnabledAddons(); + $returnConfig = []; + + foreach ($addons as $addon) { + $addonName = Strings::sanitizeFilePathItem(trim($addon)); + + $configFile = $this->addonHelper->getAddonPath() . '/' . $addonName . '/' . static::STATIC_DIR . '/' . $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); + } + + if ($configName === 'strategies') { + foreach ($config as $classname => $rule) { + if ($classname === LoggerInterface::class) { + @trigger_error(sprintf( + 'Providing a strategy for `%s` is deprecated since 2025.02 and will stop working in 5 months, please provide an implementation for `%s` via `dependency.config.php` and remove the `strategies.config.php` file in the `%s` addon.', + $classname, + LoggerFactory::class, + $addonName, + ), \E_USER_DEPRECATED); + } else { + @trigger_error(sprintf( + 'Providing strategies for `%s` via addons is deprecated since 2025.02 and will stop working in 5 months, please stop using this and remove the `strategies.config.php` file in the `%s` addon.', + $classname, + $addonName, + ), \E_USER_DEPRECATED); + } + } + } + + $returnConfig = array_merge_recursive($returnConfig, $config); + } + + return $returnConfig; } } From 1428085c4befc2a3db0a6d421dc9b8c802423bc0 Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 15 May 2025 13:15:52 +0000 Subject: [PATCH 03/23] Soft deprecate AddonLoader and ICanLoadAddons --- src/Core/Addon/Capability/ICanLoadAddons.php | 4 ++++ src/Core/Addon/Model/AddonLoader.php | 7 ++++++- static/dependencies.config.php | 1 - 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Core/Addon/Capability/ICanLoadAddons.php b/src/Core/Addon/Capability/ICanLoadAddons.php index 993c431497..63978462d3 100644 --- a/src/Core/Addon/Capability/ICanLoadAddons.php +++ b/src/Core/Addon/Capability/ICanLoadAddons.php @@ -9,12 +9,16 @@ namespace Friendica\Core\Addon\Capability; /** * Interface for loading Addons specific content + * + * @deprecated 2025.02 Use implementation of `\Friendica\Core\Addon\AddonHelper` instead. */ interface ICanLoadAddons { /** * Returns a merged config array of all active addons for a given config-name * + * @deprecated 2025.02 Use `\Friendica\Core\Addon\AddonHelper::getAddonDependencyConfig()` instead. + * * @param string $configName The config-name (config-file at the static directory, like 'hooks' => '{addon}/static/hooks.config.php) * * @return array the merged array diff --git a/src/Core/Addon/Model/AddonLoader.php b/src/Core/Addon/Model/AddonLoader.php index a608a66e3d..4e0da1c80b 100644 --- a/src/Core/Addon/Model/AddonLoader.php +++ b/src/Core/Addon/Model/AddonLoader.php @@ -14,6 +14,9 @@ use Friendica\Core\Logger\Factory\LoggerFactory; use Friendica\Util\Strings; use Psr\Log\LoggerInterface; +/** + * @deprecated 2025.02 Use implementation of `\Friendica\Core\Addon\AddonHelper` instead. + */ class AddonLoader implements ICanLoadAddons { const STATIC_PATH = 'static'; @@ -28,7 +31,9 @@ class AddonLoader implements ICanLoadAddons $this->config = $config; } - /** {@inheritDoc} */ + /** + * @deprecated 2025.02 Use `\Friendica\Core\Addon\AddonHelper::getAddonDependencyConfig()` instead. + */ public function getActiveAddonConfig(string $configName): array { $addons = array_keys(array_filter($this->config->get('addons') ?? [])); diff --git a/static/dependencies.config.php b/static/dependencies.config.php index 04e2dd2aee..1ce02a2411 100644 --- a/static/dependencies.config.php +++ b/static/dependencies.config.php @@ -39,7 +39,6 @@ return (function(string $basepath, array $getVars, array $serverVars, array $coo 'instanceOf' => \Friendica\Core\Addon\Model\AddonLoader::class, 'constructParams' => [ $basepath, - [Dice::INSTANCE => Dice::SELF], ], ], \Friendica\Core\Addon\AddonHelper::class => [ From 9870fbf84fc07831435dc42d290d644dc124965c Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 15 May 2025 13:26:34 +0000 Subject: [PATCH 04/23] Fix unit tests --- tests/Unit/Core/Addon/AddonManagerHelperTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Core/Addon/AddonManagerHelperTest.php b/tests/Unit/Core/Addon/AddonManagerHelperTest.php index 0008f80692..f4118503f7 100644 --- a/tests/Unit/Core/Addon/AddonManagerHelperTest.php +++ b/tests/Unit/Core/Addon/AddonManagerHelperTest.php @@ -120,7 +120,7 @@ class AddonManagerHelperTest extends TestCase ); $this->expectException(AddonInvalidConfigFileException::class); - $this->expectExceptionMessageMatches('^#Error loading config file .+/helloaddon/static/dependencies\.config\.php#$'); + $this->expectExceptionMessageMatches('#Error loading config file .+/helloaddon/static/dependencies\.config\.php#'); $addonManagerHelper->getAddonDependencyConfig('helloaddon'); } From 59d22b87af2015c35815d0f5da7a962191f681f7 Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 15 May 2025 13:55:48 +0000 Subject: [PATCH 05/23] Fix bath to static folder --- src/Core/Hooks/Util/StrategiesFileManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Hooks/Util/StrategiesFileManager.php b/src/Core/Hooks/Util/StrategiesFileManager.php index 15b624b916..9c3041c0a5 100644 --- a/src/Core/Hooks/Util/StrategiesFileManager.php +++ b/src/Core/Hooks/Util/StrategiesFileManager.php @@ -73,7 +73,7 @@ class StrategiesFileManager public function loadConfig() { // load core hook config - $configFile = $this->addonHelper->getAddonPath() . '/' . static::STATIC_DIR . '/' . static::CONFIG_NAME . '.config.php'; + $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)); From d00fc89a59fdf6c723dbc94a05d28392c86ac074 Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 15 May 2025 14:06:36 +0000 Subject: [PATCH 06/23] Do not use AddonHelper in StrategiesFileManager to avoid circular dependendy with strategies ans cache --- src/Core/Hooks/Util/StrategiesFileManager.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/Core/Hooks/Util/StrategiesFileManager.php b/src/Core/Hooks/Util/StrategiesFileManager.php index 9c3041c0a5..e4144722dd 100644 --- a/src/Core/Hooks/Util/StrategiesFileManager.php +++ b/src/Core/Hooks/Util/StrategiesFileManager.php @@ -30,15 +30,15 @@ class StrategiesFileManager const STATIC_DIR = 'static'; const CONFIG_NAME = 'strategies'; - private AddonHelper $addonHelper; + private IManageConfigValues $configuration; protected array $config = []; /** @var string */ protected $basePath; - public function __construct(string $basePath, AddonHelper $addonHelper) + public function __construct(string $basePath, IManageConfigValues $configuration) { - $this->basePath = $basePath; - $this->addonHelper = $addonHelper; + $this->basePath = $basePath; + $this->configuration = $configuration; } /** @@ -96,15 +96,13 @@ class StrategiesFileManager */ private function getActiveAddonConfig(string $configName): array { - $this->addonHelper->loadAddons(); - - $addons = $this->addonHelper->getEnabledAddons(); + $addons = array_keys(array_filter($this->configuration->get('addons') ?? [])); $returnConfig = []; foreach ($addons as $addon) { $addonName = Strings::sanitizeFilePathItem(trim($addon)); - $configFile = $this->addonHelper->getAddonPath() . '/' . $addonName . '/' . static::STATIC_DIR . '/' . $configName . '.config.php'; + $configFile = $this->basePath . '/addon/' . $addonName . '/' . static::STATIC_DIR . '/' . $configName . '.config.php'; if (!file_exists($configFile)) { // Addon unmodified, skipping From f9d695d80d4f60a51bedb5e5f52e0a0de6dc979a Mon Sep 17 00:00:00 2001 From: Art4 Date: Thu, 15 May 2025 14:09:36 +0000 Subject: [PATCH 07/23] Fix code style --- src/Core/Hooks/Util/StrategiesFileManager.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Core/Hooks/Util/StrategiesFileManager.php b/src/Core/Hooks/Util/StrategiesFileManager.php index e4144722dd..f4fc5878c7 100644 --- a/src/Core/Hooks/Util/StrategiesFileManager.php +++ b/src/Core/Hooks/Util/StrategiesFileManager.php @@ -7,8 +7,6 @@ namespace Friendica\Core\Hooks\Util; -use Friendica\Core\Addon\AddonHelper; -use Friendica\Core\Addon\Capability\ICanLoadAddons; use Friendica\Core\Addon\Exception\AddonInvalidConfigFileException; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Hooks\Capability\ICanRegisterStrategies; From 3f1082400a5f56468dc18cfeb3d99377502ff4e5 Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 16 May 2025 07:12:08 +0000 Subject: [PATCH 08/23] Fix tests for StrategiesFileManager --- .../Hooks/Util/StrategiesFileManagerTest.php | 145 ++++++++++-------- 1 file changed, 85 insertions(+), 60 deletions(-) diff --git a/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php b/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php index 633b636701..f06e0b09e9 100644 --- a/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php +++ b/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php @@ -8,6 +8,7 @@ namespace Friendica\Test\src\Core\Hooks\Util; use Friendica\Core\Addon\Capability\ICanLoadAddons; +use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Hooks\Capability\ICanRegisterStrategies; use Friendica\Core\Hooks\Exceptions\HookConfigException; use Friendica\Core\Hooks\Util\StrategiesFileManager; @@ -33,49 +34,61 @@ class StrategiesFileManagerTest extends MockedTestCase return [ 'normal' => [ 'content' => << [ - \Psr\Log\NullLogger::class => [''], - ], -]; -EOF, - 'addonsArray' => [], + return [ + \Psr\Log\LoggerInterface::class => [ + \Psr\Log\NullLogger::class => [''], + ], + ]; + EOF, + 'addonContent' => << [ [LoggerInterface::class, NullLogger::class, ''], ], ], 'normalWithString' => [ 'content' => << [ - \Psr\Log\NullLogger::class => '', - ], -]; -EOF, - 'addonsArray' => [], + return [ + \Psr\Log\LoggerInterface::class => [ + \Psr\Log\NullLogger::class => '', + ], + ]; + EOF, + 'addonContent' => << [ [LoggerInterface::class, NullLogger::class, ''], ], ], 'withAddons' => [ 'content' => << [ - \Psr\Log\NullLogger::class => [''], - ], -]; -EOF, - 'addonsArray' => [ - \Psr\Log\LoggerInterface::class => [ - \Psr\Log\NullLogger::class => ['null'], - ], - ], + return [ + \Psr\Log\LoggerInterface::class => [ + \Psr\Log\NullLogger::class => [''], + ], + ]; + EOF, + 'addonContent' => << [ + \Psr\Log\NullLogger::class => ['null'], + ], + ]; + EOF, 'assertStrategies' => [ [LoggerInterface::class, NullLogger::class, ''], [LoggerInterface::class, NullLogger::class, 'null'], @@ -83,19 +96,23 @@ EOF, ], 'withAddonsWithString' => [ 'content' => << [ - \Psr\Log\NullLogger::class => [''], - ], -]; -EOF, - 'addonsArray' => [ - \Psr\Log\LoggerInterface::class => [ - \Psr\Log\NullLogger::class => 'null', - ], - ], + return [ + \Psr\Log\LoggerInterface::class => [ + \Psr\Log\NullLogger::class => [''], + ], + ]; + EOF, + 'addonContent' => << [ + \Psr\Log\NullLogger::class => ['null'], + ], + ]; + EOF, 'assertStrategies' => [ [LoggerInterface::class, NullLogger::class, ''], [LoggerInterface::class, NullLogger::class, 'null'], @@ -104,19 +121,23 @@ EOF, // This should work because unique name convention is part of the instance manager logic, not of the file-infrastructure layer 'withAddonsDoubleNamed' => [ 'content' => << [ - \Psr\Log\NullLogger::class => [''], - ], -]; -EOF, - 'addonsArray' => [ - \Psr\Log\LoggerInterface::class => [ - \Psr\Log\NullLogger::class => [''], - ], - ], + return [ + \Psr\Log\LoggerInterface::class => [ + \Psr\Log\NullLogger::class => [''], + ], + ]; + EOF, + 'addonContent' => << [ + \Psr\Log\NullLogger::class => [''], + ], + ]; + EOF, 'assertStrategies' => [ [LoggerInterface::class, NullLogger::class, ''], [LoggerInterface::class, NullLogger::class, ''], @@ -128,16 +149,20 @@ EOF, /** * @dataProvider dataHooks */ - public function testSetupHooks(string $content, array $addonsArray, array $assertStrategies) + public function testSetupHooks(string $content, string $addonContent, 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(); + vfsStream::newFile('addon/testaddon/' . StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php') + ->withContent($addonContent) + ->at($this->root); - $hookFileManager = new StrategiesFileManager($this->root->url(), $addonLoader); + $config = \Mockery::mock(IManageConfigValues::class); + $config->shouldReceive('get')->andReturn(['testaddon' => ['admin' => false]])->once(); + + $hookFileManager = new StrategiesFileManager($this->root->url(), $config); $instanceManager = \Mockery::mock(ICanRegisterStrategies::class); foreach ($assertStrategies as $assertStrategy) { @@ -155,9 +180,9 @@ EOF, */ public function testMissingStrategiesFile() { - $addonLoader = \Mockery::mock(ICanLoadAddons::class); + $config = \Mockery::mock(IManageConfigValues::class); $instanceManager = \Mockery::mock(ICanRegisterStrategies::class); - $hookFileManager = new StrategiesFileManager($this->root->url(), $addonLoader); + $hookFileManager = new StrategiesFileManager($this->root->url(), $config); self::expectException(HookConfigException::class); self::expectExceptionMessage(sprintf('config file %s does not exist.', @@ -171,9 +196,9 @@ EOF, */ public function testWrongStrategiesFile() { - $addonLoader = \Mockery::mock(ICanLoadAddons::class); + $config = \Mockery::mock(IManageConfigValues::class); $instanceManager = \Mockery::mock(ICanRegisterStrategies::class); - $hookFileManager = new StrategiesFileManager($this->root->url(), $addonLoader); + $hookFileManager = new StrategiesFileManager($this->root->url(), $config); vfsStream::newFile(StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php') ->withContent(" Date: Fri, 16 May 2025 08:20:58 +0000 Subject: [PATCH 09/23] Replace AddonLoader with AddonHelper in App --- src/App.php | 13 ++++++++----- .../Core/Hooks/Util/StrategiesFileManagerTest.php | 1 - 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/App.php b/src/App.php index ffcbfd1544..b10e474885 100644 --- a/src/App.php +++ b/src/App.php @@ -18,7 +18,6 @@ use Friendica\Capabilities\ICanCreateResponses; use Friendica\Capabilities\ICanHandleRequests; use Friendica\Content\Nav; use Friendica\Core\Addon\AddonHelper; -use Friendica\Core\Addon\Capability\ICanLoadAddons; use Friendica\Core\Config\Factory\Config; use Friendica\Core\Container; use Friendica\Core\Hooks\HookEventBridge; @@ -278,11 +277,15 @@ class App private function setupContainerForAddons(): void { - /** @var ICanLoadAddons $addonLoader */ - $addonLoader = $this->container->create(ICanLoadAddons::class); + /** @var AddonHelper $addonHelper */ + $addonHelper = $this->container->create(AddonHelper::class); - foreach ($addonLoader->getActiveAddonConfig('dependencies') as $name => $rule) { - $this->container->addRule($name, $rule); + $addonHelper->loadAddons(); + + foreach ($addonHelper->getEnabledAddons() as $addonId) { + foreach ($addonHelper->getAddonDependencyConfig($addonId) as $name => $rule) { + $this->container->addRule($name, $rule); + } } } diff --git a/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php b/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php index f06e0b09e9..3349f36fd6 100644 --- a/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php +++ b/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php @@ -7,7 +7,6 @@ namespace Friendica\Test\src\Core\Hooks\Util; -use Friendica\Core\Addon\Capability\ICanLoadAddons; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Hooks\Capability\ICanRegisterStrategies; use Friendica\Core\Hooks\Exceptions\HookConfigException; From b8b7d3cd150871f7106a52662d9d80e1b2731794 Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 16 May 2025 08:23:31 +0000 Subject: [PATCH 10/23] Hard deprecate AddonLoader --- src/Core/Addon/Model/AddonLoader.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Core/Addon/Model/AddonLoader.php b/src/Core/Addon/Model/AddonLoader.php index 4e0da1c80b..b56ba3eed8 100644 --- a/src/Core/Addon/Model/AddonLoader.php +++ b/src/Core/Addon/Model/AddonLoader.php @@ -27,6 +27,8 @@ class AddonLoader implements ICanLoadAddons public function __construct(string $basePath, IManageConfigValues $config) { + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); + $this->basePath = $basePath; $this->config = $config; } @@ -36,6 +38,8 @@ class AddonLoader implements ICanLoadAddons */ public function getActiveAddonConfig(string $configName): array { + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use `\Friendica\Core\Addon\AddonHelper::getAddonDependencyConfig()` instead.', E_USER_DEPRECATED); + $addons = array_keys(array_filter($this->config->get('addons') ?? [])); $returnConfig = []; From 603f96b40381cc9a6ce81712e94c0384a21eb4c5 Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 16 May 2025 08:28:24 +0000 Subject: [PATCH 11/23] Refactor StrategiesFileManager --- src/Core/Hooks/Util/StrategiesFileManager.php | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/Core/Hooks/Util/StrategiesFileManager.php b/src/Core/Hooks/Util/StrategiesFileManager.php index f4fc5878c7..c56a13ee6a 100644 --- a/src/Core/Hooks/Util/StrategiesFileManager.php +++ b/src/Core/Hooks/Util/StrategiesFileManager.php @@ -86,13 +86,10 @@ class StrategiesFileManager /** * @deprecated 2025.02 Providing strategies via addons is deprecated and will be removed in 5 months. */ - $this->config = array_merge_recursive($config, $this->getActiveAddonConfig(static::CONFIG_NAME)); + $this->config = array_merge_recursive($config, $this->getActiveAddonConfig()); } - /** - * @deprecated 2025.02 Providing strategies via addons is deprecated and will be removed in 5 months. - */ - private function getActiveAddonConfig(string $configName): array + private function getActiveAddonConfig(): array { $addons = array_keys(array_filter($this->configuration->get('addons') ?? [])); $returnConfig = []; @@ -100,7 +97,7 @@ class StrategiesFileManager foreach ($addons as $addon) { $addonName = Strings::sanitizeFilePathItem(trim($addon)); - $configFile = $this->basePath . '/addon/' . $addonName . '/' . static::STATIC_DIR . '/' . $configName . '.config.php'; + $configFile = $this->basePath . '/addon/' . $addonName . '/' . static::STATIC_DIR . '/strategies.config.php'; if (!file_exists($configFile)) { // Addon unmodified, skipping @@ -113,22 +110,20 @@ class StrategiesFileManager throw new AddonInvalidConfigFileException('Error loading config file ' . $configFile); } - if ($configName === 'strategies') { - foreach ($config as $classname => $rule) { - if ($classname === LoggerInterface::class) { - @trigger_error(sprintf( - 'Providing a strategy for `%s` is deprecated since 2025.02 and will stop working in 5 months, please provide an implementation for `%s` via `dependency.config.php` and remove the `strategies.config.php` file in the `%s` addon.', - $classname, - LoggerFactory::class, - $addonName, - ), \E_USER_DEPRECATED); - } else { - @trigger_error(sprintf( - 'Providing strategies for `%s` via addons is deprecated since 2025.02 and will stop working in 5 months, please stop using this and remove the `strategies.config.php` file in the `%s` addon.', - $classname, - $addonName, - ), \E_USER_DEPRECATED); - } + foreach ($config as $classname => $rule) { + if ($classname === LoggerInterface::class) { + @trigger_error(sprintf( + 'Providing a strategy for `%s` is deprecated since 2025.02 and will stop working in 5 months, please provide an implementation for `%s` via `dependency.config.php` and remove the `strategies.config.php` file in the `%s` addon.', + $classname, + LoggerFactory::class, + $addonName, + ), \E_USER_DEPRECATED); + } else { + @trigger_error(sprintf( + 'Providing strategies for `%s` via addons is deprecated since 2025.02 and will stop working in 5 months, please stop using this and remove the `strategies.config.php` file in the `%s` addon.', + $classname, + $addonName, + ), \E_USER_DEPRECATED); } } From 53191caeac213cdb5bd601c1e5d6861bdeeed946 Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 16 May 2025 08:33:01 +0000 Subject: [PATCH 12/23] fix code style --- .../Core/Hooks/Util/StrategiesFileManagerTest.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php b/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php index 3349f36fd6..7adcfc0a97 100644 --- a/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php +++ b/tests/src/Core/Hooks/Util/StrategiesFileManagerTest.php @@ -184,8 +184,10 @@ class StrategiesFileManagerTest extends MockedTestCase $hookFileManager = new StrategiesFileManager($this->root->url(), $config); self::expectException(HookConfigException::class); - self::expectExceptionMessage(sprintf('config file %s does not exist.', - $this->root->url() . '/' . StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php')); + self::expectExceptionMessage(sprintf( + 'config file %s does not exist.', + $this->root->url() . '/' . StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php' + )); $hookFileManager->loadConfig(); } @@ -204,8 +206,10 @@ class StrategiesFileManagerTest extends MockedTestCase ->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')); + self::expectExceptionMessage(sprintf( + 'Error loading config file %s.', + $this->root->url() . '/' . StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php' + )); $hookFileManager->loadConfig(); } From 07db85b99da96afc9c3e336dee5fc0b2d1224ce7 Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 6 Jun 2025 08:15:00 +0000 Subject: [PATCH 13/23] Install phpstan-strict-rules --- .phpstan.neon | 6 ++++++ composer.json | 1 + composer.lock | 50 +++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/.phpstan.neon b/.phpstan.neon index fb731728b8..551cf2b69b 100644 --- a/.phpstan.neon +++ b/.phpstan.neon @@ -2,6 +2,9 @@ # # SPDX-License-Identifier: CC0-1.0 +includes: + - vendor/phpstan/phpstan-strict-rules/rules.neon + parameters: level: 3 @@ -23,6 +26,9 @@ parameters: dynamicConstantNames: - DB_UPDATE_VERSION + strictRules: + allRules: false + ignoreErrors: - # Ignore missing GdImage class in PHP <= 7.4 diff --git a/composer.json b/composer.json index 9973336590..fc26674ca7 100644 --- a/composer.json +++ b/composer.json @@ -157,6 +157,7 @@ "php-mock/php-mock-phpunit": "^2.10", "phpmd/phpmd": "^2.15", "phpstan/phpstan": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", "phpunit/phpunit": "^9" }, "scripts": { diff --git a/composer.lock b/composer.lock index fb9fadddbd..ab66a33d05 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "897b878d6db24b9a6437bd9f971478be", + "content-hash": "e93a8ac7e31cf3e5e0ca76134e5ffa0b", "packages": [ { "name": "asika/simple-console", @@ -5849,6 +5849,54 @@ ], "time": "2024-11-11T15:43:04+00:00" }, + { + "name": "phpstan/phpstan-strict-rules", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-strict-rules.git", + "reference": "a4a6a08bd4a461e516b9c3b8fdbf0f1883b34158" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/a4a6a08bd4a461e516b9c3b8fdbf0f1883b34158", + "reference": "a4a6a08bd4a461e516b9c3b8fdbf0f1883b34158", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "phpstan/phpstan": "^2.0" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Extra strict and opinionated rules for PHPStan", + "support": { + "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.0" + }, + "time": "2024-10-26T16:04:33+00:00" + }, { "name": "phpunit/php-code-coverage", "version": "9.2.31", From 95b3322731824b8dbb6ceaa0eca470e6bacc56ae Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 6 Jun 2025 10:55:59 +0000 Subject: [PATCH 14/23] Fix many strict errors --- .phpstan.neon | 1 + src/Content/Pager.php | 4 ++-- src/Content/Text/HTML.php | 4 ++-- src/Core/Lock/Type/AbstractLock.php | 2 +- src/Core/Session/Handler/Cache.php | 7 +++++-- src/Core/Session/Handler/Database.php | 14 ++++++++++++-- src/Core/Worker.php | 2 +- src/Database/PostUpdate.php | 2 +- src/Network/Probe.php | 4 ++-- src/Object/Image.php | 10 ++++------ src/Util/HTTPSignature.php | 4 ++-- 11 files changed, 33 insertions(+), 21 deletions(-) diff --git a/.phpstan.neon b/.phpstan.neon index 551cf2b69b..15d3ec15fb 100644 --- a/.phpstan.neon +++ b/.phpstan.neon @@ -26,6 +26,7 @@ parameters: dynamicConstantNames: - DB_UPDATE_VERSION + # See all rules at https://github.com/phpstan/phpstan-strict-rules/blob/2.0.x/rules.neon strictRules: allRules: false diff --git a/src/Content/Pager.php b/src/Content/Pager.php index 0cdf5c0e4a..4d35ff2b46 100644 --- a/src/Content/Pager.php +++ b/src/Content/Pager.php @@ -215,8 +215,8 @@ class Pager // Limit the number of displayed page number buttons. if ($numpages > 8) { - $numstart = (($this->getPage() > 4) ? ($this->getPage() - 4) : 1); - $numstop = (($this->getPage() > ($numpages - 7)) ? $numpages : ($numstart + 8)); + $numstart = ($this->getPage() > 4) ? ($this->getPage() - 4) : 1; + $numstop = ($this->getPage() > ($numpages - 7)) ? $numpages : ($numstart + 8); } $pages = []; diff --git a/src/Content/Text/HTML.php b/src/Content/Text/HTML.php index 72c61f38cc..701125dd27 100644 --- a/src/Content/Text/HTML.php +++ b/src/Content/Text/HTML.php @@ -55,7 +55,7 @@ class HTML $xpath = new DOMXPath($doc); - /** @var \DOMNode[] $list */ + /** @var \DOMNodeList<\DOMNode>|false $list */ $list = $xpath->query("//" . $tag); foreach ($list as $node) { $attr = []; @@ -1018,7 +1018,7 @@ class HTML */ public static function checkRelMeLink(DOMDocument $doc, UriInterface $meUrl): bool { - $xpath = new \DOMXpath($doc); + $xpath = new \DOMXPath($doc); // This expression checks that "me" is among the space-delimited values of the "rel" attribute. // And that the href attribute contains exactly the provided URL diff --git a/src/Core/Lock/Type/AbstractLock.php b/src/Core/Lock/Type/AbstractLock.php index 9d8b5849b2..6854be267c 100644 --- a/src/Core/Lock/Type/AbstractLock.php +++ b/src/Core/Lock/Type/AbstractLock.php @@ -28,7 +28,7 @@ abstract class AbstractLock implements ICanLock */ protected function hasAcquiredLock(string $key): bool { - return isset($this->acquireLock[$key]) && $this->acquiredLocks[$key] === true; + return isset($this->acquiredLocks[$key]) && $this->acquiredLocks[$key] === true; } /** diff --git a/src/Core/Session/Handler/Cache.php b/src/Core/Session/Handler/Cache.php index 7629d9a164..65b87872a7 100644 --- a/src/Core/Session/Handler/Cache.php +++ b/src/Core/Session/Handler/Cache.php @@ -99,8 +99,11 @@ class Cache extends AbstractSessionHandler } #[\ReturnTypeWillChange] - public function gc($max_lifetime): bool + /** + * @return int|false + */ + public function gc($max_lifetime) { - return true; + return 0; // Cache does not support garbage collection, so we return 0 to indicate no action taken } } diff --git a/src/Core/Session/Handler/Database.php b/src/Core/Session/Handler/Database.php index 3ef64294b9..d8146139a1 100644 --- a/src/Core/Session/Handler/Database.php +++ b/src/Core/Session/Handler/Database.php @@ -124,13 +124,23 @@ class Database extends AbstractSessionHandler } #[\ReturnTypeWillChange] - public function gc($max_lifetime): bool + /** + * @return int|false + */ + public function gc($max_lifetime) { try { - return $this->dba->delete('session', ["`expire` < ?", time()]); + $result = $this->dba->delete('session', ["`expire` < ?", time()]); } catch (\Exception $exception) { $this->logger->warning('Cannot use garbage collector.', ['exception' => $exception]); return false; } + + if ($result !== false) { + // TODO: DBA::delete() returns true, but we need to return the number of deleted rows as interger + $result = 0; + } + + return $result; } } diff --git a/src/Core/Worker.php b/src/Core/Worker.php index fd33278502..fd2ab5c242 100644 --- a/src/Core/Worker.php +++ b/src/Core/Worker.php @@ -521,7 +521,7 @@ class Worker } if ($sleeping) { - DI::logger()->info('Cooldown ended.', ['max-load' => $load_cooldown, 'max-processes' => $processes_cooldown, 'load' => $load, 'called-by' => System::callstack(1)]); + DI::logger()->info('Cooldown ended.', ['max-load' => $load_cooldown, 'max-processes' => $processes_cooldown, 'load' => $load ?? [], 'called-by' => System::callstack(1)]); } } diff --git a/src/Database/PostUpdate.php b/src/Database/PostUpdate.php index 6aebda8096..031c2fec1d 100644 --- a/src/Database/PostUpdate.php +++ b/src/Database/PostUpdate.php @@ -193,7 +193,7 @@ class PostUpdate Contact::removeDuplicates($contact['nurl'], $contact['uid']); } - DBA::close($contact); + DBA::close($contacts); DI::keyValue()->set('post_update_version', 1322); DI::logger()->info('Done'); diff --git a/src/Network/Probe.php b/src/Network/Probe.php index 471f6e313a..92ee97b816 100644 --- a/src/Network/Probe.php +++ b/src/Network/Probe.php @@ -8,7 +8,7 @@ namespace Friendica\Network; use DOMDocument; -use DomXPath; +use DOMXPath; use Exception; use Friendica\Content\Text\HTML; use Friendica\Core\Hook; @@ -1273,7 +1273,7 @@ class Probe return []; } - $xpath = new DomXPath($doc); + $xpath = new DOMXPath($doc); $vcards = $xpath->query("//div[contains(concat(' ', @class, ' '), ' vcard ')]"); if (!is_object($vcards)) { diff --git a/src/Object/Image.php b/src/Object/Image.php index 0aad761341..283a8d4895 100644 --- a/src/Object/Image.php +++ b/src/Object/Image.php @@ -363,12 +363,11 @@ class Image * Rotates image * * @param integer $degrees degrees to rotate image - * @return mixed */ - public function rotate(int $degrees) + public function rotate(int $degrees): void { if (!$this->isValid()) { - return false; + return; } if ($this->isImagick()) { @@ -393,12 +392,11 @@ class Image * * @param boolean $horiz optional, default true * @param boolean $vert optional, default false - * @return mixed */ - public function flip(bool $horiz = true, bool $vert = false) + public function flip(bool $horiz = true, bool $vert = false): void { if (!$this->isValid()) { - return false; + return; } if ($this->isImagick()) { diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php index a779a59a86..c5a182f98e 100644 --- a/src/Util/HTTPSignature.php +++ b/src/Util/HTTPSignature.php @@ -592,7 +592,7 @@ class HTTPSignature return []; } - $sig_block = self::parseSigHeader($http_headers['HTTP_SIGNATURE']); + $sig_block = self::parseSigheader($http_headers['HTTP_SIGNATURE']); if (empty($sig_block['keyId'])) { DI::logger()->debug('No keyId', ['sig_block' => $sig_block]); @@ -652,7 +652,7 @@ class HTTPSignature } } - $sig_block = self::parseSigHeader($http_headers['HTTP_SIGNATURE']); + $sig_block = self::parseSigheader($http_headers['HTTP_SIGNATURE']); // Add fields from the signature block to the header. See issue 8845 if (!empty($sig_block['created']) && empty($headers['(created)'])) { From df7c3a65665735b37759b6601b3824f48e5f3188 Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 6 Jun 2025 10:59:45 +0000 Subject: [PATCH 15/23] Simplify pagination generation --- src/Content/Pager.php | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/Content/Pager.php b/src/Content/Pager.php index 4d35ff2b46..222247c6f5 100644 --- a/src/Content/Pager.php +++ b/src/Content/Pager.php @@ -208,7 +208,7 @@ class Pager 'class' => $this->getPage() == 1 ? 'disabled' : '' ]; - $numpages = $totalItemCount / $this->getItemsPerPage(); + $numpages = (int) ceil($totalItemCount / $this->getItemsPerPage()); $numstart = 1; $numstop = $numpages; @@ -237,22 +237,6 @@ class Pager } } - if (($totalItemCount % $this->getItemsPerPage()) != 0) { - if ($i == $this->getPage()) { - $pages[$i] = [ - 'url' => '#', - 'text' => $i, - 'class' => 'current active' - ]; - } else { - $pages[$i] = [ - 'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . $i), - 'text' => $i, - 'class' => 'n' - ]; - } - } - $data['pages'] = $pages; $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages); From 696972629dcd01dda4cec6e70f57339735c3178b Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 6 Jun 2025 14:31:05 +0000 Subject: [PATCH 16/23] Fix missing return statement --- src/Console/Worker.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Console/Worker.php b/src/Console/Worker.php index 97b7160d03..09ff5f6599 100644 --- a/src/Console/Worker.php +++ b/src/Console/Worker.php @@ -92,5 +92,7 @@ HELP; CoreWorker::unclaimProcess($process); $this->processRepo->delete($process); + + return; } } From 6633d1b49106658be00d738c7926f00ce3edc2f8 Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 13 Jun 2025 12:29:36 +0000 Subject: [PATCH 17/23] Ignore type mismatches of BaseModule and BaseRepository properties in child classes --- .phpstan.neon | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.phpstan.neon b/.phpstan.neon index 15d3ec15fb..201d6acb39 100644 --- a/.phpstan.neon +++ b/.phpstan.neon @@ -45,3 +45,17 @@ parameters: # Ignore missing IMAP\Connection class in PHP <= 8.0 message: '(^Parameter .+ has invalid type IMAP\\Connection\.$)' path: src + + - + # #Fixme: Ignore type mismatch of BaseRepository::$factory in child classes + message: '#^PHPDoc type Friendica\\.+ of property Friendica\\.+\:\:\$factory is not the same as PHPDoc type Friendica\\Capabilities\\ICanCreateFromTableRow of overridden property Friendica\\BaseRepository\:\:\$factory\.$#' + identifier: property.phpDocType + count: 13 + path: src + + - + # #Fixme: Ignore type mismatch of BaseModule::$response in BaseApi module + message: '#^PHPDoc type Friendica\\Module\\Api\\ApiResponse of property Friendica\\Module\\BaseApi\:\:\$response is not the same as PHPDoc type Friendica\\Capabilities\\ICanCreateResponses of overridden property Friendica\\BaseModule\:\:\$response\.$#' + identifier: property.phpDocType + count: 1 + path: src/Module/BaseApi.php From a06a816c08c3f71d29dcc4dadfed29a8a5aad90a Mon Sep 17 00:00:00 2001 From: Art4 Date: Fri, 13 Jun 2025 12:35:48 +0000 Subject: [PATCH 18/23] Fix code style --- src/Content/Pager.php | 10 +++++----- src/Object/Image.php | 34 +++++++++++++++++----------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Content/Pager.php b/src/Content/Pager.php index 222247c6f5..1199ec758f 100644 --- a/src/Content/Pager.php +++ b/src/Content/Pager.php @@ -157,10 +157,10 @@ class Pager 'text' => $this->l10n->t('newer'), 'class' => 'previous' . ($this->getPage() == 1 ? ' disabled' : '') ], - 'next' => [ + 'next' => [ 'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)), 'text' => $this->l10n->t('older'), - 'class' => 'next' . ($displayedItemCount < $this->getItemsPerPage() ? ' disabled' : '') + 'class' => 'next' . ($displayedItemCount < $this->getItemsPerPage() ? ' disabled' : '') ] ]; @@ -211,12 +211,12 @@ class Pager $numpages = (int) ceil($totalItemCount / $this->getItemsPerPage()); $numstart = 1; - $numstop = $numpages; + $numstop = $numpages; // Limit the number of displayed page number buttons. if ($numpages > 8) { $numstart = ($this->getPage() > 4) ? ($this->getPage() - 4) : 1; - $numstop = ($this->getPage() > ($numpages - 7)) ? $numpages : ($numstart + 8); + $numstop = ($this->getPage() > ($numpages - 7)) ? $numpages : ($numstart + 8); } $pages = []; @@ -239,7 +239,7 @@ class Pager $data['pages'] = $pages; - $lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages); + $lastpage = (($numpages > intval($numpages)) ? intval($numpages) + 1 : $numpages); $data['next'] = [ 'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)), diff --git a/src/Object/Image.php b/src/Object/Image.php index 283a8d4895..56c6f9f325 100644 --- a/src/Object/Image.php +++ b/src/Object/Image.php @@ -48,8 +48,8 @@ class Image public function __construct(string $data, string $type = '', string $filename = '', bool $imagick = true) { $this->filename = $filename; - $type = Images::addMimeTypeByDataIfInvalid($type, $data); - $type = Images::addMimeTypeByExtensionIfInvalid($type, $filename); + $type = Images::addMimeTypeByDataIfInvalid($type, $data); + $type = Images::addMimeTypeByExtensionIfInvalid($type, $filename); if (Images::isSupportedMimeType($type)) { $this->originType = $this->outputType = Images::getImageTypeByMimeType($type); @@ -108,7 +108,7 @@ class Image private function isAnimatedWebP(string $data) { $header_format = 'A4Riff/I1Filesize/A4Webp/A4Vp/A74Chunk'; - $header = @unpack($header_format, $data); + $header = @unpack($header_format, $data); if (!isset($header['Riff']) || strtoupper($header['Riff']) !== 'RIFF') { return false; @@ -348,7 +348,7 @@ class Image return false; } - $width = $this->getWidth(); + $width = $this->getWidth(); $height = $this->getHeight(); $scale = Images::getScalingDimensions($width, $height, $max); @@ -412,8 +412,8 @@ class Image return; } - $w = imagesx($this->image); - $h = imagesy($this->image); + $w = imagesx($this->image); + $h = imagesy($this->image); $flipped = imagecreate($w, $h); if ($horiz) { for ($x = 0; $x < $w; $x++) { @@ -521,7 +521,7 @@ class Image return false; } - $width = $this->getWidth(); + $width = $this->getWidth(); $height = $this->getHeight(); if ((!$width) || (!$height)) { @@ -530,22 +530,22 @@ class Image if ($width < $min && $height < $min) { if ($width > $height) { - $dest_width = $min; + $dest_width = $min; $dest_height = intval(($height * $min) / $width); } else { - $dest_width = intval(($width * $min) / $height); + $dest_width = intval(($width * $min) / $height); $dest_height = $min; } } else { if ($width < $min) { - $dest_width = $min; + $dest_width = $min; $dest_height = intval(($height * $min) / $width); } else { if ($height < $min) { - $dest_width = intval(($width * $min) / $height); + $dest_width = intval(($width * $min) / $height); $dest_height = $min; } else { - $dest_width = $width; + $dest_width = $width; $dest_height = $height; } } @@ -620,7 +620,7 @@ class Image imagedestroy($this->image); } - $this->image = $dest; + $this->image = $dest; $this->width = imagesx($this->image); $this->height = imagesy($this->image); } @@ -797,9 +797,9 @@ class Image } $row[] = [$colors['r'], $colors['g'], $colors['b']]; } else { - $index = imagecolorat($image->image, $x, $y); + $index = imagecolorat($image->image, $x, $y); $colors = @imagecolorsforindex($image->image, $index); - $row[] = [$colors['red'], $colors['green'], $colors['blue']]; + $row[] = [$colors['red'], $colors['green'], $colors['blue']]; } } $pixels[] = $row; @@ -828,7 +828,7 @@ class Image if ($this->isImagick()) { $this->image = new Imagick(); - $draw = new ImagickDraw(); + $draw = new ImagickDraw(); $this->image->newImage($scaled['width'], $scaled['height'], '', 'png'); } else { $this->image = imagecreatetruecolor($scaled['width'], $scaled['height']); @@ -836,7 +836,7 @@ class Image for ($y = 0; $y < $scaled['height']; ++$y) { for ($x = 0; $x < $scaled['width']; ++$x) { - [$r, $g, $b] = $pixels[$y][$x]; + list($r, $g, $b) = $pixels[$y][$x]; if ($draw !== null) { $draw->setFillColor("rgb($r, $g, $b)"); $draw->point($x, $y); From 7fd6272a32209c3367ebeed72243c35f586752c3 Mon Sep 17 00:00:00 2001 From: Marcus Funch Date: Tue, 17 Jun 2025 19:32:19 +0200 Subject: [PATCH 19/23] Frio: Fix bug making navigation tooltips disappear after scroll v2 --- view/theme/frio/templates/nav.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/theme/frio/templates/nav.tpl b/view/theme/frio/templates/nav.tpl index 37edb7118a..843a2cf00d 100644 --- a/view/theme/frio/templates/nav.tpl +++ b/view/theme/frio/templates/nav.tpl @@ -164,7 +164,7 @@