diff --git a/.github/workflows/phpunit-32bits.yml b/.github/workflows/phpunit-32bits.yml new file mode 100644 index 0000000000000..1f91281bf1613 --- /dev/null +++ b/.github/workflows/phpunit-32bits.yml @@ -0,0 +1,58 @@ +name: PHPUnit + +on: + workflow_dispatch: + schedule: + - cron: "15 1 * * 1-6" + +permissions: + contents: read + +concurrency: + group: phpunit-32bits-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + phpunit-32bits: + runs-on: ubuntu-latest + container: shivammathur/node:latest-i386 + + strategy: + matrix: + php-versions: ['8.0'] + + steps: + - name: Checkout server + uses: actions/checkout@v3 + with: + submodules: true + + - name: Install tools + run: | + sudo apt-get update + sudo apt-get install -y ffmpeg imagemagick libmagickcore-6.q16-3-extra + + - name: Set up php ${{ matrix.php-versions }} + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: ctype, curl, dom, fileinfo, gd, imagick, intl, json, mbstring, openssl, pdo_sqlite, posix, sqlite, xml, zip, apcu + tools: phpunit:9 + coverage: none + ini-values: + apc.enabled=on, + apc.enable_cli=on + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Nextcloud + env: + DB_PORT: 4444 + run: | + mkdir data + ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=autotest --database-pass=rootpassword --admin-user admin --admin-pass admin + php -f index.php + + - name: PHPUnit + working-directory: tests + run: phpunit --configuration phpunit-autotest.xml --exclude-group PRIMARY-azure,PRIMARY-s3,PRIMARY-swift,Memcached,Redis,RoutingWeirdness diff --git a/.github/workflows/smb-kerberos.yml b/.github/workflows/smb-kerberos.yml index f47a06fbed794..1cb8ae973ca88 100644 --- a/.github/workflows/smb-kerberos.yml +++ b/.github/workflows/smb-kerberos.yml @@ -6,15 +6,17 @@ on: - stable* paths: - 'apps/files_external/**' + - '.github/workflows/smb-kerberos.yml' pull_request: paths: - 'apps/files_external/**' + - '.github/workflows/smb-kerberos.yml' jobs: smb-kerberos-tests: runs-on: ubuntu-latest - name: kerberos + name: smb-kerberos-sso steps: - name: Checkout server @@ -28,9 +30,12 @@ jobs: docker pull icewind1991/samba-krb-test-client - name: Setup AD-DC run: | + cp apps/files_external/tests/*.sh . mkdir data sudo chown -R 33 data apps config - apps/files_external/tests/setup-krb.sh + DC_IP=$(./start-dc.sh) + ./start-apache.sh $DC_IP $PWD + echo "DC_IP=$DC_IP" >> $GITHUB_ENV - name: Set up Nextcloud run: | docker exec --user 33 apache ./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password @@ -39,7 +44,8 @@ jobs: # setup user_saml docker exec --user 33 apache ./occ app:enable user_saml --force docker exec --user 33 apache ./occ config:app:set user_saml type --value 'environment-variable' - docker exec --user 33 apache ./occ config:app:set user_saml general-uid_mapping --value REMOTE_USER + docker exec --user 33 apache ./occ saml:config:create + docker exec --user 33 apache ./occ saml:config:set 1 --general-uid_mapping=REMOTE_USER # setup external storage docker exec --user 33 apache ./occ app:enable files_external --force @@ -49,16 +55,18 @@ jobs: docker exec --user 33 apache ./occ files_external:list - name: Test SSO run: | - mkdir cookies - chmod 0777 cookies + mkdir /tmp/shared/cookies + chmod 0777 /tmp/shared/cookies - DC_IP=$(docker inspect dc --format '{{.NetworkSettings.IPAddress}}') echo "SAML login" - docker run --rm --name client -v $PWD/cookies:/cookies -v /tmp/shared:/shared --dns $DC_IP --hostname client.domain.test icewind1991/samba-krb-test-client \ - curl -c /cookies/jar --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/index.php/apps/user_saml/saml/login + ./client-cmd.sh ${{ env.DC_IP }} curl -c /shared/cookies/jar -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/index.php/apps/user_saml/saml/login echo "Check we are logged in" - CONTENT=$(docker run --rm --name client -v $PWD/cookies:/cookies -v /tmp/shared:/shared --dns $DC_IP --hostname client.domain.test icewind1991/samba-krb-test-client \ - curl -b /cookies/jar -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/remote.php/webdav/smb/test.txt) - echo $CONTENT - CONTENT=$(echo $CONTENT | tr -d '[:space:]') + CONTENT=$(./client-cmd.sh ${{ env.DC_IP }} curl -b /shared/cookies/jar -s --negotiate -u testuser@DOMAIN.TEST: --delegation always http://httpd.domain.test/remote.php/webdav/smb/test.txt) + CONTENT=$(echo $CONTENT | head -n 1 | tr -d '[:space:]') [[ $CONTENT == "testfile" ]] + - name: Show logs + if: failure() + run: | + docker exec --user 33 apache ./occ log:file + FILEPATH=$(docker exec --user 33 apache ./occ log:file | grep "Log file:" | cut -d' ' -f3) + docker exec --user 33 apache cat $FILEPATH diff --git a/apps/dav/l10n/cs.js b/apps/dav/l10n/cs.js index 26470e05ec90d..869065e72cda4 100644 --- a/apps/dav/l10n/cs.js +++ b/apps/dav/l10n/cs.js @@ -72,8 +72,11 @@ OC.L10N.register( "Where: %s" : "Kde: %s", "%1$s via %2$s" : "%1$s prostřednictvím %2$s", "Cancelled: %1$s" : "Zrušeno: %1$s", + "\"%1$s\" has been canceled" : "„%1$s“ bylo zrušeno", "Re: %1$s" : "Odp.: %1$s", + "%1$s has responded your invitation" : "%1$s odpověděl(a) na vaši pozvánku", "Invitation: %1$s" : "Pozvánka: %1$s", + "%1$s would like to invite you to \"%2$s\"" : "%1$s by vás ráda pozval(a) na „%2$s“", "Organizer:" : "Organizátor:", "Attendees:" : "Účastníci:", "Title:" : "Název:", diff --git a/apps/dav/l10n/cs.json b/apps/dav/l10n/cs.json index 71fda38722697..d19552b583754 100644 --- a/apps/dav/l10n/cs.json +++ b/apps/dav/l10n/cs.json @@ -70,8 +70,11 @@ "Where: %s" : "Kde: %s", "%1$s via %2$s" : "%1$s prostřednictvím %2$s", "Cancelled: %1$s" : "Zrušeno: %1$s", + "\"%1$s\" has been canceled" : "„%1$s“ bylo zrušeno", "Re: %1$s" : "Odp.: %1$s", + "%1$s has responded your invitation" : "%1$s odpověděl(a) na vaši pozvánku", "Invitation: %1$s" : "Pozvánka: %1$s", + "%1$s would like to invite you to \"%2$s\"" : "%1$s by vás ráda pozval(a) na „%2$s“", "Organizer:" : "Organizátor:", "Attendees:" : "Účastníci:", "Title:" : "Název:", diff --git a/apps/dav/lib/CalDAV/BirthdayService.php b/apps/dav/lib/CalDAV/BirthdayService.php index df351a475524a..70b2e859f89a4 100644 --- a/apps/dav/lib/CalDAV/BirthdayService.php +++ b/apps/dav/lib/CalDAV/BirthdayService.php @@ -207,33 +207,26 @@ public function buildDateFromContact(string $cardData, } catch (InvalidDataException $e) { return null; } + if ($dateParts['year'] !== null) { + $parameters = $birthday->parameters(); + $omitYear = (isset($parameters['X-APPLE-OMIT-YEAR']) + && $parameters['X-APPLE-OMIT-YEAR'] === $dateParts['year']); + // 'X-APPLE-OMIT-YEAR' is not always present, at least iOS 12.4 uses the hard coded date of 1604 (the start of the gregorian calendar) when the year is unknown + if ($omitYear || (int)$dateParts['year'] === 1604) { + $dateParts['year'] = null; + } + } - $unknownYear = false; $originalYear = null; - if (!$dateParts['year']) { - $birthday = '1970-' . $dateParts['month'] . '-' . $dateParts['date']; + if ($dateParts['year'] !== null) { + $originalYear = (int)$dateParts['year']; + } - $unknownYear = true; - } else { - $parameters = $birthday->parameters(); - if (isset($parameters['X-APPLE-OMIT-YEAR'])) { - $omitYear = $parameters['X-APPLE-OMIT-YEAR']; - if ($dateParts['year'] === $omitYear) { - $birthday = '1970-' . $dateParts['month'] . '-' . $dateParts['date']; - $unknownYear = true; - } - } else { - $originalYear = (int)$dateParts['year']; - // 'X-APPLE-OMIT-YEAR' is not always present, at least iOS 12.4 uses the hard coded date of 1604 (the start of the gregorian calendar) when the year is unknown - if ($originalYear == 1604) { - $originalYear = null; - $unknownYear = true; - $birthday = '1970-' . $dateParts['month'] . '-' . $dateParts['date']; - } - if ($originalYear < 1970) { - $birthday = '1970-' . $dateParts['month'] . '-' . $dateParts['date']; - } - } + $leapDay = ((int)$dateParts['month'] === 2 + && (int)$dateParts['date'] === 29); + if ($dateParts['year'] === null || $originalYear < 1970) { + $birthday = ($leapDay ? '1972-' : '1970-') + . $dateParts['month'] . '-' . $dateParts['date']; } try { @@ -268,10 +261,15 @@ public function buildDateFromContact(string $cardData, $vEvent->DTEND['VALUE'] = 'DATE'; $vEvent->{'UID'} = $doc->UID . $postfix; $vEvent->{'RRULE'} = 'FREQ=YEARLY'; + if ($leapDay) { + /* Sabre\VObject supports BYMONTHDAY only if BYMONTH + * is also set */ + $vEvent->{'RRULE'} = 'FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=-1'; + } $vEvent->{'SUMMARY'} = $summary; $vEvent->{'TRANSP'} = 'TRANSPARENT'; $vEvent->{'X-NEXTCLOUD-BC-FIELD-TYPE'} = $dateField; - $vEvent->{'X-NEXTCLOUD-BC-UNKNOWN-YEAR'} = $unknownYear ? '1' : '0'; + $vEvent->{'X-NEXTCLOUD-BC-UNKNOWN-YEAR'} = $dateParts['year'] === null ? '1' : '0'; if ($originalYear !== null) { $vEvent->{'X-NEXTCLOUD-BC-YEAR'} = (string) $originalYear; } diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 909a582805996..51eb505124e38 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -72,7 +72,6 @@ use OCP\IConfig; use OCP\IDBConnection; use OCP\IGroupManager; -use OCP\IUser; use OCP\IUserManager; use OCP\Security\ISecureRandom; use Psr\Log\LoggerInterface; @@ -120,7 +119,6 @@ * @package OCA\DAV\CalDAV */ class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport { - use TTransactional; public const CALENDAR_TYPE_CALENDAR = 0; @@ -346,7 +344,7 @@ public function getCalendarsForUser($principalUri) { $row['principaluri'] = (string) $row['principaluri']; $components = []; if ($row['components']) { - $components = explode(',',$row['components']); + $components = explode(',', $row['components']); } $calendar = [ @@ -420,7 +418,7 @@ public function getCalendarsForUser($principalUri) { $row['displayname'] = $row['displayname'] . ' (' . ($this->userManager->getDisplayName($name) ?? ($name ?? '')) . ')'; $components = []; if ($row['components']) { - $components = explode(',',$row['components']); + $components = explode(',', $row['components']); } $calendar = [ 'id' => $row['id'], @@ -469,7 +467,7 @@ public function getUsersOwnCalendars($principalUri) { $row['principaluri'] = (string) $row['principaluri']; $components = []; if ($row['components']) { - $components = explode(',',$row['components']); + $components = explode(',', $row['components']); } $calendar = [ 'id' => $row['id'], @@ -521,7 +519,7 @@ public function getPublicCalendars() { $row['displayname'] = $row['displayname'] . "($name)"; $components = []; if ($row['components']) { - $components = explode(',',$row['components']); + $components = explode(',', $row['components']); } $calendar = [ 'id' => $row['id'], @@ -586,7 +584,7 @@ public function getPublicCalendar($uri) { $row['displayname'] = $row['displayname'] . ' ' . "($name)"; $components = []; if ($row['components']) { - $components = explode(',',$row['components']); + $components = explode(',', $row['components']); } $calendar = [ 'id' => $row['id'], @@ -639,7 +637,7 @@ public function getCalendarByUri($principal, $uri) { $row['principaluri'] = (string) $row['principaluri']; $components = []; if ($row['components']) { - $components = explode(',',$row['components']); + $components = explode(',', $row['components']); } $calendar = [ @@ -687,7 +685,7 @@ public function getCalendarById(int $calendarId): ?array { $row['principaluri'] = (string) $row['principaluri']; $components = []; if ($row['components']) { - $components = explode(',',$row['components']); + $components = explode(',', $row['components']); } $calendar = [ @@ -779,7 +777,7 @@ public function createCalendar($principalUri, $calendarUri, array $properties) { if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) { throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet'); } - $values['components'] = implode(',',$properties[$sccs]->getValue()); + $values['components'] = implode(',', $properties[$sccs]->getValue()); } elseif (isset($properties['components'])) { // Allow to provide components internally without having // to create a SupportedCalendarComponentSet object @@ -797,7 +795,7 @@ public function createCalendar($principalUri, $calendarUri, array $properties) { } } - [$calendarId, $calendarData] = $this->atomic(function() use ($values) { + [$calendarId, $calendarData] = $this->atomic(function () use ($values) { $query = $this->db->getQueryBuilder(); $query->insert('calendars'); foreach ($values as $column => $value) { @@ -1712,7 +1710,7 @@ public function calendarSearch($principalUri, array $filters, $limit = null, $of $query->expr()->eq('c.calendarid', $query->createNamedParameter($id)), $query->expr()->eq('c.calendartype', - $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR))); + $query->createNamedParameter(self::CALENDAR_TYPE_CALENDAR))); } foreach ($sharedCalendars as $id) { $calendarExpressions[] = $query->expr()->andX( @@ -1860,7 +1858,7 @@ public function search(array $calendarInfo, $pattern, array $searchProperties, } } - if(isset($options['uid'])) { + if (isset($options['uid'])) { $outerQuery->andWhere($outerQuery->expr()->eq('uid', $outerQuery->createNamedParameter($options['uid']))); } @@ -2435,7 +2433,7 @@ public function createSubscription($principalUri, $uri, array $properties) { } } - [$subscriptionId, $subscriptionRow] = $this->atomic(function() use ($values) { + [$subscriptionId, $subscriptionRow] = $this->atomic(function () use ($values) { $valuesToInsert = []; $query = $this->db->getQueryBuilder(); foreach (array_keys($values) as $name) { diff --git a/apps/dav/lib/Connector/Sabre/Auth.php b/apps/dav/lib/Connector/Sabre/Auth.php index 1610c554b9bba..69821c63c211e 100644 --- a/apps/dav/lib/Connector/Sabre/Auth.php +++ b/apps/dav/lib/Connector/Sabre/Auth.php @@ -104,14 +104,11 @@ protected function validateUserPass($username, $password) { if ($this->userSession->isLoggedIn() && $this->isDavAuthenticated($this->userSession->getUser()->getUID()) ) { - \OC_Util::setupFS($this->userSession->getUser()->getUID()); $this->session->close(); return true; } else { - \OC_Util::setupFS(); //login hooks may need early access to the filesystem try { if ($this->userSession->logClientIn($username, $password, $this->request, $this->throttler)) { - \OC_Util::setupFS($this->userSession->getUser()->getUID()); $this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID()); $this->session->close(); return true; diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index fd8a643722237..b0f17417d2195 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -138,7 +138,7 @@ public function __construct(View $view, FileInfo $info, IManager $shareManager = public function put($data) { try { $exists = $this->fileView->file_exists($this->path); - if ($this->info && $exists && !$this->info->isUpdateable()) { + if ($exists && !$this->info->isUpdateable()) { throw new Forbidden(); } } catch (StorageNotAvailableException $e) { @@ -759,9 +759,6 @@ private function convertToSabreException(\Exception $e) { * @return string|null */ public function getChecksum() { - if (!$this->info) { - return null; - } return $this->info->getChecksum(); } diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index f53c62afba27e..a6c9b8b4ebe4e 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -53,10 +53,8 @@ use Sabre\DAV\Tree; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; -use Sabre\Uri; class FilesPlugin extends ServerPlugin { - // namespace public const NS_OWNCLOUD = 'http://owncloud.org/ns'; public const NS_NEXTCLOUD = 'http://nextcloud.org/ns'; @@ -352,7 +350,7 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) $propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) { return json_encode($this->previewManager->isAvailable($node->getFileInfo()), JSON_THROW_ON_ERROR); }); - $propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node): ?int { + $propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node): int|float { return $node->getSize(); }); $propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function () use ($node) { @@ -382,7 +380,7 @@ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) }); /** * Return file/folder name as displayname. The primary reason to - * implement it this way is to avoid costly fallback to + * implement it this way is to avoid costly fallback to * CustomPropertiesBackend (esp. visible when querying all files * in a folder). */ diff --git a/apps/dav/lib/Connector/Sabre/Node.php b/apps/dav/lib/Connector/Sabre/Node.php index 1e32e74c325b6..ee159cef1d607 100644 --- a/apps/dav/lib/Connector/Sabre/Node.php +++ b/apps/dav/lib/Connector/Sabre/Node.php @@ -44,14 +44,12 @@ use OCP\Files\FileInfo; use OCP\Files\IRootFolder; use OCP\Files\StorageNotAvailableException; -use OCP\Share\IShare; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; abstract class Node implements \Sabre\DAV\INode { - /** - * @var \OC\Files\View + * @var View */ protected $fileView; @@ -69,10 +67,7 @@ abstract class Node implements \Sabre\DAV\INode { */ protected $property_cache = null; - /** - * @var \OCP\Files\FileInfo - */ - protected $info; + protected FileInfo $info; /** * @var IManager @@ -83,10 +78,6 @@ abstract class Node implements \Sabre\DAV\INode { /** * Sets up the node, expects a full path name - * - * @param \OC\Files\View $view - * @param \OCP\Files\FileInfo $info - * @param IManager $shareManager */ public function __construct(View $view, FileInfo $info, IManager $shareManager = null) { $this->fileView = $view; @@ -109,8 +100,12 @@ public function __construct(View $view, FileInfo $info, IManager $shareManager = } } - protected function refreshInfo() { - $this->info = $this->fileView->getFileInfo($this->path); + protected function refreshInfo(): void { + $info = $this->fileView->getFileInfo($this->path); + if ($info === false) { + throw new \Sabre\DAV\Exception('Failed to get fileinfo for '. $this->path); + } + $this->info = $info; $root = \OC::$server->get(IRootFolder::class); if ($this->info->getType() === FileInfo::TYPE_FOLDER) { $this->node = new Folder($root, $this->fileView, $this->path, $this->info); @@ -145,7 +140,6 @@ public function getPath() { * @throws \Sabre\DAV\Exception\Forbidden */ public function setName($name) { - // rename is only allowed if the update privilege is granted if (!($this->info->isUpdateable() || ($this->info->getMountPoint() instanceof MoveableMount && $this->info->getInternalPath() === ''))) { throw new \Sabre\DAV\Exception\Forbidden(); @@ -233,9 +227,10 @@ public function setUploadTime(int $time) { /** * Returns the size of the node, in bytes * - * @return integer + * @psalm-suppress ImplementedReturnTypeMismatch \Sabre\DAV\IFile::getSize signature does not support 32bit + * @return int|float */ - public function getSize() { + public function getSize(): int|float { return $this->info->getSize(); } @@ -271,7 +266,6 @@ public function getInternalFileId() { * @return int */ public function getSharePermissions($user) { - // check of we access a federated share if ($user !== null) { try { diff --git a/apps/dav/lib/Connector/Sabre/QuotaPlugin.php b/apps/dav/lib/Connector/Sabre/QuotaPlugin.php index ff7396a0825e5..ddf4b2773e0b1 100644 --- a/apps/dav/lib/Connector/Sabre/QuotaPlugin.php +++ b/apps/dav/lib/Connector/Sabre/QuotaPlugin.php @@ -178,7 +178,7 @@ public function beforeCopy(string $sourcePath, string $destinationPath): bool { * This method is called before any HTTP method and validates there is enough free space to store the file * * @param string $path relative to the users home - * @param int $length + * @param int|float|null $length * @throws InsufficientStorage * @return bool */ diff --git a/apps/dav/lib/Direct/DirectFile.php b/apps/dav/lib/Direct/DirectFile.php index a4a1999aca715..45c2114747eb7 100644 --- a/apps/dav/lib/Direct/DirectFile.php +++ b/apps/dav/lib/Direct/DirectFile.php @@ -77,6 +77,10 @@ public function getETag() { return $this->file->getEtag(); } + /** + * @psalm-suppress ImplementedReturnTypeMismatch \Sabre\DAV\IFile::getSize signature does not support 32bit + * @return int|float + */ public function getSize() { $this->getFile(); diff --git a/apps/dav/lib/Upload/UploadFile.php b/apps/dav/lib/Upload/UploadFile.php index 49a2fadecf6c8..023d17955c1d8 100644 --- a/apps/dav/lib/Upload/UploadFile.php +++ b/apps/dav/lib/Upload/UploadFile.php @@ -29,7 +29,6 @@ use Sabre\DAV\IFile; class UploadFile implements IFile { - /** @var File */ private $file; @@ -53,6 +52,10 @@ public function getETag() { return $this->file->getETag(); } + /** + * @psalm-suppress ImplementedReturnTypeMismatch \Sabre\DAV\IFile::getSize signature does not support 32bit + * @return int|float + */ public function getSize() { return $this->file->getSize(); } diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php index cfdf82e9b4f89..69096d0cfbb90 100644 --- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php @@ -125,7 +125,6 @@ public function providesSharingData() { * @dataProvider providesSharingData */ public function testCalendarSharing($userCanRead, $userCanWrite, $groupCanRead, $groupCanWrite, $add): void { - /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject $l10n */ $l10n = $this->createMock(IL10N::class); $l10n @@ -423,7 +422,12 @@ public function testCalendarQuery($expectedEventsInResult, $propFilters, $compFi $events[0] = $this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z'); $events[1] = $this->createEvent($calendarId, '20130912T150000Z', '20130912T170000Z'); $events[2] = $this->createEvent($calendarId, '20130912T173000Z', '20130912T220000Z'); - $events[3] = $this->createEvent($calendarId, '21130912T130000Z', '22130912T130000Z'); + if (PHP_INT_SIZE > 8) { + $events[3] = $this->createEvent($calendarId, '21130912T130000Z', '22130912T130000Z'); + } else { + /* On 32bit we do not support events after 2038 */ + $events[3] = $this->createEvent($calendarId, '20370912T130000Z', '20370912T130000Z'); + } $result = $this->backend->calendarQuery($calendarId, [ 'name' => '', @@ -471,7 +475,7 @@ public function providesCalendarQueryParameters() { 'only-events' => [[0, 1, 2, 3], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => null, 'end' => null], 'prop-filters' => []]],], 'start' => [[1, 2, 3], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => new DateTime('2013-09-12 14:00:00', new DateTimeZone('UTC')), 'end' => null], 'prop-filters' => []]],], 'end' => [[0], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => null, 'end' => new DateTime('2013-09-12 14:00:00', new DateTimeZone('UTC'))], 'prop-filters' => []]],], - 'future' => [[3], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => new DateTime('2099-09-12 14:00:00', new DateTimeZone('UTC')), 'end' => null], 'prop-filters' => []]],], + 'future' => [[3], [], [['name' => 'VEVENT', 'is-not-defined' => false, 'comp-filters' => [], 'time-range' => ['start' => new DateTime('2036-09-12 14:00:00', new DateTimeZone('UTC')), 'end' => null], 'prop-filters' => []]],], ]; } @@ -648,8 +652,15 @@ public function testScheduling($objectData): void { * @dataProvider providesCalDataForGetDenormalizedData */ public function testGetDenormalizedData($expected, $key, $calData): void { - $actual = $this->backend->getDenormalizedData($calData); - $this->assertEquals($expected, $actual[$key]); + try { + $actual = $this->backend->getDenormalizedData($calData); + $this->assertEquals($expected, $actual[$key]); + } catch (\ValueError $e) { + if (($e->getMessage() === 'Epoch doesn\'t fit in a PHP integer') && (PHP_INT_SIZE < 8)) { + $this->markTestSkipped('This fail on 32bits because of PHP limitations in DateTime'); + } + throw $e; + } } public function providesCalDataForGetDenormalizedData() { diff --git a/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php b/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php index 6cca861f6cca9..c3e27bf3dc9a6 100644 --- a/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php +++ b/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php @@ -78,13 +78,14 @@ protected function setUp(): void { * @dataProvider providesVCards * @param string $expectedSummary * @param string $expectedDTStart + * @param string $expectedRrule * @param string $expectedFieldType * @param string $expectedUnknownYear * @param string $expectedOriginalYear * @param string|null $expectedReminder * @param string | null $data */ - public function testBuildBirthdayFromContact($expectedSummary, $expectedDTStart, $expectedFieldType, $expectedUnknownYear, $expectedOriginalYear, $expectedReminder, $data, $fieldType, $prefix, $supports4Bytes, $configuredReminder): void { + public function testBuildBirthdayFromContact($expectedSummary, $expectedDTStart, $expectedRrule, $expectedFieldType, $expectedUnknownYear, $expectedOriginalYear, $expectedReminder, $data, $fieldType, $prefix, $supports4Bytes, $configuredReminder): void { $this->dbConnection->method('supports4ByteText')->willReturn($supports4Bytes); $cal = $this->service->buildDateFromContact($data, $fieldType, $prefix, $configuredReminder); @@ -94,7 +95,7 @@ public function testBuildBirthdayFromContact($expectedSummary, $expectedDTStart, $this->assertInstanceOf('Sabre\VObject\Component\VCalendar', $cal); $this->assertEquals('-//IDN nextcloud.com//Birthday calendar//EN', $cal->PRODID->getValue()); $this->assertTrue(isset($cal->VEVENT)); - $this->assertEquals('FREQ=YEARLY', $cal->VEVENT->RRULE->getValue()); + $this->assertEquals($expectedRrule, $cal->VEVENT->RRULE->getValue()); $this->assertEquals($expectedSummary, $cal->VEVENT->SUMMARY->getValue()); $this->assertEquals($expectedDTStart, $cal->VEVENT->DTSTART->getValue()); $this->assertEquals($expectedFieldType, $cal->VEVENT->{'X-NEXTCLOUD-BC-FIELD-TYPE'}->getValue()); @@ -410,35 +411,36 @@ public function providesCardChanges() { public function providesVCards() { return [ - // $expectedSummary, $expectedDTStart, $expectedFieldType, $expectedUnknownYear, $expectedOriginalYear, $expectedReminder, $data, $fieldType, $prefix, $supports4Byte, $configuredReminder - [null, null, null, null, null, null, 'yasfewf', '', '', true, null], - [null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", 'BDAY', '', true, null], - [null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:\r\nEND:VCARD\r\n", 'BDAY', '', true, null], - [null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:someday\r\nEND:VCARD\r\n", 'BDAY', '', true, null], - ['🎂 12345 (1900)', '19700101', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nEND:VCARD\r\n", 'BDAY', '', true, null], - ['🎂 12345 (1900)', '19701231', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', true, null], - ['Death of 12345 (1900)', '19701231', 'DEATHDATE', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', true, null], - ['Death of 12345 (1900)', '19701231', 'DEATHDATE', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', false, null], - ['💍 12345 (1900)', '19701231', 'ANNIVERSARY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', true, null], - ['12345 (⚭1900)', '19701231', 'ANNIVERSARY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', false, null], - ['🎂 12345', '19701231', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:--1231\r\nEND:VCARD\r\n", 'BDAY', '', true, null], - ['🎂 12345', '19701231', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY;X-APPLE-OMIT-YEAR=1604:16041231\r\nEND:VCARD\r\n", 'BDAY', '', true, null], - [null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:;VALUE=text:circa 1800\r\nEND:VCARD\r\n", 'BDAY', '', true, null], - [null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nN:12345;;;;\r\nBDAY:20031231\r\nEND:VCARD\r\n", 'BDAY', '', true, null], - ['🎂 12345 (900)', '19701231', 'BDAY', '0', '900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:09001231\r\nEND:VCARD\r\n", 'BDAY', '', true, null], - ['12345 (*1900)', '19700101', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nEND:VCARD\r\n", 'BDAY', '', false, null], - ['12345 (*1900)', '19701231', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, null], - ['12345 *', '19701231', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:--1231\r\nEND:VCARD\r\n", 'BDAY', '', false, null], - ['12345 *', '19701231', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY;X-APPLE-OMIT-YEAR=1604:16041231\r\nEND:VCARD\r\n", 'BDAY', '', false, null], - [null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:;VALUE=text:circa 1800\r\nEND:VCARD\r\n", 'BDAY', '', false, null], - [null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nN:12345;;;;\r\nBDAY:20031231\r\nEND:VCARD\r\n", 'BDAY', '', false, null], - ['12345 (*900)', '19701231', 'BDAY', '0', '900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:09001231\r\nEND:VCARD\r\n", 'BDAY', '', false, null], - ['12345 (*1900)', '19701231', 'BDAY', '0', '1900', 'PT9H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, 'PT9H'], - ['12345 (*1900)', '19701231', 'BDAY', '0', '1900', '-PT15H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, '-PT15H'], - ['12345 (*1900)', '19701231', 'BDAY', '0', '1900', '-P6DT15H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, '-P6DT15H'], - [null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nX-NC-EXCLUDE-FROM-BIRTHDAY-CALENDAR;TYPE=boolean:true\r\nEND:VCARD\r\n", 'BDAY', '', true, null], - [null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nX-NC-EXCLUDE-FROM-BIRTHDAY-CALENDAR;TYPE=boolean:true\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', true, null], - [null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nX-NC-EXCLUDE-FROM-BIRTHDAY-CALENDAR;TYPE=boolean:true\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', true, null], + // $expectedSummary, $expectedDTStart, $expectedRrule, $expectedFieldType, $expectedUnknownYear, $expectedOriginalYear, $expectedReminder, $data, $fieldType, $prefix, $supports4Byte, $configuredReminder + [null, null, null, null, null, null, null, 'yasfewf', '', '', true, null], + [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nEND:VCARD\r\n", 'BDAY', '', true, null], + [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:\r\nEND:VCARD\r\n", 'BDAY', '', true, null], + [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:someday\r\nEND:VCARD\r\n", 'BDAY', '', true, null], + ['🎂 12345 (1900)', '19700101', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nEND:VCARD\r\n", 'BDAY', '', true, null], + ['🎂 12345 (1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', true, null], + ['Death of 12345 (1900)', '19701231', 'FREQ=YEARLY', 'DEATHDATE', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', true, null], + ['Death of 12345 (1900)', '19701231', 'FREQ=YEARLY', 'DEATHDATE', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', false, null], + ['💍 12345 (1900)', '19701231', 'FREQ=YEARLY', 'ANNIVERSARY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', true, null], + ['12345 (⚭1900)', '19701231', 'FREQ=YEARLY', 'ANNIVERSARY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', false, null], + ['🎂 12345', '19701231', 'FREQ=YEARLY', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:--1231\r\nEND:VCARD\r\n", 'BDAY', '', true, null], + ['🎂 12345', '19701231', 'FREQ=YEARLY', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY;X-APPLE-OMIT-YEAR=1604:16041231\r\nEND:VCARD\r\n", 'BDAY', '', true, null], + [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:;VALUE=text:circa 1800\r\nEND:VCARD\r\n", 'BDAY', '', true, null], + [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nN:12345;;;;\r\nBDAY:20031231\r\nEND:VCARD\r\n", 'BDAY', '', true, null], + ['🎂 12345 (900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:09001231\r\nEND:VCARD\r\n", 'BDAY', '', true, null], + ['12345 (*1900)', '19700101', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nEND:VCARD\r\n", 'BDAY', '', false, null], + ['12345 (*1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, null], + ['12345 *', '19701231', 'FREQ=YEARLY', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:--1231\r\nEND:VCARD\r\n", 'BDAY', '', false, null], + ['12345 *', '19701231', 'FREQ=YEARLY', 'BDAY', '1', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY;X-APPLE-OMIT-YEAR=1604:16041231\r\nEND:VCARD\r\n", 'BDAY', '', false, null], + [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:;VALUE=text:circa 1800\r\nEND:VCARD\r\n", 'BDAY', '', false, null], + [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nN:12345;;;;\r\nBDAY:20031231\r\nEND:VCARD\r\n", 'BDAY', '', false, null], + ['12345 (*900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '900', null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:09001231\r\nEND:VCARD\r\n", 'BDAY', '', false, null], + ['12345 (*1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', 'PT9H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, 'PT9H'], + ['12345 (*1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', '-PT15H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, '-PT15H'], + ['12345 (*1900)', '19701231', 'FREQ=YEARLY', 'BDAY', '0', '1900', '-P6DT15H', "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19001231\r\nEND:VCARD\r\n", 'BDAY', '', false, '-P6DT15H'], + [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19000101\r\nX-NC-EXCLUDE-FROM-BIRTHDAY-CALENDAR;TYPE=boolean:true\r\nEND:VCARD\r\n", 'BDAY', '', true, null], + [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nX-NC-EXCLUDE-FROM-BIRTHDAY-CALENDAR;TYPE=boolean:true\r\nDEATHDATE:19001231\r\nEND:VCARD\r\n", 'DEATHDATE', '-death', true, null], + [null, null, null, null, null, null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nANNIVERSARY:19001231\r\nX-NC-EXCLUDE-FROM-BIRTHDAY-CALENDAR;TYPE=boolean:true\r\nEND:VCARD\r\n", 'ANNIVERSARY', '-anniversary', true, null], + ['🎂 12345 (1902)', '19720229', 'FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=-1', 'BDAY', '0', null, null, "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nUID:12345\r\nFN:12345\r\nN:12345;;;;\r\nBDAY:19020229\r\nEND:VCARD\r\n", 'BDAY', '', true, null], ]; } } diff --git a/apps/dav/tests/unit/Connector/Sabre/AuthTest.php b/apps/dav/tests/unit/Connector/Sabre/AuthTest.php index 72800b842535f..be841295f0b80 100644 --- a/apps/dav/tests/unit/Connector/Sabre/AuthTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/AuthTest.php @@ -117,7 +117,7 @@ public function testValidateUserPassOfAlreadyDAVAuthenticatedUser(): void { $user = $this->getMockBuilder(IUser::class) ->disableOriginalConstructor() ->getMock(); - $user->expects($this->exactly(2)) + $user->expects($this->exactly(1)) ->method('getUID') ->willReturn('MyTestUser'); $this->userSession @@ -125,7 +125,7 @@ public function testValidateUserPassOfAlreadyDAVAuthenticatedUser(): void { ->method('isLoggedIn') ->willReturn(true); $this->userSession - ->expects($this->exactly(2)) + ->expects($this->exactly(1)) ->method('getUser') ->willReturn($user); $this->session @@ -171,7 +171,7 @@ public function testValidateUserPassOfInvalidDAVAuthenticatedUserWithValidPasswo $user = $this->getMockBuilder(IUser::class) ->disableOriginalConstructor() ->getMock(); - $user->expects($this->exactly(3)) + $user->expects($this->exactly(2)) ->method('getUID') ->willReturn('MyTestUser'); $this->userSession @@ -179,7 +179,7 @@ public function testValidateUserPassOfInvalidDAVAuthenticatedUserWithValidPasswo ->method('isLoggedIn') ->willReturn(true); $this->userSession - ->expects($this->exactly(3)) + ->expects($this->exactly(2)) ->method('getUser') ->willReturn($user); $this->session @@ -660,11 +660,11 @@ public function testAuthenticateValidCredentials(): void { $user = $this->getMockBuilder(IUser::class) ->disableOriginalConstructor() ->getMock(); - $user->expects($this->exactly(3)) + $user->expects($this->exactly(2)) ->method('getUID') ->willReturn('MyTestUser'); $this->userSession - ->expects($this->exactly(4)) + ->expects($this->exactly(3)) ->method('getUser') ->willReturn($user); $response = $this->auth->check($server->httpRequest, $server->httpResponse); diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php index dfff9493762fb..5e638bbcd89f8 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php @@ -228,13 +228,13 @@ public function testGetPropertiesForFile(): void { $this->assertEquals('00000123instanceid', $propFind->get(self::FILEID_PROPERTYNAME)); $this->assertEquals('123', $propFind->get(self::INTERNAL_FILEID_PROPERTYNAME)); $this->assertEquals('1973-11-29T21:33:09+00:00', $propFind->get(self::CREATIONDATE_PROPERTYNAME)); - $this->assertEquals(null, $propFind->get(self::SIZE_PROPERTYNAME)); + $this->assertEquals(0, $propFind->get(self::SIZE_PROPERTYNAME)); $this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); $this->assertEquals('http://example.com/', $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); $this->assertEquals('foo', $propFind->get(self::OWNER_ID_PROPERTYNAME)); $this->assertEquals('M. Foo', $propFind->get(self::OWNER_DISPLAY_NAME_PROPERTYNAME)); $this->assertEquals('my_fingerprint', $propFind->get(self::DATA_FINGERPRINT_PROPERTYNAME)); - $this->assertEquals([self::SIZE_PROPERTYNAME], $propFind->get404Properties()); + $this->assertEquals([], $propFind->get404Properties()); } public function testGetPropertiesStorageNotAvailable(): void { diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php index d38031b03d467..3119d715bec90 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php @@ -261,6 +261,8 @@ public function testOnReport(): void { $filesNode2 = $this->getMockBuilder(File::class) ->disableOriginalConstructor() ->getMock(); + $filesNode2->method('getSize') + ->willReturn(10); $this->userFolder->expects($this->exactly(2)) ->method('getById') diff --git a/apps/files/appinfo/routes.php b/apps/files/appinfo/routes.php index dabbeab978e5b..e4af2367906a4 100644 --- a/apps/files/appinfo/routes.php +++ b/apps/files/appinfo/routes.php @@ -40,12 +40,12 @@ use OCA\Files\Controller\OpenLocalEditorController; // Legacy routes above -/** @var $this \OC\Route\Router */ +/** @var \OC\Route\Router $this */ $this->create('files_ajax_download', 'apps/files/ajax/download.php') ->actionInclude('files/ajax/download.php'); /** @var Application $application */ -$application = \OC::$server->query(Application::class); +$application = \OC::$server->get(Application::class); $application->registerRoutes( $this, [ diff --git a/apps/files/js/files.js b/apps/files/js/files.js index 0ae049360b492..b0c3824818309 100644 --- a/apps/files/js/files.js +++ b/apps/files/js/files.js @@ -481,6 +481,11 @@ var dragOptions={ $('.crumbmenu').removeClass('canDropChildren'); }, drag: function(event, ui) { + // Prevent scrolling when hovering .files-controls + if ($(event.originalEvent.target).parents('.files-controls').length > 0) { + return + } + /** @type {JQuery} */ const scrollingArea = FileList.$container; diff --git a/apps/files/tests/HelperTest.php b/apps/files/tests/HelperTest.php index 230e2c6cea2c4..423fd855a9ede 100644 --- a/apps/files/tests/HelperTest.php +++ b/apps/files/tests/HelperTest.php @@ -93,7 +93,10 @@ public function sortDataProvider() { /** * @dataProvider sortDataProvider */ - public function testSortByName($sort, $sortDescending, $expectedOrder) { + public function testSortByName(string $sort, bool $sortDescending, array $expectedOrder) { + if (($sort === 'mtime') && (PHP_INT_SIZE < 8)) { + $this->markTestSkipped('Skip mtime sorting on 32bit'); + } $files = self::getTestFileList(); $files = \OCA\Files\Helper::sortFiles($files, $sort, $sortDescending); $fileNames = []; diff --git a/apps/files_external/lib/Lib/Storage/FTP.php b/apps/files_external/lib/Lib/Storage/FTP.php index 284f438fccd07..ae02f37b5754b 100644 --- a/apps/files_external/lib/Lib/Storage/FTP.php +++ b/apps/files_external/lib/Lib/Storage/FTP.php @@ -140,7 +140,7 @@ public function filemtime($path) { } } - public function filesize($path) { + public function filesize($path): false|int|float { $result = $this->getConnection()->size($this->buildPath($path)); if ($result === -1) { return false; diff --git a/apps/files_external/tests/client-cmd.sh b/apps/files_external/tests/client-cmd.sh new file mode 100755 index 0000000000000..c97045bea4cf4 --- /dev/null +++ b/apps/files_external/tests/client-cmd.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +DC_IP=$1 +shift + +docker run --rm --name client -v /tmp/shared:/shared --dns $DC_IP --hostname client.domain.test icewind1991/samba-krb-test-client $@ diff --git a/apps/files_external/tests/setup-krb.sh b/apps/files_external/tests/setup-krb.sh deleted file mode 100755 index 968ba80529e22..0000000000000 --- a/apps/files_external/tests/setup-krb.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -function getContainerHealth { - docker inspect --format "{{.State.Health.Status}}" $1 -} - -function waitContainer { - while STATUS=$(getContainerHealth $1); [ $STATUS != "healthy" ]; do - if [ $STATUS == "unhealthy" ]; then - echo "Failed!" - exit -1 - fi - printf . - lf=$'\n' - sleep 1 - done - printf "$lf" -} - -mkdir /tmp/shared - -# start the dc -docker run -dit --name dc -v /tmp/shared:/shared --hostname krb.domain.test --cap-add SYS_ADMIN icewind1991/samba-krb-test-dc -DC_IP=$(docker inspect dc --format '{{.NetworkSettings.IPAddress}}') - -waitContainer dc - -# start apache -docker run -d --name apache -v $PWD:/var/www/html -v /tmp/shared:/shared --dns $DC_IP --hostname httpd.domain.test icewind1991/samba-krb-test-apache -APACHE_IP=$(docker inspect apache --format '{{.NetworkSettings.IPAddress}}') - -# add the dns record for apache -docker exec dc samba-tool dns add krb.domain.test domain.test httpd A $APACHE_IP -U administrator --password=passwOrd1 diff --git a/apps/files_external/tests/start-apache.sh b/apps/files_external/tests/start-apache.sh new file mode 100755 index 0000000000000..2c3e178e2efeb --- /dev/null +++ b/apps/files_external/tests/start-apache.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +docker rm -f apache 2>/dev/null > /dev/null + +docker run -d --name apache -v $2:/var/www/html -v /tmp/shared:/shared --dns $1 --hostname httpd.domain.test icewind1991/samba-krb-test-apache 1>&2 +APACHE_IP=$(docker inspect apache --format '{{.NetworkSettings.IPAddress}}') + +# add the dns record for apache +docker exec dc samba-tool dns add krb.domain.test domain.test httpd A $APACHE_IP -U administrator --password=passwOrd1 1>&2 + +echo $APACHE_IP diff --git a/apps/files_external/tests/start-dc.sh b/apps/files_external/tests/start-dc.sh new file mode 100755 index 0000000000000..45fbbfbf0b52d --- /dev/null +++ b/apps/files_external/tests/start-dc.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +function getContainerHealth { + docker inspect --format "{{.State.Health.Status}}" $1 +} + +function waitContainer { + while STATUS=$(getContainerHealth $1); [ $STATUS != "healthy" ]; do + if [ $STATUS == "unhealthy" ]; then + echo "Failed!" 1>&2 + exit -1 + fi + printf . 1>&2 + lf=$'\n' + sleep 1 + done + printf "$lf" 1>&2 +} + +docker rm -f dc 2>/dev/null > /dev/null + +mkdir -p /tmp/shared + +# start the dc +docker run -dit --name dc -v /tmp/shared:/shared --hostname krb.domain.test --cap-add SYS_ADMIN icewind1991/samba-krb-test-dc 1>&2 + +waitContainer dc + +docker inspect dc --format '{{.NetworkSettings.IPAddress}}' diff --git a/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php b/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php index cb0ba707c0c6f..9c95428d7b73e 100644 --- a/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php +++ b/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php @@ -139,7 +139,7 @@ public function renderPage(IShare $share, string $token, string $path): Template if ($freeSpace < FileInfo::SPACE_UNLIMITED) { $freeSpace = (int)max($freeSpace, 0); } else { - $freeSpace = (int)((INF > 0) ? INF: PHP_INT_MAX); // work around https://bugs.php.net/bug.php?id=69188 + $freeSpace = (INF > 0) ? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188 } $hideFileList = !($share->getPermissions() & Constants::PERMISSION_READ); diff --git a/apps/files_sharing/tests/ApiTest.php b/apps/files_sharing/tests/ApiTest.php index 4d7389be24ed0..d7661297e9e86 100644 --- a/apps/files_sharing/tests/ApiTest.php +++ b/apps/files_sharing/tests/ApiTest.php @@ -205,6 +205,9 @@ public function testCreateShareGroupFolder() { $ocs->cleanup(); } + /** + * @group RoutingWeirdness + */ public function testCreateShareLink() { $ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1); $result = $ocs->createShare($this->folder, \OCP\Constants::PERMISSION_ALL, IShare::TYPE_LINK); @@ -227,6 +230,9 @@ public function testCreateShareLink() { $ocs->cleanup(); } + /** + * @group RoutingWeirdness + */ public function testCreateShareLinkPublicUpload() { $ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1); $result = $ocs->createShare($this->folder, \OCP\Constants::PERMISSION_ALL, IShare::TYPE_LINK, null, 'true'); @@ -419,6 +425,7 @@ public function testGetAllSharesWithMe() { /** * @medium + * @group RoutingWeirdness */ public function testPublicLinkUrl() { $ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1); @@ -837,7 +844,7 @@ public function testGetShareMultipleSharedFolder() { // $request = $this->createRequest(['path' => $this->subfolder]); $ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER2); - $result1 = $ocs->getShares('false','false','false', $this->subfolder); + $result1 = $ocs->getShares('false', 'false', 'false', $this->subfolder); $ocs->cleanup(); // test should return one share within $this->folder @@ -1050,10 +1057,10 @@ public function testUpdateShareExpireDate() { $config->setAppValue('core', 'shareapi_enforce_expire_date', 'yes'); $dateWithinRange = new \DateTime(); - $dateWithinRange->setTime(0,0,0); + $dateWithinRange->setTime(0, 0, 0); $dateWithinRange->add(new \DateInterval('P5D')); $dateOutOfRange = new \DateTime(); - $dateOutOfRange->setTime(0,0,0); + $dateOutOfRange->setTime(0, 0, 0); $dateOutOfRange->add(new \DateInterval('P8D')); // update expire date to a valid value @@ -1290,6 +1297,7 @@ public function datesProvider() { * Make sure only ISO 8601 dates are accepted * * @dataProvider datesProvider + * @group RoutingWeirdness */ public function testPublicLinkExpireDate($date, $valid) { $ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER1); @@ -1320,6 +1328,9 @@ public function testPublicLinkExpireDate($date, $valid) { $this->shareManager->deleteShare($share); } + /** + * @group RoutingWeirdness + */ public function testCreatePublicLinkExpireDateValid() { $config = \OC::$server->getConfig(); @@ -1343,7 +1354,7 @@ public function testCreatePublicLinkExpireDateValid() { $this->assertEquals($url, $data['url']); $share = $this->shareManager->getShareById('ocinternal:'.$data['id']); - $date->setTime(0,0,0); + $date->setTime(0, 0, 0); $this->assertEquals($date, $share->getExpirationDate()); $this->shareManager->deleteShare($share); diff --git a/apps/files_trashbin/lib/Sabre/AbstractTrash.php b/apps/files_trashbin/lib/Sabre/AbstractTrash.php index 412e432a19eaf..e02e4c5b8ba45 100644 --- a/apps/files_trashbin/lib/Sabre/AbstractTrash.php +++ b/apps/files_trashbin/lib/Sabre/AbstractTrash.php @@ -57,7 +57,11 @@ public function getFileInfo(): FileInfo { return $this->data; } - public function getSize(): int { + /** + * @psalm-suppress ImplementedReturnTypeMismatch \Sabre\DAV\IFile::getSize signature does not support 32bit + * @return int|float + */ + public function getSize(): int|float { return $this->data->getSize(); } diff --git a/apps/files_trashbin/lib/Sabre/ITrash.php b/apps/files_trashbin/lib/Sabre/ITrash.php index dcda1abe2592a..b6b4e70f3b9a3 100644 --- a/apps/files_trashbin/lib/Sabre/ITrash.php +++ b/apps/files_trashbin/lib/Sabre/ITrash.php @@ -39,7 +39,7 @@ public function getTitle(): string; public function getDeletionTime(): int; - public function getSize(); + public function getSize(): int|float; public function getFileId(): int; diff --git a/apps/files_trashbin/tests/BackgroundJob/ExpireTrashTest.php b/apps/files_trashbin/tests/BackgroundJob/ExpireTrashTest.php index bf5d6bc65ae9e..3264c11f8fa1d 100644 --- a/apps/files_trashbin/tests/BackgroundJob/ExpireTrashTest.php +++ b/apps/files_trashbin/tests/BackgroundJob/ExpireTrashTest.php @@ -33,7 +33,6 @@ use OCP\IUserManager; use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; -use Psr\Log\LoggerInterface; class ExpireTrashTest extends TestCase { /** @var IConfig|MockObject */ @@ -61,7 +60,7 @@ protected function setUp(): void { $this->time = $this->createMock(ITimeFactory::class); $this->time->method('getTime') - ->willReturn(99999999999); + ->willReturn(999999999); $this->jobList->expects($this->once()) ->method('setLastRun'); diff --git a/apps/files_versions/l10n/eu.js b/apps/files_versions/l10n/eu.js index 1a1405d924f86..f3e0632b4e578 100644 --- a/apps/files_versions/l10n/eu.js +++ b/apps/files_versions/l10n/eu.js @@ -4,10 +4,19 @@ OC.L10N.register( "Versions" : "Bertsioak", "This application automatically maintains older versions of files that are changed." : "Aplikazio honek aldatzen diren fitxategien bertsio zaharrak mantentzen ditu automatikoki.", "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user's directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user does not run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user's currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Aplikazio honek automatikoki mantentzen ditu aldatzen diren fitxategien bertsio zaharragoak. Gaituta dagoenean, ezkutuko bertsioen karpeta batez hornitzen da erabiltzaile bakoitzaren direktorioa, fitxategien bertsio zaharrak gordetzeko. Erabiltzaileak edozein unetan bertsio zaharrago batera leheneratu dezake web interfazearen bidez, ordeztutako fitxategia bertsio bihurtuz. Aplikazioak automatikoki kudeatzen du bertsioen karpeta, erabiltzailea bertsioak direla eta kuotarik gabe geratuko ez dela ziurtatzeko.\n\t\tBertsioak iraungitzeaz gain, bertsioen aplikazioak ziurtatzen du ez dela inoiz erabiliko erabiltzailearen uneko espazio librearen %50 baino gehiago. Biltegiratutako bertsioek muga hori gainditzen badute, aplikazioak bertsio zaharrenak ezabatuko ditu, mugara jaitsi arte. Informazio gehiago dago eskuragarri bertsioen dokumentazioan.", + "Edit version name" : "Editatu bertsioaren izena", "Restore version" : "Leheneratu bertsioa", "Download version" : "Deskargatu bertsioa", + "Delete version" : "Ezabatu bertsioa", + "Version name" : "Bertsioaren izena", + "Remove version name" : "Kendu bertsioaren izena", + "Save version name" : "Gorde bertsioaren izena", + "Initial version restored" : "Hasierako bertsioa leheneratua", "Version restored" : "Bertsioa leheneratu da", "Could not restore version" : "Ezin izan da bertsioa leheneratu", + "Could not set version name" : "Ezin izan da bertsioaren izena ezarri", + "Could not delete version" : "Ezin izan da bertsioa ezabatu", + "${version.label} restored" : "${version.label} leheneratuta", "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Fitxategi honek aldatzen diren fitxategien bertsio zaharrak mantentzen ditu. Aktibatzen denean, bertsioen karpeta ezkutu bat sortzen da erabiltzailearen karpeta barruan eta bertan gordetzen dira bertsio zaharrak. Erabiltzailek edozein bertsio zaharretara itzultzea erabaki dezake web interfazea erabiliz, ordezkatutako fitxategia bertsio bilakatuz. Aplikazio honek bertsioen karpeta automatikoki kudeatzen du erabiltzaileak ez dezan mugarik gainditu bertsioengatik.\n\t\tBertsioak iraungitzeaz gain, bertsioen aplikazio honek erabiltzailearen toki libre guztiaren %50 baino gehiago ez erabiltzea bermatzen du. Gordetako bertsioek muga hori gainditzen badute, aplikazioak bertsio zaharrenak ezabatuko ditu mugara iritsi arte. Informazio gehiago lortzeko aplikazioaren dokumentazioa irakurri dezakezu.", "Failed to revert {file} to revision {timestamp}." : "Errorea egon da {fitxategia} {timestamp} bertsiora leheneratzean.", "_%n byte_::_%n bytes_" : ["%nbyte","%n bytes"], diff --git a/apps/files_versions/l10n/eu.json b/apps/files_versions/l10n/eu.json index 23ee73955b1f5..43a61c3d6ee04 100644 --- a/apps/files_versions/l10n/eu.json +++ b/apps/files_versions/l10n/eu.json @@ -2,10 +2,19 @@ "Versions" : "Bertsioak", "This application automatically maintains older versions of files that are changed." : "Aplikazio honek aldatzen diren fitxategien bertsio zaharrak mantentzen ditu automatikoki.", "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user's directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user does not run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user's currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Aplikazio honek automatikoki mantentzen ditu aldatzen diren fitxategien bertsio zaharragoak. Gaituta dagoenean, ezkutuko bertsioen karpeta batez hornitzen da erabiltzaile bakoitzaren direktorioa, fitxategien bertsio zaharrak gordetzeko. Erabiltzaileak edozein unetan bertsio zaharrago batera leheneratu dezake web interfazearen bidez, ordeztutako fitxategia bertsio bihurtuz. Aplikazioak automatikoki kudeatzen du bertsioen karpeta, erabiltzailea bertsioak direla eta kuotarik gabe geratuko ez dela ziurtatzeko.\n\t\tBertsioak iraungitzeaz gain, bertsioen aplikazioak ziurtatzen du ez dela inoiz erabiliko erabiltzailearen uneko espazio librearen %50 baino gehiago. Biltegiratutako bertsioek muga hori gainditzen badute, aplikazioak bertsio zaharrenak ezabatuko ditu, mugara jaitsi arte. Informazio gehiago dago eskuragarri bertsioen dokumentazioan.", + "Edit version name" : "Editatu bertsioaren izena", "Restore version" : "Leheneratu bertsioa", "Download version" : "Deskargatu bertsioa", + "Delete version" : "Ezabatu bertsioa", + "Version name" : "Bertsioaren izena", + "Remove version name" : "Kendu bertsioaren izena", + "Save version name" : "Gorde bertsioaren izena", + "Initial version restored" : "Hasierako bertsioa leheneratua", "Version restored" : "Bertsioa leheneratu da", "Could not restore version" : "Ezin izan da bertsioa leheneratu", + "Could not set version name" : "Ezin izan da bertsioaren izena ezarri", + "Could not delete version" : "Ezin izan da bertsioa ezabatu", + "${version.label} restored" : "${version.label} leheneratuta", "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Fitxategi honek aldatzen diren fitxategien bertsio zaharrak mantentzen ditu. Aktibatzen denean, bertsioen karpeta ezkutu bat sortzen da erabiltzailearen karpeta barruan eta bertan gordetzen dira bertsio zaharrak. Erabiltzailek edozein bertsio zaharretara itzultzea erabaki dezake web interfazea erabiliz, ordezkatutako fitxategia bertsio bilakatuz. Aplikazio honek bertsioen karpeta automatikoki kudeatzen du erabiltzaileak ez dezan mugarik gainditu bertsioengatik.\n\t\tBertsioak iraungitzeaz gain, bertsioen aplikazio honek erabiltzailearen toki libre guztiaren %50 baino gehiago ez erabiltzea bermatzen du. Gordetako bertsioek muga hori gainditzen badute, aplikazioak bertsio zaharrenak ezabatuko ditu mugara iritsi arte. Informazio gehiago lortzeko aplikazioaren dokumentazioa irakurri dezakezu.", "Failed to revert {file} to revision {timestamp}." : "Errorea egon da {fitxategia} {timestamp} bertsiora leheneratzean.", "_%n byte_::_%n bytes_" : ["%nbyte","%n bytes"], diff --git a/apps/files_versions/lib/Db/VersionEntity.php b/apps/files_versions/lib/Db/VersionEntity.php index d5adbcfa104fa..a37171ef93ffe 100644 --- a/apps/files_versions/lib/Db/VersionEntity.php +++ b/apps/files_versions/lib/Db/VersionEntity.php @@ -36,8 +36,8 @@ * @method void setFileId(int $fileId) * @method int getTimestamp() * @method void setTimestamp(int $timestamp) - * @method int getSize() - * @method void setSize(int $size) + * @method int|float getSize() + * @method void setSize(int|float $size) * @method int getMimetype() * @method void setMimetype(int $mimetype) * @method array|null getMetadata() @@ -78,4 +78,4 @@ public function setLabel(string $label): void { $this->metadata['label'] = $label; $this->markFieldUpdated('metadata'); } -} \ No newline at end of file +} diff --git a/apps/files_versions/lib/Sabre/VersionFile.php b/apps/files_versions/lib/Sabre/VersionFile.php index 20ae25a762376..8fd97b0636f75 100644 --- a/apps/files_versions/lib/Sabre/VersionFile.php +++ b/apps/files_versions/lib/Sabre/VersionFile.php @@ -68,7 +68,11 @@ public function getETag(): string { return (string)$this->version->getRevisionId(); } - public function getSize(): int { + /** + * @psalm-suppress ImplementedReturnTypeMismatch \Sabre\DAV\IFile::getSize signature does not support 32bit + * @return int|float + */ + public function getSize(): int|float { return $this->version->getSize(); } diff --git a/apps/files_versions/lib/Versions/IVersion.php b/apps/files_versions/lib/Versions/IVersion.php index 8ab3357b1e216..8480658fa30fc 100644 --- a/apps/files_versions/lib/Versions/IVersion.php +++ b/apps/files_versions/lib/Versions/IVersion.php @@ -65,10 +65,10 @@ public function getTimestamp(): int; /** * Get the size of this version * - * @return int + * @return int|float * @since 15.0.0 */ - public function getSize(): int; + public function getSize(): int|float; /** * Get the name of the source file at the time of making this version diff --git a/apps/files_versions/lib/Versions/Version.php b/apps/files_versions/lib/Versions/Version.php index e87c2a593d791..0dade222abce1 100644 --- a/apps/files_versions/lib/Versions/Version.php +++ b/apps/files_versions/lib/Versions/Version.php @@ -40,7 +40,7 @@ class Version implements IVersion, INameableVersion { private string $label; - /** @var int */ + /** @var int|float */ private $size; /** @var string */ @@ -62,7 +62,7 @@ public function __construct( int $timestamp, $revisionId, string $name, - int $size, + int|float $size, string $mimetype, string $path, FileInfo $sourceFileInfo, @@ -98,7 +98,7 @@ public function getTimestamp(): int { return $this->timestamp; } - public function getSize(): int { + public function getSize(): int|float { return $this->size; } diff --git a/apps/files_versions/tests/BackgroundJob/ExpireVersionsTest.php b/apps/files_versions/tests/BackgroundJob/ExpireVersionsTest.php index 442a7020d8959..1d5812767caff 100644 --- a/apps/files_versions/tests/BackgroundJob/ExpireVersionsTest.php +++ b/apps/files_versions/tests/BackgroundJob/ExpireVersionsTest.php @@ -33,7 +33,6 @@ use Test\TestCase; class ExpireVersionsTest extends TestCase { - /** @var IConfig|MockObject */ private $config; @@ -70,7 +69,7 @@ public function testBackgroundJobDeactivated(): void { $timeFactory = $this->createMock(ITimeFactory::class); $timeFactory->method('getTime') ->with() - ->willReturn(99999999999); + ->willReturn(999999999); $job = new ExpireVersions($this->config, $this->userManager, $this->expiration, $timeFactory); $job->start($this->jobList); diff --git a/apps/provisioning_api/tests/Controller/UsersControllerTest.php b/apps/provisioning_api/tests/Controller/UsersControllerTest.php index e5c5678efd37c..9e2da244f7bb1 100644 --- a/apps/provisioning_api/tests/Controller/UsersControllerTest.php +++ b/apps/provisioning_api/tests/Controller/UsersControllerTest.php @@ -73,7 +73,6 @@ use Test\TestCase; class UsersControllerTest extends TestCase { - /** @var IUserManager|MockObject */ protected $userManager; /** @var IConfig|MockObject */ @@ -497,7 +496,7 @@ public function testAddUserSuccessfulGenerateUserID() { ->method('generate') ->with(10) ->willReturnCallback(function () { - return (string)rand(1000000000, 9999999999); + return (string)rand(100000000, 999999999); }); $this->assertTrue(key_exists( diff --git a/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/VisibilityDropdown.vue b/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/VisibilityDropdown.vue index 24f025f2da09a..f00130ec0a454 100644 --- a/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/VisibilityDropdown.vue +++ b/apps/settings/src/components/PersonalInfo/ProfileVisibilitySection/VisibilityDropdown.vue @@ -26,13 +26,12 @@ - + @option:selected="onVisibilityChange" /> @@ -41,7 +40,7 @@ import { showError } from '@nextcloud/dialogs' import { loadState } from '@nextcloud/initial-state' import { subscribe, unsubscribe } from '@nextcloud/event-bus' -import NcMultiselect from '@nextcloud/vue/dist/Components/NcMultiselect' +import NcSelect from '@nextcloud/vue/dist/Components/NcSelect.js' import { saveProfileParameterVisibility } from '../../../service/ProfileService.js' import { VISIBILITY_PROPERTY_ENUM } from '../../../constants/ProfileConstants.js' @@ -53,7 +52,7 @@ export default { name: 'VisibilityDropdown', components: { - NcMultiselect, + NcSelect, }, props: { @@ -173,8 +172,8 @@ export default { line-height: 50px; } - &__multiselect { - width: 260px; + &__select { + width: 270px; max-width: 40vw; } } diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml index c1c39c135f8ad..64c1e6983ac37 100644 --- a/build/psalm-baseline.xml +++ b/build/psalm-baseline.xml @@ -241,33 +241,6 @@ \Sabre\Uri\split($path) - - - $vevent->LOCATION - $vevent->SUMMARY - - - string - - - $lang->getValue() - - - getDateTime - getDateTime - getDateTime - getDateTime - getDateTime - hasTime - hasTime - isFloating - isFloating - isFloating - - - $iTipMessage->message->VEVENT->SUMMARY - - [$aclPlugin, 'propFind'] @@ -894,25 +867,11 @@ (int)$share['id'] - - - string - - - null - - $files_list - - - $this - $this - - $this->fileEncrypted[$fileId] @@ -1086,12 +1045,6 @@ $files_list - - $maxUploadFilesize - - - null - @@ -1274,9 +1227,6 @@ - - $quota - $groupid === null $groupid === null @@ -2360,9 +2310,6 @@ $exception->getCode() - - null - wrap @@ -2659,46 +2606,6 @@ $storage->scanner - - - copy - copyFromStorage - file_exists - file_get_contents - file_put_contents - filemtime - filesize - filetype - fopen - free_space - getDirectDownload - getETag - getLocalFile - getMimeType - getOwner - getPermissions - hash - isCreatable - isDeletable - isReadable - isSharable - isUpdatable - is_dir - is_file - mkdir - moveFromStorage - opendir - rename - rmdir - search - stat - touch - unlink - - - \Traversable - - \Normalizer::FORM_C @@ -2730,18 +2637,17 @@ $result $this->copyFromStorage($sourceStorage, $sourceInternalPath . '/' . $file, $targetInternalPath . '/' . $file, false, $isRename) - + $newUnencryptedSize $result $this->storage->file_get_contents($path) - $this->storage->filesize($path) bool int string - + $lastChunkPos $newUnencryptedSize $size @@ -2764,9 +2670,8 @@ string - + $free - 'ext' @@ -2998,19 +2903,6 @@ - - - string[] - - - $failedRecipients - - - getSubject - getSwiftMessage - getTo - - apcu_add($this->getPrefix() . $key, $value, $ttl) @@ -3537,9 +3429,6 @@ - - mt_rand() - $getType === self::ZIP_DIR $getType === self::ZIP_DIR @@ -3553,17 +3442,8 @@ $matches[1][$last_match][0] - - (INF > 0)? INF: PHP_INT_MAX - INF - - - int - - - $includeExtStorage ? 'ext' : false + $path - 'ext' count($obd_values) > 0 diff --git a/core/Command/Log/File.php b/core/Command/Log/File.php index f2c77e20174c4..6d6e530fe9aed 100644 --- a/core/Command/Log/File.php +++ b/core/Command/Log/File.php @@ -115,14 +115,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int } /** - * @param mixed $rotateSize * @throws \InvalidArgumentException */ - protected function validateRotateSize(&$rotateSize) { + protected function validateRotateSize(false|int|float $rotateSize): void { if ($rotateSize === false) { throw new \InvalidArgumentException('Error parsing log rotation file size'); } - $rotateSize = (int) $rotateSize; if ($rotateSize < 0) { throw new \InvalidArgumentException('Log rotation file size must be non-negative'); } diff --git a/core/js/login/authpicker.js b/core/js/login/authpicker.js index 8aaaf3d7361c1..1a94b02d72f95 100644 --- a/core/js/login/authpicker.js +++ b/core/js/login/authpicker.js @@ -10,4 +10,6 @@ jQuery(document).ready(function() { e.preventDefault(); document.location.href = e.target.attributes.action.value }) + + $('#login-form input').removeAttr('disabled'); }) diff --git a/core/js/setupchecks.js b/core/js/setupchecks.js index 67f8d70bd31ab..73896f9fc9156 100644 --- a/core/js/setupchecks.js +++ b/core/js/setupchecks.js @@ -421,7 +421,7 @@ messages.push({ msg: t( 'core', - 'It seems like you are running a 32-bit PHP version. Nextcloud 26 and higher require 64-bit. Please upgrade your OS and PHP to 64-bit! For further details read {linkstart}the documentation page ↗{linkend} about this.' + 'It seems like you are running a 32-bit PHP version. Nextcloud needs 64-bit to run well. Please upgrade your OS and PHP to 64-bit! For further details read {linkstart}the documentation page ↗{linkend} about this.' .replace('{linkstart}', '') .replace('{linkend}', ''), ), diff --git a/core/js/tests/specs/setupchecksSpec.js b/core/js/tests/specs/setupchecksSpec.js index 6df1659d7d3f1..4532ac457b69c 100644 --- a/core/js/tests/specs/setupchecksSpec.js +++ b/core/js/tests/specs/setupchecksSpec.js @@ -1234,7 +1234,7 @@ describe('OC.SetupChecks tests', function() { }); }); - + it('should return an error if imagick is not enabled', function(done) { var async = OC.SetupChecks.checkSetup(); @@ -1293,7 +1293,7 @@ describe('OC.SetupChecks tests', function() { }); }); - + it('should return an error if gmp or bcmath are not enabled', function(done) { var async = OC.SetupChecks.checkSetup(); @@ -1403,7 +1403,7 @@ describe('OC.SetupChecks tests', function() { async.done(function( data, s, x ){ expect(data).toEqual([{ - msg: 'It seems like you are running a 32-bit PHP version. Nextcloud 26 and higher require 64-bit. Please upgrade your OS and PHP to 64-bit! For further details read the documentation page ↗ about this.', + msg: 'It seems like you are running a 32-bit PHP version. Nextcloud needs 64-bit to run well. Please upgrade your OS and PHP to 64-bit! For further details read the documentation page ↗ about this.', type: OC.SetupChecks.MESSAGE_TYPE_WARNING }]); done(); diff --git a/core/l10n/cs.js b/core/l10n/cs.js index 9e70da8eaf66f..666f6d7d38450 100644 --- a/core/l10n/cs.js +++ b/core/l10n/cs.js @@ -25,6 +25,7 @@ OC.L10N.register( "State token does not match" : "Stavový token se neshoduje", "Invalid app password" : "Neplatné heslo pro aplikaci", "Could not complete login" : "Přihlášení se nedaří dokončit", + "State token missing" : "Chybí stavový token", "Your login token is invalid or has expired" : "Váš přihlašovací token není platný nebo jeho platnost skončila", "This community release of Nextcloud is unsupported and push notifications are limited." : "Toto komunitní vydání Nextcloud není podporováno a push oznámení jsou proto omezená.", "Login" : "Přihlásit", diff --git a/core/l10n/cs.json b/core/l10n/cs.json index 7c05b9a790e30..4461f2582d563 100644 --- a/core/l10n/cs.json +++ b/core/l10n/cs.json @@ -23,6 +23,7 @@ "State token does not match" : "Stavový token se neshoduje", "Invalid app password" : "Neplatné heslo pro aplikaci", "Could not complete login" : "Přihlášení se nedaří dokončit", + "State token missing" : "Chybí stavový token", "Your login token is invalid or has expired" : "Váš přihlašovací token není platný nebo jeho platnost skončila", "This community release of Nextcloud is unsupported and push notifications are limited." : "Toto komunitní vydání Nextcloud není podporováno a push oznámení jsou proto omezená.", "Login" : "Přihlásit", diff --git a/core/l10n/de_DE.js b/core/l10n/de_DE.js index 11c2050679ac5..3446ccdf6629e 100644 --- a/core/l10n/de_DE.js +++ b/core/l10n/de_DE.js @@ -25,6 +25,7 @@ OC.L10N.register( "State token does not match" : "Status-Token stimmen nicht überein", "Invalid app password" : "Ungültiges App-Passwort", "Could not complete login" : "Anmeldung konnte nicht abgeschlossen werden", + "State token missing" : "Zustandstoken fehlt", "Your login token is invalid or has expired" : "Ihr Anmelde-Token ist ungültig oder abgelaufen", "This community release of Nextcloud is unsupported and push notifications are limited." : "Diese Community-Version von Nextcloud wird nicht unterstützt und sofortige Benachrichtigungen sind nur begrenzt verfügbar.", "Login" : "Anmelden", diff --git a/core/l10n/de_DE.json b/core/l10n/de_DE.json index 83a3f15a92c0a..c0f6e6874ca6f 100644 --- a/core/l10n/de_DE.json +++ b/core/l10n/de_DE.json @@ -23,6 +23,7 @@ "State token does not match" : "Status-Token stimmen nicht überein", "Invalid app password" : "Ungültiges App-Passwort", "Could not complete login" : "Anmeldung konnte nicht abgeschlossen werden", + "State token missing" : "Zustandstoken fehlt", "Your login token is invalid or has expired" : "Ihr Anmelde-Token ist ungültig oder abgelaufen", "This community release of Nextcloud is unsupported and push notifications are limited." : "Diese Community-Version von Nextcloud wird nicht unterstützt und sofortige Benachrichtigungen sind nur begrenzt verfügbar.", "Login" : "Anmelden", diff --git a/core/l10n/en_GB.js b/core/l10n/en_GB.js index 6e85299142292..209b82238e657 100644 --- a/core/l10n/en_GB.js +++ b/core/l10n/en_GB.js @@ -25,6 +25,7 @@ OC.L10N.register( "State token does not match" : "State token does not match", "Invalid app password" : "Invalid app password", "Could not complete login" : "Could not complete login", + "State token missing" : "State token missing", "Your login token is invalid or has expired" : "Your login token is invalid or has expired", "This community release of Nextcloud is unsupported and push notifications are limited." : "This community release of Nextcloud is unsupported and push notifications are limited.", "Login" : "Login", diff --git a/core/l10n/en_GB.json b/core/l10n/en_GB.json index b34b209c73f59..ae229225a4248 100644 --- a/core/l10n/en_GB.json +++ b/core/l10n/en_GB.json @@ -23,6 +23,7 @@ "State token does not match" : "State token does not match", "Invalid app password" : "Invalid app password", "Could not complete login" : "Could not complete login", + "State token missing" : "State token missing", "Your login token is invalid or has expired" : "Your login token is invalid or has expired", "This community release of Nextcloud is unsupported and push notifications are limited." : "This community release of Nextcloud is unsupported and push notifications are limited.", "Login" : "Login", diff --git a/core/l10n/es.js b/core/l10n/es.js index abfcae3902e2e..41560bc0b6f6f 100644 --- a/core/l10n/es.js +++ b/core/l10n/es.js @@ -25,6 +25,7 @@ OC.L10N.register( "State token does not match" : "El token dado no coincide", "Invalid app password" : "Contraseña de la app no válida", "Could not complete login" : "No se ha podido completar el inicio de sesión", + "State token missing" : "No se encontró token de estado", "Your login token is invalid or has expired" : "Tu token de login no es válido o ha caducado", "This community release of Nextcloud is unsupported and push notifications are limited." : "Esta versión comunitaria de Nextcloud ya no está soportada y las notificaciones push son limitadas.", "Login" : "Iniciar sesión", diff --git a/core/l10n/es.json b/core/l10n/es.json index 2e44acb3a0489..40dfad4ecdf22 100644 --- a/core/l10n/es.json +++ b/core/l10n/es.json @@ -23,6 +23,7 @@ "State token does not match" : "El token dado no coincide", "Invalid app password" : "Contraseña de la app no válida", "Could not complete login" : "No se ha podido completar el inicio de sesión", + "State token missing" : "No se encontró token de estado", "Your login token is invalid or has expired" : "Tu token de login no es válido o ha caducado", "This community release of Nextcloud is unsupported and push notifications are limited." : "Esta versión comunitaria de Nextcloud ya no está soportada y las notificaciones push son limitadas.", "Login" : "Iniciar sesión", diff --git a/core/l10n/fr.js b/core/l10n/fr.js index 77c6b06ec8ccb..e13a6c79852a2 100644 --- a/core/l10n/fr.js +++ b/core/l10n/fr.js @@ -25,6 +25,7 @@ OC.L10N.register( "State token does not match" : "Les jetons de statut ne correspondent pas", "Invalid app password" : "Mot de passe d'application non valide", "Could not complete login" : "Impossible de terminer la connexion", + "State token missing" : "Jeton d'état manquant", "Your login token is invalid or has expired" : "Votre jeton de connexion est invalide ou a expiré", "This community release of Nextcloud is unsupported and push notifications are limited." : "Cette version communautaire de Nextcloud n'est pas prise en charge et les notifications push sont limitées.", "Login" : "S'identifier", diff --git a/core/l10n/fr.json b/core/l10n/fr.json index 8174e450c769e..4ff9cdf1ddd73 100644 --- a/core/l10n/fr.json +++ b/core/l10n/fr.json @@ -23,6 +23,7 @@ "State token does not match" : "Les jetons de statut ne correspondent pas", "Invalid app password" : "Mot de passe d'application non valide", "Could not complete login" : "Impossible de terminer la connexion", + "State token missing" : "Jeton d'état manquant", "Your login token is invalid or has expired" : "Votre jeton de connexion est invalide ou a expiré", "This community release of Nextcloud is unsupported and push notifications are limited." : "Cette version communautaire de Nextcloud n'est pas prise en charge et les notifications push sont limitées.", "Login" : "S'identifier", diff --git a/core/l10n/pt_BR.js b/core/l10n/pt_BR.js index e1621f75a939e..c21a01c2369f1 100644 --- a/core/l10n/pt_BR.js +++ b/core/l10n/pt_BR.js @@ -25,6 +25,7 @@ OC.L10N.register( "State token does not match" : "O estado do token não coincide", "Invalid app password" : "Senha do aplicativo inválida", "Could not complete login" : "Não foi possível concluir o login", + "State token missing" : "State token missing", "Your login token is invalid or has expired" : "Seu token de login é inválido ou expirou", "This community release of Nextcloud is unsupported and push notifications are limited." : "Esta versão da comunidade do Nextcloud não é compatível e as notificações por push são limitadas.", "Login" : "Entrar", diff --git a/core/l10n/pt_BR.json b/core/l10n/pt_BR.json index 837a19f9858f3..4fb62d1b1b243 100644 --- a/core/l10n/pt_BR.json +++ b/core/l10n/pt_BR.json @@ -23,6 +23,7 @@ "State token does not match" : "O estado do token não coincide", "Invalid app password" : "Senha do aplicativo inválida", "Could not complete login" : "Não foi possível concluir o login", + "State token missing" : "State token missing", "Your login token is invalid or has expired" : "Seu token de login é inválido ou expirou", "This community release of Nextcloud is unsupported and push notifications are limited." : "Esta versão da comunidade do Nextcloud não é compatível e as notificações por push são limitadas.", "Login" : "Entrar", diff --git a/core/l10n/tr.js b/core/l10n/tr.js index bc7d4bd3962d8..7e37270156a54 100644 --- a/core/l10n/tr.js +++ b/core/l10n/tr.js @@ -25,6 +25,7 @@ OC.L10N.register( "State token does not match" : "Durum kodu eşleşmiyor", "Invalid app password" : "Uygulama parolası geçersiz", "Could not complete login" : "Oturum açılamadı", + "State token missing" : "Durum kodu eksik", "Your login token is invalid or has expired" : "Oturum açma kodunuz geçersiz ya da geçerlilik süresi dolmuş", "This community release of Nextcloud is unsupported and push notifications are limited." : "Bu Nextcloud topluluk sürümü desteklenmiyor ve anlık bildirimler sınırlı şekilde kullanılabiliyor.", "Login" : "Oturum aç", diff --git a/core/l10n/tr.json b/core/l10n/tr.json index 40e131f91cf2a..a5501e7709ce0 100644 --- a/core/l10n/tr.json +++ b/core/l10n/tr.json @@ -23,6 +23,7 @@ "State token does not match" : "Durum kodu eşleşmiyor", "Invalid app password" : "Uygulama parolası geçersiz", "Could not complete login" : "Oturum açılamadı", + "State token missing" : "Durum kodu eksik", "Your login token is invalid or has expired" : "Oturum açma kodunuz geçersiz ya da geçerlilik süresi dolmuş", "This community release of Nextcloud is unsupported and push notifications are limited." : "Bu Nextcloud topluluk sürümü desteklenmiyor ve anlık bildirimler sınırlı şekilde kullanılabiliyor.", "Login" : "Oturum aç", diff --git a/core/l10n/zh_HK.js b/core/l10n/zh_HK.js index eec324801950a..e12a25f8bad31 100644 --- a/core/l10n/zh_HK.js +++ b/core/l10n/zh_HK.js @@ -25,6 +25,7 @@ OC.L10N.register( "State token does not match" : "狀態權杖不相符", "Invalid app password" : "無效的應用程式密碼", "Could not complete login" : "無法完成登錄", + "State token missing" : "找不到狀態權杖", "Your login token is invalid or has expired" : "您的登入權杖無效或已過期", "This community release of Nextcloud is unsupported and push notifications are limited." : "不支援 Nextcloud 的這個社群版本,且推播通知有所限制。", "Login" : "登入", diff --git a/core/l10n/zh_HK.json b/core/l10n/zh_HK.json index d619a2d13c425..5c1a3cbc09252 100644 --- a/core/l10n/zh_HK.json +++ b/core/l10n/zh_HK.json @@ -23,6 +23,7 @@ "State token does not match" : "狀態權杖不相符", "Invalid app password" : "無效的應用程式密碼", "Could not complete login" : "無法完成登錄", + "State token missing" : "找不到狀態權杖", "Your login token is invalid or has expired" : "您的登入權杖無效或已過期", "This community release of Nextcloud is unsupported and push notifications are limited." : "不支援 Nextcloud 的這個社群版本,且推播通知有所限制。", "Login" : "登入", diff --git a/core/l10n/zh_TW.js b/core/l10n/zh_TW.js index 6f5a76d85c696..06af77863ea1a 100644 --- a/core/l10n/zh_TW.js +++ b/core/l10n/zh_TW.js @@ -25,6 +25,7 @@ OC.L10N.register( "State token does not match" : "狀態權杖不相符", "Invalid app password" : "無效的應用程式密碼", "Could not complete login" : "嘗試登入時發生錯誤", + "State token missing" : "狀態權杖遺失", "Your login token is invalid or has expired" : "您的登入權杖無效或已過期", "This community release of Nextcloud is unsupported and push notifications are limited." : "不支援 Nextcloud 的這個社群版本,且推播通知有所限制。", "Login" : "登入", diff --git a/core/l10n/zh_TW.json b/core/l10n/zh_TW.json index 7a29343ec6786..9ac3c2688ef16 100644 --- a/core/l10n/zh_TW.json +++ b/core/l10n/zh_TW.json @@ -23,6 +23,7 @@ "State token does not match" : "狀態權杖不相符", "Invalid app password" : "無效的應用程式密碼", "Could not complete login" : "嘗試登入時發生錯誤", + "State token missing" : "狀態權杖遺失", "Your login token is invalid or has expired" : "您的登入權杖無效或已過期", "This community release of Nextcloud is unsupported and push notifications are limited." : "不支援 Nextcloud 的這個社群版本,且推播通知有所限制。", "Login" : "登入", diff --git a/core/src/OC/dialogs.js b/core/src/OC/dialogs.js index 286f98482908d..f49d5cd4fa029 100644 --- a/core/src/OC/dialogs.js +++ b/core/src/OC/dialogs.js @@ -427,7 +427,7 @@ const Dialogs = { if (checkInput()) { var newname = $input.val() self.filepicker.filesClient.createDirectory(self.$filePicker.data('path') + "/" + newname).always(function (status) { - self._fillFilePicker(self.$filePicker.data('path') + "/" + newname) + self._fillFilePicker(self.$filePicker.data('path') + "/" + newname, type) }) OC.hideMenus() self.$filePicker.ocdialog('unsetEnterCallback') @@ -459,10 +459,10 @@ const Dialogs = { var dir = self.$filePicker.data('path') self.filepicker.sortField = $(event.currentTarget).data('sort') self.filepicker.sortOrder = self.filepicker.sortOrder === 'asc' ? 'desc' : 'asc' - self._fillFilePicker(dir) + self._fillFilePicker(dir, type) } }) - self._fillFilePicker(path) + self._fillFilePicker(path, type) }) // build buttons @@ -1120,7 +1120,7 @@ const Dialogs = { /** * fills the filepicker with files */ - _fillFilePicker: async function(dir) { + _fillFilePicker: async function(dir, type) { var self = this this.$filelist.empty() this.$filePicker.find('.emptycontent').hide() @@ -1155,6 +1155,7 @@ const Dialogs = { console.error('Requested path does not exists, falling back to root') var files = await getFolderContents('/') this.$filePicker.data('path', '/') + this._changeButtonsText(type, '') } self.filelist = files @@ -1320,7 +1321,7 @@ const Dialogs = { _handleTreeListSelect: function(event, type) { var self = event.data var dir = $(event.target).closest('.crumb').data('dir') - self._fillFilePicker(dir) + self._fillFilePicker(dir, type) var getOcDialog = (event.target).closest('.oc-dialog') var buttonEnableDisable = $('.primary', getOcDialog) this._changeButtonsText(type, dir.split(/[/]+/).pop()) @@ -1343,7 +1344,7 @@ const Dialogs = { $element.toggleClass('filepicker_element_selected') buttonEnableDisable.prop('disabled', false) } else if ($element.data('type') === 'dir') { - this._fillFilePicker(this.$filePicker.data('path') + '/' + $element.data('entryname')) + this._fillFilePicker(this.$filePicker.data('path') + '/' + $element.data('entryname'), type) this._changeButtonsText(type, $element.data('entryname')) if (this.$filePicker.data('mimetype').indexOf('httpd/unix-directory') !== -1 || this.$filePicker.data('allowDirectoryChooser')) { buttonEnableDisable.prop('disabled', false) diff --git a/core/templates/loginflow/authpicker.php b/core/templates/loginflow/authpicker.php index 68c53818a9236..3f78081921d3e 100644 --- a/core/templates/loginflow/authpicker.php +++ b/core/templates/loginflow/authpicker.php @@ -47,7 +47,7 @@
- +

diff --git a/core/templates/loginflowv2/authpicker.php b/core/templates/loginflowv2/authpicker.php index b7ff617bd309c..0e18cc99ce15f 100644 --- a/core/templates/loginflowv2/authpicker.php +++ b/core/templates/loginflowv2/authpicker.php @@ -47,7 +47,7 @@
- +

diff --git a/dist/core-login.js b/dist/core-login.js index f2a38ad17e8e2..c251330dff1f1 100644 --- a/dist/core-login.js +++ b/dist/core-login.js @@ -1,3 +1,3 @@ /*! For license information please see core-login.js.LICENSE.txt */ -!function(){var e,o={99458:function(e,o,r){"use strict";var i=r(20144),a=r(78595),s=r(19755),l=r.n(s),c=r(79753),u={},d=[],f=r(18181),p=r(26932),h={updatableNotification:null,getDefaultNotificationFunction:null,setDefault:function(t){this.getDefaultNotificationFunction=t},hide:function(t,e){f.ZP.isFunction(t)&&(e=t,t=void 0),t?(t.each((function(){l()(this)[0].toastify?l()(this)[0].toastify.hideToast():console.error("cannot hide toast because object is not set"),this===this.updatableNotification&&(this.updatableNotification=null)})),e&&e.call(),this.getDefaultNotificationFunction&&this.getDefaultNotificationFunction()):console.error("Missing argument $row in OC.Notification.hide() call, caller needs to be adjusted to only dismiss its own notification")},showHtml:function(t,e){(e=e||{}).isHTML=!0,e.timeout=e.timeout?e.timeout:p.Rl;var n=(0,p.PV)(t,e);return n.toastElement.toastify=n,l()(n.toastElement)},show:function(t,e){(e=e||{}).timeout=e.timeout?e.timeout:p.Rl;var n=(0,p.PV)(function(t){return t.toString().split("&").join("&").split("<").join("<").split(">").join(">").split('"').join(""").split("'").join("'")}(t),e);return n.toastElement.toastify=n,l()(n.toastElement)},showUpdate:function(t){return this.updatableNotification&&this.updatableNotification.hideToast(),this.updatableNotification=(0,p.PV)(t,{timeout:p.Rl}),this.updatableNotification.toastElement.toastify=this.updatableNotification,l()(this.updatableNotification.toastElement)},showTemporary:function(t,e){(e=e||{}).timeout=e.timeout||p.TN;var n=(0,p.PV)(t,e);return n.toastElement.toastify=n,l()(n.toastElement)},isHidden:function(){return!l()("#content").find(".toastify").length}},m=f.ZP.throttle((function(){h.showTemporary(t("core","Connection to server lost"))}),7e3,{trailing:!1}),g={enableDynamicSlideToggle:function(){},showAppSidebar:function(t){(t||l()("#app-sidebar")).removeClass("disappear").show(),l()("#app-content").trigger(new(l().Event)("appresized"))},hideAppSidebar:function(t){(t||l()("#app-sidebar")).hide().addClass("disappear"),l()("#app-content").trigger(new(l().Event)("appresized"))}};function v(t,e,n){"post"!==t&&"delete"!==t||!Mt.PasswordConfirmation.requiresPasswordConfirmation()?(n=n||{},l().ajax({type:t.toUpperCase(),url:(0,c.generateOcsUrl)("apps/provisioning_api/api/v1/config/apps")+e,data:n.data||{},success:n.success,error:n.error})):Mt.PasswordConfirmation.requirePasswordConfirmation(_.bind(v,this,t,e,n))}var y=window.oc_appconfig||{},w={getValue:function(t,e,n,o){!function(t,e,n,o){(o=o||{}).data={defaultValue:n},v("get","/"+t+"/"+e,o)}(t,e,n,{success:o})},setValue:function(t,e,n){!function(t,e,n,o){(o=o||{}).data={value:n},v("post","/"+t+"/"+e,o)}(t,e,n)},getApps:function(t){!function(t){v("get","",t)}({success:t})},getKeys:function(t,e){!function(t,e){v("get","/"+t,e)}(t,{success:e})},deleteKey:function(t,e){!function(t,e,n){v("delete","/"+t+"/"+e,void 0)}(t,e)}},b=void 0!==window._oc_appswebroots&&window._oc_appswebroots,P=r(72316),C=r.n(P),A=r(76591),k={create:"POST",update:"PROPPATCH",patch:"PROPPATCH",delete:"DELETE",read:"PROPFIND"};function x(t,e){if(f.ZP.isArray(t))return f.ZP.map(t,(function(t){return x(t,e)}));var n={href:t.href};return f.ZP.each(t.propStat,(function(t){if("HTTP/1.1 200 OK"===t.status)for(var o in t.properties){var r=o;o in e&&(r=e[o]),n[r]=t.properties[o]}})),n.id||(n.id=O(n.href)),n}function O(t){var e=t.indexOf("?");e>0&&(t=t.substr(0,e));var n,o=t.split("/");do{n=o[o.length-1],o.pop()}while(!n&&o.length>0);return n}function T(t){return t>=200&&t<=299}function j(t,e,n,o){return t.propPatch(e.url,function(t,e){var n,o={};for(n in t){var r=e[n],i=t[n];r||(console.warn('No matching DAV property for property "'+n),r=n),(f.ZP.isBoolean(i)||f.ZP.isNumber(i))&&(i=""+i),o[r]=i}return o}(n.changed,e.davProperties),o).then((function(t){T(t.status)?f.ZP.isFunction(e.success)&&e.success(n.toJSON()):f.ZP.isFunction(e.error)&&e.error(t)}))}var E=C().noConflict();Object.assign(E,{davCall:function(t,e){var n=new A.dav.Client({baseUrl:t.url,xmlNamespaces:f.ZP.extend({"DAV:":"d","http://owncloud.org/ns":"oc"},t.xmlNamespaces||{})});n.resolveUrl=function(){return t.url};var o=f.ZP.extend({"X-Requested-With":"XMLHttpRequest",requesttoken:OC.requestToken},t.headers);return"PROPFIND"===t.type?function(t,e,n,o){return t.propFind(e.url,f.ZP.values(e.davProperties)||[],e.depth,o).then((function(t){if(T(t.status)){if(f.ZP.isFunction(e.success)){var n=f.ZP.invert(e.davProperties),o=x(t.body,n);e.depth>0&&o.shift(),e.success(o)}}else f.ZP.isFunction(e.error)&&e.error(t)}))}(n,t,0,o):"PROPPATCH"===t.type?j(n,t,e,o):"MKCOL"===t.type?function(t,e,n,o){return t.request(e.type,e.url,o,null).then((function(r){T(r.status)?j(t,e,n,o):f.ZP.isFunction(e.error)&&e.error(r)}))}(n,t,e,o):function(t,e,n,o){return o["Content-Type"]="application/json",t.request(e.type,e.url,o,e.data).then((function(t){if(T(t.status)){if(f.ZP.isFunction(e.success)){if("PUT"===e.type||"POST"===e.type||"MKCOL"===e.type){var o=t.body||n.toJSON(),r=t.xhr.getResponseHeader("Content-Location");return"POST"===e.type&&r&&(o.id=O(r)),void e.success(o)}if(207===t.status){var i=f.ZP.invert(e.davProperties);e.success(x(t.body,i))}else e.success(t.body)}}else f.ZP.isFunction(e.error)&&e.error(t)}))}(n,t,e,o)},davSync:function(t){return function(e,n,o){var r={type:k[e]||e},i=n instanceof t.Collection;if("update"===e&&(n.hasInnerCollection?r.type="MKCOL":(n.usePUT||n.collection&&n.collection.usePUT)&&(r.type="PUT")),o.url||(r.url=f.ZP.result(n,"url")||function(){throw new Error('A "url" property or function must be specified')}()),null!=o.data||!n||"create"!==e&&"update"!==e&&"patch"!==e||(r.data=JSON.stringify(o.attrs||n.toJSON(o))),"PROPFIND"!==r.type&&(r.processData=!1),"PROPFIND"===r.type||"PROPPATCH"===r.type){var a=n.davProperties;!a&&n.model&&(a=n.model.prototype.davProperties),a&&(f.ZP.isFunction(a)?r.davProperties=a.call(n):r.davProperties=a),r.davProperties=f.ZP.extend(r.davProperties||{},o.davProperties),f.ZP.isUndefined(o.depth)&&(o.depth=i?1:0)}var s=o.error;o.error=function(t,e,n){o.textStatus=e,o.errorThrown=n,s&&s.call(o.context,t,e,n)};var l=o.xhr=t.davCall(f.ZP.extend(r,o),n);return n.trigger("request",n,l,o),l}}(E)});var S=E,L=r(65358),N=window._oc_config||{},I=P.Model.extend({defaults:{fullName:"",lastMessage:"",actions:[],hasOneAction:!1,hasTwoActions:!1,hasManyActions:!1},initialize:function(){0===this.get("actions").length?this.set("hasOneAction",!0):1===this.get("actions").length?(this.set("hasTwoActions",!0),this.set("secondAction",this.get("actions")[0])):this.set("hasManyActions",!0)}}),U=P.Collection.extend({model:I}),$=P.View.extend({_collection:void 0,_subViews:[],initialize:function(t){this._collection=t.collection},render:function(){var t=this;return t.$el.html(""),t._subViews=[],t._collection.forEach((function(e){var n=new F({model:e});n.render(),t.$el.append(n.$el),n.on("toggle:actionmenu",t._onChildActionMenuToggle,t),t._subViews.push(n)})),t},_onChildActionMenuToggle:function(t){this._subViews.forEach((function(e){e.trigger("parent:toggle:actionmenu",t)}))}}),F=P.View.extend({className:"contact",_template:void 0,_model:void 0,_actionMenuShown:!1,events:{"click .icon-more":"_onToggleActionsMenu"},contactTemplate:r(10944),template:function(t){return this.contactTemplate(t)},initialize:function(t){this._model=t.model,this.on("parent:toggle:actionmenu",this._onOtherActionMenuOpened,this)},render:function(){return this.$el.html(this.template({contact:this._model.toJSON()})),this.delegateEvents(),this.$("div.avatar").imageplaceholder(this._model.get("fullName")),this.$(".top-action").tooltip({placement:"left"}),this.$(".second-action").tooltip({placement:"left"}),this},_onToggleActionsMenu:function(){this._actionMenuShown=!this._actionMenuShown,this._actionMenuShown?this.$(".menu").show():this.$(".menu").hide(),this.trigger("toggle:actionmenu",this.$el)},_onOtherActionMenuOpened:function(t){this.$el.is(t)||(this._actionMenuShown=!1,this.$(".menu").hide())}}),R=P.View.extend({_loadingTemplate:void 0,_errorTemplate:void 0,_contentTemplate:void 0,_contactsTemplate:void 0,_contacts:void 0,_searchTerm:"",events:{"input #contactsmenu-search":"_onSearch"},templates:{loading:r(95386),error:r(20421),menu:r(66115),list:r(34083)},_onSearch:f.ZP.debounce((function(t){var e=this.$("#contactsmenu-search").val();e!==this._searchTerm&&(this.trigger("search",this.$("#contactsmenu-search").val()),this._searchTerm=e)}),700),loadingTemplate:function(t){return this.templates.loading(t)},errorTemplate:function(e){return this.templates.error(f.ZP.extend({couldNotLoadText:t("core","Could not load your contacts")},e))},contentTemplate:function(e){return this.templates.menu(f.ZP.extend({searchContactsText:t("core","Search contacts …")},e))},contactsTemplate:function(e){return this.templates.list(f.ZP.extend({noContactsFoundText:t("core","No contacts found"),showAllContactsText:t("core","Show all contacts …"),contactsAppMgmtText:t("core","Install the Contacts app")},e))},initialize:function(t){this.options=t},showLoading:function(t){this.render(),this._contacts=void 0,this.$(".content").html(this.loadingTemplate({loadingText:t}))},showError:function(){this.render(),this._contacts=void 0,this.$(".content").html(this.errorTemplate())},showContacts:function(t,e){this._contacts=t.contacts,this.render({contacts:t.contacts});var n=new $({collection:t.contacts});n.render(),this.$(".content").html(this.contactsTemplate({contacts:t.contacts,searchTerm:e,contactsAppEnabled:t.contactsAppEnabled,contactsAppURL:Mt.generateUrl("/apps/contacts"),canInstallApp:Mt.isUserAdmin(),contactsAppMgmtURL:Mt.generateUrl("/settings/apps/social/contacts")})),this.$("#contactsmenu-contacts").html(n.$el)},render:function(t){var e=this.$("#contactsmenu-search").val();return this.$el.html(this.contentTemplate(t)),this.$("#contactsmenu-search").val(e),this.$("#contactsmenu-search").focus(),this}}),M=function(t){this.initialize(t)};M.prototype={$el:void 0,_view:void 0,_contactsPromise:void 0,initialize:function(t){this.$el=l()(t.el),this._view=new R({el:this.$el}),this._view.on("search",(function(t){this.loadContacts(t)}),this)},_getContacts:function(t){var e=Mt.generateUrl("/contactsmenu/contacts");return Promise.resolve(l().ajax(e,{method:"POST",data:{filter:t}}))},loadContacts:function(e){var n=this;return n._contactsPromise||(n._contactsPromise=n._getContacts(e)),f.ZP.isUndefined(e)||""===e?n._view.showLoading(t("core","Loading your contacts …")):n._view.showLoading(t("core","Looking for {term} …",{term:e})),n._contactsPromise.then((function(t){t.contacts=new U(t.contacts),n._view.showContacts(t,e)}),(function(t){n._view.showError(),console.error("There was an error loading your contacts",t)})).then((function(){delete n._contactsPromise})).catch(console.error.bind(this))}};var z=M,B=document.getElementsByTagName("head")[0].getAttribute("data-user"),D=document.getElementsByTagName("head")[0].getAttribute("data-user-displayname"),Z=void 0!==B&&B;function q(t){return q="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},q(t)}function Y(t,e){for(var n=0;n");p.attr("type",a?"password":"text").attr("id",c+"-input").attr("placeholder",i);var h=l()("