Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix(encryption): Correctly set encrypted to 0 when copying
If encryption got disabled, copying should set encrypted to 0 for the
 new unencrypted copy. For instance when using encryption:decrypt-all

Signed-off-by: Côme Chilliet <[email protected]>
Signed-off-by: Louis Chemineau <[email protected]>
  • Loading branch information
artonge authored and AndyScherzinger committed Sep 8, 2025
commit 2e2994d6a43851b8789779181898e7bcd8ce7c71
8 changes: 7 additions & 1 deletion apps/files/src/newMenu/newFromTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { TemplateFile } from '../types.ts'

import { Folder, Node, Permission, addNewFileMenuEntry } from '@nextcloud/files'
import { loadState } from '@nextcloud/initial-state'
import { isPublicShare } from '@nextcloud/sharing/public'
import { newNodeName } from '../utils/newNodeDialog'
import { translate as t } from '@nextcloud/l10n'
import Vue, { defineAsyncComponent } from 'vue'
Expand Down Expand Up @@ -46,7 +47,12 @@ const getTemplatePicker = async (context: Folder) => {
* Register all new-file-menu entries for all template providers
*/
export function registerTemplateEntries() {
const templates = loadState<TemplateFile[]>('files', 'templates', [])
let templates: TemplateFile[]
if (isPublicShare()) {
templates = loadState<TemplateFile[]>('files_sharing', 'templates', [])
} else {
templates = loadState<TemplateFile[]>('files', 'templates', [])
}

// Init template files menu
templates.forEach((provider, index) => {
Expand Down
124 changes: 78 additions & 46 deletions apps/files/src/views/TemplatePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ import type { TemplateFile } from '../types.ts'
import { getCurrentUser } from '@nextcloud/auth'
import { showError, spawnDialog } from '@nextcloud/dialogs'
import { emit } from '@nextcloud/event-bus'
import { File } from '@nextcloud/files'
import { File, Node } from '@nextcloud/files'
import { getClient, getRootPath, resultToNode, getDefaultPropfind } from '@nextcloud/files/dav'
import { translate as t } from '@nextcloud/l10n'
import { generateRemoteUrl } from '@nextcloud/router'
import { normalize, extname, join } from 'path'
Expand All @@ -64,6 +65,7 @@ import NcModal from '@nextcloud/vue/components/NcModal'
import TemplatePreview from '../components/TemplatePreview.vue'
import TemplateFiller from '../components/TemplateFiller.vue'
import logger from '../logger.ts'
import type { FileStat, ResponseDataDetailed } from 'webdav'

const border = 2
const margin = 8
Expand Down Expand Up @@ -167,6 +169,12 @@ export default defineComponent({
this.name = name
this.provider = provider

// Skip templates logic for external users.
if (getCurrentUser() === null) {
this.onSubmit()
return
}

const templates = await getTemplates()
const fetchedProvider = templates.find((fetchedProvider) => fetchedProvider.app === provider.app && fetchedProvider.label === provider.label)
if (fetchedProvider === null) {
Expand Down Expand Up @@ -224,56 +232,80 @@ export default defineComponent({
this.name = `${this.name}${this.provider?.extension ?? ''}`
}

try {
const fileInfo = await createFromTemplate(
normalize(`${currentDirectory}/${this.name}`),
this.selectedTemplate?.filename as string ?? '',
this.selectedTemplate?.templateType as string ?? '',
templateFields,
)
logger.debug('Created new file', fileInfo)

const owner = getCurrentUser()?.uid || null
const node = new File({
id: fileInfo.fileid,
source: generateRemoteUrl(join(`dav/files/${owner}`, fileInfo.filename)),
root: `/files/${owner}`,
mime: fileInfo.mime,
mtime: new Date(fileInfo.lastmod * 1000),
owner,
size: fileInfo.size,
permissions: fileInfo.permissions,
attributes: {
// Inherit some attributes from parent folder like the mount type and real owner
'mount-type': this.parent?.attributes?.['mount-type'],
'owner-id': this.parent?.attributes?.['owner-id'],
'owner-display-name': this.parent?.attributes?.['owner-display-name'],
...fileInfo,
'has-preview': fileInfo.hasPreview,
},
})
// Create a blank file for external users as we can't use the templates.
if (getCurrentUser() === null) {
const client = getClient()
const filename = join(getRootPath(), currentDirectory, this.name ?? '')

await client.putFileContents(filename, '')
const response = await client.stat(filename, { data: getDefaultPropfind(), details: true }) as ResponseDataDetailed<FileStat>
logger.debug('Created new file', { fileInfo: response.data })

const node = resultToNode(response.data)

// Update files list
emit('files:node:created', node)

// Open the new file
window.OCP.Files.Router.goToRoute(
null, // use default route
{ view: 'files', fileid: node.fileid },
{ dir: node.dirname, openfile: 'true' },
)

// Close the picker
this.close()
} catch (error) {
logger.error('Error while creating the new file from template', { error })
showError(t('files', 'Unable to create new file from template'))
} finally {
this.loading = false
this.handleFileCreation(node)
} else {
try {
const fileInfo = await createFromTemplate(
normalize(`${currentDirectory}/${this.name}`),
this.selectedTemplate?.filename as string ?? '',
this.selectedTemplate?.templateType as string ?? '',
templateFields,
)
logger.debug('Created new file', { fileInfo })

const owner = getCurrentUser()?.uid || null
const node = new File({
id: fileInfo.fileid,
source: generateRemoteUrl(join(`dav/files/${owner}`, fileInfo.filename)),
root: `/files/${owner}`,
mime: fileInfo.mime,
mtime: new Date(fileInfo.lastmod * 1000),
owner,
size: fileInfo.size,
permissions: fileInfo.permissions,
attributes: {
// Inherit some attributes from parent folder like the mount type and real owner
'mount-type': this.parent?.attributes?.['mount-type'],
'owner-id': this.parent?.attributes?.['owner-id'],
'owner-display-name': this.parent?.attributes?.['owner-display-name'],
...fileInfo,
'has-preview': fileInfo.hasPreview,
},
})

this.handleFileCreation(node)

// Close the picker
this.close()
} catch (error) {
logger.error('Error while creating the new file from template', { error })
showError(t('files', 'Unable to create new file from template'))
} finally {
this.loading = false
}
}
},

handleFileCreation(node: Node) {
// Update files list
emit('files:node:created', node)

// Open the new file
window.OCP.Files.Router.goToRoute(
null, // use default route
{ view: 'files', fileid: node.fileid },
{ dir: node.dirname, openfile: 'true' },
)
},

async onSubmit() {
// Skip templates logic for external users.
if (getCurrentUser() === null) {
this.loading = true
return this.createFile()
}

const fileId = this.selectedTemplate?.fileid

// Only request field extraction if there is a valid template
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\File;
use OCP\Files\Template\ITemplateManager;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IL10N;
Expand All @@ -49,6 +50,7 @@ public function __construct(
private Defaults $defaults,
private IConfig $config,
private IRequest $request,
private ITemplateManager $templateManager,
private IInitialState $initialState,
private IAppConfig $appConfig,
) {
Expand Down Expand Up @@ -119,6 +121,8 @@ public function renderPage(IShare $share, string $token, string $path): Template
$this->eventDispatcher->dispatchTyped(new LoadViewer());
}

$this->initialState->provideInitialState('templates', $this->templateManager->listCreators());

// Allow external apps to register their scripts
$this->eventDispatcher->dispatchTyped(new BeforeTemplateRenderedEvent($share));

Expand Down
6 changes: 6 additions & 0 deletions apps/files_sharing/tests/Controller/ShareControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use OCP\Files\File;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Files\Template\ITemplateManager;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IL10N;
Expand Down Expand Up @@ -68,6 +69,7 @@ class ShareControllerTest extends \Test\TestCase {
private Manager&MockObject $shareManager;
private IPreview&MockObject $previewManager;
private IUserManager&MockObject $userManager;
private ITemplateManager&MockObject $templateManager;
private IInitialState&MockObject $initialState;
private IURLGenerator&MockObject $urlGenerator;
private ISecureRandom&MockObject $secureRandom;
Expand All @@ -87,6 +89,7 @@ protected function setUp(): void {
$this->config = $this->createMock(IConfig::class);
$this->appConfig = $this->createMock(IAppConfig::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->templateManager = $this->createMock(ITemplateManager::class);
$this->initialState = $this->createMock(IInitialState::class);
$this->federatedShareProvider = $this->createMock(FederatedShareProvider::class);
$this->federatedShareProvider->expects($this->any())
Expand Down Expand Up @@ -114,6 +117,7 @@ protected function setUp(): void {
$this->defaults,
$this->config,
$this->createMock(IRequest::class),
$this->templateManager,
$this->initialState,
$this->appConfig,
)
Expand Down Expand Up @@ -338,6 +342,7 @@ public function testShowShare(): void {
'owner' => 'ownerUID',
'ownerDisplayName' => 'ownerDisplay',
'isFileRequest' => false,
'templates' => [],
];

$response = $this->shareController->showShare();
Expand Down Expand Up @@ -485,6 +490,7 @@ public function testShowFileDropShare(): void {
'isFileRequest' => false,
'note' => 'The note',
'label' => 'A label',
'templates' => [],
];

$response = $this->shareController->showShare();
Expand Down
Loading