aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_sharing
diff options
context:
space:
mode:
authorFerdinand Thiessen <opensource@fthiessen.de>2024-06-04 01:25:28 +0200
committerFerdinand Thiessen <opensource@fthiessen.de>2024-09-06 03:38:42 +0200
commit96c827558611033ac35f6095b77ea04dca8044dd (patch)
tree9657a6d191dba82ae025c23838b2b35716c415cf /apps/files_sharing
parente4fa9967014e99a70fcf0f45e84b35f610cedeb6 (diff)
downloadnextcloud-server-96c827558611033ac35f6095b77ea04dca8044dd.tar.gz
nextcloud-server-96c827558611033ac35f6095b77ea04dca8044dd.zip
feat(files_sharing): Migrate public share to use Vue files list
Co-authored-by: Ferdinand Thiessen <opensource@fthiessen.de> Co-authored-by: Côme Chilliet <91878298+come-nc@users.noreply.github.com> Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'apps/files_sharing')
-rw-r--r--apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php303
-rw-r--r--apps/files_sharing/src/init-public.ts28
-rw-r--r--apps/files_sharing/src/router/index.ts54
-rw-r--r--apps/files_sharing/src/services/SharingService.spec.ts4
-rw-r--r--apps/files_sharing/src/services/SharingService.ts13
-rw-r--r--apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue67
-rw-r--r--apps/files_sharing/src/views/publicFileDrop.ts59
-rw-r--r--apps/files_sharing/src/views/publicFileShare.ts67
-rw-r--r--apps/files_sharing/src/views/publicShare.ts28
-rw-r--r--apps/files_sharing/tests/Controller/ShareControllerTest.php498
10 files changed, 591 insertions, 530 deletions
diff --git a/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php b/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php
index 8792c385a18..4feaac82dc0 100644
--- a/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php
+++ b/apps/files_sharing/lib/DefaultPublicShareTemplateProvider.php
@@ -20,11 +20,10 @@ 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;
-use OCP\Files\FileInfo;
-use OCP\Files\Folder;
+use OCP\Files\File;
+use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IPreview;
@@ -34,7 +33,6 @@ use OCP\IUser;
use OCP\IUserManager;
use OCP\Share\IPublicShareTemplateProvider;
use OCP\Share\IShare;
-use OCP\Template;
use OCP\Util;
class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider {
@@ -51,6 +49,7 @@ class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider
private IConfig $config,
private IRequest $request,
private IInitialState $initialState,
+ private IAppConfig $appConfig,
) {
}
@@ -60,113 +59,142 @@ class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider
public function renderPage(IShare $share, string $token, string $path): TemplateResponse {
$shareNode = $share->getNode();
+ $ownerName = '';
+ $ownerId = '';
- $shareTmpl = [];
- $shareTmpl['owner'] = '';
- $shareTmpl['shareOwner'] = '';
-
+ // Only make the share owner public if they allowed to show their name
$owner = $this->userManager->get($share->getShareOwner());
if ($owner instanceof IUser) {
$ownerAccount = $this->accountManager->getAccount($owner);
- $ownerName = $ownerAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME);
- 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']);
+ $ownerNameProperty = $ownerAccount->getProperty(IAccountManager::PROPERTY_DISPLAYNAME);
+ if ($ownerNameProperty->getScope() === IAccountManager::SCOPE_PUBLISHED) {
+ $ownerName = $owner->getDisplayName();
+ $ownerId = $owner->getUID();
}
}
- // Provide initial state
- $this->initialState->provideInitialState('label', $share->getLabel());
- $this->initialState->provideInitialState('note', $share->getNote());
+ $view = 'public-share';
+ if ($shareNode instanceof File) {
+ $view = 'public-file-share';
+ } elseif (($share->getPermissions() & \OCP\Constants::PERMISSION_CREATE)
+ && !($share->getPermissions() & \OCP\Constants::PERMISSION_READ)
+ ) {
+ // share is a folder with create but no read permissions -> file drop only
+ $view = 'public-file-drop';
+ // Only needed for file drops
+ $this->initialState->provideInitialState(
+ 'disclaimer',
+ $this->appConfig->getValueString('core', 'shareapi_public_link_disclaimertext'),
+ );
+ }
+ // Set up initial state
+ $this->initialState->provideInitialState('isPublic', true);
+ $this->initialState->provideInitialState('sharingToken', $token);
$this->initialState->provideInitialState('filename', $shareNode->getName());
+ $this->initialState->provideInitialState('view', $view);
+
+ // Load scripts and styles for UI
+ \OCP\Util::addInitScript('files', 'init');
+ \OCP\Util::addInitScript(Application::APP_ID, 'init');
+ \OCP\Util::addInitScript(Application::APP_ID, 'init-public');
+ \OCP\Util::addScript('files', 'main');
+ \OCP\Util::addStyle('files', 'merged');
+
+ // Add file-request script if needed
+ $attributes = $share->getAttributes();
+ $isFileRequest = $attributes?->getAttribute('fileRequest', 'enabled') === true;
+ if ($isFileRequest) {
+ Util::addScript(Application::APP_ID, 'public-file-request');
+ }
- $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());
- $shareTmpl['dirToken'] = $token;
- $shareTmpl['sharingToken'] = $token;
- $shareTmpl['server2serversharing'] = $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
- $shareTmpl['protected'] = $share->getPassword() !== null ? 'true' : 'false';
- $shareTmpl['dir'] = '';
- $shareTmpl['nonHumanFileSize'] = $shareNode->getSize();
- $shareTmpl['fileSize'] = Util::humanFileSize($shareNode->getSize());
- $shareTmpl['hideDownload'] = $share->getHideDownload();
-
- $hideFileList = false;
-
- if ($shareNode instanceof Folder) {
- $shareIsFolder = true;
-
- $folderNode = $shareNode->get($path);
- $shareTmpl['dir'] = $shareNode->getRelativePath($folderNode->getPath());
+ // Load Viewer scripts
+ if (class_exists(LoadViewer::class)) {
+ $this->eventDispatcher->dispatchTyped(new LoadViewer());
+ }
- /*
- * The OC_Util methods require a view. This just uses the node API
- */
- $freeSpace = $share->getNode()->getStorage()->free_space($share->getNode()->getInternalPath());
- if ($freeSpace < FileInfo::SPACE_UNLIMITED) {
- $freeSpace = (int)max($freeSpace, 0);
- } else {
- $freeSpace = (INF > 0) ? INF: PHP_INT_MAX; // work around https://bugs.php.net/bug.php?id=69188
- }
+ // Allow external apps to register their scripts
+ $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($share));
- $hideFileList = !($share->getPermissions() & Constants::PERMISSION_READ);
- $maxUploadFilesize = $freeSpace;
+ // OpenGraph Support: http://ogp.me/
+ $this->addOpenGraphHeaders($share);
- $folder = new Template('files', 'list', '');
+ // CSP to allow office
+ $csp = new ContentSecurityPolicy();
+ $csp->addAllowedFrameDomain('\'self\'');
- $folder->assign('dir', $shareNode->getRelativePath($folderNode->getPath()));
- $folder->assign('dirToken', $token);
- $folder->assign('permissions', Constants::PERMISSION_READ);
- $folder->assign('isPublic', true);
- $folder->assign('hideFileList', $hideFileList);
- $folder->assign('publicUploadEnabled', 'no');
- // default to list view
- $folder->assign('showgridview', false);
- $folder->assign('uploadMaxFilesize', $maxUploadFilesize);
- $folder->assign('uploadMaxHumanFilesize', Util::humanFileSize($maxUploadFilesize));
- $folder->assign('freeSpace', $freeSpace);
- $folder->assign('usedSpacePercent', 0);
- $folder->assign('trash', false);
- $shareTmpl['folder'] = $folder->fetchPage();
+ $response = new PublicTemplateResponse(
+ 'files',
+ 'index',
+ );
+ $response->setContentSecurityPolicy($csp);
+ // If the share has a label, use it as the title
+ if ($share->getLabel() !== '') {
+ $response->setHeaderTitle($share->getLabel());
} else {
- $shareIsFolder = false;
+ $response->setHeaderTitle($shareNode->getName());
+ }
+ if ($ownerName !== '') {
+ $response->setHeaderDetails($this->l10n->t('shared by %s', [$ownerName]));
}
- // default to list view
- $shareTmpl['showgridview'] = false;
-
- $shareTmpl['hideFileList'] = $hideFileList;
- $shareTmpl['downloadURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', [
- 'token' => $token,
- 'filename' => $shareIsFolder ? null : $shareNode->getName()
- ]);
- $shareTmpl['shareUrl'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $token]);
- $shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
- $shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
- $shareTmpl['previewMaxX'] = $this->config->getSystemValue('preview_max_x', 1024);
- $shareTmpl['previewMaxY'] = $this->config->getSystemValue('preview_max_y', 1024);
- $shareTmpl['disclaimer'] = $this->config->getAppValue('core', 'shareapi_public_link_disclaimertext', '');
- $shareTmpl['previewURL'] = $shareTmpl['downloadURL'];
+ // Create the header action menu
+ $headerActions = [];
+ if ($view !== 'public-file-drop') {
+ // The download URL is used for the "download" header action as well as in some cases for the direct link
+ $downloadUrl = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', [
+ 'token' => $token,
+ 'filename' => ($shareNode instanceof File) ? $shareNode->getName() : null,
+ ]);
+
+ // If not a file drop, then add the download header action
+ $headerActions[] = new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $downloadUrl, 0, (string)$shareNode->getSize());
+
+ // If remote sharing is enabled also add the remote share action to the menu
+ if ($this->federatedShareProvider->isOutgoingServer2serverShareEnabled() && !$share->getHideDownload()) {
+ $headerActions[] = new ExternalShareMenuAction(
+ // TRANSLATORS The placeholder refers to the software product name as in 'Add to your Nextcloud'
+ $this->l10n->t('Add to your %s', [$this->defaults->getProductName()]),
+ 'icon-external',
+ $ownerId,
+ $ownerName,
+ $shareNode->getName(),
+ );
+ }
+ }
- if ($shareTmpl['previewSupported']) {
- $shareTmpl['previewImage'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview',
- ['x' => 200, 'y' => 200, 'file' => $shareTmpl['directory_path'], 'token' => $shareTmpl['dirToken']]);
- $ogPreview = $shareTmpl['previewImage'];
+ $shareUrl = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $token]);
+ // By default use the share link as the direct link
+ $directLink = $shareUrl;
+ // Add the direct link header actions
+ if ($shareNode->getMimePart() === 'image') {
+ // If this is a file and especially an image directly point to the image preview
+ $directLink = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $token]);
+ } elseif (($share->getPermissions() & \OCP\Constants::PERMISSION_READ) && !$share->getHideDownload()) {
+ // Can read and no download restriction, so just download it
+ $directLink = $downloadUrl ?? $shareUrl;
+ }
+ $headerActions[] = new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $directLink);
+ $response->setHeaderActions($headerActions);
- // We just have direct previews for image files
- if ($shareNode->getMimePart() === 'image') {
- $shareTmpl['previewURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $token]);
+ return $response;
+ }
- $ogPreview = $shareTmpl['previewURL'];
+ /**
+ * Add OpenGraph headers to response for preview
+ * @param IShare $share The share for which to add the headers
+ */
+ protected function addOpenGraphHeaders(IShare $share): void {
+ $shareNode = $share->getNode();
+ $token = $share->getToken();
+ $shareUrl = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $token]);
- //Whatapp is kind of picky about their size requirements
+ // Handle preview generation for OpenGraph
+ if ($this->previewManager->isMimeSupported($shareNode->getMimetype())) {
+ // For images we can use direct links
+ if ($shareNode->getMimePart() === 'image') {
+ $ogPreview = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $token]);
+ // Whatsapp is kind of picky about their size requirements
if ($this->request->isUserAgent(['/^WhatsApp/'])) {
$ogPreview = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview', [
'token' => $token,
@@ -175,93 +203,28 @@ class DefaultPublicShareTemplateProvider implements IPublicShareTemplateProvider
'a' => true,
]);
}
+ } else {
+ // For normal files use preview API
+ $ogPreview = $this->urlGenerator->linkToRouteAbsolute(
+ 'files_sharing.PublicPreview.getPreview',
+ [
+ 'x' => 256,
+ 'y' => 256,
+ 'file' => $share->getTarget(),
+ 'token' => $token,
+ ],
+ );
}
} else {
- $shareTmpl['previewImage'] = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-fb.png'));
- $ogPreview = $shareTmpl['previewImage'];
+ // No preview supported, so we just add the favicon
+ $ogPreview = $this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'favicon-fb.png'));
}
- // Load files we need
- Util::addScript('files', 'semaphore');
- Util::addScript('files', 'file-upload');
- Util::addStyle('files_sharing', 'publicView');
- Util::addScript('files_sharing', 'public');
- Util::addScript('files_sharing', 'templates');
- Util::addScript('files', 'fileactions');
- Util::addScript('files', 'fileactionsmenu');
- Util::addScript('files', 'jquery.fileupload');
- Util::addScript('files_sharing', 'files_drop');
-
- if (isset($shareTmpl['folder'])) {
- // JS required for folders
- Util::addStyle('files', 'merged');
- Util::addScript('files', 'filesummary');
- Util::addScript('files', 'templates');
- Util::addScript('files', 'breadcrumb');
- Util::addScript('files', 'fileinfomodel');
- Util::addScript('files', 'newfilemenu');
- Util::addScript('files', 'files');
- Util::addScript('files', 'filemultiselectmenu');
- Util::addScript('files', 'filelist');
- Util::addScript('files', 'keyboardshortcuts');
- Util::addScript('files', 'operationprogressbar');
- }
-
- // Load Viewer scripts
- if (class_exists(LoadViewer::class)) {
- $this->eventDispatcher->dispatchTyped(new LoadViewer());
- }
- // OpenGraph Support: http://ogp.me/
- Util::addHeader('meta', ['property' => 'og:title', 'content' => $shareTmpl['filename']]);
+ Util::addHeader('meta', ['property' => 'og:title', 'content' => $shareNode->getName()]);
Util::addHeader('meta', ['property' => 'og:description', 'content' => $this->defaults->getName() . ($this->defaults->getSlogan() !== '' ? ' - ' . $this->defaults->getSlogan() : '')]);
Util::addHeader('meta', ['property' => 'og:site_name', 'content' => $this->defaults->getName()]);
- Util::addHeader('meta', ['property' => 'og:url', 'content' => $shareTmpl['shareUrl']]);
+ Util::addHeader('meta', ['property' => 'og:url', 'content' => $shareUrl]);
Util::addHeader('meta', ['property' => 'og:type', 'content' => 'object']);
Util::addHeader('meta', ['property' => 'og:image', 'content' => $ogPreview]);
-
- $this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($share));
-
- $csp = new ContentSecurityPolicy();
- $csp->addAllowedFrameDomain('\'self\'');
-
- $response = new PublicTemplateResponse(Application::APP_ID, 'public', $shareTmpl);
- $response->setHeaderTitle($shareTmpl['filename']);
- if ($shareTmpl['shareOwner'] !== '') {
- $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()) {
- Util::addScript('files_sharing', 'public_note');
-
- $download = new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $shareTmpl['downloadURL'], 0, $shareTmpl['fileSize']);
- $downloadAll = new SimpleMenuAction('download', $this->l10n->t('Download all files'), 'icon-download', $shareTmpl['downloadURL'], 0, $shareTmpl['fileSize']);
- $directLink = new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $shareTmpl['previewURL']);
- // TRANSLATORS The placeholder refers to the software product name as in 'Add to your Nextcloud'
- $externalShare = new ExternalShareMenuAction($this->l10n->t('Add to your %s', [$this->defaults->getProductName()]), 'icon-external', $shareTmpl['owner'], $shareTmpl['shareOwner'], $shareTmpl['filename']);
-
- $responseComposer = [];
-
- if ($shareIsFolder) {
- $responseComposer[] = $downloadAll;
- } else {
- $responseComposer[] = $download;
- }
- $responseComposer[] = $directLink;
- if ($this->federatedShareProvider->isOutgoingServer2serverShareEnabled()) {
- $responseComposer[] = $externalShare;
- }
-
- $response->setHeaderActions($responseComposer);
- }
-
- $response->setContentSecurityPolicy($csp);
- return $response;
}
}
diff --git a/apps/files_sharing/src/init-public.ts b/apps/files_sharing/src/init-public.ts
new file mode 100644
index 00000000000..400ee73d2a1
--- /dev/null
+++ b/apps/files_sharing/src/init-public.ts
@@ -0,0 +1,28 @@
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+import { getNavigation, registerDavProperty } from '@nextcloud/files'
+import { loadState } from '@nextcloud/initial-state'
+import registerFileDropView from './views/publicFileDrop.ts'
+import registerPublicShareView from './views/publicShare.ts'
+import registerPublicFileShareView from './views/publicFileShare.ts'
+import RouterService from '../../files/src/services/RouterService'
+import router from './router'
+
+registerFileDropView()
+registerPublicShareView()
+registerPublicFileShareView()
+
+registerDavProperty('nc:share-attributes', { nc: 'http://nextcloud.org/ns' })
+registerDavProperty('oc:share-types', { oc: 'http://owncloud.org/ns' })
+registerDavProperty('ocs:share-permissions', { ocs: 'http://open-collaboration-services.org/ns' })
+
+// Get the current view from state and set it active
+const view = loadState<string>('files_sharing', 'view')
+const navigation = getNavigation()
+navigation.setActive(navigation.views.find(({ id }) => id === view) ?? null)
+
+// Force our own router
+window.OCP.Files = window.OCP.Files ?? {}
+window.OCP.Files.Router = new RouterService(router)
diff --git a/apps/files_sharing/src/router/index.ts b/apps/files_sharing/src/router/index.ts
new file mode 100644
index 00000000000..6a417975e32
--- /dev/null
+++ b/apps/files_sharing/src/router/index.ts
@@ -0,0 +1,54 @@
+/**
+ * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+import type { RawLocation, Route } from 'vue-router'
+import type { ErrorHandler } from 'vue-router/types/router.d.ts'
+
+import { loadState } from '@nextcloud/initial-state'
+import { generateUrl } from '@nextcloud/router'
+import queryString from 'query-string'
+import Router from 'vue-router'
+import Vue from 'vue'
+
+const view = loadState<string>('files_sharing', 'view')
+const sharingToken = loadState<string>('files_sharing', 'sharingToken')
+
+Vue.use(Router)
+
+// Prevent router from throwing errors when we're already on the page we're trying to go to
+const originalPush = Router.prototype.push as (to, onComplete?, onAbort?) => Promise<Route>
+Router.prototype.push = function push(to: RawLocation, onComplete?: ((route: Route) => void) | undefined, onAbort?: ErrorHandler | undefined): Promise<Route> {
+ if (onComplete || onAbort) return originalPush.call(this, to, onComplete, onAbort)
+ return originalPush.call(this, to).catch(err => err)
+}
+
+const router = new Router({
+ mode: 'history',
+
+ // if index.php is in the url AND we got this far, then it's working:
+ // let's keep using index.php in the url
+ base: generateUrl('/s'),
+ linkActiveClass: 'active',
+
+ routes: [
+ {
+ path: '/',
+ // Pretending we're using the default view
+ redirect: { name: 'filelist', params: { view, token: sharingToken } },
+ },
+ {
+ path: '/:token',
+ name: 'filelist',
+ props: true,
+ },
+ ],
+
+ // Custom stringifyQuery to prevent encoding of slashes in the url
+ stringifyQuery(query) {
+ const result = queryString.stringify(query).replace(/%2F/gmi, '/')
+ return result ? ('?' + result) : ''
+ },
+})
+
+export default router
diff --git a/apps/files_sharing/src/services/SharingService.spec.ts b/apps/files_sharing/src/services/SharingService.spec.ts
index ab0a5163618..daba81bd4f2 100644
--- a/apps/files_sharing/src/services/SharingService.spec.ts
+++ b/apps/files_sharing/src/services/SharingService.spec.ts
@@ -18,14 +18,12 @@ const axios = vi.hoisted(() => ({ get: vi.fn() }))
vi.mock('@nextcloud/auth')
vi.mock('@nextcloud/axios', () => ({ default: axios }))
-// Mock web root variable
+// Mock TAG
beforeAll(() => {
window.OC = {
...window.OC,
TAG_FAVORITE,
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- ;(window as any)._oc_webroot = ''
})
describe('SharingService methods definitions', () => {
diff --git a/apps/files_sharing/src/services/SharingService.ts b/apps/files_sharing/src/services/SharingService.ts
index e168f202fba..2f8144e216e 100644
--- a/apps/files_sharing/src/services/SharingService.ts
+++ b/apps/files_sharing/src/services/SharingService.ts
@@ -6,18 +6,17 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { AxiosPromise } from '@nextcloud/axios'
+import type { ContentsWithRoot } from '@nextcloud/files'
import type { OCSResponse } from '@nextcloud/typings/ocs'
import type { ShareAttribute } from '../sharing'
-import { Folder, File, type ContentsWithRoot, Permission } from '@nextcloud/files'
-import { generateOcsUrl, generateRemoteUrl } from '@nextcloud/router'
import { getCurrentUser } from '@nextcloud/auth'
+import { Folder, File, Permission, davRemoteURL, davRootPath } from '@nextcloud/files'
+import { generateOcsUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'
import logger from './logger'
-export const rootPath = `/files/${getCurrentUser()?.uid}`
-
const headers = {
'Content-Type': 'application/json',
}
@@ -57,7 +56,7 @@ const ocsEntryToNode = async function(ocsEntry: any): Promise<Folder | File | nu
// Generate path and strip double slashes
const path = ocsEntry.path || ocsEntry.file_target || ocsEntry.name
- const source = generateRemoteUrl(`dav/${rootPath}/${path}`.replaceAll(/\/\//gm, '/'))
+ const source = `${davRemoteURL}${davRootPath}/${path.replace(/^\/+/, '')}`
let mtime = ocsEntry.item_mtime ? new Date((ocsEntry.item_mtime) * 1000) : undefined
// Prefer share time if more recent than item mtime
@@ -73,7 +72,7 @@ const ocsEntryToNode = async function(ocsEntry: any): Promise<Folder | File | nu
mtime,
size: ocsEntry?.item_size,
permissions: ocsEntry?.item_permissions || ocsEntry?.permissions,
- root: rootPath,
+ root: davRootPath,
attributes: {
...ocsEntry,
'has-preview': hasPreview,
@@ -217,7 +216,7 @@ export const getContents = async (sharedWithYou = true, sharedWithOthers = true,
return {
folder: new Folder({
id: 0,
- source: generateRemoteUrl('dav' + rootPath),
+ source: `${davRemoteURL}${davRootPath}`,
owner: getCurrentUser()?.uid || null,
}),
contents,
diff --git a/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue b/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue
new file mode 100644
index 00000000000..538927623ed
--- /dev/null
+++ b/apps/files_sharing/src/views/FilesViewFileDropEmptyContent.vue
@@ -0,0 +1,67 @@
+<!--
+ - SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ - SPDX-License-Identifier: AGPL-3.0-or-later
+-->
+<template>
+ <NcEmptyContent class="file-drop-empty-content"
+ data-cy-files-sharing-file-drop
+ :name="t('files_sharing', 'File drop')">
+ <template #icon>
+ <NcIconSvgWrapper :svg="svgCloudUpload" />
+ </template>
+ <template #description>
+ {{ t('files_sharing', 'Upload files to {foldername}.', { foldername }) }}
+ {{ disclaimer === '' ? '' : t('files_sharing', 'By uploading files, you agree to the terms of service.') }}
+ </template>
+ <template #action>
+ <template v-if="disclaimer">
+ <!-- Terms of service if enabled -->
+ <NcButton type="primary" @click="showDialog = true">
+ {{ t('files_sharing', 'View terms of service') }}
+ </NcButton>
+ <NcDialog close-on-click-outside
+ content-classes="terms-of-service-dialog"
+ :open.sync="showDialog"
+ :name="t('files_sharing', 'Terms of service')"
+ :message="disclaimer" />
+ </template>
+ <UploadPicker allow-folders
+ :content="() => []"
+ no-menu
+ :destination="uploadDestination"
+ multiple />
+ </template>
+ </NcEmptyContent>
+</template>
+
+<script setup lang="ts">
+import { loadState } from '@nextcloud/initial-state'
+import { translate as t } from '@nextcloud/l10n'
+import { getUploader, UploadPicker } from '@nextcloud/upload'
+import { ref } from 'vue'
+
+import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
+import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
+import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
+import NcIconSvgWrapper from '@nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
+import svgCloudUpload from '@mdi/svg/svg/cloud-upload.svg?raw'
+
+defineProps<{
+ foldername: string
+}>()
+
+const disclaimer = loadState<string>('files_sharing', 'disclaimer', '')
+const showDialog = ref(false)
+const uploadDestination = getUploader().destination
+</script>
+
+<style scoped>
+:deep(.terms-of-service-dialog) {
+ min-height: min(100px, 20vh);
+}
+/* TODO fix in library */
+.file-drop-empty-content :deep(.empty-content__action) {
+ display: flex;
+ gap: var(--default-grid-baseline);
+}
+</style>
diff --git a/apps/files_sharing/src/views/publicFileDrop.ts b/apps/files_sharing/src/views/publicFileDrop.ts
new file mode 100644
index 00000000000..0d782d48fc7
--- /dev/null
+++ b/apps/files_sharing/src/views/publicFileDrop.ts
@@ -0,0 +1,59 @@
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+import type { VueConstructor } from 'vue'
+
+import { Folder, Permission, View, davRemoteURL, davRootPath, getNavigation } from '@nextcloud/files'
+import { loadState } from '@nextcloud/initial-state'
+import { translate as t } from '@nextcloud/l10n'
+import svgCloudUpload from '@mdi/svg/svg/cloud-upload.svg?raw'
+import Vue from 'vue'
+
+export default () => {
+ const foldername = loadState<string>('files_sharing', 'filename')
+
+ let FilesViewFileDropEmptyContent: VueConstructor
+ let fileDropEmptyContentInstance: Vue
+
+ const view = new View({
+ id: 'public-file-drop',
+ name: t('files_sharing', 'File drop'),
+ caption: t('files_sharing', 'Upload files to {foldername}', { foldername }),
+ icon: svgCloudUpload,
+ order: 1,
+
+ emptyView: async (div: HTMLDivElement) => {
+ if (FilesViewFileDropEmptyContent === undefined) {
+ const { default: component } = await import('../views/FilesViewFileDropEmptyContent.vue')
+ FilesViewFileDropEmptyContent = Vue.extend(component)
+ }
+ if (fileDropEmptyContentInstance) {
+ fileDropEmptyContentInstance.$destroy()
+ }
+ fileDropEmptyContentInstance = new FilesViewFileDropEmptyContent({
+ propsData: {
+ foldername,
+ },
+ })
+ fileDropEmptyContentInstance.$mount(div)
+ },
+
+ getContents: async () => {
+ return {
+ contents: [],
+ // Fake a writeonly folder as root
+ folder: new Folder({
+ id: 0,
+ source: `${davRemoteURL}${davRootPath}`,
+ root: davRootPath,
+ owner: null,
+ permissions: Permission.CREATE,
+ }),
+ }
+ },
+ })
+
+ const Navigation = getNavigation()
+ Navigation.register(view)
+}
diff --git a/apps/files_sharing/src/views/publicFileShare.ts b/apps/files_sharing/src/views/publicFileShare.ts
new file mode 100644
index 00000000000..b2b9de9ea5f
--- /dev/null
+++ b/apps/files_sharing/src/views/publicFileShare.ts
@@ -0,0 +1,67 @@
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+import type { FileStat, ResponseDataDetailed } from 'webdav'
+import { Folder, Permission, View, davGetDefaultPropfind, davRemoteURL, davRootPath, getNavigation } from '@nextcloud/files'
+import { translate as t } from '@nextcloud/l10n'
+import { CancelablePromise } from 'cancelable-promise'
+import LinkSvg from '@mdi/svg/svg/link.svg?raw'
+
+import { resultToNode } from '../../../files/src/services/Files'
+import { client } from '../../../files/src/services/WebdavClient'
+import logger from '../services/logger'
+
+export default () => {
+ const view = new View({
+ id: 'public-file-share',
+ name: t('files_sharing', 'Public file share'),
+ caption: t('files_sharing', 'Public shared file.'),
+
+ emptyTitle: t('files_sharing', 'No file'),
+ emptyCaption: t('files_sharing', 'The file shared with you will show up here'),
+
+ icon: LinkSvg,
+ order: 1,
+
+ getContents: () => {
+ return new CancelablePromise(async (resolve, reject, onCancel) => {
+ const abort = new AbortController()
+ onCancel(() => abort.abort())
+ try {
+ const node = await client.stat(
+ davRootPath,
+ {
+ data: davGetDefaultPropfind(),
+ details: true,
+ signal: abort.signal,
+ },
+ ) as ResponseDataDetailed<FileStat>
+
+ resolve({
+ // We only have one file as the content
+ contents: [resultToNode(node.data)],
+ // Fake a readonly folder as root
+ folder: new Folder({
+ id: 0,
+ source: `${davRemoteURL}${davRootPath}`,
+ root: davRootPath,
+ owner: null,
+ permissions: Permission.READ,
+ attributes: {
+ // Ensure the share note is set on the root
+ note: node.data.props?.note,
+ },
+ }),
+ })
+ } catch (e) {
+ logger.error(e as Error)
+ reject(e as Error)
+ }
+ })
+ },
+ })
+
+ const Navigation = getNavigation()
+ Navigation.register(view)
+}
diff --git a/apps/files_sharing/src/views/publicShare.ts b/apps/files_sharing/src/views/publicShare.ts
new file mode 100644
index 00000000000..118973f54f5
--- /dev/null
+++ b/apps/files_sharing/src/views/publicShare.ts
@@ -0,0 +1,28 @@
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+import { translate as t } from '@nextcloud/l10n'
+import { View, getNavigation } from '@nextcloud/files'
+import LinkSvg from '@mdi/svg/svg/link.svg?raw'
+
+import { getContents } from '../../../files/src/services/Files'
+
+export default () => {
+ const view = new View({
+ id: 'public-share',
+ name: t('files_sharing', 'Public share'),
+ caption: t('files_sharing', 'Public shared files.'),
+
+ emptyTitle: t('files_sharing', 'No files'),
+ emptyCaption: t('files_sharing', 'Files and folders shared with you will show up here'),
+
+ icon: LinkSvg,
+ order: 1,
+
+ getContents,
+ })
+
+ const Navigation = getNavigation()
+ Navigation.register(view)
+}
diff --git a/apps/files_sharing/tests/Controller/ShareControllerTest.php b/apps/files_sharing/tests/Controller/ShareControllerTest.php
index 6ce92b6fd43..09b02be5f66 100644
--- a/apps/files_sharing/tests/Controller/ShareControllerTest.php
+++ b/apps/files_sharing/tests/Controller/ShareControllerTest.php
@@ -29,7 +29,7 @@ use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
-use OCP\Files\Storage;
+use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IPreview;
@@ -50,41 +50,27 @@ use PHPUnit\Framework\MockObject\MockObject;
* @package OCA\Files_Sharing\Controllers
*/
class ShareControllerTest extends \Test\TestCase {
- /** @var string */
- private $user;
- /** @var string */
- private $oldUser;
-
- /** @var string */
- private $appName = 'files_sharing';
- /** @var ShareController */
- private $shareController;
- /** @var IURLGenerator|MockObject */
- private $urlGenerator;
- /** @var ISession|MockObject */
- private $session;
- /** @var \OCP\IPreview|MockObject */
- private $previewManager;
- /** @var \OCP\IConfig|MockObject */
- private $config;
- /** @var \OC\Share20\Manager|MockObject */
- private $shareManager;
- /** @var IUserManager|MockObject */
- private $userManager;
- /** @var FederatedShareProvider|MockObject */
- private $federatedShareProvider;
- /** @var IAccountManager|MockObject */
- private $accountManager;
- /** @var IEventDispatcher|MockObject */
- private $eventDispatcher;
- /** @var IL10N */
- private $l10n;
- /** @var ISecureRandom */
- private $secureRandom;
- /** @var Defaults|MockObject */
- private $defaults;
- /** @var IPublicShareTemplateFactory|MockObject */
- private $publicShareTemplateFactory;
+
+ private string $user;
+ private string $oldUser;
+ private string $appName = 'files_sharing';
+ private ShareController $shareController;
+
+ private IL10N&MockObject $l10n;
+ private IConfig&MockObject $config;
+ private ISession&MockObject $session;
+ private Defaults&MockObject $defaults;
+ private IAppConfig&MockObject $appConfig;
+ private Manager&MockObject $shareManager;
+ private IPreview&MockObject $previewManager;
+ private IUserManager&MockObject $userManager;
+ private IInitialState&MockObject $initialState;
+ private IURLGenerator&MockObject $urlGenerator;
+ private ISecureRandom&MockObject $secureRandom;
+ private IAccountManager&MockObject $accountManager;
+ private IEventDispatcher&MockObject $eventDispatcher;
+ private FederatedShareProvider&MockObject $federatedShareProvider;
+ private IPublicShareTemplateFactory&MockObject $publicShareTemplateFactory;
protected function setUp(): void {
parent::setUp();
@@ -95,7 +81,9 @@ class ShareControllerTest extends \Test\TestCase {
$this->session = $this->createMock(ISession::class);
$this->previewManager = $this->createMock(IPreview::class);
$this->config = $this->createMock(IConfig::class);
+ $this->appConfig = $this->createMock(IAppConfig::class);
$this->userManager = $this->createMock(IUserManager::class);
+ $this->initialState = $this->createMock(IInitialState::class);
$this->federatedShareProvider = $this->createMock(FederatedShareProvider::class);
$this->federatedShareProvider->expects($this->any())
->method('isOutgoingServer2serverShareEnabled')->willReturn(true);
@@ -122,7 +110,8 @@ class ShareControllerTest extends \Test\TestCase {
$this->defaults,
$this->config,
$this->createMock(IRequest::class),
- $this->createMock(IInitialState::class)
+ $this->initialState,
+ $this->appConfig,
)
);
@@ -246,29 +235,32 @@ class ShareControllerTest extends \Test\TestCase {
->with($owner)
->willReturn($account);
- $share = \OC::$server->getShareManager()->newShare();
- $share->setId(42);
- $share->setPassword('password')
+ /** @var Manager */
+ $manager = \OCP\Server::get(Manager::class);
+ $share = $manager->newShare();
+ $share->setId(42)
+ ->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE)
+ ->setPassword('password')
->setShareOwner('ownerUID')
->setSharedBy('initiatorUID')
->setNode($file)
->setNote($note)
- ->setTarget("/$filename");
+ ->setTarget("/$filename")
+ ->setToken('token');
$this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
$this->session->method('get')->with('public_link_authenticated')->willReturn('42');
- $this->urlGenerator->expects($this->exactly(3))
+ $this->urlGenerator->expects(self::atLeast(2))
->method('linkToRouteAbsolute')
- ->withConsecutive(
- ['files_sharing.sharecontroller.downloadShare', ['token' => 'token', 'filename' => $filename]],
- ['files_sharing.sharecontroller.showShare', ['token' => 'token']],
- ['files_sharing.PublicPreview.getPreview', ['token' => 'token', 'x' => 200, 'y' => 200, 'file' => '/'.$filename]],
- )->willReturnOnConsecutiveCalls(
- 'downloadURL',
- 'shareUrl',
- 'previewImage',
- );
+ ->willReturnMap([
+ // every file has the show show share url in the opengraph url prop
+ ['files_sharing.sharecontroller.showShare', ['token' => 'token'], 'shareUrl'],
+ // this share is not an image to the default preview is used
+ ['files_sharing.PublicPreview.getPreview', ['x' => 256, 'y' => 256, 'file' => $share->getTarget(), 'token' => 'token'], 'previewUrl'],
+ // for the direct link
+ ['files_sharing.sharecontroller.downloadShare', ['token' => 'token', 'filename' => $filename ], 'downloadUrl'],
+ ]);
$this->previewManager->method('isMimeSupported')->with('text/plain')->willReturn(true);
@@ -281,19 +273,12 @@ class ShareControllerTest extends \Test\TestCase {
['preview_max_y', 1024, 1024],
]
);
- $shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
- $shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
$this->shareManager
->expects($this->once())
->method('getShareByToken')
->with('token')
->willReturn($share);
- $this->config
- ->expects($this->once())
- ->method('getAppValue')
- ->with('core', 'shareapi_public_link_disclaimertext', null)
- ->willReturn('My disclaimer text');
$this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
if ($uid === 'ownerUID') {
@@ -325,55 +310,45 @@ class ShareControllerTest extends \Test\TestCase {
->method('getProductName')
->willReturn('Nextcloud');
- $response = $this->shareController->showShare();
- $sharedTmplParams = [
- 'owner' => 'ownerUID',
- 'filename' => $filename,
- 'directory_path' => "/$filename",
- 'mimetype' => 'text/plain',
- 'dirToken' => 'token',
+ // Ensure the correct initial state is setup
+ // Shared node is a file so this is a single file share:
+ $view = 'public-file-share';
+ // Set up initial state
+ $initialState = [];
+ $this->initialState->expects(self::any())
+ ->method('provideInitialState')
+ ->willReturnCallback(function ($key, $value) use (&$initialState) {
+ $initialState[$key] = $value;
+ });
+ $expectedInitialState = [
+ 'isPublic' => true,
'sharingToken' => 'token',
- 'server2serversharing' => true,
- 'protected' => 'true',
- 'dir' => '',
- 'downloadURL' => 'downloadURL',
- 'fileSize' => '33 B',
- 'nonHumanFileSize' => 33,
- 'maxSizeAnimateGif' => 10,
- 'previewSupported' => true,
- 'previewEnabled' => true,
- 'previewMaxX' => 1024,
- 'previewMaxY' => 1024,
- 'hideFileList' => false,
- 'shareOwner' => 'ownerDisplay',
- 'disclaimer' => 'My disclaimer text',
- 'shareUrl' => 'shareUrl',
- 'previewImage' => 'previewImage',
- 'previewURL' => 'downloadURL',
- 'note' => $note,
- 'hideDownload' => false,
- 'showgridview' => false,
- 'label' => ''
+ 'sharePermissions' => (Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE),
+ 'filename' => $filename,
+ 'view' => $view,
];
+ $response = $this->shareController->showShare();
+
+ $this->assertEquals($expectedInitialState, $initialState);
+
$csp = new \OCP\AppFramework\Http\ContentSecurityPolicy();
$csp->addAllowedFrameDomain('\'self\'');
- $expectedResponse = new PublicTemplateResponse($this->appName, 'public', $sharedTmplParams);
+ $expectedResponse = new PublicTemplateResponse('files', 'index');
$expectedResponse->setContentSecurityPolicy($csp);
- $expectedResponse->setHeaderTitle($sharedTmplParams['filename']);
- $expectedResponse->setHeaderDetails('shared by ' . $sharedTmplParams['shareOwner']);
+ $expectedResponse->setHeaderTitle($filename);
+ $expectedResponse->setHeaderDetails('shared by ownerDisplay');
$expectedResponse->setHeaderActions([
- new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $sharedTmplParams['downloadURL'], 0, $sharedTmplParams['fileSize']),
- new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $sharedTmplParams['previewURL']),
- new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', $sharedTmplParams['owner'], $sharedTmplParams['shareOwner'], $sharedTmplParams['filename']),
+ new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', 'downloadUrl', 0, '33'),
+ new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', 'owner', 'ownerDisplay', $filename),
+ new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', 'downloadUrl'),
]);
$this->assertEquals($expectedResponse, $response);
}
- public function testShowShareWithPrivateName() {
- $note = 'personal note';
- $filename = 'file1.txt';
+ public function testShowFileDropShare() {
+ $filename = 'folder1';
$this->shareController->setToken('token');
@@ -387,17 +362,15 @@ class ShareControllerTest extends \Test\TestCase {
$initiator->method('getUID')->willReturn('initiatorUID');
$initiator->method('isEnabled')->willReturn(true);
- $file = $this->createMock(File::class);
- $file->method('getName')->willReturn($filename);
- $file->method('getMimetype')->willReturn('text/plain');
- $file->method('getSize')->willReturn(33);
+ $file = $this->createMock(Folder::class);
$file->method('isReadable')->willReturn(true);
$file->method('isShareable')->willReturn(true);
- $file->method('getId')->willReturn(111);
+ $file->method('getId')->willReturn(1234);
+ $file->method('getName')->willReturn($filename);
$accountName = $this->createMock(IAccountProperty::class);
$accountName->method('getScope')
- ->willReturn(IAccountManager::SCOPE_LOCAL);
+ ->willReturn(IAccountManager::SCOPE_PUBLISHED);
$account = $this->createMock(IAccount::class);
$account->method('getProperty')
->with(IAccountManager::PROPERTY_DISPLAYNAME)
@@ -407,31 +380,34 @@ class ShareControllerTest extends \Test\TestCase {
->with($owner)
->willReturn($account);
- $share = \OC::$server->getShareManager()->newShare();
- $share->setId(42);
- $share->setPassword('password')
+ /** @var Manager */
+ $manager = \OCP\Server::get(Manager::class);
+ $share = $manager->newShare();
+ $share->setId(42)
+ ->setPermissions(Constants::PERMISSION_CREATE)
+ ->setPassword('password')
->setShareOwner('ownerUID')
->setSharedBy('initiatorUID')
->setNode($file)
- ->setNote($note)
- ->setTarget("/$filename");
+ ->setTarget("/$filename")
+ ->setToken('token');
+
+ $this->appConfig
+ ->expects($this->once())
+ ->method('getValueString')
+ ->with('core', 'shareapi_public_link_disclaimertext', '')
+ ->willReturn('My disclaimer text');
$this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
$this->session->method('get')->with('public_link_authenticated')->willReturn('42');
- $this->urlGenerator->expects($this->exactly(3))
+ $this->urlGenerator->expects(self::atLeastOnce())
->method('linkToRouteAbsolute')
- ->withConsecutive(
- ['files_sharing.sharecontroller.downloadShare', ['token' => 'token', 'filename' => $filename]],
- ['files_sharing.sharecontroller.showShare', ['token' => 'token']],
- ['files_sharing.PublicPreview.getPreview', ['token' => 'token', 'x' => 200, 'y' => 200, 'file' => '/'.$filename]],
- )->willReturnOnConsecutiveCalls(
- 'downloadURL',
- 'shareUrl',
- 'previewImage',
- );
-
- $this->previewManager->method('isMimeSupported')->with('text/plain')->willReturn(true);
+ ->willReturnMap([
+ // every file has the show show share url in the opengraph url prop
+ ['files_sharing.sharecontroller.showShare', ['token' => 'token'], 'shareUrl'],
+ // there is no preview or folders so no other link for opengraph
+ ]);
$this->config->method('getSystemValue')
->willReturnMap(
@@ -442,19 +418,12 @@ class ShareControllerTest extends \Test\TestCase {
['preview_max_y', 1024, 1024],
]
);
- $shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
- $shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
$this->shareManager
->expects($this->once())
->method('getShareByToken')
->with('token')
->willReturn($share);
- $this->config
- ->expects($this->once())
- ->method('getAppValue')
- ->with('core', 'shareapi_public_link_disclaimertext', null)
- ->willReturn('My disclaimer text');
$this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
if ($uid === 'ownerUID') {
@@ -478,67 +447,50 @@ class ShareControllerTest extends \Test\TestCase {
$this->l10n->expects($this->any())
->method('t')
- ->will($this->returnCallback(function ($text, $parameters) {
+ ->willReturnCallback(function ($text, $parameters) {
return vsprintf($text, $parameters);
- }));
-
- $this->defaults->expects(self::any())
- ->method('getProductName')
- ->willReturn('Nextcloud');
+ });
- $response = $this->shareController->showShare();
- $sharedTmplParams = [
- 'owner' => '',
- 'filename' => $filename,
- 'directory_path' => "/$filename",
- 'mimetype' => 'text/plain',
- 'dirToken' => 'token',
+ // Set up initial state
+ $initialState = [];
+ $this->initialState->expects(self::any())
+ ->method('provideInitialState')
+ ->willReturnCallback(function ($key, $value) use (&$initialState) {
+ $initialState[$key] = $value;
+ });
+ $expectedInitialState = [
+ 'isPublic' => true,
'sharingToken' => 'token',
- 'server2serversharing' => true,
- 'protected' => 'true',
- 'dir' => '',
- 'downloadURL' => 'downloadURL',
- 'fileSize' => '33 B',
- 'nonHumanFileSize' => 33,
- 'maxSizeAnimateGif' => 10,
- 'previewSupported' => true,
- 'previewEnabled' => true,
- 'previewMaxX' => 1024,
- 'previewMaxY' => 1024,
- 'hideFileList' => false,
- 'shareOwner' => '',
+ 'sharePermissions' => Constants::PERMISSION_CREATE,
+ 'filename' => $filename,
+ 'view' => 'public-file-drop',
'disclaimer' => 'My disclaimer text',
- 'shareUrl' => 'shareUrl',
- 'previewImage' => 'previewImage',
- 'previewURL' => 'downloadURL',
- 'note' => $note,
- 'hideDownload' => false,
- 'showgridview' => false,
- 'label' => ''
];
+ $response = $this->shareController->showShare();
+
+ $this->assertEquals($expectedInitialState, $initialState);
+
$csp = new \OCP\AppFramework\Http\ContentSecurityPolicy();
$csp->addAllowedFrameDomain('\'self\'');
- $expectedResponse = new PublicTemplateResponse($this->appName, 'public', $sharedTmplParams);
+ $expectedResponse = new PublicTemplateResponse('files', 'index');
$expectedResponse->setContentSecurityPolicy($csp);
- $expectedResponse->setHeaderTitle($sharedTmplParams['filename']);
- $expectedResponse->setHeaderDetails('');
+ $expectedResponse->setHeaderTitle($filename);
+ $expectedResponse->setHeaderDetails('shared by ownerDisplay');
$expectedResponse->setHeaderActions([
- new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $sharedTmplParams['downloadURL'], 0, $sharedTmplParams['fileSize']),
- new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $sharedTmplParams['previewURL']),
- new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', $sharedTmplParams['owner'], $sharedTmplParams['shareOwner'], $sharedTmplParams['filename']),
+ new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', 'shareUrl'),
]);
$this->assertEquals($expectedResponse, $response);
}
- public function testShowShareHideDownload() {
+ public function testShowShareWithPrivateName() {
$note = 'personal note';
$filename = 'file1.txt';
$this->shareController->setToken('token');
- $owner = $this->getMockBuilder(IUser::class)->getMock();
+ $owner = $this->createMock(IUser::class);
$owner->method('getDisplayName')->willReturn('ownerDisplay');
$owner->method('getUID')->willReturn('ownerUID');
$owner->method('isEnabled')->willReturn(true);
@@ -548,7 +500,7 @@ class ShareControllerTest extends \Test\TestCase {
$initiator->method('getUID')->willReturn('initiatorUID');
$initiator->method('isEnabled')->willReturn(true);
- $file = $this->getMockBuilder('OCP\Files\File')->getMock();
+ $file = $this->createMock(File::class);
$file->method('getName')->willReturn($filename);
$file->method('getMimetype')->willReturn('text/plain');
$file->method('getSize')->willReturn(33);
@@ -558,7 +510,7 @@ class ShareControllerTest extends \Test\TestCase {
$accountName = $this->createMock(IAccountProperty::class);
$accountName->method('getScope')
- ->willReturn(IAccountManager::SCOPE_PUBLISHED);
+ ->willReturn(IAccountManager::SCOPE_LOCAL);
$account = $this->createMock(IAccount::class);
$account->method('getProperty')
->with(IAccountManager::PROPERTY_DISPLAYNAME)
@@ -568,33 +520,31 @@ class ShareControllerTest extends \Test\TestCase {
->with($owner)
->willReturn($account);
- $share = \OC::$server->getShareManager()->newShare();
+ /** @var IShare */
+ $share = \OCP\Server::get(Manager::class)->newShare();
$share->setId(42);
$share->setPassword('password')
->setShareOwner('ownerUID')
->setSharedBy('initiatorUID')
->setNode($file)
->setNote($note)
- ->setTarget("/$filename")
- ->setHideDownload(true);
+ ->setToken('token')
+ ->setPermissions(\OCP\Constants::PERMISSION_ALL & ~\OCP\Constants::PERMISSION_SHARE)
+ ->setTarget("/$filename");
$this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
$this->session->method('get')->with('public_link_authenticated')->willReturn('42');
- // Even if downloads are disabled the "downloadURL" parameter is
- // provided to the template, as it is needed to preview audio and GIF
- // files.
- $this->urlGenerator->expects($this->exactly(3))
+ $this->urlGenerator->expects(self::atLeast(2))
->method('linkToRouteAbsolute')
- ->withConsecutive(
- ['files_sharing.sharecontroller.downloadShare', ['token' => 'token', 'filename' => $filename]],
- ['files_sharing.sharecontroller.showShare', ['token' => 'token']],
- ['files_sharing.PublicPreview.getPreview', ['token' => 'token', 'x' => 200, 'y' => 200, 'file' => '/'.$filename]],
- )->willReturnOnConsecutiveCalls(
- 'downloadURL',
- 'shareUrl',
- 'previewImage',
- );
+ ->willReturnMap([
+ // every file has the show show share url in the opengraph url prop
+ ['files_sharing.sharecontroller.showShare', ['token' => 'token'], 'shareUrl'],
+ // this share is not an image to the default preview is used
+ ['files_sharing.PublicPreview.getPreview', ['x' => 256, 'y' => 256, 'file' => $share->getTarget(), 'token' => 'token'], 'previewUrl'],
+ // for the direct link
+ ['files_sharing.sharecontroller.downloadShare', ['token' => 'token', 'filename' => $filename ], 'downloadUrl'],
+ ]);
$this->previewManager->method('isMimeSupported')->with('text/plain')->willReturn(true);
@@ -615,11 +565,6 @@ class ShareControllerTest extends \Test\TestCase {
->method('getShareByToken')
->with('token')
->willReturn($share);
- $this->config
- ->expects($this->once())
- ->method('getAppValue')
- ->with('core', 'shareapi_public_link_disclaimertext', null)
- ->willReturn('My disclaimer text');
$this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
if ($uid === 'ownerUID') {
@@ -643,176 +588,29 @@ class ShareControllerTest extends \Test\TestCase {
$this->l10n->expects($this->any())
->method('t')
- ->willReturnCallback(function ($text, $parameters) {
+ ->will($this->returnCallback(function ($text, $parameters) {
return vsprintf($text, $parameters);
- });
-
- $response = $this->shareController->showShare();
- $sharedTmplParams = [
- 'owner' => 'ownerUID',
- 'filename' => $filename,
- 'directory_path' => "/$filename",
- 'mimetype' => 'text/plain',
- 'dirToken' => 'token',
- 'sharingToken' => 'token',
- 'server2serversharing' => true,
- 'protected' => 'true',
- 'dir' => '',
- 'downloadURL' => 'downloadURL',
- 'fileSize' => '33 B',
- 'nonHumanFileSize' => 33,
- 'maxSizeAnimateGif' => 10,
- 'previewSupported' => true,
- 'previewEnabled' => true,
- 'previewMaxX' => 1024,
- 'previewMaxY' => 1024,
- 'hideFileList' => false,
- 'shareOwner' => 'ownerDisplay',
- 'disclaimer' => 'My disclaimer text',
- 'shareUrl' => 'shareUrl',
- 'previewImage' => 'previewImage',
- 'previewURL' => 'downloadURL',
- 'note' => $note,
- 'hideDownload' => true,
- 'showgridview' => false,
- 'label' => ''
- ];
-
- $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy();
- $csp->addAllowedFrameDomain('\'self\'');
- $expectedResponse = new PublicTemplateResponse($this->appName, 'public', $sharedTmplParams);
- $expectedResponse->setContentSecurityPolicy($csp);
- $expectedResponse->setHeaderTitle($sharedTmplParams['filename']);
- $expectedResponse->setHeaderDetails('shared by ' . $sharedTmplParams['shareOwner']);
- $expectedResponse->setHeaderActions([]);
-
- $this->assertEquals($expectedResponse, $response);
- }
-
- /**
- * Checks file drop shares:
- * - there must not be any header action
- * - the template param "hideFileList" should be true
- *
- * @test
- * @return void
- */
- public function testShareFileDrop() {
- $this->shareController->setToken('token');
-
- $owner = $this->getMockBuilder(IUser::class)->getMock();
- $owner->method('getDisplayName')->willReturn('ownerDisplay');
- $owner->method('getUID')->willReturn('ownerUID');
- $owner->method('isEnabled')->willReturn(true);
-
- $initiator = $this->createMock(IUser::class);
- $initiator->method('getDisplayName')->willReturn('initiatorDisplay');
- $initiator->method('getUID')->willReturn('initiatorUID');
- $initiator->method('isEnabled')->willReturn(true);
-
- /* @var MockObject|Storage $storage */
- $storage = $this->getMockBuilder(Storage::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- /* @var MockObject|Folder $folder */
- $folder = $this->getMockBuilder(Folder::class)
- ->disableOriginalConstructor()
- ->getMock();
- $folder->method('getName')->willReturn('/fileDrop');
- $folder->method('isReadable')->willReturn(true);
- $folder->method('isShareable')->willReturn(true);
- $folder->method('getStorage')->willReturn($storage);
- $folder->method('get')->with('')->willReturn($folder);
- $folder->method('getSize')->willReturn(1337);
- $folder->method('getId')->willReturn(111);
-
- $accountName = $this->createMock(IAccountProperty::class);
- $accountName->method('getScope')
- ->willReturn(IAccountManager::SCOPE_PUBLISHED);
- $account = $this->createMock(IAccount::class);
- $account->method('getProperty')
- ->with(IAccountManager::PROPERTY_DISPLAYNAME)
- ->willReturn($accountName);
- $this->accountManager->expects($this->once())
- ->method('getAccount')
- ->with($owner)
- ->willReturn($account);
-
- $share = \OC::$server->getShareManager()->newShare();
- $share->setId(42);
- $share->setPermissions(Constants::PERMISSION_CREATE)
- ->setShareOwner('ownerUID')
- ->setSharedBy('initiatorUID')
- ->setNode($folder)
- ->setTarget('/fileDrop');
-
- $this->shareManager
- ->expects($this->once())
- ->method('getShareByToken')
- ->with('token')
- ->willReturn($share);
-
- $this->userManager->method('get')->willReturnCallback(function (string $uid) use ($owner, $initiator) {
- if ($uid === 'ownerUID') {
- return $owner;
- }
- if ($uid === 'initiatorUID') {
- return $initiator;
- }
- return null;
- });
+ }));
- $this->l10n->expects($this->any())
- ->method('t')
- ->willReturnCallback(function ($text, $parameters) {
- return vsprintf($text, $parameters);
- });
+ $this->defaults->expects(self::any())
+ ->method('getProductName')
+ ->willReturn('Nextcloud');
$response = $this->shareController->showShare();
- // skip the "folder" param for tests
- $responseParams = $response->getParams();
- unset($responseParams['folder']);
- $response->setParams($responseParams);
-
- $sharedTmplParams = [
- 'owner' => 'ownerUID',
- 'filename' => '/fileDrop',
- 'directory_path' => '/fileDrop',
- 'mimetype' => null,
- 'dirToken' => 'token',
- 'sharingToken' => 'token',
- 'server2serversharing' => true,
- 'protected' => 'false',
- 'dir' => null,
- 'downloadURL' => '',
- 'fileSize' => '1 KB',
- 'nonHumanFileSize' => 1337,
- 'maxSizeAnimateGif' => null,
- 'previewSupported' => null,
- 'previewEnabled' => null,
- 'previewMaxX' => null,
- 'previewMaxY' => null,
- 'hideFileList' => true,
- 'shareOwner' => 'ownerDisplay',
- 'disclaimer' => null,
- 'shareUrl' => '',
- 'previewImage' => '',
- 'previewURL' => '',
- 'note' => '',
- 'hideDownload' => false,
- 'showgridview' => false,
- 'label' => ''
- ];
$csp = new \OCP\AppFramework\Http\ContentSecurityPolicy();
$csp->addAllowedFrameDomain('\'self\'');
- $expectedResponse = new PublicTemplateResponse($this->appName, 'public', $sharedTmplParams);
+ $expectedResponse = new PublicTemplateResponse('files', 'index');
$expectedResponse->setContentSecurityPolicy($csp);
- $expectedResponse->setHeaderTitle($sharedTmplParams['filename']);
- $expectedResponse->setHeaderDetails('shared by ' . $sharedTmplParams['shareOwner']);
+ $expectedResponse->setHeaderTitle($filename);
+ $expectedResponse->setHeaderDetails('');
+ $expectedResponse->setHeaderActions([
+ new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', 'downloadUrl', 0, '33'),
+ new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', 'owner', 'ownerDisplay', $filename),
+ new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', 'downloadUrl'),
+ ]);
- self::assertEquals($expectedResponse, $response);
+ $this->assertEquals($expectedResponse, $response);
}