diff --git a/.github/workflows/phpunit-32bits.yml b/.github/workflows/phpunit-32bits.yml index 0bcbb786e25..f1d8da06fd0 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 34bd1e4ab44..6a0b7b48d7a 100644 --- a/apps/sharing/lib/ResponseDefinitions.php +++ b/apps/sharing/lib/ResponseDefinitions.php @@ -123,7 +123,7 @@ use NCU\Sharing\Source\IShareSourceType; * 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 191435bf86c..98beb6864c1 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 fae0f6717c1..19b1ca1b271 100644 --- a/apps/sharing/tests/Command/CommandTest.php +++ b/apps/sharing/tests/Command/CommandTest.php @@ -164,7 +164,7 @@ final class CommandTest extends AbstractSharingManagerTests { } /** - * @return array + * @return SharingShare */ #[Override] protected function createShare(ShareAccessContext $accessContext): array { @@ -177,12 +177,12 @@ final class CommandTest extends AbstractSharingManagerTests { ], [], ); - /** @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 @@ final class CommandTest extends AbstractSharingManagerTests { ], [], ); - /** @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 @@ final class CommandTest extends AbstractSharingManagerTests { ], [], ); - /** @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 @@ final class CommandTest extends AbstractSharingManagerTests { ], [], ); - /** @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 @@ final class CommandTest extends AbstractSharingManagerTests { ], [], ); - /** @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 @@ final class CommandTest extends AbstractSharingManagerTests { ], [], ); - /** @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 @@ final class CommandTest extends AbstractSharingManagerTests { ], [], ); - /** @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 @@ final class CommandTest extends AbstractSharingManagerTests { ], [], ); - /** @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 @@ final class CommandTest extends AbstractSharingManagerTests { ], [], ); - /** @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 @@ final class CommandTest extends AbstractSharingManagerTests { ], [], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } @@ -379,7 +379,7 @@ final class CommandTest extends AbstractSharingManagerTests { ], [], ); - /** @psalm-suppress MixedReturnStatement */ + /** @var SharingShare */ return json_decode($stdout, true, 512, JSON_THROW_ON_ERROR); } @@ -399,7 +399,7 @@ final class CommandTest extends AbstractSharingManagerTests { ['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 eaf057a8f4a..05395007891 100644 --- a/apps/sharing/tests/Controller/ApiV1ControllerTest.php +++ b/apps/sharing/tests/Controller/ApiV1ControllerTest.php @@ -95,53 +95,63 @@ final class ApiV1ControllerTest extends AbstractSharingManagerTests { #[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 @@ final class ApiV1ControllerTest extends AbstractSharingManagerTests { $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 bf5dec468f2..05da80bae04 100644 --- a/lib/private/ServerInfo.php +++ b/lib/private/ServerInfo.php @@ -25,7 +25,7 @@ readonly class ServerInfo implements IServerInfo { 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 35abf383155..c57ffbdd6bc 100644 --- a/lib/private/Sharing/SharingBackend.php +++ b/lib/private/Sharing/SharingBackend.php @@ -543,8 +543,8 @@ final readonly class SharingBackend implements ISharingBackend { $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 @@ final readonly class SharingBackend implements ISharingBackend { } // 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 @@ final readonly class SharingBackend implements ISharingBackend { /** @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 @@ final readonly class SharingBackend implements ISharingBackend { '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 @@ final readonly class SharingBackend implements ISharingBackend { } $value = $row['source_value']; - $id = (string)$row['share_id']; + $id = $row['share_id']; $shares[$id]['sources'][] = new ShareSource( $typeClass, $value, @@ -777,7 +777,7 @@ final readonly class SharingBackend implements ISharingBackend { '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 @@ final readonly class SharingBackend implements ISharingBackend { '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 @@ final readonly class SharingBackend implements ISharingBackend { '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 @@ final readonly class SharingBackend implements ISharingBackend { 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 51b8dc18b0e..4c2b0f2ee11 100644 --- a/lib/private/Sharing/SharingManager.php +++ b/lib/private/Sharing/SharingManager.php @@ -753,18 +753,17 @@ final readonly class SharingManager implements ISharingManager, IEventListener { } /** - * @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 feed3031aa3..004313e724d 100644 --- a/lib/unstable/Sharing/Share.php +++ b/lib/unstable/Sharing/Share.php @@ -132,7 +132,7 @@ use OCP\L10N\IFactory; * 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 3aaf8b5a32b..a0d248d466c 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 3ab9d5e15c3..ee248397d8c 100644 --- a/tests/lib/Sharing/AbstractSharingManagerTests.php +++ b/tests/lib/Sharing/AbstractSharingManagerTests.php @@ -42,24 +42,54 @@ use Test\TestCase; 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 class AbstractSharingManagerTests extends TestCase { 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);