Merge pull request #25090 from nextcloud/enh/file-templates
File templates
This commit is contained in:
commit
62fa85c7bf
|
@ -138,6 +138,21 @@ $application->registerRoutes(
|
|||
'url' => '/api/v1/directEditing/create',
|
||||
'verb' => 'POST'
|
||||
],
|
||||
[
|
||||
'name' => 'Template#list',
|
||||
'url' => '/api/v1/templates',
|
||||
'verb' => 'GET'
|
||||
],
|
||||
[
|
||||
'name' => 'Template#create',
|
||||
'url' => '/api/v1/templates/create',
|
||||
'verb' => 'POST'
|
||||
],
|
||||
[
|
||||
'name' => 'Template#path',
|
||||
'url' => '/api/v1/templates/path',
|
||||
'verb' => 'POST'
|
||||
],
|
||||
[
|
||||
'name' => 'TransferOwnership#transfer',
|
||||
'url' => '/api/v1/transferownership',
|
||||
|
|
|
@ -35,6 +35,7 @@ return array(
|
|||
'OCA\\Files\\Controller\\ApiController' => $baseDir . '/../lib/Controller/ApiController.php',
|
||||
'OCA\\Files\\Controller\\DirectEditingController' => $baseDir . '/../lib/Controller/DirectEditingController.php',
|
||||
'OCA\\Files\\Controller\\DirectEditingViewController' => $baseDir . '/../lib/Controller/DirectEditingViewController.php',
|
||||
'OCA\\Files\\Controller\\TemplateController' => $baseDir . '/../lib/Controller/TemplateController.php',
|
||||
'OCA\\Files\\Controller\\TransferOwnershipController' => $baseDir . '/../lib/Controller/TransferOwnershipController.php',
|
||||
'OCA\\Files\\Controller\\ViewController' => $baseDir . '/../lib/Controller/ViewController.php',
|
||||
'OCA\\Files\\Db\\TransferOwnership' => $baseDir . '/../lib/Db/TransferOwnership.php',
|
||||
|
|
|
@ -50,6 +50,7 @@ class ComposerStaticInitFiles
|
|||
'OCA\\Files\\Controller\\ApiController' => __DIR__ . '/..' . '/../lib/Controller/ApiController.php',
|
||||
'OCA\\Files\\Controller\\DirectEditingController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingController.php',
|
||||
'OCA\\Files\\Controller\\DirectEditingViewController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingViewController.php',
|
||||
'OCA\\Files\\Controller\\TemplateController' => __DIR__ . '/..' . '/../lib/Controller/TemplateController.php',
|
||||
'OCA\\Files\\Controller\\TransferOwnershipController' => __DIR__ . '/..' . '/../lib/Controller/TransferOwnershipController.php',
|
||||
'OCA\\Files\\Controller\\ViewController' => __DIR__ . '/..' . '/../lib/Controller/ViewController.php',
|
||||
'OCA\\Files\\Db\\TransferOwnership' => __DIR__ . '/..' . '/../lib/Db/TransferOwnership.php',
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -119,6 +119,11 @@
|
|||
|
||||
var lastPos;
|
||||
var checkInput = function () {
|
||||
// Special handling for the setup template directory
|
||||
if ($target.attr('data-action') === 'template-init') {
|
||||
return true;
|
||||
}
|
||||
|
||||
var filename = $input.val();
|
||||
try {
|
||||
if (!Files.isFileNameValid(filename)) {
|
||||
|
@ -198,7 +203,21 @@
|
|||
iconClass: actionSpec.iconClass,
|
||||
fileType: actionSpec.fileType,
|
||||
actionHandler: actionSpec.actionHandler,
|
||||
});
|
||||
checkFilename: actionSpec.checkFilename
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a menu item from the "New" file menu
|
||||
* @param {string} actionId
|
||||
*/
|
||||
removeMenuEntry: function(actionId) {
|
||||
var index = this._menuItems.findIndex(function (actionSpec) {
|
||||
return actionSpec.id === actionId;
|
||||
});
|
||||
if (index > -1) {
|
||||
this._menuItems.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCA\Files\Controller;
|
||||
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCS\OCSForbiddenException;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\Files\GenericFileException;
|
||||
use OCP\Files\Template\ITemplateManager;
|
||||
use OCP\IRequest;
|
||||
|
||||
class TemplateController extends OCSController {
|
||||
protected $templateManager;
|
||||
|
||||
public function __construct($appName, IRequest $request, ITemplateManager $templateManager) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->templateManager = $templateManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function list(): DataResponse {
|
||||
return new DataResponse($this->templateManager->listTemplates());
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @throws OCSForbiddenException
|
||||
*/
|
||||
public function create(string $filePath, string $templatePath = '', string $templateType = 'user'): DataResponse {
|
||||
try {
|
||||
return new DataResponse($this->templateManager->createFromTemplate($filePath, $templatePath, $templateType));
|
||||
} catch (GenericFileException $e) {
|
||||
throw new OCSForbiddenException($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function path(string $templatePath = '', bool $copySystemTemplates = false) {
|
||||
try {
|
||||
$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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,10 +44,12 @@ use OCP\AppFramework\Http\ContentSecurityPolicy;
|
|||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\Template\ITemplateManager;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
|
@ -80,6 +82,10 @@ class ViewController extends Controller {
|
|||
protected $rootFolder;
|
||||
/** @var Helper */
|
||||
protected $activityHelper;
|
||||
/** @var IInitialState */
|
||||
private $initialState;
|
||||
/** @var ITemplateManager */
|
||||
private $templateManager;
|
||||
|
||||
public function __construct(string $appName,
|
||||
IRequest $request,
|
||||
|
@ -90,7 +96,9 @@ class ViewController extends Controller {
|
|||
IUserSession $userSession,
|
||||
IAppManager $appManager,
|
||||
IRootFolder $rootFolder,
|
||||
Helper $activityHelper
|
||||
Helper $activityHelper,
|
||||
IInitialState $initialState,
|
||||
ITemplateManager $templateManager
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->appName = $appName;
|
||||
|
@ -103,6 +111,8 @@ class ViewController extends Controller {
|
|||
$this->appManager = $appManager;
|
||||
$this->rootFolder = $rootFolder;
|
||||
$this->activityHelper = $activityHelper;
|
||||
$this->initialState = $initialState;
|
||||
$this->templateManager = $templateManager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,6 +190,7 @@ class ViewController extends Controller {
|
|||
// Load the files we need
|
||||
\OCP\Util::addStyle('files', 'merged');
|
||||
\OCP\Util::addScript('files', 'merged-index');
|
||||
\OCP\Util::addScript('files', 'dist/templates');
|
||||
|
||||
// mostly for the home storage's free space
|
||||
// FIXME: Make non static
|
||||
|
@ -283,6 +294,8 @@ class ViewController extends Controller {
|
|||
if (class_exists(LoadViewer::class)) {
|
||||
$this->eventDispatcher->dispatchTyped(new LoadViewer());
|
||||
}
|
||||
$this->initialState->provideInitialState('templates_path', $this->templateManager->hasTemplateDirectory() ? $this->templateManager->getTemplatePath() : null);
|
||||
$this->initialState->provideInitialState('templates', $this->templateManager->listCreators());
|
||||
|
||||
$params = [];
|
||||
$params['usedSpacePercent'] = (int) $storageInfo['relative'];
|
||||
|
|
|
@ -30,7 +30,8 @@ namespace OCA\Files\Event;
|
|||
use OCP\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* This event is triggered when the files app is rendered. It canb e used to add additional scripts to the files app.
|
||||
* This event is triggered when the files app is rendered.
|
||||
* It can be used to add additional scripts to the files app.
|
||||
*
|
||||
* @since 17.0.0
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2020 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/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<li class="template-picker__item">
|
||||
<input :id="id"
|
||||
:checked="checked"
|
||||
type="radio"
|
||||
class="radio"
|
||||
name="template-picker"
|
||||
@change="onCheck">
|
||||
|
||||
<label :for="id" class="template-picker__label">
|
||||
<div class="template-picker__preview"
|
||||
:class="failedPreview ? 'template-picker__preview--failed' : ''">
|
||||
<img class="template-picker__image"
|
||||
:src="realPreviewUrl"
|
||||
alt=""
|
||||
draggable="false"
|
||||
@error="onFailure">
|
||||
</div>
|
||||
|
||||
<span class="template-picker__title">
|
||||
{{ nameWithoutExt }}
|
||||
</span>
|
||||
</label>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { encodeFilePath } from '../utils/fileUtils'
|
||||
import { getToken, isPublic } from '../utils/davUtils'
|
||||
|
||||
// preview width generation
|
||||
const previewWidth = 256
|
||||
|
||||
export default {
|
||||
name: 'TemplatePreview',
|
||||
inheritAttrs: false,
|
||||
|
||||
props: {
|
||||
basename: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
fileid: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
filename: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
previewUrl: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
hasPreview: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
mime: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
ratio: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
failedPreview: false,
|
||||
}
|
||||
},
|
||||
|
||||
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}`
|
||||
},
|
||||
|
||||
realPreviewUrl() {
|
||||
// If original preview failed, fallback to mime icon
|
||||
if (this.failedPreview && this.mimeIcon) {
|
||||
return this.mimeIcon
|
||||
}
|
||||
|
||||
if (this.previewUrl) {
|
||||
return this.previewUrl
|
||||
}
|
||||
// TODO: find a nicer standard way of doing this?
|
||||
if (isPublic()) {
|
||||
return generateUrl(`/apps/files_sharing/publicpreview/${getToken()}?fileId=${this.fileid}&file=${encodeFilePath(this.filename)}&x=${previewWidth}&y=${previewWidth}&a=1`)
|
||||
}
|
||||
return generateUrl(`/core/preview?fileId=${this.fileid}&x=${previewWidth}&y=${previewWidth}&a=1`)
|
||||
},
|
||||
|
||||
mimeIcon() {
|
||||
return OC.MimeType.getIconUrl(this.mime)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
onCheck() {
|
||||
this.$emit('check', this.fileid)
|
||||
},
|
||||
onFailure() {
|
||||
this.failedPreview = true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.template-picker {
|
||||
&__item {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: flex;
|
||||
// Align in the middle of the grid
|
||||
align-items: center;
|
||||
flex: 1 1;
|
||||
flex-direction: column;
|
||||
|
||||
&, * {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__preview {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
// Stretch so all entries are the same width
|
||||
flex: 1 1;
|
||||
width: var(--width);
|
||||
min-height: var(--height);
|
||||
max-height: var(--height);
|
||||
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);
|
||||
|
||||
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 {
|
||||
overflow: hidden;
|
||||
// also count preview border
|
||||
max-width: calc(var(--width) + 2*2px);
|
||||
padding: var(--margin);
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2020 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 { 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()
|
||||
.setApp('files')
|
||||
.detectUser()
|
||||
.build()
|
||||
|
||||
// Add translates functions
|
||||
Vue.mixin({
|
||||
methods: {
|
||||
t,
|
||||
n,
|
||||
},
|
||||
})
|
||||
|
||||
// Create document root
|
||||
const TemplatePickerRoot = document.createElement('div')
|
||||
TemplatePickerRoot.id = 'template-picker'
|
||||
document.body.appendChild(TemplatePickerRoot)
|
||||
|
||||
// Retrieve and init 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)
|
||||
const TemplatePicker = new View({
|
||||
name: 'TemplatePicker',
|
||||
propsData: {
|
||||
logger,
|
||||
},
|
||||
})
|
||||
TemplatePicker.$mount('#template-picker')
|
||||
|
||||
// Init template engine after load to make sure it's the last injected entry
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
if (!templatesPath) {
|
||||
logger.debug('Templates folder not initialized')
|
||||
const initTemplatesPlugin = {
|
||||
attach(menu) {
|
||||
// register the new menu entry
|
||||
menu.addMenuEntry({
|
||||
id: 'template-init',
|
||||
displayName: t('files', 'Set up templates folder'),
|
||||
templateName: t('files', 'Templates'),
|
||||
iconClass: 'icon-template-add',
|
||||
fileType: 'file',
|
||||
actionHandler(name) {
|
||||
initTemplatesFolder(name)
|
||||
menu.removeMenuEntry('template-init')
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
||||
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'))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2019 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 { generateRemoteUrl } from '@nextcloud/router'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
|
||||
export const getRootPath = function() {
|
||||
if (getCurrentUser()) {
|
||||
return generateRemoteUrl(`dav/files/${getCurrentUser().uid}`)
|
||||
} else {
|
||||
return generateRemoteUrl('webdav').replace('/remote.php', '/public.php')
|
||||
}
|
||||
}
|
||||
|
||||
export const isPublic = function() {
|
||||
return !getCurrentUser()
|
||||
}
|
||||
|
||||
export const getToken = function() {
|
||||
return document.getElementById('sharingToken') && document.getElementById('sharingToken').value
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, '/')
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get an url encoded path
|
||||
*
|
||||
* @param {String} path the full path
|
||||
* @returns {string} url encoded file path
|
||||
*/
|
||||
const encodeFilePath = function(path) {
|
||||
const pathSections = (path.startsWith('/') ? path : `/${path}`).split('/')
|
||||
let relativePath = ''
|
||||
pathSections.forEach((section) => {
|
||||
if (section !== '') {
|
||||
relativePath += '/' + encodeURIComponent(section)
|
||||
}
|
||||
})
|
||||
return relativePath
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract dir and name from file path
|
||||
*
|
||||
* @param {String} path the full path
|
||||
* @returns {String[]} [dirPath, fileName]
|
||||
*/
|
||||
const extractFilePaths = function(path) {
|
||||
const pathSections = path.split('/')
|
||||
const fileName = pathSections[pathSections.length - 1]
|
||||
const dirPath = pathSections.slice(0, pathSections.length - 1).join('/')
|
||||
return [dirPath, fileName]
|
||||
}
|
||||
|
||||
export { encodeFilePath, extractFilePaths }
|
|
@ -0,0 +1,294 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2020 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/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<Modal v-if="opened"
|
||||
:clear-view-delay="-1"
|
||||
class="templates-picker"
|
||||
size="large"
|
||||
@close="close">
|
||||
<form class="templates-picker__form"
|
||||
:style="style"
|
||||
@submit.prevent.stop="onSubmit">
|
||||
<h2>{{ t('files', 'Pick a template for {name}', { name: nameWithoutExt }) }}</h2>
|
||||
|
||||
<!-- Templates list -->
|
||||
<ul class="templates-picker__list">
|
||||
<TemplatePreview
|
||||
v-bind="emptyTemplate"
|
||||
:checked="checked === emptyTemplate.fileid"
|
||||
@check="onCheck" />
|
||||
|
||||
<TemplatePreview
|
||||
v-for="template in provider.templates"
|
||||
:key="template.fileid"
|
||||
v-bind="template"
|
||||
:checked="checked === template.fileid"
|
||||
:ratio="provider.ratio"
|
||||
@check="onCheck" />
|
||||
</ul>
|
||||
|
||||
<!-- Cancel and submit -->
|
||||
<div class="templates-picker__buttons">
|
||||
<button @click="close">
|
||||
{{ t('files', 'Cancel') }}
|
||||
</button>
|
||||
<input type="submit"
|
||||
class="primary"
|
||||
:value="t('files', 'Create')"
|
||||
:aria-label="t('files', 'Create a new file with the selected template')">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<EmptyContent v-if="loading" class="templates-picker__loading" icon="icon-loading">
|
||||
{{ t('files', 'Creating file') }}
|
||||
</EmptyContent>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<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
|
||||
const margin = 8
|
||||
const width = margin * 20
|
||||
|
||||
export default {
|
||||
name: 'TemplatePicker',
|
||||
|
||||
components: {
|
||||
EmptyContent,
|
||||
Modal,
|
||||
TemplatePreview,
|
||||
},
|
||||
|
||||
props: {
|
||||
logger: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
// Check empty template by default
|
||||
checked: -1,
|
||||
loading: false,
|
||||
name: null,
|
||||
opened: false,
|
||||
provider: null,
|
||||
}
|
||||
},
|
||||
|
||||
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'),
|
||||
fileid: -1,
|
||||
filename: this.t('files', 'Blank'),
|
||||
hasPreview: false,
|
||||
mime: this.provider?.mimetypes[0] || this.provider?.mimetypes,
|
||||
}
|
||||
},
|
||||
|
||||
selectedTemplate() {
|
||||
return this.provider.templates.find(template => template.fileid === this.checked)
|
||||
},
|
||||
|
||||
/**
|
||||
* Style css vars bin,d
|
||||
* @returns {Object}
|
||||
*/
|
||||
style() {
|
||||
return {
|
||||
'--margin': margin + 'px',
|
||||
'--width': width + 'px',
|
||||
'--border': border + 'px',
|
||||
'--fullwidth': width + 2 * margin + 2 * border + 'px',
|
||||
'--height': this.provider.ratio ? Math.round(width / this.provider.ratio) + 'px' : null,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Open the picker
|
||||
* @param {string} name the file name to create
|
||||
* @param {object} provider the template provider picked
|
||||
*/
|
||||
async open(name, provider) {
|
||||
|
||||
this.checked = this.emptyTemplate.fileid
|
||||
this.name = name
|
||||
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
|
||||
},
|
||||
|
||||
/**
|
||||
* Close the picker and reset variables
|
||||
*/
|
||||
close() {
|
||||
this.checked = this.emptyTemplate.fileid
|
||||
this.loading = false
|
||||
this.name = null
|
||||
this.opened = false
|
||||
this.provider = null
|
||||
},
|
||||
|
||||
/**
|
||||
* Manages the radio template picker change
|
||||
* @param {number} fileid the selected template file id
|
||||
*/
|
||||
onCheck(fileid) {
|
||||
this.checked = fileid
|
||||
},
|
||||
|
||||
async onSubmit() {
|
||||
this.loading = true
|
||||
const currentDirectory = getCurrentDirectory()
|
||||
const fileList = OCA?.Files?.App?.currentFileList
|
||||
|
||||
try {
|
||||
const response = await axios.post(generateOcsUrl('apps/files/api/v1/templates', 2) + 'create', {
|
||||
filePath: `${currentDirectory}/${this.name}`,
|
||||
templatePath: this.selectedTemplate?.filename,
|
||||
templateType: this.selectedTemplate?.templateType,
|
||||
})
|
||||
|
||||
const fileInfo = response.data.ocs.data
|
||||
this.logger.debug('Created new file', fileInfo)
|
||||
|
||||
// Run default action
|
||||
const fileAction = OCA.Files.fileActions.getDefaultFileAction(fileInfo.mime, 'file', OC.PERMISSION_ALL)
|
||||
fileAction.action(fileInfo.basename, {
|
||||
$file: null,
|
||||
dir: currentDirectory,
|
||||
fileList,
|
||||
fileActions: fileList?.fileActions,
|
||||
})
|
||||
|
||||
// Reload files list
|
||||
fileList?.reload?.() || window.location.reload()
|
||||
|
||||
this.close()
|
||||
} catch (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
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.templates-picker {
|
||||
&__form {
|
||||
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 {
|
||||
display: grid;
|
||||
grid-gap: calc(var(--margin) * 2);
|
||||
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, 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;
|
||||
bottom: 0;
|
||||
background-image: linear-gradient(0, var(--gradient-main-background));
|
||||
|
||||
button, input[type='submit'] {
|
||||
height: 44px;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we're relative for the loading emptycontent on top
|
||||
/deep/ .modal-container {
|
||||
position: relative;
|
||||
overflow-y: auto !important;
|
||||
}
|
||||
|
||||
&__loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
background-color: var(--color-main-background-translucent);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -36,10 +36,12 @@ use OCA\Files\Activity\Helper;
|
|||
use OCA\Files\Controller\ViewController;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\Template\ITemplateManager;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
|
@ -78,6 +80,10 @@ class ViewControllerTest extends TestCase {
|
|||
private $rootFolder;
|
||||
/** @var Helper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $activityHelper;
|
||||
/** @var IInitialState|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $initialState;
|
||||
/** @var ITemplateManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $templateManager;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
@ -97,6 +103,8 @@ class ViewControllerTest extends TestCase {
|
|||
->willReturn($this->user);
|
||||
$this->rootFolder = $this->getMockBuilder('\OCP\Files\IRootFolder')->getMock();
|
||||
$this->activityHelper = $this->createMock(Helper::class);
|
||||
$this->initialState = $this->createMock(IInitialState::class);
|
||||
$this->templateManager = $this->createMock(ITemplateManager::class);
|
||||
$this->viewController = $this->getMockBuilder('\OCA\Files\Controller\ViewController')
|
||||
->setConstructorArgs([
|
||||
'files',
|
||||
|
@ -109,6 +117,8 @@ class ViewControllerTest extends TestCase {
|
|||
$this->appManager,
|
||||
$this->rootFolder,
|
||||
$this->activityHelper,
|
||||
$this->initialState,
|
||||
$this->templateManager,
|
||||
])
|
||||
->setMethods([
|
||||
'getStorageInfo',
|
||||
|
|
|
@ -2,7 +2,8 @@ const path = require('path');
|
|||
|
||||
module.exports = {
|
||||
entry: {
|
||||
'sidebar': path.join(__dirname, 'src', 'sidebar.js'),
|
||||
sidebar: path.join(__dirname, 'src', 'sidebar.js'),
|
||||
templates: path.join(__dirname, 'src', 'templates.js'),
|
||||
'files-app-settings': path.join(__dirname, 'src', 'files-app-settings.js'),
|
||||
'personal-settings': path.join(__dirname, 'src', 'main-personal-settings.js'),
|
||||
},
|
||||
|
|
|
@ -317,6 +317,21 @@ $CONFIG = [
|
|||
*/
|
||||
'skeletondirectory' => '/path/to/nextcloud/core/skeleton',
|
||||
|
||||
|
||||
/**
|
||||
* The directory where the template files are located. These files will be
|
||||
* copied to the template directory of new users. Leave empty to not copy any
|
||||
* template files.
|
||||
* ``{lang}`` can be used as a placeholder for the language of the user.
|
||||
* If the directory does not exist, it falls back to non dialect (from ``de_DE``
|
||||
* to ``de``). If that does not exist either, it falls back to ``default``
|
||||
*
|
||||
* If this is not set creating a template directory will only happen if no custom
|
||||
* ``skeletondirectory`` is defined, otherwise the shipped templates will be used
|
||||
* to create a template directory for the user.
|
||||
*/
|
||||
'templatesdirectory' => '/path/to/nextcloud/templates',
|
||||
|
||||
/**
|
||||
* If your user backend does not allow password resets (e.g. when it's a
|
||||
* read-only user backend like LDAP), you can specify a custom link, where the
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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>
|
After Width: | Height: | Size: 337 B |
|
@ -315,6 +315,11 @@ return array(
|
|||
'OCP\\Files\\Storage\\IStorage' => $baseDir . '/lib/public/Files/Storage/IStorage.php',
|
||||
'OCP\\Files\\Storage\\IStorageFactory' => $baseDir . '/lib/public/Files/Storage/IStorageFactory.php',
|
||||
'OCP\\Files\\Storage\\IWriteStreamStorage' => $baseDir . '/lib/public/Files/Storage/IWriteStreamStorage.php',
|
||||
'OCP\\Files\\Template\\FileCreatedFromTemplateEvent' => $baseDir . '/lib/public/Files/Template/FileCreatedFromTemplateEvent.php',
|
||||
'OCP\\Files\\Template\\ICustomTemplateProvider' => $baseDir . '/lib/public/Files/Template/ICustomTemplateProvider.php',
|
||||
'OCP\\Files\\Template\\ITemplateManager' => $baseDir . '/lib/public/Files/Template/ITemplateManager.php',
|
||||
'OCP\\Files\\Template\\Template' => $baseDir . '/lib/public/Files/Template/Template.php',
|
||||
'OCP\\Files\\Template\\TemplateFileCreator' => $baseDir . '/lib/public/Files/Template/TemplateFileCreator.php',
|
||||
'OCP\\Files\\UnseekableException' => $baseDir . '/lib/public/Files/UnseekableException.php',
|
||||
'OCP\\Files_FullTextSearch\\Model\\AFilesDocument' => $baseDir . '/lib/public/Files_FullTextSearch/Model/AFilesDocument.php',
|
||||
'OCP\\FullTextSearch\\Exceptions\\FullTextSearchAppNotAvailableException' => $baseDir . '/lib/public/FullTextSearch/Exceptions/FullTextSearchAppNotAvailableException.php',
|
||||
|
@ -1125,6 +1130,7 @@ return array(
|
|||
'OC\\Files\\Stream\\HashWrapper' => $baseDir . '/lib/private/Files/Stream/HashWrapper.php',
|
||||
'OC\\Files\\Stream\\Quota' => $baseDir . '/lib/private/Files/Stream/Quota.php',
|
||||
'OC\\Files\\Stream\\SeekableHttpStream' => $baseDir . '/lib/private/Files/Stream/SeekableHttpStream.php',
|
||||
'OC\\Files\\Template\\TemplateManager' => $baseDir . '/lib/private/Files/Template/TemplateManager.php',
|
||||
'OC\\Files\\Type\\Detection' => $baseDir . '/lib/private/Files/Type/Detection.php',
|
||||
'OC\\Files\\Type\\Loader' => $baseDir . '/lib/private/Files/Type/Loader.php',
|
||||
'OC\\Files\\Type\\TemplateManager' => $baseDir . '/lib/private/Files/Type/TemplateManager.php',
|
||||
|
|
|
@ -344,6 +344,11 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OCP\\Files\\Storage\\IStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IStorage.php',
|
||||
'OCP\\Files\\Storage\\IStorageFactory' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IStorageFactory.php',
|
||||
'OCP\\Files\\Storage\\IWriteStreamStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IWriteStreamStorage.php',
|
||||
'OCP\\Files\\Template\\FileCreatedFromTemplateEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Template/FileCreatedFromTemplateEvent.php',
|
||||
'OCP\\Files\\Template\\ICustomTemplateProvider' => __DIR__ . '/../../..' . '/lib/public/Files/Template/ICustomTemplateProvider.php',
|
||||
'OCP\\Files\\Template\\ITemplateManager' => __DIR__ . '/../../..' . '/lib/public/Files/Template/ITemplateManager.php',
|
||||
'OCP\\Files\\Template\\Template' => __DIR__ . '/../../..' . '/lib/public/Files/Template/Template.php',
|
||||
'OCP\\Files\\Template\\TemplateFileCreator' => __DIR__ . '/../../..' . '/lib/public/Files/Template/TemplateFileCreator.php',
|
||||
'OCP\\Files\\UnseekableException' => __DIR__ . '/../../..' . '/lib/public/Files/UnseekableException.php',
|
||||
'OCP\\Files_FullTextSearch\\Model\\AFilesDocument' => __DIR__ . '/../../..' . '/lib/public/Files_FullTextSearch/Model/AFilesDocument.php',
|
||||
'OCP\\FullTextSearch\\Exceptions\\FullTextSearchAppNotAvailableException' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Exceptions/FullTextSearchAppNotAvailableException.php',
|
||||
|
@ -1154,6 +1159,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\Files\\Stream\\HashWrapper' => __DIR__ . '/../../..' . '/lib/private/Files/Stream/HashWrapper.php',
|
||||
'OC\\Files\\Stream\\Quota' => __DIR__ . '/../../..' . '/lib/private/Files/Stream/Quota.php',
|
||||
'OC\\Files\\Stream\\SeekableHttpStream' => __DIR__ . '/../../..' . '/lib/private/Files/Stream/SeekableHttpStream.php',
|
||||
'OC\\Files\\Template\\TemplateManager' => __DIR__ . '/../../..' . '/lib/private/Files/Template/TemplateManager.php',
|
||||
'OC\\Files\\Type\\Detection' => __DIR__ . '/../../..' . '/lib/private/Files/Type/Detection.php',
|
||||
'OC\\Files\\Type\\Loader' => __DIR__ . '/../../..' . '/lib/private/Files/Type/Loader.php',
|
||||
'OC\\Files\\Type\\TemplateManager' => __DIR__ . '/../../..' . '/lib/private/Files/Type/TemplateManager.php',
|
||||
|
|
|
@ -77,6 +77,9 @@ class RegistrationContext {
|
|||
/** @var array[] */
|
||||
private $wellKnownHandlers = [];
|
||||
|
||||
/** @var array[] */
|
||||
private $templateProviders = [];
|
||||
|
||||
/** @var ILogger */
|
||||
private $logger;
|
||||
|
||||
|
@ -186,6 +189,13 @@ class RegistrationContext {
|
|||
$class
|
||||
);
|
||||
}
|
||||
|
||||
public function registerTemplateProvider(string $providerClass): void {
|
||||
$this->context->registerTemplateProvider(
|
||||
$this->appId,
|
||||
$providerClass
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -279,6 +289,13 @@ class RegistrationContext {
|
|||
];
|
||||
}
|
||||
|
||||
public function registerTemplateProvider(string $appId, string $class): void {
|
||||
$this->templateProviders[] = [
|
||||
'appId' => $appId,
|
||||
'class' => $class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param App[] $apps
|
||||
*/
|
||||
|
@ -451,7 +468,7 @@ class RegistrationContext {
|
|||
}
|
||||
|
||||
/**
|
||||
* @erturn array[]
|
||||
* @return array[]
|
||||
*/
|
||||
public function getInitialStates(): array {
|
||||
return $this->initialStates;
|
||||
|
@ -463,4 +480,8 @@ class RegistrationContext {
|
|||
public function getWellKnownHandlers(): array {
|
||||
return $this->wellKnownHandlers;
|
||||
}
|
||||
|
||||
public function getTemplateProviders(): array {
|
||||
return $this->templateProviders;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,346 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace OC\Files\Template;
|
||||
|
||||
use OC\AppFramework\Bootstrap\Coordinator;
|
||||
use OC\Files\Cache\Scanner;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\File;
|
||||
use OCP\Files\GenericFileException;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\Node;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\Files\Template\FileCreatedFromTemplateEvent;
|
||||
use OCP\Files\Template\ICustomTemplateProvider;
|
||||
use OCP\Files\Template\ITemplateManager;
|
||||
use OCP\Files\Template\Template;
|
||||
use OCP\Files\Template\TemplateFileCreator;
|
||||
use OCP\IConfig;
|
||||
use OCP\IPreview;
|
||||
use OCP\IServerContainer;
|
||||
use OCP\IUserSession;
|
||||
use OCP\L10N\IFactory;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class TemplateManager implements ITemplateManager {
|
||||
private $registeredTypes = [];
|
||||
private $types = [];
|
||||
|
||||
/** @var array|null */
|
||||
private $providers = null;
|
||||
|
||||
private $serverContainer;
|
||||
private $eventDispatcher;
|
||||
private $rootFolder;
|
||||
private $previewManager;
|
||||
private $config;
|
||||
private $l10n;
|
||||
private $logger;
|
||||
private $userId;
|
||||
private $l10nFactory;
|
||||
/** @var Coordinator */
|
||||
private $bootstrapCoordinator;
|
||||
|
||||
public function __construct(
|
||||
IServerContainer $serverContainer,
|
||||
IEventDispatcher $eventDispatcher,
|
||||
Coordinator $coordinator,
|
||||
IRootFolder $rootFolder,
|
||||
IUserSession $userSession,
|
||||
IPreview $previewManager,
|
||||
IConfig $config,
|
||||
IFactory $l10nFactory,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->serverContainer = $serverContainer;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
$this->bootstrapCoordinator = $coordinator;
|
||||
$this->rootFolder = $rootFolder;
|
||||
$this->previewManager = $previewManager;
|
||||
$this->config = $config;
|
||||
$this->l10nFactory = $l10nFactory;
|
||||
$this->l10n = $l10nFactory->get('lib');
|
||||
$this->logger = $logger;
|
||||
$user = $userSession->getUser();
|
||||
$this->userId = $user ? $user->getUID() : null;
|
||||
}
|
||||
|
||||
public function registerTemplateFileCreator(callable $callback): void {
|
||||
$this->registeredTypes[] = $callback;
|
||||
}
|
||||
|
||||
public function getRegisteredProviders(): array {
|
||||
if ($this->providers !== null) {
|
||||
return $this->providers;
|
||||
}
|
||||
|
||||
$context = $this->bootstrapCoordinator->getRegistrationContext();
|
||||
|
||||
$this->providers = [];
|
||||
foreach ($context->getTemplateProviders() as $provider) {
|
||||
$this->providers[$provider['class']] = $this->serverContainer->get($provider['class']);
|
||||
}
|
||||
return $this->providers;
|
||||
}
|
||||
|
||||
public function getTypes(): array {
|
||||
foreach ($this->registeredTypes as $registeredType) {
|
||||
$this->types[] = $registeredType();
|
||||
}
|
||||
return $this->types;
|
||||
}
|
||||
|
||||
public function listCreators(): array {
|
||||
$types = $this->getTypes();
|
||||
usort($types, function (TemplateFileCreator $a, TemplateFileCreator $b) {
|
||||
return $a->getOrder() - $b->getOrder();
|
||||
});
|
||||
return $types;
|
||||
}
|
||||
|
||||
public function listTemplates(): array {
|
||||
return array_map(function (TemplateFileCreator $entry) {
|
||||
return array_merge($entry->jsonSerialize(), [
|
||||
'templates' => $this->getTemplateFiles($entry)
|
||||
]);
|
||||
}, $this->listCreators());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filePath
|
||||
* @param string $templateId
|
||||
* @return array
|
||||
* @throws GenericFileException
|
||||
*/
|
||||
public function createFromTemplate(string $filePath, string $templateId = '', string $templateType = 'user'): array {
|
||||
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||
try {
|
||||
$userFolder->get($filePath);
|
||||
throw new GenericFileException($this->l10n->t('File already exists'));
|
||||
} catch (NotFoundException $e) {
|
||||
}
|
||||
try {
|
||||
$targetFile = $userFolder->newFile($filePath);
|
||||
if ($templateType === 'user' && $templateId !== '') {
|
||||
$template = $userFolder->get($templateId);
|
||||
$template->copy($targetFile->getPath());
|
||||
} else {
|
||||
$matchingProvider = array_filter($this->getRegisteredProviders(), function (ICustomTemplateProvider $provider) use ($templateType) {
|
||||
return $templateType === get_class($provider);
|
||||
});
|
||||
$provider = array_shift($matchingProvider);
|
||||
if ($provider) {
|
||||
$template = $provider->getCustomTemplate($templateId);
|
||||
$template->copy($targetFile->getPath());
|
||||
}
|
||||
}
|
||||
$this->eventDispatcher->dispatchTyped(new FileCreatedFromTemplateEvent($template, $targetFile));
|
||||
return $this->formatFile($userFolder->get($filePath));
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error($e->getMessage(), ['exception' => $e]);
|
||||
throw new GenericFileException($this->l10n->t('Failed to create file from template'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Folder
|
||||
* @throws \OCP\Files\NotFoundException
|
||||
* @throws \OCP\Files\NotPermittedException
|
||||
* @throws \OC\User\NoUserException
|
||||
*/
|
||||
private function getTemplateFolder(): Node {
|
||||
if ($this->getTemplatePath() !== '') {
|
||||
return $this->rootFolder->getUserFolder($this->userId)->get($this->getTemplatePath());
|
||||
}
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
private function getTemplateFiles(TemplateFileCreator $type): array {
|
||||
$templates = [];
|
||||
foreach ($this->getRegisteredProviders() as $provider) {
|
||||
foreach ($type->getMimetypes() as $mimetype) {
|
||||
foreach ($provider->getCustomTemplates($mimetype) as $template) {
|
||||
$templates[] = $template;
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
$userTemplateFolder = $this->getTemplateFolder();
|
||||
} catch (\Exception $e) {
|
||||
return $templates;
|
||||
}
|
||||
foreach ($type->getMimetypes() as $mimetype) {
|
||||
foreach ($userTemplateFolder->searchByMime($mimetype) as $templateFile) {
|
||||
$template = new Template(
|
||||
'user',
|
||||
$this->rootFolder->getUserFolder($this->userId)->getRelativePath($templateFile->getPath()),
|
||||
$templateFile
|
||||
);
|
||||
$template->setHasPreview($this->previewManager->isAvailable($templateFile));
|
||||
$templates[] = $template;
|
||||
}
|
||||
}
|
||||
|
||||
return $templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node|File $file
|
||||
* @return array
|
||||
* @throws NotFoundException
|
||||
* @throws \OCP\Files\InvalidPathException
|
||||
*/
|
||||
private function formatFile(Node $file): array {
|
||||
return [
|
||||
'basename' => $file->getName(),
|
||||
'etag' => $file->getEtag(),
|
||||
'fileid' => $file->getId(),
|
||||
'filename' => $this->rootFolder->getUserFolder($this->userId)->getRelativePath($file->getPath()),
|
||||
'lastmod' => $file->getMTime(),
|
||||
'mime' => $file->getMimetype(),
|
||||
'size' => $file->getSize(),
|
||||
'type' => $file->getType(),
|
||||
'hasPreview' => $this->previewManager->isAvailable($file)
|
||||
];
|
||||
}
|
||||
|
||||
public function hasTemplateDirectory(): bool {
|
||||
try {
|
||||
$this->getTemplateFolder();
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function setTemplatePath(string $path): void {
|
||||
$this->config->setUserValue($this->userId, 'core', 'templateDirectory', $path);
|
||||
}
|
||||
|
||||
public function getTemplatePath(): string {
|
||||
return $this->config->getUserValue($this->userId, 'core', 'templateDirectory', '');
|
||||
}
|
||||
|
||||
public function initializeTemplateDirectory(string $path = null, string $userId = null, $copyTemplates = true): string {
|
||||
if ($userId !== null) {
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
$defaultSkeletonDirectory = \OC::$SERVERROOT . '/core/skeleton';
|
||||
$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 {
|
||||
$l10n = $this->l10nFactory->get('lib', $userLang);
|
||||
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||
$userTemplatePath = $path ?? $l10n->t('Templates') . '/';
|
||||
|
||||
// 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) {
|
||||
if (!$userFolder->nodeExists('Templates')) {
|
||||
return '';
|
||||
}
|
||||
$newPath = $userFolder->getPath() . '/' . $userTemplatePath;
|
||||
if ($newPath !== $userFolder->get('Templates')->getPath()) {
|
||||
$userFolder->get('Templates')->move($newPath);
|
||||
}
|
||||
$this->setTemplatePath($userTemplatePath);
|
||||
return $userTemplatePath;
|
||||
}
|
||||
|
||||
if ($isDefaultSkeleton && !empty($skeletonTemplatePath) && !$isDefaultTemplates && $userFolder->nodeExists('Templates')) {
|
||||
$shippedSkeletonTemplates = $userFolder->get('Templates');
|
||||
$shippedSkeletonTemplates->delete();
|
||||
}
|
||||
}
|
||||
|
||||
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 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) {
|
||||
$localizedSkeletonTemplatePath = str_replace('{lang}', $userLang, $skeletonTemplatePath);
|
||||
|
||||
if (!file_exists($localizedSkeletonTemplatePath)) {
|
||||
$dialectStart = strpos($userLang, '_');
|
||||
if ($dialectStart !== false) {
|
||||
$localizedSkeletonTemplatePath = str_replace('{lang}', substr($userLang, 0, $dialectStart), $skeletonTemplatePath);
|
||||
}
|
||||
if ($dialectStart === false || !file_exists($localizedSkeletonTemplatePath)) {
|
||||
$localizedSkeletonTemplatePath = str_replace('{lang}', 'default', $skeletonTemplatePath);
|
||||
}
|
||||
}
|
||||
|
||||
return $localizedSkeletonTemplatePath;
|
||||
}
|
||||
}
|
|
@ -95,6 +95,7 @@ use OC\Files\Node\HookConnector;
|
|||
use OC\Files\Node\LazyRoot;
|
||||
use OC\Files\Node\Root;
|
||||
use OC\Files\Storage\StorageFactory;
|
||||
use OC\Files\Template\TemplateManager;
|
||||
use OC\Files\Type\Loader;
|
||||
use OC\Files\View;
|
||||
use OC\FullTextSearch\FullTextSearchManager;
|
||||
|
@ -166,6 +167,7 @@ use OCP\Files\IRootFolder;
|
|||
use OCP\Files\Mount\IMountManager;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\Storage\IStorageFactory;
|
||||
use OCP\Files\Template\ITemplateManager;
|
||||
use OCP\FullTextSearch\IFullTextSearchManager;
|
||||
use OCP\GlobalScale\IConfig;
|
||||
use OCP\Group\Events\BeforeGroupCreatedEvent;
|
||||
|
@ -289,6 +291,7 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
$this->registerDeprecatedAlias('ContactsManager', \OCP\Contacts\IManager::class);
|
||||
|
||||
$this->registerAlias(\OCP\DirectEditing\IManager::class, \OC\DirectEditing\Manager::class);
|
||||
$this->registerAlias(ITemplateManager::class, TemplateManager::class);
|
||||
|
||||
$this->registerAlias(IActionFactory::class, ActionFactory::class);
|
||||
|
||||
|
|
|
@ -66,11 +66,13 @@
|
|||
use bantu\IniGetWrapper\IniGetWrapper;
|
||||
use OC\AppFramework\Http\Request;
|
||||
use OC\Files\Storage\LocalRootStorage;
|
||||
use OCP\Files\Template\ITemplateManager;
|
||||
use OCP\IConfig;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\ILogger;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserSession;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class OC_Util {
|
||||
public static $scripts = [];
|
||||
|
@ -411,6 +413,9 @@ class OC_Util {
|
|||
* @suppress PhanDeprecatedFunction
|
||||
*/
|
||||
public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) {
|
||||
/** @var LoggerInterface $logger */
|
||||
$logger = \OC::$server->get(LoggerInterface::class);
|
||||
|
||||
$plainSkeletonDirectory = \OC::$server->getConfig()->getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton');
|
||||
$userLang = \OC::$server->getL10NFactory()->findLanguage();
|
||||
$skeletonDirectory = str_replace('{lang}', $userLang, $plainSkeletonDirectory);
|
||||
|
@ -439,14 +444,14 @@ class OC_Util {
|
|||
}
|
||||
|
||||
if (!empty($skeletonDirectory)) {
|
||||
\OCP\Util::writeLog(
|
||||
'files_skeleton',
|
||||
'copying skeleton for '.$userId.' from '.$skeletonDirectory.' to '.$userDirectory->getFullPath('/'),
|
||||
ILogger::DEBUG
|
||||
);
|
||||
$logger->debug('copying skeleton for '.$userId.' from '.$skeletonDirectory.' to '.$userDirectory->getFullPath('/'), ['app' => 'files_skeleton']);
|
||||
self::copyr($skeletonDirectory, $userDirectory);
|
||||
// update the file cache
|
||||
$userDirectory->getStorage()->getScanner()->scan('', \OC\Files\Cache\Scanner::SCAN_RECURSIVE);
|
||||
|
||||
/** @var ITemplateManager $templateManager */
|
||||
$templateManager = \OC::$server->get(ITemplateManager::class);
|
||||
$templateManager->initializeTemplateDirectory(null, $userId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ namespace OCP\AppFramework\Bootstrap;
|
|||
|
||||
use OCP\AppFramework\IAppContainer;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\Template\ICustomTemplateProvider;
|
||||
use OCP\IContainer;
|
||||
|
||||
/**
|
||||
|
@ -196,4 +197,14 @@ interface IRegistrationContext {
|
|||
* @since 21.0.0
|
||||
*/
|
||||
public function registerWellKnownHandler(string $class): void;
|
||||
|
||||
/**
|
||||
* Register a custom template provider class that is able to inject custom templates
|
||||
* in addition to the user defined ones
|
||||
*
|
||||
* @param string $providerClass
|
||||
* @psalm-param class-string<ICustomTemplateProvider> $providerClass
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function registerTemplateProvider(string $providerClass): void;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
/*
|
||||
* @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace OCP\Files\Template;
|
||||
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\Files\File;
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
class FileCreatedFromTemplateEvent extends Event {
|
||||
private $template;
|
||||
private $target;
|
||||
|
||||
/**
|
||||
* @param File|null $template
|
||||
* @param File $target
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function __construct(?File $template, File $target) {
|
||||
$this->template = $template;
|
||||
$this->target = $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return File|null
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function getTemplate(): ?File {
|
||||
return $this->template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return File
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function getTarget(): File {
|
||||
return $this->target;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
/*
|
||||
* @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace OCP\Files\Template;
|
||||
|
||||
use OCP\Files\File;
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
interface ICustomTemplateProvider {
|
||||
/**
|
||||
* Return a list of additional templates that the template provider is offering
|
||||
*
|
||||
* @return File[]
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function getCustomTemplates(string $mimetype): array;
|
||||
|
||||
/**
|
||||
* Return the file for a given template id
|
||||
*
|
||||
* @param string $template identifier of the template
|
||||
* @return File
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function getCustomTemplate(string $template): File;
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace OCP\Files\Template;
|
||||
|
||||
use OCP\Files\GenericFileException;
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
interface ITemplateManager {
|
||||
|
||||
/**
|
||||
* Register a template type support
|
||||
*
|
||||
* @param callable(): TemplateFileCreator $callback A callback which returns the TemplateFileCreator instance to register
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function registerTemplateFileCreator(callable $callback): void;
|
||||
|
||||
/**
|
||||
* Get a list of available file creators
|
||||
*
|
||||
* @return array
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function listCreators(): array;
|
||||
|
||||
/**
|
||||
* Get a list of available file creators and their offered templates
|
||||
*
|
||||
* @return array
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function listTemplates(): array;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function hasTemplateDirectory(): bool;
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return void
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function setTemplatePath(string $path): void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function getTemplatePath(): string;
|
||||
|
||||
/**
|
||||
* @param string|null $path
|
||||
* @param string|null $userId
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function initializeTemplateDirectory(string $path = null, string $userId = null, $copyTemplates = true): string;
|
||||
|
||||
/**
|
||||
* @param string $filePath
|
||||
* @param string $templateId
|
||||
* @return array
|
||||
* @throws GenericFileException
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function createFromTemplate(string $filePath, string $templateId = '', string $templateType = 'user'): array;
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
/*
|
||||
* @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace OCP\Files\Template;
|
||||
|
||||
use OCP\Files\File;
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
final class Template implements \JsonSerializable {
|
||||
|
||||
/** @var string */
|
||||
private $templateType;
|
||||
/** @var string */
|
||||
private $templateId;
|
||||
/** @var File */
|
||||
private $file;
|
||||
/** @var bool */
|
||||
private $hasPreview = false;
|
||||
/** @var string|null */
|
||||
private $previewUrl = null;
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function __construct(string $templateType, string $templateId, File $file) {
|
||||
$this->templateType = $templateType;
|
||||
$this->templateId = $templateId;
|
||||
$this->file = $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function setCustomPreviewUrl(string $previewUrl): void {
|
||||
$this->previewUrl = $previewUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function setHasPreview(bool $hasPreview): void {
|
||||
$this->hasPreview = $hasPreview;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function jsonSerialize() {
|
||||
return [
|
||||
'templateType' => $this->templateType,
|
||||
'templateId' => $this->templateId,
|
||||
'basename' => $this->file->getName(),
|
||||
'etag' => $this->file->getEtag(),
|
||||
'fileid' => $this->file->getId(),
|
||||
'filename' => $this->templateId,
|
||||
'lastmod' => $this->file->getMTime(),
|
||||
'mime' => $this->file->getMimetype(),
|
||||
'size' => $this->file->getSize(),
|
||||
'type' => $this->file->getType(),
|
||||
'hasPreview' => $this->hasPreview,
|
||||
'previewUrl' => $this->previewUrl
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
/*
|
||||
* @copyright Copyright (c) 2021 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace OCP\Files\Template;
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
final class TemplateFileCreator implements \JsonSerializable {
|
||||
protected $appId;
|
||||
protected $mimetypes = [];
|
||||
protected $actionName;
|
||||
protected $fileExtension;
|
||||
protected $iconClass;
|
||||
protected $ratio = null;
|
||||
protected $order = 100;
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
string $appId, string $actionName, string $fileExtension
|
||||
) {
|
||||
$this->appId = $appId;
|
||||
$this->actionName = $actionName;
|
||||
$this->fileExtension = $fileExtension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function getAppId(): string {
|
||||
return $this->appId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function setIconClass(string $iconClass): TemplateFileCreator {
|
||||
$this->iconClass = $iconClass;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function addMimetype(string $mimetype): TemplateFileCreator {
|
||||
$this->mimetypes[] = $mimetype;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
public function getMimetypes(): array {
|
||||
return $this->mimetypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 21.0.0
|
||||
*/
|
||||
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() {
|
||||
return [
|
||||
'app' => $this->appId,
|
||||
'label' => $this->actionName,
|
||||
'extension' => $this->fileExtension,
|
||||
'iconClass' => $this->iconClass,
|
||||
'mimetypes' => $this->mimetypes,
|
||||
'ratio' => $this->ratio
|
||||
];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue