Initial version
All checks were successful
ci/woodpecker/push/code-style Pipeline was successful
ci/woodpecker/push/functional-tests Pipeline was successful

This commit is contained in:
Ferdinand Kuhl 2023-10-17 18:49:19 +02:00
parent 072f9b2fa0
commit 57d3efe1df
8 changed files with 375 additions and 0 deletions

View file

@ -0,0 +1,8 @@
pipeline:
code-style:
image: composer
commands:
- composer global config repositories.repo-name vcs https://git.digital-competence.de/Packages/php-codesniffer
- composer global config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
- composer global require digicomp/php-codesniffer:@dev
- composer global exec -- phpcs --runtime-set ignore_warnings_on_exit 1 --standard=DigiComp Classes/ Tests/

View file

@ -0,0 +1,28 @@
workspace:
base: /woodpecker
path: package
matrix:
include:
- FLOW_VERSION: 7.3
PHP_VERSION: 8.1
- FLOW_VERSION: 8.3
PHP_VERSION: 8.2
pipeline:
functional-tests:
image: "thecodingmachine/php:${PHP_VERSION}-v4-cli"
environment:
# Enable the PDO_SQLITE extension
- "PHP_EXTENSION_PDO_SQLITE=1"
- "FLOW_VERSION=${FLOW_VERSION}"
- "NEOS_BUILD_DIR=/woodpecker/Build-${FLOW_VERSION}"
commands:
- "sudo mkdir $NEOS_BUILD_DIR"
- "sudo chown -R docker:docker $NEOS_BUILD_DIR"
- "cd $NEOS_BUILD_DIR"
- "composer create-project --no-install neos/flow-base-distribution:^$FLOW_VERSION ."
- "composer config repositories.repo-name path /woodpecker/package"
- "composer remove --dev --no-update neos/behat || composer remove --no-update neos/behat"
- "composer require digicomp/attachment-view-utility:@dev"
- "bin/phpunit --configuration Build/BuildEssentials/PhpUnit/FunctionalTests.xml Packages/Application/DigiComp.AttachmentViewUtility/Tests/Functional"

View file

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace DigiComp\AttachmentViewUtility;
use cardinalby\ContentDisposition\ContentDisposition;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
trait AttachmentViewTrait
{
abstract protected function getAttachmentName(): string;
/**
* @return string|resource|StreamInterface|null
*/
abstract protected function getAttachmentContent();
abstract protected function getAttachmentMimeType(): string;
protected function addOptionsForAttachment(): void
{
if (\property_exists($this, 'supportedOptions')) {
$this->supportedOptions['attachmentCharset'] = [
'utf-8',
'Charset of the content or FALSE if you want to suppress the information in header',
'string|false',
];
$this->supportedOptions['attachmentDisposition'] = [
'attachment',
'One of "inline" or "attachment"',
'string',
];
} else {
throw new \RuntimeException('supported option could not be set', 1697552694);
}
}
public function render(): ResponseInterface
{
if ($this->options['attachmentCharset'] === false) {
$charset = '';
} else {
$charset = '; charset=' . $this->options['attachmentCharset'];
}
return new Response(
200,
[
'Content-Disposition' => ContentDisposition::create(
$this->getAttachmentName(),
true,
$this->options['attachmentDisposition']
)->format(),
'Content-Type' => $this->getAttachmentMimeType() . $charset,
],
$this->getAttachmentContent()
);
}
}

View file

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace DigiComp\AttachmentViewUtility;
use Neos\Eel\CompilingEvaluator;
use Neos\Eel\Context;
use Neos\Eel\Helper\ArrayHelper;
use Neos\Eel\Helper\StringHelper;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\I18n\EelHelper\TranslationHelper;
use Neos\FluidAdaptor\View\TemplateView;
use Neos\Utility\TypeHandling;
trait EelFilenameTrait
{
protected string $eelExpressionOptionKey = 'filenameEelExpression';
protected function addOptionFilenameEelExpression(): void
{
if (\property_exists($this, 'supportedOptions')) {
$this->supportedOptions['filenameEelExpression'] = [
null,
'Callable which creates a filename from variables',
'string',
true,
];
} else {
throw new \RuntimeException('supported option could not be set', 1697552694);
}
}
protected function getAttachmentName(): string
{
if ($this instanceof TemplateView) {
$variables = $this->getRenderingContext()->getVariableProvider()->getAll();
} elseif (\property_exists($this, 'variables')) {
$variables = $this->variables;
} else {
throw new \RuntimeException(
'No variables can be detected for this kind of view: ' . TypeHandling::getTypeForValue($this),
1697550214
);
}
if (!\property_exists($this, 'options')) {
throw new \RuntimeException('Your view options could not be found', 1697550440);
}
$expression = $this->options[$this->eelExpressionOptionKey];
$context = new Context(\array_merge($variables, [
'Array' => new ArrayHelper(),
'String' => new StringHelper(),
'Translation' => new TranslationHelper(),
]));
$this->emitFilenameEelExpressionContext($context);
return (new CompilingEvaluator())->evaluate($expression, $context);
}
/**
* @SuppressWarnings("unused")
*/
#[Flow\Signal]
protected function emitFilenameEelExpressionContext(Context $context): void
{
}
}

58
README.md Normal file
View file

@ -0,0 +1,58 @@
# DigiComp.AttachmentViewUtility
This package helps with recurring tasks of creating views for `neos/flow` projects.
It delivers two traits:
1. `AttachmentViewTrait` creates views which returns downloadable resources
2. `EelFilenameTrait` allows to set the filename with an eel expression, which got the view variables as context
They can be used together or standalone - but the filename trait does not make sense, if your view is not delivered by attachment.
The `EelContext` contains the `String`, `Array` and `Translation` - Helpers. If you need more, you can connect to the `YourView::filenameEelExpressionContext` signal, which gets the context as argument. That way you can extend the context with whatever you need.
## Example
SimpleAttachmentView.php:
```php
class SimpleAttachmentView extends AbstractView
{
use AttachmentViewTrait;
use EelFilenameTrait;
public function __construct(array $options = [])
{
$this->addOptionFilenameEelExpression();
$this->addOptionsForAttachment();
parent::__construct($options);
}
protected function getAttachmentContent()
{
return 'Hello ' . $this->variables['name'];
}
protected function getAttachmentMimeType(): string
{
return 'text/plain';
}
}
```
Controller:
```php
class DefaultController extends ActionController {
public function downloadAction()
{
$this->view->assign('name', 'World');
}
}
```
Views.yaml:
```yaml
-
requestFilter: "isController('Default') && isAction('download')"
viewObjectName: "Acme\\Vendor\\SimpleAttachmentView"
options:
filenameEelExpression: "'hello-' + name + '.txt"
```

View file

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace DigiComp\AttachmentViewUtility\Tests\Functional;
use DigiComp\AttachmentViewUtility\Tests\Functional\Fixtures\SimpleAttachmentTemplateView;
use Neos\Eel\Context;
use Neos\Flow\SignalSlot\Dispatcher;
use Neos\Flow\Tests\FunctionalTestCase;
use Psr\Http\Message\ResponseInterface;
class AttachmentViewTraitTest extends FunctionalTestCase
{
/**
* @test
*/
public function itCreatesAttachmentResponses(): void
{
$view = new SimpleAttachmentTemplateView([
'templateSource' => 'TollesTemplate! {testVar}',
'filenameEelExpression' => 'testVar + ".txt"',
'attachmentCharset' => 'iso-8859-1'
]);
$view->assign('testVar', '£ and € rates');
$result = $view->render();
static::assertInstanceOf(ResponseInterface::class, $result);
static::assertEquals(
'attachment; filename="£ and ? rates.txt"; filename*=UTF-8\'\'%C2%A3%20and%20%E2%82%AC%20rates.txt',
$result->getHeaderLine('Content-Disposition')
);
static::assertEquals('text/plain; charset=iso-8859-1', $result->getHeaderLine('Content-Type'));
static::assertEquals('TollesTemplate! £ and € rates', (string)$result->getBody());
}
/**
* @test
*/
public function itIsPossibleToSuppressCharset(): void
{
$view = new SimpleAttachmentTemplateView([
'templateSource' => 'TollesTemplate! {testVar}',
'filenameEelExpression' => 'testVar + ".txt"',
'attachmentCharset' => false
]);
$view->assign('testVar', 'WORLD');
$result = $view->render();
static::assertEquals('text/plain', $result->getHeaderLine('Content-Type'));
}
/**
* @test
*/
public function itIsPossibleToExtendTheContextBySignalSlot(): void
{
$view = new SimpleAttachmentTemplateView([
'templateSource' => 'TollesTemplate! {testVar}',
'filenameEelExpression' => 'greet(testVar) + ".txt"',
'attachmentCharset' => false
]);
$view->assign('testVar', 'WORLD');
$dispatcher = $this->objectManager->get(Dispatcher::class);
$dispatcher->connect(
SimpleAttachmentTemplateView::class,
'filenameEelExpressionContext',
function (Context $eelContext) {
$eelContext->push(
function ($name) {
return 'Hello ' . $name;
},
'greet'
);
}
);
$result = $view->render();
static::assertEquals(
'attachment; filename="Hello WORLD.txt"',
$result->getHeaderLine('Content-Disposition')
);
}
}

View file

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace DigiComp\AttachmentViewUtility\Tests\Functional\Fixtures;
use DigiComp\AttachmentViewUtility\AttachmentViewTrait;
use DigiComp\AttachmentViewUtility\EelFilenameTrait;
use Neos\Flow\Mvc\View\SimpleTemplateView;
class SimpleAttachmentTemplateView extends SimpleTemplateView
{
use AttachmentViewTrait;
use EelFilenameTrait;
public function __construct(array $options = [])
{
$this->addOptionFilenameEelExpression();
$this->addOptionsForAttachment();
parent::__construct($options);
}
protected function getAttachmentContent()
{
/** @noinspection MissUsingParentKeywordInspection */
return parent::render();
}
protected function getAttachmentMimeType(): string
{
return 'text/plain';
}
}

39
composer.json Normal file
View file

@ -0,0 +1,39 @@
{
"name": "digicomp/attachment-view-utility",
"type": "neos-package",
"description": "",
"require": {
"cardinalby/content-disposition": "^1.1",
"neos/eel": "^7.3 | ^8.3",
"neos/flow": "^7.3 | ^8.3",
"php": "^8.0"
},
"autoload": {
"psr-4": {
"DigiComp\\AttachmentViewUtility\\": "Classes"
}
},
"extra": {
"branch-alias": {
"dev-develop": "1.0.x-dev"
},
"applied-flow-migrations": [
]
},
"authors": [
{
"name": "Ferdinand Kuhl",
"email": "f.kuhl@digital-competence.de",
"homepage": "https://www.digital-competence.de",
"role": "Developer"
}
],
"license": "MIT",
"homepage": "https://git.digital-competence.de/Packages/DigiComp.AttachmentViewUtility",
"keywords": [
"Neos",
"Flow",
"view",
"mvc"
]
}