mirror of
https://github.com/nextcloud/server.git
synced 2026-06-06 07:13:23 -04:00
Merge pull request #45698 from nextcloud/fix/files-remote-shares
This commit is contained in:
commit
4c32ab7b72
22 changed files with 290 additions and 82 deletions
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
use OC\Share20\Exception\BackendError;
|
||||
use OCA\DAV\Connector\Sabre\Node as DavNode;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\Node;
|
||||
|
|
@ -33,24 +34,19 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
|||
* @var \Sabre\DAV\Server
|
||||
*/
|
||||
private $server;
|
||||
private IManager $shareManager;
|
||||
private Tree $tree;
|
||||
private string $userId;
|
||||
private Folder $userFolder;
|
||||
|
||||
/** @var IShare[][] */
|
||||
private array $cachedShares = [];
|
||||
/** @var string[] */
|
||||
private array $cachedFolders = [];
|
||||
|
||||
public function __construct(
|
||||
Tree $tree,
|
||||
IUserSession $userSession,
|
||||
Folder $userFolder,
|
||||
IManager $shareManager
|
||||
private Tree $tree,
|
||||
private IUserSession $userSession,
|
||||
private Folder $userFolder,
|
||||
private IManager $shareManager,
|
||||
) {
|
||||
$this->tree = $tree;
|
||||
$this->shareManager = $shareManager;
|
||||
$this->userFolder = $userFolder;
|
||||
$this->userId = $userSession->getUser()->getUID();
|
||||
}
|
||||
|
||||
|
|
@ -91,18 +87,29 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
|||
IShare::TYPE_DECK,
|
||||
IShare::TYPE_SCIENCEMESH,
|
||||
];
|
||||
|
||||
foreach ($requestedShareTypes as $requestedShareType) {
|
||||
$shares = $this->shareManager->getSharesBy(
|
||||
$result = array_merge($result, $this->shareManager->getSharesBy(
|
||||
$this->userId,
|
||||
$requestedShareType,
|
||||
$node,
|
||||
false,
|
||||
-1
|
||||
);
|
||||
foreach ($shares as $share) {
|
||||
$result[] = $share;
|
||||
));
|
||||
|
||||
// Also check for shares where the user is the recipient
|
||||
try {
|
||||
$result = array_merge($result, $this->shareManager->getSharedWith(
|
||||
$this->userId,
|
||||
$requestedShareType,
|
||||
$node,
|
||||
-1
|
||||
));
|
||||
} catch (BackendError $e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
|
@ -124,27 +131,29 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
|||
*/
|
||||
private function getShares(DavNode $sabreNode): array {
|
||||
if (isset($this->cachedShares[$sabreNode->getId()])) {
|
||||
$shares = $this->cachedShares[$sabreNode->getId()];
|
||||
} else {
|
||||
[$parentPath,] = \Sabre\Uri\split($sabreNode->getPath());
|
||||
if ($parentPath === '') {
|
||||
$parentPath = '/';
|
||||
}
|
||||
// if we already cached the folder this file is in we know there are no shares for this file
|
||||
if (array_search($parentPath, $this->cachedFolders) === false) {
|
||||
try {
|
||||
$node = $sabreNode->getNode();
|
||||
} catch (NotFoundException $e) {
|
||||
return [];
|
||||
}
|
||||
$shares = $this->getShare($node);
|
||||
$this->cachedShares[$sabreNode->getId()] = $shares;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
return $this->cachedShares[$sabreNode->getId()];
|
||||
}
|
||||
|
||||
return $shares;
|
||||
[$parentPath,] = \Sabre\Uri\split($sabreNode->getPath());
|
||||
if ($parentPath === '') {
|
||||
$parentPath = '/';
|
||||
}
|
||||
|
||||
// if we already cached the folder containing this file
|
||||
// then we already know there are no shares here.
|
||||
if (array_search($parentPath, $this->cachedFolders) === false) {
|
||||
try {
|
||||
$node = $sabreNode->getNode();
|
||||
} catch (NotFoundException $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$shares = $this->getShare($node);
|
||||
$this->cachedShares[$sabreNode->getId()] = $shares;
|
||||
return $shares;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -161,7 +170,9 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
|||
return;
|
||||
}
|
||||
|
||||
// need prefetch ?
|
||||
// If the node is a directory and we are requesting share types or sharees
|
||||
// then we get all the shares in the folder and cache them.
|
||||
// This is more performant than iterating each files afterwards.
|
||||
if ($sabreNode instanceof Directory
|
||||
&& $propFind->getDepth() !== 0
|
||||
&& (
|
||||
|
|
|
|||
|
|
@ -263,13 +263,15 @@ class Server {
|
|||
$this->server->tree, \OC::$server->getTagManager()
|
||||
)
|
||||
);
|
||||
|
||||
// TODO: switch to LazyUserFolder
|
||||
$userFolder = \OC::$server->getUserFolder();
|
||||
$shareManager = \OCP\Server::get(\OCP\Share\IManager::class);
|
||||
$this->server->addPlugin(new SharesPlugin(
|
||||
$this->server->tree,
|
||||
$userSession,
|
||||
$userFolder,
|
||||
\OC::$server->getShareManager()
|
||||
$shareManager,
|
||||
));
|
||||
$this->server->addPlugin(new CommentPropertiesPlugin(
|
||||
\OC::$server->getCommentsManager(),
|
||||
|
|
@ -304,7 +306,7 @@ class Server {
|
|||
$this->server->tree,
|
||||
$user,
|
||||
\OC::$server->getRootFolder(),
|
||||
\OC::$server->getShareManager(),
|
||||
$shareManager,
|
||||
$view,
|
||||
\OCP\Server::get(IFilesMetadataManager::class)
|
||||
));
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ class SharesPluginTest extends \Test\TestCase {
|
|||
->with(
|
||||
$this->equalTo('user1'),
|
||||
$this->anything(),
|
||||
$this->anything(),
|
||||
$this->equalTo($node),
|
||||
$this->equalTo(false),
|
||||
$this->equalTo(-1)
|
||||
)
|
||||
|
|
@ -111,6 +111,16 @@ class SharesPluginTest extends \Test\TestCase {
|
|||
return [];
|
||||
});
|
||||
|
||||
$this->shareManager->expects($this->any())
|
||||
->method('getSharedWith')
|
||||
->with(
|
||||
$this->equalTo('user1'),
|
||||
$this->anything(),
|
||||
$this->equalTo($node),
|
||||
$this->equalTo(-1)
|
||||
)
|
||||
->willReturn([]);
|
||||
|
||||
$propFind = new \Sabre\DAV\PropFind(
|
||||
'/dummyPath',
|
||||
[self::SHARETYPES_PROPERTYNAME],
|
||||
|
|
@ -199,6 +209,16 @@ class SharesPluginTest extends \Test\TestCase {
|
|||
return [];
|
||||
});
|
||||
|
||||
$this->shareManager->expects($this->any())
|
||||
->method('getSharedWith')
|
||||
->with(
|
||||
$this->equalTo('user1'),
|
||||
$this->anything(),
|
||||
$this->equalTo($node),
|
||||
$this->equalTo(-1)
|
||||
)
|
||||
->willReturn([]);
|
||||
|
||||
$this->shareManager->expects($this->any())
|
||||
->method('getSharesInFolder')
|
||||
->with(
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ export const canDownload = (nodes: Node[]) => {
|
|||
}
|
||||
|
||||
export const canCopy = (nodes: Node[]) => {
|
||||
// For now the only restriction is that a shared file
|
||||
// cannot be copied if the download is disabled
|
||||
// a shared file cannot be copied if the download is disabled
|
||||
// it can be copied if the user has at least read permissions
|
||||
return canDownload(nodes)
|
||||
&& !nodes.some(node => node.permissions === Permission.NONE)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
class="files-list__row-mtime"
|
||||
data-cy-files-list-row-mtime
|
||||
@click="openDetailsIfAvailable">
|
||||
<NcDateTime :timestamp="source.mtime" :ignore-seconds="true" />
|
||||
<NcDateTime v-if="source.mtime" :timestamp="source.mtime" :ignore-seconds="true" />
|
||||
</td>
|
||||
|
||||
<!-- View columns -->
|
||||
|
|
@ -177,8 +177,8 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
size() {
|
||||
const size = parseInt(this.source.size, 10) || 0
|
||||
if (typeof size !== 'number' || size < 0) {
|
||||
const size = parseInt(this.source.size, 10)
|
||||
if (typeof size !== 'number' || isNaN(size) || size < 0) {
|
||||
return this.t('files', 'Pending')
|
||||
}
|
||||
return formatFileSize(size, true)
|
||||
|
|
@ -186,8 +186,8 @@ export default defineComponent({
|
|||
sizeOpacity() {
|
||||
const maxOpacitySize = 10 * 1024 * 1024
|
||||
|
||||
const size = parseInt(this.source.size, 10) || 0
|
||||
if (!size || size < 0) {
|
||||
const size = parseInt(this.source.size, 10)
|
||||
if (!size || isNaN(size) || size < 0) {
|
||||
return {}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,14 +17,22 @@ import { getCurrentUser } from '@nextcloud/auth'
|
|||
|
||||
import './sharingStatusAction.scss'
|
||||
|
||||
const generateAvatarSvg = (userId: string) => {
|
||||
const avatarUrl = generateUrl('/avatar/{userId}/32', { userId })
|
||||
const isDarkMode = window?.matchMedia?.('(prefers-color-scheme: dark)')?.matches === true
|
||||
|| document.querySelector('[data-themes*=dark]') !== null
|
||||
|
||||
const generateAvatarSvg = (userId: string, isGuest = false) => {
|
||||
const url = isDarkMode ? '/avatar/{userId}/32/dark' : '/avatar/{userId}/32'
|
||||
const avatarUrl = generateUrl(isGuest ? url : url + '?guestFallback=true', { userId })
|
||||
return `<svg width="32" height="32" viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg" class="sharing-status__avatar">
|
||||
<image href="${avatarUrl}" height="32" width="32" />
|
||||
</svg>`
|
||||
}
|
||||
|
||||
const isExternal = (node: Node) => {
|
||||
return node.attributes.remote_id !== undefined
|
||||
}
|
||||
|
||||
export const action = new FileAction({
|
||||
id: 'sharing-status',
|
||||
displayName(nodes: Node[]) {
|
||||
|
|
@ -33,7 +41,7 @@ export const action = new FileAction({
|
|||
const ownerId = node?.attributes?.['owner-id']
|
||||
|
||||
if (shareTypes.length > 0
|
||||
|| (ownerId && ownerId !== getCurrentUser()?.uid)) {
|
||||
|| (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
|
||||
return t('files_sharing', 'Shared')
|
||||
}
|
||||
|
||||
|
|
@ -46,11 +54,11 @@ export const action = new FileAction({
|
|||
const ownerDisplayName = node?.attributes?.['owner-display-name']
|
||||
|
||||
// Mixed share types
|
||||
if (Array.isArray(node.attributes?.['share-types'])) {
|
||||
if (Array.isArray(node.attributes?.['share-types']) && node.attributes?.['share-types'].length > 1) {
|
||||
return t('files_sharing', 'Shared multiple times with different people')
|
||||
}
|
||||
|
||||
if (ownerId && ownerId !== getCurrentUser()?.uid) {
|
||||
if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
|
||||
return t('files_sharing', 'Shared by {ownerDisplayName}', { ownerDisplayName })
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +70,7 @@ export const action = new FileAction({
|
|||
const shareTypes = Object.values(node?.attributes?.['share-types'] || {}).flat() as number[]
|
||||
|
||||
// Mixed share types
|
||||
if (Array.isArray(node.attributes?.['share-types'])) {
|
||||
if (Array.isArray(node.attributes?.['share-types']) && node.attributes?.['share-types'].length > 1) {
|
||||
return AccountPlusSvg
|
||||
}
|
||||
|
||||
|
|
@ -84,8 +92,8 @@ export const action = new FileAction({
|
|||
}
|
||||
|
||||
const ownerId = node?.attributes?.['owner-id']
|
||||
if (ownerId && ownerId !== getCurrentUser()?.uid) {
|
||||
return generateAvatarSvg(ownerId)
|
||||
if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
|
||||
return generateAvatarSvg(ownerId, isExternal(node))
|
||||
}
|
||||
|
||||
return AccountPlusSvg
|
||||
|
|
@ -107,7 +115,7 @@ export const action = new FileAction({
|
|||
}
|
||||
|
||||
// If the node is shared by someone else
|
||||
if (ownerId && ownerId !== getCurrentUser()?.uid) {
|
||||
if (ownerId && (ownerId !== getCurrentUser()?.uid || isExternal(node))) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -336,12 +336,27 @@ describe('SharingService share to Node mapping', () => {
|
|||
expect(folder.attributes.favorite).toBe(1)
|
||||
})
|
||||
|
||||
test('Empty', async () => {
|
||||
jest.spyOn(logger, 'error').mockImplementationOnce(() => {})
|
||||
jest.spyOn(axios, 'get').mockReturnValueOnce(Promise.resolve({
|
||||
data: {
|
||||
ocs: {
|
||||
data: [],
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
const shares = await getContents(false, true, false, false)
|
||||
expect(shares.contents).toHaveLength(0)
|
||||
expect(logger.error).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
test('Error', async () => {
|
||||
jest.spyOn(logger, 'error').mockImplementationOnce(() => {})
|
||||
jest.spyOn(axios, 'get').mockReturnValueOnce(Promise.resolve({
|
||||
data: {
|
||||
ocs: {
|
||||
data: [{}],
|
||||
data: [null],
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
import type { AxiosPromise } from 'axios'
|
||||
import type { OCSResponse } from '@nextcloud/typings/ocs'
|
||||
|
||||
import { Folder, File, type ContentsWithRoot } from '@nextcloud/files'
|
||||
import { Folder, File, type ContentsWithRoot, Permission } from '@nextcloud/files'
|
||||
import { generateOcsUrl, generateRemoteUrl } from '@nextcloud/router'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
|
@ -19,16 +19,34 @@ const headers = {
|
|||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
const ocsEntryToNode = function(ocsEntry: any): Folder | File | null {
|
||||
const ocsEntryToNode = async function(ocsEntry: any): Promise<Folder | File | null> {
|
||||
try {
|
||||
// Federated share handling
|
||||
if (ocsEntry?.remote_id !== undefined) {
|
||||
const mime = (await import('mime')).default
|
||||
// This won't catch files without an extension, but this is the best we can do
|
||||
ocsEntry.mimetype = mime.getType(ocsEntry.name)
|
||||
ocsEntry.item_type = ocsEntry.mimetype ? 'file' : 'folder'
|
||||
|
||||
// Need to set permissions to NONE for federated shares
|
||||
ocsEntry.item_permissions = Permission.NONE
|
||||
ocsEntry.permissions = Permission.NONE
|
||||
|
||||
ocsEntry.uid_owner = ocsEntry.owner
|
||||
// TODO: have the real display name stored somewhere
|
||||
ocsEntry.displayname_owner = ocsEntry.owner
|
||||
}
|
||||
|
||||
const isFolder = ocsEntry?.item_type === 'folder'
|
||||
const hasPreview = ocsEntry?.has_preview === true
|
||||
const Node = isFolder ? Folder : File
|
||||
|
||||
const fileid = ocsEntry.file_source
|
||||
// If this is an external share that is not yet accepted,
|
||||
// we don't have an id. We can fallback to the row id temporarily
|
||||
const fileid = ocsEntry.file_source || ocsEntry.id
|
||||
|
||||
// Generate path and strip double slashes
|
||||
const path = ocsEntry?.path || ocsEntry.file_target
|
||||
const path = ocsEntry?.path || ocsEntry.file_target || ocsEntry.name
|
||||
const source = generateRemoteUrl(`dav/${rootPath}/${path}`.replaceAll(/\/\//gm, '/'))
|
||||
|
||||
// Prefer share time if more recent than item mtime
|
||||
|
|
@ -41,7 +59,7 @@ const ocsEntryToNode = function(ocsEntry: any): Folder | File | null {
|
|||
id: fileid,
|
||||
source,
|
||||
owner: ocsEntry?.uid_owner,
|
||||
mime: ocsEntry?.mimetype,
|
||||
mime: ocsEntry?.mimetype || 'application/octet-stream',
|
||||
mtime,
|
||||
size: ocsEntry?.item_size,
|
||||
permissions: ocsEntry?.item_permissions || ocsEntry?.permissions,
|
||||
|
|
@ -150,7 +168,7 @@ export const getContents = async (sharedWithYou = true, sharedWithOthers = true,
|
|||
|
||||
const responses = await Promise.all(promises)
|
||||
const data = responses.map((response) => response.data.ocs.data).flat()
|
||||
let contents = data.map(ocsEntryToNode)
|
||||
let contents = (await Promise.all(data.map(ocsEntryToNode)))
|
||||
.filter((node) => node !== null) as (Folder | File)[]
|
||||
|
||||
if (filterTypes.length > 0) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use OCP\AppFramework\Http\Attribute\FrontpageRoute;
|
|||
use OCP\AppFramework\Http\DataDisplayResponse;
|
||||
use OCP\AppFramework\Http\FileDisplayResponse;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IAvatarManager;
|
||||
|
|
@ -40,6 +41,7 @@ class AvatarController extends Controller {
|
|||
protected LoggerInterface $logger,
|
||||
protected ?string $userId,
|
||||
protected TimeFactory $timeFactory,
|
||||
protected GuestAvatarController $guestAvatarController,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
}
|
||||
|
|
@ -54,13 +56,15 @@ class AvatarController extends Controller {
|
|||
*
|
||||
* @param string $userId ID of the user
|
||||
* @param int $size Size of the avatar
|
||||
* @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string, X-NC-IsCustomAvatar: int}>|JSONResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>
|
||||
* @param bool $guestFallback Fallback to guest avatar if not found
|
||||
* @return FileDisplayResponse<Http::STATUS_OK|Http::STATUS_CREATED, array{Content-Type: string, X-NC-IsCustomAvatar: int}>|JSONResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>|Response<Http::STATUS_INTERNAL_SERVER_ERROR, array{}>
|
||||
*
|
||||
* 200: Avatar returned
|
||||
* 201: Avatar returned
|
||||
* 404: Avatar not found
|
||||
*/
|
||||
#[FrontpageRoute(verb: 'GET', url: '/avatar/{userId}/{size}/dark')]
|
||||
public function getAvatarDark(string $userId, int $size) {
|
||||
public function getAvatarDark(string $userId, int $size, bool $guestFallback = false) {
|
||||
if ($size <= 64) {
|
||||
if ($size !== 64) {
|
||||
$this->logger->debug('Avatar requested in deprecated size ' . $size);
|
||||
|
|
@ -82,6 +86,9 @@ class AvatarController extends Controller {
|
|||
['Content-Type' => $avatarFile->getMimeType(), 'X-NC-IsCustomAvatar' => (int)$avatar->isCustomAvatar()]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
if ($guestFallback) {
|
||||
return $this->guestAvatarController->getAvatarDark($userId, (string)$size);
|
||||
}
|
||||
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
|
@ -101,13 +108,15 @@ class AvatarController extends Controller {
|
|||
*
|
||||
* @param string $userId ID of the user
|
||||
* @param int $size Size of the avatar
|
||||
* @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string, X-NC-IsCustomAvatar: int}>|JSONResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>
|
||||
* @param bool $guestFallback Fallback to guest avatar if not found
|
||||
* @return FileDisplayResponse<Http::STATUS_OK|Http::STATUS_CREATED, array{Content-Type: string, X-NC-IsCustomAvatar: int}>|JSONResponse<Http::STATUS_NOT_FOUND, array<empty>, array{}>|Response<Http::STATUS_INTERNAL_SERVER_ERROR, array{}>
|
||||
*
|
||||
* 200: Avatar returned
|
||||
* 201: Avatar returned
|
||||
* 404: Avatar not found
|
||||
*/
|
||||
#[FrontpageRoute(verb: 'GET', url: '/avatar/{userId}/{size}')]
|
||||
public function getAvatar(string $userId, int $size) {
|
||||
public function getAvatar(string $userId, int $size, bool $guestFallback = false) {
|
||||
if ($size <= 64) {
|
||||
if ($size !== 64) {
|
||||
$this->logger->debug('Avatar requested in deprecated size ' . $size);
|
||||
|
|
@ -129,6 +138,9 @@ class AvatarController extends Controller {
|
|||
['Content-Type' => $avatarFile->getMimeType(), 'X-NC-IsCustomAvatar' => (int)$avatar->isCustomAvatar()]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
if ($guestFallback) {
|
||||
return $this->guestAvatarController->getAvatar($userId, (string)$size);
|
||||
}
|
||||
return new JSONResponse([], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class GuestAvatarController extends Controller {
|
|||
* @param string $guestName The guest name, e.g. "Albert"
|
||||
* @param string $size The desired avatar size, e.g. 64 for 64x64px
|
||||
* @param bool|null $darkTheme Return dark avatar
|
||||
* @return FileDisplayResponse<Http::STATUS_OK|Http::STATUS_CREATED, array{Content-Type: string}>|Response<Http::STATUS_INTERNAL_SERVER_ERROR, array{}>
|
||||
* @return FileDisplayResponse<Http::STATUS_OK|Http::STATUS_CREATED, array{Content-Type: string, X-NC-IsCustomAvatar: int}>|Response<Http::STATUS_INTERNAL_SERVER_ERROR, array{}>
|
||||
*
|
||||
* 200: Custom avatar returned
|
||||
* 201: Avatar returned
|
||||
|
|
@ -68,7 +68,7 @@ class GuestAvatarController extends Controller {
|
|||
$resp = new FileDisplayResponse(
|
||||
$avatarFile,
|
||||
$avatar->isCustomAvatar() ? Http::STATUS_OK : Http::STATUS_CREATED,
|
||||
['Content-Type' => $avatarFile->getMimeType()]
|
||||
['Content-Type' => $avatarFile->getMimeType(), 'X-NC-IsCustomAvatar' => (int)$avatar->isCustomAvatar()]
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('error while creating guest avatar', [
|
||||
|
|
@ -92,7 +92,7 @@ class GuestAvatarController extends Controller {
|
|||
*
|
||||
* @param string $guestName The guest name, e.g. "Albert"
|
||||
* @param string $size The desired avatar size, e.g. 64 for 64x64px
|
||||
* @return FileDisplayResponse<Http::STATUS_OK|Http::STATUS_CREATED, array{Content-Type: string}>|Response<Http::STATUS_INTERNAL_SERVER_ERROR, array{}>
|
||||
* @return FileDisplayResponse<Http::STATUS_OK|Http::STATUS_CREATED, array{Content-Type: string, X-NC-IsCustomAvatar: int}>|Response<Http::STATUS_INTERNAL_SERVER_ERROR, array{}>
|
||||
*
|
||||
* 200: Custom avatar returned
|
||||
* 201: Avatar returned
|
||||
|
|
|
|||
|
|
@ -7318,6 +7318,19 @@
|
|||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "guestFallback",
|
||||
"in": "query",
|
||||
"description": "Fallback to guest avatar if not found",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"enum": [
|
||||
0,
|
||||
1
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "userId",
|
||||
"in": "path",
|
||||
|
|
@ -7358,6 +7371,25 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"201": {
|
||||
"description": "Avatar returned",
|
||||
"headers": {
|
||||
"X-NC-IsCustomAvatar": {
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"*/*": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Avatar not found",
|
||||
"content": {
|
||||
|
|
@ -7365,6 +7397,9 @@
|
|||
"schema": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7386,6 +7421,19 @@
|
|||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "guestFallback",
|
||||
"in": "query",
|
||||
"description": "Fallback to guest avatar if not found",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"enum": [
|
||||
0,
|
||||
1
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "userId",
|
||||
"in": "path",
|
||||
|
|
@ -7426,6 +7474,25 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"201": {
|
||||
"description": "Avatar returned",
|
||||
"headers": {
|
||||
"X-NC-IsCustomAvatar": {
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"*/*": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Avatar not found",
|
||||
"content": {
|
||||
|
|
@ -7433,6 +7500,9 @@
|
|||
"schema": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7569,6 +7639,14 @@
|
|||
"responses": {
|
||||
"200": {
|
||||
"description": "Custom avatar returned",
|
||||
"headers": {
|
||||
"X-NC-IsCustomAvatar": {
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"*/*": {
|
||||
"schema": {
|
||||
|
|
@ -7580,6 +7658,14 @@
|
|||
},
|
||||
"201": {
|
||||
"description": "Avatar returned",
|
||||
"headers": {
|
||||
"X-NC-IsCustomAvatar": {
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"*/*": {
|
||||
"schema": {
|
||||
|
|
@ -7634,6 +7720,14 @@
|
|||
"responses": {
|
||||
"200": {
|
||||
"description": "Custom avatar returned",
|
||||
"headers": {
|
||||
"X-NC-IsCustomAvatar": {
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"*/*": {
|
||||
"schema": {
|
||||
|
|
@ -7645,6 +7739,14 @@
|
|||
},
|
||||
"201": {
|
||||
"description": "Avatar returned",
|
||||
"headers": {
|
||||
"X-NC-IsCustomAvatar": {
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"*/*": {
|
||||
"schema": {
|
||||
|
|
|
|||
2
dist/857-857.js
vendored
Normal file
2
dist/857-857.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/857-857.js.map
vendored
Normal file
1
dist/857-857.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
dist/files-init.js
vendored
6
dist/files-init.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-init.js.map
vendored
2
dist/files-init.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/files-main.js
vendored
6
dist/files-main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/files-main.js.map
vendored
2
dist/files-main.js.map
vendored
File diff suppressed because one or more lines are too long
6
dist/files_sharing-init.js
vendored
6
dist/files_sharing-init.js
vendored
File diff suppressed because one or more lines are too long
5
dist/files_sharing-init.js.license
vendored
5
dist/files_sharing-init.js.license
vendored
|
|
@ -97,3 +97,8 @@
|
|||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
|
|
|||
2
dist/files_sharing-init.js.map
vendored
2
dist/files_sharing-init.js.map
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1205,7 +1205,7 @@ class Manager implements IManager {
|
|||
throw new \Exception("non-shallow getSharesInFolder is no longer supported");
|
||||
}
|
||||
|
||||
return array_reduce($providers, function ($shares, IShareProvider $provider) use ($userId, $node, $reshares, $shallow) {
|
||||
return array_reduce($providers, function ($shares, IShareProvider $provider) use ($userId, $node, $reshares) {
|
||||
$newShares = $provider->getSharesInFolder($userId, $node, $reshares);
|
||||
foreach ($newShares as $fid => $data) {
|
||||
if (!isset($shares[$fid])) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ namespace Tests\Core\Controller;
|
|||
|
||||
use OC\AppFramework\Utility\TimeFactory;
|
||||
use OC\Core\Controller\AvatarController;
|
||||
use OC\Core\Controller\GuestAvatarController;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\IRootFolder;
|
||||
|
|
@ -42,13 +43,15 @@ use Psr\Log\LoggerInterface;
|
|||
class AvatarControllerTest extends \Test\TestCase {
|
||||
/** @var AvatarController */
|
||||
private $avatarController;
|
||||
/** @var GuestAvatarController */
|
||||
private $guestAvatarController;
|
||||
|
||||
/** @var IAvatar|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $avatarMock;
|
||||
/** @var IUser|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $userMock;
|
||||
/** @var ISimpleFile|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $avatarFile;
|
||||
|
||||
/** @var IAvatarManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $avatarManager;
|
||||
/** @var ICache|\PHPUnit\Framework\MockObject\MockObject */
|
||||
|
|
@ -83,6 +86,13 @@ class AvatarControllerTest extends \Test\TestCase {
|
|||
$this->avatarMock = $this->getMockBuilder('OCP\IAvatar')->getMock();
|
||||
$this->userMock = $this->getMockBuilder(IUser::class)->getMock();
|
||||
|
||||
$this->guestAvatarController = new GuestAvatarController(
|
||||
'core',
|
||||
$this->request,
|
||||
$this->avatarManager,
|
||||
$this->logger
|
||||
);
|
||||
|
||||
$this->avatarController = new AvatarController(
|
||||
'core',
|
||||
$this->request,
|
||||
|
|
@ -93,7 +103,8 @@ class AvatarControllerTest extends \Test\TestCase {
|
|||
$this->rootFolder,
|
||||
$this->logger,
|
||||
'userid',
|
||||
$this->timeFactory
|
||||
$this->timeFactory,
|
||||
$this->guestAvatarController,
|
||||
);
|
||||
|
||||
// Configure userMock
|
||||
|
|
|
|||
Loading…
Reference in a new issue