[FEATURE] Access single values in form templates 16/62416/9
authorMathias Brodala <mbrodala@pagemachine.de>
Sat, 23 Nov 2019 12:15:21 +0000 (13:15 +0100)
committerRalf Zimmermann <ralf.zimmermann@tritum.de>
Sat, 23 Nov 2019 18:09:39 +0000 (19:09 +0100)
Resolves: #84713
Releases: master
Change-Id: Ic7b3dba44b8dc57d2aa4950c887c63b2b290517f
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/62416
Reviewed-by: Björn Jacob <bjoern.jacob@tritum.de>
Reviewed-by: Ralf Zimmermann <ralf.zimmermann@tritum.de>
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Björn Jacob <bjoern.jacob@tritum.de>
Tested-by: Ralf Zimmermann <ralf.zimmermann@tritum.de>
typo3/sysext/core/Documentation/Changelog/master/Feature-84713-AccessSingleFormValuesInTemplates.rst [new file with mode: 0644]
typo3/sysext/form/Classes/Domain/Model/FormDefinition.php
typo3/sysext/form/Classes/ViewHelpers/RenderAllFormValuesViewHelper.php
typo3/sysext/form/Classes/ViewHelpers/RenderFormValueViewHelper.php [new file with mode: 0644]

diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-84713-AccessSingleFormValuesInTemplates.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-84713-AccessSingleFormValuesInTemplates.rst
new file mode 100644 (file)
index 0000000..7ae66f5
--- /dev/null
@@ -0,0 +1,38 @@
+.. include:: ../../Includes.txt
+
+========================================================
+Feature: #84713 - Access single values in form templates
+========================================================
+
+See :issue:`84713`
+
+Description
+===========
+
+It is now possible to access single form values in templates of the "form" extension. For this a new :php:`RenderFormValueViewHelper` has been added which complements the existing :php:`RenderAllFormValuesViewHelper`.
+
+The :php:`RenderFormValueViewHelper` accepts a single form element and renders it exactly like the :php:`RenderAllFormValuesViewHelper` used to do within its internal traversal of renderable elements:
+
+.. code-block:: html
+
+   <p>The following message was just sent by <b><formvh:renderFormValue renderable="{page.rootForm.elements.name}" as="formValue">{formValue.processedValue}</formvh:renderFormValue><b>:</p>
+
+   <blockquote>
+      <formvh:renderFormValue renderable="{page.rootForm.elements.message}" as="formValue">
+         {formValue.processedValue}
+      </formvh:renderFormValue>
+   </blockquote>
+
+To make it possible to access single form elements, a new method :php:`FormDefinition::getElements()` has been added. This method returns an array containing all elements in the form with their identifiers as keys.
+
+.. code-block:: html
+
+   <f:debug>{page.rootForm.elements}</f:debug>
+
+
+Impact
+======
+
+Form values can now be placed freely in Fluid templates of the "form" extension instead of being bound to traverse all form values and skip rendering.
+
+.. index:: Fluid, Frontend, ext:form
index 58002b0..b9a84e7 100644 (file)
@@ -555,6 +555,16 @@ class FormDefinition extends AbstractCompositeRenderable implements VariableRend
     }
 
     /**
+     * Get all form elements with their identifiers as keys
+     *
+     * @return FormElementInterface[]
+     */
+    public function getElements(): array
+    {
+        return $this->elementsByIdentifier;
+    }
+
+    /**
      * Get a Form Element by its identifier
      *
      * If identifier does not exist, returns NULL.
index f6505a4..64eff7b 100644 (file)
@@ -18,11 +18,7 @@ namespace TYPO3\CMS\Form\ViewHelpers;
  */
 
 use TYPO3\CMS\Core\Resource\File;
-use TYPO3\CMS\Extbase\Domain\Model\FileReference;
-use TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface;
-use TYPO3\CMS\Form\Domain\Model\FormElements\StringableFormElementInterface;
 use TYPO3\CMS\Form\Domain\Model\Renderable\CompositeRenderableInterface;
-use TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface;
 use TYPO3\CMS\Form\Domain\Model\Renderable\RootRenderableInterface;
 use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
 use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
@@ -64,11 +60,6 @@ class RenderAllFormValuesViewHelper extends AbstractViewHelper
     public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
     {
         $renderable = $arguments['renderable'];
-        $as = $arguments['as'];
-
-        if (!$renderable->isEnabled()) {
-            return '';
-        }
 
         if ($renderable instanceof CompositeRenderableInterface) {
             $elements = $renderable->getRenderablesRecursively();
@@ -76,154 +67,20 @@ class RenderAllFormValuesViewHelper extends AbstractViewHelper
             $elements = [$renderable];
         }
 
-        $formRuntime = $renderingContext
-            ->getViewHelperVariableContainer()
-            ->get(RenderRenderableViewHelper::class, 'formRuntime');
-
+        $as = $arguments['as'];
         $output = '';
-        foreach ($elements as $element) {
-            $renderingOptions = $element->getRenderingOptions();
-
-            if (!$element instanceof FormElementInterface || !$element->isEnabled() || self::hasDisabledParent($element)) {
-                continue;
-            }
-
-            if ($renderingOptions['_isSection'] ?? false) {
-                $data = [
-                    'element' => $element,
-                    'isSection' => true,
-                ];
-            } elseif ($renderingOptions['_isCompositeFormElement'] ?? false) {
-                continue;
-            } else {
-                $value = $formRuntime[$element->getIdentifier()];
-                $data = [
-                    'element' => $element,
-                    'value' => $value,
-                    'processedValue' => self::processElementValue($element, $value, $renderChildrenClosure, $renderingContext),
-                    'isMultiValue' => is_array($value) || $value instanceof \Iterator
-                ];
-            }
 
-            $renderingContext->getVariableProvider()->add($as, $data);
-            $output .= $renderChildrenClosure();
-            $renderingContext->getVariableProvider()->remove($as);
-        }
-        return $output;
-    }
-
-    /**
-     * Converts the given value to a simple type (string or array) considering the underlying FormElement definition
-     *
-     * @param FormElementInterface $element
-     * @param mixed $value
-     * @param \Closure $renderChildrenClosure
-     * @param RenderingContextInterface $renderingContext
-     * @return mixed
-     * @internal
-     */
-    public static function processElementValue(
-        FormElementInterface $element,
-        $value,
-        \Closure $renderChildrenClosure,
-        RenderingContextInterface $renderingContext
-    ) {
-        $properties = $element->getProperties();
-        if (isset($properties['options']) && is_array($properties['options'])) {
-            $properties['options'] = TranslateElementPropertyViewHelper::renderStatic(
-                ['element' => $element, 'property' => 'options'],
+        foreach ($elements as $element) {
+            $output .= RenderFormValueViewHelper::renderStatic(
+                [
+                    'renderable' => $element,
+                    'as' => $as,
+                ],
                 $renderChildrenClosure,
                 $renderingContext
             );
-            if (is_array($value)) {
-                return self::mapValuesToOptions($value, $properties['options']);
-            }
-            return self::mapValueToOption($value, $properties['options']);
-        }
-        if (is_object($value)) {
-            return self::processObject($element, $value);
-        }
-        return $value;
-    }
-
-    /**
-     * Replaces the given values (=keys) with the corresponding elements in $options
-     * @see mapValueToOption()
-     *
-     * @param array $value
-     * @param array $options
-     * @return array
-     * @internal
-     */
-    public static function mapValuesToOptions(array $value, array $options): array
-    {
-        $result = [];
-        foreach ($value as $key) {
-            $result[] = self::mapValueToOption($key, $options);
-        }
-        return $result;
-    }
-
-    /**
-     * Replaces the given value (=key) with the corresponding element in $options
-     * If the key does not exist in $options, it is returned without modification
-     *
-     * @param mixed $value
-     * @param array $options
-     * @return mixed
-     * @internal
-     */
-    public static function mapValueToOption($value, array $options)
-    {
-        return $options[$value] ?? $value;
-    }
-
-    /**
-     * Converts the given $object to a string representation considering the $element FormElement definition
-     *
-     * @param FormElementInterface $element
-     * @param object $object
-     * @return string
-     * @internal
-     */
-    public static function processObject(FormElementInterface $element, $object): string
-    {
-        $properties = $element->getProperties();
-
-        if ($element instanceof StringableFormElementInterface) {
-            return $element->valueToString($object);
         }
 
-        if ($object instanceof \DateTime) {
-            return $object->format(\DateTime::W3C);
-        }
-
-        if ($object instanceof File || $object instanceof FileReference) {
-            if ($object instanceof FileReference) {
-                $object = $object->getOriginalResource();
-            }
-            return $object->getName();
-        }
-
-        if (method_exists($object, '__toString')) {
-            return (string)$object;
-        }
-        return 'Object [' . get_class($object) . ']';
-    }
-
-    /**
-     * @param RenderableInterface $renderable
-     * @return bool
-     * @internal
-     */
-    public static function hasDisabledParent(RenderableInterface $renderable): bool
-    {
-        while ($renderable = $renderable->getParentRenderable()) {
-            if ($renderable instanceof RenderableInterface && !$renderable->isEnabled()) {
-                return true;
-            }
-        }
-
-        return false;
+        return $output;
     }
 }
diff --git a/typo3/sysext/form/Classes/ViewHelpers/RenderFormValueViewHelper.php b/typo3/sysext/form/Classes/ViewHelpers/RenderFormValueViewHelper.php
new file mode 100644 (file)
index 0000000..a6b15d1
--- /dev/null
@@ -0,0 +1,220 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Form\ViewHelpers;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It originated from the Neos.Form package (www.neos.io)
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Extbase\Domain\Model\FileReference;
+use TYPO3\CMS\Form\Domain\Model\FormElements\FormElementInterface;
+use TYPO3\CMS\Form\Domain\Model\FormElements\StringableFormElementInterface;
+use TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface;
+use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
+use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
+use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
+
+/**
+ * Renders a single value of a form
+ *
+ * Scope: frontend
+ */
+class RenderFormValueViewHelper extends AbstractViewHelper
+{
+    use CompileWithRenderStatic;
+
+    /**
+     * @var bool
+     */
+    protected $escapeOutput = false;
+
+    /**
+     * Initialize the arguments
+     *
+     * @internal
+     */
+    public function initializeArguments()
+    {
+        $this->registerArgument('renderable', RenderableInterface::class, 'A renderable element', true);
+        $this->registerArgument('as', 'string', 'The name within the template', false, 'formValue');
+    }
+
+    /**
+     * Return array element by key
+     *
+     * @param array $arguments
+     * @param \Closure $renderChildrenClosure
+     * @param RenderingContextInterface $renderingContext
+     * @return string the rendered form values
+     */
+    public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
+    {
+        $element = $arguments['renderable'];
+
+        if (!$element instanceof FormElementInterface || !self::isEnabled($element)) {
+            return '';
+        }
+
+        $renderingOptions = $element->getRenderingOptions();
+
+        if ($renderingOptions['_isSection'] ?? false) {
+            $data = [
+                'element' => $element,
+                'isSection' => true,
+            ];
+        } elseif ($renderingOptions['_isCompositeFormElement'] ?? false) {
+            return '';
+        } else {
+            $formRuntime = $renderingContext
+                ->getViewHelperVariableContainer()
+                ->get(RenderRenderableViewHelper::class, 'formRuntime');
+            $value = $formRuntime[$element->getIdentifier()];
+            $data = [
+                'element' => $element,
+                'value' => $value,
+                'processedValue' => self::processElementValue($element, $value, $renderChildrenClosure, $renderingContext),
+                'isMultiValue' => is_iterable($value),
+            ];
+        }
+
+        $as = $arguments['as'];
+        $renderingContext->getVariableProvider()->add($as, $data);
+        $output = $renderChildrenClosure();
+        $renderingContext->getVariableProvider()->remove($as);
+
+        return $output;
+    }
+
+    /**
+     * Converts the given value to a simple type (string or array) considering the underlying FormElement definition
+     *
+     * @param FormElementInterface $element
+     * @param mixed $value
+     * @param \Closure $renderChildrenClosure
+     * @param RenderingContextInterface $renderingContext
+     * @return mixed
+     * @internal
+     */
+    public static function processElementValue(
+        FormElementInterface $element,
+        $value,
+        \Closure $renderChildrenClosure,
+        RenderingContextInterface $renderingContext
+    ) {
+        $properties = $element->getProperties();
+        if (isset($properties['options']) && is_array($properties['options'])) {
+            $properties['options'] = TranslateElementPropertyViewHelper::renderStatic(
+                ['element' => $element, 'property' => 'options'],
+                $renderChildrenClosure,
+                $renderingContext
+            );
+            if (is_array($value)) {
+                return self::mapValuesToOptions($value, $properties['options']);
+            }
+            return self::mapValueToOption($value, $properties['options']);
+        }
+        if (is_object($value)) {
+            return self::processObject($element, $value);
+        }
+        return $value;
+    }
+
+    /**
+     * Replaces the given values (=keys) with the corresponding elements in $options
+     * @see mapValueToOption()
+     *
+     * @param array $value
+     * @param array $options
+     * @return array
+     * @internal
+     */
+    public static function mapValuesToOptions(array $value, array $options): array
+    {
+        $result = [];
+        foreach ($value as $key) {
+            $result[] = self::mapValueToOption($key, $options);
+        }
+        return $result;
+    }
+
+    /**
+     * Replaces the given value (=key) with the corresponding element in $options
+     * If the key does not exist in $options, it is returned without modification
+     *
+     * @param mixed $value
+     * @param array $options
+     * @return mixed
+     * @internal
+     */
+    public static function mapValueToOption($value, array $options)
+    {
+        return $options[$value] ?? $value;
+    }
+
+    /**
+     * Converts the given $object to a string representation considering the $element FormElement definition
+     *
+     * @param FormElementInterface $element
+     * @param object $object
+     * @return string
+     * @internal
+     */
+    public static function processObject(FormElementInterface $element, $object): string
+    {
+        $properties = $element->getProperties();
+
+        if ($element instanceof StringableFormElementInterface) {
+            return $element->valueToString($object);
+        }
+
+        if ($object instanceof \DateTime) {
+            return $object->format(\DateTime::W3C);
+        }
+
+        if ($object instanceof File || $object instanceof FileReference) {
+            if ($object instanceof FileReference) {
+                $object = $object->getOriginalResource();
+            }
+
+            return $object->getName();
+        }
+
+        if (method_exists($object, '__toString')) {
+            return (string)$object;
+        }
+
+        return 'Object [' . get_class($object) . ']';
+    }
+
+    /**
+     * @param RenderableInterface $renderable
+     * @return bool
+     * @internal
+     */
+    public static function isEnabled(RenderableInterface $renderable): bool
+    {
+        if (!$renderable->isEnabled()) {
+            return false;
+        }
+
+        while ($renderable = $renderable->getParentRenderable()) {
+            if ($renderable instanceof RenderableInterface && !$renderable->isEnabled()) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+}