diff options
author | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2021-01-19 16:38:51 +0100 |
---|---|---|
committer | Julius Härtl <jus@bitgrid.net> | 2021-01-28 12:00:20 +0100 |
commit | 4f90766ba314171bbfc78d1e988307c50633e7f3 (patch) | |
tree | f5f910ff0f3dd2ff8fa5a05c8fd1f905ffc21ba5 | |
parent | 7e6d69d166cbc92fb457fc72efc9abe850a0bbe4 (diff) | |
download | nextcloud-server-4f90766ba314171bbfc78d1e988307c50633e7f3.tar.gz nextcloud-server-4f90766ba314171bbfc78d1e988307c50633e7f3.zip |
Skip template picker if none available
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
-rw-r--r-- | apps/files/lib/Controller/TemplateController.php | 12 | ||||
-rw-r--r-- | apps/files/lib/Controller/ViewController.php | 2 | ||||
-rw-r--r-- | apps/files/src/components/TemplatePreview.vue | 45 | ||||
-rw-r--r-- | apps/files/src/services/Templates.js | 29 | ||||
-rw-r--r-- | apps/files/src/templates.js | 91 | ||||
-rw-r--r-- | apps/files/src/utils/davUtils.js | 18 | ||||
-rw-r--r-- | apps/files/src/views/TemplatePicker.vue | 72 | ||||
-rw-r--r-- | core/css/css-variables.scss | 3 | ||||
-rw-r--r-- | core/css/icons.scss | 1 | ||||
-rw-r--r-- | core/img/actions/template-add.svg | 1 | ||||
-rw-r--r-- | lib/private/Files/Template/TemplateManager.php | 95 | ||||
-rw-r--r-- | lib/private/legacy/OC_Util.php | 6 | ||||
-rw-r--r-- | lib/public/Files/Template/ITemplateManager.php | 4 | ||||
-rw-r--r-- | lib/public/Files/Template/TemplateFileCreator.php | 19 |
14 files changed, 294 insertions, 104 deletions
diff --git a/apps/files/lib/Controller/TemplateController.php b/apps/files/lib/Controller/TemplateController.php index 08a324a46ec..5a163343223 100644 --- a/apps/files/lib/Controller/TemplateController.php +++ b/apps/files/lib/Controller/TemplateController.php @@ -64,12 +64,12 @@ class TemplateController extends OCSController { */ public function path(string $templatePath = '', bool $copySystemTemplates = false) { try { - $this->templateManager->setTemplatePath($templatePath); - if ($copySystemTemplates) { - $this->templateManager->initializeTemplateDirectory($templatePath); - } - return new DataResponse(); - } catch (GenericFileException $e) { + $templatePath = $this->templateManager->initializeTemplateDirectory($templatePath, null, $copySystemTemplates); + return new DataResponse([ + 'template_path' => $templatePath, + 'templates' => $this->templateManager->listCreators() + ]); + } catch (\Exception $e) { throw new OCSForbiddenException($e->getMessage()); } } diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php index 846ec14c4aa..aade5a5b44a 100644 --- a/apps/files/lib/Controller/ViewController.php +++ b/apps/files/lib/Controller/ViewController.php @@ -294,7 +294,7 @@ class ViewController extends Controller { if (class_exists(LoadViewer::class)) { $this->eventDispatcher->dispatchTyped(new LoadViewer()); } - $this->initialState->provideInitialState('template_path', $this->templateManager->hasTemplateDirectory() ? $this->templateManager->getTemplatePath() : null); + $this->initialState->provideInitialState('templates_path', $this->templateManager->hasTemplateDirectory() ? $this->templateManager->getTemplatePath() : null); $this->initialState->provideInitialState('templates', $this->templateManager->listCreators()); $params = []; diff --git a/apps/files/src/components/TemplatePreview.vue b/apps/files/src/components/TemplatePreview.vue index 538e1bcff7b..89162ba4efd 100644 --- a/apps/files/src/components/TemplatePreview.vue +++ b/apps/files/src/components/TemplatePreview.vue @@ -30,9 +30,9 @@ @change="onCheck"> <label :for="id" class="template-picker__label"> - <div class="template-picker__preview"> + <div class="template-picker__preview" + :class="failedPreview ? 'template-picker__preview--failed' : ''"> <img class="template-picker__image" - :class="failedPreview ? 'template-picker__image--failed' : ''" :src="realPreviewUrl" alt="" draggable="false" @@ -40,7 +40,7 @@ </div> <span class="template-picker__title"> - {{ basename }} + {{ nameWithoutExt }} </span> </label> </li> @@ -100,6 +100,14 @@ export default { }, computed: { + /** + * Strip away extension from name + * @returns {string} + */ + nameWithoutExt() { + return this.basename.indexOf('.') > -1 ? this.basename.split('.').slice(0, -1).join('.') : this.basename + }, + id() { return `template-picker-${this.fileid}` }, @@ -107,7 +115,7 @@ export default { realPreviewUrl() { // If original preview failed, fallback to mime icon if (this.failedPreview && this.mimeIcon) { - return generateUrl(this.mimeIcon) + return this.mimeIcon } if (this.previewUrl) { @@ -149,7 +157,6 @@ export default { align-items: center; flex: 1 1; flex-direction: column; - margin: var(--margin); &, * { cursor: pointer; @@ -162,32 +169,42 @@ export default { } &__preview { - display: flex; + display: block; overflow: hidden; // Stretch so all entries are the same width flex: 1 1; width: var(--width); - min-height: var(--width); + min-height: var(--height); max-height: var(--height); - padding: var(--margin); + padding: 0; border: var(--border) solid var(--color-border); border-radius: var(--border-radius-large); input:checked + label > & { border-color: var(--color-primary); } + + &--failed { + // Make sure to properly center fallback icon + display: flex; + } } &__image { max-width: 100%; background-color: var(--color-main-background); - &--failed { - width: calc(var(--margin) * 8); - // Center mime icon - margin: auto; - background-color: transparent !important; - } + object-fit: cover; + } + + // Failed preview, fallback to mime icon + &__preview--failed &__image { + width: calc(var(--margin) * 8); + // Center mime icon + margin: auto; + background-color: transparent !important; + + object-fit: initial; } &__title { diff --git a/apps/files/src/services/Templates.js b/apps/files/src/services/Templates.js new file mode 100644 index 00000000000..c1a8992d661 --- /dev/null +++ b/apps/files/src/services/Templates.js @@ -0,0 +1,29 @@ +/** + * @copyright Copyright (c) 2021 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ <skjnldsv@protonmail.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +import { generateOcsUrl } from '@nextcloud/router' +import axios from '@nextcloud/axios' + +export const getTemplates = async function() { + const response = await axios.get(generateOcsUrl('apps/files/api/v1', 2) + 'templates') + return response.data.ocs.data +} diff --git a/apps/files/src/templates.js b/apps/files/src/templates.js index 3cffc30085c..c493e254caf 100644 --- a/apps/files/src/templates.js +++ b/apps/files/src/templates.js @@ -23,9 +23,14 @@ import { getLoggerBuilder } from '@nextcloud/logger' import { loadState } from '@nextcloud/initial-state' import { translate as t, translatePlural as n } from '@nextcloud/l10n' +import { generateOcsUrl } from '@nextcloud/router' +import { getCurrentDirectory } from './utils/davUtils' +import axios from '@nextcloud/axios' import Vue from 'vue' import TemplatePickerView from './views/TemplatePicker' +import { getCurrentUser } from '@nextcloud/auth' +import { showError } from '@nextcloud/dialogs' // Set up logger const logger = getLoggerBuilder() @@ -47,8 +52,10 @@ TemplatePickerRoot.id = 'template-picker' document.body.appendChild(TemplatePickerRoot) // Retrieve and init templates -const templates = loadState('files', 'templates', []) +let templates = loadState('files', 'templates', []) +let templatesPath = loadState('files', 'templates_path', false) logger.debug('Templates providers', templates) +logger.debug('Templates folder', { templatesPath }) // Init vue app const View = Vue.extend(TemplatePickerView) @@ -60,33 +67,77 @@ const TemplatePicker = new View({ }) TemplatePicker.$mount('#template-picker') -// Init template engine after load +// Init template engine after load to make sure it's the last injected entry window.addEventListener('DOMContentLoaded', function() { - // Init template files menu - templates.forEach((provider, index) => { - - const newTemplatePlugin = { + if (!templatesPath) { + logger.debug('Templates folder not initialized') + const initTemplatesPlugin = { attach(menu) { - const fileList = menu.fileList - - // only attach to main file list, public view is not supported yet - if (fileList.id !== 'files' && fileList.id !== 'files.public') { - return - } - // register the new menu entry menu.addMenuEntry({ - id: `template-new-${provider.app}-${index}`, - displayName: provider.label, - templateName: provider.label + provider.extension, - iconClass: provider.iconClass || 'icon-file', + id: 'template-init', + displayName: t('files', 'Set up templates folder'), + templateName: t('files', 'Templates'), + iconClass: 'icon-template-add', fileType: 'file', actionHandler(name) { - TemplatePicker.open(name, provider) + initTemplatesFolder(name) }, }) }, } - OC.Plugins.register('OCA.Files.NewFileMenu', newTemplatePlugin) - }) + OC.Plugins.register('OCA.Files.NewFileMenu', initTemplatesPlugin) + } +}) + +// Init template files menu +templates.forEach((provider, index) => { + const newTemplatePlugin = { + attach(menu) { + const fileList = menu.fileList + + // only attach to main file list, public view is not supported yet + if (fileList.id !== 'files' && fileList.id !== 'files.public') { + return + } + + // register the new menu entry + menu.addMenuEntry({ + id: `template-new-${provider.app}-${index}`, + displayName: provider.label, + templateName: provider.label + provider.extension, + iconClass: provider.iconClass || 'icon-file', + fileType: 'file', + actionHandler(name) { + TemplatePicker.open(name, provider) + }, + }) + }, + } + OC.Plugins.register('OCA.Files.NewFileMenu', newTemplatePlugin) }) + +/** + * Init the template directory + * + * @param {string} name the templates folder name + */ +const initTemplatesFolder = async function(name) { + const templatePath = (getCurrentDirectory() + `/${name}`).replace('//', '/') + try { + logger.debug('Initializing the templates directory', { templatePath }) + const response = await axios.post(generateOcsUrl('apps/files/api/v1/templates', 2) + 'path', { + templatePath, + copySystemTemplates: true, + }) + + // Go to template directory + OCA.Files.App.currentFileList.changeDirectory(templatePath, true, true) + + templates = response.data.ocs.data.templates + templatesPath = response.data.ocs.data.template_path + } catch (error) { + logger.error('Unable to initialize the templates directory') + showError(t('files', 'Unable to initialize the templates directory')) + } +} diff --git a/apps/files/src/utils/davUtils.js b/apps/files/src/utils/davUtils.js index f64801f08dd..cd5732a4772 100644 --- a/apps/files/src/utils/davUtils.js +++ b/apps/files/src/utils/davUtils.js @@ -23,7 +23,7 @@ import { generateRemoteUrl } from '@nextcloud/router' import { getCurrentUser } from '@nextcloud/auth' -const getRootPath = function() { +export const getRootPath = function() { if (getCurrentUser()) { return generateRemoteUrl(`dav/files/${getCurrentUser().uid}`) } else { @@ -31,12 +31,22 @@ const getRootPath = function() { } } -const isPublic = function() { +export const isPublic = function() { return !getCurrentUser() } -const getToken = function() { +export const getToken = function() { return document.getElementById('sharingToken') && document.getElementById('sharingToken').value } -export { getRootPath, getToken, isPublic } +/** + * Return the current directory, fallback to root + * @returns {string} + */ +export const getCurrentDirectory = function() { + const currentDirInfo = OCA?.Files?.App?.currentFileList?.dirInfo + || { path: '/', name: '' } + + // Make sure we don't have double slashes + return `${currentDirInfo.path}/${currentDirInfo.name}`.replace(/\/\//gi, '/') +} diff --git a/apps/files/src/views/TemplatePicker.vue b/apps/files/src/views/TemplatePicker.vue index 84c7c38aba4..95ba3d3a4dc 100644 --- a/apps/files/src/views/TemplatePicker.vue +++ b/apps/files/src/views/TemplatePicker.vue @@ -29,7 +29,7 @@ <form class="templates-picker__form" :style="style" @submit.prevent.stop="onSubmit"> - <h3>{{ t('files', 'Pick a template') }}</h3> + <h2>{{ t('files', 'Pick a template for {name}', { name: nameWithoutExt }) }}</h2> <!-- Templates list --> <ul class="templates-picker__list"> @@ -55,11 +55,11 @@ <input type="submit" class="primary" :value="t('files', 'Create')" - :aria-label="t('files', 'Create a new file with the ')"> + :aria-label="t('files', 'Create a new file with the selected template')"> </div> </form> - <EmptyContent class="templates-picker__loading" v-if="loading" icon="icon-loading"> + <EmptyContent v-if="loading" class="templates-picker__loading" icon="icon-loading"> {{ t('files', 'Creating file') }} </EmptyContent> </Modal> @@ -68,11 +68,12 @@ <script> import { generateOcsUrl } from '@nextcloud/router' import { showError } from '@nextcloud/dialogs' - import axios from '@nextcloud/axios' import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent' import Modal from '@nextcloud/vue/dist/Components/Modal' +import { getCurrentDirectory } from '../utils/davUtils' +import { getTemplates } from '../services/Templates' import TemplatePreview from '../components/TemplatePreview' const border = 2 @@ -107,6 +108,14 @@ export default { }, computed: { + /** + * Strip away extension from name + * @returns {string} + */ + nameWithoutExt() { + return this.name.indexOf('.') > -1 ? this.name.split('.').slice(0, -1).join('.') : this.name + }, + emptyTemplate() { return { basename: t('files', 'Blank'), @@ -131,7 +140,7 @@ export default { '--width': width + 'px', '--border': border + 'px', '--fullwidth': width + 2 * margin + 2 * border + 'px', - '--height': this.ratio ? width * this.ratio + 'px' : null, + '--height': this.provider.ratio ? Math.round(width / this.provider.ratio) + 'px' : null, } }, }, @@ -142,11 +151,27 @@ export default { * @param {string} name the file name to create * @param {object} provider the template provider picked */ - open(name, provider) { + async open(name, provider) { + this.checked = this.emptyTemplate.fileid this.name = name - this.opened = true this.provider = provider + + const templates = await getTemplates() + const fetchedProvider = templates.find((fetchedProvider) => fetchedProvider.app === provider.app && fetchedProvider.label === provider.label) + if (fetchedProvider === null) { + throw new Error('Failed to match provider in results') + } + this.provider = fetchedProvider + + // If there is no templates available, just create an empty file + if (fetchedProvider.templates.length === 0) { + this.onSubmit() + return + } + + // Else, open the picker + this.opened = true }, /** @@ -170,7 +195,7 @@ export default { async onSubmit() { this.loading = true - const currentDirectory = this.getCurrentDirectory() + const currentDirectory = getCurrentDirectory() const fileList = OCA?.Files?.App?.currentFileList try { @@ -197,24 +222,13 @@ export default { this.close() } catch (error) { - this.logger.error('Error while creating the new file from template', error) + this.logger.error('Error while creating the new file from template') + console.error(error) showError(this.t('files', 'Unable to create new file from template')) } finally { this.loading = false } }, - - /** - * Return the current directory, fallback to root - * @returns {string} - */ - getCurrentDirectory() { - const currentDirInfo = OCA?.Files?.App?.currentFileList?.dirInfo - || { path: '/', name: '' } - - // Make sure we don't have double slashes - return `${currentDirInfo.path}/${currentDirInfo.name}`.replace(/\/\//gi, '/') - }, }, } </script> @@ -225,6 +239,12 @@ export default { padding: calc(var(--margin) * 2); // Will be handled by the buttons padding-bottom: 0; + + h2 { + text-align: center; + font-weight: bold; + margin: var(--margin) 0 calc(var(--margin) * 2); + } } &__list { @@ -233,18 +253,20 @@ export default { grid-auto-columns: 1fr; // We want maximum 5 columns. Putting 6 as we don't count the grid gap. So it will always be lower than 6 max-width: calc(var(--fullwidth) * 6); - grid-template-columns: repeat(auto-fit, minmax(var(--fullwidth), 1fr)); + grid-template-columns: repeat(auto-fit, var(--fullwidth)); // Make sure all rows are the same height grid-auto-rows: 1fr; + // Center the columns set + justify-content: center; } + &__buttons { display: flex; justify-content: space-between; padding: calc(var(--margin) * 2) var(--margin); position: sticky; - // Make sure the templates list doesn't weirdly peak under when scrolled. Happens on some rare occasions - bottom: -1px; - background-color: var(--color-main-background); + bottom: 0; + background-image: linear-gradient(0, var(--gradient-main-background)); } // Make sure we're relative for the loading emptycontent on top diff --git a/core/css/css-variables.scss b/core/css/css-variables.scss index 2e0fbad5aa1..86f80611a6c 100644 --- a/core/css/css-variables.scss +++ b/core/css/css-variables.scss @@ -7,6 +7,9 @@ --color-main-background: #{$color-main-background}; --color-main-background-translucent: #{$color-main-background-translucent}; + // To use like this: background-image: linear-gradient(0, var(--gradient-main-background)); + --gradient-main-background: var(--color-main-background) 0%, var(--color-main-background-translucent) 85%, transparent 100%; + --color-background-hover: #{$color-background-hover}; --color-background-dark: #{$color-background-dark}; --color-background-darker: #{$color-background-darker}; diff --git a/core/css/icons.scss b/core/css/icons.scss index b3ef33e9fa5..c38bdb89daa 100644 --- a/core/css/icons.scss +++ b/core/css/icons.scss @@ -229,6 +229,7 @@ audio, canvas, embed, iframe, img, input, object, video { @include icon-black-white('quota', 'actions', 1, true); @include icon-black-white('rename', 'actions', 1, true); @include icon-black-white('screen', 'actions', 1, true); +@include icon-black-white('template-add', 'actions', 1, true); .icon-screen-white { filter: drop-shadow(1px 1px 4px var(--color-box-shadow)); diff --git a/core/img/actions/template-add.svg b/core/img/actions/template-add.svg new file mode 100644 index 00000000000..828dd074f80 --- /dev/null +++ b/core/img/actions/template-add.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21.33 21.33" width="16" height="16"><path fill="none" d="M0 0h24v24H0z"/><path d="M15.33 18h-12V6H10V4H3.33a2 2 0 00-2 2v12c0 1.1.9 2 2 2h12a2 2 0 002-2v-6.67h-2zM17.33 1.33h-2V4h-2.66v2h2.66v2.67h2V6H20V4h-2.67z"/><path d="M5.33 14.33h8v2h-8zm8-1.33v-2h-8v2zm-8-5.33h8v2h-8z"/></svg>
\ No newline at end of file diff --git a/lib/private/Files/Template/TemplateManager.php b/lib/private/Files/Template/TemplateManager.php index 614440327e8..808c0fbb999 100644 --- a/lib/private/Files/Template/TemplateManager.php +++ b/lib/private/Files/Template/TemplateManager.php @@ -34,6 +34,7 @@ use OCP\Files\GenericFileException; use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; use OCP\Files\Template\CreatedFromTemplateEvent; use OCP\Files\Template\ICustomTemplateProvider; use OCP\Files\Template\ITemplateManager; @@ -103,7 +104,15 @@ class TemplateManager implements ITemplateManager { return $this->providers; } - public function listCreators(): array { + public function listCreators():? array { + if ($this->types === null) { + return null; + } + + usort($this->types, function (TemplateFileCreator $a, TemplateFileCreator $b) { + return $a->getOrder() - $b->getOrder(); + }); + return array_map(function (TemplateFileCreator $entry) { return array_merge($entry->jsonSerialize(), [ 'templates' => $this->getTemplateFiles($entry) @@ -154,7 +163,10 @@ class TemplateManager implements ITemplateManager { * @throws \OC\User\NoUserException */ private function getTemplateFolder(): Node { - return $this->rootFolder->getUserFolder($this->userId)->get($this->getTemplatePath()); + if ($this->getTemplatePath() !== '') { + return $this->rootFolder->getUserFolder($this->userId)->get($this->getTemplatePath()); + } + throw new NotFoundException(); } private function getTemplateFiles(TemplateFileCreator $type): array { @@ -220,10 +232,10 @@ class TemplateManager implements ITemplateManager { } public function getTemplatePath(): string { - return $this->config->getUserValue($this->userId, 'core', 'templateDirectory', $this->l10n->t('Templates') . '/'); + return $this->config->getUserValue($this->userId, 'core', 'templateDirectory', ''); } - public function initializeTemplateDirectory(string $path = null, string $userId = null): void { + public function initializeTemplateDirectory(string $path = null, string $userId = null, $copyTemplates = true): string { if ($userId !== null) { $this->userId = $userId; } @@ -232,6 +244,8 @@ class TemplateManager implements ITemplateManager { $defaultTemplateDirectory = \OC::$SERVERROOT . '/core/skeleton/Templates'; $skeletonPath = $this->config->getSystemValue('skeletondirectory', $defaultSkeletonDirectory); $skeletonTemplatePath = $this->config->getSystemValue('templatedirectory', $defaultTemplateDirectory); + $isDefaultSkeleton = $skeletonPath === $defaultSkeletonDirectory; + $isDefaultTemplates = $skeletonTemplatePath === $defaultTemplateDirectory; $userLang = $this->l10nFactory->getUserLanguage(); try { @@ -239,39 +253,64 @@ class TemplateManager implements ITemplateManager { $userFolder = $this->rootFolder->getUserFolder($this->userId); $userTemplatePath = $path ?? $l10n->t('Templates') . '/'; - // All locations are default so we just need to rename the directory to the users language - if ($skeletonPath === $defaultSkeletonDirectory && $skeletonTemplatePath === $defaultTemplateDirectory && $userFolder->nodeExists('Templates')) { - $newPath = $userFolder->getPath() . '/' . $userTemplatePath; - if ($newPath !== $userFolder->get('Templates')->getPath()) { - $userFolder->get('Templates')->move($newPath); + // Initial user setup without a provided path + if ($path === null) { + // All locations are default so we just need to rename the directory to the users language + if ($isDefaultSkeleton && $isDefaultTemplates && $userFolder->nodeExists('Templates')) { + $newPath = $userFolder->getPath() . '/' . $userTemplatePath; + if ($newPath !== $userFolder->get('Templates')->getPath()) { + $userFolder->get('Templates')->move($newPath); + } + $this->setTemplatePath($userTemplatePath); + return $userTemplatePath; } - $this->setTemplatePath($userTemplatePath); - return; - } - // A custom template directory is specified - if (!empty($skeletonTemplatePath) && $skeletonTemplatePath !== $defaultTemplateDirectory) { - // In case the shipped template files are in place we remove them - if ($skeletonPath === $defaultSkeletonDirectory && $userFolder->nodeExists('Templates')) { + if ($isDefaultSkeleton && !empty($skeletonTemplatePath) && !$isDefaultTemplates && $userFolder->nodeExists('Templates')) { $shippedSkeletonTemplates = $userFolder->get('Templates'); $shippedSkeletonTemplates->delete(); } - try { - $userFolder->get($userTemplatePath); - } catch (NotFoundException $e) { - $folder = $userFolder->newFolder($userTemplatePath); - - $localizedSkeletonTemplatePath = $this->getLocalizedTemplatePath($skeletonTemplatePath, $userLang); - if (!empty($localizedSkeletonTemplatePath) && file_exists($localizedSkeletonTemplatePath)) { - \OC_Util::copyr($localizedSkeletonTemplatePath, $folder); - $userFolder->getStorage()->getScanner()->scan($userTemplatePath, Scanner::SCAN_RECURSIVE); - } - } + } + + try { + $folder = $userFolder->newFolder($userTemplatePath); + } catch (NotPermittedException $e) { + $folder = $userFolder->get($userTemplatePath); + } + + $folderIsEmpty = count($folder->getDirectoryListing()) === 0; + + if (!$copyTemplates) { $this->setTemplatePath($userTemplatePath); + return $userTemplatePath; + } + + if (!$isDefaultTemplates && $folderIsEmpty) { + $localizedSkeletonTemplatePath = $this->getLocalizedTemplatePath($skeletonTemplatePath, $userLang); + if (!empty($localizedSkeletonTemplatePath) && file_exists($localizedSkeletonTemplatePath)) { + \OC_Util::copyr($localizedSkeletonTemplatePath, $folder); + $userFolder->getStorage()->getScanner()->scan($userTemplatePath, Scanner::SCAN_RECURSIVE); + $this->setTemplatePath($userTemplatePath); + return $userTemplatePath; + } } + + if ($path !== null && $isDefaultSkeleton && $isDefaultTemplates && $folderIsEmpty) { + $localizedSkeletonPath = $this->getLocalizedTemplatePath($skeletonPath . '/Templates', $userLang); + if (!empty($localizedSkeletonPath) && file_exists($localizedSkeletonPath)) { + \OC_Util::copyr($localizedSkeletonPath, $folder); + $userFolder->getStorage()->getScanner()->scan($userTemplatePath, Scanner::SCAN_RECURSIVE); + $this->setTemplatePath($userTemplatePath); + return $userTemplatePath; + } + } + + $this->setTemplatePath($path ?? ''); + return $this->getTemplatePath(); } catch (\Throwable $e) { - $this->logger->error('Failed to rename templates directory to user language ' . $userLang . ' for ' . $userId, ['app' => 'files_templates']); + $this->logger->error('Failed to initialize templates directory to user language ' . $userLang . ' for ' . $userId, ['app' => 'files_templates', 'exception' => $e]); } + $this->setTemplatePath(''); + return $this->getTemplatePath(); } private function getLocalizedTemplatePath(string $skeletonTemplatePath, string $userLang) { diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php index 05d54cf84e6..f1e88166e97 100644 --- a/lib/private/legacy/OC_Util.php +++ b/lib/private/legacy/OC_Util.php @@ -449,9 +449,9 @@ class OC_Util { // update the file cache $userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE); - /** @var ITemplateManager $templateManaer */ - $templateManaer = \OC::$server->get(ITemplateManager::class); - $templateManaer->initializeTemplateDirectory(null, $userId); + /** @var ITemplateManager $templateManager */ + $templateManager = \OC::$server->get(ITemplateManager::class); + $templateManager->initializeTemplateDirectory(null, $userId); } } diff --git a/lib/public/Files/Template/ITemplateManager.php b/lib/public/Files/Template/ITemplateManager.php index 28d57a8b94c..58b5b6c4846 100644 --- a/lib/public/Files/Template/ITemplateManager.php +++ b/lib/public/Files/Template/ITemplateManager.php @@ -56,7 +56,7 @@ interface ITemplateManager { * @return array * @since 21.0.0 */ - public function listCreators(): array; + public function listCreators():? array; /** * @return bool @@ -82,7 +82,7 @@ interface ITemplateManager { * @param string|null $userId * @since 21.0.0 */ - public function initializeTemplateDirectory(string $path = null, string $userId = null): void; + public function initializeTemplateDirectory(string $path = null, string $userId = null, $copyTemplates = true): string; /** * @param string $filePath diff --git a/lib/public/Files/Template/TemplateFileCreator.php b/lib/public/Files/Template/TemplateFileCreator.php index c41a6514ee5..e40fa8e91b3 100644 --- a/lib/public/Files/Template/TemplateFileCreator.php +++ b/lib/public/Files/Template/TemplateFileCreator.php @@ -35,6 +35,7 @@ final class TemplateFileCreator implements \JsonSerializable { protected $fileExtension; protected $iconClass; protected $ratio = null; + protected $order = 100; /** * @since 21.0.0 @@ -80,12 +81,28 @@ final class TemplateFileCreator implements \JsonSerializable { /** * @since 21.0.0 */ - public function setRatio(float $ratio) { + public function setRatio(float $ratio): TemplateFileCreator { $this->ratio = $ratio; return $this; } /** + * @param int $order order in which the create action shall be listed + * @since 21.0.0 + */ + public function setOrder(int $order): TemplateFileCreator { + $this->order = $order; + return $this; + } + + /** + * @since 21.0.0 + */ + public function getOrder(): int { + return $this->order; + } + + /** * @since 21.0.0 */ public function jsonSerialize() { |