From 0948ed5520dd13a22256c3fdb66b66f93d06960d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Wed, 24 Feb 2021 16:53:03 +0100 Subject: Use FileCreatedFromTemplateEvent to inject the already existing empty template files for Collabora MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl Cleanup template loading Signed-off-by: Julius Härtl Fix template handling Signed-off-by: Julius Härtl --- appinfo/info.xml | 3 +- emptyTemplates/docxtemplate.docx | Bin 790 -> 0 bytes emptyTemplates/odgtemplate.otg | Bin 8158 -> 0 bytes emptyTemplates/odttemplate.odt | Bin 642 -> 0 bytes emptyTemplates/pptxtemplate.pptx | Bin 836 -> 0 bytes emptyTemplates/template.docx | Bin 0 -> 790 bytes emptyTemplates/template.odg | Bin 0 -> 8158 bytes emptyTemplates/template.pptx | Bin 0 -> 836 bytes emptyTemplates/template.xlsx | Bin 0 -> 1409 bytes emptyTemplates/xlsxtemplate.xlsx | Bin 1409 -> 0 bytes lib/AppInfo/Application.php | 3 + lib/Backgroundjobs/Cleanup.php | 49 ++++++++ lib/Controller/DocumentController.php | 11 +- lib/Listener/FileCreatedFromTemplateListener.php | 71 +++++++++++ lib/Migration/Version50200Date20211220212457.php | 51 ++++++++ lib/Template/CollaboraTemplateProvider.php | 4 - lib/TemplateManager.php | 151 +++++++++++++++++------ 17 files changed, 299 insertions(+), 44 deletions(-) delete mode 100644 emptyTemplates/docxtemplate.docx delete mode 100644 emptyTemplates/odgtemplate.otg delete mode 100644 emptyTemplates/odttemplate.odt delete mode 100644 emptyTemplates/pptxtemplate.pptx create mode 100644 emptyTemplates/template.docx create mode 100644 emptyTemplates/template.odg create mode 100644 emptyTemplates/template.pptx create mode 100644 emptyTemplates/template.xlsx delete mode 100644 emptyTemplates/xlsxtemplate.xlsx create mode 100644 lib/Backgroundjobs/Cleanup.php create mode 100644 lib/Listener/FileCreatedFromTemplateListener.php create mode 100644 lib/Migration/Version50200Date20211220212457.php diff --git a/appinfo/info.xml b/appinfo/info.xml index 465480a7..23bfd2b0 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -6,7 +6,7 @@ - 5.0.1 + 5.0.2-dev.1 agpl Collabora Productivity based on work of Frank Karlitschek, Victor Dubiniuk @@ -31,6 +31,7 @@ You can also edit your documents off-line with the Collabora Office app from the OCA\Richdocuments\Backgroundjobs\ObtainCapabilities + OCA\Richdocuments\Backgroundjobs\Cleanup OCA\Richdocuments\Command\ActivateConfig diff --git a/emptyTemplates/docxtemplate.docx b/emptyTemplates/docxtemplate.docx deleted file mode 100644 index 1b8fdf5a..00000000 Binary files a/emptyTemplates/docxtemplate.docx and /dev/null differ diff --git a/emptyTemplates/odgtemplate.otg b/emptyTemplates/odgtemplate.otg deleted file mode 100644 index f29cd812..00000000 Binary files a/emptyTemplates/odgtemplate.otg and /dev/null differ diff --git a/emptyTemplates/odttemplate.odt b/emptyTemplates/odttemplate.odt deleted file mode 100644 index d2a8dc27..00000000 Binary files a/emptyTemplates/odttemplate.odt and /dev/null differ diff --git a/emptyTemplates/pptxtemplate.pptx b/emptyTemplates/pptxtemplate.pptx deleted file mode 100644 index e93a532d..00000000 Binary files a/emptyTemplates/pptxtemplate.pptx and /dev/null differ diff --git a/emptyTemplates/template.docx b/emptyTemplates/template.docx new file mode 100644 index 00000000..1b8fdf5a Binary files /dev/null and b/emptyTemplates/template.docx differ diff --git a/emptyTemplates/template.odg b/emptyTemplates/template.odg new file mode 100644 index 00000000..f29cd812 Binary files /dev/null and b/emptyTemplates/template.odg differ diff --git a/emptyTemplates/template.pptx b/emptyTemplates/template.pptx new file mode 100644 index 00000000..e93a532d Binary files /dev/null and b/emptyTemplates/template.pptx differ diff --git a/emptyTemplates/template.xlsx b/emptyTemplates/template.xlsx new file mode 100644 index 00000000..42e73eca Binary files /dev/null and b/emptyTemplates/template.xlsx differ diff --git a/emptyTemplates/xlsxtemplate.xlsx b/emptyTemplates/xlsxtemplate.xlsx deleted file mode 100644 index 42e73eca..00000000 Binary files a/emptyTemplates/xlsxtemplate.xlsx and /dev/null differ diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 696fb4e6..a8af2239 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -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 { diff --git a/lib/Backgroundjobs/Cleanup.php b/lib/Backgroundjobs/Cleanup.php new file mode 100644 index 00000000..e5adb55a --- /dev/null +++ b/lib/Backgroundjobs/Cleanup.php @@ -0,0 +1,49 @@ + + * + * @author Roeland Jago Douma + * + * @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 . + * + */ + +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(); + } +} diff --git a/lib/Controller/DocumentController.php b/lib/Controller/DocumentController.php index 9f1f7cec..8e1932a2 100644 --- a/lib/Controller/DocumentController.php +++ b/lib/Controller/DocumentController.php @@ -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); } diff --git a/lib/Listener/FileCreatedFromTemplateListener.php b/lib/Listener/FileCreatedFromTemplateListener.php new file mode 100644 index 00000000..6cc4d472 --- /dev/null +++ b/lib/Listener/FileCreatedFromTemplateListener.php @@ -0,0 +1,71 @@ + + * + * @author Julius Härtl + * + * @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 . + * + */ + +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() + ]); + } +} diff --git a/lib/Migration/Version50200Date20211220212457.php b/lib/Migration/Version50200Date20211220212457.php new file mode 100644 index 00000000..5eb408c1 --- /dev/null +++ b/lib/Migration/Version50200Date20211220212457.php @@ -0,0 +1,51 @@ +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; + } +} diff --git a/lib/Template/CollaboraTemplateProvider.php b/lib/Template/CollaboraTemplateProvider.php index 409ba70c..e7abad9d 100644 --- a/lib/Template/CollaboraTemplateProvider.php +++ b/lib/Template/CollaboraTemplateProvider.php @@ -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. - } } diff --git a/lib/TemplateManager.php b/lib/TemplateManager.php index 2974a9c6..1f4d1499 100644 --- a/lib/TemplateManager.php +++ b/lib/TemplateManager.php @@ -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; + } } -- cgit v1.2.3