Use FileCreatedFromTemplateEvent to inject the already existing empty template files for Collabora

Signed-off-by: Julius Härtl <jus@bitgrid.net>

Cleanup template loading

Signed-off-by: Julius Härtl <jus@bitgrid.net>

Fix template handling

Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
Julius Härtl 2021-02-24 16:53:03 +01:00
parent 03d939e0c4
commit d491bfa9ff
No known key found for this signature in database
GPG key ID: 4C614C6ED2CDE6DF
13 changed files with 299 additions and 44 deletions

View file

@ -6,7 +6,7 @@
<description><![CDATA[This application can connect to a Collabora Online (or other) server (WOPI-like Client). Nextcloud is the WOPI Host. Please read the documentation to learn more about that.
You can also edit your documents off-line with the Collabora Office app from the **[Android](https://play.google.com/store/apps/details?id=com.collabora.libreoffice)** and **[iOS](https://apps.apple.com/us/app/collabora-office/id1440482071)** store.]]></description>
<version>5.0.0-beta1</version>
<version>6.0.0-dev.1</version>
<licence>agpl</licence>
<author>Collabora Productivity based on work of Frank Karlitschek, Victor Dubiniuk</author>
<types>
@ -31,6 +31,7 @@ You can also edit your documents off-line with the Collabora Office app from the
</dependencies>
<background-jobs>
<job>OCA\Richdocuments\Backgroundjobs\ObtainCapabilities</job>
<job>OCA\Richdocuments\Backgroundjobs\Cleanup</job>
</background-jobs>
<commands>
<command>OCA\Richdocuments\Command\ActivateConfig</command>

Binary file not shown.

View file

@ -31,6 +31,7 @@ use OCA\Files_Sharing\Listener\LoadAdditionalListener;
use OCA\Richdocuments\AppConfig;
use OCA\Richdocuments\Capabilities;
use OCA\Richdocuments\Middleware\WOPIMiddleware;
use OCA\Richdocuments\Listener\FileCreatedFromTemplateListener;
use OCA\Richdocuments\PermissionManager;
use OCA\Richdocuments\Preview\MSExcel;
use OCA\Richdocuments\Preview\MSWord;
@ -48,6 +49,7 @@ use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Template\FileCreatedFromTemplateEvent;
use OCP\Files\Template\ITemplateManager;
use OCP\Files\Template\TemplateFileCreator;
use OCP\IConfig;
@ -68,6 +70,7 @@ class Application extends App implements IBootstrap {
$context->registerTemplateProvider(CollaboraTemplateProvider::class);
$context->registerCapability(Capabilities::class);
$context->registerMiddleWare(WOPIMiddleware::class);
$context->registerEventListener(FileCreatedFromTemplateEvent::class, FileCreatedFromTemplateListener::class);
}
public function boot(IBootContext $context): void {

View file

@ -0,0 +1,49 @@
<?php
/**
* @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
*
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @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/>.
*
*/
namespace OCA\Richdocuments\Backgroundjobs;
use OC\BackgroundJob\TimedJob;
use OCA\Richdocuments\Service\CapabilitiesService;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
class Cleanup extends TimedJob {
/** @var IDBConnection */
private $db;
public function __construct(IDBConnection $db) {
$this->db = $db;
$this->setInterval(60*60);
}
protected function run($argument) {
// Expire template mappings for file creation
$query = $this->db->getQueryBuilder();
$query->delete('richdocuments_template')
->where($query->expr()->lte('timestamp', $query->createNamedParameter(time() - 60, IQueryBuilder::PARAM_INT)));
$query->executeStatement();
}
}

View file

@ -11,6 +11,7 @@
namespace OCA\Richdocuments\Controller;
use OCA\Richdocuments\AppInfo\Application;
use OCA\Richdocuments\Events\BeforeFederationRedirectEvent;
use OCA\Richdocuments\Service\FederationService;
use OCA\Richdocuments\Service\InitialStateService;
@ -210,7 +211,14 @@ class DocumentController extends Controller {
return $response;
}
list($urlSrc, $token, $wopi) = $this->tokenManager->getToken($item->getId());
$templateFile = $this->templateManager->getTemplateSource($item->getId());
if ($templateFile) {
list($urlSrc, $wopi) = $this->tokenManager->getTokenForTemplate($templateFile, $this->uid, $item->getId());
$token = $wopi->getToken();
} else {
list($urlSrc, $token, $wopi) = $this->tokenManager->getToken($item->getId());
}
$params = [
'permissions' => $item->getPermissions(),
'title' => $item->getName(),
@ -575,6 +583,7 @@ class DocumentController extends Controller {
}
if (!$content){
// FIXME: see if this is used,
$content = file_get_contents(dirname(dirname(__DIR__)) . self::ODT_TEMPLATE_PATH);
}

View file

@ -0,0 +1,71 @@
<?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\Richdocuments\Listener;
use OCA\Richdocuments\AppInfo\Application;
use OCA\Richdocuments\TemplateManager;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Template\FileCreatedFromTemplateEvent;
use OCP\IConfig;
class FileCreatedFromTemplateListener implements IEventListener {
/** @var TemplateManager */
private $templateManager;
public function __construct(
TemplateManager $templateManager
) {
$this->templateManager = $templateManager;
}
public function handle(Event $event): void {
if (!($event instanceof FileCreatedFromTemplateEvent)) {
return;
}
$templateFile = $event->getTemplate();
// Empty template
if ($templateFile === null) {
$event->getTarget()->putContent($this->templateManager->getEmptyFileContent($event->getTarget()->getExtension()));
return;
}
if ($this->templateManager->isSupportedTemplateSource($templateFile->getExtension())) {
// Only use TemplateSource if supported filetype
$this->templateManager->setTemplateSource($event->getTarget()->getId(), $templateFile->getId());
}
// Avoid having the mimetype of the source file set
$event->getTarget()->getStorage()->getCache()->update($event->getTarget()->getId(), [
'mimetype' => $event->getTarget()->getMimeType()
]);
}
}

View file

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace OCA\Richdocuments\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
/**
* Auto-generated migration step: Please modify to your needs!
*/
class Version50200Date20211220212457 extends SimpleMigrationStep {
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if (!$schema->hasTable('richdocuments_template')) {
$table = $schema->createTable('richdocuments_template');
$table->addColumn('id', 'bigint', [
'autoincrement' => true,
'notnull' => true,
'length' => 20,
'unsigned' => true,
]);
$table->addColumn('userid', 'string', [
'notnull' => false,
'length' => 64,
]);
$table->addColumn('fileid', 'bigint', [
'notnull' => true,
'length' => 20,
]);
$table->addColumn('templateid', 'bigint', [
'notnull' => true,
'length' => 20,
]);
$table->addColumn('timestamp', 'bigint', [
'notnull' => true,
'length' => 20,
'unsigned' => true,
]);
$table->setPrimaryKey(['id']);
$table->addUniqueIndex(['userid', 'fileid'], 'rd_t_user_file');
}
return $schema;
}
}

View file

@ -91,8 +91,4 @@ class CollaboraTemplateProvider implements ICustomTemplateProvider {
public function getCustomTemplate(string $template): File {
return $this->templateManager->get((int)$template);
}
public function createFromTemplate(File $template, File $target): void {
// TODO: Implement createFromTemplate() method.
}
}

View file

@ -24,6 +24,8 @@ declare (strict_types = 1);
namespace OCA\Richdocuments;
use OCA\Richdocuments\AppInfo\Application;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\File;
use OCP\Files\Folder;
use OCP\Files\IAppData;
@ -31,16 +33,14 @@ use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IL10N;
use OCP\IPreview;
use OCP\IURLGenerator;
use OC\Files\AppData\Factory;
use Psr\Log\LoggerInterface;
use Throwable;
class TemplateManager {
/** @var string */
protected $appName;
/** @var string */
protected $userId;
@ -56,6 +56,15 @@ class TemplateManager {
/** @var IL10N */
private $l;
/** @var IDBConnection */
private $db;
/** @var IAppData */
private $appData;
/** @var LoggerInterface */
private $logger;
/** Accepted templates mime types */
const MIMES_DOCUMENTS = [
'application/vnd.oasis.opendocument.text-template',
@ -97,39 +106,27 @@ class TemplateManager {
'presentation' => 'pptx',
];
/**
* Template manager
*
* @param string $appName
* @param string $userId
* @param IConfig $config
* @param Factory $appDataFactory
* @param IURLGenerator $urlGenerator
* @param IRootFolder $rootFolder
* @param IL10N $l
* @throws \OCP\Files\NotPermittedException
*/
public function __construct($appName,
$userId,
IConfig $config,
IAppData $appData,
IURLGenerator $urlGenerator,
IRootFolder $rootFolder,
IL10N $l) {
$this->appName = $appName;
$this->userId = $userId;
$this->config = $config;
$this->rootFolder = $rootFolder;
$this->urlGenerator = $urlGenerator;
public function __construct(
$userId,
IConfig $config,
IAppData $appData,
IURLGenerator $urlGenerator,
IRootFolder $rootFolder,
IL10N $l,
IDBConnection $connection,
LoggerInterface $logger
) {
$this->userId = $userId;
$this->config = $config;
$this->rootFolder = $rootFolder;
$this->urlGenerator = $urlGenerator;
$this->db = $connection;
$this->logger = $logger;
$this->appData = $appData;
$this->createAppDataFolders();
$this->l = $l;
}
private function createAppDataFolders() {
private function ensureAppDataFolders() {
/*
* Init the appdata folder
* We need an actual folder for the fileid and previews.
@ -200,7 +197,7 @@ class TemplateManager {
});
}
private function getEmpty($type = null) {
public function getEmpty($type = null) {
$folder = $this->getEmptyTemplateDir();
$templateFiles = $folder->getDirectoryListing();
@ -228,6 +225,7 @@ class TemplateManager {
* Remove empty_templates in appdata and recreate it from the apps templates
*/
public function updateEmptyTemplates() {
$this->ensureAppDataFolders();
try {
$folder = $this->getEmptyTemplateDir();
$folder->delete();
@ -393,7 +391,7 @@ class TemplateManager {
}
// has the user manually set a directory as the default template dir ?
$templateDirPath = $this->config->getUserValue($this->userId, $this->appName, 'templateFolder', false);
$templateDirPath = $this->config->getUserValue($this->userId, Application::APPNAME, 'templateFolder', false);
$userFolder = $this->rootFolder->getUserFolder($this->userId);
if ($templateDirPath !== false) {
@ -418,6 +416,7 @@ class TemplateManager {
* @return Folder
*/
private function getSystemTemplateDir() {
$this->ensureAppDataFolders();
$path = 'appdata_' . $this->config->getSystemValue('instanceid', null) . '/richdocuments/templates';
return $this->rootFolder->get($path);
}
@ -426,6 +425,7 @@ class TemplateManager {
* @return Folder
*/
private function getEmptyTemplateDir() {
$this->ensureAppDataFolders();
$path = 'appdata_' . $this->config->getSystemValue('instanceid', null) . '/richdocuments/empty_templates';
return $this->rootFolder->get($path);
}
@ -437,7 +437,7 @@ class TemplateManager {
* @return array
*/
public function formatNodeReturn(File $template) {
$ooxml = $this->config->getAppValue($this->appName, 'doc_format', '') === 'ooxml';
$ooxml = $this->config->getAppValue(Application::APPNAME, 'doc_format', '') === 'ooxml';
$documentType = $this->flipTypes()[$template->getMimeType()];
return [
'id' => $template->getId(),
@ -466,7 +466,7 @@ class TemplateManager {
}
public function formatEmpty(File $template) {
$ooxml = $this->config->getAppValue($this->appName, 'doc_format', '') === 'ooxml';
$ooxml = $this->config->getAppValue(Application::APPNAME, 'doc_format', '') === 'ooxml';
$documentType = $this->flipTypes()[$template->getMimeType()];
return [
'id' => $template->getId(),
@ -490,4 +490,79 @@ class TemplateManager {
return true;
}
/**
* Return default content for empty files of a given filename by file extension
*/
public function getEmptyFileContent(string $extension): string {
$supportedExtensions = ['odt', 'ods', 'odp', 'odg', 'docx', 'xlsx', 'pptx'];
$emptyPath = __DIR__ . '/../emptyTemplates/template.' . $extension;
if (in_array($extension, $supportedExtensions, true) && file_exists($emptyPath)) {
return file_get_contents($emptyPath);
}
return '';
}
public function isSupportedTemplateSource(string $extension): bool {
$supportedExtensions = ['ott', 'otg', 'otp', 'ots'];
return in_array($extension, $supportedExtensions, true);
}
public function setTemplateSource(int $fileId, int $templateId): void {
try {
$query = $this->db->getQueryBuilder();
$query->insert('richdocuments_template')
->values([
'userid' => $query->createNamedParameter($this->userId),
'fileid' => $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT),
'templateid' => $query->createNamedParameter($templateId, IQueryBuilder::PARAM_INT),
'timestamp' => $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT)
]);
$query->executeStatement();
} catch (Throwable $e) {
$this->logger->warning('Could not store template source', ['exception' => $e]);
// Ignore failure and proceed with empty template
}
}
public function getTemplateSource(int $fileId): ?File {
$templateId = 0;
try {
$query = $this->db->getQueryBuilder();
$query->select('templateid')
->from('richdocuments_template')
->where($query->expr()->eq('userid', $query->createNamedParameter($this->userId)))
->andWhere($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
$result = $query->executeQuery();
$templateId = (int)$result->fetchOne();
$query->delete('richdocuments_template')
->where($query->expr()->eq('userid', $query->createNamedParameter($this->userId)))
->andWhere($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
$query->executeStatement();
} catch (Throwable $e) {
// Ignore failure and proceed with empty template
$this->logger->warning('Could not retrieve template source', ['exception' => $e]);
return null;
}
if ($templateId !== 0) {
try {
$template = $this->get($templateId);
} catch (NotFoundException $e) {
$userFolder = $this->rootFolder->getUserFolder($this->userId);
try {
$template = $userFolder->getById($templateId);
} catch (NotFoundException $e) {
$this->logger->warning('Could not retrieve template source file', ['exception' => $e]);
return null;
}
}
return $template;
}
return null;
}
}