diff options
author | John Molakvoæ <skjnldsv@users.noreply.github.com> | 2024-07-19 09:14:09 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-19 09:14:09 +0200 |
commit | 0bde47a39256dfad3baa8d3ffa275ac3d113a9d5 (patch) | |
tree | 4543f6d971922c082fd3ec32110ab42be4213786 /apps/files_sharing | |
parent | 22efc6da6a8196852e7346e9de5364fa05c6c784 (diff) | |
parent | 725736a754c81ccee17ba394de4da38f5bfa0e68 (diff) | |
download | nextcloud-server-0bde47a39256dfad3baa8d3ffa275ac3d113a9d5.tar.gz nextcloud-server-0bde47a39256dfad3baa8d3ffa275ac3d113a9d5.zip |
Merge pull request #46589 from nextcloud/fix/files_sharing-file-request-followup
Diffstat (limited to 'apps/files_sharing')
21 files changed, 461 insertions, 136 deletions
diff --git a/apps/files_sharing/composer/composer/autoload_classmap.php b/apps/files_sharing/composer/composer/autoload_classmap.php index e1abddb3a64..b7a931a6228 100644 --- a/apps/files_sharing/composer/composer/autoload_classmap.php +++ b/apps/files_sharing/composer/composer/autoload_classmap.php @@ -59,6 +59,7 @@ return array( 'OCA\\Files_Sharing\\Listener\\BeforeDirectFileDownloadListener' => $baseDir . '/../lib/Listener/BeforeDirectFileDownloadListener.php', 'OCA\\Files_Sharing\\Listener\\BeforeZipCreatedListener' => $baseDir . '/../lib/Listener/BeforeZipCreatedListener.php', 'OCA\\Files_Sharing\\Listener\\LoadAdditionalListener' => $baseDir . '/../lib/Listener/LoadAdditionalListener.php', + 'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => $baseDir . '/../lib/Listener/LoadPublicFileRequestAuthListener.php', 'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php', 'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => $baseDir . '/../lib/Listener/ShareInteractionListener.php', 'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => $baseDir . '/../lib/Listener/UserAddedToGroupListener.php', diff --git a/apps/files_sharing/composer/composer/autoload_static.php b/apps/files_sharing/composer/composer/autoload_static.php index 5d2fb3bac2a..70dc7be7cdf 100644 --- a/apps/files_sharing/composer/composer/autoload_static.php +++ b/apps/files_sharing/composer/composer/autoload_static.php @@ -74,6 +74,7 @@ class ComposerStaticInitFiles_Sharing 'OCA\\Files_Sharing\\Listener\\BeforeDirectFileDownloadListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeDirectFileDownloadListener.php', 'OCA\\Files_Sharing\\Listener\\BeforeZipCreatedListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeZipCreatedListener.php', 'OCA\\Files_Sharing\\Listener\\LoadAdditionalListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalListener.php', + 'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => __DIR__ . '/..' . '/../lib/Listener/LoadPublicFileRequestAuthListener.php', 'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php', 'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/ShareInteractionListener.php', 'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => __DIR__ . '/..' . '/../lib/Listener/UserAddedToGroupListener.php', diff --git a/apps/files_sharing/js/files_drop.js b/apps/files_sharing/js/files_drop.js index fd9b796ee2c..450af078af2 100644 --- a/apps/files_sharing/js/files_drop.js +++ b/apps/files_sharing/js/files_drop.js @@ -22,7 +22,7 @@ // note: password not be required, the endpoint // will recognize previous validation from the session root: OC.getRootPath() + '/public.php/dav/files/' + $('#sharingToken').val() + '/', - useHTTPS: OC.getProtocol() === 'https' + useHTTPS: OC.getProtocol() === 'https', }); // We only process one file at a time 🤷‍♀️ @@ -47,6 +47,10 @@ data.headers = {}; } + if (localStorage.getItem('nick') !== null) { + data.headers['X-NC-Nickname'] = localStorage.getItem('nick') + } + $('#drop-upload-done-indicator').addClass('hidden'); $('#drop-upload-progress-indicator').removeClass('hidden'); diff --git a/apps/files_sharing/lib/AppInfo/Application.php b/apps/files_sharing/lib/AppInfo/Application.php index 82a5981febf..98c2d280856 100644 --- a/apps/files_sharing/lib/AppInfo/Application.php +++ b/apps/files_sharing/lib/AppInfo/Application.php @@ -18,6 +18,7 @@ use OCA\Files_Sharing\Helper; use OCA\Files_Sharing\Listener\BeforeDirectFileDownloadListener; use OCA\Files_Sharing\Listener\BeforeZipCreatedListener; use OCA\Files_Sharing\Listener\LoadAdditionalListener; +use OCA\Files_Sharing\Listener\LoadPublicFileRequestAuthListener; use OCA\Files_Sharing\Listener\LoadSidebarListener; use OCA\Files_Sharing\Listener\ShareInteractionListener; use OCA\Files_Sharing\Listener\UserAddedToGroupListener; @@ -34,6 +35,7 @@ use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; +use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as ResourcesLoadAdditionalScriptsEvent; use OCP\EventDispatcher\IEventDispatcher; use OCP\Federation\ICloudIdManager; @@ -85,7 +87,7 @@ class Application extends App implements IBootstrap { $context->registerEventListener(GroupChangedEvent::class, GroupDisplayNameCache::class); $context->registerEventListener(GroupDeletedEvent::class, GroupDisplayNameCache::class); - // sidebar and files scripts + // Sidebar and files scripts $context->registerEventListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class); $context->registerEventListener(LoadSidebar::class, LoadSidebarListener::class); $context->registerEventListener(ShareCreatedEvent::class, ShareInteractionListener::class); @@ -95,6 +97,9 @@ class Application extends App implements IBootstrap { // Handle download events for view only checks $context->registerEventListener(BeforeZipCreatedEvent::class, BeforeZipCreatedListener::class); $context->registerEventListener(BeforeDirectFileDownloadEvent::class, BeforeDirectFileDownloadListener::class); + + // File request auth + $context->registerEventListener(BeforeTemplateRenderedEvent::class, LoadPublicFileRequestAuthListener::class); } public function boot(IBootContext $context): void { diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index f2ace7b4d70..1e6750a5bce 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -596,8 +596,10 @@ class ShareAPIController extends OCSController { throw new OCSNotFoundException($this->l->t('Invalid permissions')); } - // Shares always require read permissions - $permissions |= Constants::PERMISSION_READ; + // Shares always require read permissions OR create permissions + if (($permissions & Constants::PERMISSION_READ) === 0 && ($permissions & Constants::PERMISSION_CREATE) === 0) { + $permissions |= Constants::PERMISSION_READ; + } if ($node instanceof \OCP\Files\File) { // Single file shares should never have delete or create permissions diff --git a/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php b/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php index 8f5ea9fd0be..477bc9f82ce 100644 --- a/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php +++ b/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php @@ -19,6 +19,7 @@ use OCP\AppFramework\Http\Template\LinkMenuAction; use OCP\AppFramework\Http\Template\PublicTemplateResponse; use OCP\AppFramework\Http\Template\SimpleMenuAction; use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Services\IInitialState; use OCP\Constants; use OCP\Defaults; use OCP\EventDispatcher\IEventDispatcher; @@ -37,39 +38,20 @@ use OCP\Template; use OCP\Util; class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider { - private IUserManager $userManager; - private IAccountManager $accountManager; - private IPreview $previewManager; - protected FederatedShareProvider $federatedShareProvider; - private IURLGenerator $urlGenerator; - private IEventDispatcher $eventDispatcher; - private IL10N $l10n; - private Defaults $defaults; - private IConfig $config; - private IRequest $request; public function __construct( - IUserManager $userManager, - IAccountManager $accountManager, - IPreview $previewManager, - FederatedShareProvider $federatedShareProvider, - IUrlGenerator $urlGenerator, - IEventDispatcher $eventDispatcher, - IL10N $l10n, - Defaults $defaults, - IConfig $config, - IRequest $request + private IUserManager $userManager, + private IAccountManager $accountManager, + private IPreview $previewManager, + protected FederatedShareProvider $federatedShareProvider, + private IUrlGenerator $urlGenerator, + private IEventDispatcher $eventDispatcher, + private IL10N $l10n, + private Defaults $defaults, + private IConfig $config, + private IRequest $request, + private IInitialState $initialState, ) { - $this->userManager = $userManager; - $this->accountManager = $accountManager; - $this->previewManager = $previewManager; - $this->federatedShareProvider = $federatedShareProvider; - $this->urlGenerator = $urlGenerator; - $this->eventDispatcher = $eventDispatcher; - $this->l10n = $l10n; - $this->defaults = $defaults; - $this->config = $config; - $this->request = $request; } public function shouldRespond(IShare $share): bool { @@ -91,11 +73,19 @@ class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider if ($ownerName->getScope() === IAccountManager::SCOPE_PUBLISHED) { $shareTmpl['owner'] = $owner->getUID(); $shareTmpl['shareOwner'] = $owner->getDisplayName(); + $this->initialState->provideInitialState('owner', $shareTmpl['owner']); + $this->initialState->provideInitialState('ownerDisplayName', $shareTmpl['shareOwner']); } } + // Provide initial state + $this->initialState->provideInitialState('label', $share->getLabel()); + $this->initialState->provideInitialState('note', $share->getNote()); + $this->initialState->provideInitialState('filename', $shareNode->getName()); + $shareTmpl['filename'] = $shareNode->getName(); $shareTmpl['directory_path'] = $share->getTarget(); + $shareTmpl['label'] = $share->getLabel(); $shareTmpl['note'] = $share->getNote(); $shareTmpl['mimetype'] = $shareNode->getMimetype(); $shareTmpl['previewSupported'] = $this->previewManager->isMimeSupported($shareNode->getMimetype()); @@ -240,6 +230,11 @@ class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider $response->setHeaderDetails($this->l10n->t('shared by %s', [$shareTmpl['shareOwner']])); } + // If the share has a label, use it as the title + if ($shareTmpl['label'] !== '') { + $response->setHeaderTitle($shareTmpl['label']); + } + $isNoneFileDropFolder = $shareIsFolder === false || $share->getPermissions() !== Constants::PERMISSION_CREATE; if ($isNoneFileDropFolder && !$share->getHideDownload()) { diff --git a/apps/files_sharing/lib/Listener/LoadPublicFileRequestAuthListener.php b/apps/files_sharing/lib/Listener/LoadPublicFileRequestAuthListener.php new file mode 100644 index 00000000000..f1e054c7ee5 --- /dev/null +++ b/apps/files_sharing/lib/Listener/LoadPublicFileRequestAuthListener.php @@ -0,0 +1,59 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\Files_Sharing\Listener; + +use OCA\Files_Sharing\AppInfo\Application; +use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Share\IManager; +use OCP\Util; + +/** @template-implements IEventListener<BeforeTemplateRenderedEvent> */ +class LoadPublicFileRequestAuthListener implements IEventListener { + public function __construct( + private IManager $shareManager, + ) { + } + + public function handle(Event $event): void { + if (!$event instanceof BeforeTemplateRenderedEvent) { + return; + } + + // Make sure we are on a public page rendering + if ($event->getResponse()->getRenderAs() !== TemplateResponse::RENDER_AS_PUBLIC) { + return; + } + + $token = $event->getResponse()->getParams()['sharingToken'] ?? null; + if ($token === null || $token === '') { + return; + } + + // Check if the share is a file request + $isFileRequest = false; + try { + $share = $this->shareManager->getShareByToken($token); + $attributes = $share->getAttributes(); + if ($attributes === null) { + return; + } + + $isFileRequest = $attributes->getAttribute('fileRequest', 'enabled') === true; + } catch (\Exception $e) { + // Ignore, this is not a file request or the share does not exist + } + + if ($isFileRequest) { + // Add the script to the public page + Util::addScript(Application::APP_ID, 'public-file-request'); + } + } +} diff --git a/apps/files_sharing/src/actions/openInFilesAction.ts b/apps/files_sharing/src/actions/openInFilesAction.ts index 51b9ec84a1d..82b66927c9e 100644 --- a/apps/files_sharing/src/actions/openInFilesAction.ts +++ b/apps/files_sharing/src/actions/openInFilesAction.ts @@ -11,7 +11,7 @@ import { sharesViewId, sharedWithYouViewId, sharedWithOthersViewId, sharingByLin export const action = new FileAction({ id: 'open-in-files', - displayName: () => t('files', 'Open in Files'), + displayName: () => t('files_sharing', 'Open in Files'), iconSvgInline: () => '', enabled: (nodes, view) => [ diff --git a/apps/files_sharing/src/components/NewFileRequestDialog.vue b/apps/files_sharing/src/components/NewFileRequestDialog.vue index 35cd4395290..6b0bb5a1fc2 100644 --- a/apps/files_sharing/src/components/NewFileRequestDialog.vue +++ b/apps/files_sharing/src/components/NewFileRequestDialog.vue @@ -50,8 +50,22 @@ <!-- Controls --> <template #actions> + <!-- Back --> + <NcButton v-show="currentStep === STEP.SECOND" + :aria-label="t('files_sharing', 'Previous step')" + :disabled="loading" + data-cy-file-request-dialog-controls="back" + type="tertiary" + @click="currentStep = STEP.FIRST"> + {{ t('files_sharing', 'Previous step') }} + </NcButton> + + <!-- Align right --> + <span class="dialog__actions-separator" /> + <!-- Cancel the creation --> - <NcButton :aria-label="t('files_sharing', 'Cancel')" + <NcButton v-if="currentStep !== STEP.LAST" + :aria-label="t('files_sharing', 'Cancel')" :disabled="loading" :title="t('files_sharing', 'Cancel the file request creation')" data-cy-file-request-dialog-controls="cancel" @@ -60,17 +74,15 @@ {{ t('files_sharing', 'Cancel') }} </NcButton> - <!-- Align right --> - <span class="dialog__actions-separator" /> - - <!-- Back --> - <NcButton v-show="currentStep === STEP.SECOND" - :aria-label="t('files_sharing', 'Previous step')" + <!-- Cancel email and just close --> + <NcButton v-else-if="emails.length !== 0" + :aria-label="t('files_sharing', 'Close without sending emails')" :disabled="loading" - data-cy-file-request-dialog-controls="back" + :title="t('files_sharing', 'Close without sending emails')" + data-cy-file-request-dialog-controls="cancel" type="tertiary" - @click="currentStep = STEP.FIRST"> - {{ t('files_sharing', 'Previous step') }} + @click="onCancel"> + {{ t('files_sharing', 'Close') }} </NcButton> <!-- Next --> @@ -115,7 +127,7 @@ import { generateOcsUrl } from '@nextcloud/router' import { Permission } from '@nextcloud/files' import { ShareType } from '@nextcloud/sharing' import { showError, showSuccess } from '@nextcloud/dialogs' -import { translate, translatePlural } from '@nextcloud/l10n' +import { n, t } from '@nextcloud/l10n' import axios from '@nextcloud/axios' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' @@ -170,9 +182,8 @@ export default defineComponent({ setup() { return { STEP, - - n: translatePlural, - t: translate, + n, + t, isShareByMailEnabled: sharingConfig.isMailShareAllowed, } @@ -198,9 +209,9 @@ export default defineComponent({ computed: { finishButtonLabel() { if (this.emails.length === 0) { - return this.t('files_sharing', 'Close') + return t('files_sharing', 'Close') } - return this.n('files_sharing', 'Close and send email', 'Close and send {count} emails', this.emails.length, { count: this.emails.length }) + return n('files_sharing', 'Send email and close', 'Send {count} emails and close', this.emails.length, { count: this.emails.length }) }, }, @@ -209,13 +220,14 @@ export default defineComponent({ const form = this.$refs.form as HTMLFormElement if (!form.checkValidity()) { form.reportValidity() + return } // custom destination validation // cannot share root if (this.destination === '/' || this.destination === '') { const destinationInput = form.querySelector('input[name="destination"]') as HTMLInputElement - destinationInput?.setCustomValidity(this.t('files_sharing', 'Please select a folder, you cannot share the root directory.')) + destinationInput?.setCustomValidity(t('files_sharing', 'Please select a folder, you cannot share the root directory.')) form.reportValidity() return } @@ -239,26 +251,44 @@ export default defineComponent({ async onFinish() { if (this.emails.length === 0 || this.isShareByMailEnabled === false) { - showSuccess(this.t('files_sharing', 'File request created')) + showSuccess(t('files_sharing', 'File request created')) this.$emit('close') return } - await this.setShareEmails() - await this.sendEmails() - showSuccess(this.t('files_sharing', 'File request created and emails sent')) + if (sharingConfig.isMailShareAllowed && this.emails.length > 0) { + await this.setShareEmails() + await this.sendEmails() + showSuccess(n('files_sharing', 'File request created and email sent', 'File request created and {count} emails sent', this.emails.length, { count: this.emails.length })) + } else { + showSuccess(t('files_sharing', 'File request created')) + } + this.$emit('close') }, async createShare() { this.loading = true + // This should never happen™ + if (this.expirationDate == null) { + throw new Error('Expiration date is missing') + } + + const year = this.expirationDate.getFullYear() + const month = (this.expirationDate.getMonth() + 1).toString().padStart(2, '0') + const day = this.expirationDate.getDate().toString().padStart(2, '0') + // Format must be YYYY-MM-DD - const expireDate = this.expirationDate ? this.expirationDate.toISOString().split('T')[0] : undefined + const expireDate = this.expirationDate + ? `${year}-${month}-${day}` + : undefined const shareUrl = generateOcsUrl('apps/files_sharing/api/v1/shares') try { const request = await axios.post<OCSResponse>(shareUrl, { - shareType: ShareType.Email, + // Always create a file request, but without mail share + // permissions, only a share link will be created. + shareType: sharingConfig.isMailShareAllowed ? ShareType.Email : ShareType.Link, permissions: Permission.CREATE, label: this.label, @@ -294,8 +324,8 @@ export default defineComponent({ const errorMessage = (error as AxiosError<OCSResponse>)?.response?.data?.ocs?.meta?.message showError( errorMessage - ? this.t('files_sharing', 'Error creating the share: {errorMessage}', { errorMessage }) - : this.t('files_sharing', 'Error creating the share'), + ? t('files_sharing', 'Error creating the share: {errorMessage}', { errorMessage }) + : t('files_sharing', 'Error creating the share'), ) logger.error('Error while creating share', { error, errorMessage }) throw error @@ -320,6 +350,11 @@ export default defineComponent({ value: this.emails, key: 'emails', scope: 'shareWith', + }, + { + value: true, + key: 'enabled', + scope: 'fileRequest', }]), }) @@ -366,8 +401,8 @@ export default defineComponent({ const errorMessage = error.response?.data?.ocs?.meta?.message showError( errorMessage - ? this.t('files_sharing', 'Error sending emails: {errorMessage}', { errorMessage }) - : this.t('files_sharing', 'Error sending emails'), + ? t('files_sharing', 'Error sending emails: {errorMessage}', { errorMessage }) + : t('files_sharing', 'Error sending emails'), ) logger.error('Error while sending emails', { error, errorMessage }) }, @@ -375,10 +410,9 @@ export default defineComponent({ }) </script> -<style scoped lang="scss"> +<style lang="scss"> .file-request-dialog { - --margin: 36px; - --secondary-margin: 18px; + --margin: 18px; &__header { margin: 0 var(--margin); @@ -387,35 +421,45 @@ export default defineComponent({ &__form { position: relative; overflow: auto; - padding: var(--secondary-margin) var(--margin); + padding: var(--margin) var(--margin); // overlap header bottom padding - margin-top: calc(-1 * var(--secondary-margin)); + margin-top: calc(-1 * var(--margin)); } - :deep(fieldset) { + fieldset { display: flex; flex-direction: column; width: 100%; - margin-top: var(--secondary-margin); + margin-top: var(--margin); - :deep(legend) { + legend { display: flex; align-items: center; width: 100%; } } - :deep(.dialog__actions) { + // Using a NcNoteCard was a bit much sometimes. + // Using a simple paragraph instead does it. + &__info { + color: var(--color-text-maxcontrast); + padding-block: 4px; + display: flex; + align-items: center; + .file-request-dialog__info-icon { + margin-inline-end: 8px; + } + } + + .dialog__actions { width: auto; margin-inline: 12px; - // align left and remove margin - margin-left: 0; span.dialog__actions-separator { margin-left: auto; } } - :deep(.input-field__helper-text-message) { + .input-field__helper-text-message { // reduce helper text standing out color: var(--color-text-maxcontrast); } diff --git a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue index 9bb1863e1d1..0eb89388122 100644 --- a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue +++ b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue @@ -12,17 +12,13 @@ <!-- Expiration date --> <fieldset class="file-request-dialog__expiration" data-cy-file-request-dialog-fieldset="expiration"> - <NcNoteCard v-if="defaultExpireDateEnforced" type="info"> - {{ t('files_sharing', 'Your administrator has enforced a default expiration date with a maximum {days} days.', { days: defaultExpireDate }) }} - </NcNoteCard> - <!-- Enable expiration --> <legend>{{ t('files_sharing', 'When should the request expire?') }}</legend> <NcCheckboxRadioSwitch v-show="!defaultExpireDateEnforced" :checked="defaultExpireDateEnforced || expirationDate !== null" :disabled="disabled || defaultExpireDateEnforced" @update:checked="onToggleDeadline"> - {{ t('files_sharing', 'Set a submission expirationDate') }} + {{ t('files_sharing', 'Set a submission expiration date') }} </NcCheckboxRadioSwitch> <!-- Date picker --> @@ -38,15 +34,16 @@ :value="expirationDate" name="expirationDate" type="date" - @update:value="$emit('update:expirationDate', $event)" /> + @input="$emit('update:expirationDate', $event)" /> + + <p v-if="defaultExpireDateEnforced" class="file-request-dialog__info"> + <IconInfo :size="18" class="file-request-dialog__info-icon" /> + {{ t('files_sharing', 'Your administrator has enforced a {count} days expiration policy.', { count: defaultExpireDate }) }} + </p> </fieldset> <!-- Password --> <fieldset class="file-request-dialog__password" data-cy-file-request-dialog-fieldset="password"> - <NcNoteCard v-if="enforcePasswordForPublicLink" type="info"> - {{ t('files_sharing', 'Your administrator has enforced a password protection.') }} - </NcNoteCard> - <!-- Enable password --> <legend>{{ t('files_sharing', 'What password should be used for the request?') }}</legend> <NcCheckboxRadioSwitch v-show="!enforcePasswordForPublicLink" @@ -75,13 +72,18 @@ </template> </NcButton> </div> + + <p v-if="enforcePasswordForPublicLink" class="file-request-dialog__info"> + <IconInfo :size="18" class="file-request-dialog__info-icon" /> + {{ t('files_sharing', 'Your administrator has enforced a password protection.') }} + </p> </fieldset> </div> </template> <script lang="ts"> import { defineComponent, type PropType } from 'vue' -import { translate } from '@nextcloud/l10n' +import { t } from '@nextcloud/l10n' import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' @@ -89,6 +91,7 @@ import NcDateTimePickerNative from '@nextcloud/vue/dist/Components/NcDateTimePic import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' import NcPasswordField from '@nextcloud/vue/dist/Components/NcPasswordField.js' +import IconInfo from 'vue-material-design-icons/Information.vue' import IconPasswordGen from 'vue-material-design-icons/AutoFix.vue' import Config from '../../services/ConfigService' @@ -100,6 +103,7 @@ export default defineComponent({ name: 'NewFileRequestDialogDatePassword', components: { + IconInfo, IconPasswordGen, NcButton, NcCheckboxRadioSwitch, @@ -133,7 +137,7 @@ export default defineComponent({ setup() { return { - t: translate, + t, // Default expiration date if defaultExpireDateEnabled is true defaultExpireDate: sharingConfig.defaultExpireDate, @@ -159,19 +163,19 @@ export default defineComponent({ computed: { passwordAndExpirationSummary(): string { if (this.expirationDate && this.password) { - return this.t('files_sharing', 'The request will expire on {date} at midnight and will be password protected.', { + return t('files_sharing', 'The request will expire on {date} at midnight and will be password protected.', { date: this.expirationDate.toLocaleDateString(), }) } if (this.expirationDate) { - return this.t('files_sharing', 'The request will expire on {date} at midnight.', { + return t('files_sharing', 'The request will expire on {date} at midnight.', { date: this.expirationDate.toLocaleDateString(), }) } if (this.password) { - return this.t('files_sharing', 'The request will be password protected.') + return t('files_sharing', 'The request will be password protected.') } return '' @@ -232,5 +236,11 @@ export default defineComponent({ display: flex; align-items: flex-start; gap: 8px; + // Compensate label gab with legend + margin-top: 12px; + > div { + // Force margin to 0 as we handle it above + margin: 0; + } } </style> diff --git a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogFinish.vue b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogFinish.vue index 19753b8e6e2..e626cb31751 100644 --- a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogFinish.vue +++ b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogFinish.vue @@ -7,7 +7,7 @@ <div> <!-- Request note --> <NcNoteCard type="success"> - {{ t('files_sharing', 'Once created, you can share the link below to allow people to upload files to your directory.') }} + {{ t('files_sharing', 'You can now share the link below to allow people to upload files to your directory.') }} </NcNoteCard> <!-- Copy share link --> @@ -18,7 +18,7 @@ :show-trailing-button="true" :trailing-button-label="t('files_sharing', 'Copy to clipboard')" @click="copyShareLink" - @click-trailing-button="copyShareLink"> + @trailing-button-click="copyShareLink"> <template #trailing-button-icon> <IconCheck v-if="isCopied" :size="20" /> <IconClipboard v-else :size="20" /> @@ -32,7 +32,8 @@ :placeholder="t('files_sharing', 'Enter an email address or paste a list')" type="email" @keypress.enter.stop="addNewEmail" - @paste.stop.prevent="onPasteEmails" /> + @paste.stop.prevent="onPasteEmails" + @focusout.native="addNewEmail" /> <!-- Email list --> <div v-if="emails.length > 0" class="file-request-dialog__emails"> @@ -44,9 +45,10 @@ <template #icon> <NcAvatar :disable-menu="true" :disable-tooltip="true" - :is-guest="true" - :size="24" - :user="mail" /> + :display-name="mail" + :is-no-user="true" + :show-user-status="false" + :size="24" /> </template> </NcChip> </div> @@ -61,7 +63,7 @@ import Share from '../../models/Share' import { defineComponent } from 'vue' import { generateUrl, getBaseUrl } from '@nextcloud/router' import { showError, showSuccess } from '@nextcloud/dialogs' -import { translate, translatePlural } from '@nextcloud/l10n' +import { n, t } from '@nextcloud/l10n' import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js' import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js' @@ -70,7 +72,7 @@ import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' import NcChip from '@nextcloud/vue/dist/Components/NcChip.js' import IconCheck from 'vue-material-design-icons/Check.vue' -import IconClipboard from 'vue-material-design-icons/Clipboard.vue' +import IconClipboard from 'vue-material-design-icons/ClipboardText.vue' export default defineComponent({ name: 'NewFileRequestDialogFinish', @@ -104,8 +106,7 @@ export default defineComponent({ setup() { return { - n: translatePlural, - t: translate, + n, t, } }, @@ -131,13 +132,13 @@ export default defineComponent({ if (!navigator.clipboard) { // Clipboard API not available - window.prompt(this.t('files_sharing', 'Automatically copying failed, please copy the share link manually'), this.shareLink) + window.prompt(t('files_sharing', 'Automatically copying failed, please copy the share link manually'), this.shareLink) return } await navigator.clipboard.writeText(this.shareLink) - showSuccess(this.t('files_sharing', 'Link copied to clipboard')) + showSuccess(t('files_sharing', 'Link copied to clipboard')) this.isCopied = true event.target?.select?.() @@ -147,7 +148,15 @@ export default defineComponent({ }, addNewEmail(e: KeyboardEvent) { + if (this.email.trim() === '') { + return + } + if (e.target instanceof HTMLInputElement) { + // Reset the custom validity + e.target.setCustomValidity('') + + // Check if the field is valid if (e.target.checkValidity() === false) { e.target.reportValidity() return @@ -155,13 +164,14 @@ export default defineComponent({ // The email is already in the list if (this.emails.includes(this.email.trim())) { - e.target.setCustomValidity(this.t('files_sharing', 'Email already added')) + e.target.setCustomValidity(t('files_sharing', 'Email already added')) e.target.reportValidity() return } + // Check if the email is valid if (!this.isValidEmail(this.email.trim())) { - e.target.setCustomValidity(this.t('files_sharing', 'Invalid email address')) + e.target.setCustomValidity(t('files_sharing', 'Invalid email address')) e.target.reportValidity() return } @@ -188,24 +198,24 @@ export default defineComponent({ // Warn about invalid emails if (invalidEmails.length > 0) { - showError(this.n('files_sharing', 'The following email address is not valid: {emails}', 'The following email addresses are not valid: {emails}', invalidEmails.length, { emails: invalidEmails.join(', ') })) + showError(n('files_sharing', 'The following email address is not valid: {emails}', 'The following email addresses are not valid: {emails}', invalidEmails.length, { emails: invalidEmails.join(', ') })) } // Warn about duplicate emails if (duplicateEmails.length > 0) { - showError(this.n('files_sharing', '1 email address already added', '{count} email addresses already added', duplicateEmails.length, { count: duplicateEmails.length })) + showError(n('files_sharing', '1 email address already added', '{count} email addresses already added', duplicateEmails.length, { count: duplicateEmails.length })) } if (validEmails.length > 0) { - showSuccess(this.n('files_sharing', '1 email address added', '{count} email addresses added', validEmails.length, { count: validEmails.length })) + showSuccess(n('files_sharing', '1 email address added', '{count} email addresses added', validEmails.length, { count: validEmails.length })) } this.email = '' }, - isValidEmail(email) { - const regExpEmail = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ - return regExpEmail.test(email) + // No need to have a fancy regex, just check for an @ + isValidEmail(email: string): boolean { + return email.includes('@') }, }, }) @@ -213,7 +223,7 @@ export default defineComponent({ <style scoped> .input-field, .file-request-dialog__emails { - margin-top: var(--secondary-margin); + margin-top: var(--margin); } .file-request-dialog__emails { diff --git a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogIntro.vue b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogIntro.vue index c995cdc2442..805b13fdf95 100644 --- a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogIntro.vue +++ b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogIntro.vue @@ -26,7 +26,6 @@ </legend> <NcTextField :value="destination" :disabled="disabled" - :helper-text="t('files_sharing', 'The uploaded files are visible only to you unless you choose to share them.')" :label="t('files_sharing', 'Upload destination')" :minlength="2/* cannot share root */" :placeholder="t('files_sharing', 'Select a destination')" @@ -42,6 +41,11 @@ @trailing-button-click="$emit('update:destination', '')"> <IconFolder :size="18" /> </NcTextField> + + <p class="file-request-dialog__info"> + <IconLock :size="18" class="file-request-dialog__info-icon" /> + {{ t('files_sharing', 'The uploaded files are visible only to you unless you choose to share them.') }} + </p> </fieldset> <!-- Request note --> @@ -56,6 +60,11 @@ :required="false" name="note" @update:value="$emit('update:note', $event)" /> + + <p class="file-request-dialog__info"> + <IconInfo :size="18" class="file-request-dialog__info-icon" /> + {{ t('files_sharing', 'You can add links, date or any other information that will help the recipient understand what you are requesting.') }} + </p> </fieldset> </div> </template> @@ -66,9 +75,11 @@ import type { Folder, Node } from '@nextcloud/files' import { defineComponent } from 'vue' import { getFilePickerBuilder } from '@nextcloud/dialogs' -import { translate } from '@nextcloud/l10n' +import { t } from '@nextcloud/l10n' import IconFolder from 'vue-material-design-icons/Folder.vue' +import IconInfo from 'vue-material-design-icons/Information.vue' +import IconLock from 'vue-material-design-icons/Lock.vue' import NcTextArea from '@nextcloud/vue/dist/Components/NcTextArea.js' import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' @@ -77,6 +88,8 @@ export default defineComponent({ components: { IconFolder, + IconInfo, + IconLock, NcTextArea, NcTextField, }, @@ -113,17 +126,17 @@ export default defineComponent({ setup() { return { - t: translate, + t, } }, methods: { onPickDestination() { - const filepicker = getFilePickerBuilder(this.t('files_sharing', 'Select a destination')) + const filepicker = getFilePickerBuilder(t('files_sharing', 'Select a destination')) .addMimeTypeFilter('httpd/unix-directory') .allowDirectories(true) .addButton({ - label: this.t('files_sharing', 'Select'), + label: t('files_sharing', 'Select'), callback: this.onPickedDestination, }) .setFilter(node => node.path !== '/') diff --git a/apps/files_sharing/src/components/SelectShareFolderDialogue.vue b/apps/files_sharing/src/components/SelectShareFolderDialogue.vue index 1b717da8b67..ec29aff9b8a 100644 --- a/apps/files_sharing/src/components/SelectShareFolderDialogue.vue +++ b/apps/files_sharing/src/components/SelectShareFolderDialogue.vue @@ -57,7 +57,7 @@ export default { async pickFolder() { // Setup file picker - const picker = getFilePickerBuilder(t('files', 'Choose a default folder for accepted shares')) + const picker = getFilePickerBuilder(t('files_sharing', 'Choose a default folder for accepted shares')) .startAt(this.readableDirectory) .setMultiSelect(false) .setType(1) @@ -69,7 +69,7 @@ export default { // Init user folder picking const dir = await picker.pick() || '/' if (!dir.startsWith('/')) { - throw new Error(t('files', 'Invalid path selected')) + throw new Error(t('files_sharing', 'Invalid path selected')) } // Fix potential path issues and save results @@ -78,7 +78,7 @@ export default { shareFolder: this.directory, }) } catch (error) { - showError(error.message || t('files', 'Unknown error')) + showError(error.message || t('files_sharing', 'Unknown error')) } }, diff --git a/apps/files_sharing/src/components/SharingEntryLink.vue b/apps/files_sharing/src/components/SharingEntryLink.vue index 7fad50ebc95..ad3f4033b28 100644 --- a/apps/files_sharing/src/components/SharingEntryLink.vue +++ b/apps/files_sharing/src/components/SharingEntryLink.vue @@ -25,8 +25,8 @@ </div> <!-- clipboard --> - <NcActions v-if="share && !isEmailShareType && share.token" ref="copyButton" class="sharing-entry__copy"> - <NcActionButton :title="copyLinkTooltip" + <NcActions v-if="share && (!isEmailShareType || isFileRequest) && share.token" ref="copyButton" class="sharing-entry__copy"> + <NcActionButton :title="copyLinkTooltip" :aria-label="copyLinkTooltip" @click.prevent="copyLink"> <template #icon> diff --git a/apps/files_sharing/src/new/newFileRequest.ts b/apps/files_sharing/src/new/newFileRequest.ts index b7e5b3f2144..79f9eae3098 100644 --- a/apps/files_sharing/src/new/newFileRequest.ts +++ b/apps/files_sharing/src/new/newFileRequest.ts @@ -4,22 +4,27 @@ */ import type { Entry, Folder, Node } from '@nextcloud/files' +import { Permission } from '@nextcloud/files' import { translate as t } from '@nextcloud/l10n' -import Vue, { defineAsyncComponent } from 'vue' import FileUploadSvg from '@mdi/svg/svg/file-upload.svg?raw' +import Vue, { defineAsyncComponent } from 'vue' +import Config from '../services/ConfigService' const NewFileRequestDialogVue = defineAsyncComponent(() => import('../components/NewFileRequestDialog.vue')) +const sharingConfig = new Config() + export const entry = { id: 'file-request', - displayName: t('files', 'Create new file request'), + displayName: t('files_sharing', 'Create file request'), iconSvgInline: FileUploadSvg, order: 30, - enabled(): boolean { - // TODO: determine requirements - // 1. user can share the root folder - // 2. OR user can create subfolders ? - return true + enabled(context: Folder): boolean { + if ((context.permissions & Permission.SHARE) !== 0) { + // We need to have either link shares creation permissions + return sharingConfig.isPublicShareAllowed + } + return false }, async handler(context: Folder, content: Node[]) { // Create document root diff --git a/apps/files_sharing/src/public-file-request.ts b/apps/files_sharing/src/public-file-request.ts new file mode 100644 index 00000000000..763c4f60624 --- /dev/null +++ b/apps/files_sharing/src/public-file-request.ts @@ -0,0 +1,23 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import { spawnDialog } from '@nextcloud/dialogs' +import { defineAsyncComponent } from 'vue' +import logger from './services/logger' + +const nick = localStorage.getItem('nick') +const publicAuthPromptShown = localStorage.getItem('publicAuthPromptShown') + +// If we don't have a nickname or the public auth prompt hasn't been shown yet, show it +// We still show the prompt if the user has a nickname to double check +if (!nick || !publicAuthPromptShown) { + spawnDialog( + defineAsyncComponent(() => import('./views/PublicAuthPrompt.vue')), + {}, + () => localStorage.setItem('publicAuthPromptShown', 'true'), + ) +} else { + logger.debug(`Public auth prompt already shown. Current nickname is '${nick}'`) +} diff --git a/apps/files_sharing/src/services/ConfigService.ts b/apps/files_sharing/src/services/ConfigService.ts index f8f7994bdab..94db0454428 100644 --- a/apps/files_sharing/src/services/ConfigService.ts +++ b/apps/files_sharing/src/services/ConfigService.ts @@ -211,13 +211,20 @@ export default class Config { } /** + * Is public sharing enabled ? + */ + get isPublicShareAllowed(): boolean { + return this._capabilities?.files_sharing?.public?.enabled === true + } + + /** * Is sharing my mail (link share) enabled ? */ get isMailShareAllowed(): boolean { // eslint-disable-next-line camelcase return this._capabilities?.files_sharing?.sharebymail?.enabled === true // eslint-disable-next-line camelcase - && this._capabilities?.files_sharing?.public?.enabled === true + && this.isPublicShareAllowed === true } /** diff --git a/apps/files_sharing/src/views/PublicAuthPrompt.vue b/apps/files_sharing/src/views/PublicAuthPrompt.vue new file mode 100644 index 00000000000..a929afffefb --- /dev/null +++ b/apps/files_sharing/src/views/PublicAuthPrompt.vue @@ -0,0 +1,136 @@ +<!-- + - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> + +<template> + <NcDialog class="public-auth-prompt" + dialog-classes="public-auth-prompt__dialog" + :can-close="false" + :name="dialogName"> + <h3 v-if="owner" class="public-auth-prompt__subtitle"> + {{ t('files_sharing', '{ownerDisplayName} shared a folder with you.', { ownerDisplayName }) }} + </h3> + + <!-- Header --> + <NcNoteCard type="info" class="public-auth-prompt__header"> + <p id="public-auth-prompt-dialog-description" class="public-auth-prompt__description"> + {{ t('files_sharing', 'To upload files, you need to provide your name first.') }} + </p> + </NcNoteCard> + + <!-- Form --> + <form ref="form" + aria-describedby="public-auth-prompt-dialog-description" + class="public-auth-prompt__form" + @submit.prevent.stop=""> + <NcTextField ref="input" + class="public-auth-prompt__input" + :label="t('files_sharing', 'Enter your name')" + name="name" + :required="true" + :minlength="2" + :value.sync="name" /> + </form> + + <!-- Submit --> + <template #actions> + <NcButton ref="submit" + :disabled="name.trim() === ''" + @click="onSubmit"> + {{ t('files_sharing', 'Submit name') }} + </NcButton> + </template> + </NcDialog> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue' +import { t } from '@nextcloud/l10n' + +import NcButton from '@nextcloud/vue/dist/Components/NcButton.js' +import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js' +import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js' +import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js' +import { loadState } from '@nextcloud/initial-state' + +export default defineComponent({ + name: 'PublicAuthPrompt', + + components: { + NcButton, + NcDialog, + NcNoteCard, + NcTextField, + }, + + setup() { + return { + t, + + owner: loadState('files_sharing', 'owner', ''), + ownerDisplayName: loadState('files_sharing', 'ownerDisplayName', ''), + label: loadState('files_sharing', 'label', ''), + note: loadState('files_sharing', 'note', ''), + filename: loadState('files_sharing', 'filename', ''), + } + }, + + data() { + return { + name: '', + } + }, + + computed: { + dialogName() { + return this.t('files_sharing', 'Upload files to {folder}', { folder: this.label || this.filename }) + }, + }, + + beforeMount() { + // Pre-load the name from local storage if already set by another app + // like Talk, Colabora or Text... + const talkNick = localStorage.getItem('nick') + if (talkNick) { + this.name = talkNick + } + }, + + methods: { + onSubmit() { + const form = this.$refs.form as HTMLFormElement + if (!form.checkValidity()) { + form.reportValidity() + return + } + + if (this.name.trim() === '') { + return + } + + localStorage.setItem('nick', this.name) + this.$emit('close') + }, + }, +}) +</script> +<style lang="scss"> +.public-auth-prompt { + &__subtitle { + // Smaller than dialog title + font-size: 16px; + margin-block: 12px; + } + + &__header { + // Fix extra margin generating an unwanted gap + margin-block: 12px; + } + + &__form { + // Double the margin of the header + margin-block: 24px; + } +} +</style> diff --git a/apps/files_sharing/src/views/SharingDetailsTab.vue b/apps/files_sharing/src/views/SharingDetailsTab.vue index 023c137a254..a0cb71fc392 100644 --- a/apps/files_sharing/src/views/SharingDetailsTab.vue +++ b/apps/files_sharing/src/views/SharingDetailsTab.vue @@ -603,7 +603,10 @@ export default { return (this.fileInfo.canDownload() || this.canDownload) }, canRemoveReadPermission() { - return this.allowsFileDrop && this.share.type === this.SHARE_TYPES.SHARE_TYPE_LINK + return this.allowsFileDrop && ( + this.share.type === this.SHARE_TYPES.SHARE_TYPE_LINK + || this.share.type === this.SHARE_TYPES.SHARE_TYPE_EMAIL + ) }, // if newPassword exists, but is empty, it means // the user deleted the original password diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index 7de2bd3075e..7620d309ff6 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -27,6 +27,7 @@ <input type="hidden" name="mimetypeIcon" value="<?php p(\OC::$server->getMimeTypeDetector()->mimeTypeIcon($_['mimetype'])); ?>" id="mimetypeIcon"> <input type="hidden" name="hideDownload" value="<?php p($_['hideDownload'] ? 'true' : 'false'); ?>" id="hideDownload"> <input type="hidden" id="disclaimerText" value="<?php p($_['disclaimer']) ?>"> + <?php $upload_max_filesize = OC::$server->get(\bantu\IniGetWrapper\IniGetWrapper::class)->getBytes('upload_max_filesize'); $post_max_size = OC::$server->get(\bantu\IniGetWrapper\IniGetWrapper::class)->getBytes('post_max_size'); @@ -102,11 +103,11 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size); class="emptycontent <?php if (!empty($_['note'])) { ?>has-note<?php } ?>"> <?php if ($_['shareOwner']) { ?> <div id="displayavatar"><div class="avatardiv"></div></div> - <h2><?php p($l->t('Upload files to %s', [$_['shareOwner']])) ?></h2> - <p><span class="icon-folder"></span> <?php p($_['filename']) ?></p> + <h2><?php p($l->t('Upload files to %s', [$_['label'] ?: $_['filename']])) ?></h2> + <p><?php p($l->t('%s shared a folder with you.', [$_['shareOwner']])) ?></p> <?php } else { ?> <div id="displayavatar"><span class="icon-folder"></span></div> - <h2><?php p($l->t('Upload files to %s', [$_['filename']])) ?></h2> + <h2><?php p($l->t('Upload files to %s', [$_['label'] ?: $_['filename']])) ?></h2> <?php } ?> <?php if (empty($_['note']) === false) { ?> diff --git a/apps/files_sharing/tests/Controller/ShareControllerTest.php b/apps/files_sharing/tests/Controller/ShareControllerTest.php index 493ac10a24b..79b90d8a156 100644 --- a/apps/files_sharing/tests/Controller/ShareControllerTest.php +++ b/apps/files_sharing/tests/Controller/ShareControllerTest.php @@ -22,6 +22,7 @@ use OCP\AppFramework\Http\Template\ExternalShareMenuAction; use OCP\AppFramework\Http\Template\LinkMenuAction; use OCP\AppFramework\Http\Template\PublicTemplateResponse; use OCP\AppFramework\Http\Template\SimpleMenuAction; +use OCP\AppFramework\Services\IInitialState; use OCP\Constants; use OCP\Defaults; use OCP\EventDispatcher\IEventDispatcher; @@ -121,6 +122,7 @@ class ShareControllerTest extends \Test\TestCase { $this->defaults, $this->config, $this->createMock(IRequest::class), + $this->createMock(IInitialState::class) ) ); @@ -350,7 +352,8 @@ class ShareControllerTest extends \Test\TestCase { 'previewURL' => 'downloadURL', 'note' => $note, 'hideDownload' => false, - 'showgridview' => false + 'showgridview' => false, + 'label' => '' ]; $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); @@ -511,7 +514,8 @@ class ShareControllerTest extends \Test\TestCase { 'previewURL' => 'downloadURL', 'note' => $note, 'hideDownload' => false, - 'showgridview' => false + 'showgridview' => false, + 'label' => '' ]; $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); @@ -672,7 +676,8 @@ class ShareControllerTest extends \Test\TestCase { 'previewURL' => 'downloadURL', 'note' => $note, 'hideDownload' => true, - 'showgridview' => false + 'showgridview' => false, + 'label' => '' ]; $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); @@ -798,7 +803,8 @@ class ShareControllerTest extends \Test\TestCase { 'previewURL' => '', 'note' => '', 'hideDownload' => false, - 'showgridview' => false + 'showgridview' => false, + 'label' => '' ]; $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); |