Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ precedence = "aggregate"
SPDX-FileCopyrightText = "2025 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "AGPL-3.0-or-later"

[[annotations]]
path = ["vendor-bin/rector/composer.json", "vendor-bin/rector/composer.lock", "tests/psalm-baseline.xml"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2026 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "AGPL-3.0-or-later"

[[annotations]]
path = ["img/app-dark.svg", "img/app.svg", "img/view.svg", "img/view-dark.svg", "img/material/*.svg"]
precedence = "aggregate"
Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"nextcloud/ocp": "dev-stable33",
"staabm/annotate-pull-request-from-checkstyle": "^1.8.7",
"phpunit/phpunit": "9.6.36",
"psalm/phar": "^5.26.1"
"psalm/phar": "^6.16"
},
"config": {
"optimize-autoloader": true,
Expand All @@ -44,6 +44,8 @@
"psalm:fix": "./vendor/bin/psalm.phar --no-cache --alter --issues=InvalidReturnType,InvalidNullableReturnType,MismatchingDocblockParamType,MismatchingDocblockReturnType,MissingParamType,InvalidFalsableReturnType",
"psalm:fix:dry": "./vendor/bin/psalm.phar --no-cache --alter --issues=InvalidReturnType,InvalidNullableReturnType,MismatchingDocblockParamType,MismatchingDocblockReturnType,MissingParamType,InvalidFalsableReturnType --dry-run",
"openapi": "generate-spec --verbose && (npm run typescript:generate || echo 'Please manually regenerate the typescript OpenAPI models')",
"rector:check": "rector --dry-run",
"rector:fix": "rector",
"scoper:update-deps": "./update-scoper-dependencies.sh",
"post-install-cmd": [
"composer bin all install --ansi",
Expand Down
16 changes: 8 additions & 8 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 40 additions & 18 deletions lib/Activity/ActivityManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,30 +81,43 @@
) {
}

public function triggerEvent($objectType, $object, $subject, $additionalParams = [], $author = null) {
/**
* @param Row2|Table $object
* @param \OCA\Tables\Model\ImportStats[]|null|string $additionalParams
* @param (array|null)[]|null|string $author
*
* @psalm-param 'tables_row'|'tables_table' $objectType
* @psalm-param array{importStats?: \OCA\Tables\Model\ImportStats}|null|string $additionalParams
* @psalm-param array{before: array|null, after: array|null}|null|string $author
*/
public function triggerEvent(string $objectType, Table|Row2 $object, string $subject, array|string|null $additionalParams = [], array|string|null $author = null) {
if ($author === null) {
$author = $this->userId;
}

try {
$event = $this->createEvent($objectType, $object, $subject, $additionalParams, $author);

Check failure on line 99 in lib/Activity/ActivityManager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable34

InvalidArgument

lib/Activity/ActivityManager.php:99:64: InvalidArgument: Argument 4 of OCA\Tables\Activity\ActivityManager::createEvent expects array{after?: mixed, before?: mixed}, but array{importStats?: OCA\Tables\Model\ImportStats}|null|string with additional array shape fields (importStats) was provided (see https://psalm.dev/004)

Check failure on line 99 in lib/Activity/ActivityManager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

InvalidArgument

lib/Activity/ActivityManager.php:99:64: InvalidArgument: Argument 4 of OCA\Tables\Activity\ActivityManager::createEvent expects array{after?: mixed, before?: mixed}, but array{importStats?: OCA\Tables\Model\ImportStats}|null|string with additional array shape fields (importStats) was provided (see https://psalm.dev/004)

Check failure on line 99 in lib/Activity/ActivityManager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable33

InvalidArgument

lib/Activity/ActivityManager.php:99:64: InvalidArgument: Argument 4 of OCA\Tables\Activity\ActivityManager::createEvent expects array{after?: mixed, before?: mixed}, but array{importStats?: OCA\Tables\Model\ImportStats}|null|string with additional array shape fields (importStats) was provided (see https://psalm.dev/004)

if ($event !== null) {
$this->sendToUsers($event, $object);
}
} catch (\Exception $e) {
} catch (\Exception) {
// Ignore exception for undefined activities on update events
}
}

public function triggerUpdateEvents($objectType, ChangeSet $changeSet, $subject) {
/**
* @psalm-param 'tables_table' $objectType
* @psalm-param 'table_update' $subject
*/
public function triggerUpdateEvents(string $objectType, ChangeSet $changeSet, string $subject) {
$previousEntity = $changeSet->getBefore();
$entity = $changeSet->getAfter();
$events = [];

if ($previousEntity !== null) {
foreach ($entity->getUpdatedFields() as $field => $value) {
$getter = 'get' . ucfirst($field);
$getter = 'get' . ucfirst((string)$field);
$subjectComplete = $subject . '_' . $field;
$changes = [
'before' => $previousEntity->$getter(),
Expand All @@ -116,15 +129,15 @@
if ($event !== null) {
$events[] = $event;
}
} catch (\Exception $e) {
} catch (\Exception) {
// Ignore exception for undefined activities on update events
}
}
}
} else {
try {
$events = [$this->createEvent($objectType, $entity, $subject)];
} catch (\Exception $e) {
} catch (\Exception) {
// Ignore exception for undefined activities on update events
}
}
Expand All @@ -134,7 +147,14 @@
}
}

private function createEvent($objectType, $object, $subject, $additionalParams = [], $author = null) {
/**
* @psalm-param array{before?: mixed, after?: mixed} $additionalParams
* @psalm-param 'tables_row'|'tables_table' $objectType
* @psalm-param array{before: array|null, after: array|null}|null|string $author
*
* @param (array|null)[]|null|string $author
*/
private function createEvent(string $objectType, Row2|Table $object, string $subject, array $additionalParams = [], array|string|null $author = null) {
if ($object instanceof Table) {
$objectTitle = $object->getTitle();
$table = $object;
Expand All @@ -158,7 +178,7 @@
*/
$eventType = 'tables';
$subjectParams = [
'author' => $author === null ? $this->userId : $author,
'author' => $author ?? $this->userId,
'table' => $table,
'objectType' => $objectType,
];
Expand Down Expand Up @@ -198,10 +218,10 @@
case self::SUBJECT_SHARE_UPDATE:
case self::SUBJECT_SHARE_DELETE:
$eventType = self::EVENT_TYPE_SHARING;
$subjectParams['sharedWith'] = $this->buildSharedWithParam($additionalParams['share'] ?? []);

Check failure on line 221 in lib/Activity/ActivityManager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable34

InvalidArrayOffset

lib/Activity/ActivityManager.php:221:64: InvalidArrayOffset: Cannot access value on variable $additionalParams using offset value of 'share', expecting 'before' or 'after' (see https://psalm.dev/115)

Check failure on line 221 in lib/Activity/ActivityManager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

InvalidArrayOffset

lib/Activity/ActivityManager.php:221:64: InvalidArrayOffset: Cannot access value on variable $additionalParams using offset value of 'share', expecting 'before' or 'after' (see https://psalm.dev/115)

Check failure on line 221 in lib/Activity/ActivityManager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable33

InvalidArrayOffset

lib/Activity/ActivityManager.php:221:64: InvalidArrayOffset: Cannot access value on variable $additionalParams using offset value of 'share', expecting 'before' or 'after' (see https://psalm.dev/115)
break;
case self::SUBJECT_IMPORT_FINISHED:
$subjectParams['importStats'] = $additionalParams['importStats'] ?? null;

Check failure on line 224 in lib/Activity/ActivityManager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable34

InvalidArrayOffset

lib/Activity/ActivityManager.php:224:37: InvalidArrayOffset: Cannot access value on variable $additionalParams using offset value of 'importStats', expecting 'before' or 'after' (see https://psalm.dev/115)

Check failure on line 224 in lib/Activity/ActivityManager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

InvalidArrayOffset

lib/Activity/ActivityManager.php:224:37: InvalidArrayOffset: Cannot access value on variable $additionalParams using offset value of 'importStats', expecting 'before' or 'after' (see https://psalm.dev/115)

Check failure on line 224 in lib/Activity/ActivityManager.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable33

InvalidArrayOffset

lib/Activity/ActivityManager.php:224:37: InvalidArrayOffset: Cannot access value on variable $additionalParams using offset value of 'importStats', expecting 'before' or 'after' (see https://psalm.dev/115)
break;
default:
throw new \Exception(sprintf('Unknown subject "%s" for activity.', $subject));
Expand Down Expand Up @@ -295,7 +315,7 @@
];
}

private function sendToUsers(IEvent $event, $object) {
private function sendToUsers(IEvent $event, Row2|Table $object) {
// Handle share events with restricted recipients
$subject = $event->getSubject();
if (in_array($subject, [self::SUBJECT_SHARE_CREATE, self::SUBJECT_SHARE_UPDATE, self::SUBJECT_SHARE_DELETE], true)) {
Expand Down Expand Up @@ -405,8 +425,10 @@

/**
* Send share events only to: event author, table owner, and shared-with receiver users.
*
* @param Row2|Table $object
*/
private function sendShareEventToUsers(IEvent $event, $object): void {
private function sendShareEventToUsers(IEvent $event, Table|Row2 $object): void {
if (!$object instanceof Table && !$object instanceof View) {
Server::get(LoggerInterface::class)->error('Could not send share activity. Invalid object type.', (array)$object);
return;
Expand Down Expand Up @@ -539,7 +561,7 @@
return $columnIds;
}

public function getActivitySubject($language, $subjectIdentifier, $subjectParams = [], $ownActivity = false) {
public function getActivitySubject(string $language, $subjectIdentifier, array $subjectParams = [], bool $ownActivity = false) {
$l = $this->l10nFactory->get(Application::APP_ID, $language);
$isViewContext = $subjectParams['isViewContext'] ?? false;
$isViewObject = ($subjectParams['objectType'] ?? null) === self::TABLES_OBJECT_VIEW;
Expand Down Expand Up @@ -577,7 +599,7 @@
return '';
}

private function formatTableActivity($l, string $subjectIdentifier, bool $ownActivity): ?string {
private function formatTableActivity(\OCP\IL10N $l, string $subjectIdentifier, bool $ownActivity): ?string {
return match ($subjectIdentifier) {
self::SUBJECT_TABLE_CREATE => $ownActivity ? $l->t('You have created a new table {table}') : $l->t('{user} has created a new table {table}'),
self::SUBJECT_TABLE_UPDATE => $ownActivity ? $l->t('You have updated the table {table}') : $l->t('{user} has updated the table {table}'),
Expand All @@ -588,7 +610,7 @@
};
}

private function formatViewActivity($l, string $subjectIdentifier, bool $ownActivity): ?string {
private function formatViewActivity(\OCP\IL10N $l, string $subjectIdentifier, bool $ownActivity): ?string {
return match ($subjectIdentifier) {
self::SUBJECT_VIEW_CREATE => $ownActivity ? $l->t('You have created a new view {view} in table {table}') : $l->t('{user} has created a new view {view} in table {table}'),
self::SUBJECT_VIEW_UPDATE => $ownActivity ? $l->t('You have updated the view {view} in table {table}') : $l->t('{user} has updated the view {view} in table {table}'),
Expand All @@ -599,7 +621,7 @@
};
}

private function formatRowActivity($l, string $subjectIdentifier, array $subjectParams, bool $ownActivity, bool $isViewContext): ?string {
private function formatRowActivity(\OCP\IL10N $l, string $subjectIdentifier, array $subjectParams, bool $ownActivity, bool $isViewContext): ?string {
switch ($subjectIdentifier) {
case self::SUBJECT_ROW_CREATE:
if ($isViewContext) {
Expand All @@ -620,7 +642,7 @@
}
}

private function formatRowUpdateActivity($l, array $subjectParams, bool $ownActivity, bool $isViewContext): string {
private function formatRowUpdateActivity(\OCP\IL10N $l, array $subjectParams, bool $ownActivity, bool $isViewContext): string {
$visibleChangeCols = $this->getVisibleChangeCols($subjectParams);
$columns = '';
$count = 1;
Expand Down Expand Up @@ -664,7 +686,7 @@
);
}

private function formatColumnActivity($l, string $subjectIdentifier, bool $ownActivity, bool $isViewContext): ?string {
private function formatColumnActivity(\OCP\IL10N $l, string $subjectIdentifier, bool $ownActivity, bool $isViewContext): ?string {
if ($isViewContext) {
return match ($subjectIdentifier) {
self::SUBJECT_COLUMN_CREATE => $ownActivity ? $l->t('You have created a new column {column} in view {view}') : $l->t('{user} has created a new column {column} in view {view}'),
Expand All @@ -682,7 +704,7 @@
};
}

private function formatShareActivity($l, string $subjectIdentifier, array $subjectParams, bool $ownActivity, bool $isViewObject, mixed $sharedWith): ?string {
private function formatShareActivity(\OCP\IL10N $l, string $subjectIdentifier, array $subjectParams, bool $ownActivity, bool $isViewObject, mixed $sharedWith): ?string {
$sharedWithYou = $this->isSharedWithYou($subjectParams, $ownActivity);
$isLinkShare = $this->isLinkShare($sharedWith);

Expand Down Expand Up @@ -766,7 +788,7 @@
return is_array($sharedWith) && ($sharedWith['type'] ?? null) === ShareReceiverType::LINK;
}

public function getActivityMessage($language, $subjectIdentifier) {
public function getActivityMessage(string $language, $subjectIdentifier) {
$l = $this->l10nFactory->get(Application::APP_ID, $language);

switch ($subjectIdentifier) {
Expand Down
4 changes: 2 additions & 2 deletions lib/Activity/ChangeSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
}
}

public function setBefore($before) {
public function setBefore(Entity $before) {
$this->before = clone $before;
}

public function setAfter($after) {
public function setAfter(Entity $after) {
$this->after = clone $after;
}

Expand All @@ -40,7 +40,7 @@
return $this->after;
}

public function jsonSerialize(): array {

Check failure on line 43 in lib/Activity/ChangeSet.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable34

MissingOverrideAttribute

lib/Activity/ChangeSet.php:43:2: MissingOverrideAttribute: Method OCA\Tables\Activity\ChangeSet::jsonserialize should have the "Override" attribute (see https://psalm.dev/358)

Check failure on line 43 in lib/Activity/ChangeSet.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-master

MissingOverrideAttribute

lib/Activity/ChangeSet.php:43:2: MissingOverrideAttribute: Method OCA\Tables\Activity\ChangeSet::jsonserialize should have the "Override" attribute (see https://psalm.dev/358)

Check failure on line 43 in lib/Activity/ChangeSet.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis dev-stable33

MissingOverrideAttribute

lib/Activity/ChangeSet.php:43:2: MissingOverrideAttribute: Method OCA\Tables\Activity\ChangeSet::jsonserialize should have the "Override" attribute (see https://psalm.dev/358)
return [
'before' => $this->getBefore(),
'after' => $this->getAfter()
Expand Down
10 changes: 5 additions & 5 deletions lib/Activity/TablesProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ class TablesProvider implements IProvider {

public function __construct(
private $userId,
private IURLGenerator $urlGenerator,
private ActivityManager $activityManager,
private IUserManager $userManager,
private LoggerInterface $logger,
private PermissionsService $permissionsService,
private readonly IURLGenerator $urlGenerator,
private readonly ActivityManager $activityManager,
private readonly IUserManager $userManager,
private readonly LoggerInterface $logger,
private readonly PermissionsService $permissionsService,
) {
}

Expand Down
36 changes: 10 additions & 26 deletions lib/Analytics/AnalyticsDatasource.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,15 @@

class AnalyticsDatasource implements IDatasource {

private LoggerInterface $logger;
private IL10N $l10n;
private TableService $tableService;
private ViewService $viewService;
private RowService $rowService;
private ColumnService $columnService;

protected ?string $userId;

public function __construct(
IL10N $l10n,
LoggerInterface $logger,
TableService $tableService,
ViewService $viewService,
ColumnService $columnService,
RowService $rowService,
?string $userId,
private readonly IL10N $l10n,
private readonly LoggerInterface $logger,
private readonly TableService $tableService,
private readonly ViewService $viewService,
private readonly ColumnService $columnService,
private readonly RowService $rowService,
protected ?string $userId,
) {
$this->l10n = $l10n;
$this->logger = $logger;
$this->tableService = $tableService;
$this->viewService = $viewService;
$this->columnService = $columnService;
$this->rowService = $rowService;
$this->userId = $userId;
}

/**
Expand Down Expand Up @@ -110,7 +94,7 @@ public function getTemplate(): array {
// concatenate the option-string. The format is tableId:viewId-title
$tableString = $tableString . $table->getId() . ':' . $view->getId() . '-' . $view->getTitle() . '/';
}
} catch (PermissionError $e) {
} catch (PermissionError) {
// this is a shared table without shared views;
continue;
}
Expand Down Expand Up @@ -164,7 +148,7 @@ public function getTemplate(): array {
*/
public function readData($option): array {
// get the ids which come in the format tableId:viewId
$ids = explode(':', $option['tableId']);
$ids = explode(':', (string)$option['tableId']);
$this->userId = $option['user_id'];

if (count($ids) === 1) {
Expand Down Expand Up @@ -338,7 +322,7 @@ private function formatBooleanValue(mixed $value): string {
return '';
}

private function formatTextValue(Column $column, mixed $value): string {
private function formatTextValue(Column $column, string $value): string {
if ($value === null || $value === '') {
return '';
}
Expand Down
13 changes: 5 additions & 8 deletions lib/Api/V1Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,11 @@
use OCP\AppFramework\Db\MultipleObjectsReturnedException;

class V1Api {
private RowService $rowService;
private ColumnService $columnService;
private ?string $userId;

public function __construct(ColumnService $columnService, RowService $rowService, ?string $userId) {
$this->columnService = $columnService;
$this->rowService = $rowService;
$this->userId = $userId;
public function __construct(
private readonly ColumnService $columnService,
private readonly RowService $rowService,
private ?string $userId,
) {
}

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public function register(IRegistrationContext $context): void {
}

public function boot(IBootContext $context): void {
$context->injectFn([$this, 'registerCloudFederationProviderManager']);
$context->injectFn($this->registerCloudFederationProviderManager(...));
}

public function registerCloudFederationProviderManager(ICloudFederationProviderManager $manager): void {
Expand Down
Loading
Loading