<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Entity;

use Sulu\Component\Persistence\Model\AuditableInterface;

class Dynamic implements AuditableInterface
{
    const TYPE_ATTACHMENT = 'attachment';
    const TYPE_EMAIL = 'email';

    protected static $ARRAY_TYPES = [
        'checkboxMultiple',
        'dropdownMultiple',
        self::TYPE_ATTACHMENT,
    ];

    public static $HIDDEN_TYPES = [
        'spacer',
        'headline',
        'freeText',
        'recaptcha',
    ];

    /**
     * @var int
     */
    private $id;

    /**
     * @var string
     */
    private $type;

    /**
     * @var string
     */
    private $typeId;

    /**
     * @var string
     */
    private $typeName;

    /**
     * @var string
     */
    private $locale;

    /**
     * @var Form
     */
    private $form;

    /**
     * @var string
     */
    private $webspaceKey;

    /**
     * @var string
     */
    private $salutation;

    /**
     * @var string
     */
    private $title;

    /**
     * @var string
     */
    private $firstName;

    /**
     * @var string
     */
    private $lastName;

    /**
     * @var string
     */
    private $email;

    /**
     * @var string
     */
    private $phone;

    /**
     * @var string
     */
    private $fax;

    /**
     * @var string
     */
    private $street;

    /**
     * @var string
     */
    private $zip;

    /**
     * @var string
     */
    private $city;

    /**
     * @var string
     */
    private $state;

    /**
     * @var string
     */
    private $country;

    /**
     * @var string
     */
    private $function;

    /**
     * @var string
     */
    private $company;

    /**
     * @var string
     */
    private $text;

    /**
     * @var string
     */
    private $textarea;

    /**
     * @var string
     */
    private $date;

    /**
     * @var string
     */
    private $attachment;

    /**
     * @var string
     */
    private $checkbox;

    /**
     * @var string
     */
    private $checkboxMultiple;

    /**
     * @var string
     */
    private $dropdown;

    /**
     * @var string
     */
    private $dropdownMultiple;

    /**
     * @var string
     */
    private $radioButtons;

    /**
     * @var string
     */
    private $data = '[]';

    /**
     * @var \DateTime
     */
    private $created;

    /**
     * @var \DateTime
     */
    private $changed;

    /**
     * @var
     */
    private $creator;

    /**
     * @var
     */
    private $changer;

    /**
     * Dynamic constructor.
     *
     * @param string $type
     * @param string $typeId
     * @param string $locale
     * @param Form $form
     * @param array $data
     * @param string $webspaceKey
     * @param string $typeName
     */
    public function __construct($type, $typeId, $locale, $form, $data = [], $webspaceKey = null, $typeName = '')
    {
        $this->type = $type;
        $this->typeId = $typeId;
        $this->locale = $locale;
        $this->form = $form;
        $this->webspaceKey = $webspaceKey;
        $this->typeName = $typeName;

        foreach ($data as $name => $value) {
            $this->__set($name, $value);
        }
    }

    /**
     * Get data.
     *
     * @return array
     */
    public function getData()
    {
        return json_decode($this->data, true);
    }

    /**
     * {@inheritdoc}
     */
    public function __set($name, $value)
    {
        if (property_exists($this, $name)) {
            if (in_array($name, self::$ARRAY_TYPES)) {
                $value = json_encode($value, JSON_UNESCAPED_UNICODE);
            }

            $this->$name = $value;
        } else {
            $array = $this->getData();
            $array[$name] = $value;

            $this->data = json_encode($array, JSON_UNESCAPED_UNICODE);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function __isset($name)
    {
        if (!is_string($name)) {
            return false;
        }

        if (property_exists($this, $name) && $this->$name !== null) {
            return true;
        }

        $data = $this->getData();

        return isset($data[$name]);
    }

    /**
     * {@inheritdoc}
     */
    public function __get($name)
    {
        return $this->getField($name);
    }

    /**
     * Get field.
     *
     * @param string $key
     *
     * @return string|array
     */
    public function getField($key)
    {
        if (property_exists($this, $key)) {
            if (in_array($key, self::$ARRAY_TYPES)) {
                if (!is_string($this->$key)) {
                    return;
                }

                return json_decode($this->$key, true);
            }

            return $this->$key;
        }

        $array = $this->getData();

        if (isset($array[$key])) {
            return $array[$key];
        }
    }

    /**
     * Get fields.
     *
     * @param bool $hideHidden
     *
     * @return array
     */
    public function getFields($hideHidden = false)
    {
        $entry = [];

        if (!$this->form) {
            return [];
        }

        foreach ($this->form->getFields() as $field) {
            if ($hideHidden && in_array($field->getType(), self::$HIDDEN_TYPES)) {
                continue;
            }

            $entry[$field->getKey()] = $this->getField($field->getKey());
        }

        return $entry;
    }

    /**
     * Get fields by type.
     *
     * @param string $type
     *
     * @return array
     */
    public function getFieldsByType($type)
    {
        $entry = [];

        if (!$this->form) {
            return [];
        }

        foreach ($this->form->getFieldsByType($type) as $field) {
            $entry[$field->getKey()] = $this->getField($field->getKey());
        }

        return $entry;
    }

    /**
     * Get field type.
     *
     * @param string $key
     *
     * @return string
     */
    public function getFieldType($key)
    {
        if (!$this->form) {
            return;
        }

        return $this->form->getFieldType($key);
    }

    /**
     * Get id.
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Get form.
     *
     * @return Form
     */
    public function getForm()
    {
        return $this->form;
    }

    /**
     * Get locale.
     *
     * @return string
     */
    public function getLocale()
    {
        return $this->locale;
    }

    /**
     * Set locale.
     *
     * @param string $locale
     *
     * @return $this
     */
    public function setLocale($locale)
    {
        $this->locale = $locale;

        return $this;
    }

    /**
     * Get type.
     *
     * @return string
     */
    public function getType()
    {
        return $this->type;
    }

    /**
     * Get typeId.
     *
     * @return string
     */
    public function getTypeId()
    {
        return $this->typeId;
    }

    /**
     * Get typeName.
     *
     * @return string
     */
    public function getTypeName()
    {
        return $this->typeName;
    }

    /**
     * Get webspaceKey.
     *
     * @return string
     */
    public function getWebspaceKey()
    {
        return $this->webspaceKey;
    }

    /**
     * {@inheritdoc}
     */
    public function getCreated()
    {
        return $this->created;
    }

    /**
     * {@inheritdoc}
     */
    public function getChanged()
    {
        return $this->changed;
    }

    /**
     * {@inheritdoc}
     */
    public function getCreator()
    {
        return $this->creator;
    }

    /**
     * {@inheritdoc}
     */
    public function getChanger()
    {
        return $this->changer;
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\DependencyInjection;

use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

/**
 * This is the class that validates and merges configuration from your app/config files.
 *
 * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html#cookbook-bundles-extension-config-class}
 */
class Configuration implements ConfigurationInterface
{
    /**
     * {@inheritdoc}
     */
    public function getConfigTreeBuilder()
    {
        $treeBuilder = new TreeBuilder();
        $rootNode = $treeBuilder->root('sulu_form');

        $rootNode->children()
            ->scalarNode('mailchimp_api_key')->defaultValue(null)->end()
            ->scalarNode('mailchimp_subscribe_status')->defaultValue('subscribed')->end()
            ->enumNode('media_collection_strategy')
                ->values([
                    SuluFormExtension::MEDIA_COLLECTION_STRATEGY_SINGLE,
                    SuluFormExtension::MEDIA_COLLECTION_STRATEGY_TREE,
                ])
                ->defaultValue(SuluFormExtension::MEDIA_COLLECTION_STRATEGY_SINGLE)
            ->end()
            ->arrayNode('static_forms')
                ->useAttributeAsKey('name')
                ->prototype('array')
                    ->children()
                        ->scalarNode('class')->end()
                    ->end()
                ->end()
            ->end()
            ->arrayNode('mail')
                ->addDefaultsIfNotSet()
                ->children()
                    ->scalarNode('from')->defaultValue('%sulu_admin.email%')->end()
                    ->scalarNode('to')->defaultValue('%sulu_admin.email%')->end()
                    ->arrayNode('templates')
                        ->addDefaultsIfNotSet()
                        ->children()
                            ->scalarNode('notify')
                                ->defaultValue('SuluFormBundle:mails:notify.html.twig')
                            ->end()
                            ->scalarNode('customer')
                                ->defaultValue('SuluFormBundle:mails:customer.html.twig')
                            ->end()
                        ->end()
                    ->end()
                ->end()
            ->end()
            ->arrayNode('dynamic_list_builder')
                ->addDefaultsIfNotSet()
                ->children()
                    ->scalarNode('default')->defaultValue('simple')->end()
                    ->scalarNode('delimiter')->defaultValue(PHP_EOL)->end()
                ->end()
            ->end()
            ->booleanNode('dynamic_auto_title')->defaultValue(true)->end()
            ->arrayNode('dynamic_widths')
                ->normalizeKeys(false)
                ->prototype('scalar')->end()->defaultValue([
                    'full' => 'sulu_form.width.full',
                    'half' => 'sulu_form.width.half',
                    'one-third' => 'sulu_form.width.one-third',
                    'two-thirds' => 'sulu_form.width.two-thirds',
                    'one-quarter' => 'sulu_form.width.one-quarter',
                    'three-quarters' => 'sulu_form.width.three-quarters',
                    'one-sixth' => 'sulu_form.width.one-sixth',
                    'five-sixths' => 'sulu_form.width.five-sixths',
                ])
            ->end()
            ->variableNode('dynamic_lists')->defaultValue([])->end()
            ->arrayNode('ajax_templates')
                ->normalizeKeys(false)
                ->prototype('scalar')->end()->defaultValue([])
            ->end()
            ->arrayNode('dynamic_disabled_types')
                ->prototype('scalar')->end()->defaultValue([])
            ->end()
        ;

        return $treeBuilder;
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Configuration;

use Sulu\Bundle\FormBundle\Entity\Dynamic;
use Sulu\Bundle\FormBundle\Form\Type\AbstractType;
use Sulu\Bundle\FormBundle\Media\CollectionStrategyInterface;

/**
 * Form configuration factory to create form config by form entities and types.
 */
class FormConfigurationFactory
{
    /**
     * @var CollectionStrategyInterface
     */
    private $collectionStrategy;

    /**
     * @var string
     */
    private $mailAdminTemplate;

    /**
     * @var string
     */
    private $mailWebsiteTemplate;

    /**
     * FormConfigurationFactory constructor.
     *
     * @param CollectionStrategyInterface $collectionStrategy
     * @param string $mailAdminTemplate
     * @param string $mailWebsiteTemplate
     */
    public function __construct(
        CollectionStrategyInterface $collectionStrategy,
        $mailAdminTemplate,
        $mailWebsiteTemplate
    ) {
        $this->collectionStrategy = $collectionStrategy;
        $this->mailAdminTemplate = $mailAdminTemplate;
        $this->mailWebsiteTemplate = $mailWebsiteTemplate;
    }

    /**
     * Build by dynamic entity.
     *
     * @param Dynamic $dynamic
     *
     * @return FormConfigurationInterface
     */
    public function buildByDynamic(Dynamic $dynamic)
    {
        $config = $this->create($dynamic->getLocale());
        $config->setFileFields($this->getFileFieldsByDynamic($dynamic));

        $adminMailConfiguration = $this->buildAdminMailConfigurationByDynamic($dynamic);
        $websiteMailConfiguration = $this->buildWebsiteMailConfigurationByDynamic($dynamic);

        $config->setAdminMailConfiguration($adminMailConfiguration);
        $config->setWebsiteMailConfiguration($websiteMailConfiguration);

        return $config;
    }

    /**
     * Build by type.
     *
     * @param AbstractType $type
     * @param mixed $formData
     * @param string $locale
     * @param array $attributes
     *
     * @return FormConfigurationInterface
     */
    public function buildByType(AbstractType $type, $formData, $locale, $attributes)
    {
        $config = $this->create($locale);
        $config->setFileFields(array_fill_keys($type->getFileFields(), $type->getCollectionId()));

        $attributes['form'] = [
            'data' => $formData,
        ];

        $adminMailConfiguration = $this->buildAdminMailConfigurationByTypeAndData(
            $type,
            $formData,
            $locale,
            $attributes
        );
        $websiteMailConfiguration = $this->buildWebsiteMailConfigurationByTypeAndData(
            $type,
            $formData,
            $locale,
            $attributes
        );

        $config->setAdminMailConfiguration($adminMailConfiguration);
        $config->setWebsiteMailConfiguration($websiteMailConfiguration);

        return $config;
    }

    /**
     * Build admin mail configuration by type.
     *
     * @param AbstractType $type
     * @param mixed $formData
     * @param string $locale
     * @param array $attributes
     *
     * @return MailConfiguration|null
     */
    private function buildAdminMailConfigurationByTypeAndData(AbstractType $type, $formData, $locale, $attributes)
    {
        if ($type->getNotifyDeactivateMails($formData)) {
            return null;
        }

        $adminMailConfiguration = $this->createMailConfiguration($locale);

        $adminMailConfiguration->setSubject($type->getNotifySubject($formData));
        $adminMailConfiguration->setFrom($type->getNotifyFromMailAddress($formData));
        $adminMailConfiguration->setTo($type->getNotifyToMailAddress($formData));
        $adminMailConfiguration->setReplyTo($type->getNotifyReplyToMailAddress($formData));
        $adminMailConfiguration->setAddAttachments($type->getNotifySendAttachments($formData));
        $adminMailConfiguration->setTemplate($type->getNotifyMail($formData));
        $adminMailConfiguration->setTemplateAttributes($attributes);

        return $adminMailConfiguration;
    }

    /**
     * Build admin mail configuration by type.
     *
     * @param AbstractType $type
     * @param mixed $formData
     * @param string $locale
     * @param array $attributes
     *
     * @return MailConfiguration|null
     */
    private function buildWebsiteMailConfigurationByTypeAndData(AbstractType $type, $formData, $locale, $attributes)
    {
        if ($type->getCustomerDeactivateMails($formData)) {
            return null;
        }

        $websiteMailConfiguration = $this->createMailConfiguration($locale);

        $websiteMailConfiguration->setSubject($type->getCustomerSubject($formData));
        $websiteMailConfiguration->setFrom($type->getCustomerFromMailAddress($formData));
        $websiteMailConfiguration->setTo($type->getCustomerToMailAddress($formData));
        $websiteMailConfiguration->setReplyTo($type->getCustomerReplyToMailAddress($formData));
        $websiteMailConfiguration->setAddAttachments(false); // Currently not implemented in the AbstractType.
        $websiteMailConfiguration->setTemplate($type->getCustomerMail($formData));
        $websiteMailConfiguration->setTemplateAttributes($attributes);

        return $websiteMailConfiguration;
    }

    /**
     * Build admin mail configuration by dynamic entity.
     *
     * @param Dynamic $dynamic
     *
     * @return MailConfiguration|null
     */
    private function buildAdminMailConfigurationByDynamic(Dynamic $dynamic)
    {
        $form = $dynamic->getForm();
        $locale = $dynamic->getLocale();
        $translation = $form->getTranslation($locale);

        if ($translation->getDeactivateNotifyMails()) {
            return null;
        }

        $adminMailConfiguration = $this->createMailConfiguration($locale);

        $adminMailConfiguration->setSubject($translation->getSubject());
        $adminMailConfiguration->setFrom(
            $this->getEmail($translation->getFromEmail(), $translation->getFromName())
        );

        // Set Receivers for the email.
        $toList = $this->getEmail($translation->getToEmail(), $translation->getToName()) ?: [];
        $ccList = [];
        $bccList = [];

        foreach ($translation->getReceivers() as $receiver) {
            $email = $this->getEmail($receiver->getEmail(), $receiver->getName());

            if (MailConfigurationInterface::TYPE_TO == $receiver->getType()) {
                $toList = array_merge($toList, $email);
            } elseif (MailConfigurationInterface::TYPE_CC == $receiver->getType()) {
                $ccList = array_merge($ccList, $email);
            } elseif (MailConfigurationInterface::TYPE_BCC == $receiver->getType()) {
                $bccList = array_merge($bccList, $email);
            }
        }

        $adminMailConfiguration->setTo(array_filter($toList));
        $adminMailConfiguration->setCc(array_filter($ccList));
        $adminMailConfiguration->setBcc(array_filter($bccList));

        if ($translation->getReplyTo()) {
            $adminMailConfiguration->setReplyTo($this->getEmailFromDynamic($dynamic));
        }

        // Set attachment configuration.
        $adminMailConfiguration->setAddAttachments($translation->getSendAttachments());

        // Set template.
        $adminMailConfiguration->setTemplate($this->mailAdminTemplate);
        $adminMailConfiguration->setTemplateAttributes($this->getTemplateAttributesFromDynamic($dynamic));

        return $adminMailConfiguration;
    }

    /**
     * Build website mail configuration by form translation.
     *
     * @param Dynamic $dynamic
     *
     * @return MailConfiguration|null
     */
    private function buildWebsiteMailConfigurationByDynamic(Dynamic $dynamic)
    {
        $form = $dynamic->getForm();
        $locale = $dynamic->getLocale();
        $translation = $form->getTranslation($locale);

        if ($translation->getDeactivateCustomerMails()) {
            return null;
        }

        $websiteMailConfiguration = $this->createMailConfiguration($locale);

        $websiteMailConfiguration->setSubject($translation->getSubject());
        $websiteMailConfiguration->setFrom(
            $this->getEmail($translation->getFromEmail(), $translation->getFromName())
        );
        $websiteMailConfiguration->setTo($this->getEmailFromDynamic($dynamic));

        // Set attachment configuration.
        $websiteMailConfiguration->setAddAttachments($translation->getSendAttachments());

        // Set template.
        $websiteMailConfiguration->setTemplate($this->mailWebsiteTemplate);
        $websiteMailConfiguration->setTemplateAttributes($this->getTemplateAttributesFromDynamic($dynamic));

        return $websiteMailConfiguration;
    }

    /**
     * Get file fields by dynamic.
     *
     * @param Dynamic $dynamic
     *
     * @return int[]
     */
    private function getFileFieldsByDynamic(Dynamic $dynamic)
    {
        $form = $dynamic->getForm();

        $fields = $form->getFieldsByType(Dynamic::TYPE_ATTACHMENT);

        if (0 === count($fields)) {
            return [];
        }

        $collectionId = $this->getCollectionIdByDynamic($dynamic);

        $fileFields = [];
        foreach ($fields as $field) {
            $fileFields[$field->getKey()] = $collectionId;
        }

        return $fileFields;
    }

    /**
     * Get collection id by dynamic.
     *
     * @param Dynamic $dynamic
     *
     * @return array|int
     */
    private function getCollectionIdByDynamic(Dynamic $dynamic)
    {
        $form = $dynamic->getForm();

        return $this->collectionStrategy->getCollectionId(
            $form->getId(),
            $form->getTranslation($dynamic->getLocale(), false, true)->getTitle(),
            $dynamic->getType(),
            $dynamic->getTypeId(),
            $dynamic->getLocale()
        );
    }

    /**
     * Get template attributes from dynamic.
     *
     * @param Dynamic $dynamic
     *
     * @return array
     */
    private function getTemplateAttributesFromDynamic(Dynamic $dynamic)
    {
        return [
            // TODO FIXME this is currently overwritten in RequestListener to get the medias correctly for emails.
            'formEntity' => $dynamic->getForm()->serializeForLocale($dynamic->getLocale(), $dynamic),
        ];
    }

    /**
     * Get email from dynamic.
     *
     * @param Dynamic $dynamic
     *
     * @return array|null
     */
    private function getEmailFromDynamic(Dynamic $dynamic)
    {
        $emails = $dynamic->getFieldsByType(Dynamic::TYPE_EMAIL);
        $email = reset($emails);

        return $this->getEmail($email);
    }

    /**
     * Get email.
     *
     * @param string $email
     * @param string $name
     *
     * @return array|null
     */
    private function getEmail($email, $name = null)
    {
        if (!$email) {
            return null;
        }

        if (!$name) {
            $name = $email;
        }

        return [$email => $name];
    }

    /**
     * Create form configuration.
     *
     * @param string $locale
     *
     * @return FormConfiguration
     */
    private function create($locale)
    {
        return new FormConfiguration($locale);
    }

    /**
     * @return MailConfiguration
     */
    private function createMailConfiguration($locale)
    {
        return new MailConfiguration($locale);
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Form;

use Sulu\Bundle\FormBundle\Dynamic\Checksum;
use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypePool;
use Sulu\Bundle\FormBundle\Entity\Dynamic;
use Sulu\Bundle\FormBundle\Entity\Form;
use Sulu\Bundle\FormBundle\Form\Type\DynamicFormType;
use Sulu\Bundle\FormBundle\Repository\FormRepository;
use Sulu\Bundle\FormBundle\TitleProvider\TitleProviderPoolInterface;
use Symfony\Component\Form\FormFactory;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\HttpException;

/**
 * Builds a dynamic form.
 */
class Builder implements BuilderInterface
{
    /**
     * @var array
     */
    private $cache = [];

    /**
     * @var RequestStack
     */
    protected $requestStack;

    /**
     * @var FormFieldTypePool
     */
    protected $formFieldTypePool;

    /**
     * @var TitleProviderPoolInterface
     */
    protected $titleProviderPool;

    /**
     * @var FormRepository
     */
    protected $formRepository;

    /**
     * @var FormFactory
     */
    protected $formFactory;

    /**
     * @var Checksum
     */
    protected $checksum;

    /**
     * Builder constructor.
     *
     * @param RequestStack $requestStack
     * @param FormFieldTypePool $formFieldTypePool
     * @param TitleProviderPoolInterface $titleProviderPool,
     * @param FormRepository $formRepository
     * @param FormFactory $formFactory
     * @param Checksum $checksum
     */
    public function __construct(
        RequestStack $requestStack,
        FormFieldTypePool $formFieldTypePool,
        TitleProviderPoolInterface $titleProviderPool,
        FormRepository $formRepository,
        FormFactory $formFactory,
        Checksum $checksum
    ) {
        $this->requestStack = $requestStack;
        $this->formFieldTypePool = $formFieldTypePool;
        $this->titleProviderPool = $titleProviderPool;
        $this->formRepository = $formRepository;
        $this->formFactory = $formFactory;
        $this->checksum = $checksum;
    }

    /**
     * {@inheritdoc}
     */
    public function buildByRequest(Request $request)
    {
        foreach ($request->request->all() as $key => $parameters) {
            if (0 === strpos($key, 'dynamic_')) {
                $formNameParts = explode('dynamic_', $key, 2);
                $checksumCheck = $this->checksum->check(
                    $parameters['checksum'],
                    $parameters['type'],
                    $parameters['typeId'],
                    $parameters['formId'],
                    $parameters['formName']
                );

                if (!isset($formNameParts[1])) {
                    continue;
                }

                if (!$checksumCheck) {
                    throw new HttpException(400, 'SuluFormBundle: Checksum not valid!');
                }

                if (!isset($parameters['type'])
                    || !isset($parameters['formId'])
                    || !isset($parameters['formName'])
                    || !isset($parameters['typeId'])
                ) {
                    continue;
                }

                $locale = $request->getLocale();
                if (isset($parameters['locale'])) {
                    $locale = $parameters['locale'];
                }

                return $this->build(
                    $parameters['formId'],
                    $parameters['type'],
                    $parameters['typeId'],
                    $locale,
                    $parameters['formName']
                );
            }
        }

        return null;
    }

    /**
     * {@inheritdoc}
     */
    public function build($id, $type, $typeId, $locale = null, $name = 'form')
    {
        $request = $this->requestStack->getCurrentRequest();

        if (!$locale) {
            $locale = $request->getLocale();
        }

        // Check if form was builded before and return the cached form.
        $key = $this->getKey($id, $type, $typeId, $locale, $name);

        if (!isset($this->cache[$key])) {
            $this->cache[$key] = $this->buildForm($id, $type, $typeId, $locale, $name);
        }

        return $this->cache[$key];
    }

    /**
     * Returns formType and the built form.
     *
     * @param int $id
     * @param string $type
     * @param string $typeId
     * @param string $locale
     * @param string $name
     *
     * @return FormInterface
     */
    protected function buildForm($id, $type, $typeId, $locale, $name)
    {
        $request = $this->requestStack->getCurrentRequest();

        // Load Form entity
        $formEntity = $this->loadFormEntity($id, $locale);

        if (!$formEntity) {
            return;
        }

        $webspaceKey = $this->getWebspaceKey();

        // Create Form
        $form = $this->createForm(
            $name,
            $type,
            $typeId,
            $locale,
            $formEntity,
            $webspaceKey
        );

        // Handle request
        $form->handleRequest($request);

        return $form;
    }

    /**
     * Get key.
     *
     * @param int $id
     * @param string $type
     * @param string $typeId
     * @param string $locale
     * @param string $name
     *
     * @return string
     */
    private function getKey($id, $type, $typeId, $locale, $name)
    {
        return implode('__', func_get_args());
    }

    /**
     * Create form.
     *
     * @param string $name
     * @param string $type
     * @param string $typeId
     * @param string $locale
     * @param Form $formEntity
     * @param string $webspaceKey
     *
     * @return FormInterface
     */
    private function createForm($name, $type, $typeId, $locale, $formEntity, $webspaceKey)
    {
        $defaults = $this->getDefaults($formEntity, $locale);
        $typeName = $this->titleProviderPool->get($type)->getTitle($typeId);

        $recaptchaFields = $formEntity->getFieldsByType('recaptcha');
        $csrfTokenProtection = true;

        if (count($recaptchaFields)) {
            $csrfTokenProtection = false;
        }

        return $this->formFactory->createNamed(
            'dynamic_' . $name . $formEntity->getId(),
            DynamicFormType::class,
            new Dynamic($type, $typeId, $locale, $formEntity, $defaults, $webspaceKey, $typeName),
            [
                'formEntity' => $formEntity,
                'locale' => $locale,
                'type' => $type,
                'typeId' => $typeId,
                'csrf_protection' => $csrfTokenProtection,
                'name' => $name,
                'block_name' => 'dynamic_' . $name,
            ]
        );
    }

    /**
     * Load Form entity.
     *
     * @param $id
     * @param $locale
     *
     * @return Form|null
     */
    private function loadFormEntity($id, $locale)
    {
        $formEntity = $this->formRepository->loadById($id, $locale);

        if (!$formEntity) {
            return null;
        }

        $translation = $formEntity->getTranslation($locale);

        if (!$translation) {
            // No translation for this locale exists
            return null;
        }

        return $formEntity;
    }

    /**
     * Get defaults.
     *
     * @param Form $formEntity
     * @param string $locale
     *
     * @return array
     */
    private function getDefaults(Form $formEntity, $locale)
    {
        // set Defaults
        $defaults = [];

        foreach ($formEntity->getFields() as $field) {
            $fieldTranslation = $field->getTranslation($locale);

            if ($fieldTranslation && $fieldTranslation->getDefaultValue()) {
                $value = $this->formFieldTypePool->get($field->getType())->getDefaultValue($field, $locale);
                $defaults[$field->getKey()] = $value;
            }
        }

        return $defaults;
    }

    /**
     * Get webspace key.
     *
     * @return string
     */
    private function getWebspaceKey()
    {
        $request = $this->requestStack->getCurrentRequest();
        $webspaceKey = null;

        if ($request->get('_sulu')) {
            if ($request->get('_sulu')->getAttribute('webspace')) {
                $webspaceKey = $request->get('_sulu')->getAttribute('webspace')->getKey();
            }
        }

        return $webspaceKey;
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Mail;

use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

class NullHelper implements HelperInterface
{
    /**
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * @param LoggerInterface $logger
     */
    public function __construct($logger = null)
    {
        $this->logger = $logger ?: new NullLogger();
    }

    /**
     * {@inheritdoc}
     */
    public function sendMail(
        $subject,
        $body,
        $toMail = null,
        $fromMail = null,
        $html = true,
        $replyTo = null,
        $attachments = [],
        $ccMail = [],
        $bccMail = []
    ) {
        $this->logger->info(sprintf(
            'SuluFormBundle NullMailHelper: ' . PHP_EOL .
            '   From: %s' . PHP_EOL .
            '   To: %s' . PHP_EOL .
            '   Reply to: %s' . PHP_EOL .
            '   Subject: %s' . PHP_EOL .
            '   CC: %s' . PHP_EOL .
            '   BCC: %s' . PHP_EOL,
            is_string($fromMail) ? $fromMail : serialize($fromMail),
            is_string($toMail) ? $toMail : serialize($toMail),
            is_string($replyTo) ? $replyTo : serialize($toMail),
            is_string($subject) ? $subject : serialize($subject),
            is_string($ccMail) ? $ccMail : serialize($ccMail),
            is_string($bccMail) ? $bccMail : serialize($bccMail)
        ));
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Dynamic;

use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;

/**
 * Checksum.
 */
class Checksum
{
    /**
     * @var string
     */
    private $secret;

    /**
     * @var MessageDigestPasswordEncoder
     */
    private $encoder;

    /**
     * Checksum constructor.
     *
     * @param string $secret
     */
    public function __construct($secret)
    {
        $this->secret = $secret;
        $this->encoder = new MessageDigestPasswordEncoder();
    }

    /**
     * Check checksum with given parameters.
     *
     * @param string $checksum
     * @param string $type
     * @param string $typeId
     * @param string $formId
     * @param string $formName
     *
     * @return bool
     */
    public function check($checksum, $type, $typeId, $formId, $formName)
    {
        $checksumRaw = $this->createKey($type, $typeId, $formId, $formName);

        return $this->encoder->isPasswordValid($checksum, $checksumRaw, $this->secret);
    }

    /**
     * Create a key with given parameteres.
     *
     * @param string $type
     * @param string $typeId
     * @param string $formId
     * @param string $formName
     *
     * @return string
     */
    private function createKey($type, $typeId, $formId, $formName)
    {
        return $type . $typeId . $formId . $formName;
    }

    /**
     * Create a checksum and encode with secret and given parameters.
     *
     * @param string $type
     * @param string $typeId
     * @param string $formId
     * @param string $formName
     *
     * @return string
     */
    public function get($type, $typeId, $formId, $formName)
    {
        $checksumRaw = $this->createKey($type, $typeId, $formId, $formName);

        return $this->encoder->encodePassword($checksumRaw, $this->secret);
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Manager;

use Doctrine\ORM\EntityManagerInterface;
use Sulu\Bundle\FormBundle\Entity\Form;
use Sulu\Bundle\FormBundle\Entity\FormField;
use Sulu\Bundle\FormBundle\Entity\FormTranslation;
use Sulu\Bundle\FormBundle\Entity\FormTranslationReceiver;
use Sulu\Bundle\FormBundle\Repository\FormRepository;

/**
 * Generated by https://github.com/alexander-schranz/sulu-backend-bundle.
 */
class FormManager
{
    /**
     * @var EntityManagerInterface
     */
    protected $entityManager;

    /**
     * @var FormRepository
     */
    protected $formRepository;

    /**
     * EventManager constructor.
     *
     * @param EntityManagerInterface $entityManager
     * @param FormRepository $formRepository
     */
    public function __construct(
        EntityManagerInterface $entityManager,
        FormRepository $formRepository
    ) {
        $this->entityManager = $entityManager;
        $this->formRepository = $formRepository;
    }

    /**
     * @param int $id
     * @param string $locale
     *
     * @return Form
     */
    public function findById($id, $locale = null)
    {
        return $this->formRepository->loadById($id, $locale);
    }

    /**
     * @param string $locale
     * @param array $filters
     *
     * @return Form[]
     */
    public function findAll($locale = null, $filters = [])
    {
        return $this->formRepository->loadAll($locale, $filters);
    }

    /**
     * @param string $locale
     * @param array $filters
     *
     * @return int
     */
    public function count($locale = null, $filters = [])
    {
        return $this->formRepository->countByFilters($locale, $filters);
    }

    /**
     * @param array $data
     * @param string $locale
     * @param int $id
     *
     * @return Form|null
     */
    public function save($data, $locale = null, $id = null)
    {
        $form = new Form();

        // Find exist or create new entity.
        if ($id) {
            $form = $this->findById($id, $locale);

            if (!$form) {
                return null;
            }
        }

        // Translation
        $translation = $form->getTranslation($locale, true);
        $translation->setTitle(self::getValue($data, 'title'));
        $translation->setSubject(self::getValue($data, 'subject'));
        $translation->setFromEmail(self::getValue($data, 'fromEmail'));
        $translation->setFromName(self::getValue($data, 'fromName'));
        $translation->setToEmail(self::getValue($data, 'toEmail'));
        $translation->setToName(self::getValue($data, 'toName'));
        $translation->setMailText(self::getValue($data, 'mailText'));
        $translation->setSubmitLabel(self::getValue($data, 'submitLabel'));
        $translation->setSuccessText(self::getValue($data, 'successText'));
        $translation->setSendAttachments(self::getValue($data, 'sendAttachments', false));
        $translation->setDeactivateNotifyMails(self::getValue($data, 'deactivateNotifyMails', false));
        $translation->setDeactivateCustomerMails(self::getValue($data, 'deactivateCustomerMails', false));
        $translation->setReplyTo(self::getValue($data, 'replyTo', false));
        $translation->setChanged(new \DateTime());

        // Add Translation to Form.
        if (!$translation->getId()) {
            $translation->setForm($form);
            $form->addTranslation($translation);
        }

        // Set Default Locale.
        if (!$form->getId()) {
            $form->setDefaultLocale($locale);
        }

        // Update field of form and the receivers.
        $this->updateFields($data, $form, $locale);
        $this->updateReceivers($data, $translation);

        $this->entityManager->persist($form);
        $this->entityManager->flush();

        if (!$id) {
            // To avoid lazy load of sub entities in the serializer reload whole object with sub entities from db
            // remove this when you don`t join anything in `findById`.
            $form = $this->findById($form->getId(), $locale);
        }

        return $form;
    }

    /**
     * @param int $id
     * @param string $locale
     *
     * @return Form|null
     */
    public function delete($id, $locale = null)
    {
        $object = $this->findById($id, $locale);

        if (!$object) {
            return;
        }

        $this->entityManager->remove($object);
        $this->entityManager->flush();

        return $object;
    }

    /**
     * @param array $data
     * @param FormTranslation $translation
     */
    public function updateReceivers($data, $translation)
    {
        $translation->setReceivers([]);
        $receiversRepository = $this->entityManager->getRepository('SuluFormBundle:FormTranslationReceiver');
        $receiverDatas = self::getValue($data, 'receivers', []);

        // Remove old receivers.
        $oldReceivers = $receiversRepository->findBy(['formTranslation' => $translation]);
        foreach ($oldReceivers as $oldReceiver) {
            $this->entityManager->remove($oldReceiver);
        }

        $receivers = [];
        foreach ($receiverDatas as $receiverData) {
            $receiver = new FormTranslationReceiver();
            $receiver->setType($receiverData['type']);
            $receiver->setEmail($receiverData['email']);
            $receiver->setName($receiverData['name']);
            $receiver->setFormTranslation($translation);

            $receivers[] = $receiver;
            $this->entityManager->persist($receiver);
        }
        $translation->setReceivers($receivers);
    }

    /**
     * Updates the contained fields in the form.
     *
     * @param array $data
     * @param Form $form
     * @param string $locale
     */
    protected function updateFields($data, $form, $locale)
    {
        $reservedKeys = array_column(self::getValue($data, 'fields', []), 'key');

        $counter = 0;

        foreach (self::getValue($data, 'fields', []) as $fieldData) {
            ++$counter;
            $fieldType = self::getValue($fieldData, 'type');
            $fieldKey = self::getValue($fieldData, 'key');
            $field = $form->getField($fieldKey);
            $uniqueKey = $this->getUniqueKey($fieldType, $reservedKeys);

            if (!$field) {
                $field = $form->getField($uniqueKey);
            }

            if (!$field) {
                $field = new FormField();
                $field->setKey($uniqueKey);
                $reservedKeys[] = $uniqueKey;
            } elseif ($field->getType() !== $fieldType || !$field->getKey()) {
                $field->setKey($uniqueKey);
                $reservedKeys[] = $uniqueKey;
            }

            $field->setOrder($counter);
            $field->setType($fieldType);
            $field->setWidth(self::getValue($fieldData, 'width', 'full'));
            $field->setRequired(self::getValue($fieldData, 'required', false));

            // Field Translation
            $fieldTranslation = $field->getTranslation($locale, true);
            $fieldTranslation->setTitle(self::getValue($fieldData, 'title'));
            $fieldTranslation->setPlaceholder(self::getValue($fieldData, 'placeholder'));
            $fieldTranslation->setDefaultValue(self::getValue($fieldData, 'defaultValue'));
            $fieldTranslation->setShortTitle(self::getValue($fieldData, 'shortTitle'));

            // Field Options
            $prefix = 'options[';

            $keys = array_filter(array_keys($fieldData), function ($key) use ($prefix) {
                return 0 === strpos($key, $prefix);
            });

            $options = array_intersect_key($fieldData, array_flip($keys));

            foreach ($options as $key => $value) {
                unset($options[$key]);
                $options[trim(substr($key, strlen($prefix)), ']')] = $value;
            }

            $fieldTranslation->setOptions($options);

            // Add Translation to Field
            if (!$fieldTranslation->getId()) {
                $fieldTranslation->setField($field);
                $field->addTranslation($fieldTranslation);
            }

            // Add Field to Form
            if (!$field->getId()) {
                $field->setDefaultLocale($locale);
                $field->setForm($form);
                $form->addField($field);
            }
        }

        // Remove Fields
        foreach ($form->getFieldsNotInArray($reservedKeys) as $deletedField) {
            $form->removeField($deletedField);
            $this->entityManager->remove($deletedField);
        }
    }

    /**
     * @param array $data
     * @param string $value
     * @param mixed $default
     * @param string $type
     *
     * @return mixed
     */
    protected static function getValue($data, $value, $default = null, $type = null)
    {
        if (isset($data[$value])) {
            if ('date' === $type) {
                if (!$data[$value]) {
                    return $default;
                }

                return new \DateTime($data[$value]);
            }

            return $data[$value];
        }

        return $default;
    }

    /**
     * @param string $type
     * @param array $keys
     * @param int $counter
     *
     * @return string
     */
    protected function getUniqueKey($type, $keys, $counter = 0)
    {
        $name = $type;

        if ($counter) {
            $name .= $counter;
        }

        if (!in_array($name, $keys)) {
            return $name;
        }

        return $this->getUniqueKey($type, $keys, ++$counter);
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Controller;

use Doctrine\ORM\EntityManagerInterface;
use FOS\RestBundle\Routing\ClassResourceInterface;
use Sulu\Bundle\FormBundle\Entity\Dynamic;
use Sulu\Bundle\FormBundle\Entity\Form;
use Sulu\Bundle\FormBundle\Repository\DynamicRepository;
use Sulu\Bundle\MediaBundle\Media\Exception\MediaNotFoundException;
use Sulu\Component\Rest\ListBuilder\ListRepresentation;
use Sulu\Component\Rest\RestController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;

/**
 * Controller to create dynamic form entries list.
 */
class DynamicController extends RestController implements ClassResourceInterface
{
    /**
     * Return dynamic form entries.
     *
     * @param Request $request
     *
     * @return Response
     */
    public function cgetAction(Request $request)
    {
        /** @var DynamicRepository $repository */
        $repository = $this->get('sulu_form.repository.dynamic');

        $filters = $this->getFilters($request);
        $page = $request->get('page', 1);
        $limit = $request->get('limit');
        $offset = (($page - 1) * $limit);
        $view = $request->get('view', 'default');
        $sortOrder = $request->get('sortOrder', 'asc');
        $sortBy = $request->get('sortBy', 'created');

        $entries = $repository->findByFilters(
            $filters,
            [$sortBy => $sortOrder],
            $limit,
            $offset
        );

        $entries = $this->get('sulu_form.list_builder.dynamic_list_factory')->build($entries, $view);

        // avoid total request when entries < limit
        if (count($entries) == $limit) {
            $total = $repository->countByFilters($filters);
        } else {
            // calculate total
            $total = count($entries) + $offset;
        }

        // create list representation
        $representation = new ListRepresentation(
            $entries,
            'dynamics',
            $request->get('_route'),
            $request->query->all(),
            $page,
            $limit,
            $total
        );

        return $this->handleView($this->view($representation));
    }

    /**
     * Returns the fields for a dynamic form.
     *
     * @param Request $request
     *
     * @return Response
     */
    public function cgetFieldsAction(Request $request)
    {
        $form = $this->loadForm($request);
        $fieldDescriptors = [];

        if ($form) {
            $locale = $this->getLocale($request);
            $fieldDescriptors = $this->get('sulu_form.list_builder.dynamic_list_factory')
                ->getFieldDescriptors($form, $locale);
        }

        return $this->handleView($this->view(array_values($fieldDescriptors)));
    }

    /**
     * Delete dynamic form entry.
     *
     * @param Request $request
     *
     * @return Response
     */
    public function deleteAction(Request $request, $id)
    {
        $mediaManager = $this->get('sulu_media.media_manager');

        /** @var EntityManagerInterface $entityManager */
        $entityManager = $this->get('doctrine.orm.entity_manager');

        /** @var DynamicRepository $repository */
        $repository = $this->get('sulu_form.repository.dynamic');

        /** @var Dynamic $dynamic */
        $dynamic = $repository->find($id);

        $attachments = array_values($dynamic->getFieldsByType(Dynamic::TYPE_ATTACHMENT));

        foreach ($attachments as $mediaIds) {
            foreach ($mediaIds as $mediaId) {
                if ($mediaId) {
                    try {
                        $mediaManager->delete($mediaId);
                    } catch (MediaNotFoundException $e) {
                        // Do nothing when meida was removed before.
                    }
                }
            }
        }
        $entityManager->remove($dynamic);
        $entityManager->flush();

        return new Response('', 204);
    }

    /**
     * Get filters.
     *
     * @param Request $request
     *
     * @return array
     */
    protected function getFilters(Request $request)
    {
        $filters = [
            'type' => $request->get('type'),
            'typeId' => $request->get('typeId'),
            'webspaceKey' => $request->get('webspaceKey'),
            'form' => $request->get('form'),
            'fromDate' => $request->get('fromDate'),
            'toDate' => $request->get('toDate'),
            'search' => $request->get('search'),
            'searchFields' => array_filter(explode(',', $request->get('searchFields', ''))),
        ];

        return array_filter($filters);
    }

    /**
     * Get form.
     *
     * @param Request $request
     *
     * @return Form
     */
    protected function loadForm(Request $request)
    {
        $formId = (int) $request->get('form');

        if (!$formId) {
            throw new BadRequestHttpException('"form" is required parameter');
        }

        return $this->get('sulu_form.repository.form')->loadById($formId);
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Admin;

use Sulu\Bundle\AdminBundle\Navigation\ContentNavigationItem;
use Sulu\Bundle\AdminBundle\Navigation\ContentNavigationProviderInterface;
use Sulu\Bundle\AdminBundle\Navigation\DisplayCondition;
use Sulu\Bundle\FormBundle\Provider\ListProviderRegistry;

class TemplateNavigationProvider implements ContentNavigationProviderInterface
{
    /**
     * @var ListProviderRegistry
     */
    protected $listProviderRegistry;

    /**
     * TemplateNavigationProvider constructor.
     *
     * @param ListProviderRegistry $listProviderRegistry
     */
    public function __construct(
        ListProviderRegistry $listProviderRegistry
    ) {
        $this->listProviderRegistry = $listProviderRegistry;
    }

    /**
     * @param array $options
     *
     * @return array
     */
    public function getNavigationItems(array $options = [])
    {
        $items = [];

        foreach ($this->listProviderRegistry->getProviders() as $name => $provider) {
            $item = new ContentNavigationItem('Formular');
            $item->setAction('form-list');
            $item->setDisplay(['edit']);
            $item->setComponent('content/list@suluform');
            $item->setComponentOptions([
                'template' => $name,
            ]);

            $item->setDisplayConditions(
                [
                    new DisplayCondition('template', DisplayCondition::OPERATOR_EQUAL, $name),
                ]
            );

            $items[] = $item;
        }

        return $items;
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\ListBuilder;

use Sulu\Bundle\FormBundle\Entity\Dynamic;
use Symfony\Component\Routing\RouterInterface;

/**
 * Dynamic list builder.
 */
class DynamicListBuilder implements DynamicListBuilderInterface
{
    /**
     * @var string
     */
    protected $delimiter;

    /**
     * @var RouterInterface
     */
    protected $router;

    /**
     * @var string
     */
    protected $downloadUrl;

    /**
     * DynamicListResolver constructor.
     *
     * @param string $delimiter
     * @param RouterInterface $router
     */
    public function __construct($delimiter, $router)
    {
        $this->delimiter = $delimiter;
        $this->router = $router;
    }

    /**
     * @param Dynamic $dynamic
     * @param $locale
     *
     * @return array
     */
    public function build(Dynamic $dynamic, $locale)
    {
        $entry = $dynamic->getFields();

        $singleEntry = [
            'id' => $dynamic->getId(),
        ];

        foreach ($entry as $key => $value) {
            if (Dynamic::TYPE_ATTACHMENT === $dynamic->getFieldType($key)) {
                $singleEntry[$key] = $this->getMediaUrls($value);
            } else {
                $singleEntry[$key] = $this->toString($value);
            }
        }

        $singleEntry['created'] = $dynamic->getCreated()->format('c');

        return [$singleEntry];
    }

    /**
     * Convert value to string.
     *
     * @param string|array $value
     *
     * @return string
     *
     * @throws \Exception
     */
    protected function toString($value)
    {
        if (is_string($value) || is_numeric($value)) {
            return $value;
        }

        if (is_bool($value)) {
            return $value ? 1 : 0;
        }

        if ($value instanceof \DateTime) {
            return $value->format('c');
        }

        if (!$value) {
            return '';
        }

        if (!is_array($value)) {
            throw new \Exception('Invalid value for list builder.');
        }

        return implode($this->delimiter, $value);
    }

    /**
     * Get media urls.
     *
     * @param string $value
     *
     * @return string
     */
    protected function getMediaUrls($value)
    {
        if (is_string($value)) {
            return $this->getMediaUrl($value);
        }

        if (is_array($value)) {
            foreach ($value as $key => $mediaId) {
                $value[$key] = $this->getMediaUrl($mediaId);
            }

            return implode($this->delimiter, $value);
        }

        return $this->toString($value);
    }

    /**
     * Get media url.
     *
     * @param string $value
     *
     * @return string
     */
    protected function getMediaUrl($value)
    {
        return str_replace(urlencode('{id}'), $value, $this->getDownloadUrl());
    }

    /**
     * For performance generate route only once.
     *
     * @return string
     */
    protected function getDownloadUrl()
    {
        if (null === $this->downloadUrl) {
            $this->downloadUrl = $this->router->generate(
                'sulu_media.website.media.download',
                [
                    'slug' => 'file',
                    'id' => '{id}',
                ],
                true
            );
        }

        return $this->downloadUrl;
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Entity;

/**
 * Form field translation entity.
 */
class FormFieldTranslation
{
    /**
     * @var string
     */
    private $title;

    /**
     * @var string
     */
    private $locale;

    /**
     * @var int
     */
    private $id;

    /**
     * @var FormField
     */
    private $field;

    /**
     * @var string
     */
    private $placeholder;

    /**
     * @var string
     */
    private $defaultValue;

    /**
     * @var string
     */
    private $shortTitle;

    /**
     * @var string
     */
    private $options;

    /**
     * @param string $title
     *
     * @return FormFieldTranslation
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * @param string $locale
     *
     * @return FormFieldTranslation
     */
    public function setLocale($locale)
    {
        $this->locale = $locale;

        return $this;
    }

    /**
     * @return string
     */
    public function getLocale()
    {
        return $this->locale;
    }

    /**
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param FormField $field
     *
     * @return FormFieldTranslation
     */
    public function setField(FormField $field)
    {
        $this->field = $field;

        return $this;
    }

    /**
     * @return FormField
     */
    public function getField()
    {
        return $this->field;
    }

    /**
     * @return string|bool
     */
    public function getPlaceholder()
    {
        return $this->placeholder;
    }

    /**
     * @param string $placeholder
     *
     * @return $this
     */
    public function setPlaceholder($placeholder)
    {
        $this->placeholder = $placeholder;

        return $this;
    }

    /**
     * @return string
     */
    public function getDefaultValue()
    {
        return $this->defaultValue;
    }

    /**
     * @param string $defaultValue
     *
     * @return $this
     */
    public function setDefaultValue($defaultValue)
    {
        $this->defaultValue = $defaultValue;

        return $this;
    }

    /**
     * @return string
     */
    public function getShortTitle()
    {
        return $this->shortTitle;
    }

    /**
     * @param string $shortTitle
     *
     * @return FormFieldTranslation
     */
    public function setShortTitle($shortTitle)
    {
        $this->shortTitle = $shortTitle;

        return $this;
    }

    /**
     * @return array
     */
    public function getOptions()
    {
        if (!$this->options) {
            return [];
        }

        return json_decode($this->options, true);
    }

    /**
     * @param array $options
     *
     * @return $this
     */
    public function setOptions($options)
    {
        if (is_array($options)) {
            $options = json_encode($options);
        }

        $this->options = $options;

        return $this;
    }

    /**
     * @param string $key
     *
     * @return string
     */
    public function getOption($key)
    {
        if (isset($this->getOptions()[$key])) {
            return $this->getOptions()[$key];
        }
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Content\Types;

use Sulu\Bundle\FormBundle\Form\BuilderInterface;
use Sulu\Bundle\FormBundle\Repository\FormRepository;
use Sulu\Component\Content\Compat\PropertyInterface;
use Sulu\Component\Content\Compat\Structure\PageBridge;
use Sulu\Component\Content\Compat\Structure\StructureBridge;
use Sulu\Component\Content\SimpleContentType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Validator\Exception\MissingOptionsException;

/**
 * ContentType for selecting a form.
 */
class FormSelect extends SimpleContentType
{
    /**
     * @var string
     */
    private $template;

    /**
     * @var FormRepository
     */
    private $formRepository;

    /**
     * @var BuilderInterface
     */
    private $formBuilder;

    /**
     * FormSelect constructor.
     *
     * @param string $template
     * @param FormRepository $formRepository
     * @param BuilderInterface $formBuilder
     */
    public function __construct(
        $template,
        FormRepository $formRepository,
        BuilderInterface $formBuilder
    ) {
        parent::__construct('FormSelect', '');
        $this->template = $template;
        $this->formRepository = $formRepository;
        $this->formBuilder = $formBuilder;
    }

    /**
     * {@inheritdoc}
     */
    public function getContentData(PropertyInterface $property)
    {
        $id = (int) $property->getValue();

        if (!$id) {
            return;
        }

        if (!isset($property->getParams()['type'])) {
            throw new MissingOptionsException(
                'SuluFormBundle: The parameter "type" is missing on "form_select" content-type.',
                []
            );
        }

        $type = $property->getParams()['type']->getValue();

        /** @var PageBridge $structure */
        $structure = $property->getStructure();

        /** @var FormInterface $form */
        $form = $this->formBuilder->build(
            $id,
            $type,
            $structure->getUuid(),
            $structure->getLanguageCode(),
            $property->getName()
        );

        if (!$form) {
            $form = $this->loadShadowForm($property, $id, $type);

            if (!$form) {
                return;
            }
        }

        return $form->createView();
    }

    private function loadShadowForm(PropertyInterface $property, $id, $type)
    {
        $structure = $property->getStructure();

        if (!$structure instanceof StructureBridge) {
            return;
        }

        if (!$structure->getIsShadow()) {
            return;
        }

        return $this->formBuilder->build(
            $id,
            $type,
            $structure->getUuid(),
            $structure->getShadowBaseLanguage(),
            $property->getName()
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getViewData(PropertyInterface $property)
    {
        $id = (int) $property->getValue();

        if (!$id) {
            return [];
        }

        $locale = $property->getStructure()->getLanguageCode();

        $formEntity = $this->formRepository->loadById($id, $locale);

        if (!$formEntity) {
            return [];
        }

        return [
            'entity' => $formEntity->serializeForLocale($locale),
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getTemplate()
    {
        return $this->template;
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Dynamic\Types;

use Sulu\Bundle\FormBundle\Entity\FormFieldTranslation;

/**
 * Trait for handling multi choice form types.
 */
trait ChoiceTrait
{
    /**
     * Get choices.
     *
     * @param FormFieldTranslation $translation
     *
     * @return array
     */
    protected function getChoices(FormFieldTranslation $translation)
    {
        $choices = preg_split('/\r\n|\r|\n/', $translation->getOption('choices'), -1, PREG_SPLIT_NO_EMPTY);

        return array_combine($choices, $choices);
    }

    /**
     * Returns options for multichoice form type like select, multiple select, radio or checkboxes.
     *
     * @param FormFieldTranslation $translation
     * @param array $options
     *
     * @return array
     */
    private function getChoiceOptions(
        FormFieldTranslation $translation,
        $options
    ) {
        if (isset($options['attr']['placeholder'])) {
            $options['placeholder'] = $options['attr']['placeholder'];
            unset($options['attr']['placeholder']);
        }

        // Choices.
        $choices = $this->getChoices($translation);
        $options['choices_as_values'] = true;
        $options['choices'] = $choices;

        return $options;
    }

    /**
     * Returns default options for multichoice form type.
     *
     * @param string $value
     *
     * @return string[]
     */
    private function getDefaultOptions($value)
    {
        return preg_split('/\r\n|\r|\n/', $value, -1, PREG_SPLIT_NO_EMPTY);
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Controller;

use FOS\RestBundle\Routing\ClassResourceInterface;
use Sulu\Component\Rest\ListBuilder\Doctrine\DoctrineListBuilderFactory;
use Sulu\Component\Rest\ListBuilder\ListRepresentation;
use Sulu\Component\Rest\RestController;
use Sulu\Component\Rest\RestHelperInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class ListController extends RestController implements ClassResourceInterface
{
    /**
     * @param Request $request
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function cgetFieldsAction(Request $request)
    {
        $template = $request->get('template');
        $locale = $request->get('locale');
        $webspace = $request->get('webspace');
        $uuid = $request->get('uuid');

        if (!$template) {
            throw new NotFoundHttpException('"template" is required parameter!');
        }

        $fieldDescriptors = $this->getProviderRegistry()->getFieldDescriptors($template, $webspace, $locale, $uuid);

        return $this->handleView($this->view(array_values($fieldDescriptors)));
    }

    /**
     * @param Request $request
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function cgetAction(Request $request)
    {
        $template = $request->get('template');
        $webspace = $request->get('webspace');
        $locale = $request->get('locale');
        $uuid = $request->get('uuid');

        if (!$template) {
            throw new NotFoundHttpException('"template" is required parameter');
        }

        $fieldDescriptors = $this->getProviderRegistry()->getFieldDescriptors($template, $webspace, $locale, $uuid);
        $entityName = $this->getProviderRegistry()->getEntityName($template, $webspace, $locale, $uuid);

        /** @var RestHelperInterface $restHelper */
        $restHelper = $this->get('sulu_core.doctrine_rest_helper');

        /** @var DoctrineListBuilderFactory $factory */
        $factory = $this->get('sulu_core.doctrine_list_builder_factory');

        // get model class
        $listBuilder = $factory->create($entityName);

        // add filters
        if (isset($fieldDescriptors['uuid'])) {
            $listBuilder->where($fieldDescriptors['uuid'], $uuid);
        }
        if (isset($fieldDescriptors['webspaceKey'])) {
            $listBuilder->where($fieldDescriptors['webspaceKey'], $webspace);
        }
        if (isset($fieldDescriptors['template'])) {
            $listBuilder->where($fieldDescriptors['template'], $template);
        }

        // Init List Builder
        $restHelper->initializeListBuilder($listBuilder, $fieldDescriptors);

        // load entities
        $list = $listBuilder->execute();

        // get pagination
        $total = $listBuilder->count();
        $page = $listBuilder->getCurrentPage();
        $limit = $listBuilder->getLimit();

        // create list representation
        $representation = new ListRepresentation(
            $list,
            'entries',
            $request->get('_route'),
            $request->query->all(),
            $page,
            $limit,
            $total
        );

        return $this->handleView($this->view($representation));
    }

    /**
     * @return \Sulu\Bundle\FormBundle\Provider\ListProviderRegistry
     */
    protected function getProviderRegistry()
    {
        return $this->get('sulu.list.provider.registry');
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Dynamic\Types;

use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeConfiguration;
use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeInterface;
use Sulu\Bundle\FormBundle\Entity\FormField;
use Symfony\Component\Form\FormBuilderInterface;

/**
 * The Recaptcha form field type.
 */
class RecaptchaType implements FormFieldTypeInterface
{
    use SimpleTypeTrait;

    /**
     * {@inheritdoc}
     */
    public function getConfiguration()
    {
        return new FormFieldTypeConfiguration(
            'sulu_form.type.recaptcha',
            'SuluFormBundle:forms:fields/types/recaptcha.html.twig',
            [],
            'special'
        );
    }

    /**
     * {@inheritdoc}
     */
    public function build(FormBuilderInterface $builder, FormField $field, $locale, $options)
    {
        // Use in this way the recaptcha bundle could maybe not exists.
        $options['mapped'] = false;
        $options['constraints'][] = new \EWZ\Bundle\RecaptchaBundle\Validator\Constraints\IsTrue();
        $options['attr']['options'] = [
            'theme' => 'light',
            'type' => 'image',
            'size' => 'normal',
        ];
        $builder->add($field->getKey(), \EWZ\Bundle\RecaptchaBundle\Form\Type\EWZRecaptchaType::class, $options);
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Repository;

use Doctrine\ORM\NoResultException;
use Sulu\Bundle\FormBundle\Entity\Form;

class FormRepository extends \Doctrine\ORM\EntityRepository
{
    /**
     * @param int $id
     * @param string $locale
     *
     * @return Form|null
     */
    public function loadById($id, $locale = null)
    {
        $queryBuilder = $this->createQueryBuilder('form')
            ->leftJoin('form.translations', 'translation')->addSelect('translation')
            ->leftJoin('form.fields', 'field')->addSelect('field')
            ->leftJoin('field.translations', 'fieldTranslation')->addSelect('fieldTranslation')
            ->leftJoin('translation.receivers', 'receiver')->addSelect('receiver');

        $queryBuilder->where($queryBuilder->expr()->eq('form.id', ':id'));
        $queryBuilder->setParameter('id', $id);
        $queryBuilder->orderBy('field.order');
        $query = $queryBuilder->getQuery();

        try {
            return $query->getSingleResult();
        } catch (NoResultException $e) {
            return null;
        }
    }

    /**
     * @param string $locale
     * @param array $filters
     *
     * @return Form[]
     */
    public function loadAll($locale = null, $filters = [])
    {
        $queryBuilder = $this->createQueryBuilder('form')
            ->leftJoin('form.translations', 'translation')->addSelect('translation')
            ->leftJoin('form.fields', 'field')->addSelect('field')
            ->leftJoin('field.translations', 'fieldTranslation')->addSelect('fieldTranslation');

        $queryBuilder->setMaxResults(self::getValue($filters, 'limit'))
            ->setFirstResult(self::getValue($filters, 'offset'));

        $queryBuilder->orderBy('form.id');
        $queryBuilder->addOrderBy('field.order');
        $query = $queryBuilder->getQuery();

        return $query->getResult();
    }

    /**
     * @param string $locale
     * @param array $filters
     *
     * @return int
     */
    public function countByFilters($locale = null, $filters = [])
    {
        $queryBuilder = $this->createQueryBuilder('form');
        $queryBuilder->select($queryBuilder->expr()->count('form.id'));

        return (int) $queryBuilder->getQuery()->getSingleScalarResult();
    }

    /**
     * @param array $data
     * @param string $key
     * @param mixed $default
     *
     * @return int|null
     */
    protected static function getValue($data, $key, $default = null)
    {
        if (isset($data[$key])) {
            return $data[$key];
        }

        return $default;
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Dynamic\Types;

use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeConfiguration;
use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeInterface;
use Sulu\Bundle\FormBundle\Entity\FormField;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType as TypeCheckboxType;
use Symfony\Component\Form\FormBuilderInterface;

/**
 * The Checkbox form field type.
 */
class CheckboxType implements FormFieldTypeInterface
{
    use SimpleTypeTrait;

    /**
     * {@inheritdoc}
     */
    public function getConfiguration()
    {
        return new FormFieldTypeConfiguration(
            'sulu_form.type.checkbox',
            'SuluFormBundle:forms:fields/types/checkbox.html.twig',
            [],
            'complex'
        );
    }

    /**
     * {@inheritdoc}
     */
    public function build(FormBuilderInterface $builder, FormField $field, $locale, $options)
    {
        $type = TypeCheckboxType::class;
        $builder->add($field->getKey(), $type, $options);
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Twig;

use Sulu\Bundle\FormBundle\Form\BuilderInterface;
use Symfony\Component\Form\FormView;

/**
 * Extension for content form generation.
 */
class FormTwigExtension extends \Twig_Extension
{
    /**
     * @var BuilderInterface
     */
    private $formBuilder;

    /**
     * FormTwigExtension constructor.
     *
     * @param BuilderInterface $formBuilder
     */
    public function __construct(BuilderInterface $formBuilder)
    {
        $this->formBuilder = $formBuilder;
    }

    /**
     * Returns an array of possible function in this extension.
     *
     * @return array
     */
    public function getFunctions()
    {
        return [
            new \Twig_SimpleFunction('sulu_form_get_by_id', [$this, 'getFormById']),
        ];
    }

    /**
     * Returns FormView by given params.
     *
     * @param int $id
     * @param string $type
     * @param string $typeId
     * @param mixed $locale
     * @param string $name
     *
     * @return FormView
     */
    public function getFormById($id, $type, $typeId, $locale = null, $name = 'form')
    {
        $form = $this->formBuilder->build((int) $id, $type, $typeId, $locale, $name);

        if (!$form) {
            return;
        }

        return $form->createView();
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Provider;

use Sulu\Component\Rest\ListBuilder\FieldDescriptor;

class ListProviderRegistry
{
    /**
     * @var ListProviderInterface[]
     */
    protected $providers = [];

    /**
     * @param ListProviderInterface $provider
     * @param $name
     */
    public function add(ListProviderInterface $provider, $name)
    {
        $this->providers[$name] = $provider;
    }

    /**
     * @param string $template
     * @param string $webspace
     * @param string $locale
     * @param string $uuid
     *
     * @return FieldDescriptor[]
     */
    public function getFieldDescriptors($template, $webspace, $locale, $uuid)
    {
        $provider = $this->getProvider($template);

        return $provider->getFieldDescriptors($webspace, $locale, $uuid);
    }

    /**
     * @param string $template
     * @param string $webspace
     * @param string $locale
     * @param string $uuid
     *
     * @return string
     */
    public function getEntityName($template, $webspace, $locale, $uuid)
    {
        $provider = $this->getProvider($template);

        return $provider->getEntityName($webspace, $locale, $uuid);
    }

    /**
     * @return ListProviderInterface[]
     */
    public function getProviders()
    {
        return $this->providers;
    }

    /**
     * @param $template
     *
     * @return ListProviderInterface
     */
    protected function getProvider($template)
    {
        return $this->providers[$template];
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Dynamic\Types;

use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeConfiguration;
use Sulu\Bundle\FormBundle\Dynamic\FormFieldTypeInterface;
use Sulu\Bundle\FormBundle\Entity\FormField;
use Symfony\Component\Form\Extension\Core\Type\CountryType as TypeCountryType;
use Symfony\Component\Form\FormBuilderInterface;

/**
 * The Country form field type.
 */
class CountryType implements FormFieldTypeInterface
{
    use SimpleTypeTrait;

    /**
     * {@inheritdoc}
     */
    public function getConfiguration()
    {
        return new FormFieldTypeConfiguration(
            'sulu_form.type.country',
            'SuluFormBundle:forms:fields/types/country.html.twig'
        );
    }

    /**
     * {@inheritdoc}
     */
    public function build(FormBuilderInterface $builder, FormField $field, $locale, $options = [])
    {
        if (isset($options['attr']['placeholder'])) {
            $options['placeholder'] = $options['attr']['placeholder'];
            unset($options['attr']['placeholder']);
        }

        $type = TypeCountryType::class;
        $builder->add($field->getKey(), $type, $options);
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Event;

use Sulu\Bundle\FormBundle\Configuration\FormConfigurationFactory;
use Sulu\Bundle\FormBundle\Entity\Dynamic;
use Sulu\Bundle\FormBundle\Form\BuilderInterface;
use Sulu\Bundle\FormBundle\Form\HandlerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;

class RequestListener
{
    /**
     * @var BuilderInterface
     */
    protected $formBuilder;

    /**
     * @var HandlerInterface
     */
    protected $formHandler;

    /**
     * @var FormConfigurationFactory
     */
    protected $formConfigurationFactory;

    /**
     * @var EventDispatcherInterface
     */
    protected $eventDispatcher;

    /**
     * RequestListener constructor.
     *
     * @param BuilderInterface $formBuilder
     * @param HandlerInterface $formHandler
     * @param EventDispatcherInterface $eventDispatcher
     */
    public function __construct(
        BuilderInterface $formBuilder,
        HandlerInterface $formHandler,
        FormConfigurationFactory $formConfigurationFactory,
        EventDispatcherInterface $eventDispatcher
    ) {
        $this->formBuilder = $formBuilder;
        $this->formHandler = $formHandler;
        $this->formConfigurationFactory = $formConfigurationFactory;
        $this->eventDispatcher = $eventDispatcher;
    }

    /**
     * On Kernel request.
     *
     * @param GetResponseEvent $event
     */
    public function onKernelRequest(GetResponseEvent $event)
    {
        if (!$event->isMasterRequest()) {
            // do nothing if it's not the master request
            return;
        }

        $request = $event->getRequest();

        if (!$request->isMethod('post')) {
            // do nothing if it's not a post request
            return;
        }

        try {
            /** @var FormInterface $form */
            $form = $this->formBuilder->buildByRequest($request);

            if (!$form || !$form->isSubmitted() || !$form->isValid()) {
                // do nothing when no form was found or not valid
                return;
            }
        } catch (\Exception $e) {
            // Catch all exception on build form by request
            return;
        }

        /** @var Dynamic $dynamic */
        $dynamic = $form->getData();
        $configuration = $this->formConfigurationFactory->buildByDynamic($dynamic);
        $dynamic->setLocale($request->getLocale()); // Need to be set to request locale for shadow pages, configuraiton will hold the original locale

        if ($this->formHandler->handle($form, $configuration)) {
            $serializedObject = $dynamic->getForm()->serializeForLocale($dynamic->getLocale(), $dynamic);
            $dynFormSavedEvent = new DynFormSavedEvent($serializedObject, $dynamic);
            $this->eventDispatcher->dispatch(DynFormSavedEvent::NAME, $dynFormSavedEvent);

            $response = new RedirectResponse('?send=true');
            $event->setResponse($response);
        }
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;

/**
 * Form entity.
 */
class Form
{
    /**
     * @var int
     */
    private $id;

    /**
     * @var string
     */
    private $defaultLocale;

    /**
     * @var \Doctrine\Common\Collections\Collection|FormTranslation[]
     */
    private $translations;

    /**
     * @var \Doctrine\Common\Collections\Collection|FormField[]
     */
    private $fields;

    /**
     * Constructor.
     */
    public function __construct()
    {
        $this->translations = new ArrayCollection();
        $this->fields = new ArrayCollection();
    }

    /**
     * Get id.
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Get default locale.
     *
     * @return string
     */
    public function getDefaultLocale()
    {
        return $this->defaultLocale;
    }

    /**
     * Set default locale.
     *
     * @param $defaultLocale
     *
     * @return $this
     */
    public function setDefaultLocale($defaultLocale)
    {
        $this->defaultLocale = $defaultLocale;

        return $this;
    }

    /**
     * Add translation.
     *
     * @param FormTranslation $translation
     *
     * @return $this
     */
    public function addTranslation(FormTranslation $translation)
    {
        $this->translations[] = $translation;

        return $this;
    }

    /**
     * Remove translation.
     *
     * @param FormTranslation $translation
     *
     * @return $this
     */
    public function removeTranslation(FormTranslation $translation)
    {
        $this->translations->removeElement($translation);

        return $this;
    }

    /**
     * Get translations.
     *
     * @return \Doctrine\Common\Collections\Collection|FormTranslation[]
     */
    public function getTranslations()
    {
        return $this->translations;
    }

    /**
     * Get translation.
     *
     * @param string $locale
     * @param bool $create
     * @param bool $fallback
     *
     * @return FormTranslation|null
     */
    public function getTranslation($locale, $create = false, $fallback = false)
    {
        foreach ($this->translations as $translation) {
            if ($translation->getLocale() == $locale) {
                return $translation;
            }
        }

        if ($create) {
            $translation = new FormTranslation();
            $translation->setLocale($locale);

            return $translation;
        }

        if ($fallback) {
            return $this->getTranslation($this->getDefaultLocale());
        }

        return;
    }

    /**
     * Add field.
     *
     * @param FormField $field
     *
     * @return Form
     */
    public function addField(FormField $field)
    {
        $this->fields[] = $field;

        return $this;
    }

    /**
     * Remove field.
     *
     * @param FormField $field
     *
     * @return $this
     */
    public function removeField(FormField $field)
    {
        $this->fields->removeElement($field);

        return $this;
    }

    /**
     * @return \Doctrine\Common\Collections\Collection|FormField[]
     */
    public function getFields()
    {
        return $this->fields;
    }

    /**
     * Get fields by type.
     *
     * @return \Doctrine\Common\Collections\Collection|FormField[]
     */
    public function getFieldsByType($type)
    {
        $fields = [];

        foreach ($this->fields as $field) {
            if ($field->getType() === $type) {
                $fields[] = $field;
            }
        }

        return $fields;
    }

    /**
     * Get field by key.
     *
     * @param string $key
     *
     * @return FormField|null
     */
    public function getField($key)
    {
        foreach ($this->fields as $field) {
            if ($field->getKey() == $key) {
                return $field;
            }
        }
    }

    /**
     * Get field type by key.
     *
     * @param string $key
     *
     * @return string|void
     */
    public function getFieldType($key)
    {
        $field = $this->getField($key);

        if (!$field) {
            return;
        }

        return $field->getType();
    }

    /**
     * Get fields not in array.
     *
     * @param $keys
     *
     * @return FormField[]
     */
    public function getFieldsNotInArray($keys)
    {
        $fields = [];

        foreach ($this->fields as $field) {
            if (!in_array($field->getKey(), $keys)) {
                $fields[] = $field;
            }
        }

        return $fields;
    }

    /**
     * Return a localized array of the object.
     *
     * @param $locale
     * @param Dynamic $dynamic
     *
     * @return array
     */
    public function serializeForLocale($locale, Dynamic $dynamic = null)
    {
        $fields = [];

        foreach ($this->fields as $field) {
            $fieldTranslation = $field->getTranslation($locale, false, true);
            $value = null;

            if ($dynamic) {
                $value = $dynamic->{$field->getKey()};
            }

            $fields[$field->getOrder()] = [
                'key' => $field->getKey(),
                'type' => $field->getType(),
                'title' => $fieldTranslation->getTitle(),
                'options' => $fieldTranslation->getOptions(),
                'defaultValue' => $fieldTranslation->getDefaultValue(),
                'placeholder' => $fieldTranslation->getPlaceholder(),
                'shortTitle' => $fieldTranslation->getShortTitle(),
                'value' => $value,
            ];

            ksort($fields);
        }

        $translation = $this->getTranslation($locale, false, true);

        return [
            'id' => $dynamic ? $dynamic->getId() : null,
            'formId' => $this->getId(),
            'title' => $translation->getTitle(),
            'subject' => $translation->getSubject(),
            'mailText' => $translation->getMailText(),
            'submitLabel' => $translation->getSubmitLabel(),
            'successText' => $translation->getSuccessText(),
            'fromEmail' => $translation->getFromEmail(),
            'fromName' => $translation->getFromName(),
            'toEmail' => $translation->getToEmail(),
            'toName' => $translation->getToName(),
            'fields' => $fields,
            'created' => $dynamic ? $dynamic->getCreated() : null,
        ];
    }
}


<?php

/*
 * This file is part of Sulu.
 *
 * (c) MASSIVE ART WebServices GmbH
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Sulu\Bundle\FormBundle\Controller;

use Sulu\Bundle\FormBundle\Form\Type\AbstractType;
use Sulu\Bundle\WebsiteBundle\Controller\DefaultController;
use Sulu\Component\Content\Compat\StructureInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

class FormWebsiteController extends DefaultController
{
    /**
     * @var FormInterface
     */
    protected $form;

    /**
     * @var array
     */
    protected $attributes;

    /**
     * Form action.
     *
     * @param StructureInterface $structure
     * @param bool $preview
     * @param bool $partial
     *
     * @return JsonResponse|RedirectResponse|Response
     */
    public function formAction(StructureInterface $structure, $preview = false, $partial = false)
    {
        /** @var Request $request */
        $request = $this->get('request_stack')->getCurrentRequest();

        // get attributes
        $attributes = $this->getAttributes([], $structure, $preview);

        $template = $structure->getKey();

        $typeClass = $this->getTypeClass($template);
        /** @var AbstractType $type */
        $type = $this->get('form.registry')->getType($typeClass)->getInnerType();
        $type->setAttributes($attributes);

        $this->form = $this->get('form.factory')->create($typeClass);
        $this->form->handleRequest($request);

        if ($this->form->isSubmitted()
            && $this->form->isValid()
            && $response = $this->handleFormSubmit($request, $type, $attributes)
        ) {
            // success form submit

            return $response;
        }

        return parent::indexAction($structure, $preview, $partial);
    }

    /**
     * Form only action.
     *
     * @param Request $request
     * @param string $key
     *
     * @return RedirectResponse|Response
     *
     * @throws NotFoundHttpException
     */
    public function onlyAction(Request $request, $key)
    {
        $ajaxTemplates = $this->container->getParameter('sulu_form.ajax_templates');

        if (!$ajaxTemplates[$key]) {
            throw new NotFoundHttpException();
        }

        $typeClass = $this->getTypeClass($key);
        /** @var AbstractType $type */
        $type = $this->get('form.registry')->getType($typeClass)->getInnerType();
        $this->form = $this->get('form.factory')->create($typeClass);
        $this->form->handleRequest($request);

        if ($this->form->isSubmitted()
            && $this->form->isValid()
            && $response = $this->handleFormOnlySubmit($request, $type)
        ) {
            // success form submit

            return $response;
        }

        return $this->render($ajaxTemplates[$key], ['form' => $this->form->createView()]);
    }

    /**
     * Handle form submit.
     *
     * @param Request $request
     * @param AbstractType $type
     * @param array $attributes
     *
     * @return JsonResponse|RedirectResponse
     */
    private function handleFormSubmit(Request $request, $type, $attributes)
    {
        // handle form submit
        $configuration = $this->get('sulu_form.configuration.form_configuration_factory')->buildByType(
            $type,
            $this->form->getData(),
            $request->getLocale(),
            $attributes
        );

        $success = $this->get('sulu_form.handler')->handle($this->form, $configuration);

        if ($success) {
            if ($request->isXmlHttpRequest()) {
                return new JsonResponse(['send' => $success]);
            }

            return new RedirectResponse('?send=true');
        }

        if ($request->isXmlHttpRequest()) {
            return new JsonResponse(
                [
                    'send' => false,
                    'errors' => $this->getErrors(),
                ],
                400
            );
        }
    }

    /**
     * Handle form only submit.
     *
     * @param Request $request
     * @param AbstractType $type
     *
     * @return RedirectResponse
     */
    private function handleFormOnlySubmit(Request $request, $type)
    {
        // handle form submit
        $configuration = $this->get('sulu_form.configuration.form_configuration_factory')->buildByType(
            $type,
            $this->form->getData(),
            $request->getLocale(),
            []
        );

        if ($this->get('sulu_form.handler')->handle($this->form, $configuration)) {
            return new RedirectResponse('?send=true');
        }
    }

    /**
     * Generates a token for the form.
     *
     * @param Request $request
     *
     * @return Response
     */
    public function tokenAction(Request $request)
    {
        $formName = $request->get('form');
        $csrfToken = $this->get('security.csrf.token_manager')->getToken(
            $request->get('form')
        )->getValue();

        $content = $csrfToken;

        // this should be the default behaviour in future because for varnish its needed
        if ($request->get('html')) {
            $content = sprintf(
                '<input type="hidden" id="%s__token" name="%s[_token]" value="%s" />',
                $formName,
                $formName,
                $csrfToken
            );
        }

        $response = new Response($content);

        /* Deactivate Cache for this token action */
        $response->setSharedMaxAge(0);
        $response->setMaxAge(0);
        // set shared will set the request to public so it need to be done after shared max set to 0
        $response->setPrivate();
        $response->headers->addCacheControlDirective('no-cache', true);
        $response->headers->addCacheControlDirective('must-revalidate', true);
        $response->headers->addCacheControlDirective('no-store', true);

        return $response;
    }

    /**
     * Get errors.
     *
     * @return array
     */
    protected function getErrors()
    {
        $errors = [];

        $generalErrors = [];
        foreach ($this->form->getErrors() as $error) {
            $generalErrors[] = $error->getMessage();
        }

        if (!empty($generalErrors)) {
            $errors['general'] = $generalErrors;
        }

        foreach ($this->form->all() as $field) {
            $fieldErrors = [];

            foreach ($field->getErrors() as $error) {
                $fieldErrors[] = $error->getMessage();
            }

            if (!empty($fieldErrors)) {
                $errors[$field->getName()] = $fieldErrors;
            }
        }

        return $errors;
    }

    /**
     * {@inheritdoc}
     */
    protected function getAttributes($attributes, StructureInterface $structure = null, $preview = false)
    {
        if (null === $this->attributes) { // for performance only called once
            $this->attributes = parent::getAttributes($attributes, $structure, $preview);
        }

        if (!empty($this->form)) {
            $this->attributes['form'] = $this->form->createView();
        }

        return $this->attributes;
    }

    /**
     * Get type class.
     *
     * @param string $key
     *
     * @return string
     */
    private function getTypeClass($key)
    {
        $staticForms = $this->getParameter('sulu_form.static_forms');

        return $staticForms[$key]['class'];
    }
}