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

[stable30] Pass data to field depending on field type #3954

Merged
merged 9 commits into from
Aug 29, 2024
308 changes: 168 additions & 140 deletions composer.lock

Large diffs are not rendered by default.

129 changes: 94 additions & 35 deletions cypress/e2e/templates.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* SPDX-FileCopyrightText: 2023 Julius Härtl <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

describe('Create new office files from templates', function() {

let randUser
Expand All @@ -16,28 +17,20 @@ describe('Create new office files from templates', function() {

it('Create a new file from a user template', function() {
cy.visit('/apps/files')
cy.get('.files-controls .button.new')
.should('be.visible')
.click()

cy.get('.newFileMenu', { timeout: 10000 })
.should('be.visible')
.contains('.menuitem', 'New presentation')
.as('menuitem')
cy.get('.files-list__header div[menu-title="New"] button')
.should('be.visible')
.click()
.as('newFileMenu')

cy.get('@menuitem').find('.filenameform input[type=text]').type('FileFromTemplate')
cy.get('@menuitem').find('.filenameform .icon-confirm').click()
cy.get('@newFileMenu').click()
cy.get('button[role="menuitem"]').contains('New presentation').click()

cy.get('.templates-picker__form')
.as('form')
.should('be.visible')
.contains('.template-picker__label', 'presentation')
.should('be.visible')
.click()
cy.get('input[data-cy-files-new-node-dialog-input=""]').type('FileFromTemplate')
cy.get('button[data-cy-files-new-node-dialog-submit=""]').click()

cy.get('@form').find('.templates-picker__buttons input[type=submit]').click()
cy.get('form.templates-picker__form').as('templatePicker')
cy.get('@templatePicker').contains('presentation').click()
cy.get('@templatePicker').find('input[type="submit"]').click()

cy.waitForViewer()
cy.waitForCollabora()
Expand All @@ -47,34 +40,26 @@ describe('Create new office files from templates', function() {
cy.uploadSystemTemplate()
cy.login(randUser)
cy.visit('/apps/files')
cy.get('.files-controls .button.new')
.should('be.visible')
.click()

cy.get('.newFileMenu', { timeout: 10000 })
.should('be.visible')
.contains('.menuitem', 'New presentation')
.as('menuitem')
cy.get('.files-list__header div[menu-title="New"] button')
.should('be.visible')
.click()
.as('newFileMenu')

cy.get('@menuitem').find('.filenameform input[type=text]').type('FileFromTemplate')
cy.get('@menuitem').find('.filenameform .icon-confirm').click()
cy.get('@newFileMenu').click()
cy.get('button[role="menuitem"]').contains('New presentation').click()

cy.get('.templates-picker__form')
.as('form')
.should('be.visible')
.contains('.template-picker__label', 'systemtemplate')
.should('be.visible')
.click()
cy.get('input[data-cy-files-new-node-dialog-input=""]').type('FileFromSystemTemplate')
cy.get('button[data-cy-files-new-node-dialog-submit=""]').click()

cy.get('@form').find('.templates-picker__buttons input[type=submit]').click()
cy.get('form.templates-picker__form').as('templatePicker')
cy.get('@templatePicker').contains('systemtemplate').click()
cy.get('@templatePicker').find('input[type="submit"]').click()

cy.waitForViewer()
cy.waitForCollabora()
})

it.only('Create a file from a system template as guest', () => {
it('Create a file from a system template as guest', () => {
cy.uploadSystemTemplate()
cy.createFolder(randUser, '/my-share')

Expand Down Expand Up @@ -113,3 +98,77 @@ describe('Create new office files from templates', function() {
})
})
})

describe('Create templates with fields', () => {
let randUser

before(() => {
cy.createRandomUser().then(user => {
randUser = user

cy.login(randUser)
cy.visit('/apps/files')

// Create a templates folder
cy.get('.files-list__header div[menu-title="New"] button')
.should('be.visible')
.as('newFileMenu')

cy.get('@newFileMenu').click()
cy.get('button[role="menuitem"]').contains('Create templates folder').click()

cy.get('button[data-cy-files-new-node-dialog-submit=""]').click()

// Upload the fixtures into the templates folder
cy.uploadFile(randUser, 'templates/document_template_with_fields.odt', 'application/vnd.oasis.opendocument.text', '/Templates/document.odt')
})
})

it('Create a document from a template with fields', () => {
const fields = [
{ type: 'rich-text', alias: 'Name', content: 'Nextcloud' },
{ type: 'rich-text', alias: 'Favorite app', content: 'richdocuments' },
{ type: 'checkbox', alias: 'Uses Nextcloud at home', checked: true },
]

cy.visit('/apps/files')

// Create a new document
cy.get('.files-list__header div[menu-title="New"] button')
.should('be.visible')
.as('newFileMenu')

cy.get('@newFileMenu').click()
cy.get('button[role="menuitem"]').contains('New document').click()

cy.get('input[data-cy-files-new-node-dialog-input=""]').type('FileFromTemplateWithFields')
cy.get('button[data-cy-files-new-node-dialog-submit=""]').click()

// Choose the document template
cy.get('form.templates-picker__form').as('templatePicker')
cy.get('@templatePicker').contains('document').click()
cy.get('@templatePicker').find('input[type="submit"]').click()

// Intercept the POST request to verify the correct fields are submitted
cy.intercept('POST', '**/templates/create', (req) => {
const templateFields = Object.values(req.body.templateFields)

expect(templateFields[0].content).to.equal(fields[0].content)
expect(templateFields[1].content).to.equal(fields[1].content)

req.continue()
}).as('reqFillFields')

cy.submitTemplateFields(fields)

// Wait for the response and collect the file ID of the created file
cy.wait('@reqFillFields').then(({ response }) => {
cy.wrap(response.body.ocs.data.fileid).as('createdFileId')
})

// Test if the fields currently match the values we passed to the template
cy.get('@createdFileId').then(createdFileId => {
cy.verifyTemplateFields(fields, createdFileId)
})
})
})
Binary file not shown.
61 changes: 61 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,64 @@ Cypress.Commands.add('uploadSystemTemplate', () => {
}, { force: true })
cy.get('#richdocuments-templates li').contains('systemtemplate.otp')
})

Cypress.Commands.add('submitTemplateFields', (fields) => {
// Enter test values into the template filler
cy.get('.template-field-modal__content').as('templateFiller')
cy.get('.template-field-modal__buttons').as('templateFillerButtons')

for (const field of fields) {
switch (field.type) {
case 'rich-text':
cy.get('@templateFiller')
.find(`input[placeholder="${field.alias}"]`)
.type(field.content)
break
case 'checkbox':
cy.get('@templateFiller')
.find('span.checkbox-radio-switch__text').contains(field.alias)
.click()
break
default:
expect.fail('Using a field type not yet supported')
break
}
}

// Submit the template fields
cy.get('@templateFillerButtons').find('button[aria-label="Submit button"]').click()
})

Cypress.Commands.add('verifyTemplateFields', (fields, fileId) => {
const apiEndpoint = '/ocs/v2.php/apps/richdocuments/api/v1/template/fields/extract/'

cy.request('/csrftoken')
.then(({ body }) => body.token)
.as('requestToken')

cy.get('@requestToken').then(requesttoken => {
cy.request({
method: 'GET',
url: Cypress.env('baseUrl') + apiEndpoint + fileId + '?format=json',
headers: {
requesttoken,
},
}).then(({ body }) => {
for (const index in body.ocs.data) {
const field = body.ocs.data[index]

switch (field.type) {
case 'rich-text':
expect(field.content).to.equal(fields[index].content)
break
case 'checkbox':
expect(field.checked).to.equal(fields[index].checked)
break
default:
expect.fail('Using a field type not yet supported')
break
}
}
})
})
})
6 changes: 6 additions & 0 deletions 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\Capabilities;
use OCA\Richdocuments\Service\CapabilitiesService;
use OCA\Richdocuments\Service\TemplateFieldService;
use OCA\Richdocuments\TemplateManager;
Expand All @@ -31,6 +32,11 @@ public function handle(Event $event): void {
return;
}

$targetFile = $event->getTarget();
if (!in_array($targetFile->getMimetype(), Capabilities::MIMETYPES) && $targetFile->getMimeType() !== 'application/pdf') {
return;
}

$templateFile = $event->getTemplate();

// Empty template
Expand Down
10 changes: 6 additions & 4 deletions lib/Service/PdfService.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

use mikehaertl\pdftk\Pdf;
use OCP\Files\Node;
use OCP\Files\Template\Field;
use OCP\Files\Template\FieldFactory;
use OCP\Files\Template\FieldType;
use Psr\Log\LoggerInterface;

Expand All @@ -34,12 +34,14 @@
continue;
}

$templateFields[] = new Field(
$templateField = FieldFactory::createField(

Check failure on line 37 in lib/Service/PdfService.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

UndefinedClass

lib/Service/PdfService.php:37:22: UndefinedClass: Class, interface or enum named OCP\Files\Template\FieldFactory does not exist (see https://psalm.dev/019)
(string)$index,
$field['FieldValue'],
$fieldType,
alias: $field['FieldName'],
);
$templateField->setValue($field['FieldValue']);
$templateField->alias = $field['FieldName'];

$templateFields[] = $templateField;
$index++;
}
return $templateFields;
Expand Down
40 changes: 30 additions & 10 deletions lib/Service/TemplateFieldService.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
use OCP\Files\Node;
use OCP\Files\NotFoundException;
use OCP\Files\Template\Field;
use OCP\Files\Template\FieldFactory;
use OCP\Files\Template\FieldType;
use OCP\Files\Template\InvalidFieldTypeException;
use OCP\Http\Client\IClientService;
use OCP\ICacheFactory;
use Psr\Log\LoggerInterface;
Expand Down Expand Up @@ -94,16 +96,27 @@
continue;
}

$fields[] = [
new Field(
$index,
$attr["content"],
$fieldType,
$attr["alias"],
$attr["id"],
$attr["tag"]
)
];
try {
$field = FieldFactory::createField($index, $fieldType);

Check failure on line 100 in lib/Service/TemplateFieldService.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

UndefinedClass

lib/Service/TemplateFieldService.php:100:15: UndefinedClass: Class, interface or enum named OCP\Files\Template\FieldFactory does not exist (see https://psalm.dev/019)
} catch (InvalidFieldTypeException) {
continue;
}
$field->id = $attr['id'];
$field->tag = $attr['tag'];
$field->alias = $attr['alias'];

switch ($fieldType) {
case FieldType::RichText:
$field->setValue($attr['content']);
break;
case FieldType::CheckBox:
$field->setValue($attr['Checked'] === 'true');
break;
default:
break;
}

$fields[] = [$field];
}

$fields = array_merge([], ...$fields);
Expand All @@ -128,9 +141,16 @@

if (is_int($file)) {
$file = $this->rootFolder->getFirstNodeById($file);

if (!$file) {
$e = new NotFoundException();
$this->logger->error($e->getMessage());

throw $e;
}
}

if (!$file || !$file instanceof File) {

Check failure on line 153 in lib/Service/TemplateFieldService.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

TypeDoesNotContainType

lib/Service/TemplateFieldService.php:153:7: TypeDoesNotContainType: Operand of type false is always falsy (see https://psalm.dev/056)

Check failure on line 153 in lib/Service/TemplateFieldService.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

TypeDoesNotContainType

lib/Service/TemplateFieldService.php:153:7: TypeDoesNotContainType: Type OCP\Files\Node for $file is never !falsy (see https://psalm.dev/056)

Check failure on line 153 in lib/Service/TemplateFieldService.php

View workflow job for this annotation

GitHub Actions / static-psalm-analysis

TypeDoesNotContainType

lib/Service/TemplateFieldService.php:153:7: TypeDoesNotContainType: Type OCP\Files\Node for $file is never falsy (see https://psalm.dev/056)
$e = new NotFoundException();
$this->logger->error($e->getMessage());

Expand Down
Loading