diff --git a/.github/workflows/phpunit-32bits.yml b/.github/workflows/phpunit-32bits.yml index 0bcbb786e25a4..f1d8da06fd0e2 100644 --- a/.github/workflows/phpunit-32bits.yml +++ b/.github/workflows/phpunit-32bits.yml @@ -29,8 +29,6 @@ jobs: strategy: fail-fast: false - matrix: - php-versions: ['8.3', '8,5'] steps: - name: Checkout server @@ -43,7 +41,7 @@ jobs: uses: docker://ghcr.io/nextcloud/continuous-integration-php8.4-32bit:latest with: args: /bin/sh -c " - git config --global --add safe.directory /github/workspace && + git config --global --add safe.directory ${GITHUB_WORKSPACE} && composer install --no-interaction" - name: Set up Nextcloud diff --git a/apps/sharing/lib/ResponseDefinitions.php b/apps/sharing/lib/ResponseDefinitions.php index 34bd1e4ab44b9..6a0b7b48d7a0c 100644 --- a/apps/sharing/lib/ResponseDefinitions.php +++ b/apps/sharing/lib/ResponseDefinitions.php @@ -123,7 +123,7 @@ * id: non-empty-string, * owner: SharingUser, * // Unix time in milliseconds - * last_updated: non-negative-int, + * last_updated: numeric-string, * state: SharingState, * sources: list, * recipients: list, diff --git a/apps/sharing/openapi.json b/apps/sharing/openapi.json index 191435bf86c60..98beb6864c1d6 100644 --- a/apps/sharing/openapi.json +++ b/apps/sharing/openapi.json @@ -465,10 +465,8 @@ "$ref": "#/components/schemas/User" }, "last_updated": { - "type": "integer", - "format": "int64", - "description": "Unix time in milliseconds", - "minimum": 0 + "type": "string", + "description": "Unix time in milliseconds" }, "state": { "$ref": "#/components/schemas/State" diff --git a/apps/sharing/tests/Command/CommandTest.php b/apps/sharing/tests/Command/CommandTest.php index fae0f6717c181..19b1ca1b27139 100644 --- a/apps/sharing/tests/Command/CommandTest.php +++ b/apps/sharing/tests/Command/CommandTest.php @@ -164,7 +164,7 @@ protected function searchRecipients(ShareAccessContext $accessContext, ?array $f } /** - * @return array + * @return SharingShare */ #[Override] protected function createShare(ShareAccessContext $accessContext): array { @@ -177,12 +177,12 @@ protected function createShare(ShareAccessContext $accessContext): array { ], [], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } /** - * @return array + * @return SharingShare */ #[Override] protected function updateShareState(ShareAccessContext $accessContext, string $id, ShareState $state): array { @@ -195,12 +195,12 @@ protected function updateShareState(ShareAccessContext $accessContext, string $i ], [], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } /** - * @return array + * @return SharingShare */ #[Override] protected function addShareSource(ShareAccessContext $accessContext, string $id, ShareSource $source): array { @@ -214,12 +214,12 @@ protected function addShareSource(ShareAccessContext $accessContext, string $id, ], [], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } /** - * @return array + * @return SharingShare */ #[Override] protected function removeShareSource(ShareAccessContext $accessContext, string $id, ShareSource $source): array { @@ -233,12 +233,12 @@ protected function removeShareSource(ShareAccessContext $accessContext, string $ ], [], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } /** - * @return array + * @return SharingShare */ #[Override] protected function addShareRecipient(ShareAccessContext $accessContext, string $id, ShareRecipient $recipient): array { @@ -253,12 +253,12 @@ protected function addShareRecipient(ShareAccessContext $accessContext, string $ ], [], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } /** - * @return array + * @return SharingShare */ #[Override] protected function removeShareRecipient(ShareAccessContext $accessContext, string $id, ShareRecipient $recipient): array { @@ -273,12 +273,12 @@ protected function removeShareRecipient(ShareAccessContext $accessContext, strin ], [], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } /** - * @return array + * @return SharingShare */ #[Override] protected function updateShareRecipientSecret(ShareAccessContext $accessContext, string $id, ShareRecipient $recipient, string $secret): array { @@ -294,12 +294,12 @@ protected function updateShareRecipientSecret(ShareAccessContext $accessContext, ], [], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } /** - * @return array + * @return SharingShare */ #[Override] protected function updateShareProperty(ShareAccessContext $accessContext, string $id, ShareProperty $property): array { @@ -313,12 +313,12 @@ protected function updateShareProperty(ShareAccessContext $accessContext, string ], [], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } /** - * @return array + * @return SharingShare */ #[Override] protected function updateSharePermission(ShareAccessContext $accessContext, string $id, SharePermission $permission): array { @@ -332,12 +332,12 @@ protected function updateSharePermission(ShareAccessContext $accessContext, stri ], [], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } /** - * @return array + * @return SharingShare */ #[Override] protected function selectSharePermissionPreset(ShareAccessContext $accessContext, string $id, string $permissionPresetClass): array { @@ -350,7 +350,7 @@ protected function selectSharePermissionPreset(ShareAccessContext $accessContext ], [], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } @@ -379,7 +379,7 @@ protected function getShare(ShareAccessContext $accessContext, string $id): arra ], [], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } @@ -399,7 +399,7 @@ protected function getShares(ShareAccessContext $accessContext, ?string $filterS ['limit', $limit], ], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare[] */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } } diff --git a/apps/sharing/tests/Controller/ApiV1ControllerTest.php b/apps/sharing/tests/Controller/ApiV1ControllerTest.php index eaf057a8f4aa6..05395007891c8 100644 --- a/apps/sharing/tests/Controller/ApiV1ControllerTest.php +++ b/apps/sharing/tests/Controller/ApiV1ControllerTest.php @@ -95,53 +95,63 @@ protected function searchRecipients(ShareAccessContext $accessContext, ?array $f #[Override] protected function createShare(ShareAccessContext $accessContext): array { + /** @var SharingShare */ return $this->executeRequest($accessContext, fn (ApiV1Controller $controller): DataResponse => $controller->createShare()); } #[Override] protected function updateShareState(ShareAccessContext $accessContext, string $id, ShareState $state): array { + /** @var SharingShare */ return $this->executeRequest($accessContext, fn (ApiV1Controller $controller): DataResponse => $controller->updateShareState($id, $state->value)); } #[Override] protected function addShareSource(ShareAccessContext $accessContext, string $id, ShareSource $source): array { + /** @var SharingShare */ return $this->executeRequest($accessContext, fn (ApiV1Controller $controller): DataResponse => $controller->addShareSource($id, $source->class, $source->value)); } #[Override] protected function removeShareSource(ShareAccessContext $accessContext, string $id, ShareSource $source): array { + /** @var SharingShare */ return $this->executeRequest($accessContext, fn (ApiV1Controller $controller): DataResponse => $controller->removeShareSource($id, $source->class, $source->value)); } #[Override] protected function addShareRecipient(ShareAccessContext $accessContext, string $id, ShareRecipient $recipient): array { + /** @var SharingShare */ return $this->executeRequest($accessContext, fn (ApiV1Controller $controller): DataResponse => $controller->addShareRecipient($id, $recipient->class, $recipient->value, $recipient->instance)); } #[Override] protected function removeShareRecipient(ShareAccessContext $accessContext, string $id, ShareRecipient $recipient): array { + /** @var SharingShare */ return $this->executeRequest($accessContext, fn (ApiV1Controller $controller): DataResponse => $controller->removeShareRecipient($id, $recipient->class, $recipient->value, $recipient->instance)); } #[Override] protected function updateShareRecipientSecret(ShareAccessContext $accessContext, string $id, ShareRecipient $recipient, string $secret): array { /** @psalm-suppress ArgumentTypeCoercion */ + /** @var SharingShare */ return $this->executeRequest($accessContext, fn (ApiV1Controller $controller): DataResponse => $controller->updateShareRecipientSecret($id, $recipient->class, $recipient->value, $recipient->instance, $secret)); } #[Override] protected function updateShareProperty(ShareAccessContext $accessContext, string $id, ShareProperty $property): array { + /** @var SharingShare */ return $this->executeRequest($accessContext, fn (ApiV1Controller $controller): DataResponse => $controller->updateShareProperty($id, $property->class, $property->value)); } #[Override] protected function updateSharePermission(ShareAccessContext $accessContext, string $id, SharePermission $permission): array { + /** @var SharingShare */ return $this->executeRequest($accessContext, fn (ApiV1Controller $controller): DataResponse => $controller->updateSharePermission($id, $permission->class, $permission->enabled)); } #[Override] protected function selectSharePermissionPreset(ShareAccessContext $accessContext, string $id, string $permissionPresetClass): array { /** @psalm-suppress ArgumentTypeCoercion */ + /** @var SharingShare */ return $this->executeRequest($accessContext, fn (ApiV1Controller $controller): DataResponse => $controller->selectSharePermissionPreset($id, $permissionPresetClass)); } @@ -150,11 +160,9 @@ protected function deleteShare(ShareAccessContext $accessContext, string $id): v $this->executeRequest($accessContext, fn (ApiV1Controller $controller): DataResponse => $controller->deleteShare($id)); } - /** - * @psalm-suppress MixedReturnTypeCoercion - */ #[Override] protected function getShare(ShareAccessContext $accessContext, string $id): array { + /** @var SharingShare */ return $this->executeRequest(new ShareAccessContext($accessContext->currentUser, null, [], $accessContext->overrideChecks), fn (ApiV1Controller $controller): DataResponse => $controller->getShare($id, $accessContext->secret, $accessContext->arguments)); } diff --git a/lib/private/ServerInfo.php b/lib/private/ServerInfo.php index bf5dec468f270..05da80bae046f 100644 --- a/lib/private/ServerInfo.php +++ b/lib/private/ServerInfo.php @@ -25,7 +25,7 @@ public function getServerId(): int { if ($serverid < 1) { // Fallback: generates a server ID based on hostname /** @var int<0,max> */ - $serverid = PHP_INT_SIZE === 4 + $serverid = PHP_INT_SIZE === 8 ? hexdec(hash('xxh32', $this->getHostname())) // Makes sure it doesn't overflow 32 bits int : hexdec(substr(hash('xxh32', $this->getHostname()), -3)); diff --git a/lib/private/Sharing/SharingBackend.php b/lib/private/Sharing/SharingBackend.php index 35abf38315568..c57ffbdd6bcff 100644 --- a/lib/private/Sharing/SharingBackend.php +++ b/lib/private/Sharing/SharingBackend.php @@ -543,8 +543,8 @@ public function setLastUpdated(array $ids, \DateTimeImmutable $lastUpdated): voi $rowCount = $qb ->update('sharing_share') - ->set('last_updated', $qb->createNamedParameter(SharingManager::timeToMs($lastUpdated), IQueryBuilder::PARAM_INT)) - ->where($qb->expr()->in('id', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY))) + ->set('last_updated', $qb->createNamedParameter(SharingManager::timeToMs($lastUpdated))) + ->where($qb->expr()->in('id', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_STR_ARRAY))) ->executeStatement(); if ($rowCount !== count($chunk)) { throw new ShareNotFoundException(); @@ -618,7 +618,7 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID, } // The key type is array-key, because PHP will automatically cast the value. We can't type it as integer though, because we need to also support 32 bit systems and there the autocasting doesn't happen, if the value is too large. - /** @var array, recipients: list, properties: array, ShareProperty>, permissions: array, SharePermission>}> $shares */ + /** @var array, recipients: list, properties: array, ShareProperty>, permissions: array, SharePermission>}> $shares */ $shares = []; foreach ($queries as $qb) { $qb @@ -672,8 +672,8 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID, /** @var non-empty-string $id */ $id = (string)$row['id']; - /** @var non-negative-int $lastUpdated */ - $lastUpdated = (int)$row['last_updated']; + /** @var numeric-string $lastUpdated */ + $lastUpdated = (string)$row['last_updated']; /** @var string $state */ $state = $row['state']; $shares[$id] ??= [ @@ -718,10 +718,10 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID, 'ss.source_value', ) ->from('sharing_share_sources', 'ss') - ->where($qb->expr()->in('ss.share_id', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY))); + ->where($qb->expr()->in('ss.share_id', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_STR_ARRAY))); $result = $qb->executeQuery(); - /** @var array{source_class_id: mixed, source_value: non-empty-string, share_id: int}[] $rows */ + /** @var array{source_class_id: mixed, source_value: non-empty-string, share_id: string}[] $rows */ $rows = $result->fetchAll(); foreach ($rows as $row) { @@ -749,7 +749,7 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID, } $value = $row['source_value']; - $id = (string)$row['share_id']; + $id = $row['share_id']; $shares[$id]['sources'][] = new ShareSource( $typeClass, $value, @@ -777,7 +777,7 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID, 'sr.initiator_instance', ) ->from('sharing_share_recipients', 'sr') - ->where($qb->expr()->in('sr.share_id', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY))); + ->where($qb->expr()->in('sr.share_id', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_STR_ARRAY))); foreach ($qb->executeQuery()->fetchAll() as $row) { /** @var class-string $typeClass */ @@ -890,7 +890,7 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID, 'sp.property_value', ) ->from('sharing_share_properties', 'sp') - ->where($qb->expr()->in('sp.share_id', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY))); + ->where($qb->expr()->in('sp.share_id', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_STR_ARRAY))); $result = $qb->executeQuery(); foreach ($result->fetchAll() as $row) { @@ -961,7 +961,7 @@ private function list(ShareAccessContext $accessContext, ?string $filterShareID, 'sp.permission_enabled', ) ->from('sharing_share_permissions', 'sp') - ->where($qb->expr()->in('sp.share_id', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_INT_ARRAY))); + ->where($qb->expr()->in('sp.share_id', $qb->createNamedParameter($chunk, IQueryBuilder::PARAM_STR_ARRAY))); $result = $qb->executeQuery(); foreach ($result->fetchAll() as $row) { @@ -1110,7 +1110,7 @@ public function createSharePermissionDefaultValue(Share $share, string $permissi return $share; } - private static function parseTimestamp(int $timestampMs): \DateTimeImmutable { + private static function parseTimestamp(string $timestampMs): \DateTimeImmutable { if (method_exists(\DateTimeImmutable::class, 'createFromTimestamp')) { // with php 8.3 the method doesn't exist and psalm doesn't know the return type /** @psalm-suppress MixedReturnStatement */ diff --git a/lib/private/Sharing/SharingManager.php b/lib/private/Sharing/SharingManager.php index 51b8dc18b0ec5..4c2b0f2ee11ca 100644 --- a/lib/private/Sharing/SharingManager.php +++ b/lib/private/Sharing/SharingManager.php @@ -753,18 +753,17 @@ private function processShareUpdates(array $sharesOrIds): array { } /** - * @return non-negative-int + * @return numeric-string */ - public static function timeToMs(\DateTimeImmutable $time): int { + public static function timeToMs(\DateTimeImmutable $time): string { if (method_exists($time, 'getMicrosecond')) { - /** @var int $micros */ - $micros = $time->getMicrosecond(); + $micros = (float)$time->getMicrosecond(); } else { - $micros = (int)$time->format('u'); + $micros = (float)$time->format('u'); } - $time = $time->getTimestamp() * 1000 + (int)floor($micros / 1000); - if ($time > 0) { + $time = (string)floor((float)$time->getTimestamp() * 1000.0 + $micros / 1000.0); + if ((float)$time > 0) { return $time; } diff --git a/lib/unstable/Sharing/Share.php b/lib/unstable/Sharing/Share.php index feed3031aa370..004313e724d19 100644 --- a/lib/unstable/Sharing/Share.php +++ b/lib/unstable/Sharing/Share.php @@ -132,7 +132,7 @@ * id: non-empty-string, * owner: SharingUser, * // Unix time in milliseconds - * last_updated: non-negative-int, + * last_updated: numeric-string, * state: SharingState, * sources: list, * recipients: list, diff --git a/openapi.json b/openapi.json index 3aaf8b5a32b70..a0d248d466ca2 100644 --- a/openapi.json +++ b/openapi.json @@ -4653,10 +4653,8 @@ "$ref": "#/components/schemas/SharingUser" }, "last_updated": { - "type": "integer", - "format": "int64", - "description": "Unix time in milliseconds", - "minimum": 0 + "type": "string", + "description": "Unix time in milliseconds" }, "state": { "$ref": "#/components/schemas/SharingState" diff --git a/tests/lib/Sharing/AbstractSharingManagerTests.php b/tests/lib/Sharing/AbstractSharingManagerTests.php index 3ab9d5e15c337..ee248397d8cf9 100644 --- a/tests/lib/Sharing/AbstractSharingManagerTests.php +++ b/tests/lib/Sharing/AbstractSharingManagerTests.php @@ -42,24 +42,54 @@ abstract class AbstractSharingManagerTests extends TestCase { abstract protected function searchRecipients(ShareAccessContext $accessContext, ?array $filterRecipientTypeClasses, string $query, int $limit, int $offset, ?string $id = null): array; + /** + * @return SharingShare + */ abstract protected function createShare(ShareAccessContext $accessContext): array; + /** + * @return SharingShare + */ abstract protected function updateShareState(ShareAccessContext $accessContext, string $id, ShareState $state): array; + /** + * @return SharingShare + */ abstract protected function addShareSource(ShareAccessContext $accessContext, string $id, ShareSource $source): array; + /** + * @return SharingShare + */ abstract protected function removeShareSource(ShareAccessContext $accessContext, string $id, ShareSource $source): array; + /** + * @return SharingShare + */ abstract protected function addShareRecipient(ShareAccessContext $accessContext, string $id, ShareRecipient $recipient): array; + /** + * @return SharingShare + */ abstract protected function removeShareRecipient(ShareAccessContext $accessContext, string $id, ShareRecipient $recipient): array; + /** + * @return SharingShare + */ abstract protected function updateShareRecipientSecret(ShareAccessContext $accessContext, string $id, ShareRecipient $recipient, string $secret): array; + /** + * @return SharingShare + */ abstract protected function updateShareProperty(ShareAccessContext $accessContext, string $id, ShareProperty $property): array; + /** + * @return SharingShare + */ abstract protected function updateSharePermission(ShareAccessContext $accessContext, string $id, SharePermission $permission): array; + /** + * @return SharingShare + */ abstract protected function selectSharePermissionPreset(ShareAccessContext $accessContext, string $id, string $permissionPresetClass): array; abstract protected function deleteShare(ShareAccessContext $accessContext, string $id): void; @@ -86,8 +116,7 @@ abstract protected function getShares(ShareAccessContext $accessContext, ?string protected IUser $user2; - private function parseTime(mixed $timestampMs): \DateTimeImmutable { - $timestampMs = (int)$timestampMs; + private function parseTime(string $timestampMs): \DateTimeImmutable { $time = \DateTimeImmutable::createFromFormat('U.u', number_format((float)$timestampMs / 1000.0, 3, '.', '')); if ($time === false) { throw new \RuntimeException('invalid timestamp: ' . $timestampMs);