Compare commits

...

32 commits

Author SHA1 Message Date
Matthew Exon
548c0b9602 Change defaults: infinite scroll enabled by default, stay local enabled by default 2025-08-01 07:45:32 +02:00
Philipp
9c32e22530
Merge pull request #14983 from mf-fx/img_alt_tag_indicator
Issue 14491 - Add alt tag indicators to images
2025-06-23 00:25:31 +02:00
Philipp
67330cb497
Merge pull request #14987 from mf-fx/css_bug_global_missing_end_paren
global.css: Add missing end parenthesis
2025-06-22 14:28:38 +02:00
Marcus Funch
5d481426cd global.css: Add missing end paren 2025-06-22 14:15:58 +02:00
Marcus Funch
e214aabf31 Issue 14491 - Add alt tag indicators to images 2025-06-21 19:47:46 +02:00
Philipp
5805d21688
Merge pull request #14980 from mf-fx/search_no_results_no_notification
Search: Replace "No results" notification with translated text
2025-06-21 14:22:35 +02:00
Marcus Funch
f97abc11f0 Search: Replace "No results" notification with text, with translations 2025-06-21 13:25:31 +02:00
Philipp
14e4fb8334
Merge pull request #14937 from Art4/deprecate-addonloader
Deprecate AddonLoader
2025-06-18 06:54:34 +02:00
Philipp
ccd3ebbd11
Merge pull request #14966 from Art4/phpstan-strict-rules
Add PHPStan strict rules
2025-06-18 06:53:47 +02:00
Michael Vogel
e64bd7178c
Merge pull request #14975 from mf-fx/frio_fix_tooltips_positions_scroll_v2
Frio: Fix bug making navigation tooltips disappear after scroll v2
2025-06-17 21:14:50 +02:00
Marcus Funch
7fd6272a32 Frio: Fix bug making navigation tooltips disappear after scroll v2 2025-06-17 19:45:55 +02:00
Art4
a06a816c08 Fix code style 2025-06-13 12:35:48 +00:00
Art4
6633d1b491 Ignore type mismatches of BaseModule and BaseRepository properties in child classes 2025-06-13 12:29:36 +00:00
Art4
326f7d677b Merge branch 'develop' into deprecate-addonloader 2025-06-13 09:46:30 +00:00
Art4
696972629d Fix missing return statement 2025-06-06 14:31:05 +00:00
Art4
df7c3a6566 Simplify pagination generation 2025-06-06 10:59:45 +00:00
Art4
95b3322731 Fix many strict errors 2025-06-06 10:55:59 +00:00
Art4
07db85b99d Install phpstan-strict-rules 2025-06-06 08:15:00 +00:00
Art4
4bac217806 Merge branch 'rework-addon-class' into deprecate-addonloader 2025-06-04 10:55:51 +00:00
Art4
4f4f7f8a64 Merge branch 'rework-addon-class' into deprecate-addonloader 2025-05-16 08:46:44 +00:00
Art4
53191caeac fix code style 2025-05-16 08:33:01 +00:00
Art4
603f96b403 Refactor StrategiesFileManager 2025-05-16 08:28:24 +00:00
Art4
b8b7d3cd15 Hard deprecate AddonLoader 2025-05-16 08:23:31 +00:00
Art4
f8b1770efa Replace AddonLoader with AddonHelper in App 2025-05-16 08:20:58 +00:00
Art4
3f1082400a Fix tests for StrategiesFileManager 2025-05-16 07:12:08 +00:00
Art4
f9d695d80d Fix code style 2025-05-15 14:09:36 +00:00
Art4
d00fc89a59 Do not use AddonHelper in StrategiesFileManager
to avoid circular dependendy with strategies ans cache
2025-05-15 14:06:36 +00:00
Art4
59d22b87af Fix bath to static folder 2025-05-15 13:55:48 +00:00
Art4
9870fbf84f Fix unit tests 2025-05-15 13:26:34 +00:00
Art4
1428085c4b Soft deprecate AddonLoader and ICanLoadAddons 2025-05-15 13:15:52 +00:00
Art4
203e9642d5 Inline AddonLoader for strategies into StrategiesFileManager 2025-05-15 13:12:17 +00:00
Art4
694893e2bb Implement getAddonDependencyConfig 2025-05-15 12:37:57 +00:00
29 changed files with 442 additions and 156 deletions

View file

@ -2,6 +2,9 @@
# #
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
includes:
- vendor/phpstan/phpstan-strict-rules/rules.neon
parameters: parameters:
level: 3 level: 3
@ -23,6 +26,10 @@ parameters:
dynamicConstantNames: dynamicConstantNames:
- DB_UPDATE_VERSION - DB_UPDATE_VERSION
# See all rules at https://github.com/phpstan/phpstan-strict-rules/blob/2.0.x/rules.neon
strictRules:
allRules: false
ignoreErrors: ignoreErrors:
- -
# Ignore missing GdImage class in PHP <= 7.4 # Ignore missing GdImage class in PHP <= 7.4
@ -38,3 +45,17 @@ parameters:
# Ignore missing IMAP\Connection class in PHP <= 8.0 # Ignore missing IMAP\Connection class in PHP <= 8.0
message: '(^Parameter .+ has invalid type IMAP\\Connection\.$)' message: '(^Parameter .+ has invalid type IMAP\\Connection\.$)'
path: src 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

View file

@ -157,6 +157,7 @@
"php-mock/php-mock-phpunit": "^2.10", "php-mock/php-mock-phpunit": "^2.10",
"phpmd/phpmd": "^2.15", "phpmd/phpmd": "^2.15",
"phpstan/phpstan": "^2.0", "phpstan/phpstan": "^2.0",
"phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpunit": "^9" "phpunit/phpunit": "^9"
}, },
"scripts": { "scripts": {

50
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "897b878d6db24b9a6437bd9f971478be", "content-hash": "e93a8ac7e31cf3e5e0ca76134e5ffa0b",
"packages": [ "packages": [
{ {
"name": "asika/simple-console", "name": "asika/simple-console",
@ -5849,6 +5849,54 @@
], ],
"time": "2024-11-11T15:43:04+00:00" "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", "name": "phpunit/php-code-coverage",
"version": "9.2.31", "version": "9.2.31",

View file

@ -18,7 +18,6 @@ use Friendica\Capabilities\ICanCreateResponses;
use Friendica\Capabilities\ICanHandleRequests; use Friendica\Capabilities\ICanHandleRequests;
use Friendica\Content\Nav; use Friendica\Content\Nav;
use Friendica\Core\Addon\AddonHelper; use Friendica\Core\Addon\AddonHelper;
use Friendica\Core\Addon\Capability\ICanLoadAddons;
use Friendica\Core\Config\Factory\Config; use Friendica\Core\Config\Factory\Config;
use Friendica\Core\Container; use Friendica\Core\Container;
use Friendica\Core\Hooks\HookEventBridge; use Friendica\Core\Hooks\HookEventBridge;
@ -278,11 +277,15 @@ class App
private function setupContainerForAddons(): void private function setupContainerForAddons(): void
{ {
/** @var ICanLoadAddons $addonLoader */ /** @var AddonHelper $addonHelper */
$addonLoader = $this->container->create(ICanLoadAddons::class); $addonHelper = $this->container->create(AddonHelper::class);
foreach ($addonLoader->getActiveAddonConfig('dependencies') as $name => $rule) { $addonHelper->loadAddons();
$this->container->addRule($name, $rule);
foreach ($addonHelper->getEnabledAddons() as $addonId) {
foreach ($addonHelper->getAddonDependencyConfig($addonId) as $name => $rule) {
$this->container->addRule($name, $rule);
}
} }
} }

View file

@ -92,5 +92,7 @@ HELP;
CoreWorker::unclaimProcess($process); CoreWorker::unclaimProcess($process);
$this->processRepo->delete($process); $this->processRepo->delete($process);
return;
} }
} }

View file

@ -157,10 +157,10 @@ class Pager
'text' => $this->l10n->t('newer'), 'text' => $this->l10n->t('newer'),
'class' => 'previous' . ($this->getPage() == 1 ? ' disabled' : '') 'class' => 'previous' . ($this->getPage() == 1 ? ' disabled' : '')
], ],
'next' => [ 'next' => [
'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)), 'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)),
'text' => $this->l10n->t('older'), 'text' => $this->l10n->t('older'),
'class' => 'next' . ($displayedItemCount < $this->getItemsPerPage() ? ' disabled' : '') 'class' => 'next' . ($displayedItemCount < $this->getItemsPerPage() ? ' disabled' : '')
] ]
]; ];
@ -208,15 +208,15 @@ class Pager
'class' => $this->getPage() == 1 ? 'disabled' : '' 'class' => $this->getPage() == 1 ? 'disabled' : ''
]; ];
$numpages = $totalItemCount / $this->getItemsPerPage(); $numpages = (int) ceil($totalItemCount / $this->getItemsPerPage());
$numstart = 1; $numstart = 1;
$numstop = $numpages; $numstop = $numpages;
// Limit the number of displayed page number buttons. // Limit the number of displayed page number buttons.
if ($numpages > 8) { if ($numpages > 8) {
$numstart = (($this->getPage() > 4) ? ($this->getPage() - 4) : 1); $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 = []; $pages = [];
@ -237,25 +237,9 @@ 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; $data['pages'] = $pages;
$lastpage = (($numpages > intval($numpages)) ? intval($numpages)+1 : $numpages); $lastpage = (($numpages > intval($numpages)) ? intval($numpages) + 1 : $numpages);
$data['next'] = [ $data['next'] = [
'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)), 'url' => Strings::ensureQueryParameter($this->baseQueryString . '&page=' . ($this->getPage() + 1)),

View file

@ -55,7 +55,7 @@ class HTML
$xpath = new DOMXPath($doc); $xpath = new DOMXPath($doc);
/** @var \DOMNode[] $list */ /** @var \DOMNodeList<\DOMNode>|false $list */
$list = $xpath->query("//" . $tag); $list = $xpath->query("//" . $tag);
foreach ($list as $node) { foreach ($list as $node) {
$attr = []; $attr = [];
@ -1018,7 +1018,7 @@ class HTML
*/ */
public static function checkRelMeLink(DOMDocument $doc, UriInterface $meUrl): bool 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. // 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 // And that the href attribute contains exactly the provided URL

View file

@ -9,6 +9,8 @@ declare(strict_types=1);
namespace Friendica\Core\Addon; namespace Friendica\Core\Addon;
use Friendica\Core\Addon\Exception\AddonInvalidConfigFileException;
/** /**
* Some functions to handle addons * Some functions to handle addons
*/ */
@ -66,6 +68,17 @@ interface AddonHelper
*/ */
public function getAddonInfo(string $addonId): AddonInfo; 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 * Checks if the provided addon is enabled
*/ */

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace Friendica\Core\Addon; namespace Friendica\Core\Addon;
use Friendica\Core\Addon\Exception\AddonInvalidConfigFileException;
use Friendica\Core\Addon\Exception\InvalidAddonException; use Friendica\Core\Addon\Exception\InvalidAddonException;
use Friendica\Core\Cache\Capability\ICanCache; use Friendica\Core\Cache\Capability\ICanCache;
use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Config\Capability\IManageConfigValues;
@ -270,6 +271,34 @@ final class AddonManagerHelper implements AddonHelper
return AddonInfo::fromString($addonId, $matches[0]); return AddonInfo::fromString($addonId, $matches[0]);
} }
/**
* 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 * Checks if the provided addon is enabled
*/ */

View file

@ -9,12 +9,16 @@ namespace Friendica\Core\Addon\Capability;
/** /**
* Interface for loading Addons specific content * Interface for loading Addons specific content
*
* @deprecated 2025.02 Use implementation of `\Friendica\Core\Addon\AddonHelper` instead.
*/ */
interface ICanLoadAddons interface ICanLoadAddons
{ {
/** /**
* Returns a merged config array of all active addons for a given config-name * 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) * @param string $configName The config-name (config-file at the static directory, like 'hooks' => '{addon}/static/hooks.config.php)
* *
* @return array the merged array * @return array the merged array

View file

@ -14,6 +14,9 @@ use Friendica\Core\Logger\Factory\LoggerFactory;
use Friendica\Util\Strings; use Friendica\Util\Strings;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/**
* @deprecated 2025.02 Use implementation of `\Friendica\Core\Addon\AddonHelper` instead.
*/
class AddonLoader implements ICanLoadAddons class AddonLoader implements ICanLoadAddons
{ {
const STATIC_PATH = 'static'; const STATIC_PATH = 'static';
@ -24,13 +27,19 @@ class AddonLoader implements ICanLoadAddons
public function __construct(string $basePath, IManageConfigValues $config) 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->basePath = $basePath;
$this->config = $config; $this->config = $config;
} }
/** {@inheritDoc} */ /**
* @deprecated 2025.02 Use `\Friendica\Core\Addon\AddonHelper::getAddonDependencyConfig()` instead.
*/
public function getActiveAddonConfig(string $configName): array 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') ?? [])); $addons = array_keys(array_filter($this->config->get('addons') ?? []));
$returnConfig = []; $returnConfig = [];

View file

@ -7,9 +7,13 @@
namespace Friendica\Core\Hooks\Util; namespace Friendica\Core\Hooks\Util;
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\Capability\ICanRegisterStrategies;
use Friendica\Core\Hooks\Exceptions\HookConfigException; 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 * Manage all strategies.config.php files
@ -24,17 +28,15 @@ class StrategiesFileManager
const STATIC_DIR = 'static'; const STATIC_DIR = 'static';
const CONFIG_NAME = 'strategies'; const CONFIG_NAME = 'strategies';
/** @var ICanLoadAddons */ private IManageConfigValues $configuration;
protected $addonLoader; protected array $config = [];
/** @var array */
protected $config = [];
/** @var string */ /** @var string */
protected $basePath; protected $basePath;
public function __construct(string $basePath, ICanLoadAddons $addonLoader) public function __construct(string $basePath, IManageConfigValues $configuration)
{ {
$this->basePath = $basePath; $this->basePath = $basePath;
$this->addonLoader = $addonLoader; $this->configuration = $configuration;
} }
/** /**
@ -84,6 +86,50 @@ class StrategiesFileManager
/** /**
* @deprecated 2025.02 Providing strategies via addons is deprecated and will be removed in 5 months. * @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());
}
private function getActiveAddonConfig(): array
{
$addons = array_keys(array_filter($this->configuration->get('addons') ?? []));
$returnConfig = [];
foreach ($addons as $addon) {
$addonName = Strings::sanitizeFilePathItem(trim($addon));
$configFile = $this->basePath . '/addon/' . $addonName . '/' . static::STATIC_DIR . '/strategies.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);
}
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;
} }
} }

View file

@ -28,7 +28,7 @@ abstract class AbstractLock implements ICanLock
*/ */
protected function hasAcquiredLock(string $key): bool protected function hasAcquiredLock(string $key): bool
{ {
return isset($this->acquireLock[$key]) && $this->acquiredLocks[$key] === true; return isset($this->acquiredLocks[$key]) && $this->acquiredLocks[$key] === true;
} }
/** /**

View file

@ -99,8 +99,11 @@ class Cache extends AbstractSessionHandler
} }
#[\ReturnTypeWillChange] #[\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
} }
} }

View file

@ -124,13 +124,23 @@ class Database extends AbstractSessionHandler
} }
#[\ReturnTypeWillChange] #[\ReturnTypeWillChange]
public function gc($max_lifetime): bool /**
* @return int|false
*/
public function gc($max_lifetime)
{ {
try { try {
return $this->dba->delete('session', ["`expire` < ?", time()]); $result = $this->dba->delete('session', ["`expire` < ?", time()]);
} catch (\Exception $exception) { } catch (\Exception $exception) {
$this->logger->warning('Cannot use garbage collector.', ['exception' => $exception]); $this->logger->warning('Cannot use garbage collector.', ['exception' => $exception]);
return false; 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;
} }
} }

View file

@ -521,7 +521,7 @@ class Worker
} }
if ($sleeping) { 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)]);
} }
} }

View file

@ -193,7 +193,7 @@ class PostUpdate
Contact::removeDuplicates($contact['nurl'], $contact['uid']); Contact::removeDuplicates($contact['nurl'], $contact['uid']);
} }
DBA::close($contact); DBA::close($contacts);
DI::keyValue()->set('post_update_version', 1322); DI::keyValue()->set('post_update_version', 1322);
DI::logger()->info('Done'); DI::logger()->info('Done');

View file

@ -136,7 +136,9 @@ class Index extends BaseSearch
// Tags don't look like an URL and the fulltext search does only work with natural words // Tags don't look like an URL and the fulltext search does only work with natural words
if (parse_url($search, PHP_URL_SCHEME) && parse_url($search, PHP_URL_HOST)) { if (parse_url($search, PHP_URL_SCHEME) && parse_url($search, PHP_URL_HOST)) {
$this->logger->info('Skipping tag and fulltext search since the search looks like a URL.', ['q' => $search]); $this->logger->info('Skipping tag and fulltext search since the search looks like a URL.', ['q' => $search]);
DI::sysmsg()->addNotice(DI::l10n()->t('No results.')); $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), [
'$title' => DI::l10n()->t('No results.')
]);
return $o; return $o;
} }
@ -186,7 +188,9 @@ class Index extends BaseSearch
if (empty($items)) { if (empty($items)) {
if (empty($last_uriid)) { if (empty($last_uriid)) {
DI::sysmsg()->addNotice(DI::l10n()->t('No results.')); $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), [
'$title' => DI::l10n()->t('No results.')
]);
} }
return $o; return $o;
} }

View file

@ -234,11 +234,11 @@ class Display extends BaseSettings
$update_content = $this->pConfig->get($uid, 'system', 'update_content') ?? false; $update_content = $this->pConfig->get($uid, 'system', 'update_content') ?? false;
$enable_smile = !$this->pConfig->get($uid, 'system', 'no_smilies', false); $enable_smile = !$this->pConfig->get($uid, 'system', 'no_smilies', false);
$infinite_scroll = $this->pConfig->get($uid, 'system', 'infinite_scroll', false); $infinite_scroll = $this->pConfig->get($uid, 'system', 'infinite_scroll', true);
$enable_smart_threading = !$this->pConfig->get($uid, 'system', 'no_smart_threading', false); $enable_smart_threading = !$this->pConfig->get($uid, 'system', 'no_smart_threading', false);
$enable_dislike = !$this->pConfig->get($uid, 'system', 'hide_dislike', false); $enable_dislike = !$this->pConfig->get($uid, 'system', 'hide_dislike', false);
$display_resharer = $this->pConfig->get($uid, 'system', 'display_resharer', false); $display_resharer = $this->pConfig->get($uid, 'system', 'display_resharer', false);
$stay_local = $this->pConfig->get($uid, 'system', 'stay_local', false); $stay_local = $this->pConfig->get($uid, 'system', 'stay_local', true);
$show_page_drop = $this->pConfig->get($uid, 'system', 'show_page_drop', true); $show_page_drop = $this->pConfig->get($uid, 'system', 'show_page_drop', true);
$display_eventlist = $this->pConfig->get($uid, 'system', 'display_eventlist', true); $display_eventlist = $this->pConfig->get($uid, 'system', 'display_eventlist', true);

View file

@ -8,7 +8,7 @@
namespace Friendica\Network; namespace Friendica\Network;
use DOMDocument; use DOMDocument;
use DomXPath; use DOMXPath;
use Exception; use Exception;
use Friendica\Content\Text\HTML; use Friendica\Content\Text\HTML;
use Friendica\Core\Hook; use Friendica\Core\Hook;
@ -1273,7 +1273,7 @@ class Probe
return []; return [];
} }
$xpath = new DomXPath($doc); $xpath = new DOMXPath($doc);
$vcards = $xpath->query("//div[contains(concat(' ', @class, ' '), ' vcard ')]"); $vcards = $xpath->query("//div[contains(concat(' ', @class, ' '), ' vcard ')]");
if (!is_object($vcards)) { if (!is_object($vcards)) {

View file

@ -48,8 +48,8 @@ class Image
public function __construct(string $data, string $type = '', string $filename = '', bool $imagick = true) public function __construct(string $data, string $type = '', string $filename = '', bool $imagick = true)
{ {
$this->filename = $filename; $this->filename = $filename;
$type = Images::addMimeTypeByDataIfInvalid($type, $data); $type = Images::addMimeTypeByDataIfInvalid($type, $data);
$type = Images::addMimeTypeByExtensionIfInvalid($type, $filename); $type = Images::addMimeTypeByExtensionIfInvalid($type, $filename);
if (Images::isSupportedMimeType($type)) { if (Images::isSupportedMimeType($type)) {
$this->originType = $this->outputType = Images::getImageTypeByMimeType($type); $this->originType = $this->outputType = Images::getImageTypeByMimeType($type);
@ -108,7 +108,7 @@ class Image
private function isAnimatedWebP(string $data) private function isAnimatedWebP(string $data)
{ {
$header_format = 'A4Riff/I1Filesize/A4Webp/A4Vp/A74Chunk'; $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') { if (!isset($header['Riff']) || strtoupper($header['Riff']) !== 'RIFF') {
return false; return false;
@ -348,7 +348,7 @@ class Image
return false; return false;
} }
$width = $this->getWidth(); $width = $this->getWidth();
$height = $this->getHeight(); $height = $this->getHeight();
$scale = Images::getScalingDimensions($width, $height, $max); $scale = Images::getScalingDimensions($width, $height, $max);
@ -363,12 +363,11 @@ class Image
* Rotates image * Rotates image
* *
* @param integer $degrees degrees to rotate image * @param integer $degrees degrees to rotate image
* @return mixed
*/ */
public function rotate(int $degrees) public function rotate(int $degrees): void
{ {
if (!$this->isValid()) { if (!$this->isValid()) {
return false; return;
} }
if ($this->isImagick()) { if ($this->isImagick()) {
@ -393,12 +392,11 @@ class Image
* *
* @param boolean $horiz optional, default true * @param boolean $horiz optional, default true
* @param boolean $vert optional, default false * @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()) { if (!$this->isValid()) {
return false; return;
} }
if ($this->isImagick()) { if ($this->isImagick()) {
@ -414,8 +412,8 @@ class Image
return; return;
} }
$w = imagesx($this->image); $w = imagesx($this->image);
$h = imagesy($this->image); $h = imagesy($this->image);
$flipped = imagecreate($w, $h); $flipped = imagecreate($w, $h);
if ($horiz) { if ($horiz) {
for ($x = 0; $x < $w; $x++) { for ($x = 0; $x < $w; $x++) {
@ -523,7 +521,7 @@ class Image
return false; return false;
} }
$width = $this->getWidth(); $width = $this->getWidth();
$height = $this->getHeight(); $height = $this->getHeight();
if ((!$width) || (!$height)) { if ((!$width) || (!$height)) {
@ -532,22 +530,22 @@ class Image
if ($width < $min && $height < $min) { if ($width < $min && $height < $min) {
if ($width > $height) { if ($width > $height) {
$dest_width = $min; $dest_width = $min;
$dest_height = intval(($height * $min) / $width); $dest_height = intval(($height * $min) / $width);
} else { } else {
$dest_width = intval(($width * $min) / $height); $dest_width = intval(($width * $min) / $height);
$dest_height = $min; $dest_height = $min;
} }
} else { } else {
if ($width < $min) { if ($width < $min) {
$dest_width = $min; $dest_width = $min;
$dest_height = intval(($height * $min) / $width); $dest_height = intval(($height * $min) / $width);
} else { } else {
if ($height < $min) { if ($height < $min) {
$dest_width = intval(($width * $min) / $height); $dest_width = intval(($width * $min) / $height);
$dest_height = $min; $dest_height = $min;
} else { } else {
$dest_width = $width; $dest_width = $width;
$dest_height = $height; $dest_height = $height;
} }
} }
@ -622,7 +620,7 @@ class Image
imagedestroy($this->image); imagedestroy($this->image);
} }
$this->image = $dest; $this->image = $dest;
$this->width = imagesx($this->image); $this->width = imagesx($this->image);
$this->height = imagesy($this->image); $this->height = imagesy($this->image);
} }
@ -799,9 +797,9 @@ class Image
} }
$row[] = [$colors['r'], $colors['g'], $colors['b']]; $row[] = [$colors['r'], $colors['g'], $colors['b']];
} else { } else {
$index = imagecolorat($image->image, $x, $y); $index = imagecolorat($image->image, $x, $y);
$colors = @imagecolorsforindex($image->image, $index); $colors = @imagecolorsforindex($image->image, $index);
$row[] = [$colors['red'], $colors['green'], $colors['blue']]; $row[] = [$colors['red'], $colors['green'], $colors['blue']];
} }
} }
$pixels[] = $row; $pixels[] = $row;
@ -830,7 +828,7 @@ class Image
if ($this->isImagick()) { if ($this->isImagick()) {
$this->image = new Imagick(); $this->image = new Imagick();
$draw = new ImagickDraw(); $draw = new ImagickDraw();
$this->image->newImage($scaled['width'], $scaled['height'], '', 'png'); $this->image->newImage($scaled['width'], $scaled['height'], '', 'png');
} else { } else {
$this->image = imagecreatetruecolor($scaled['width'], $scaled['height']); $this->image = imagecreatetruecolor($scaled['width'], $scaled['height']);
@ -838,7 +836,7 @@ class Image
for ($y = 0; $y < $scaled['height']; ++$y) { for ($y = 0; $y < $scaled['height']; ++$y) {
for ($x = 0; $x < $scaled['width']; ++$x) { for ($x = 0; $x < $scaled['width']; ++$x) {
[$r, $g, $b] = $pixels[$y][$x]; list($r, $g, $b) = $pixels[$y][$x];
if ($draw !== null) { if ($draw !== null) {
$draw->setFillColor("rgb($r, $g, $b)"); $draw->setFillColor("rgb($r, $g, $b)");
$draw->point($x, $y); $draw->point($x, $y);

View file

@ -592,7 +592,7 @@ class HTTPSignature
return []; return [];
} }
$sig_block = self::parseSigHeader($http_headers['HTTP_SIGNATURE']); $sig_block = self::parseSigheader($http_headers['HTTP_SIGNATURE']);
if (empty($sig_block['keyId'])) { if (empty($sig_block['keyId'])) {
DI::logger()->debug('No keyId', ['sig_block' => $sig_block]); 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 // Add fields from the signature block to the header. See issue 8845
if (!empty($sig_block['created']) && empty($headers['(created)'])) { if (!empty($sig_block['created']) && empty($headers['(created)'])) {

View file

@ -39,7 +39,6 @@ return (function(string $basepath, array $getVars, array $serverVars, array $coo
'instanceOf' => \Friendica\Core\Addon\Model\AddonLoader::class, 'instanceOf' => \Friendica\Core\Addon\Model\AddonLoader::class,
'constructParams' => [ 'constructParams' => [
$basepath, $basepath,
[Dice::INSTANCE => Dice::SELF],
], ],
], ],
\Friendica\Core\Addon\AddonHelper::class => [ \Friendica\Core\Addon\AddonHelper::class => [

View file

@ -12,6 +12,7 @@ namespace Friendica\Test\Unit\Core\Addon;
use Exception; use Exception;
use Friendica\Core\Addon\AddonInfo; use Friendica\Core\Addon\AddonInfo;
use Friendica\Core\Addon\AddonManagerHelper; use Friendica\Core\Addon\AddonManagerHelper;
use Friendica\Core\Addon\Exception\AddonInvalidConfigFileException;
use Friendica\Core\Addon\Exception\InvalidAddonException; use Friendica\Core\Addon\Exception\InvalidAddonException;
use Friendica\Core\Cache\Capability\ICanCache; use Friendica\Core\Cache\Capability\ICanCache;
use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Config\Capability\IManageConfigValues;
@ -81,6 +82,76 @@ class AddonManagerHelperTest extends TestCase
$addonManagerHelper->getAddonInfo('helloaddon'); $addonManagerHelper->getAddonInfo('helloaddon');
} }
public function testGetAddonDependencyConfigReturnsArray(): void
{
$root = vfsStream::setup(__FUNCTION__ . '_addons', 0777, [
'helloaddon' => [
'static' => [
'dependencies.config.php' => <<<PHP
<?php
return [
'foo' => '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' => '<?php return null;',
],
]
]);
$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->expectException(AddonInvalidConfigFileException::class);
$this->expectExceptionMessageMatches('#Error loading config file .+/helloaddon/static/dependencies\.config\.php#');
$addonManagerHelper->getAddonDependencyConfig('helloaddon');
}
public function testEnabledAddons(): void public function testEnabledAddons(): void
{ {
$config = $this->createStub(IManageConfigValues::class); $config = $this->createStub(IManageConfigValues::class);

View file

@ -7,7 +7,7 @@
namespace Friendica\Test\src\Core\Hooks\Util; 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\Capability\ICanRegisterStrategies;
use Friendica\Core\Hooks\Exceptions\HookConfigException; use Friendica\Core\Hooks\Exceptions\HookConfigException;
use Friendica\Core\Hooks\Util\StrategiesFileManager; use Friendica\Core\Hooks\Util\StrategiesFileManager;
@ -33,49 +33,61 @@ class StrategiesFileManagerTest extends MockedTestCase
return [ return [
'normal' => [ 'normal' => [
'content' => <<<EOF 'content' => <<<EOF
<?php <?php
return [ return [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''], \Psr\Log\NullLogger::class => [''],
], ],
]; ];
EOF, EOF,
'addonsArray' => [], 'addonContent' => <<<EOF
<?php
return [];
EOF,
'assertStrategies' => [ 'assertStrategies' => [
[LoggerInterface::class, NullLogger::class, ''], [LoggerInterface::class, NullLogger::class, ''],
], ],
], ],
'normalWithString' => [ 'normalWithString' => [
'content' => <<<EOF 'content' => <<<EOF
<?php <?php
return [ return [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => '', \Psr\Log\NullLogger::class => '',
], ],
]; ];
EOF, EOF,
'addonsArray' => [], 'addonContent' => <<<EOF
<?php
return [];
EOF,
'assertStrategies' => [ 'assertStrategies' => [
[LoggerInterface::class, NullLogger::class, ''], [LoggerInterface::class, NullLogger::class, ''],
], ],
], ],
'withAddons' => [ 'withAddons' => [
'content' => <<<EOF 'content' => <<<EOF
<?php <?php
return [ return [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''], \Psr\Log\NullLogger::class => [''],
], ],
]; ];
EOF, EOF,
'addonsArray' => [ 'addonContent' => <<<EOF
\Psr\Log\LoggerInterface::class => [ <?php
\Psr\Log\NullLogger::class => ['null'],
], return [
], \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => ['null'],
],
];
EOF,
'assertStrategies' => [ 'assertStrategies' => [
[LoggerInterface::class, NullLogger::class, ''], [LoggerInterface::class, NullLogger::class, ''],
[LoggerInterface::class, NullLogger::class, 'null'], [LoggerInterface::class, NullLogger::class, 'null'],
@ -83,19 +95,23 @@ EOF,
], ],
'withAddonsWithString' => [ 'withAddonsWithString' => [
'content' => <<<EOF 'content' => <<<EOF
<?php <?php
return [ return [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''], \Psr\Log\NullLogger::class => [''],
], ],
]; ];
EOF, EOF,
'addonsArray' => [ 'addonContent' => <<<EOF
\Psr\Log\LoggerInterface::class => [ <?php
\Psr\Log\NullLogger::class => 'null',
], return [
], \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => ['null'],
],
];
EOF,
'assertStrategies' => [ 'assertStrategies' => [
[LoggerInterface::class, NullLogger::class, ''], [LoggerInterface::class, NullLogger::class, ''],
[LoggerInterface::class, NullLogger::class, 'null'], [LoggerInterface::class, NullLogger::class, 'null'],
@ -104,19 +120,23 @@ EOF,
// This should work because unique name convention is part of the instance manager logic, not of the file-infrastructure layer // This should work because unique name convention is part of the instance manager logic, not of the file-infrastructure layer
'withAddonsDoubleNamed' => [ 'withAddonsDoubleNamed' => [
'content' => <<<EOF 'content' => <<<EOF
<?php <?php
return [ return [
\Psr\Log\LoggerInterface::class => [ \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''], \Psr\Log\NullLogger::class => [''],
], ],
]; ];
EOF, EOF,
'addonsArray' => [ 'addonContent' => <<<EOF
\Psr\Log\LoggerInterface::class => [ <?php
\Psr\Log\NullLogger::class => [''],
], return [
], \Psr\Log\LoggerInterface::class => [
\Psr\Log\NullLogger::class => [''],
],
];
EOF,
'assertStrategies' => [ 'assertStrategies' => [
[LoggerInterface::class, NullLogger::class, ''], [LoggerInterface::class, NullLogger::class, ''],
[LoggerInterface::class, NullLogger::class, ''], [LoggerInterface::class, NullLogger::class, ''],
@ -128,16 +148,20 @@ EOF,
/** /**
* @dataProvider dataHooks * @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') vfsStream::newFile(StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php')
->withContent($content) ->withContent($content)
->at($this->root); ->at($this->root);
$addonLoader = \Mockery::mock(ICanLoadAddons::class); vfsStream::newFile('addon/testaddon/' . StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php')
$addonLoader->shouldReceive('getActiveAddonConfig')->andReturn($addonsArray)->once(); ->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); $instanceManager = \Mockery::mock(ICanRegisterStrategies::class);
foreach ($assertStrategies as $assertStrategy) { foreach ($assertStrategies as $assertStrategy) {
@ -155,13 +179,15 @@ EOF,
*/ */
public function testMissingStrategiesFile() public function testMissingStrategiesFile()
{ {
$addonLoader = \Mockery::mock(ICanLoadAddons::class); $config = \Mockery::mock(IManageConfigValues::class);
$instanceManager = \Mockery::mock(ICanRegisterStrategies::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::expectException(HookConfigException::class);
self::expectExceptionMessage(sprintf('config file %s does not exist.', self::expectExceptionMessage(sprintf(
$this->root->url() . '/' . StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php')); 'config file %s does not exist.',
$this->root->url() . '/' . StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php'
));
$hookFileManager->loadConfig(); $hookFileManager->loadConfig();
} }
@ -171,17 +197,19 @@ EOF,
*/ */
public function testWrongStrategiesFile() public function testWrongStrategiesFile()
{ {
$addonLoader = \Mockery::mock(ICanLoadAddons::class); $config = \Mockery::mock(IManageConfigValues::class);
$instanceManager = \Mockery::mock(ICanRegisterStrategies::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') vfsStream::newFile(StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php')
->withContent("<?php return 'WRONG_CONTENT';") ->withContent("<?php return 'WRONG_CONTENT';")
->at($this->root); ->at($this->root);
self::expectException(HookConfigException::class); self::expectException(HookConfigException::class);
self::expectExceptionMessage(sprintf('Error loading config file %s.', self::expectExceptionMessage(sprintf(
$this->root->url() . '/' . StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php')); 'Error loading config file %s.',
$this->root->url() . '/' . StrategiesFileManager::STATIC_DIR . '/' . StrategiesFileManager::CONFIG_NAME . '.config.php'
));
$hookFileManager->loadConfig(); $hookFileManager->loadConfig();
} }

View file

@ -806,6 +806,7 @@ summary.wall-item-summary {
/* Margin to create space between the icon and the text */ /* Margin to create space between the icon and the text */
.notif-image { .notif-image {
margin-right: 10px; margin-right: 10px;
}
/* Styles to ensure the text wraps properly after 70 characters */ /* Styles to ensure the text wraps properly after 70 characters */
.notif-text { .notif-text {
@ -813,3 +814,16 @@ summary.wall-item-summary {
max-width: 70ch; max-width: 70ch;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
/* Add alt tag indicators to the relevant images */
a:has(img.has-alt-description)::after {
background: lightgray;
border-radius: 4px;
color: black;
content: "ALT";
font-weight: bold;
padding: 3px 8px;
position: absolute;
bottom: 10px;
right: 10px;
}

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2025.02-dev\n" "Project-Id-Version: 2025.02-dev\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-06-14 15:06+0200\n" "POT-Creation-Date: 2025-06-21 13:03+0200\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"
@ -646,7 +646,7 @@ msgstr ""
msgid "Map" msgid "Map"
msgstr "" msgstr ""
#: src/App.php:451 #: src/App.php:454
msgid "Apologies but the website is unavailable at the moment." msgid "Apologies but the website is unavailable at the moment."
msgstr "" msgstr ""
@ -2227,11 +2227,11 @@ msgstr ""
msgid "prev" msgid "prev"
msgstr "" msgstr ""
#: src/Content/Pager.php:262 src/Module/Calendar/Show.php:122 #: src/Content/Pager.php:246 src/Module/Calendar/Show.php:122
msgid "next" msgid "next"
msgstr "" msgstr ""
#: src/Content/Pager.php:267 #: src/Content/Pager.php:251
msgid "last" msgid "last"
msgstr "" msgstr ""
@ -6108,7 +6108,7 @@ msgstr ""
msgid "Search your contacts" msgid "Search your contacts"
msgstr "" msgstr ""
#: src/Module/Contact.php:441 src/Module/Search/Index.php:202 #: src/Module/Contact.php:441 src/Module/Search/Index.php:206
#, php-format #, php-format
msgid "Results for: %s" msgid "Results for: %s"
msgstr "" msgstr ""
@ -6698,8 +6698,8 @@ msgid "Unable to unfollow this contact, please contact your administrator"
msgstr "" msgstr ""
#: src/Module/Conversation/Channel.php:125 #: src/Module/Conversation/Channel.php:125
#: src/Module/Conversation/Community.php:114 src/Module/Search/Index.php:139 #: src/Module/Conversation/Community.php:114 src/Module/Search/Index.php:140
#: src/Module/Search/Index.php:189 #: src/Module/Search/Index.php:192
msgid "No results." msgid "No results."
msgstr "" msgstr ""
@ -8901,7 +8901,7 @@ msgstr ""
msgid "Only one search per minute is permitted for not logged in users." msgid "Only one search per minute is permitted for not logged in users."
msgstr "" msgstr ""
#: src/Module/Search/Index.php:200 #: src/Module/Search/Index.php:204
#, php-format #, php-format
msgid "Items tagged with: %s" msgid "Items tagged with: %s"
msgstr "" msgstr ""

View file

@ -174,9 +174,8 @@ $(document).ready(function () {
// temporary workaround to avoid 'undefined' being displayed (issue #9789) // temporary workaround to avoid 'undefined' being displayed (issue #9789)
// https://github.com/friendica/friendica/issues/9789 // https://github.com/friendica/friendica/issues/9789
// TODO: find a way to localize this string
if (typeof searchText === "undefined") { if (typeof searchText === "undefined") {
searchText = "No results"; searchText = "";
} }
// insert the plain text in a <h4> heading and give it a class // insert the plain text in a <h4> heading and give it a class
var newText = '<h4 class="search-heading">' + searchText + "</h4>"; var newText = '<h4 class="search-heading">' + searchText + "</h4>";

View file

@ -164,7 +164,7 @@
<form class="navbar-form" role="search" method="get" action="{{$nav.search.0}}"> <form class="navbar-form" role="search" method="get" action="{{$nav.search.0}}">
<div class="form-group form-group-search"> <div class="form-group form-group-search">
<input accesskey="s" id="nav-search-input-field" class="form-control form-search" <input accesskey="s" id="nav-search-input-field" class="form-control form-search"
type="text" name="q" data-toggle="tooltip" title="{{$search_hint}}" type="text" name="q" data-toggle="tooltip" data-viewport="#topbar-first" title="{{$search_hint}}"
placeholder="{{$nav.search.1}}"> placeholder="{{$nav.search.1}}">
<button class="btn btn-default btn-sm form-button-search" <button class="btn btn-default btn-sm form-button-search"
type="submit">{{$nav.search.1}}</button> type="submit">{{$nav.search.1}}</button>