Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: PDF Template Workflow #3798

Merged
merged 31 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ead5d1b
feat: Add Controller, Service files
elzody Jun 21, 2024
ea936f9
refactor: Rename controller
elzody Jun 21, 2024
e8d18af
testing
elzody Jun 24, 2024
23981a3
fix: Add `fileId` param to route path
elzody Jun 24, 2024
919e316
Basic controller and service
elzody Jun 24, 2024
0920c41
fix: Add attributes
elzody Jun 24, 2024
c8d564f
fix: Use required attributes/classes
elzody Jun 24, 2024
8d82a68
fix: Composer dump
elzody Jun 24, 2024
c8b4549
fix: Add SPDX headers
elzody Jun 24, 2024
6323ae5
fix: Hit Collabora endpoint from service
elzody Jun 24, 2024
1974607
fix: `fillFields` method
elzody Jun 25, 2024
bc59697
fix: Remove use of type Node
elzody Jun 26, 2024
4fb597e
fix: Add field classes to template field service
elzody Jun 28, 2024
965c665
fix: Inject template fields via new `BeforeGetTemplates` events
elzody Jul 5, 2024
b5cfc69
fix: SPDX header for `BeforeGetTemplatesListener`
elzody Jul 5, 2024
f425774
fix: Template field classes
elzody Jul 5, 2024
1014bdb
fix: `TemplateFieldService` can use either Node or file ID
elzody Jul 9, 2024
924c25f
fix: Add index and content to Field type
elzody Jul 10, 2024
a30356c
fix: move Field and FieldType back to server API
elzody Jul 10, 2024
3e217c7
fix: Extract fields from Collabora response
elzody Jul 12, 2024
16a6605
fix: Hit Collabora endpoint to extract fields
elzody Jul 16, 2024
68f70c8
fix: Get Node if given an ID
elzody Jul 16, 2024
6c39bd5
fix: Add alias to field
elzody Jul 16, 2024
fda372f
feat: Add API for filling templates
elzody Jul 24, 2024
5a4ec78
fix: Map field type to enum
juliushaertl Jul 25, 2024
9e881ad
fix: Update nextcloud/ocp to fix psalm errors
elzody Jul 25, 2024
8f112c3
fix: Use constructor promotion to reduce boilerplate
elzody Jul 25, 2024
3fd504c
fix: Remove unneeded class from phpstub
elzody Jul 25, 2024
f63d502
fix: Use default options in requests
elzody Jul 25, 2024
6f85ff4
fix: Log and handle errors
elzody Jul 25, 2024
940feb4
fix: Send back more generic error message instead of exception
elzody Jul 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,8 @@

['name' => 'Target#getTargets', 'url' => '/api/v1/targets', 'verb' => 'GET'],
['name' => 'Target#getPreview', 'url' => '/api/v1/targets/preview', 'verb' => 'GET'],

['name' => 'TemplateField#extractFields', 'url' => '/api/v1/template/fields/extract/{fileId}', 'verb' => 'GET'],
['name' => 'TemplateField#fillFields', 'url' => '/api/v1/template/fields/fill', 'verb' => 'POST'],
],
];
8 changes: 4 additions & 4 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
'OCA\\Richdocuments\\Controller\\OCSController' => $baseDir . '/../lib/Controller/OCSController.php',
'OCA\\Richdocuments\\Controller\\SettingsController' => $baseDir . '/../lib/Controller/SettingsController.php',
'OCA\\Richdocuments\\Controller\\TargetController' => $baseDir . '/../lib/Controller/TargetController.php',
'OCA\\Richdocuments\\Controller\\TemplateFieldController' => $baseDir . '/../lib/Controller/TemplateFieldController.php',
'OCA\\Richdocuments\\Controller\\TemplatesController' => $baseDir . '/../lib/Controller/TemplatesController.php',
'OCA\\Richdocuments\\Controller\\WopiController' => $baseDir . '/../lib/Controller/WopiController.php',
'OCA\\Richdocuments\\Db\\Asset' => $baseDir . '/../lib/Db/Asset.php',
Expand All @@ -42,6 +43,7 @@
'OCA\\Richdocuments\\Listener\\AddContentSecurityPolicyListener' => $baseDir . '/../lib/Listener/AddContentSecurityPolicyListener.php',
'OCA\\Richdocuments\\Listener\\AddFeaturePolicyListener' => $baseDir . '/../lib/Listener/AddFeaturePolicyListener.php',
'OCA\\Richdocuments\\Listener\\BeforeFetchPreviewListener' => $baseDir . '/../lib/Listener/BeforeFetchPreviewListener.php',
'OCA\\Richdocuments\\Listener\\BeforeGetTemplatesListener' => $baseDir . '/../lib/Listener/BeforeGetTemplatesListener.php',
'OCA\\Richdocuments\\Listener\\BeforeTemplateRenderedListener' => $baseDir . '/../lib/Listener/BeforeTemplateRenderedListener.php',
'OCA\\Richdocuments\\Listener\\FileCreatedFromTemplateListener' => $baseDir . '/../lib/Listener/FileCreatedFromTemplateListener.php',
'OCA\\Richdocuments\\Listener\\LoadAdditionalListener' => $baseDir . '/../lib/Listener/LoadAdditionalListener.php',
Expand Down Expand Up @@ -77,6 +79,7 @@
'OCA\\Richdocuments\\Service\\InitialStateService' => $baseDir . '/../lib/Service/InitialStateService.php',
'OCA\\Richdocuments\\Service\\RemoteOptionsService' => $baseDir . '/../lib/Service/RemoteOptionsService.php',
'OCA\\Richdocuments\\Service\\RemoteService' => $baseDir . '/../lib/Service/RemoteService.php',
'OCA\\Richdocuments\\Service\\TemplateFieldService' => $baseDir . '/../lib/Service/TemplateFieldService.php',
'OCA\\Richdocuments\\Service\\UserScopeService' => $baseDir . '/../lib/Service/UserScopeService.php',
'OCA\\Richdocuments\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php',
'OCA\\Richdocuments\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php',
Expand Down
3 changes: 3 additions & 0 deletions composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class ComposerStaticInitRichdocuments
'OCA\\Richdocuments\\Controller\\OCSController' => __DIR__ . '/..' . '/../lib/Controller/OCSController.php',
'OCA\\Richdocuments\\Controller\\SettingsController' => __DIR__ . '/..' . '/../lib/Controller/SettingsController.php',
'OCA\\Richdocuments\\Controller\\TargetController' => __DIR__ . '/..' . '/../lib/Controller/TargetController.php',
'OCA\\Richdocuments\\Controller\\TemplateFieldController' => __DIR__ . '/..' . '/../lib/Controller/TemplateFieldController.php',
'OCA\\Richdocuments\\Controller\\TemplatesController' => __DIR__ . '/..' . '/../lib/Controller/TemplatesController.php',
'OCA\\Richdocuments\\Controller\\WopiController' => __DIR__ . '/..' . '/../lib/Controller/WopiController.php',
'OCA\\Richdocuments\\Db\\Asset' => __DIR__ . '/..' . '/../lib/Db/Asset.php',
Expand All @@ -57,6 +58,7 @@ class ComposerStaticInitRichdocuments
'OCA\\Richdocuments\\Listener\\AddContentSecurityPolicyListener' => __DIR__ . '/..' . '/../lib/Listener/AddContentSecurityPolicyListener.php',
'OCA\\Richdocuments\\Listener\\AddFeaturePolicyListener' => __DIR__ . '/..' . '/../lib/Listener/AddFeaturePolicyListener.php',
'OCA\\Richdocuments\\Listener\\BeforeFetchPreviewListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeFetchPreviewListener.php',
'OCA\\Richdocuments\\Listener\\BeforeGetTemplatesListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeGetTemplatesListener.php',
'OCA\\Richdocuments\\Listener\\BeforeTemplateRenderedListener' => __DIR__ . '/..' . '/../lib/Listener/BeforeTemplateRenderedListener.php',
'OCA\\Richdocuments\\Listener\\FileCreatedFromTemplateListener' => __DIR__ . '/..' . '/../lib/Listener/FileCreatedFromTemplateListener.php',
'OCA\\Richdocuments\\Listener\\LoadAdditionalListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalListener.php',
Expand Down Expand Up @@ -92,6 +94,7 @@ class ComposerStaticInitRichdocuments
'OCA\\Richdocuments\\Service\\InitialStateService' => __DIR__ . '/..' . '/../lib/Service/InitialStateService.php',
'OCA\\Richdocuments\\Service\\RemoteOptionsService' => __DIR__ . '/..' . '/../lib/Service/RemoteOptionsService.php',
'OCA\\Richdocuments\\Service\\RemoteService' => __DIR__ . '/..' . '/../lib/Service/RemoteService.php',
'OCA\\Richdocuments\\Service\\TemplateFieldService' => __DIR__ . '/..' . '/../lib/Service/TemplateFieldService.php',
'OCA\\Richdocuments\\Service\\UserScopeService' => __DIR__ . '/..' . '/../lib/Service/UserScopeService.php',
'OCA\\Richdocuments\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php',
'OCA\\Richdocuments\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php',
Expand Down
3 changes: 3 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use OCA\Richdocuments\Listener\AddContentSecurityPolicyListener;
use OCA\Richdocuments\Listener\AddFeaturePolicyListener;
use OCA\Richdocuments\Listener\BeforeFetchPreviewListener;
use OCA\Richdocuments\Listener\BeforeGetTemplatesListener;
use OCA\Richdocuments\Listener\BeforeTemplateRenderedListener;
use OCA\Richdocuments\Listener\FileCreatedFromTemplateListener;
use OCA\Richdocuments\Listener\LoadAdditionalListener;
Expand All @@ -38,6 +39,7 @@
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\Collaboration\Reference\RenderReferenceEvent;
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent;
use OCP\Files\Template\BeforeGetTemplatesEvent;
use OCP\Files\Template\FileCreatedFromTemplateEvent;
use OCP\Files\Template\RegisterTemplateCreatorEvent;
use OCP\Preview\BeforePreviewFetchedEvent;
Expand Down Expand Up @@ -65,6 +67,7 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(BeforePreviewFetchedEvent::class, BeforeFetchPreviewListener::class);
$context->registerEventListener(RenderReferenceEvent::class, ReferenceListener::class);
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
$context->registerEventListener(BeforeGetTemplatesEvent::class, BeforeGetTemplatesListener::class);
$context->registerReferenceProvider(OfficeTargetReferenceProvider::class);
$context->registerSensitiveMethods(WopiMapper::class, [
'getPathForToken',
Expand Down
72 changes: 72 additions & 0 deletions lib/Controller/TemplateFieldController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Richdocuments\Controller;

use OCA\Richdocuments\Service\TemplateFieldService;
use OCA\Richdocuments\TemplateManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\IRequest;

class TemplateFieldController extends OCSController {
private TemplateFieldService $templateFieldService;
private TemplateManager $templateManager;

/**
* Template fields controller
*
* @param string $appName,
* @param IRequest $request,
* @param TemplateFieldService $templateFieldService
* @param TemplateManager $templateManager
*/
public function __construct(
string $appName,
IRequest $request,
TemplateFieldService $templateFieldService,
TemplateManager $templateManager
) {
parent::__construct($appName, $request);

$this->templateFieldService = $templateFieldService;
$this->templateManager = $templateManager;
}

/**
* @param int $fileId
* @return DataResponse
*/
#[NoAdminRequired]
public function extractFields(int $fileId): DataResponse {
try {
$fields = $this->templateFieldService->extractFields($fileId);

return new DataResponse($fields, Http::STATUS_OK);
} catch (\Exception $e) {
return new DataResponse(["Unable to extract fields from given file"], Http::STATUS_INTERNAL_SERVER_ERROR);
}
}

/**
* @param int $fileId
* @param array $fields
* @return DataResponse
*/
#[NoAdminRequired]
public function fillFields(int $fileId, array $fields): DataResponse {
try {
$this->templateFieldService->fillFields($fileId, $fields);

return new DataResponse([], Http::STATUS_OK);
} catch (\Exception $e) {
return new DataResponse(["Unable to fill fields into the given file"], Http::STATUS_INTERNAL_SERVER_ERROR);
}
}
}
34 changes: 34 additions & 0 deletions lib/Listener/BeforeGetTemplatesListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Richdocuments\Listener;

use OCA\Richdocuments\Service\TemplateFieldService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Template\BeforeGetTemplatesEvent;

/** @template-implements IEventListener<BeforeGetTemplatesEvent|Event> */
class BeforeGetTemplatesListener implements IEventListener {
public function __construct(
private TemplateFieldService $templateFieldService
) {
}

public function handle(Event $event): void {
if (!$event instanceof BeforeGetTemplatesEvent) {
return;
}

foreach($event->getTemplates() as $template) {
$templateFileId = $template->jsonSerialize()["fileid"];
$fields = $this->templateFieldService->extractFields($templateFileId);

$template->setFields($fields);
}
}
}
10 changes: 9 additions & 1 deletion lib/Listener/FileCreatedFromTemplateListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace OCA\Richdocuments\Listener;

use OCA\Richdocuments\Service\TemplateFieldService;
use OCA\Richdocuments\TemplateManager;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
Expand All @@ -18,11 +19,15 @@
class FileCreatedFromTemplateListener implements IEventListener {
/** @var TemplateManager */
private $templateManager;
/** @var TemplateFieldService */
private $templateFieldService;

public function __construct(
TemplateManager $templateManager
TemplateManager $templateManager,
TemplateFieldService $templateFieldService
) {
$this->templateManager = $templateManager;
$this->templateFieldService = $templateFieldService;
}

public function handle(Event $event): void {
Expand Down Expand Up @@ -50,6 +55,9 @@ public function handle(Event $event): void {
$this->templateManager->setTemplateSource($event->getTarget()->getId(), $templateFile->getId());
}

$filledTemplate = $this->templateFieldService->fillFields($templateFile, $event->getTemplateFields());
$event->getTarget()->putContent($filledTemplate);

// Avoid having the mimetype of the source file set
$event->getTarget()->getStorage()->getCache()->update($event->getTarget()->getId(), [
'mimetype' => $event->getTarget()->getMimeType()
Expand Down
125 changes: 125 additions & 0 deletions lib/Service/TemplateFieldService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Richdocuments\Service;

use OCA\Richdocuments\AppConfig;
use OCP\Files\IRootFolder;
use OCP\Files\Node;
use OCP\Files\Template\Field;
use OCP\Files\Template\FieldType;
use OCP\Http\Client\IClientService;
use Psr\Log\LoggerInterface;

class TemplateFieldService {
public function __construct(
private IClientService $clientService,
private AppConfig $appConfig,
private IRootFolder $rootFolder,
private LoggerInterface $logger
) {
}

/**
* @param Node|int $file
* @return array|string
*/
public function extractFields(Node|int $file) {
if (is_int($file)) {
$file = $this->rootFolder->getFirstNodeById($file);
}

$collaboraUrl = $this->appConfig->getCollaboraUrlInternal();
$httpClient = $this->clientService->newClient();

$form = RemoteOptionsService::getDefaultOptions();
$form['query'] = ['limit' => 'content-control'];
$form['multipart'] = [[
'name' => 'data',
'contents' => $file->getStorage()->fopen($file->getInternalPath(), 'r'),
'headers' => ['Content-Type' => 'multipart/form-data'],
]];

try {
$response = $httpClient->post(
$collaboraUrl . "/cool/extract-document-structure",
$form
);

$documentStructure = json_decode($response->getBody(), true)['DocStructure'];
$fields = [];

foreach ($documentStructure as $index => $attr) {
$fieldType = FieldType::tryFrom($attr['type']) ?? null;
if ($fieldType === null) {
continue;
}

$fields[] = [
new Field(
$index,
$attr["content"],
$fieldType,
$attr["alias"],
$attr["id"],
$attr["tag"]
)
];
}

return array_merge([], ...$fields);
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
return [];
}
}

/**
* @param Node|int $file
* @param array $fields
* @return string|resource
*/
public function fillFields(Node|int $file, array $fields = []) {
if (is_int($file)) {
$file = $this->rootFolder->getFirstNodeById($file);
}

$collaboraUrl = $this->appConfig->getCollaboraUrlInternal();
$httpClient = $this->clientService->newClient();

$formData = [
'name' => 'data',
'contents' => $file->getStorage()->fopen($file->getInternalPath(), 'r'),
'headers' => ['Content-Type' => 'multipart/form-data'],
];

$formTransform = [
'name' => 'transform',
'contents' => '{"Transforms": ' . json_encode($fields) . '}',
];

$formFormat = [
'name' => 'format',
'contents' => $file->getExtension(),
];

$form = RemoteOptionsService::getDefaultOptions();
$form['multipart'] = [$formData, $formTransform, $formFormat];

try {
$response = $httpClient->post(
$collaboraUrl . '/cool/transform-document-structure',
$form
);

return $response->getBody();
} catch (\Exception $e) {
$this->logger->error($e->getMessage());
throw $e;
}
}
}
16 changes: 5 additions & 11 deletions lib/Template/CollaboraTemplateProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,11 @@
use OCP\IURLGenerator;

class CollaboraTemplateProvider implements ICustomTemplateProvider {
/** @var TemplateManager */
private $templateManager;
/** @var IURLGenerator */
private $urlGenerator;
/** @var ITemplateManager */
private $coreTemplateManager;

public function __construct(TemplateManager $templateManager, IURLGenerator $urlGenerator, ITemplateManager $coreTemplateManager) {
$this->templateManager = $templateManager;
$this->urlGenerator = $urlGenerator;
$this->coreTemplateManager = $coreTemplateManager;
public function __construct(
private TemplateManager $templateManager,
private IURLGenerator $urlGenerator,
private ITemplateManager $coreTemplateManager
) {
}

public function getTemplateType(): string {
Expand Down
Loading