Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
58bc96a
fix: Remove static var in Memcache/Redis
come-nc Mar 13, 2026
e4e1c46
fix: Remove static vars in TaskProcessing, TextProcessing, TextToImage
come-nc Mar 13, 2026
7f3d2f8
chore: Remove types from const properties
come-nc Mar 17, 2026
9cd72b7
feat: Add a psalm plugin to forbid static properties and variables
come-nc Mar 13, 2026
8248c73
fix: Remove static vars in trashbin, versions and storages
come-nc Mar 17, 2026
db9128f
chore: Move Util static property to top of the file
come-nc Mar 26, 2026
82b06c7
chore: Remove static vars in TemplateLayout
come-nc Mar 26, 2026
84c33b2
fix: Add a factory for Memcached object instead of a static var
come-nc Mar 26, 2026
ed35b1a
fix: Fix TemplateLayout tests
come-nc Apr 7, 2026
3c99734
fix: Remove static vars in preview Generator
come-nc Apr 7, 2026
69a670d
fix: Remove static var is Access class
come-nc Apr 7, 2026
361e925
fix: Use a CappedMemoryCache instead of an array to cache stuff in us…
come-nc Apr 7, 2026
b95495f
fix: Turn static var into standard one in Tags class
come-nc Apr 28, 2026
cca08ec
fix: Remove static property use in SeekableHttpStream
come-nc Apr 28, 2026
ce50b1b
fix: Fix suppressing ImpureStaticProperty and suppress it in a few pl…
come-nc Apr 28, 2026
6827da0
chore: Remove dead code detected by psalm
come-nc May 11, 2026
27c8d42
chore(user_ldap): Move static var to static property and silence warning
come-nc May 11, 2026
7391c49
chore: Silence psalm false-positives
come-nc May 11, 2026
702f9ea
fix(user_ldap): Move accesses to AccessFactory instead of static var
come-nc May 12, 2026
dd80bc2
chore(files_versions): Be extra-cautious to please psalm
come-nc May 12, 2026
19a85a9
chore: Suppress last known impure static properties
come-nc May 19, 2026
ab6109b
chore: Get cache lazily in OC\Memcache\Redis
come-nc Jun 1, 2026
a1036fe
fix: Make sure getNumConcurrentPreviews never returns 0
come-nc Jun 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/encryption/lib/Crypto/Encryption.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ class Encryption implements IEncryptionModule {
/** @var int Current version of the file */
private int $version = 0;

/** @var array remember encryption signature version */
/**
* @var array remember encryption signature version
* @psalm-suppress ImpureStaticProperty
*/
private static $rememberVersion = [];

public function __construct(
Expand Down
9 changes: 5 additions & 4 deletions apps/files_external/lib/Service/BackendService.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class BackendService {

/** @var IConfigHandler[] */
private array $configHandlers = [];
private bool $eventSent = false;

public function __construct(
protected readonly IAppConfig $appConfig,
Expand All @@ -79,14 +80,14 @@ public function registerBackendProvider(IBackendProvider $provider) {
$this->backendProviders[] = $provider;
}

private function callForRegistrations() {
static $eventSent = false;
if (!$eventSent) {
private function callForRegistrations(): void {
$instance = Server::get(self::class);
if (!$instance->eventSent) {
Server::get(IEventDispatcher::class)->dispatch(
'OCA\\Files_External::loadAdditionalBackends',
new GenericEvent()
);
$eventSent = true;
$instance->eventSent = true;
}
}

Expand Down
3 changes: 3 additions & 0 deletions apps/files_sharing/lib/SharedStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ class SharedStorage extends Jail implements LegacyISharedStorage, ISharedStorage
private IAppConfig $appConfig;
private IShareManager $shareManager;

/**
* @psalm-suppress ImpureStaticProperty
*/
private static int $initDepth = 0;

public function __construct(array $parameters) {
Expand Down
6 changes: 3 additions & 3 deletions apps/files_trashbin/lib/Command/RestoreAllFiles.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class RestoreAllFiles extends Base {
private const SCOPE_USER = 1;
private const SCOPE_GROUPFOLDERS = 2;

private static array $SCOPE_MAP = [
private const SCOPE_MAP = [
'user' => self::SCOPE_USER,
'groupfolders' => self::SCOPE_GROUPFOLDERS,
'all' => self::SCOPE_ALL
Expand Down Expand Up @@ -221,8 +221,8 @@ protected function parseArgs(InputInterface $input): array {
}

protected function parseScope(string $scope): int {
if (isset(self::$SCOPE_MAP[$scope])) {
return self::$SCOPE_MAP[$scope];
if (isset(self::SCOPE_MAP[$scope])) {
return self::SCOPE_MAP[$scope];
}

throw new InvalidOptionException("Invalid scope '$scope'");
Expand Down
30 changes: 18 additions & 12 deletions apps/files_versions/lib/Storage.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,17 @@ class Storage {
public const DELETE_TRIGGER_QUOTA_EXCEEDED = 2;

// files for which we can remove the versions after the delete operation was successful
private static $deletedFiles = [];
/**
* @psalm-suppress ImpureStaticProperty
*/
private static array $deletedFiles = [];

private static $sourcePathAndUser = [];
/**
* @psalm-suppress ImpureStaticProperty
*/
private static array $sourcePathAndUser = [];

private static $max_versions_per_interval = [
private const MAX_VERSIONS_PER_INTERVAL = [
//first 10sec, one version every 2sec
1 => ['intervalEndsAfter' => 10, 'step' => 2],
//next minute, one version every 10sec
Expand Down Expand Up @@ -762,12 +768,8 @@ protected static function getAutoExpireList($time, $versions) {
$toDelete = []; // versions we want to delete

$interval = 1;
$step = Storage::$max_versions_per_interval[$interval]['step'];
if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
$nextInterval = -1;
} else {
$nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
}
$step = Storage::MAX_VERSIONS_PER_INTERVAL[$interval]['step'];
$nextInterval = $time - Storage::MAX_VERSIONS_PER_INTERVAL[$interval]['intervalEndsAfter'];

$firstVersion = reset($versions);

Expand Down Expand Up @@ -796,12 +798,16 @@ protected static function getAutoExpireList($time, $versions) {
$newInterval = false; // version checked so we can move to the next one
} else { // time to move on to the next interval
$interval++;
$step = Storage::$max_versions_per_interval[$interval]['step'];
if ($interval > count(Storage::MAX_VERSIONS_PER_INTERVAL)) {
/* Should never happen, as last interval has -1 as nextInterval */
throw new \Exception('MAX_VERSIONS_PER_INTERVAL is malformed or there is a logic issue');
}
$step = Storage::MAX_VERSIONS_PER_INTERVAL[$interval]['step'];
$nextVersion = $prevTimestamp - $step;
if (Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'] === -1) {
if (Storage::MAX_VERSIONS_PER_INTERVAL[$interval]['intervalEndsAfter'] === -1) {
$nextInterval = -1;
} else {
$nextInterval = $time - Storage::$max_versions_per_interval[$interval]['intervalEndsAfter'];
$nextInterval = $time - Storage::MAX_VERSIONS_PER_INTERVAL[$interval]['intervalEndsAfter'];
}
$newInterval = true; // we changed the interval -> check same version with new interval
}
Expand Down
10 changes: 6 additions & 4 deletions apps/user_ldap/lib/Access.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use OCA\User_LDAP\Mapping\AbstractMapping;
use OCA\User_LDAP\User\Manager;
use OCA\User_LDAP\User\OfflineUser;
use OCP\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\HintException;
use OCP\IAppConfig;
Expand Down Expand Up @@ -49,6 +50,7 @@ class Access extends LDAPUtility {
protected $groupMapper;

private string $lastCookie = '';
private CappedMemoryCache $intermediates;

public function __construct(
ILDAPWrapper $ldap,
Expand All @@ -62,6 +64,7 @@ public function __construct(
) {
parent::__construct($ldap);
$this->userManager->setLdapAccess($this);
$this->intermediates = new CappedMemoryCache();
}

/**
Expand Down Expand Up @@ -484,8 +487,7 @@ public function dn2username($fdn) {
* @throws \Exception
*/
public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped = null, ?array $record = null, bool $autoMapping = true) {
static $intermediates = [];
if (isset($intermediates[($isUser ? 'user-' : 'group-') . $fdn])) {
if (isset($this->intermediates[($isUser ? 'user-' : 'group-') . $fdn])) {
return false; // is a known intermediate
}

Expand Down Expand Up @@ -532,7 +534,7 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped
$record = $this->readAttributes($fdn, $attributesToRead, $filter);
if ($record === false) {
$this->logger->debug('Cannot read attributes for ' . $fdn . '. Skipping.', ['filter' => $filter]);
$intermediates[($isUser ? 'user-' : 'group-') . $fdn] = true;
$this->intermediates[($isUser ? 'user-' : 'group-') . $fdn] = true;
return false;
}
}
Expand Down Expand Up @@ -579,7 +581,7 @@ public function dn2ocname($fdn, $ldapName = null, $isUser = true, &$newlyMapped
$ldapName = $record[$nameAttribute];
if (!isset($ldapName[0]) || empty($ldapName[0])) {
$this->logger->debug('No or empty name for ' . $fdn . ' with filter ' . $filter . '.', ['app' => 'user_ldap']);
$intermediates['group-' . $fdn] = true;
$this->intermediates['group-' . $fdn] = true;
return false;
}
$ldapName = $ldapName[0];
Expand Down
23 changes: 23 additions & 0 deletions apps/user_ldap/lib/AccessFactory.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\User_LDAP;

use OCA\User_LDAP\Mapping\GroupMapping;
use OCA\User_LDAP\Mapping\UserMapping;
use OCA\User_LDAP\User\Manager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IAppConfig;
Expand All @@ -15,6 +19,8 @@
use Psr\Log\LoggerInterface;

class AccessFactory {
/** @var array<string,Access> */
private array $accesses = [];

public function __construct(
private ILDAPWrapper $ldap,
Expand All @@ -23,6 +29,8 @@ public function __construct(
private IUserManager $ncUserManager,
private LoggerInterface $logger,
private IEventDispatcher $dispatcher,
private UserMapping $userMapping,
private GroupMapping $groupMapping,
) {
}

Expand All @@ -39,4 +47,19 @@ public function get(Connection $connection): Access {
$this->dispatcher,
);
}

public function getAccessForPrefix(string $configPrefix): Access {
if (!isset(self::$accesses[$configPrefix])) {
$this->addAccess($configPrefix);
}
return $this->accesses[$configPrefix];
}

private function addAccess(string $configPrefix): void {
$connector = new Connection($this->ldap, $configPrefix);
$access = $this->get($connector);
$access->setUserMapper($this->userMapping);
$access->setGroupMapper($this->groupMapping);
$this->accesses[$configPrefix] = $access;
}
}
8 changes: 5 additions & 3 deletions apps/user_ldap/lib/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ class Connection extends LDAPUtility {
protected LoggerInterface $logger;
private IL10N $l10n;

/** @psalm-suppress ImpureStaticProperty This is a cache for whether php-ldap is installed, which cannot change mid-process */
private static bool $phpLDAPinstalled = true;

/**
* Constructor
* @param string $configPrefix a string with the prefix for the configkey column (appconfig table)
Expand Down Expand Up @@ -589,8 +592,7 @@ private function establishConnection(): ?bool {
if (!$this->configuration->ldapConfigurationActive) {
return null;
}
static $phpLDAPinstalled = true;
if (!$phpLDAPinstalled) {
if (!static::$phpLDAPinstalled) {
return false;
}
if (!$this->ignoreValidation && !$this->configured) {
Expand All @@ -602,7 +604,7 @@ private function establishConnection(): ?bool {
}
if (!$this->ldapConnectionRes) {
if (!$this->ldap->areLDAPFunctionsAvailable()) {
$phpLDAPinstalled = false;
static::$phpLDAPinstalled = false;
$this->logger->error(
'function ldap_connect is not available. Make sure that the PHP ldap module is installed.',
['app' => 'user_ldap']
Expand Down
20 changes: 1 addition & 19 deletions apps/user_ldap/lib/Proxy.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@

namespace OCA\User_LDAP;

use OCA\User_LDAP\Mapping\GroupMapping;
use OCA\User_LDAP\Mapping\UserMapping;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\Server;
Expand All @@ -18,8 +16,6 @@
* @template T
*/
abstract class Proxy {
/** @var array<string,Access> */
private static array $accesses = [];
private ?bool $isSingleBackend = null;
private ?ICache $cache = null;

Expand Down Expand Up @@ -71,22 +67,8 @@ public function getBackend(string $configPrefix): object {
return $this->backends[$configPrefix];
}

private function addAccess(string $configPrefix): void {
$userMap = Server::get(UserMapping::class);
$groupMap = Server::get(GroupMapping::class);

$connector = new Connection($this->ldap, $configPrefix);
$access = $this->accessFactory->get($connector);
$access->setUserMapper($userMap);
$access->setGroupMapper($groupMap);
self::$accesses[$configPrefix] = $access;
}

protected function getAccess(string $configPrefix): Access {
if (!isset(self::$accesses[$configPrefix])) {
$this->addAccess($configPrefix);
}
return self::$accesses[$configPrefix];
return $this->accessFactory->getAccessForPrefix($configPrefix);
}

/**
Expand Down
46 changes: 46 additions & 0 deletions build/psalm/StaticVarsChecker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\Static_;
use Psalm\CodeLocation;
use Psalm\IssueBuffer;
use Psalm\Plugin\EventHandler\AfterStatementAnalysisInterface;
use Psalm\Plugin\EventHandler\Event\AfterStatementAnalysisEvent;

/**
* Complains about static property in classes and static vars in methods
*/
class StaticVarsChecker implements AfterStatementAnalysisInterface {
public static function afterStatementAnalysis(AfterStatementAnalysisEvent $event): ?bool {
$stmt = $event->getStmt();
if ($stmt instanceof Property) {
if ($stmt->isStatic()) {
IssueBuffer::maybeAdd(
// ImpureStaticProperty is close enough, all static properties are impure to my eyes
Comment thread
CarlSchwan marked this conversation as resolved.
new \Psalm\Issue\ImpureStaticProperty(
'Static property should not be used as they do not follow requests lifecycle',
new CodeLocation($event->getStatementsSource(), $stmt),
),
$event->getStatementsSource()->getSuppressedIssues(),
);
}
} elseif ($stmt instanceof Static_) {
IssueBuffer::maybeAdd(
// Same logic
new \Psalm\Issue\ImpureStaticVariable(
'Static var should not be used as they do not follow requests lifecycle and are hard to reset',
new CodeLocation($event->getStatementsSource(), $stmt),
),
$event->getStatementsSource()->getSuppressedIssues(),
);
}
return null;
}
}
23 changes: 23 additions & 0 deletions build/psalm/StaticVarsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

class StaticVarsTest {
public static $forbiddenStaticProperty;
protected static $forbiddenProtectedStaticProperty;
private static $forbiddenPrivateStaticProperty;
private static $forbiddenPrivateStaticPropertyWithValue = [];

public function normalFunction(): void {
static $forbiddenStaticVar = false;
}

public static function staticFunction(): void {
static $forbiddenStaticVar = false;
}
}
Loading
Loading