feat: add example contact on first login

Signed-off-by: Hamza Mahjoubi <hamzamahjoubi221@gmail.com>
This commit is contained in:
Hamza Mahjoubi 2025-02-07 20:47:09 +07:00
parent c85b8aa36c
commit 099d9fb9be
132 changed files with 4310 additions and 162 deletions

View file

@ -167,6 +167,10 @@ Files: apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-1.ics app
Copyright: 2023 Nextcloud GmbH and Nextcloud contributors
License: AGPL-3.0-or-later
Files: apps/dav/lib/ExampleContentFiles/exampleContact.vcf
Copyright: 2025 Nextcloud GmbH and Nextcloud contributors
License: AGPL-3.0-or-later
Files: composer.json composer.lock .github/CODEOWNERS __tests__/tsconfig.json tsconfig.json build/integration/composer.* vendor-bin/*/composer.json vendor-bin/*/composer.lock apps/*/composer/composer.json apps/*/composer/composer.lock apps/*/composer/composer/installed.json
Copyright: 2011-2016 ownCloud, Inc., 2016-2024 Nextcloud GmbH and Nextcloud contributors
License: AGPL-3.0-only OR AGPL-3.0-or-later

View file

@ -74,6 +74,7 @@
<settings>
<admin>OCA\DAV\Settings\CalDAVSettings</admin>
<admin>OCA\DAV\Settings\ExampleContentSettings</admin>
<personal>OCA\DAV\Settings\AvailabilitySettings</personal>
</settings>

View file

@ -11,6 +11,8 @@ return [
['name' => 'invitation_response#decline', 'url' => '/invitation/decline/{token}', 'verb' => 'GET'],
['name' => 'invitation_response#options', 'url' => '/invitation/moreOptions/{token}', 'verb' => 'GET'],
['name' => 'invitation_response#processMoreOptionsResult', 'url' => '/invitation/moreOptions/{token}', 'verb' => 'POST'],
['name' => 'example_content#setDefaultContact', 'url' => '/api/defaultcontact/contact', 'verb' => 'PUT'],
['name' => 'example_content#setEnableDefaultContact', 'url' => '/api/defaultcontact/config', 'verb' => 'PUT'],
],
'ocs' => [
['name' => 'direct#getUrl', 'url' => '/api/v1/direct', 'verb' => 'POST'],

View file

@ -221,6 +221,7 @@ return array(
'OCA\\DAV\\Connector\\Sabre\\ZipFolderPlugin' => $baseDir . '/../lib/Connector/Sabre/ZipFolderPlugin.php',
'OCA\\DAV\\Controller\\BirthdayCalendarController' => $baseDir . '/../lib/Controller/BirthdayCalendarController.php',
'OCA\\DAV\\Controller\\DirectController' => $baseDir . '/../lib/Controller/DirectController.php',
'OCA\\DAV\\Controller\\ExampleContentController' => $baseDir . '/../lib/Controller/ExampleContentController.php',
'OCA\\DAV\\Controller\\InvitationResponseController' => $baseDir . '/../lib/Controller/InvitationResponseController.php',
'OCA\\DAV\\Controller\\OutOfOfficeController' => $baseDir . '/../lib/Controller/OutOfOfficeController.php',
'OCA\\DAV\\Controller\\UpcomingEventsController' => $baseDir . '/../lib/Controller/UpcomingEventsController.php',
@ -355,9 +356,11 @@ return array(
'OCA\\DAV\\Server' => $baseDir . '/../lib/Server.php',
'OCA\\DAV\\ServerFactory' => $baseDir . '/../lib/ServerFactory.php',
'OCA\\DAV\\Service\\AbsenceService' => $baseDir . '/../lib/Service/AbsenceService.php',
'OCA\\DAV\\Service\\DefaultContactService' => $baseDir . '/../lib/Service/DefaultContactService.php',
'OCA\\DAV\\Settings\\Admin\\SystemAddressBookSettings' => $baseDir . '/../lib/Settings/Admin/SystemAddressBookSettings.php',
'OCA\\DAV\\Settings\\AvailabilitySettings' => $baseDir . '/../lib/Settings/AvailabilitySettings.php',
'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php',
'OCA\\DAV\\Settings\\ExampleContentSettings' => $baseDir . '/../lib/Settings/ExampleContentSettings.php',
'OCA\\DAV\\SetupChecks\\NeedsSystemAddressBookSync' => $baseDir . '/../lib/SetupChecks/NeedsSystemAddressBookSync.php',
'OCA\\DAV\\SetupChecks\\WebdavEndpoint' => $baseDir . '/../lib/SetupChecks/WebdavEndpoint.php',
'OCA\\DAV\\Storage\\PublicOwnerWrapper' => $baseDir . '/../lib/Storage/PublicOwnerWrapper.php',

View file

@ -236,6 +236,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Connector\\Sabre\\ZipFolderPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/ZipFolderPlugin.php',
'OCA\\DAV\\Controller\\BirthdayCalendarController' => __DIR__ . '/..' . '/../lib/Controller/BirthdayCalendarController.php',
'OCA\\DAV\\Controller\\DirectController' => __DIR__ . '/..' . '/../lib/Controller/DirectController.php',
'OCA\\DAV\\Controller\\ExampleContentController' => __DIR__ . '/..' . '/../lib/Controller/ExampleContentController.php',
'OCA\\DAV\\Controller\\InvitationResponseController' => __DIR__ . '/..' . '/../lib/Controller/InvitationResponseController.php',
'OCA\\DAV\\Controller\\OutOfOfficeController' => __DIR__ . '/..' . '/../lib/Controller/OutOfOfficeController.php',
'OCA\\DAV\\Controller\\UpcomingEventsController' => __DIR__ . '/..' . '/../lib/Controller/UpcomingEventsController.php',
@ -370,9 +371,11 @@ class ComposerStaticInitDAV
'OCA\\DAV\\Server' => __DIR__ . '/..' . '/../lib/Server.php',
'OCA\\DAV\\ServerFactory' => __DIR__ . '/..' . '/../lib/ServerFactory.php',
'OCA\\DAV\\Service\\AbsenceService' => __DIR__ . '/..' . '/../lib/Service/AbsenceService.php',
'OCA\\DAV\\Service\\DefaultContactService' => __DIR__ . '/..' . '/../lib/Service/DefaultContactService.php',
'OCA\\DAV\\Settings\\Admin\\SystemAddressBookSettings' => __DIR__ . '/..' . '/../lib/Settings/Admin/SystemAddressBookSettings.php',
'OCA\\DAV\\Settings\\AvailabilitySettings' => __DIR__ . '/..' . '/../lib/Settings/AvailabilitySettings.php',
'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php',
'OCA\\DAV\\Settings\\ExampleContentSettings' => __DIR__ . '/..' . '/../lib/Settings/ExampleContentSettings.php',
'OCA\\DAV\\SetupChecks\\NeedsSystemAddressBookSync' => __DIR__ . '/..' . '/../lib/SetupChecks/NeedsSystemAddressBookSync.php',
'OCA\\DAV\\SetupChecks\\WebdavEndpoint' => __DIR__ . '/..' . '/../lib/SetupChecks/WebdavEndpoint.php',
'OCA\\DAV\\Storage\\PublicOwnerWrapper' => __DIR__ . '/..' . '/../lib/Storage/PublicOwnerWrapper.php',

View file

@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\DAV\Controller;
use OCA\DAV\AppInfo\Application;
use OCP\App\IAppManager;
use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
use OCP\Files\AppData\IAppDataFactory;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\IRequest;
use Psr\Log\LoggerInterface;
class ExampleContentController extends ApiController {
private IAppData $appData;
public function __construct(
IRequest $request,
private IConfig $config,
private IAppDataFactory $appDataFactory,
private IAppManager $appManager,
private LoggerInterface $logger,
) {
parent::__construct(Application::APP_ID, $request);
$this->appData = $this->appDataFactory->get('dav');
}
public function setEnableDefaultContact($allow) {
if ($allow === 'yes' && !$this->defaultContactExists()) {
try {
$this->setCard();
} catch (\Exception $e) {
$this->logger->error('Could not create default contact', ['exception' => $e]);
return new JSONResponse([], Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
$this->config->setAppValue(Application::APP_ID, 'enableDefaultContact', $allow);
return new JSONResponse([], Http::STATUS_OK);
}
public function setDefaultContact(?string $contactData = null) {
if (!$this->config->getAppValue(Application::APP_ID, 'enableDefaultContact', 'no')) {
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
$this->setCard($contactData);
return new JSONResponse([], Http::STATUS_OK);
}
private function setCard(?string $cardData = null) {
try {
$folder = $this->appData->getFolder('defaultContact');
} catch (NotFoundException $e) {
$folder = $this->appData->newFolder('defaultContact');
}
if (is_null($cardData)) {
$cardData = file_get_contents(__DIR__ . '/../ExampleContentFiles/exampleContact.vcf');
}
if (!$cardData) {
throw new \Exception('Could not read exampleContact.vcf');
}
$file = (!$folder->fileExists('defaultContact.vcf')) ? $folder->newFile('defaultContact.vcf') : $folder->getFile('defaultContact.vcf');
$file->putContent($cardData);
}
private function defaultContactExists(): bool {
try {
$folder = $this->appData->getFolder('defaultContact');
} catch (NotFoundException $e) {
return false;
}
return $folder->fileExists('defaultContact.vcf');
}
}

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,7 @@ namespace OCA\DAV\Listener;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\CardDAV\SyncService;
use OCA\DAV\Service\DefaultContactService;
use OCP\Defaults;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
@ -44,6 +45,7 @@ class UserEventsListener implements IEventListener {
private CalDavBackend $calDav,
private CardDavBackend $cardDav,
private Defaults $themingDefaults,
private DefaultContactService $defaultContactService,
) {
}
@ -139,14 +141,18 @@ class UserEventsListener implements IEventListener {
Server::get(LoggerInterface::class)->error($e->getMessage(), ['exception' => $e]);
}
}
$addressBookId = null;
if ($this->cardDav->getAddressBooksForUserCount($principal) === 0) {
try {
$this->cardDav->createAddressBook($principal, CardDavBackend::PERSONAL_ADDRESSBOOK_URI, [
$addressBookId = $this->cardDav->createAddressBook($principal, CardDavBackend::PERSONAL_ADDRESSBOOK_URI, [
'{DAV:}displayname' => CardDavBackend::PERSONAL_ADDRESSBOOK_NAME,
]);
} catch (\Exception $e) {
Server::get(LoggerInterface::class)->error($e->getMessage(), ['exception' => $e]);
}
}
if ($addressBookId) {
$this->defaultContactService->createDefaultContact($addressBookId);
}
}
}

View file

@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\DAV\Service;
use OCA\DAV\CardDAV\CardDavBackend;
use OCP\App\IAppManager;
use OCP\Files\AppData\IAppDataFactory;
use Psr\Log\LoggerInterface;
use Symfony\Component\Uid\Uuid;
class DefaultContactService {
public function __construct(
private CardDavBackend $cardDav,
private IAppManager $appManager,
private IAppDataFactory $appDataFactory,
private LoggerInterface $logger,
) {
}
public function createDefaultContact(int $addressBookId): void {
$appData = $this->appDataFactory->get('dav');
try {
$folder = $appData->getFolder('defaultContact');
$defaultContactFile = $folder->getFile('defaultContact.vcf');
$data = $defaultContactFile->getContent();
} catch (\Exception $e) {
$this->logger->error('Couldn\'t get default contact file', ['exception' => $e]);
return;
}
// Make sure the UID is unique
$newUid = Uuid::v4()->toRfc4122();
$newRev = date('Ymd\THis\Z');
$vcard = \Sabre\VObject\Reader::read($data, \Sabre\VObject\Reader::OPTION_FORGIVING);
if ($vcard->UID) {
$vcard->UID->setValue($newUid);
} else {
$vcard->add('UID', $newUid);
}
if ($vcard->REV) {
$vcard->REV->setValue($newRev);
} else {
$vcard->add('REV', $newRev);
}
// Level 3 means that the document is invalid
// https://sabre.io/vobject/vcard/#validating-vcard
$level3Warnings = array_filter($vcard->validate(), function ($warning) {
return $warning['level'] === 3;
});
if (!empty($level3Warnings)) {
$this->logger->error('Default contact is invalid', ['warnings' => $level3Warnings]);
return;
}
try {
$this->cardDav->createCard($addressBookId, 'default', $vcard->serialize(), false);
} catch (\Exception $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
}
}
}

View file

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\DAV\Settings;
use OCA\DAV\AppInfo\Application;
use OCP\App\IAppManager;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Services\IInitialState;
use OCP\IConfig;
use OCP\Settings\ISettings;
class ExampleContentSettings implements ISettings {
public function __construct(
private IConfig $config,
private IInitialState $initialState,
private IAppManager $appManager,
) {
}
public function getForm(): TemplateResponse {
$enableDefaultContact = $this->config->getAppValue(Application::APP_ID, 'enableDefaultContact', 'no');
$this->initialState->provideInitialState('enableDefaultContact', $enableDefaultContact);
return new TemplateResponse(Application::APP_ID, 'settings-example-content');
}
public function getSection(): ?string {
if (!$this->appManager->isEnabledForUser('contacts')) {
return null;
}
return 'groupware';
}
public function getPriority(): int {
return 10;
}
}

View file

@ -0,0 +1,13 @@
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import Vue from 'vue'
import { translate } from '@nextcloud/l10n'
import ExampleContactSettings from './views/ExampleContactSettings.vue'
Vue.prototype.$t = translate
const View = Vue.extend(ExampleContactSettings);
(new View({})).$mount('#settings-example-content')

View file

@ -0,0 +1,160 @@
<!--
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
<NcSettingsSection id="exmaple-content"
:name="$t('dav', 'Example Content')"
class="example-content-setting"
:description="$t('dav', 'Set example content to be created on new user first login.')">
<div class="example-content-setting__contacts">
<input id="enable-default-contact"
v-model="enableDefaultContact"
type="checkbox"
class="checkbox"
@change="updateEnableDefaultContact">
<label for="enable-default-contact"> {{ $t('dav',"Default contact is added to the user's own address book on user's first login.") }} </label>
<div v-if="enableDefaultContact" class="example-content-setting__contacts__buttons">
<NcButton type="primary"
class="example-content-setting__contacts__buttons__button"
@click="toggleModal">
<template #icon>
<IconUpload :size="20" />
</template>
{{ $t('dav', 'Import contact') }}
</NcButton>
<NcButton type="secondary"
class="example-content-setting__contacts__buttons__button"
@click="resetContact">
<template #icon>
<IconRestore :size="20" />
</template>
{{ $t('dav', 'Reset to default contact') }}
</NcButton>
</div>
</div>
<NcDialog :open.sync="isModalOpen"
:name="$t('dav', 'Import contacts')"
:buttons="buttons">
<div>
<p>{{ $t('dav', 'Importing a new .vcf file will delete the existing default contact and replace it with the new one. Do you want to continue?') }}</p>
</div>
</NcDialog>
<input id="example-contact-import"
ref="exampleContactImportInput"
:disabled="loading"
type="file"
accept=".vcf"
class="hidden-visually"
@change="processFile">
</NcSettingsSection>
</template>
<script>
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import { NcDialog, NcButton, NcSettingsSection } from '@nextcloud/vue'
import { showError, showSuccess } from '@nextcloud/dialogs'
import IconUpload from 'vue-material-design-icons/Upload.vue'
import IconRestore from 'vue-material-design-icons/Restore.vue'
import IconCancel from '@mdi/svg/svg/cancel.svg?raw'
import IconCheck from '@mdi/svg/svg/check.svg?raw'
const enableDefaultContact = loadState('dav', 'enableDefaultContact') === 'yes'
export default {
name: 'ExampleContactSettings',
components: {
NcDialog,
NcButton,
NcSettingsSection,
IconUpload,
IconRestore,
},
data() {
return {
enableDefaultContact,
isModalOpen: false,
loading: false,
buttons: [
{
label: this.$t('dav', 'Cancel'),
icon: IconCancel,
callback: () => { this.isModalOpen = false },
},
{
label: this.$t('dav', 'Import'),
type: 'primary',
icon: IconCheck,
callback: () => { this.clickImportInput() },
},
],
}
},
methods: {
updateEnableDefaultContact() {
axios.put(generateUrl('apps/dav/api/defaultcontact/config'), {
allow: this.enableDefaultContact ? 'yes' : 'no',
}).catch(() => {
this.enableDefaultContact = !this.enableDefaultContact
showError(this.$t('dav', 'Error while saving settings'))
})
},
toggleModal() {
this.isModalOpen = !this.isModalOpen
},
clickImportInput() {
this.$refs.exampleContactImportInput.click()
},
resetContact() {
this.loading = true
axios.put(generateUrl('/apps/dav/api/defaultcontact/contact'))
.then(() => {
showSuccess(this.$t('dav', 'Contact reset successfully'))
})
.catch((error) => {
console.error('Error importing contact:', error)
showError(this.$t('dav', 'Error while resetting contact'))
})
.finally(() => {
this.loading = false
})
},
processFile(event) {
this.loading = true
const file = event.target.files[0]
const reader = new FileReader()
reader.onload = async () => {
this.isModalOpen = false
try {
await axios.put(generateUrl('/apps/dav/api/defaultcontact/contact'), { contactData: reader.result })
showSuccess(this.$t('dav', 'Contact imported successfully'))
} catch (error) {
console.error('Error importing contact:', error)
showError(this.$t('dav', 'Error while importing contact'))
} finally {
this.loading = false
event.target.value = ''
}
}
reader.readAsText(file)
},
},
}
</script>
<style lang="scss" scoped>
.example-content-setting{
&__contacts{
&__buttons{
margin-top: 1rem;
display: flex;
&__button{
margin-inline-end: 5px;
}
}
}
}
</style>

View file

@ -0,0 +1,11 @@
<?php
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
script('dav', 'settings-example-content');
?>
<div id="settings-example-content"></div>

View file

@ -14,6 +14,7 @@ use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\CardDAV\SyncService;
use OCA\DAV\Listener\UserEventsListener;
use OCA\DAV\Service\DefaultContactService;
use OCP\Defaults;
use OCP\IUser;
use OCP\IUserManager;
@ -27,6 +28,8 @@ class UserEventsListenerTest extends TestCase {
private CardDavBackend&MockObject $cardDavBackend;
private Defaults&MockObject $defaults;
private DefaultContactService&MockObject $defaultContactService;
private UserEventsListener $userEventsListener;
protected function setUp(): void {
@ -36,12 +39,14 @@ class UserEventsListenerTest extends TestCase {
$this->calDavBackend = $this->createMock(CalDavBackend::class);
$this->cardDavBackend = $this->createMock(CardDavBackend::class);
$this->defaults = $this->createMock(Defaults::class);
$this->defaultContactService = $this->createMock(DefaultContactService::class);
$this->userEventsListener = new UserEventsListener(
$this->userManager,
$this->syncService,
$this->calDavBackend,
$this->cardDavBackend,
$this->defaults,
$this->defaultContactService,
);
}

View file

@ -0,0 +1,148 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\DAV\Tests\Unit\Service;
use OCA\DAV\CardDAV\CardDavBackend;
use OCA\DAV\Service\DefaultContactService;
use OCP\App\IAppManager;
use OCP\Files\AppData\IAppDataFactory;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\Files\SimpleFS\ISimpleFolder;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Symfony\Component\Uid\Uuid;
use Test\TestCase;
class DefaultContactServiceTest extends TestCase {
private DefaultContactService $service;
private MockObject|CardDavBackend $cardDav;
private MockObject|IAppManager $appManager;
private MockObject|IAppDataFactory $appDataFactory;
private MockObject|LoggerInterface $logger;
protected function setUp(): void {
parent::setUp();
$this->cardDav = $this->createMock(CardDavBackend::class);
$this->appManager = $this->createMock(IAppManager::class);
$this->appDataFactory = $this->createMock(IAppDataFactory::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new DefaultContactService(
$this->cardDav,
$this->appManager,
$this->appDataFactory,
$this->logger
);
}
public function testCreateDefaultContactWithInvalidCard(): void {
// Invalid vCard missing required FN property
$vcardContent = "BEGIN:VCARD\nVERSION:3.0\nEND:VCARD";
$appData = $this->createMock(IAppData::class);
$folder = $this->createMock(ISimpleFolder::class);
$file = $this->createMock(ISimpleFile::class);
$file->method('getContent')->willReturn($vcardContent);
$folder->method('getFile')->willReturn($file);
$appData->method('getFolder')->willReturn($folder);
$this->appDataFactory->method('get')->willReturn($appData);
$this->logger->expects($this->once())
->method('error')
->with('Default contact is invalid', $this->anything());
$this->cardDav->expects($this->never())
->method('createCard');
$this->service->createDefaultContact(123);
}
public function testUidAndRevAreUpdated(): void {
$originalUid = 'original-uid';
$originalRev = '20200101T000000Z';
$vcardContent = "BEGIN:VCARD\nVERSION:3.0\nFN:Test User\nUID:$originalUid\nREV:$originalRev\nEND:VCARD";
$appData = $this->createMock(IAppData::class);
$folder = $this->createMock(ISimpleFolder::class);
$file = $this->createMock(ISimpleFile::class);
$file->method('getContent')->willReturn($vcardContent);
$folder->method('getFile')->willReturn($file);
$appData->method('getFolder')->willReturn($folder);
$this->appDataFactory->method('get')->willReturn($appData);
$capturedCardData = null;
$this->cardDav->expects($this->once())
->method('createCard')
->with(
$this->anything(),
$this->anything(),
$this->callback(function ($cardData) use (&$capturedCardData) {
$capturedCardData = $cardData;
return true;
}),
$this->anything()
)->willReturn(null);
$this->service->createDefaultContact(123);
$vcard = \Sabre\VObject\Reader::read($capturedCardData);
$this->assertNotEquals($originalUid, $vcard->UID->getValue());
$this->assertTrue(Uuid::isValid($vcard->UID->getValue()));
$this->assertNotEquals($originalRev, $vcard->REV->getValue());
}
public function testDefaultContactFileDoesNotExist(): void {
$appData = $this->createMock(IAppData::class);
$appData->method('getFolder')->willThrowException(new NotFoundException());
$this->appDataFactory->method('get')->willReturn($appData);
$this->cardDav->expects($this->never())
->method('createCard');
$this->service->createDefaultContact(123);
}
public function testUidAndRevAreAddedIfMissing(): void {
$vcardContent = "BEGIN:VCARD\nVERSION:3.0\nFN:Test User\nEND:VCARD";
$appData = $this->createMock(IAppData::class);
$folder = $this->createMock(ISimpleFolder::class);
$file = $this->createMock(ISimpleFile::class);
$file->method('getContent')->willReturn($vcardContent);
$folder->method('getFile')->willReturn($file);
$appData->method('getFolder')->willReturn($folder);
$this->appDataFactory->method('get')->willReturn($appData);
$capturedCardData = 'new-card-data';
$this->cardDav
->expects($this->once())
->method('createCard')
->with(
$this->anything(),
$this->anything(),
$this->callback(function ($cardData) use (&$capturedCardData) {
$capturedCardData = $cardData;
return true;
}),
$this->anything()
);
$this->service->createDefaultContact(123);
$vcard = \Sabre\VObject\Reader::read($capturedCardData);
$this->assertNotNull($vcard->REV);
$this->assertNotNull($vcard->UID);
$this->assertTrue(Uuid::isValid($vcard->UID->getValue()));
}
}

4
dist/2441-2441.js vendored
View file

@ -1,2 +1,2 @@
"use strict";(self.webpackChunknextcloud=self.webpackChunknextcloud||[]).push([[2441],{82441:(e,c,l)=>{l.d(c,{FilePickerVue:()=>n});const n=(0,l(85471).$V)((()=>Promise.all([l.e(4208),l.e(8289),l.e(6146)]).then(l.bind(l,26146))))}}]);
//# sourceMappingURL=2441-2441.js.map?v=44b85e4901c485417f88
"use strict";(self.webpackChunknextcloud=self.webpackChunknextcloud||[]).push([[2441],{82441:(e,c,l)=>{l.d(c,{FilePickerVue:()=>n});const n=(0,l(85471).$V)((()=>Promise.all([l.e(4208),l.e(9904)]).then(l.bind(l,26146))))}}]);
//# sourceMappingURL=2441-2441.js.map?v=abb0f9caea1b27348963

View file

@ -1 +1 @@
{"version":3,"file":"2441-2441.js?v=44b85e4901c485417f88","mappings":"oIACA,MAAMA,GAAgB,E,SAAA,KAAqB,IAAM,oE","sources":["webpack:///nextcloud/node_modules/@nextcloud/dialogs/dist/chunks/index-Ly0obkwS.mjs"],"sourcesContent":["import { defineAsyncComponent } from \"vue\";\nconst FilePickerVue = defineAsyncComponent(() => import(\"./FilePicker-CSmrMOEO.mjs\"));\nexport {\n FilePickerVue\n};\n"],"names":["FilePickerVue"],"sourceRoot":""}
{"version":3,"file":"2441-2441.js?v=abb0f9caea1b27348963","mappings":"oIACA,MAAMA,GAAgB,E,SAAA,KAAqB,IAAM,0D","sources":["webpack:///nextcloud/node_modules/@nextcloud/dialogs/dist/chunks/index-Ly0obkwS.mjs"],"sourcesContent":["import { defineAsyncComponent } from \"vue\";\nconst FilePickerVue = defineAsyncComponent(() => import(\"./FilePicker-CSmrMOEO.mjs\"));\nexport {\n FilePickerVue\n};\n"],"names":["FilePickerVue"],"sourceRoot":""}

2
dist/6146-6146.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
6146-6146.js.license

2
dist/8289-8289.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
8289-8289.js.license

2
dist/9904-9904.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/9904-9904.js.map vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/9904-9904.js.map.license vendored Symbolic link
View file

@ -0,0 +1 @@
9904-9904.js.license

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/core-common.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/core-login.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/core-main.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
dist/dav-settings-example-content.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -11,10 +11,12 @@ SPDX-FileCopyrightText: xiemengxiong
SPDX-FileCopyrightText: xiaokai <kexiaokai@gmail.com>
SPDX-FileCopyrightText: rhysd <lin90162@yahoo.co.jp>
SPDX-FileCopyrightText: inline-style-parser developers
SPDX-FileCopyrightText: inherits developers
SPDX-FileCopyrightText: escape-html developers
SPDX-FileCopyrightText: debounce developers
SPDX-FileCopyrightText: atomiks
SPDX-FileCopyrightText: Victor Felder <victor@draft.li> (https://draft.li)
SPDX-FileCopyrightText: Varun A P
SPDX-FileCopyrightText: Tobias Koppers @sokra
SPDX-FileCopyrightText: Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)
SPDX-FileCopyrightText: Thorsten Lünborg
@ -23,6 +25,7 @@ SPDX-FileCopyrightText: Stefan Thomas <justmoon@members.fsf.org> (http://www.jus
SPDX-FileCopyrightText: Sindre Sorhus
SPDX-FileCopyrightText: Roman Shtylman <shtylman@gmail.com>
SPDX-FileCopyrightText: Roeland Jago Douma
SPDX-FileCopyrightText: Rob Cresswell <robcresswell@pm.me>
SPDX-FileCopyrightText: Richie Bendall
SPDX-FileCopyrightText: Philipp Kewisch
SPDX-FileCopyrightText: Paul Vorbach <paul@vorba.ch> (http://paul.vorba.ch)
@ -32,6 +35,7 @@ SPDX-FileCopyrightText: Nextcloud GmbH and Nextcloud contributors
SPDX-FileCopyrightText: Matt Zabriskie
SPDX-FileCopyrightText: Mark <mark@remarkablemark.org>
SPDX-FileCopyrightText: Mapbox
SPDX-FileCopyrightText: Joyent
SPDX-FileCopyrightText: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
SPDX-FileCopyrightText: Jeff Sagal <sagalbot@gmail.com>
SPDX-FileCopyrightText: Jacob Clevenger<https://github.com/wheatjs>
@ -46,9 +50,11 @@ SPDX-FileCopyrightText: Dr.-Ing. Mario Heiderich, Cure53 <mario@cure53.de> (http
SPDX-FileCopyrightText: David Clark
SPDX-FileCopyrightText: Christoph Wurst
SPDX-FileCopyrightText: Borys Serebrov
SPDX-FileCopyrightText: Austin Andrews
SPDX-FileCopyrightText: Antoni Andre <antoniandre.web@gmail.com>
SPDX-FileCopyrightText: Anthony Fu <https://github.com/antfu>
SPDX-FileCopyrightText: Andrea Giammarchi
SPDX-FileCopyrightText: @nextcloud/dialogs developers
This file is generated from multiple sources. Included packages:
@ -73,6 +79,9 @@ This file is generated from multiple sources. Included packages:
- @mapbox/hast-util-table-cell-style
- version: 0.2.1
- license: BSD-2-Clause
- @mdi/svg
- version: 7.4.47
- license: Apache-2.0
- @nextcloud/auth
- version: 2.4.0
- license: GPL-3.0-or-later
@ -85,6 +94,9 @@ This file is generated from multiple sources. Included packages:
- @nextcloud/capabilities
- version: 1.2.0
- license: GPL-3.0-or-later
- @nextcloud/dialogs
- version: 6.1.1
- license: AGPL-3.0-or-later
- semver
- version: 7.6.3
- license: ISC
@ -343,6 +355,15 @@ This file is generated from multiple sources. Included packages:
- buffer
- version: 6.0.3
- license: MIT
- inherits
- version: 2.0.3
- license: ISC
- util
- version: 0.10.4
- license: MIT
- path
- version: 0.12.7
- license: MIT
- process
- version: 0.11.10
- license: MIT
@ -385,6 +406,9 @@ This file is generated from multiple sources. Included packages:
- tabbable
- version: 6.2.0
- license: MIT
- toastify-js
- version: 1.12.0
- license: MIT
- trim-lines
- version: 3.0.1
- license: MIT
@ -424,6 +448,12 @@ This file is generated from multiple sources. Included packages:
- vue-frag
- version: 1.4.3
- license: MIT
- vue-loader
- version: 15.11.1
- license: MIT
- vue-material-design-icons
- version: 5.3.1
- license: MIT
- vue-router
- version: 3.6.5
- license: MIT
@ -433,6 +463,12 @@ This file is generated from multiple sources. Included packages:
- web-namespaces
- version: 2.0.1
- license: MIT
- webpack
- version: 5.94.0
- license: MIT
- zwitch
- version: 2.0.4
- license: MIT
- nextcloud
- version: 1.0.0
- license: AGPL-3.0-or-later

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
dav-settings-example-content.js.license

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/files-init.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/files-main.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,2 +1,2 @@
(()=>{"use strict";var e,r,t,i={97986:(e,r,t)=>{var i=t(61338),o=t(85168),a=t(63814),n=t(53334);const l=(0,t(35947).YK)().setApp("files").detectUser().build();document.addEventListener("DOMContentLoaded",(function(){const e=window.OCA;e.UnifiedSearch&&(l.info("Initializing unified search plugin: folder search from files app"),e.UnifiedSearch.registerFilterAction({id:"in-folder",appId:"files",searchFrom:"files",label:(0,n.Tl)("files","In folder"),icon:(0,a.d0)("files","app.svg"),callback:function(){arguments.length>0&&void 0!==arguments[0]&&!arguments[0]?l.debug("Folder search callback was handled without showing the file picker, it might already be open"):(0,o.a1)("Pick plain text files").addMimeTypeFilter("httpd/unix-directory").allowDirectories(!0).addButton({label:"Pick",callback:e=>{l.info("Folder picked",{folder:e[0]});const r=e[0];(0,i.Ic)("nextcloud:unified-search:add-filter",{id:"in-folder",appId:"files",searchFrom:"files",payload:r,filterUpdateText:(0,n.Tl)("files","Search in folder: {folder}",{folder:r.basename}),filterParams:{path:r.path}})}}).build().pick()}}))}))}},o={};function a(e){var r=o[e];if(void 0!==r)return r.exports;var t=o[e]={id:e,loaded:!1,exports:{}};return i[e].call(t.exports,t,t.exports,a),t.loaded=!0,t.exports}a.m=i,e=[],a.O=(r,t,i,o)=>{if(!t){var n=1/0;for(s=0;s<e.length;s++){t=e[s][0],i=e[s][1],o=e[s][2];for(var l=!0,d=0;d<t.length;d++)(!1&o||n>=o)&&Object.keys(a.O).every((e=>a.O[e](t[d])))?t.splice(d--,1):(l=!1,o<n&&(n=o));if(l){e.splice(s--,1);var c=i();void 0!==c&&(r=c)}}return r}o=o||0;for(var s=e.length;s>0&&e[s-1][2]>o;s--)e[s]=e[s-1];e[s]=[t,i,o]},a.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return a.d(r,{a:r}),r},a.d=(e,r)=>{for(var t in r)a.o(r,t)&&!a.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},a.f={},a.e=e=>Promise.all(Object.keys(a.f).reduce(((r,t)=>(a.f[t](e,r),r)),[])),a.u=e=>e+"-"+e+".js?v="+{2441:"44b85e4901c485417f88",5862:"142cd48ca8ec32e57725",6146:"5f2015343db7411125d5",8289:"8f098190dce9305dab1e"}[e],a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),a.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},t="nextcloud:",a.l=(e,i,o,n)=>{if(r[e])r[e].push(i);else{var l,d;if(void 0!==o)for(var c=document.getElementsByTagName("script"),s=0;s<c.length;s++){var f=c[s];if(f.getAttribute("src")==e||f.getAttribute("data-webpack")==t+o){l=f;break}}l||(d=!0,(l=document.createElement("script")).charset="utf-8",l.timeout=120,a.nc&&l.setAttribute("nonce",a.nc),l.setAttribute("data-webpack",t+o),l.src=e),r[e]=[i];var u=(t,i)=>{l.onerror=l.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],l.parentNode&&l.parentNode.removeChild(l),o&&o.forEach((e=>e(i))),t)return t(i)},p=setTimeout(u.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=u.bind(null,l.onerror),l.onload=u.bind(null,l.onload),d&&document.head.appendChild(l)}},a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),a.j=2277,(()=>{var e;a.g.importScripts&&(e=a.g.location+"");var r=a.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var i=t.length-1;i>-1&&(!e||!/^http(s?):/.test(e));)e=t[i--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),a.p=e})(),(()=>{a.b=document.baseURI||self.location.href;var e={2277:0};a.f.j=(r,t)=>{var i=a.o(e,r)?e[r]:void 0;if(0!==i)if(i)t.push(i[2]);else{var o=new Promise(((t,o)=>i=e[r]=[t,o]));t.push(i[2]=o);var n=a.p+a.u(r),l=new Error;a.l(n,(t=>{if(a.o(e,r)&&(0!==(i=e[r])&&(e[r]=void 0),i)){var o=t&&("load"===t.type?"missing":t.type),n=t&&t.target&&t.target.src;l.message="Loading chunk "+r+" failed.\n("+o+": "+n+")",l.name="ChunkLoadError",l.type=o,l.request=n,i[1](l)}}),"chunk-"+r,r)}},a.O.j=r=>0===e[r];var r=(r,t)=>{var i,o,n=t[0],l=t[1],d=t[2],c=0;if(n.some((r=>0!==e[r]))){for(i in l)a.o(l,i)&&(a.m[i]=l[i]);if(d)var s=d(a)}for(r&&r(t);c<n.length;c++)o=n[c],a.o(e,o)&&e[o]&&e[o][0](),e[o]=0;return a.O(s)},t=self.webpackChunknextcloud=self.webpackChunknextcloud||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),a.nc=void 0;var n=a.O(void 0,[4208],(()=>a(97986)));n=a.O(n)})();
//# sourceMappingURL=files-search.js.map?v=cf9e1d416f4a63412a17
(()=>{"use strict";var e,r,t,i={97986:(e,r,t)=>{var i=t(61338),o=t(85168),a=t(63814),n=t(53334);const l=(0,t(35947).YK)().setApp("files").detectUser().build();document.addEventListener("DOMContentLoaded",(function(){const e=window.OCA;e.UnifiedSearch&&(l.info("Initializing unified search plugin: folder search from files app"),e.UnifiedSearch.registerFilterAction({id:"in-folder",appId:"files",searchFrom:"files",label:(0,n.Tl)("files","In folder"),icon:(0,a.d0)("files","app.svg"),callback:function(){arguments.length>0&&void 0!==arguments[0]&&!arguments[0]?l.debug("Folder search callback was handled without showing the file picker, it might already be open"):(0,o.a1)("Pick plain text files").addMimeTypeFilter("httpd/unix-directory").allowDirectories(!0).addButton({label:"Pick",callback:e=>{l.info("Folder picked",{folder:e[0]});const r=e[0];(0,i.Ic)("nextcloud:unified-search:add-filter",{id:"in-folder",appId:"files",searchFrom:"files",payload:r,filterUpdateText:(0,n.Tl)("files","Search in folder: {folder}",{folder:r.basename}),filterParams:{path:r.path}})}}).build().pick()}}))}))}},o={};function a(e){var r=o[e];if(void 0!==r)return r.exports;var t=o[e]={id:e,loaded:!1,exports:{}};return i[e].call(t.exports,t,t.exports,a),t.loaded=!0,t.exports}a.m=i,e=[],a.O=(r,t,i,o)=>{if(!t){var n=1/0;for(s=0;s<e.length;s++){t=e[s][0],i=e[s][1],o=e[s][2];for(var l=!0,d=0;d<t.length;d++)(!1&o||n>=o)&&Object.keys(a.O).every((e=>a.O[e](t[d])))?t.splice(d--,1):(l=!1,o<n&&(n=o));if(l){e.splice(s--,1);var c=i();void 0!==c&&(r=c)}}return r}o=o||0;for(var s=e.length;s>0&&e[s-1][2]>o;s--)e[s]=e[s-1];e[s]=[t,i,o]},a.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return a.d(r,{a:r}),r},a.d=(e,r)=>{for(var t in r)a.o(r,t)&&!a.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},a.f={},a.e=e=>Promise.all(Object.keys(a.f).reduce(((r,t)=>(a.f[t](e,r),r)),[])),a.u=e=>e+"-"+e+".js?v="+{2441:"abb0f9caea1b27348963",5862:"142cd48ca8ec32e57725",9904:"524c00bcce4222bb9815"}[e],a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),a.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},t="nextcloud:",a.l=(e,i,o,n)=>{if(r[e])r[e].push(i);else{var l,d;if(void 0!==o)for(var c=document.getElementsByTagName("script"),s=0;s<c.length;s++){var f=c[s];if(f.getAttribute("src")==e||f.getAttribute("data-webpack")==t+o){l=f;break}}l||(d=!0,(l=document.createElement("script")).charset="utf-8",l.timeout=120,a.nc&&l.setAttribute("nonce",a.nc),l.setAttribute("data-webpack",t+o),l.src=e),r[e]=[i];var u=(t,i)=>{l.onerror=l.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],l.parentNode&&l.parentNode.removeChild(l),o&&o.forEach((e=>e(i))),t)return t(i)},p=setTimeout(u.bind(null,void 0,{type:"timeout",target:l}),12e4);l.onerror=u.bind(null,l.onerror),l.onload=u.bind(null,l.onload),d&&document.head.appendChild(l)}},a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),a.j=2277,(()=>{var e;a.g.importScripts&&(e=a.g.location+"");var r=a.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var i=t.length-1;i>-1&&(!e||!/^http(s?):/.test(e));)e=t[i--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),a.p=e})(),(()=>{a.b=document.baseURI||self.location.href;var e={2277:0};a.f.j=(r,t)=>{var i=a.o(e,r)?e[r]:void 0;if(0!==i)if(i)t.push(i[2]);else{var o=new Promise(((t,o)=>i=e[r]=[t,o]));t.push(i[2]=o);var n=a.p+a.u(r),l=new Error;a.l(n,(t=>{if(a.o(e,r)&&(0!==(i=e[r])&&(e[r]=void 0),i)){var o=t&&("load"===t.type?"missing":t.type),n=t&&t.target&&t.target.src;l.message="Loading chunk "+r+" failed.\n("+o+": "+n+")",l.name="ChunkLoadError",l.type=o,l.request=n,i[1](l)}}),"chunk-"+r,r)}},a.O.j=r=>0===e[r];var r=(r,t)=>{var i,o,n=t[0],l=t[1],d=t[2],c=0;if(n.some((r=>0!==e[r]))){for(i in l)a.o(l,i)&&(a.m[i]=l[i]);if(d)var s=d(a)}for(r&&r(t);c<n.length;c++)o=n[c],a.o(e,o)&&e[o]&&e[o][0](),e[o]=0;return a.O(s)},t=self.webpackChunknextcloud=self.webpackChunknextcloud||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),a.nc=void 0;var n=a.O(void 0,[4208],(()=>a(97986)));n=a.O(n)})();
//# sourceMappingURL=files-search.js.map?v=ac56153c5e375af6d5ff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show more