Skip to content

Commit

Permalink
FEATURE: Make formState initialization extendable
Browse files Browse the repository at this point in the history
This adds a mechanism to hook into the form state initialization and define custom initializers.

Resolves: #152
  • Loading branch information
daniellienert committed Mar 25, 2022
1 parent b40e709 commit 124d43c
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 17 deletions.
25 changes: 8 additions & 17 deletions Classes/Core/Runtime/FormRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
use Neos\Form\Core\Renderer\RendererInterface;
use Neos\Form\Exception\PropertyMappingException;
use Neos\Form\Exception\RenderingException;
use Neos\Form\FormState\FormStateInitializerChain;
use Neos\Utility\Arrays;

/**
Expand Down Expand Up @@ -99,6 +100,12 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
*/
protected $formState;

/**
* @Flow\Inject
* @var FormStateInitializerChain
*/
protected $formStateInitializerChain;

/**
* The current page is the page which will be displayed to the user
* during rendering.
Expand Down Expand Up @@ -162,30 +169,14 @@ public function __construct(FormDefinition $formDefinition, ActionRequest $reque
*/
public function initializeObject()
{
$this->initializeFormStateFromRequest();
$this->formState = $this->formStateInitializerChain->initializeFormState($this->formDefinition, $this->request);
$this->initializeCurrentPageFromRequest();

if (!$this->isFirstRequest()) {
$this->processSubmittedFormValues();
}
}

/**
* @return void
* @internal
*/
protected function initializeFormStateFromRequest()
{
$serializedFormStateWithHmac = $this->request->getInternalArgument('__state');
if ($serializedFormStateWithHmac === null) {
$this->formState = new FormState();
} else {
$serializedFormState = $this->hashService->validateAndStripHmac($serializedFormStateWithHmac);
/** @noinspection UnserializeExploitsInspection The unserialize call is safe because of the HMAC check above */
$this->formState = unserialize(base64_decode($serializedFormState));
}
}

/**
* @return void
* @internal
Expand Down
47 changes: 47 additions & 0 deletions Classes/FormState/FormStateInitializerChain.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);

namespace Neos\Form\FormState;

/*
* This file is part of the Neos.Form package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Form\Core\Model\FormDefinition;
use Neos\Fusion\Form\Runtime\Domain\FormState;
use Neos\Utility\PositionalArraySorter;

final class FormStateInitializerChain
{

/**
* @Flow\InjectConfiguration(path="formStateInitializerChain")
* @var string[][]
*/
protected $formStateInitializerChain;

public function initializeFormState(FormDefinition $formDefinition, ActionRequest $actionRequest): FormState
{
$formState = new FormState();
$sortedChain = (new PositionalArraySorter($this->formStateInitializerChain))->toArray();

foreach ($sortedChain as $initializer) {
$class = $initializer['class'] ?? '';
if (!is_a($class, FormStateInitializerInterface::class)) {
throw new \RuntimeException(sprintf('The given class "%s" does not implement the interface %s', $class, FormStateInitializerInterface::class), 1648204540);
}

$formState = (new $class())->initializeState($formDefinition, $actionRequest, $formState);
}

return $formState;
}
}
23 changes: 23 additions & 0 deletions Classes/FormState/FormStateInitializerInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);

namespace Neos\Form\FormState;

/*
* This file is part of the Neos.Form package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

use Neos\Flow\Mvc\ActionRequest;
use Neos\Form\Core\Model\FormDefinition;
use Neos\Form\Core\Runtime\FormState;

interface FormStateInitializerInterface
{
public function initializeState(FormDefinition $formDefinition, ActionRequest $actionRequest, FormState $previousFormState): FormState;
}
43 changes: 43 additions & 0 deletions Classes/FormState/SerializedFormStateArgumentInitializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);

namespace Neos\Form\FormState;

/*
* This file is part of the Neos.Form package.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Security\Cryptography\HashService;
use Neos\Form\Core\Model\FormDefinition;
use Neos\Form\Core\Runtime\FormState;

class SerializedFormStateArgumentInitializer implements FormStateInitializerInterface
{

/**
* @Flow\Inject
* @var HashService
* @internal
*/
protected $hashService;

public function initializeState(FormDefinition $formDefinition, ActionRequest $actionRequest, FormState $previousFormState): FormState
{
$serializedFormStateWithHmac = $actionRequest->getInternalArgument('__state');
if ($serializedFormStateWithHmac !== null) {
$serializedFormState = $this->hashService->validateAndStripHmac($serializedFormStateWithHmac);
/** @noinspection UnserializeExploitsInspection The unserialize call is safe because of the HMAC check above */
return unserialize(base64_decode($serializedFormState));
}

return $previousFormState;
}
}
7 changes: 7 additions & 0 deletions Configuration/Settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ Neos:
Form:
yamlPersistenceManager:
savePath: '%FLOW_PATH_DATA%Forms/'

supertypeResolver:
hiddenProperties: { }

presets:
default:
title: Default
Expand Down Expand Up @@ -200,3 +202,8 @@ Neos:
implementationClassName: Neos\Flow\Validation\Validator\RegularExpressionValidator
'Neos.Flow:Count':
implementationClassName: Neos\Flow\Validation\Validator\CountValidator

formStateInitializerChain:
serializedFormStateArgument:
class: 'Neos\Form\FormState\SerializedFormStateArgumentInitializer'
position: 'start'

0 comments on commit 124d43c

Please sign in to comment.