ÿØÿà JFIF    ÿÛ „ ( %!1!%*+...983,7(-.- Csrf/Type/FormTypeCsrfExtension.php000064400000012341152405076560013366 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Csrf\Type; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfTokenManagerAdapter; use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Translation\TranslatorInterface; /** * @author Bernhard Schussek */ class FormTypeCsrfExtension extends AbstractTypeExtension { /** * @var CsrfTokenManagerInterface */ private $defaultTokenManager; /** * @var Boolean */ private $defaultEnabled; /** * @var string */ private $defaultFieldName; /** * @var TranslatorInterface */ private $translator; /** * @var null|string */ private $translationDomain; public function __construct($defaultTokenManager, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null) { if ($defaultTokenManager instanceof CsrfProviderInterface) { $defaultTokenManager = new CsrfProviderAdapter($defaultTokenManager); } elseif (!$defaultTokenManager instanceof CsrfTokenManagerInterface) { throw new UnexpectedTypeException($defaultTokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); } $this->defaultTokenManager = $defaultTokenManager; $this->defaultEnabled = $defaultEnabled; $this->defaultFieldName = $defaultFieldName; $this->translator = $translator; $this->translationDomain = $translationDomain; } /** * Adds a CSRF field to the form when the CSRF protection is enabled. * * @param FormBuilderInterface $builder The form builder * @param array $options The options */ public function buildForm(FormBuilderInterface $builder, array $options) { if (!$options['csrf_protection']) { return; } $builder ->addEventSubscriber(new CsrfValidationListener( $options['csrf_field_name'], $options['csrf_token_manager'], $options['csrf_token_id'] ?: ($builder->getName() ?: get_class($builder->getType()->getInnerType())), $options['csrf_message'], $this->translator, $this->translationDomain )) ; } /** * Adds a CSRF field to the root form view. * * @param FormView $view The form view * @param FormInterface $form The form * @param array $options The options */ public function finishView(FormView $view, FormInterface $form, array $options) { if ($options['csrf_protection'] && !$view->parent && $options['compound']) { $factory = $form->getConfig()->getFormFactory(); $tokenId = $options['csrf_token_id'] ?: ($form->getName() ?: get_class($form->getConfig()->getType()->getInnerType())); $data = (string) $options['csrf_token_manager']->getToken($tokenId); $csrfForm = $factory->createNamed($options['csrf_field_name'], 'hidden', $data, array( 'mapped' => false, )); $view->children[$options['csrf_field_name']] = $csrfForm->createView($view); } } /** * {@inheritDoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { // BC clause for the "intention" option $csrfTokenId = function (Options $options) { return $options['intention']; }; // BC clause for the "csrf_provider" option $csrfTokenManager = function (Options $options) { return $options['csrf_provider'] instanceof CsrfTokenManagerAdapter ? $options['csrf_provider']->getTokenManager() : new CsrfProviderAdapter($options['csrf_provider']); }; $resolver->setDefaults(array( 'csrf_protection' => $this->defaultEnabled, 'csrf_field_name' => $this->defaultFieldName, 'csrf_message' => 'The CSRF token is invalid. Please try to resubmit the form.', 'csrf_token_manager' => $csrfTokenManager, 'csrf_token_id' => $csrfTokenId, 'csrf_provider' => new CsrfTokenManagerAdapter($this->defaultTokenManager), 'intention' => null, )); } /** * {@inheritDoc} */ public function getExtendedType() { return 'form'; } } Csrf/CsrfExtension.php000064400000004304152405076560010757 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Csrf; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\AbstractExtension; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Translation\TranslatorInterface; /** * This extension protects forms by using a CSRF token. * * @author Bernhard Schussek */ class CsrfExtension extends AbstractExtension { /** * @var CsrfTokenManagerInterface */ private $tokenManager; /** * @var TranslatorInterface */ private $translator; /** * @var null|string */ private $translationDomain; /** * Constructor. * * @param CsrfTokenManagerInterface $tokenManager The CSRF token manager * @param TranslatorInterface $translator The translator for translating error messages * @param null|string $translationDomain The translation domain for translating */ public function __construct($tokenManager, TranslatorInterface $translator = null, $translationDomain = null) { if ($tokenManager instanceof CsrfProviderInterface) { $tokenManager = new CsrfProviderAdapter($tokenManager); } elseif (!$tokenManager instanceof CsrfTokenManagerInterface) { throw new UnexpectedTypeException($tokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); } $this->tokenManager = $tokenManager; $this->translator = $translator; $this->translationDomain = $translationDomain; } /** * {@inheritDoc} */ protected function loadTypeExtensions() { return array( new Type\FormTypeCsrfExtension($this->tokenManager, true, '_token', $this->translator, $this->translationDomain), ); } } Csrf/CsrfProvider/CsrfTokenManagerAdapter.php000064400000002524152405076560015271 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider; use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; /** * Adapter for using the new token generator with the old interface. * * @since 2.4 * @author Bernhard Schussek * * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. */ class CsrfTokenManagerAdapter implements CsrfProviderInterface { /** * @var CsrfTokenManagerInterface */ private $tokenManager; public function __construct(CsrfTokenManagerInterface $tokenManager) { $this->tokenManager = $tokenManager; } public function getTokenManager() { return $this->tokenManager; } /** * {@inheritdoc} */ public function generateCsrfToken($intention) { return $this->tokenManager->getToken($intention)->getValue(); } /** * {@inheritdoc} */ public function isCsrfTokenValid($intention, $token) { return $this->tokenManager->isTokenValid(new CsrfToken($intention, $token)); } } Csrf/CsrfProvider/CsrfProviderAdapter.php000064400000003344152405076560014511 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider; use Symfony\Component\Form\Exception\BadMethodCallException; use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; /** * Adapter for using old CSRF providers where the new {@link CsrfTokenManagerInterface} * is expected. * * @since 2.4 * @author Bernhard Schussek * * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. */ class CsrfProviderAdapter implements CsrfTokenManagerInterface { /** * @var CsrfProviderInterface */ private $csrfProvider; public function __construct(CsrfProviderInterface $csrfProvider) { $this->csrfProvider = $csrfProvider; } public function getCsrfProvider() { return $this->csrfProvider; } /** * {@inheritdoc} */ public function getToken($tokenId) { return new CsrfToken($tokenId, $this->csrfProvider->generateCsrfToken($tokenId)); } /** * {@inheritdoc} */ public function refreshToken($tokenId) { throw new BadMethodCallException('Not supported'); } /** * {@inheritdoc} */ public function removeToken($tokenId) { throw new BadMethodCallException('Not supported'); } /** * {@inheritdoc} */ public function isTokenValid(CsrfToken $token) { return $this->csrfProvider->isCsrfTokenValid($token->getId(), $token->getValue()); } } Csrf/CsrfProvider/CsrfProviderInterface.php000064400000003706152405076560015033 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider; /** * Marks classes able to provide CSRF protection * * You can generate a CSRF token by using the method generateCsrfToken(). To * this method you should pass a value that is unique to the page that should * be secured against CSRF attacks. This value doesn't necessarily have to be * secret. Implementations of this interface are responsible for adding more * secret information. * * If you want to secure a form submission against CSRF attacks, you could * supply an "intention" string. This way you make sure that the form can only * be submitted to pages that are designed to handle the form, that is, that use * the same intention string to validate the CSRF token with isCsrfTokenValid(). * * @author Bernhard Schussek * * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. Use * {@link \Symfony\Component\Security\Csrf\CsrfTokenManagerInterface} * instead. */ interface CsrfProviderInterface { /** * Generates a CSRF token for a page of your application. * * @param string $intention Some value that identifies the action intention * (i.e. "authenticate"). Doesn't have to be a secret value. * * @return string The generated token */ public function generateCsrfToken($intention); /** * Validates a CSRF token. * * @param string $intention The intention used when generating the CSRF token * @param string $token The token supplied by the browser * * @return Boolean Whether the token supplied by the browser is correct */ public function isCsrfTokenValid($intention, $token); } Csrf/CsrfProvider/SessionCsrfProvider.php000064400000003224152405076560014551 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider; use Symfony\Component\HttpFoundation\Session\Session; /** * This provider uses a Symfony2 Session object to retrieve the user's * session ID. * * @see DefaultCsrfProvider * * @author Bernhard Schussek * * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. Use * {@link \Symfony\Component\Security\Csrf\CsrfTokenManager} in * combination with {@link \Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage} * instead. */ class SessionCsrfProvider extends DefaultCsrfProvider { /** * The user session from which the session ID is returned * @var Session */ protected $session; /** * Initializes the provider with a Session object and a secret value. * * A recommended value for the secret is a generated value with at least * 32 characters and mixed letters, digits and special characters. * * @param Session $session The user session * @param string $secret A secret value included in the CSRF token */ public function __construct(Session $session, $secret) { parent::__construct($secret); $this->session = $session; } /** * {@inheritdoc} */ protected function getSessionId() { $this->session->start(); return $this->session->getId(); } } Csrf/CsrfProvider/DefaultCsrfProvider.php000064400000004264152405076560014517 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider; /** * Default implementation of CsrfProviderInterface. * * This provider uses the session ID returned by session_id() as well as a * user-defined secret value to secure the CSRF token. * * @author Bernhard Schussek * * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. Use * {@link \Symfony\Component\Security\Csrf\CsrfTokenManager} in * combination with {@link \Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage} * instead. */ class DefaultCsrfProvider implements CsrfProviderInterface { /** * A secret value used for generating the CSRF token * @var string */ protected $secret; /** * Initializes the provider with a secret value * * A recommended value for the secret is a generated value with at least * 32 characters and mixed letters, digits and special characters. * * @param string $secret A secret value included in the CSRF token */ public function __construct($secret) { $this->secret = $secret; } /** * {@inheritDoc} */ public function generateCsrfToken($intention) { return sha1($this->secret.$intention.$this->getSessionId()); } /** * {@inheritDoc} */ public function isCsrfTokenValid($intention, $token) { return $token === $this->generateCsrfToken($intention); } /** * Returns the ID of the user session. * * Automatically starts the session if necessary. * * @return string The session ID */ protected function getSessionId() { if (version_compare(PHP_VERSION, '5.4', '>=')) { if (PHP_SESSION_NONE === session_status()) { session_start(); } } elseif (!session_id()) { session_start(); } return session_id(); } } Csrf/EventListener/CsrfValidationListener.php000064400000007157152405076560015403 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Csrf\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Translation\TranslatorInterface; /** * @author Bernhard Schussek */ class CsrfValidationListener implements EventSubscriberInterface { /** * The name of the CSRF field * @var string */ private $fieldName; /** * The generator for CSRF tokens * @var CsrfTokenManagerInterface */ private $tokenManager; /** * A text mentioning the tokenId of the CSRF token * * Validation of the token will only succeed if it was generated in the * same session and with the same tokenId. * * @var string */ private $tokenId; /** * The message displayed in case of an error. * @var string */ private $errorMessage; /** * @var TranslatorInterface */ private $translator; /** * @var null|string */ private $translationDomain; public static function getSubscribedEvents() { return array( FormEvents::PRE_SUBMIT => 'preSubmit', ); } public function __construct($fieldName, $tokenManager, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null) { if ($tokenManager instanceof CsrfProviderInterface) { $tokenManager = new CsrfProviderAdapter($tokenManager); } elseif (!$tokenManager instanceof CsrfTokenManagerInterface) { throw new UnexpectedTypeException($tokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); } $this->fieldName = $fieldName; $this->tokenManager = $tokenManager; $this->tokenId = $tokenId; $this->errorMessage = $errorMessage; $this->translator = $translator; $this->translationDomain = $translationDomain; } public function preSubmit(FormEvent $event) { $form = $event->getForm(); $data = $event->getData(); if ($form->isRoot() && $form->getConfig()->getOption('compound')) { if (!isset($data[$this->fieldName]) || !$this->tokenManager->isTokenValid(new CsrfToken($this->tokenId, $data[$this->fieldName]))) { $errorMessage = $this->errorMessage; if (null !== $this->translator) { $errorMessage = $this->translator->trans($errorMessage, array(), $this->translationDomain); } $form->addError(new FormError($errorMessage)); } if (is_array($data)) { unset($data[$this->fieldName]); } } $event->setData($data); } /** * Alias of {@link preSubmit()}. * * @deprecated Deprecated since version 2.3, to be removed in 3.0. Use * {@link preSubmit()} instead. */ public function preBind(FormEvent $event) { $this->preSubmit($event); } } Templating/TemplatingRendererEngine.php000064400000010363152405076560014317 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Templating; use Symfony\Component\Form\AbstractRendererEngine; use Symfony\Component\Form\FormView; use Symfony\Component\Templating\EngineInterface; /** * @author Bernhard Schussek */ class TemplatingRendererEngine extends AbstractRendererEngine { /** * @var EngineInterface */ private $engine; public function __construct(EngineInterface $engine, array $defaultThemes = array()) { parent::__construct($defaultThemes); $this->engine = $engine; } /** * {@inheritdoc} */ public function renderBlock(FormView $view, $resource, $blockName, array $variables = array()) { return trim($this->engine->render($resource, $variables)); } /** * Loads the cache with the resource for a given block name. * * This implementation tries to load as few blocks as possible, since each block * is represented by a template on the file system. * * @see getResourceForBlock() * * @param string $cacheKey The cache key of the form view. * @param FormView $view The form view for finding the applying themes. * @param string $blockName The name of the block to load. * * @return Boolean True if the resource could be loaded, false otherwise. */ protected function loadResourceForBlockName($cacheKey, FormView $view, $blockName) { // Recursively try to find the block in the themes assigned to $view, // then of its parent form, then of the parent form of the parent and so on. // When the root form is reached in this recursion, also the default // themes are taken into account. // Check each theme whether it contains the searched block if (isset($this->themes[$cacheKey])) { for ($i = count($this->themes[$cacheKey]) - 1; $i >= 0; --$i) { if ($this->loadResourceFromTheme($cacheKey, $blockName, $this->themes[$cacheKey][$i])) { return true; } } } // Check the default themes once we reach the root form without success if (!$view->parent) { for ($i = count($this->defaultThemes) - 1; $i >= 0; --$i) { if ($this->loadResourceFromTheme($cacheKey, $blockName, $this->defaultThemes[$i])) { return true; } } } // If we did not find anything in the themes of the current view, proceed // with the themes of the parent view if ($view->parent) { $parentCacheKey = $view->parent->vars[self::CACHE_KEY_VAR]; if (!isset($this->resources[$parentCacheKey][$blockName])) { $this->loadResourceForBlockName($parentCacheKey, $view->parent, $blockName); } // If a template exists in the parent themes, cache that template // for the current theme as well to speed up further accesses if ($this->resources[$parentCacheKey][$blockName]) { $this->resources[$cacheKey][$blockName] = $this->resources[$parentCacheKey][$blockName]; return true; } } // Cache that we didn't find anything to speed up further accesses $this->resources[$cacheKey][$blockName] = false; return false; } /** * Tries to load the resource for a block from a theme. * * @param string $cacheKey The cache key for storing the resource. * @param string $blockName The name of the block to load a resource for. * @param mixed $theme The theme to load the block from. * * @return Boolean True if the resource could be loaded, false otherwise. */ protected function loadResourceFromTheme($cacheKey, $blockName, $theme) { if ($this->engine->exists($templateName = $theme.':'.$blockName.'.html.php')) { $this->resources[$cacheKey][$blockName] = $templateName; return true; } return false; } } Templating/TemplatingExtension.php000064400000003121152405076560013371 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Templating; use Symfony\Component\Form\AbstractExtension; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\FormRenderer; use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Templating\PhpEngine; use Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper; /** * Integrates the Templating component with the Form library. * * @author Bernhard Schussek */ class TemplatingExtension extends AbstractExtension { public function __construct(PhpEngine $engine, $csrfTokenManager = null, array $defaultThemes = array()) { if ($csrfTokenManager instanceof CsrfProviderInterface) { $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { throw new UnexpectedTypeException($csrfTokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); } $engine->addHelpers(array( new FormHelper(new FormRenderer(new TemplatingRendererEngine($engine, $defaultThemes), $csrfTokenManager)) )); } } DependencyInjection/DependencyInjectionExtension.php000064400000005527152405076560017037 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\DependencyInjection; use Symfony\Component\Form\FormExtensionInterface; use Symfony\Component\Form\FormTypeGuesserChain; use Symfony\Component\Form\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\ContainerInterface; class DependencyInjectionExtension implements FormExtensionInterface { private $container; private $typeServiceIds; private $typeExtensionServiceIds; private $guesserServiceIds; private $guesser; private $guesserLoaded = false; public function __construct(ContainerInterface $container, array $typeServiceIds, array $typeExtensionServiceIds, array $guesserServiceIds) { $this->container = $container; $this->typeServiceIds = $typeServiceIds; $this->typeExtensionServiceIds = $typeExtensionServiceIds; $this->guesserServiceIds = $guesserServiceIds; } public function getType($name) { if (!isset($this->typeServiceIds[$name])) { throw new InvalidArgumentException(sprintf('The field type "%s" is not registered with the service container.', $name)); } $type = $this->container->get($this->typeServiceIds[$name]); if ($type->getName() !== $name) { throw new InvalidArgumentException( sprintf('The type name specified for the service "%s" does not match the actual name. Expected "%s", given "%s"', $this->typeServiceIds[$name], $name, $type->getName() )); } return $type; } public function hasType($name) { return isset($this->typeServiceIds[$name]); } public function getTypeExtensions($name) { $extensions = array(); if (isset($this->typeExtensionServiceIds[$name])) { foreach ($this->typeExtensionServiceIds[$name] as $serviceId) { $extensions[] = $this->container->get($serviceId); } } return $extensions; } public function hasTypeExtensions($name) { return isset($this->typeExtensionServiceIds[$name]); } public function getTypeGuesser() { if (!$this->guesserLoaded) { $this->guesserLoaded = true; $guessers = array(); foreach ($this->guesserServiceIds as $serviceId) { $guessers[] = $this->container->get($serviceId); } if (count($guessers) > 0) { $this->guesser = new FormTypeGuesserChain($guessers); } } return $this->guesser; } } DataCollector/DataCollectorExtension.php000064400000002061152405076560014423 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\DataCollector; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\AbstractExtension; /** * Extension for collecting data of the forms on a page. * * @since 2.4 * @author Robert Schönthal * @author Bernhard Schussek */ class DataCollectorExtension extends AbstractExtension { /** * @var EventSubscriberInterface */ private $dataCollector; public function __construct(FormDataCollectorInterface $dataCollector) { $this->dataCollector = $dataCollector; } /** * {@inheritDoc} */ protected function loadTypeExtensions() { return array( new Type\DataCollectorTypeExtension($this->dataCollector) ); } } DataCollector/Type/DataCollectorTypeExtension.php000064400000002565152405076560016217 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\DataCollector\Type; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\DataCollector\EventListener\DataCollectorListener; use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface; use Symfony\Component\Form\FormBuilderInterface; /** * Type extension for collecting data of a form with this type. * * @since 2.4 * @author Robert Schönthal * @author Bernhard Schussek */ class DataCollectorTypeExtension extends AbstractTypeExtension { /** * @var \Symfony\Component\EventDispatcher\EventSubscriberInterface */ private $listener; public function __construct(FormDataCollectorInterface $dataCollector) { $this->listener = new DataCollectorListener($dataCollector); } /** * {@inheritDoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addEventSubscriber($this->listener); } /** * {@inheritDoc} */ public function getExtendedType() { return 'form'; } } DataCollector/FormDataExtractorInterface.php000064400000002761152405076560015227 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\DataCollector; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; /** * Extracts arrays of information out of forms. * * @since 2.4 * @author Bernhard Schussek */ interface FormDataExtractorInterface { /** * Extracts the configuration data of a form. * * @param FormInterface $form The form * * @return array Information about the form's configuration */ public function extractConfiguration(FormInterface $form); /** * Extracts the default data of a form. * * @param FormInterface $form The form * * @return array Information about the form's default data */ public function extractDefaultData(FormInterface $form); /** * Extracts the submitted data of a form. * * @param FormInterface $form The form * * @return array Information about the form's submitted data */ public function extractSubmittedData(FormInterface $form); /** * Extracts the view variables of a form. * * @param FormView $view The form view * * @return array Information about the view's variables */ public function extractViewVariables(FormView $view); } DataCollector/Proxy/ResolvedTypeDataCollectorProxy.php000064400000007701152405076560017265 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\DataCollector\Proxy; use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\ResolvedFormTypeInterface; /** * Proxy that invokes a data collector when creating a form and its view. * * @since 2.4 * @author Bernhard Schussek */ class ResolvedTypeDataCollectorProxy implements ResolvedFormTypeInterface { /** * @var ResolvedFormTypeInterface */ private $proxiedType; /** * @var FormDataCollectorInterface */ private $dataCollector; public function __construct(ResolvedFormTypeInterface $proxiedType, FormDataCollectorInterface $dataCollector) { $this->proxiedType = $proxiedType; $this->dataCollector = $dataCollector; } /** * {@inheritdoc} */ public function getName() { return $this->proxiedType->getName(); } /** * {@inheritdoc} */ public function getParent() { return $this->proxiedType->getParent(); } /** * {@inheritdoc} */ public function getInnerType() { return $this->proxiedType->getInnerType(); } /** * {@inheritdoc} */ public function getTypeExtensions() { return $this->proxiedType->getTypeExtensions(); } /** * {@inheritdoc} */ public function createBuilder(FormFactoryInterface $factory, $name, array $options = array()) { $builder = $this->proxiedType->createBuilder($factory, $name, $options); $builder->setAttribute('data_collector/passed_options', $options); $builder->setType($this); return $builder; } /** * {@inheritdoc} */ public function createView(FormInterface $form, FormView $parent = null) { return $this->proxiedType->createView($form, $parent); } /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $this->proxiedType->buildForm($builder, $options); } /** * {@inheritdoc} */ public function buildView(FormView $view, FormInterface $form, array $options) { $this->proxiedType->buildView($view, $form, $options); } /** * {@inheritdoc} */ public function finishView(FormView $view, FormInterface $form, array $options) { $this->proxiedType->finishView($view, $form, $options); // Remember which view belongs to which form instance, so that we can // get the collected data for a view when its form instance is not // available (e.g. CSRF token) $this->dataCollector->associateFormWithView($form, $view); // Since the CSRF token is only present in the FormView tree, we also // need to check the FormView tree instead of calling isRoot() on the // FormInterface tree if (null === $view->parent) { $this->dataCollector->collectViewVariables($view); // Re-assemble data, in case FormView instances were added, for // which no FormInterface instances were present (e.g. CSRF token). // Since finishView() is called after finishing the views of all // children, we can safely assume that information has been // collected about the complete form tree. $this->dataCollector->buildFinalFormTree($form, $view); } } /** * {@inheritdoc} */ public function getOptionsResolver() { return $this->proxiedType->getOptionsResolver(); } } DataCollector/Proxy/ResolvedTypeFactoryDataCollectorProxy.php000064400000003050152405076560020606 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\DataCollector\Proxy; use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface; use Symfony\Component\Form\FormTypeInterface; use Symfony\Component\Form\ResolvedFormTypeFactoryInterface; use Symfony\Component\Form\ResolvedFormTypeInterface; /** * Proxy that wraps resolved types into {@link ResolvedTypeDataCollectorProxy} * instances. * * @since 2.4 * @author Bernhard Schussek */ class ResolvedTypeFactoryDataCollectorProxy implements ResolvedFormTypeFactoryInterface { /** * @var ResolvedFormTypeFactoryInterface */ private $proxiedFactory; /** * @var FormDataCollectorInterface */ private $dataCollector; public function __construct(ResolvedFormTypeFactoryInterface $proxiedFactory, FormDataCollectorInterface $dataCollector) { $this->proxiedFactory = $proxiedFactory; $this->dataCollector = $dataCollector; } /** * {@inheritdoc} */ public function createResolvedType(FormTypeInterface $type, array $typeExtensions, ResolvedFormTypeInterface $parent = null) { return new ResolvedTypeDataCollectorProxy( $this->proxiedFactory->createResolvedType($type, $typeExtensions, $parent), $this->dataCollector ); } } DataCollector/EventListener/DataCollectorListener.php000064400000005025152405076560017026 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\DataCollector\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\Extension\DataCollector\FormDataCollectorInterface; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; /** * Listener that invokes a data collector for the {@link FormEvents::POST_SET_DATA} * and {@link FormEvents::POST_SUBMIT} events. * * @since 2.4 * @author Bernhard Schussek */ class DataCollectorListener implements EventSubscriberInterface { /** * @var FormDataCollectorInterface */ private $dataCollector; public function __construct(FormDataCollectorInterface $dataCollector) { $this->dataCollector = $dataCollector; } /** * {@inheritDoc} */ public static function getSubscribedEvents() { return array( // High priority in order to be called as soon as possible FormEvents::POST_SET_DATA => array('postSetData', 255), // Low priority in order to be called as late as possible FormEvents::POST_SUBMIT => array('postSubmit', -255), ); } /** * Listener for the {@link FormEvents::POST_SET_DATA} event. * * @param FormEvent $event The event object */ public function postSetData(FormEvent $event) { if ($event->getForm()->isRoot()) { // Collect basic information about each form $this->dataCollector->collectConfiguration($event->getForm()); // Collect the default data $this->dataCollector->collectDefaultData($event->getForm()); } } /** * Listener for the {@link FormEvents::POST_SUBMIT} event. * * @param FormEvent $event The event object */ public function postSubmit(FormEvent $event) { if ($event->getForm()->isRoot()) { // Collect the submitted data of each form $this->dataCollector->collectSubmittedData($event->getForm()); // Assemble a form tree // This is done again in collectViewVariables(), but that method // is not guaranteed to be called (i.e. when no view is created) $this->dataCollector->buildPreliminaryFormTree($event->getForm()); } } } DataCollector/FormDataCollector.php000064400000016662152405076560013366 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\DataCollector; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; /** * Data collector for {@link \Symfony\Component\Form\FormInterface} instances. * * @since 2.4 * @author Robert Schönthal * @author Bernhard Schussek */ class FormDataCollector extends DataCollector implements FormDataCollectorInterface { /** * @var FormDataExtractor */ private $dataExtractor; /** * Stores the collected data per {@link FormInterface} instance. * * Uses the hashes of the forms as keys. This is preferable over using * {@link \SplObjectStorage}, because in this way no references are kept * to the {@link FormInterface} instances. * * @var array */ private $dataByForm; /** * Stores the collected data per {@link FormView} instance. * * Uses the hashes of the views as keys. This is preferable over using * {@link \SplObjectStorage}, because in this way no references are kept * to the {@link FormView} instances. * * @var array */ private $dataByView; /** * Connects {@link FormView} with {@link FormInterface} instances. * * Uses the hashes of the views as keys and the hashes of the forms as * values. This is preferable over storing the objects directly, because * this way they can safely be discarded by the GC. * * @var array */ private $formsByView; public function __construct(FormDataExtractorInterface $dataExtractor) { $this->dataExtractor = $dataExtractor; $this->data = array( 'forms' => array(), 'nb_errors' => 0, ); } /** * Does nothing. The data is collected during the form event listeners. */ public function collect(Request $request, Response $response, \Exception $exception = null) { } /** * {@inheritdoc} */ public function associateFormWithView(FormInterface $form, FormView $view) { $this->formsByView[spl_object_hash($view)] = spl_object_hash($form); } /** * {@inheritdoc} */ public function collectConfiguration(FormInterface $form) { $hash = spl_object_hash($form); if (!isset($this->dataByForm[$hash])) { $this->dataByForm[$hash] = array(); } $this->dataByForm[$hash] = array_replace( $this->dataByForm[$hash], $this->dataExtractor->extractConfiguration($form) ); foreach ($form as $child) { $this->collectConfiguration($child); } } /** * {@inheritdoc} */ public function collectDefaultData(FormInterface $form) { $hash = spl_object_hash($form); if (!isset($this->dataByForm[$hash])) { $this->dataByForm[$hash] = array(); } $this->dataByForm[$hash] = array_replace( $this->dataByForm[$hash], $this->dataExtractor->extractDefaultData($form) ); foreach ($form as $child) { $this->collectDefaultData($child); } } /** * {@inheritdoc} */ public function collectSubmittedData(FormInterface $form) { $hash = spl_object_hash($form); if (!isset($this->dataByForm[$hash])) { $this->dataByForm[$hash] = array(); } $this->dataByForm[$hash] = array_replace( $this->dataByForm[$hash], $this->dataExtractor->extractSubmittedData($form) ); // Count errors if (isset($this->dataByForm[$hash]['errors'])) { $this->data['nb_errors'] += count($this->dataByForm[$hash]['errors']); } foreach ($form as $child) { $this->collectSubmittedData($child); } } /** * {@inheritdoc} */ public function collectViewVariables(FormView $view) { $hash = spl_object_hash($view); if (!isset($this->dataByView[$hash])) { $this->dataByView[$hash] = array(); } $this->dataByView[$hash] = array_replace( $this->dataByView[$hash], $this->dataExtractor->extractViewVariables($view) ); foreach ($view->children as $child) { $this->collectViewVariables($child); } } /** * {@inheritdoc} */ public function buildPreliminaryFormTree(FormInterface $form) { $this->data['forms'][$form->getName()] = array(); $this->recursiveBuildPreliminaryFormTree($form, $this->data['forms'][$form->getName()]); } /** * {@inheritdoc} */ public function buildFinalFormTree(FormInterface $form, FormView $view) { $this->data['forms'][$form->getName()] = array(); $this->recursiveBuildFinalFormTree($form, $view, $this->data['forms'][$form->getName()]); } /** * {@inheritDoc} */ public function getName() { return 'form'; } /** * {@inheritdoc} */ public function getData() { return $this->data; } private function recursiveBuildPreliminaryFormTree(FormInterface $form, &$output = null) { $hash = spl_object_hash($form); $output = isset($this->dataByForm[$hash]) ? $this->dataByForm[$hash] : array(); $output['children'] = array(); foreach ($form as $name => $child) { $output['children'][$name] = array(); $this->recursiveBuildPreliminaryFormTree($child, $output['children'][$name]); } } private function recursiveBuildFinalFormTree(FormInterface $form = null, FormView $view, &$output = null) { $viewHash = spl_object_hash($view); $formHash = null; if (null !== $form) { $formHash = spl_object_hash($form); } elseif (isset($this->formsByView[$viewHash])) { // The FormInterface instance of the CSRF token is never contained in // the FormInterface tree of the form, so we need to get the // corresponding FormInterface instance for its view in a different way $formHash = $this->formsByView[$viewHash]; } $output = isset($this->dataByView[$viewHash]) ? $this->dataByView[$viewHash] : array(); if (null !== $formHash) { $output = array_replace( $output, isset($this->dataByForm[$formHash]) ? $this->dataByForm[$formHash] : array() ); } $output['children'] = array(); foreach ($view->children as $name => $childView) { // The CSRF token, for example, is never added to the form tree. // It is only present in the view. $childForm = null !== $form && $form->has($name) ? $form->get($name) : null; $output['children'][$name] = array(); $this->recursiveBuildFinalFormTree($childForm, $childView, $output['children'][$name]); } } } DataCollector/FormDataCollectorInterface.php000064400000006240152405076560015176 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\DataCollector; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; /** * Collects and structures information about forms. * * @since 2.4 * @author Bernhard Schussek */ interface FormDataCollectorInterface extends DataCollectorInterface { /** * Stores configuration data of the given form and its children. * * @param FormInterface $form A root form */ public function collectConfiguration(FormInterface $form); /** * Stores the default data of the given form and its children. * * @param FormInterface $form A root form */ public function collectDefaultData(FormInterface $form); /** * Stores the submitted data of the given form and its children. * * @param FormInterface $form A root form */ public function collectSubmittedData(FormInterface $form); /** * Stores the view variables of the given form view and its children. * * @param FormView $view A root form view */ public function collectViewVariables(FormView $view); /** * Specifies that the given objects represent the same conceptual form. * * @param FormInterface $form A form object * @param FormView $view A view object */ public function associateFormWithView(FormInterface $form, FormView $view); /** * Assembles the data collected about the given form and its children as * a tree-like data structure. * * The result can be queried using {@link getData()}. * * @param FormInterface $form A root form */ public function buildPreliminaryFormTree(FormInterface $form); /** * Assembles the data collected about the given form and its children as * a tree-like data structure. * * The result can be queried using {@link getData()}. * * Contrary to {@link buildPreliminaryFormTree()}, a {@link FormView} * object has to be passed. The tree structure of this view object will be * used for structuring the resulting data. That means, if a child is * present in the view, but not in the form, it will be present in the final * data array anyway. * * When {@link FormView} instances are present in the view tree, for which * no corresponding {@link FormInterface} objects can be found in the form * tree, only the view data will be included in the result. If a * corresponding {@link FormInterface} exists otherwise, call * {@link associateFormWithView()} before calling this method. * * @param FormInterface $form A root form * @param FormView $view A root view */ public function buildFinalFormTree(FormInterface $form, FormView $view); /** * Returns all collected data. * * @return array */ public function getData(); } DataCollector/FormDataExtractor.php000064400000010604152405076560013401 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\DataCollector; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; /** * Default implementation of {@link FormDataExtractorInterface}. * * @since 2.4 * @author Bernhard Schussek */ class FormDataExtractor implements FormDataExtractorInterface { /** * @var ValueExporter */ private $valueExporter; /** * Constructs a new data extractor. */ public function __construct(ValueExporter $valueExporter = null) { $this->valueExporter = $valueExporter ?: new ValueExporter(); } /** * {@inheritdoc} */ public function extractConfiguration(FormInterface $form) { $data = array( 'id' => $this->buildId($form), 'type' => $form->getConfig()->getType()->getName(), 'type_class' => get_class($form->getConfig()->getType()->getInnerType()), 'synchronized' => $this->valueExporter->exportValue($form->isSynchronized()), 'passed_options' => array(), 'resolved_options' => array(), ); foreach ($form->getConfig()->getAttribute('data_collector/passed_options', array()) as $option => $value) { $data['passed_options'][$option] = $this->valueExporter->exportValue($value); } foreach ($form->getConfig()->getOptions() as $option => $value) { $data['resolved_options'][$option] = $this->valueExporter->exportValue($value); } ksort($data['passed_options']); ksort($data['resolved_options']); return $data; } /** * {@inheritdoc} */ public function extractDefaultData(FormInterface $form) { $data = array( 'default_data' => array( 'norm' => $this->valueExporter->exportValue($form->getNormData()), ), 'submitted_data' => array(), ); if ($form->getData() !== $form->getNormData()) { $data['default_data']['model'] = $this->valueExporter->exportValue($form->getData()); } if ($form->getViewData() !== $form->getNormData()) { $data['default_data']['view'] = $this->valueExporter->exportValue($form->getViewData()); } return $data; } /** * {@inheritdoc} */ public function extractSubmittedData(FormInterface $form) { $data = array( 'submitted_data' => array( 'norm' => $this->valueExporter->exportValue($form->getNormData()), ), 'errors' => array(), ); if ($form->getViewData() !== $form->getNormData()) { $data['submitted_data']['view'] = $this->valueExporter->exportValue($form->getViewData()); } if ($form->getData() !== $form->getNormData()) { $data['submitted_data']['model'] = $this->valueExporter->exportValue($form->getData()); } foreach ($form->getErrors() as $error) { $data['errors'][] = array( 'message' => $error->getMessage(), ); } $data['synchronized'] = $this->valueExporter->exportValue($form->isSynchronized()); return $data; } /** * {@inheritdoc} */ public function extractViewVariables(FormView $view) { $data = array(); // Set the ID in case no FormInterface object was collected for this // view if (isset($view->vars['id'])) { $data['id'] = $view->vars['id']; } foreach ($view->vars as $varName => $value) { $data['view_vars'][$varName] = $this->valueExporter->exportValue($value); } ksort($data['view_vars']); return $data; } /** * Recursively builds an HTML ID for a form. * * @param FormInterface $form The form * * @return string The HTML ID */ private function buildId(FormInterface $form) { $id = $form->getName(); if (null !== $form->getParent()) { $id = $this->buildId($form->getParent()).'_'.$id; } return $id; } } HttpFoundation/HttpFoundationRequestHandler.php000064400000005075152405076560016061 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\HttpFoundation; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\RequestHandlerInterface; use Symfony\Component\HttpFoundation\Request; /** * A request processor using the {@link Request} class of the HttpFoundation * component. * * @author Bernhard Schussek */ class HttpFoundationRequestHandler implements RequestHandlerInterface { /** * {@inheritdoc} */ public function handleRequest(FormInterface $form, $request = null) { if (!$request instanceof Request) { throw new UnexpectedTypeException($request, 'Symfony\Component\HttpFoundation\Request'); } $name = $form->getName(); $method = $form->getConfig()->getMethod(); if ($method !== $request->getMethod()) { return; } if ('GET' === $method) { if ('' === $name) { $data = $request->query->all(); } else { // Don't submit GET requests if the form's name does not exist // in the request if (!$request->query->has($name)) { return; } $data = $request->query->get($name); } } else { if ('' === $name) { $params = $request->request->all(); $files = $request->files->all(); } elseif ($request->request->has($name) || $request->files->has($name)) { $default = $form->getConfig()->getCompound() ? array() : null; $params = $request->request->get($name, $default); $files = $request->files->get($name, $default); } else { // Don't submit the form if it is not present in the request return; } if (is_array($params) && is_array($files)) { $data = array_replace_recursive($params, $files); } else { $data = $params ?: $files; } } // Don't auto-submit the form unless at least one field is present. if ('' === $name && count(array_intersect_key($data, $form->all())) <= 0) { return; } $form->submit($data, 'PATCH' !== $method); } } HttpFoundation/Type/FormTypeHttpFoundationExtension.php000064400000002546152405076560017516 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\HttpFoundation\Type; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\Form\Extension\HttpFoundation\EventListener\BindRequestListener; use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler; use Symfony\Component\Form\FormBuilderInterface; /** * @author Bernhard Schussek */ class FormTypeHttpFoundationExtension extends AbstractTypeExtension { /** * @var BindRequestListener */ private $listener; /** * @var HttpFoundationRequestHandler */ private $requestHandler; public function __construct() { $this->listener = new BindRequestListener(); $this->requestHandler = new HttpFoundationRequestHandler(); } /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addEventSubscriber($this->listener); $builder->setRequestHandler($this->requestHandler); } /** * {@inheritdoc} */ public function getExtendedType() { return 'form'; } } HttpFoundation/EventListener/BindRequestListener.php000064400000005630152405076560016763 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\HttpFoundation\EventListener; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\Exception\LogicException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Request; /** * @author Bernhard Schussek * * @deprecated Deprecated since version 2.3, to be removed in 3.0. Pass the * Request instance to {@link Form::handleRequest()} instead. */ class BindRequestListener implements EventSubscriberInterface { public static function getSubscribedEvents() { // High priority in order to supersede other listeners return array(FormEvents::PRE_BIND => array('preBind', 128)); } public function preBind(FormEvent $event) { $form = $event->getForm(); /* @var Request $request */ $request = $event->getData(); // Only proceed if we actually deal with a Request if (!$request instanceof Request) { return; } // Uncomment this as soon as the deprecation note should be shown // trigger_error('Passing a Request instance to Form::submit() is deprecated since version 2.3 and will be disabled in 3.0. Call Form::process($request) instead.', E_USER_DEPRECATED); $name = $form->getConfig()->getName(); $default = $form->getConfig()->getCompound() ? array() : null; // Store the bound data in case of a post request switch ($request->getMethod()) { case 'POST': case 'PUT': case 'DELETE': case 'PATCH': if ('' === $name) { // Form bound without name $params = $request->request->all(); $files = $request->files->all(); } else { $params = $request->request->get($name, $default); $files = $request->files->get($name, $default); } if (is_array($params) && is_array($files)) { $data = array_replace_recursive($params, $files); } else { $data = $params ?: $files; } break; case 'GET': $data = '' === $name ? $request->query->all() : $request->query->get($name, $default); break; default: throw new LogicException(sprintf( 'The request method "%s" is not supported', $request->getMethod() )); } $event->setData($data); } } HttpFoundation/HttpFoundationExtension.php000064400000001246152405076560015103 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\HttpFoundation; use Symfony\Component\Form\AbstractExtension; /** * Integrates the HttpFoundation component with the Form library. * * @author Bernhard Schussek */ class HttpFoundationExtension extends AbstractExtension { protected function loadTypeExtensions() { return array( new Type\FormTypeHttpFoundationExtension(), ); } } Core/DataMapper/PropertyPathMapper.php000064400000006417152405076560014013 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataMapper; use Symfony\Component\Form\DataMapperInterface; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; /** * A data mapper using property paths to read/write data. * * @author Bernhard Schussek */ class PropertyPathMapper implements DataMapperInterface { /** * @var PropertyAccessorInterface */ private $propertyAccessor; /** * Creates a new property path mapper. * * @param PropertyAccessorInterface $propertyAccessor */ public function __construct(PropertyAccessorInterface $propertyAccessor = null) { $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); } /** * {@inheritdoc} */ public function mapDataToForms($data, $forms) { $empty = null === $data || array() === $data; if (!$empty && !is_array($data) && !is_object($data)) { throw new UnexpectedTypeException($data, 'object, array or empty'); } foreach ($forms as $form) { $propertyPath = $form->getPropertyPath(); $config = $form->getConfig(); if (!$empty && null !== $propertyPath && $config->getMapped()) { $form->setData($this->propertyAccessor->getValue($data, $propertyPath)); } else { $form->setData($form->getConfig()->getData()); } } } /** * {@inheritdoc} */ public function mapFormsToData($forms, &$data) { if (null === $data) { return; } if (!is_array($data) && !is_object($data)) { throw new UnexpectedTypeException($data, 'object, array or empty'); } foreach ($forms as $form) { $propertyPath = $form->getPropertyPath(); $config = $form->getConfig(); // Write-back is disabled if the form is not synchronized (transformation failed), // if the form was not submitted and if the form is disabled (modification not allowed) if (null !== $propertyPath && $config->getMapped() && $form->isSubmitted() && $form->isSynchronized() && !$form->isDisabled()) { // If the field is of type DateTime and the data is the same skip the update to // keep the original object hash if ($form->getData() instanceof \DateTime && $form->getData() == $this->propertyAccessor->getValue($data, $propertyPath)) { continue; } // If the data is identical to the value in $data, we are // dealing with a reference if (!is_object($data) || !$config->getByReference() || $form->getData() !== $this->propertyAccessor->getValue($data, $propertyPath)) { $this->propertyAccessor->setValue($data, $propertyPath, $form->getData()); } } } } } Core/View/ChoiceView.php000064400000002120152405076560011111 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\View; /** * Represents a choice in templates. * * @author Bernhard Schussek */ class ChoiceView { /** * The original choice value. * * @var mixed */ public $data; /** * The view representation of the choice. * * @var string */ public $value; /** * The label displayed to humans. * * @var string */ public $label; /** * Creates a new ChoiceView. * * @param mixed $data The original choice. * @param string $value The view representation of the choice. * @param string $label The label displayed to humans. */ public function __construct($data, $value, $label) { $this->data = $data; $this->value = $value; $this->label = $label; } } Core/Type/IntegerType.php000064400000004140152405076560011336 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\Extension\Core\DataTransformer\IntegerToLocalizedStringTransformer; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class IntegerType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addViewTransformer( new IntegerToLocalizedStringTransformer( $options['precision'], $options['grouping'], $options['rounding_mode'] )); } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( // default precision is locale specific (usually around 3) 'precision' => null, 'grouping' => false, // Integer cast rounds towards 0, so do the same when displaying fractions 'rounding_mode' => IntegerToLocalizedStringTransformer::ROUND_DOWN, 'compound' => false, )); $resolver->setAllowedValues(array( 'rounding_mode' => array( IntegerToLocalizedStringTransformer::ROUND_FLOOR, IntegerToLocalizedStringTransformer::ROUND_DOWN, IntegerToLocalizedStringTransformer::ROUND_HALF_DOWN, IntegerToLocalizedStringTransformer::ROUND_HALF_EVEN, IntegerToLocalizedStringTransformer::ROUND_HALF_UP, IntegerToLocalizedStringTransformer::ROUND_UP, IntegerToLocalizedStringTransformer::ROUND_CEILING, ), )); } /** * {@inheritdoc} */ public function getName() { return 'integer'; } } Core/Type/CountryType.php000064400000001642152405076560011410 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Intl\Intl; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class CountryType extends AbstractType { /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'choices' => Intl::getRegionBundle()->getCountryNames(), )); } /** * {@inheritdoc} */ public function getParent() { return 'choice'; } /** * {@inheritdoc} */ public function getName() { return 'country'; } } Core/Type/SubmitType.php000064400000001735152405076560011213 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\SubmitButtonTypeInterface; /** * A submit button. * * @author Bernhard Schussek */ class SubmitType extends AbstractType implements SubmitButtonTypeInterface { public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['clicked'] = $form->isClicked(); } /** * {@inheritdoc} */ public function getParent() { return 'button'; } /** * {@inheritdoc} */ public function getName() { return 'submit'; } } Core/Type/SearchType.php000064400000001112152405076560011142 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; class SearchType extends AbstractType { /** * {@inheritdoc} */ public function getParent() { return 'text'; } /** * {@inheritdoc} */ public function getName() { return 'search'; } } Core/Type/LanguageType.php000064400000001647152405076560011475 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Intl\Intl; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class LanguageType extends AbstractType { /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'choices' => Intl::getLanguageBundle()->getLanguageNames(), )); } /** * {@inheritdoc} */ public function getParent() { return 'choice'; } /** * {@inheritdoc} */ public function getName() { return 'language'; } } Core/Type/ResetType.php000064400000001352152405076560011025 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\ButtonTypeInterface; /** * A reset button. * * @author Bernhard Schussek */ class ResetType extends AbstractType implements ButtonTypeInterface { /** * {@inheritdoc} */ public function getParent() { return 'button'; } /** * {@inheritdoc} */ public function getName() { return 'reset'; } } Core/Type/HiddenType.php000064400000001652152405076560011141 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class HiddenType extends AbstractType { /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( // hidden fields cannot have a required attribute 'required' => false, // Pass errors to the parent 'error_bubbling' => true, 'compound' => false, )); } /** * {@inheritdoc} */ public function getName() { return 'hidden'; } } Core/Type/DateTimeType.php000064400000023630152405076560011442 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\ReversedTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DataTransformerChain; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToRfc3339Transformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ArrayToPartsTransformer; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class DateTimeType extends AbstractType { const DEFAULT_DATE_FORMAT = \IntlDateFormatter::MEDIUM; const DEFAULT_TIME_FORMAT = \IntlDateFormatter::MEDIUM; /** * This is not quite the HTML5 format yet, because ICU lacks the * capability of parsing and generating RFC 3339 dates, which * are like the below pattern but with a timezone suffix. The * timezone suffix is * * * "Z" for UTC * * "(-|+)HH:mm" for other timezones (note the colon!) * * For more information see: * * http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax * http://www.w3.org/TR/html-markup/input.datetime.html * http://tools.ietf.org/html/rfc3339 * * An ICU ticket was created: * http://icu-project.org/trac/ticket/9421 * * It was supposedly fixed, but is not available in all PHP installations * yet. To temporarily circumvent this issue, DateTimeToRfc3339Transformer * is used when the format matches this constant. */ const HTML5_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"; private static $acceptedFormats = array( \IntlDateFormatter::FULL, \IntlDateFormatter::LONG, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, ); /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $parts = array('year', 'month', 'day', 'hour'); $dateParts = array('year', 'month', 'day'); $timeParts = array('hour'); if ($options['with_minutes']) { $parts[] = 'minute'; $timeParts[] = 'minute'; } if ($options['with_seconds']) { $parts[] = 'second'; $timeParts[] = 'second'; } $dateFormat = is_int($options['date_format']) ? $options['date_format'] : self::DEFAULT_DATE_FORMAT; $timeFormat = self::DEFAULT_TIME_FORMAT; $calendar = \IntlDateFormatter::GREGORIAN; $pattern = is_string($options['format']) ? $options['format'] : null; if (!in_array($dateFormat, self::$acceptedFormats, true)) { throw new InvalidOptionsException('The "date_format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.'); } if ('single_text' === $options['widget']) { if (self::HTML5_FORMAT === $pattern) { $builder->addViewTransformer(new DateTimeToRfc3339Transformer( $options['model_timezone'], $options['view_timezone'] )); } else { $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer( $options['model_timezone'], $options['view_timezone'], $dateFormat, $timeFormat, $calendar, $pattern )); } } else { // Only pass a subset of the options to children $dateOptions = array_intersect_key($options, array_flip(array( 'years', 'months', 'days', 'empty_value', 'required', 'translation_domain', ))); $timeOptions = array_intersect_key($options, array_flip(array( 'hours', 'minutes', 'seconds', 'with_minutes', 'with_seconds', 'empty_value', 'required', 'translation_domain', ))); if (null !== $options['date_widget']) { $dateOptions['widget'] = $options['date_widget']; } if (null !== $options['time_widget']) { $timeOptions['widget'] = $options['time_widget']; } if (null !== $options['date_format']) { $dateOptions['format'] = $options['date_format']; } $dateOptions['input'] = $timeOptions['input'] = 'array'; $dateOptions['error_bubbling'] = $timeOptions['error_bubbling'] = true; $builder ->addViewTransformer(new DataTransformerChain(array( new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts), new ArrayToPartsTransformer(array( 'date' => $dateParts, 'time' => $timeParts, )), ))) ->add('date', 'date', $dateOptions) ->add('time', 'time', $timeOptions) ; } if ('string' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone']) )); } elseif ('timestamp' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone']) )); } elseif ('array' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts) )); } } /** * {@inheritdoc} */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['widget'] = $options['widget']; // Change the input to a HTML5 date input if // * the widget is set to "single_text" // * the format matches the one expected by HTML5 if ('single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) { $view->vars['type'] = 'datetime'; } } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $compound = function (Options $options) { return $options['widget'] !== 'single_text'; }; // Defaults to the value of "widget" $dateWidget = function (Options $options) { return $options['widget']; }; // Defaults to the value of "widget" $timeWidget = function (Options $options) { return $options['widget']; }; $resolver->setDefaults(array( 'input' => 'datetime', 'model_timezone' => null, 'view_timezone' => null, 'format' => self::HTML5_FORMAT, 'date_format' => null, 'widget' => null, 'date_widget' => $dateWidget, 'time_widget' => $timeWidget, 'with_minutes' => true, 'with_seconds' => false, // Don't modify \DateTime classes by reference, we treat // them like immutable value objects 'by_reference' => false, 'error_bubbling' => false, // If initialized with a \DateTime object, FormType initializes // this option to "\DateTime". Since the internal, normalized // representation is not \DateTime, but an array, we need to unset // this option. 'data_class' => null, 'compound' => $compound, )); // Don't add some defaults in order to preserve the defaults // set in DateType and TimeType $resolver->setOptional(array( 'empty_value', 'years', 'months', 'days', 'hours', 'minutes', 'seconds', )); $resolver->setAllowedValues(array( 'input' => array( 'datetime', 'string', 'timestamp', 'array', ), 'date_widget' => array( null, // inherit default from DateType 'single_text', 'text', 'choice', ), 'time_widget' => array( null, // inherit default from TimeType 'single_text', 'text', 'choice', ), // This option will overwrite "date_widget" and "time_widget" options 'widget' => array( null, // default, don't overwrite options 'single_text', 'text', 'choice', ), )); } /** * {@inheritdoc} */ public function getName() { return 'datetime'; } } Core/Type/FileType.php000064400000002557152405076560010632 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class FileType extends AbstractType { /** * {@inheritdoc} */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars = array_replace($view->vars, array( 'type' => 'file', 'value' => '', )); } /** * {@inheritdoc} */ public function finishView(FormView $view, FormInterface $form, array $options) { $view ->vars['multipart'] = true ; } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'compound' => false, 'data_class' => 'Symfony\Component\HttpFoundation\File\File', 'empty_data' => null, )); } /** * {@inheritdoc} */ public function getName() { return 'file'; } } Core/Type/EmailType.php000064400000001110152405076560010762 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; class EmailType extends AbstractType { /** * {@inheritdoc} */ public function getParent() { return 'text'; } /** * {@inheritdoc} */ public function getName() { return 'email'; } } Core/Type/RadioType.php000064400000001114152405076560010775 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; class RadioType extends AbstractType { /** * {@inheritdoc} */ public function getParent() { return 'checkbox'; } /** * {@inheritdoc} */ public function getName() { return 'radio'; } } Core/Type/RepeatedType.php000064400000004016152405076560011474 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\Extension\Core\DataTransformer\ValueToDuplicatesTransformer; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class RepeatedType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { // Overwrite required option for child fields $options['first_options']['required'] = $options['required']; $options['second_options']['required'] = $options['required']; if (!isset($options['options']['error_bubbling'])) { $options['options']['error_bubbling'] = $options['error_bubbling']; } $builder ->addViewTransformer(new ValueToDuplicatesTransformer(array( $options['first_name'], $options['second_name'], ))) ->add($options['first_name'], $options['type'], array_merge($options['options'], $options['first_options'])) ->add($options['second_name'], $options['type'], array_merge($options['options'], $options['second_options'])) ; } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'type' => 'text', 'options' => array(), 'first_options' => array(), 'second_options' => array(), 'first_name' => 'first', 'second_name' => 'second', 'error_bubbling' => false, )); } /** * {@inheritdoc} */ public function getName() { return 'repeated'; } } Core/Type/BaseType.php000064400000010507152405076560010617 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolverInterface; /** * Encapsulates common logic of {@link FormType} and {@link ButtonType}. * * This type does not appear in the form's type inheritance chain and as such * cannot be extended (via {@link FormTypeExtension}s) nor themed. * * @author Bernhard Schussek */ abstract class BaseType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->setDisabled($options['disabled']); $builder->setAutoInitialize($options['auto_initialize']); } /** * {@inheritdoc} */ public function buildView(FormView $view, FormInterface $form, array $options) { $name = $form->getName(); $blockName = $options['block_name'] ?: $form->getName(); $translationDomain = $options['translation_domain']; if ($view->parent) { if ('' !== ($parentFullName = $view->parent->vars['full_name'])) { $id = sprintf('%s_%s', $view->parent->vars['id'], $name); $fullName = sprintf('%s[%s]', $parentFullName, $name); $uniqueBlockPrefix = sprintf('%s_%s', $view->parent->vars['unique_block_prefix'], $blockName); } else { $id = $name; $fullName = $name; $uniqueBlockPrefix = '_'.$blockName; } if (!$translationDomain) { $translationDomain = $view->parent->vars['translation_domain']; } } else { $id = $name; $fullName = $name; $uniqueBlockPrefix = '_'.$blockName; // Strip leading underscores and digits. These are allowed in // form names, but not in HTML4 ID attributes. // http://www.w3.org/TR/html401/struct/global.html#adef-id $id = ltrim($id, '_0123456789'); } $blockPrefixes = array(); for ($type = $form->getConfig()->getType(); null !== $type; $type = $type->getParent()) { array_unshift($blockPrefixes, $type->getName()); } $blockPrefixes[] = $uniqueBlockPrefix; if (!$translationDomain) { $translationDomain = 'messages'; } $view->vars = array_replace($view->vars, array( 'form' => $view, 'id' => $id, 'name' => $name, 'full_name' => $fullName, 'disabled' => $form->isDisabled(), 'label' => $options['label'], 'multipart' => false, 'attr' => $options['attr'], 'block_prefixes' => $blockPrefixes, 'unique_block_prefix' => $uniqueBlockPrefix, 'translation_domain' => $translationDomain, // Using the block name here speeds up performance in collection // forms, where each entry has the same full block name. // Including the type is important too, because if rows of a // collection form have different types (dynamically), they should // be rendered differently. // https://github.com/symfony/symfony/issues/5038 'cache_key' => $uniqueBlockPrefix.'_'.$form->getConfig()->getType()->getName(), )); } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'block_name' => null, 'disabled' => false, 'label' => null, 'attr' => array(), 'translation_domain' => null, 'auto_initialize' => true, )); $resolver->setAllowedTypes(array( 'attr' => 'array', )); } } Core/Type/BirthdayType.php000064400000001566152405076560011520 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class BirthdayType extends AbstractType { /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'years' => range(date('Y') - 120, date('Y')), )); } /** * {@inheritdoc} */ public function getParent() { return 'date'; } /** * {@inheritdoc} */ public function getName() { return 'birthday'; } } Core/Type/TimeType.php000064400000017366152405076560010655 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\ReversedTransformer; use Symfony\Component\Form\Exception\InvalidConfigurationException; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class TimeType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $parts = array('hour'); $format = 'H'; if ($options['with_seconds'] && !$options['with_minutes']) { throw new InvalidConfigurationException('You can not disable minutes if you have enabled seconds.'); } if ($options['with_minutes']) { $format .= ':i'; $parts[] = 'minute'; } if ($options['with_seconds']) { $format .= ':s'; $parts[] = 'second'; } if ('single_text' === $options['widget']) { $builder->addViewTransformer(new DateTimeToStringTransformer($options['model_timezone'], $options['view_timezone'], $format)); } else { $hourOptions = $minuteOptions = $secondOptions = array( 'error_bubbling' => true, ); if ('choice' === $options['widget']) { $hours = $minutes = array(); foreach ($options['hours'] as $hour) { $hours[$hour] = str_pad($hour, 2, '0', STR_PAD_LEFT); } // Only pass a subset of the options to children $hourOptions['choices'] = $hours; $hourOptions['empty_value'] = $options['empty_value']['hour']; if ($options['with_minutes']) { foreach ($options['minutes'] as $minute) { $minutes[$minute] = str_pad($minute, 2, '0', STR_PAD_LEFT); } $minuteOptions['choices'] = $minutes; $minuteOptions['empty_value'] = $options['empty_value']['minute']; } if ($options['with_seconds']) { $seconds = array(); foreach ($options['seconds'] as $second) { $seconds[$second] = str_pad($second, 2, '0', STR_PAD_LEFT); } $secondOptions['choices'] = $seconds; $secondOptions['empty_value'] = $options['empty_value']['second']; } // Append generic carry-along options foreach (array('required', 'translation_domain') as $passOpt) { $hourOptions[$passOpt] = $options[$passOpt]; if ($options['with_minutes']) { $minuteOptions[$passOpt] = $options[$passOpt]; } if ($options['with_seconds']) { $secondOptions[$passOpt] = $options[$passOpt]; } } } $builder->add('hour', $options['widget'], $hourOptions); if ($options['with_minutes']) { $builder->add('minute', $options['widget'], $minuteOptions); } if ($options['with_seconds']) { $builder->add('second', $options['widget'], $secondOptions); } $builder->addViewTransformer(new DateTimeToArrayTransformer($options['model_timezone'], $options['view_timezone'], $parts, 'text' === $options['widget'])); } if ('string' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone'], 'H:i:s') )); } elseif ('timestamp' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone']) )); } elseif ('array' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], $parts) )); } } /** * {@inheritdoc} */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars = array_replace($view->vars, array( 'widget' => $options['widget'], 'with_minutes' => $options['with_minutes'], 'with_seconds' => $options['with_seconds'], )); if ('single_text' === $options['widget']) { $view->vars['type'] = 'time'; } } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $compound = function (Options $options) { return $options['widget'] !== 'single_text'; }; $emptyValue = $emptyValueDefault = function (Options $options) { return $options['required'] ? null : ''; }; $emptyValueNormalizer = function (Options $options, $emptyValue) use ($emptyValueDefault) { if (is_array($emptyValue)) { $default = $emptyValueDefault($options); return array_merge( array('hour' => $default, 'minute' => $default, 'second' => $default), $emptyValue ); } return array( 'hour' => $emptyValue, 'minute' => $emptyValue, 'second' => $emptyValue ); }; $resolver->setDefaults(array( 'hours' => range(0, 23), 'minutes' => range(0, 59), 'seconds' => range(0, 59), 'widget' => 'choice', 'input' => 'datetime', 'with_minutes' => true, 'with_seconds' => false, 'model_timezone' => null, 'view_timezone' => null, 'empty_value' => $emptyValue, // Don't modify \DateTime classes by reference, we treat // them like immutable value objects 'by_reference' => false, 'error_bubbling' => false, // If initialized with a \DateTime object, FormType initializes // this option to "\DateTime". Since the internal, normalized // representation is not \DateTime, but an array, we need to unset // this option. 'data_class' => null, 'compound' => $compound, )); $resolver->setNormalizers(array( 'empty_value' => $emptyValueNormalizer, )); $resolver->setAllowedValues(array( 'input' => array( 'datetime', 'string', 'timestamp', 'array', ), 'widget' => array( 'single_text', 'text', 'choice', ), )); } /** * {@inheritdoc} */ public function getName() { return 'time'; } } Core/Type/DateType.php000064400000026063152405076560010626 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToArrayTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer; use Symfony\Component\Form\ReversedTransformer; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; class DateType extends AbstractType { const DEFAULT_FORMAT = \IntlDateFormatter::MEDIUM; const HTML5_FORMAT = 'yyyy-MM-dd'; private static $acceptedFormats = array( \IntlDateFormatter::FULL, \IntlDateFormatter::LONG, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, ); /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $dateFormat = is_int($options['format']) ? $options['format'] : self::DEFAULT_FORMAT; $timeFormat = \IntlDateFormatter::NONE; $calendar = \IntlDateFormatter::GREGORIAN; $pattern = is_string($options['format']) ? $options['format'] : null; if (!in_array($dateFormat, self::$acceptedFormats, true)) { throw new InvalidOptionsException('The "format" option must be one of the IntlDateFormatter constants (FULL, LONG, MEDIUM, SHORT) or a string representing a custom format.'); } if (null !== $pattern && (false === strpos($pattern, 'y') || false === strpos($pattern, 'M') || false === strpos($pattern, 'd'))) { throw new InvalidOptionsException(sprintf('The "format" option should contain the letters "y", "M" and "d". Its current value is "%s".', $pattern)); } if ('single_text' === $options['widget']) { $builder->addViewTransformer(new DateTimeToLocalizedStringTransformer( $options['model_timezone'], $options['view_timezone'], $dateFormat, $timeFormat, $calendar, $pattern )); } else { $yearOptions = $monthOptions = $dayOptions = array( 'error_bubbling' => true, ); $formatter = new \IntlDateFormatter( \Locale::getDefault(), $dateFormat, $timeFormat, 'UTC', $calendar, $pattern ); $formatter->setLenient(false); if ('choice' === $options['widget']) { // Only pass a subset of the options to children $yearOptions['choices'] = $this->formatTimestamps($formatter, '/y+/', $this->listYears($options['years'])); $yearOptions['empty_value'] = $options['empty_value']['year']; $monthOptions['choices'] = $this->formatTimestamps($formatter, '/[M|L]+/', $this->listMonths($options['months'])); $monthOptions['empty_value'] = $options['empty_value']['month']; $dayOptions['choices'] = $this->formatTimestamps($formatter, '/d+/', $this->listDays($options['days'])); $dayOptions['empty_value'] = $options['empty_value']['day']; } // Append generic carry-along options foreach (array('required', 'translation_domain') as $passOpt) { $yearOptions[$passOpt] = $monthOptions[$passOpt] = $dayOptions[$passOpt] = $options[$passOpt]; } $builder ->add('year', $options['widget'], $yearOptions) ->add('month', $options['widget'], $monthOptions) ->add('day', $options['widget'], $dayOptions) ->addViewTransformer(new DateTimeToArrayTransformer( $options['model_timezone'], $options['view_timezone'], array('year', 'month', 'day') )) ->setAttribute('formatter', $formatter) ; } if ('string' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( new DateTimeToStringTransformer($options['model_timezone'], $options['model_timezone'], 'Y-m-d') )); } elseif ('timestamp' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( new DateTimeToTimestampTransformer($options['model_timezone'], $options['model_timezone']) )); } elseif ('array' === $options['input']) { $builder->addModelTransformer(new ReversedTransformer( new DateTimeToArrayTransformer($options['model_timezone'], $options['model_timezone'], array('year', 'month', 'day')) )); } } /** * {@inheritdoc} */ public function finishView(FormView $view, FormInterface $form, array $options) { $view->vars['widget'] = $options['widget']; // Change the input to a HTML5 date input if // * the widget is set to "single_text" // * the format matches the one expected by HTML5 if ('single_text' === $options['widget'] && self::HTML5_FORMAT === $options['format']) { $view->vars['type'] = 'date'; } if ($form->getConfig()->hasAttribute('formatter')) { $pattern = $form->getConfig()->getAttribute('formatter')->getPattern(); // remove special characters unless the format was explicitly specified if (!is_string($options['format'])) { $pattern = preg_replace('/[^yMd]+/', '', $pattern); } // set right order with respect to locale (e.g.: de_DE=dd.MM.yy; en_US=M/d/yy) // lookup various formats at http://userguide.icu-project.org/formatparse/datetime if (preg_match('/^([yMd]+)[^yMd]*([yMd]+)[^yMd]*([yMd]+)$/', $pattern)) { $pattern = preg_replace(array('/y+/', '/M+/', '/d+/'), array('{{ year }}', '{{ month }}', '{{ day }}'), $pattern); } else { // default fallback $pattern = '{{ year }}{{ month }}{{ day }}'; } $view->vars['date_pattern'] = $pattern; } } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $compound = function (Options $options) { return $options['widget'] !== 'single_text'; }; $emptyValue = $emptyValueDefault = function (Options $options) { return $options['required'] ? null : ''; }; $emptyValueNormalizer = function (Options $options, $emptyValue) use ($emptyValueDefault) { if (is_array($emptyValue)) { $default = $emptyValueDefault($options); return array_merge( array('year' => $default, 'month' => $default, 'day' => $default), $emptyValue ); } return array( 'year' => $emptyValue, 'month' => $emptyValue, 'day' => $emptyValue ); }; $format = function (Options $options) { return $options['widget'] === 'single_text' ? DateType::HTML5_FORMAT : DateType::DEFAULT_FORMAT; }; $resolver->setDefaults(array( 'years' => range(date('Y') - 5, date('Y') + 5), 'months' => range(1, 12), 'days' => range(1, 31), 'widget' => 'choice', 'input' => 'datetime', 'format' => $format, 'model_timezone' => null, 'view_timezone' => null, 'empty_value' => $emptyValue, // Don't modify \DateTime classes by reference, we treat // them like immutable value objects 'by_reference' => false, 'error_bubbling' => false, // If initialized with a \DateTime object, FormType initializes // this option to "\DateTime". Since the internal, normalized // representation is not \DateTime, but an array, we need to unset // this option. 'data_class' => null, 'compound' => $compound, )); $resolver->setNormalizers(array( 'empty_value' => $emptyValueNormalizer, )); $resolver->setAllowedValues(array( 'input' => array( 'datetime', 'string', 'timestamp', 'array', ), 'widget' => array( 'single_text', 'text', 'choice', ), )); $resolver->setAllowedTypes(array( 'format' => array('int', 'string'), )); } /** * {@inheritdoc} */ public function getName() { return 'date'; } private function formatTimestamps(\IntlDateFormatter $formatter, $regex, array $timestamps) { $pattern = $formatter->getPattern(); $timezone = $formatter->getTimezoneId(); if ($setTimeZone = method_exists($formatter, 'setTimeZone')) { $formatter->setTimeZone('UTC'); } else { $formatter->setTimeZoneId('UTC'); } if (preg_match($regex, $pattern, $matches)) { $formatter->setPattern($matches[0]); foreach ($timestamps as $key => $timestamp) { $timestamps[$key] = $formatter->format($timestamp); } // I'd like to clone the formatter above, but then we get a // segmentation fault, so let's restore the old state instead $formatter->setPattern($pattern); } if ($setTimeZone) { $formatter->setTimeZone($timezone); } else { $formatter->setTimeZoneId($timezone); } return $timestamps; } private function listYears(array $years) { $result = array(); foreach ($years as $year) { if (false !== $y = gmmktime(0, 0, 0, 6, 15, $year)) { $result[$year] = $y; } } return $result; } private function listMonths(array $months) { $result = array(); foreach ($months as $month) { $result[$month] = gmmktime(0, 0, 0, $month, 15); } return $result; } private function listDays(array $days) { $result = array(); foreach ($days as $day) { $result[$day] = gmmktime(0, 0, 0, 5, $day); } return $result; } } Core/Type/PasswordType.php000064400000002313152405076560011543 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class PasswordType extends AbstractType { /** * {@inheritdoc} */ public function buildView(FormView $view, FormInterface $form, array $options) { if ($options['always_empty'] || !$form->isSubmitted()) { $view->vars['value'] = ''; } } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'always_empty' => true, 'trim' => false, )); } /** * {@inheritdoc} */ public function getParent() { return 'text'; } /** * {@inheritdoc} */ public function getName() { return 'password'; } } Core/Type/TextType.php000064400000001354152405076560010671 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class TextType extends AbstractType { /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'compound' => false, )); } /** * {@inheritdoc} */ public function getName() { return 'text'; } } Core/Type/UrlType.php000064400000002274152405076560010511 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\Extension\Core\EventListener\FixUrlProtocolListener; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class UrlType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addEventSubscriber(new FixUrlProtocolListener($options['default_protocol'])); } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'default_protocol' => 'http', )); } /** * {@inheritdoc} */ public function getParent() { return 'text'; } /** * {@inheritdoc} */ public function getName() { return 'url'; } } Core/Type/MoneyType.php000064400000006213152405076560011033 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\Extension\Core\DataTransformer\MoneyToLocalizedStringTransformer; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class MoneyType extends AbstractType { protected static $patterns = array(); /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->addViewTransformer(new MoneyToLocalizedStringTransformer( $options['precision'], $options['grouping'], null, $options['divisor'] )) ; } /** * {@inheritdoc} */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['money_pattern'] = self::getPattern($options['currency']); } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'precision' => 2, 'grouping' => false, 'divisor' => 1, 'currency' => 'EUR', 'compound' => false, )); } /** * {@inheritdoc} */ public function getName() { return 'money'; } /** * Returns the pattern for this locale * * The pattern contains the placeholder "{{ widget }}" where the HTML tag should * be inserted */ protected static function getPattern($currency) { if (!$currency) { return '{{ widget }}'; } $locale = \Locale::getDefault(); if (!isset(self::$patterns[$locale])) { self::$patterns[$locale] = array(); } if (!isset(self::$patterns[$locale][$currency])) { $format = new \NumberFormatter($locale, \NumberFormatter::CURRENCY); $pattern = $format->formatCurrency('123', $currency); // the spacings between currency symbol and number are ignored, because // a single space leads to better readability in combination with input // fields // the regex also considers non-break spaces (0xC2 or 0xA0 in UTF-8) preg_match('/^([^\s\xc2\xa0]*)[\s\xc2\xa0]*123(?:[,.]0+)?[\s\xc2\xa0]*([^\s\xc2\xa0]*)$/u', $pattern, $matches); if (!empty($matches[1])) { self::$patterns[$locale][$currency] = $matches[1].' {{ widget }}'; } elseif (!empty($matches[2])) { self::$patterns[$locale][$currency] = '{{ widget }} '.$matches[2]; } else { self::$patterns[$locale][$currency] = '{{ widget }}'; } } return self::$patterns[$locale][$currency]; } } Core/Type/TextareaType.php000064400000001511152405076560011515 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; class TextareaType extends AbstractType { /** * {@inheritdoc} */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars['pattern'] = null; } /** * {@inheritdoc} */ public function getParent() { return 'text'; } /** * {@inheritdoc} */ public function getName() { return 'textarea'; } } Core/Type/ChoiceType.php000064400000025644152405076560011147 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\View\ChoiceView; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\Exception\LogicException; use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList; use Symfony\Component\Form\Extension\Core\EventListener\FixRadioInputListener; use Symfony\Component\Form\Extension\Core\EventListener\FixCheckboxInputListener; use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToBooleanArrayTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToBooleanArrayTransformer; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class ChoiceType extends AbstractType { /** * Caches created choice lists. * @var array */ private $choiceListCache = array(); /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { if (!$options['choice_list'] && !is_array($options['choices']) && !$options['choices'] instanceof \Traversable) { throw new LogicException('Either the option "choices" or "choice_list" must be set.'); } if ($options['expanded']) { // Initialize all choices before doing the index check below. // This helps in cases where index checks are optimized for non // initialized choice lists. For example, when using an SQL driver, // the index check would read in one SQL query and the initialization // requires another SQL query. When the initialization is done first, // one SQL query is sufficient. $preferredViews = $options['choice_list']->getPreferredViews(); $remainingViews = $options['choice_list']->getRemainingViews(); // Check if the choices already contain the empty value // Only add the empty value option if this is not the case if (null !== $options['empty_value'] && 0 === count($options['choice_list']->getChoicesForValues(array('')))) { $placeholderView = new ChoiceView(null, '', $options['empty_value']); // "placeholder" is a reserved index // see also ChoiceListInterface::getIndicesForChoices() $this->addSubForms($builder, array('placeholder' => $placeholderView), $options); } $this->addSubForms($builder, $preferredViews, $options); $this->addSubForms($builder, $remainingViews, $options); if ($options['multiple']) { $builder->addViewTransformer(new ChoicesToBooleanArrayTransformer($options['choice_list'])); $builder->addEventSubscriber(new FixCheckboxInputListener($options['choice_list']), 10); } else { $builder->addViewTransformer(new ChoiceToBooleanArrayTransformer($options['choice_list'], $builder->has('placeholder'))); $builder->addEventSubscriber(new FixRadioInputListener($options['choice_list'], $builder->has('placeholder')), 10); } } else { if ($options['multiple']) { $builder->addViewTransformer(new ChoicesToValuesTransformer($options['choice_list'])); } else { $builder->addViewTransformer(new ChoiceToValueTransformer($options['choice_list'])); } } if ($options['multiple'] && $options['by_reference']) { // Make sure the collection created during the client->norm // transformation is merged back into the original collection $builder->addEventSubscriber(new MergeCollectionListener(true, true)); } } /** * {@inheritdoc} */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars = array_replace($view->vars, array( 'multiple' => $options['multiple'], 'expanded' => $options['expanded'], 'preferred_choices' => $options['choice_list']->getPreferredViews(), 'choices' => $options['choice_list']->getRemainingViews(), 'separator' => '-------------------', 'empty_value' => null, )); // The decision, whether a choice is selected, is potentially done // thousand of times during the rendering of a template. Provide a // closure here that is optimized for the value of the form, to // avoid making the type check inside the closure. if ($options['multiple']) { $view->vars['is_selected'] = function ($choice, array $values) { return false !== array_search($choice, $values, true); }; } else { $view->vars['is_selected'] = function ($choice, $value) { return $choice === $value; }; } // Check if the choices already contain the empty value $view->vars['empty_value_in_choices'] = 0 !== count($options['choice_list']->getChoicesForValues(array(''))); // Only add the empty value option if this is not the case if (null !== $options['empty_value'] && !$view->vars['empty_value_in_choices']) { $view->vars['empty_value'] = $options['empty_value']; } if ($options['multiple'] && !$options['expanded']) { // Add "[]" to the name in case a select tag with multiple options is // displayed. Otherwise only one of the selected options is sent in the // POST request. $view->vars['full_name'] = $view->vars['full_name'].'[]'; } } /** * {@inheritdoc} */ public function finishView(FormView $view, FormInterface $form, array $options) { if ($options['expanded']) { // Radio buttons should have the same name as the parent $childName = $view->vars['full_name']; // Checkboxes should append "[]" to allow multiple selection if ($options['multiple']) { $childName .= '[]'; } foreach ($view as $childView) { $childView->vars['full_name'] = $childName; } } } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $choiceListCache =& $this->choiceListCache; $choiceList = function (Options $options) use (&$choiceListCache) { // Harden against NULL values (like in EntityType and ModelType) $choices = null !== $options['choices'] ? $options['choices'] : array(); // Reuse existing choice lists in order to increase performance $hash = hash('sha256', json_encode(array($choices, $options['preferred_choices']))); if (!isset($choiceListCache[$hash])) { $choiceListCache[$hash] = new SimpleChoiceList($choices, $options['preferred_choices']); } return $choiceListCache[$hash]; }; $emptyData = function (Options $options) { if ($options['multiple'] || $options['expanded']) { return array(); } return ''; }; $emptyValue = function (Options $options) { return $options['required'] ? null : ''; }; $emptyValueNormalizer = function (Options $options, $emptyValue) { if ($options['multiple']) { // never use an empty value for this case return null; } elseif (false === $emptyValue) { // an empty value should be added but the user decided otherwise return null; } elseif ($options['expanded'] && '' === $emptyValue) { // never use an empty label for radio buttons return 'None'; } // empty value has been set explicitly return $emptyValue; }; $compound = function (Options $options) { return $options['expanded']; }; $resolver->setDefaults(array( 'multiple' => false, 'expanded' => false, 'choice_list' => $choiceList, 'choices' => array(), 'preferred_choices' => array(), 'empty_data' => $emptyData, 'empty_value' => $emptyValue, 'error_bubbling' => false, 'compound' => $compound, // The view data is always a string, even if the "data" option // is manually set to an object. // See https://github.com/symfony/symfony/pull/5582 'data_class' => null, )); $resolver->setNormalizers(array( 'empty_value' => $emptyValueNormalizer, )); $resolver->setAllowedTypes(array( 'choice_list' => array('null', 'Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface'), )); } /** * {@inheritdoc} */ public function getName() { return 'choice'; } /** * Adds the sub fields for an expanded choice field. * * @param FormBuilderInterface $builder The form builder. * @param array $choiceViews The choice view objects. * @param array $options The build options. */ private function addSubForms(FormBuilderInterface $builder, array $choiceViews, array $options) { foreach ($choiceViews as $i => $choiceView) { if (is_array($choiceView)) { // Flatten groups $this->addSubForms($builder, $choiceView, $options); } else { $choiceOpts = array( 'value' => $choiceView->value, 'label' => $choiceView->label, 'translation_domain' => $options['translation_domain'], ); if ($options['multiple']) { $choiceType = 'checkbox'; // The user can check 0 or more checkboxes. If required // is true, he is required to check all of them. $choiceOpts['required'] = false; } else { $choiceType = 'radio'; } $builder->add($i, $choiceType, $choiceOpts); } } } } Core/Type/NumberType.php000064400000003747152405076560011205 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class NumberType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addViewTransformer(new NumberToLocalizedStringTransformer( $options['precision'], $options['grouping'], $options['rounding_mode'] )); } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( // default precision is locale specific (usually around 3) 'precision' => null, 'grouping' => false, 'rounding_mode' => NumberToLocalizedStringTransformer::ROUND_HALF_UP, 'compound' => false, )); $resolver->setAllowedValues(array( 'rounding_mode' => array( NumberToLocalizedStringTransformer::ROUND_FLOOR, NumberToLocalizedStringTransformer::ROUND_DOWN, NumberToLocalizedStringTransformer::ROUND_HALF_DOWN, NumberToLocalizedStringTransformer::ROUND_HALF_EVEN, NumberToLocalizedStringTransformer::ROUND_HALF_UP, NumberToLocalizedStringTransformer::ROUND_UP, NumberToLocalizedStringTransformer::ROUND_CEILING, ), )); } /** * {@inheritdoc} */ public function getName() { return 'number'; } } Core/Type/FormType.php000064400000016015152405076560010650 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\Extension\Core\EventListener\TrimListener; use Symfony\Component\Form\Extension\Core\DataMapper\PropertyPathMapper; use Symfony\Component\Form\Exception\LogicException; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; class FormType extends BaseType { /** * @var PropertyAccessorInterface */ private $propertyAccessor; public function __construct(PropertyAccessorInterface $propertyAccessor = null) { $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); } /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { parent::buildForm($builder, $options); $isDataOptionSet = array_key_exists('data', $options); $builder ->setRequired($options['required']) ->setErrorBubbling($options['error_bubbling']) ->setEmptyData($options['empty_data']) ->setPropertyPath($options['property_path']) ->setMapped($options['mapped']) ->setByReference($options['by_reference']) ->setInheritData($options['inherit_data']) ->setCompound($options['compound']) ->setData($isDataOptionSet ? $options['data'] : null) ->setDataLocked($isDataOptionSet) ->setDataMapper($options['compound'] ? new PropertyPathMapper($this->propertyAccessor) : null) ->setMethod($options['method']) ->setAction($options['action']) ; if ($options['trim']) { $builder->addEventSubscriber(new TrimListener()); } } /** * {@inheritdoc} */ public function buildView(FormView $view, FormInterface $form, array $options) { parent::buildView($view, $form, $options); $name = $form->getName(); $readOnly = $options['read_only']; if ($view->parent) { if ('' === $name) { throw new LogicException('Form node with empty name can be used only as root form node.'); } // Complex fields are read-only if they themselves or their parents are. if (!$readOnly) { $readOnly = $view->parent->vars['read_only']; } } $view->vars = array_replace($view->vars, array( 'read_only' => $readOnly, 'errors' => $form->getErrors(), 'valid' => $form->isSubmitted() ? $form->isValid() : true, 'value' => $form->getViewData(), 'data' => $form->getNormData(), 'required' => $form->isRequired(), 'max_length' => $options['max_length'], 'pattern' => $options['pattern'], 'size' => null, 'label_attr' => $options['label_attr'], 'compound' => $form->getConfig()->getCompound(), 'method' => $form->getConfig()->getMethod(), 'action' => $form->getConfig()->getAction(), 'submitted' => $form->isSubmitted(), )); } /** * {@inheritdoc} */ public function finishView(FormView $view, FormInterface $form, array $options) { $multipart = false; foreach ($view->children as $child) { if ($child->vars['multipart']) { $multipart = true; break; } } $view->vars['multipart'] = $multipart; } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { parent::setDefaultOptions($resolver); // Derive "data_class" option from passed "data" object $dataClass = function (Options $options) { return isset($options['data']) && is_object($options['data']) ? get_class($options['data']) : null; }; // Derive "empty_data" closure from "data_class" option $emptyData = function (Options $options) { $class = $options['data_class']; if (null !== $class) { return function (FormInterface $form) use ($class) { return $form->isEmpty() && !$form->isRequired() ? null : new $class(); }; } return function (FormInterface $form) { return $form->getConfig()->getCompound() ? array() : ''; }; }; // For any form that is not represented by a single HTML control, // errors should bubble up by default $errorBubbling = function (Options $options) { return $options['compound']; }; // BC with old "virtual" option $inheritData = function (Options $options) { if (null !== $options['virtual']) { // Uncomment this as soon as the deprecation note should be shown // trigger_error('The form option "virtual" is deprecated since version 2.3 and will be removed in 3.0. Use "inherit_data" instead.', E_USER_DEPRECATED); return $options['virtual']; } return false; }; // If data is given, the form is locked to that data // (independent of its value) $resolver->setOptional(array( 'data', )); $resolver->setDefaults(array( 'data_class' => $dataClass, 'empty_data' => $emptyData, 'trim' => true, 'required' => true, 'read_only' => false, 'max_length' => null, 'pattern' => null, 'property_path' => null, 'mapped' => true, 'by_reference' => true, 'error_bubbling' => $errorBubbling, 'label_attr' => array(), 'virtual' => null, 'inherit_data' => $inheritData, 'compound' => true, 'method' => 'POST', // According to RFC 2396 (http://www.ietf.org/rfc/rfc2396.txt) // section 4.2., empty URIs are considered same-document references 'action' => '', )); $resolver->setAllowedTypes(array( 'label_attr' => 'array', )); } /** * {@inheritdoc} */ public function getParent() { return null; } /** * {@inheritdoc} */ public function getName() { return 'form'; } } Core/Type/ButtonType.php000064400000001777152405076560011231 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\ButtonTypeInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; /** * A form button. * * @author Bernhard Schussek */ class ButtonType extends BaseType implements ButtonTypeInterface { /** * {@inheritdoc} */ public function getParent() { return null; } /** * {@inheritdoc} */ public function getName() { return 'button'; } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { parent::setDefaultOptions($resolver); $resolver->setDefaults(array( 'auto_initialize' => false, )); } } Core/Type/CurrencyType.php000064400000001647152405076560011544 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Intl\Intl; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class CurrencyType extends AbstractType { /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'choices' => Intl::getCurrencyBundle()->getCurrencyNames(), )); } /** * {@inheritdoc} */ public function getParent() { return 'choice'; } /** * {@inheritdoc} */ public function getName() { return 'currency'; } } Core/Type/PercentType.php000064400000002536152405076560011350 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\Extension\Core\DataTransformer\PercentToLocalizedStringTransformer; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class PercentType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addViewTransformer(new PercentToLocalizedStringTransformer($options['precision'], $options['type'])); } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'precision' => 0, 'type' => 'fractional', 'compound' => false, )); $resolver->setAllowedValues(array( 'type' => array( 'fractional', 'integer', ), )); } /** * {@inheritdoc} */ public function getName() { return 'percent'; } } Core/Type/LocaleType.php000064400000001704152405076560011143 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Intl\Intl; use Symfony\Component\Locale\Locale; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class LocaleType extends AbstractType { /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'choices' => Intl::getLocaleBundle()->getLocaleNames(), )); } /** * {@inheritdoc} */ public function getParent() { return 'choice'; } /** * {@inheritdoc} */ public function getName() { return 'locale'; } } Core/Type/TimezoneType.php000064400000004114152405076560011534 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class TimezoneType extends AbstractType { /** * Stores the available timezone choices * @var array */ private static $timezones; /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults(array( 'choices' => self::getTimezones(), )); } /** * {@inheritdoc} */ public function getParent() { return 'choice'; } /** * {@inheritdoc} */ public function getName() { return 'timezone'; } /** * Returns the timezone choices. * * The choices are generated from the ICU function * \DateTimeZone::listIdentifiers(). They are cached during a single request, * so multiple timezone fields on the same page don't lead to unnecessary * overhead. * * @return array The timezone choices */ public static function getTimezones() { if (null === static::$timezones) { static::$timezones = array(); foreach (\DateTimeZone::listIdentifiers() as $timezone) { $parts = explode('/', $timezone); if (count($parts) > 2) { $region = $parts[0]; $name = $parts[1].' - '.$parts[2]; } elseif (count($parts) > 1) { $region = $parts[0]; $name = $parts[1]; } else { $region = 'Other'; $name = $parts[0]; } static::$timezones[$region][$timezone] = str_replace('_', ' ', $name); } } return static::$timezones; } } Core/Type/CheckboxType.php000064400000004124152405076560011471 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\Extension\Core\DataTransformer\BooleanToStringTransformer; use Symfony\Component\Form\FormView; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class CheckboxType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { // Unlike in other types, where the data is NULL by default, it // needs to be a Boolean here. setData(null) is not acceptable // for checkboxes and radio buttons (unless a custom model // transformer handles this case). // We cannot solve this case via overriding the "data" option, because // doing so also calls setDataLocked(true). $builder->setData(isset($options['data']) ? $options['data'] : false); $builder->addViewTransformer(new BooleanToStringTransformer($options['value'])); } /** * {@inheritdoc} */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars = array_replace($view->vars, array( 'value' => $options['value'], 'checked' => null !== $form->getViewData(), )); } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $emptyData = function (FormInterface $form, $viewData) { return $viewData; }; $resolver->setDefaults(array( 'value' => '1', 'empty_data' => $emptyData, 'compound' => false, )); } /** * {@inheritdoc} */ public function getName() { return 'checkbox'; } } Core/Type/CollectionType.php000064400000005655152405076560012050 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\Type; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\Extension\Core\EventListener\ResizeFormListener; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class CollectionType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { if ($options['allow_add'] && $options['prototype']) { $prototype = $builder->create($options['prototype_name'], $options['type'], array_replace(array( 'label' => $options['prototype_name'].'label__', ), $options['options'])); $builder->setAttribute('prototype', $prototype->getForm()); } $resizeListener = new ResizeFormListener( $options['type'], $options['options'], $options['allow_add'], $options['allow_delete'] ); $builder->addEventSubscriber($resizeListener); } /** * {@inheritdoc} */ public function buildView(FormView $view, FormInterface $form, array $options) { $view->vars = array_replace($view->vars, array( 'allow_add' => $options['allow_add'], 'allow_delete' => $options['allow_delete'], )); if ($form->getConfig()->hasAttribute('prototype')) { $view->vars['prototype'] = $form->getConfig()->getAttribute('prototype')->createView($view); } } /** * {@inheritdoc} */ public function finishView(FormView $view, FormInterface $form, array $options) { if ($form->getConfig()->hasAttribute('prototype') && $view->vars['prototype']->vars['multipart']) { $view->vars['multipart'] = true; } } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { $optionsNormalizer = function (Options $options, $value) { $value['block_name'] = 'entry'; return $value; }; $resolver->setDefaults(array( 'allow_add' => false, 'allow_delete' => false, 'prototype' => true, 'prototype_name' => '__name__', 'type' => 'text', 'options' => array(), )); $resolver->setNormalizers(array( 'options' => $optionsNormalizer, )); } /** * {@inheritdoc} */ public function getName() { return 'collection'; } } Core/DataTransformer/DateTimeToLocalizedStringTransformer.php000064400000013042152405076560020513 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Exception\UnexpectedTypeException; /** * Transforms between a normalized time and a localized time string * * @author Bernhard Schussek * @author Florian Eckerstorfer */ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer { private $dateFormat; private $timeFormat; private $pattern; private $calendar; /** * Constructor. * * @see BaseDateTimeTransformer::formats for available format options * * @param string $inputTimezone The name of the input timezone * @param string $outputTimezone The name of the output timezone * @param integer $dateFormat The date format * @param integer $timeFormat The time format * @param integer $calendar One of the \IntlDateFormatter calendar constants * @param string $pattern A pattern to pass to \IntlDateFormatter * * @throws UnexpectedTypeException If a format is not supported or if a timezone is not a string */ public function __construct($inputTimezone = null, $outputTimezone = null, $dateFormat = null, $timeFormat = null, $calendar = \IntlDateFormatter::GREGORIAN, $pattern = null) { parent::__construct($inputTimezone, $outputTimezone); if (null === $dateFormat) { $dateFormat = \IntlDateFormatter::MEDIUM; } if (null === $timeFormat) { $timeFormat = \IntlDateFormatter::SHORT; } if (!in_array($dateFormat, self::$formats, true)) { throw new UnexpectedTypeException($dateFormat, implode('", "', self::$formats)); } if (!in_array($timeFormat, self::$formats, true)) { throw new UnexpectedTypeException($timeFormat, implode('", "', self::$formats)); } $this->dateFormat = $dateFormat; $this->timeFormat = $timeFormat; $this->calendar = $calendar; $this->pattern = $pattern; } /** * Transforms a normalized date into a localized date string/array. * * @param \DateTime $dateTime Normalized date. * * @return string|array Localized date string/array. * * @throws TransformationFailedException If the given value is not an instance * of \DateTime or if the date could not * be transformed. */ public function transform($dateTime) { if (null === $dateTime) { return ''; } if (!$dateTime instanceof \DateTime) { throw new TransformationFailedException('Expected a \DateTime.'); } // convert time to UTC before passing it to the formatter $dateTime = clone $dateTime; if ('UTC' !== $this->inputTimezone) { $dateTime->setTimezone(new \DateTimeZone('UTC')); } $value = $this->getIntlDateFormatter()->format((int) $dateTime->format('U')); if (intl_get_error_code() != 0) { throw new TransformationFailedException(intl_get_error_message()); } return $value; } /** * Transforms a localized date string/array into a normalized date. * * @param string|array $value Localized date string/array * * @return \DateTime Normalized date * * @throws TransformationFailedException if the given value is not a string, * if the date could not be parsed or * if the input timezone is not supported */ public function reverseTransform($value) { if (!is_string($value)) { throw new TransformationFailedException('Expected a string.'); } if ('' === $value) { return null; } $timestamp = $this->getIntlDateFormatter()->parse($value); if (intl_get_error_code() != 0) { throw new TransformationFailedException(intl_get_error_message()); } try { // read timestamp into DateTime object - the formatter delivers in UTC $dateTime = new \DateTime(sprintf('@%s UTC', $timestamp)); } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } if ('UTC' !== $this->inputTimezone) { try { $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } } return $dateTime; } /** * Returns a preconfigured IntlDateFormatter instance * * @return \IntlDateFormatter */ protected function getIntlDateFormatter() { $dateFormat = $this->dateFormat; $timeFormat = $this->timeFormat; $timezone = $this->outputTimezone; $calendar = $this->calendar; $pattern = $this->pattern; $intlDateFormatter = new \IntlDateFormatter(\Locale::getDefault(), $dateFormat, $timeFormat, $timezone, $calendar, $pattern); $intlDateFormatter->setLenient(false); return $intlDateFormatter; } } Core/DataTransformer/DataTransformerChain.php000064400000004614152405076560015317 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; /** * Passes a value through multiple value transformers * * @author Bernhard Schussek */ class DataTransformerChain implements DataTransformerInterface { /** * The value transformers * @var DataTransformerInterface[] */ protected $transformers; /** * Uses the given value transformers to transform values * * @param array $transformers */ public function __construct(array $transformers) { $this->transformers = $transformers; } /** * Passes the value through the transform() method of all nested transformers * * The transformers receive the value in the same order as they were passed * to the constructor. Each transformer receives the result of the previous * transformer as input. The output of the last transformer is returned * by this method. * * @param mixed $value The original value * * @return mixed The transformed value * * @throws TransformationFailedException */ public function transform($value) { foreach ($this->transformers as $transformer) { $value = $transformer->transform($value); } return $value; } /** * Passes the value through the reverseTransform() method of all nested * transformers * * The transformers receive the value in the reverse order as they were passed * to the constructor. Each transformer receives the result of the previous * transformer as input. The output of the last transformer is returned * by this method. * * @param mixed $value The transformed value * * @return mixed The reverse-transformed value * * @throws TransformationFailedException */ public function reverseTransform($value) { for ($i = count($this->transformers) - 1; $i >= 0; --$i) { $value = $this->transformers[$i]->reverseTransform($value); } return $value; } } Core/DataTransformer/BaseDateTimeTransformer.php000064400000004342152405076560015770 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\InvalidArgumentException; use Symfony\Component\Form\Exception\UnexpectedTypeException; abstract class BaseDateTimeTransformer implements DataTransformerInterface { protected static $formats = array( \IntlDateFormatter::NONE, \IntlDateFormatter::FULL, \IntlDateFormatter::LONG, \IntlDateFormatter::MEDIUM, \IntlDateFormatter::SHORT, ); protected $inputTimezone; protected $outputTimezone; /** * Constructor. * * @param string $inputTimezone The name of the input timezone * @param string $outputTimezone The name of the output timezone * * @throws UnexpectedTypeException if a timezone is not a string * @throws InvalidArgumentException if a timezone is not valid */ public function __construct($inputTimezone = null, $outputTimezone = null) { if (!is_string($inputTimezone) && null !== $inputTimezone) { throw new UnexpectedTypeException($inputTimezone, 'string'); } if (!is_string($outputTimezone) && null !== $outputTimezone) { throw new UnexpectedTypeException($outputTimezone, 'string'); } $this->inputTimezone = $inputTimezone ?: date_default_timezone_get(); $this->outputTimezone = $outputTimezone ?: date_default_timezone_get(); // Check if input and output timezones are valid try { new \DateTimeZone($this->inputTimezone); } catch (\Exception $e) { throw new InvalidArgumentException(sprintf('Input timezone is invalid: %s.', $this->inputTimezone), $e->getCode(), $e); } try { new \DateTimeZone($this->outputTimezone); } catch (\Exception $e) { throw new InvalidArgumentException(sprintf('Output timezone is invalid: %s.', $this->outputTimezone), $e->getCode(), $e); } } } Core/DataTransformer/PercentToLocalizedStringTransformer.php000064400000010144152405076560020417 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Exception\UnexpectedTypeException; /** * Transforms between a normalized format (integer or float) and a percentage value. * * @author Bernhard Schussek * @author Florian Eckerstorfer */ class PercentToLocalizedStringTransformer implements DataTransformerInterface { const FRACTIONAL = 'fractional'; const INTEGER = 'integer'; protected static $types = array( self::FRACTIONAL, self::INTEGER, ); private $type; private $precision; /** * Constructor. * * @see self::$types for a list of supported types * * @param integer $precision The precision * @param string $type One of the supported types * * @throws UnexpectedTypeException if the given value of type is unknown */ public function __construct($precision = null, $type = null) { if (null === $precision) { $precision = 0; } if (null === $type) { $type = self::FRACTIONAL; } if (!in_array($type, self::$types, true)) { throw new UnexpectedTypeException($type, implode('", "', self::$types)); } $this->type = $type; $this->precision = $precision; } /** * Transforms between a normalized format (integer or float) into a percentage value. * * @param number $value Normalized value * * @return number Percentage value * * @throws TransformationFailedException If the given value is not numeric or * if the value could not be transformed. */ public function transform($value) { if (null === $value) { return ''; } if (!is_numeric($value)) { throw new TransformationFailedException('Expected a numeric.'); } if (self::FRACTIONAL == $this->type) { $value *= 100; } $formatter = $this->getNumberFormatter(); $value = $formatter->format($value); if (intl_is_failure($formatter->getErrorCode())) { throw new TransformationFailedException($formatter->getErrorMessage()); } // replace the UTF-8 non break spaces return $value; } /** * Transforms between a percentage value into a normalized format (integer or float). * * @param number $value Percentage value. * * @return number Normalized value. * * @throws TransformationFailedException If the given value is not a string or * if the value could not be transformed. */ public function reverseTransform($value) { if (!is_string($value)) { throw new TransformationFailedException('Expected a string.'); } if ('' === $value) { return null; } $formatter = $this->getNumberFormatter(); // replace normal spaces so that the formatter can read them $value = $formatter->parse(str_replace(' ', ' ', $value)); if (intl_is_failure($formatter->getErrorCode())) { throw new TransformationFailedException($formatter->getErrorMessage()); } if (self::FRACTIONAL == $this->type) { $value /= 100; } return $value; } /** * Returns a preconfigured \NumberFormatter instance * * @return \NumberFormatter */ protected function getNumberFormatter() { $formatter = new \NumberFormatter(\Locale::getDefault(), \NumberFormatter::DECIMAL); $formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->precision); return $formatter; } } Core/DataTransformer/ArrayToPartsTransformer.php000064400000004417152405076560016077 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; /** * @author Bernhard Schussek */ class ArrayToPartsTransformer implements DataTransformerInterface { private $partMapping; public function __construct(array $partMapping) { $this->partMapping = $partMapping; } public function transform($array) { if (null === $array) { $array = array(); } if (!is_array($array) ) { throw new TransformationFailedException('Expected an array.'); } $result = array(); foreach ($this->partMapping as $partKey => $originalKeys) { if (empty($array)) { $result[$partKey] = null; } else { $result[$partKey] = array_intersect_key($array, array_flip($originalKeys)); } } return $result; } public function reverseTransform($array) { if (!is_array($array) ) { throw new TransformationFailedException('Expected an array.'); } $result = array(); $emptyKeys = array(); foreach ($this->partMapping as $partKey => $originalKeys) { if (!empty($array[$partKey])) { foreach ($originalKeys as $originalKey) { if (isset($array[$partKey][$originalKey])) { $result[$originalKey] = $array[$partKey][$originalKey]; } } } else { $emptyKeys[] = $partKey; } } if (count($emptyKeys) > 0) { if (count($emptyKeys) === count($this->partMapping)) { // All parts empty return null; } throw new TransformationFailedException( sprintf('The keys "%s" should not be empty', implode('", "', $emptyKeys) )); } return $result; } } Core/DataTransformer/ChoicesToValuesTransformer.php000064400000004147152405076560016544 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface; /** * @author Bernhard Schussek */ class ChoicesToValuesTransformer implements DataTransformerInterface { private $choiceList; /** * Constructor. * * @param ChoiceListInterface $choiceList */ public function __construct(ChoiceListInterface $choiceList) { $this->choiceList = $choiceList; } /** * @param array $array * * @return array * * @throws TransformationFailedException If the given value is not an array. */ public function transform($array) { if (null === $array) { return array(); } if (!is_array($array)) { throw new TransformationFailedException('Expected an array.'); } return $this->choiceList->getValuesForChoices($array); } /** * @param array $array * * @return array * * @throws TransformationFailedException If the given value is not an array * or if no matching choice could be * found for some given value. */ public function reverseTransform($array) { if (null === $array) { return array(); } if (!is_array($array)) { throw new TransformationFailedException('Expected an array.'); } $choices = $this->choiceList->getChoicesForValues($array); if (count($choices) !== count($array)) { throw new TransformationFailedException('Could not find all matching choices for the given values'); } return $choices; } } Core/DataTransformer/ValueToDuplicatesTransformer.php000064400000004416152405076560017100 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; /** * @author Bernhard Schussek */ class ValueToDuplicatesTransformer implements DataTransformerInterface { private $keys; public function __construct(array $keys) { $this->keys = $keys; } /** * Duplicates the given value through the array. * * @param mixed $value The value * * @return array The array */ public function transform($value) { $result = array(); foreach ($this->keys as $key) { $result[$key] = $value; } return $result; } /** * Extracts the duplicated value from an array. * * @param array $array * * @return mixed The value * * @throws TransformationFailedException If the given value is not an array or * if the given array can not be transformed. */ public function reverseTransform($array) { if (!is_array($array)) { throw new TransformationFailedException('Expected an array.'); } $result = current($array); $emptyKeys = array(); foreach ($this->keys as $key) { if (!empty($array[$key])) { if ($array[$key] !== $result) { throw new TransformationFailedException( 'All values in the array should be the same' ); } } else { $emptyKeys[] = $key; } } if (count($emptyKeys) > 0) { if (count($emptyKeys) == count($this->keys)) { // All keys empty return null; } throw new TransformationFailedException( sprintf('The keys "%s" should not be empty', implode('", "', $emptyKeys) )); } return $result; } } Core/DataTransformer/DateTimeToRfc3339Transformer.php000064400000004420152405076560016452 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\Exception\TransformationFailedException; /** * @author Bernhard Schussek */ class DateTimeToRfc3339Transformer extends BaseDateTimeTransformer { /** * {@inheritDoc} */ public function transform($dateTime) { if (null === $dateTime) { return ''; } if (!$dateTime instanceof \DateTime) { throw new TransformationFailedException('Expected a \DateTime.'); } if ($this->inputTimezone !== $this->outputTimezone) { $dateTime = clone $dateTime; $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); } return preg_replace('/\+00:00$/', 'Z', $dateTime->format('c')); } /** * {@inheritDoc} */ public function reverseTransform($rfc3339) { if (!is_string($rfc3339)) { throw new TransformationFailedException('Expected a string.'); } if ('' === $rfc3339) { return null; } try { $dateTime = new \DateTime($rfc3339); } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } if ($this->outputTimezone !== $dateTime->getTimezone()->getName()) { try { $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } } if (preg_match('/(\d{4})-(\d{2})-(\d{2})/', $rfc3339, $matches)) { if (!checkdate($matches[2], $matches[3], $matches[1])) { throw new TransformationFailedException(sprintf( 'The date "%s-%s-%s" is not a valid date.', $matches[1], $matches[2], $matches[3] )); } } return $dateTime; } } Core/DataTransformer/MoneyToLocalizedStringTransformer.php000064400000004504152405076560020111 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\Exception\TransformationFailedException; /** * Transforms between a normalized format and a localized money string. * * @author Bernhard Schussek * @author Florian Eckerstorfer */ class MoneyToLocalizedStringTransformer extends NumberToLocalizedStringTransformer { private $divisor; public function __construct($precision = 2, $grouping = true, $roundingMode = self::ROUND_HALF_UP, $divisor = 1) { if (null === $grouping) { $grouping = true; } if (null === $precision) { $precision = 2; } parent::__construct($precision, $grouping, $roundingMode); if (null === $divisor) { $divisor = 1; } $this->divisor = $divisor; } /** * Transforms a normalized format into a localized money string. * * @param number $value Normalized number * * @return string Localized money string. * * @throws TransformationFailedException If the given value is not numeric or * if the value can not be transformed. */ public function transform($value) { if (null !== $value) { if (!is_numeric($value)) { throw new TransformationFailedException('Expected a numeric.'); } $value /= $this->divisor; } return parent::transform($value); } /** * Transforms a localized money string into a normalized format. * * @param string $value Localized money string * * @return number Normalized number * * @throws TransformationFailedException If the given value is not a string * or if the value can not be transformed. */ public function reverseTransform($value) { $value = parent::reverseTransform($value); if (null !== $value) { $value *= $this->divisor; } return $value; } } Core/DataTransformer/ChoicesToBooleanArrayTransformer.php000064400000007065152405076560017665 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; /** * @author Bernhard Schussek */ class ChoicesToBooleanArrayTransformer implements DataTransformerInterface { private $choiceList; public function __construct(ChoiceListInterface $choiceList) { $this->choiceList = $choiceList; } /** * Transforms an array of choices to a format appropriate for the nested * checkboxes/radio buttons. * * The result is an array with the options as keys and true/false as values, * depending on whether a given option is selected. If this field is rendered * as select tag, the value is not modified. * * @param mixed $array An array * * @return mixed An array * * @throws TransformationFailedException If the given value is not an array * or if the choices can not be retrieved. */ public function transform($array) { if (null === $array) { return array(); } if (!is_array($array)) { throw new TransformationFailedException('Expected an array.'); } try { $values = $this->choiceList->getValues(); } catch (\Exception $e) { throw new TransformationFailedException('Can not get the choice list', $e->getCode(), $e); } $valueMap = array_flip($this->choiceList->getValuesForChoices($array)); foreach ($values as $i => $value) { $values[$i] = isset($valueMap[$value]); } return $values; } /** * Transforms a checkbox/radio button array to an array of choices. * * The input value is an array with the choices as keys and true/false as * values, depending on whether a given choice is selected. The output * is an array with the selected choices. * * @param mixed $values An array * * @return mixed An array * * @throws TransformationFailedException If the given value is not an array, * if the recuperation of the choices * fails or if some choice can't be * found. */ public function reverseTransform($values) { if (!is_array($values)) { throw new TransformationFailedException('Expected an array.'); } try { $choices = $this->choiceList->getChoices(); } catch (\Exception $e) { throw new TransformationFailedException('Can not get the choice list', $e->getCode(), $e); } $result = array(); $unknown = array(); foreach ($values as $i => $selected) { if ($selected) { if (isset($choices[$i])) { $result[] = $choices[$i]; } else { $unknown[] = $i; } } } if (count($unknown) > 0) { throw new TransformationFailedException(sprintf('The choices "%s" were not found', implode('", "', $unknown))); } return $result; } } Core/DataTransformer/BooleanToStringTransformer.php000064400000003771152405076560016557 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; /** * Transforms between a Boolean and a string. * * @author Bernhard Schussek * @author Florian Eckerstorfer */ class BooleanToStringTransformer implements DataTransformerInterface { /** * The value emitted upon transform if the input is true * @var string */ private $trueValue; /** * Sets the value emitted upon transform if the input is true. * * @param string $trueValue */ public function __construct($trueValue) { $this->trueValue = $trueValue; } /** * Transforms a Boolean into a string. * * @param Boolean $value Boolean value. * * @return string String value. * * @throws TransformationFailedException If the given value is not a Boolean. */ public function transform($value) { if (null === $value) { return null; } if (!is_bool($value)) { throw new TransformationFailedException('Expected a Boolean.'); } return $value ? $this->trueValue : null; } /** * Transforms a string into a Boolean. * * @param string $value String value. * * @return Boolean Boolean value. * * @throws TransformationFailedException If the given value is not a string. */ public function reverseTransform($value) { if (null === $value) { return false; } if (!is_string($value)) { throw new TransformationFailedException('Expected a string.'); } return true; } } Core/DataTransformer/ChoiceToBooleanArrayTransformer.php000064400000007446152405076560017505 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; /** * @author Bernhard Schussek */ class ChoiceToBooleanArrayTransformer implements DataTransformerInterface { private $choiceList; private $placeholderPresent; /** * Constructor. * * @param ChoiceListInterface $choiceList * @param Boolean $placeholderPresent */ public function __construct(ChoiceListInterface $choiceList, $placeholderPresent) { $this->choiceList = $choiceList; $this->placeholderPresent = $placeholderPresent; } /** * Transforms a single choice to a format appropriate for the nested * checkboxes/radio buttons. * * The result is an array with the options as keys and true/false as values, * depending on whether a given option is selected. If this field is rendered * as select tag, the value is not modified. * * @param mixed $choice An array if "multiple" is set to true, a scalar * value otherwise. * * @return mixed An array * * @throws TransformationFailedException If the given value is not scalar or * if the choices can not be retrieved. */ public function transform($choice) { try { $values = $this->choiceList->getValues(); } catch (\Exception $e) { throw new TransformationFailedException('Can not get the choice list', $e->getCode(), $e); } $valueMap = array_flip($this->choiceList->getValuesForChoices(array($choice))); foreach ($values as $i => $value) { $values[$i] = isset($valueMap[$value]); } if ($this->placeholderPresent) { $values['placeholder'] = 0 === count($valueMap); } return $values; } /** * Transforms a checkbox/radio button array to a single choice. * * The input value is an array with the choices as keys and true/false as * values, depending on whether a given choice is selected. The output * is the selected choice. * * @param array $values An array of values * * @return mixed A scalar value * * @throws TransformationFailedException If the given value is not an array, * if the recuperation of the choices * fails or if some choice can't be * found. */ public function reverseTransform($values) { if (!is_array($values)) { throw new TransformationFailedException('Expected an array.'); } try { $choices = $this->choiceList->getChoices(); } catch (\Exception $e) { throw new TransformationFailedException('Can not get the choice list', $e->getCode(), $e); } foreach ($values as $i => $selected) { if ($selected) { if (isset($choices[$i])) { return $choices[$i] === '' ? null : $choices[$i]; } elseif ($this->placeholderPresent && 'placeholder' === $i) { return null; } else { throw new TransformationFailedException(sprintf('The choice "%s" does not exist', $i)); } } } return null; } } Core/DataTransformer/DateTimeToArrayTransformer.php000064400000015014152405076560016475 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Exception\UnexpectedTypeException; /** * Transforms between a normalized time and a localized time string/array. * * @author Bernhard Schussek * @author Florian Eckerstorfer */ class DateTimeToArrayTransformer extends BaseDateTimeTransformer { private $pad; private $fields; /** * Constructor. * * @param string $inputTimezone The input timezone * @param string $outputTimezone The output timezone * @param array $fields The date fields * @param Boolean $pad Whether to use padding * * @throws UnexpectedTypeException if a timezone is not a string */ public function __construct($inputTimezone = null, $outputTimezone = null, array $fields = null, $pad = false) { parent::__construct($inputTimezone, $outputTimezone); if (null === $fields) { $fields = array('year', 'month', 'day', 'hour', 'minute', 'second'); } $this->fields = $fields; $this->pad = (Boolean) $pad; } /** * Transforms a normalized date into a localized date. * * @param \DateTime $dateTime Normalized date. * * @return array Localized date. * * @throws TransformationFailedException If the given value is not an * instance of \DateTime or if the * output timezone is not supported. */ public function transform($dateTime) { if (null === $dateTime) { return array_intersect_key(array( 'year' => '', 'month' => '', 'day' => '', 'hour' => '', 'minute' => '', 'second' => '', ), array_flip($this->fields)); } if (!$dateTime instanceof \DateTime) { throw new TransformationFailedException('Expected a \DateTime.'); } $dateTime = clone $dateTime; if ($this->inputTimezone !== $this->outputTimezone) { try { $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } } $result = array_intersect_key(array( 'year' => $dateTime->format('Y'), 'month' => $dateTime->format('m'), 'day' => $dateTime->format('d'), 'hour' => $dateTime->format('H'), 'minute' => $dateTime->format('i'), 'second' => $dateTime->format('s'), ), array_flip($this->fields)); if (!$this->pad) { foreach ($result as &$entry) { // remove leading zeros $entry = (string) (int) $entry; } } return $result; } /** * Transforms a localized date into a normalized date. * * @param array $value Localized date * * @return \DateTime Normalized date * * @throws TransformationFailedException If the given value is not an array, * if the value could not be transformed * or if the input timezone is not * supported. */ public function reverseTransform($value) { if (null === $value) { return null; } if (!is_array($value)) { throw new TransformationFailedException('Expected an array.'); } if ('' === implode('', $value)) { return null; } $emptyFields = array(); foreach ($this->fields as $field) { if (!isset($value[$field])) { $emptyFields[] = $field; } } if (count($emptyFields) > 0) { throw new TransformationFailedException( sprintf('The fields "%s" should not be empty', implode('", "', $emptyFields) )); } if (isset($value['month']) && !ctype_digit((string) $value['month'])) { throw new TransformationFailedException('This month is invalid'); } if (isset($value['day']) && !ctype_digit((string) $value['day'])) { throw new TransformationFailedException('This day is invalid'); } if (isset($value['year']) && !ctype_digit((string) $value['year'])) { throw new TransformationFailedException('This year is invalid'); } if (!empty($value['month']) && !empty($value['day']) && !empty($value['year']) && false === checkdate($value['month'], $value['day'], $value['year'])) { throw new TransformationFailedException('This is an invalid date'); } if (isset($value['hour']) && !ctype_digit((string) $value['hour'])) { throw new TransformationFailedException('This hour is invalid'); } if (isset($value['minute']) && !ctype_digit((string) $value['minute'])) { throw new TransformationFailedException('This minute is invalid'); } if (isset($value['second']) && !ctype_digit((string) $value['second'])) { throw new TransformationFailedException('This second is invalid'); } try { $dateTime = new \DateTime(sprintf( '%s-%s-%s %s:%s:%s %s', empty($value['year']) ? '1970' : $value['year'], empty($value['month']) ? '1' : $value['month'], empty($value['day']) ? '1' : $value['day'], empty($value['hour']) ? '0' : $value['hour'], empty($value['minute']) ? '0' : $value['minute'], empty($value['second']) ? '0' : $value['second'], $this->outputTimezone )); if ($this->inputTimezone !== $this->outputTimezone) { $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); } } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } return $dateTime; } } Core/DataTransformer/IntegerToLocalizedStringTransformer.php000064400000002410152405076560020411 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; /** * Transforms between an integer and a localized number with grouping * (each thousand) and comma separators. * * @author Bernhard Schussek */ class IntegerToLocalizedStringTransformer extends NumberToLocalizedStringTransformer { /** * Constructs a transformer. * * @param integer $precision Unused. * @param Boolean $grouping Whether thousands should be grouped. * @param integer $roundingMode One of the ROUND_ constants in this class. */ public function __construct($precision = 0, $grouping = false, $roundingMode = self::ROUND_DOWN) { if (null === $roundingMode) { $roundingMode = self::ROUND_DOWN; } parent::__construct(0, $grouping, $roundingMode); } /** * {@inheritDoc} */ public function reverseTransform($value) { $result = parent::reverseTransform($value); return null !== $result ? (int) $result : null; } } Core/DataTransformer/DateTimeToStringTransformer.php000064400000020202152405076560016660 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Exception\UnexpectedTypeException; /** * Transforms between a date string and a DateTime object * * @author Bernhard Schussek * @author Florian Eckerstorfer */ class DateTimeToStringTransformer extends BaseDateTimeTransformer { /** * Format used for generating strings * @var string */ private $generateFormat; /** * Format used for parsing strings * * Different than the {@link $generateFormat} because formats for parsing * support additional characters in PHP that are not supported for * generating strings. * * @var string */ private $parseFormat; /** * Whether to parse by appending a pipe "|" to the parse format. * * This only works as of PHP 5.3.7. * * @var Boolean */ private $parseUsingPipe; /** * Transforms a \DateTime instance to a string * * @see \DateTime::format() for supported formats * * @param string $inputTimezone The name of the input timezone * @param string $outputTimezone The name of the output timezone * @param string $format The date format * @param Boolean $parseUsingPipe Whether to parse by appending a pipe "|" to the parse format * * @throws UnexpectedTypeException if a timezone is not a string */ public function __construct($inputTimezone = null, $outputTimezone = null, $format = 'Y-m-d H:i:s', $parseUsingPipe = null) { parent::__construct($inputTimezone, $outputTimezone); $this->generateFormat = $this->parseFormat = $format; // The pipe in the parser pattern only works as of PHP 5.3.7 // See http://bugs.php.net/54316 $this->parseUsingPipe = null === $parseUsingPipe ? version_compare(phpversion(), '5.3.7', '>=') : $parseUsingPipe; // See http://php.net/manual/en/datetime.createfromformat.php // The character "|" in the format makes sure that the parts of a date // that are *not* specified in the format are reset to the corresponding // values from 1970-01-01 00:00:00 instead of the current time. // Without "|" and "Y-m-d", "2010-02-03" becomes "2010-02-03 12:32:47", // where the time corresponds to the current server time. // With "|" and "Y-m-d", "2010-02-03" becomes "2010-02-03 00:00:00", // which is at least deterministic and thus used here. if ($this->parseUsingPipe && false === strpos($this->parseFormat, '|')) { $this->parseFormat .= '|'; } } /** * Transforms a DateTime object into a date string with the configured format * and timezone * * @param \DateTime $value A DateTime object * * @return string A value as produced by PHP's date() function * * @throws TransformationFailedException If the given value is not a \DateTime * instance or if the output timezone * is not supported. */ public function transform($value) { if (null === $value) { return ''; } if (!$value instanceof \DateTime) { throw new TransformationFailedException('Expected a \DateTime.'); } $value = clone $value; try { $value->setTimezone(new \DateTimeZone($this->outputTimezone)); } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } return $value->format($this->generateFormat); } /** * Transforms a date string in the configured timezone into a DateTime object. * * @param string $value A value as produced by PHP's date() function * * @return \DateTime An instance of \DateTime * * @throws TransformationFailedException If the given value is not a string, * if the date could not be parsed or * if the input timezone is not supported. */ public function reverseTransform($value) { if (empty($value)) { return null; } if (!is_string($value)) { throw new TransformationFailedException('Expected a string.'); } try { $outputTz = new \DateTimeZone($this->outputTimezone); $dateTime = \DateTime::createFromFormat($this->parseFormat, $value, $outputTz); $lastErrors = \DateTime::getLastErrors(); if (0 < $lastErrors['warning_count'] || 0 < $lastErrors['error_count']) { throw new TransformationFailedException( implode(', ', array_merge( array_values($lastErrors['warnings']), array_values($lastErrors['errors']) )) ); } // On PHP versions < 5.3.7 we need to emulate the pipe operator // and reset parts not given in the format to their equivalent // of the UNIX base timestamp. if (!$this->parseUsingPipe) { list($year, $month, $day, $hour, $minute, $second) = explode('-', $dateTime->format('Y-m-d-H-i-s')); // Check which of the date parts are present in the pattern preg_match( '/(' . '(?P[djDl])|' . '(?P[FMmn])|' . '(?P[Yy])|' . '(?P[ghGH])|' . '(?Pi)|' . '(?Ps)|' . '(?Pz)|' . '(?PU)|' . '[^djDlFMmnYyghGHiszU]' . ')*/', $this->parseFormat, $matches ); // preg_match() does not guarantee to set all indices, so // set them unless given $matches = array_merge(array( 'day' => false, 'month' => false, 'year' => false, 'hour' => false, 'minute' => false, 'second' => false, 'dayofyear' => false, 'timestamp' => false, ), $matches); // Reset all parts that don't exist in the format to the // corresponding part of the UNIX base timestamp if (!$matches['timestamp']) { if (!$matches['dayofyear']) { if (!$matches['day']) { $day = 1; } if (!$matches['month']) { $month = 1; } } if (!$matches['year']) { $year = 1970; } if (!$matches['hour']) { $hour = 0; } if (!$matches['minute']) { $minute = 0; } if (!$matches['second']) { $second = 0; } $dateTime->setDate($year, $month, $day); $dateTime->setTime($hour, $minute, $second); } } if ($this->inputTimezone !== $this->outputTimezone) { $dateTime->setTimeZone(new \DateTimeZone($this->inputTimezone)); } } catch (TransformationFailedException $e) { throw $e; } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } return $dateTime; } } Core/DataTransformer/NumberToLocalizedStringTransformer.php000064400000021622152405076560020252 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; /** * Transforms between a number type and a localized number with grouping * (each thousand) and comma separators. * * @author Bernhard Schussek * @author Florian Eckerstorfer */ class NumberToLocalizedStringTransformer implements DataTransformerInterface { /** * Rounds a number towards positive infinity. * * Rounds 1.4 to 2 and -1.4 to -1. */ const ROUND_CEILING = \NumberFormatter::ROUND_CEILING; /** * Rounds a number towards negative infinity. * * Rounds 1.4 to 1 and -1.4 to -2. */ const ROUND_FLOOR = \NumberFormatter::ROUND_FLOOR; /** * Rounds a number away from zero. * * Rounds 1.4 to 2 and -1.4 to -2. */ const ROUND_UP = \NumberFormatter::ROUND_UP; /** * Rounds a number towards zero. * * Rounds 1.4 to 1 and -1.4 to -1. */ const ROUND_DOWN = \NumberFormatter::ROUND_DOWN; /** * Rounds to the nearest number and halves to the next even number. * * Rounds 2.5, 1.6 and 1.5 to 2 and 1.4 to 1. */ const ROUND_HALF_EVEN = \NumberFormatter::ROUND_HALFEVEN; /** * Rounds to the nearest number and halves away from zero. * * Rounds 2.5 to 3, 1.6 and 1.5 to 2 and 1.4 to 1. */ const ROUND_HALF_UP = \NumberFormatter::ROUND_HALFUP; /** * Rounds to the nearest number and halves towards zero. * * Rounds 2.5 and 1.6 to 2, 1.5 and 1.4 to 1. */ const ROUND_HALF_DOWN = \NumberFormatter::ROUND_HALFDOWN; /** * Alias for {@link self::ROUND_HALF_EVEN}. * * @deprecated Deprecated as of Symfony 2.4, to be removed in Symfony 3.0. */ const ROUND_HALFEVEN = self::ROUND_HALF_EVEN; /** * Alias for {@link self::ROUND_HALF_UP}. * * @deprecated Deprecated as of Symfony 2.4, to be removed in Symfony 3.0. */ const ROUND_HALFUP = self::ROUND_HALF_UP; /** * Alias for {@link self::ROUND_HALF_DOWN}. * * @deprecated Deprecated as of Symfony 2.4, to be removed in Symfony 3.0. */ const ROUND_HALFDOWN = self::ROUND_HALF_DOWN; protected $precision; protected $grouping; protected $roundingMode; public function __construct($precision = null, $grouping = false, $roundingMode = self::ROUND_HALF_UP) { if (null === $grouping) { $grouping = false; } if (null === $roundingMode) { $roundingMode = self::ROUND_HALF_UP; } $this->precision = $precision; $this->grouping = $grouping; $this->roundingMode = $roundingMode; } /** * Transforms a number type into localized number. * * @param integer|float $value Number value. * * @return string Localized value. * * @throws TransformationFailedException If the given value is not numeric * or if the value can not be transformed. */ public function transform($value) { if (null === $value) { return ''; } if (!is_numeric($value)) { throw new TransformationFailedException('Expected a numeric.'); } $formatter = $this->getNumberFormatter(); $value = $formatter->format($value); if (intl_is_failure($formatter->getErrorCode())) { throw new TransformationFailedException($formatter->getErrorMessage()); } // Convert fixed spaces to normal ones $value = str_replace("\xc2\xa0", ' ', $value); return $value; } /** * Transforms a localized number into an integer or float * * @param string $value The localized value * * @return integer|float The numeric value * * @throws TransformationFailedException If the given value is not a string * or if the value can not be transformed. */ public function reverseTransform($value) { if (!is_string($value)) { throw new TransformationFailedException('Expected a string.'); } if ('' === $value) { return null; } if ('NaN' === $value) { throw new TransformationFailedException('"NaN" is not a valid number'); } $position = 0; $formatter = $this->getNumberFormatter(); $groupSep = $formatter->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL); $decSep = $formatter->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); if ('.' !== $decSep && (!$this->grouping || '.' !== $groupSep)) { $value = str_replace('.', $decSep, $value); } if (',' !== $decSep && (!$this->grouping || ',' !== $groupSep)) { $value = str_replace(',', $decSep, $value); } $result = $formatter->parse($value, \NumberFormatter::TYPE_DOUBLE, $position); if (intl_is_failure($formatter->getErrorCode())) { throw new TransformationFailedException($formatter->getErrorMessage()); } if ($result >= PHP_INT_MAX || $result <= -PHP_INT_MAX) { throw new TransformationFailedException('I don\'t have a clear idea what infinity looks like'); } if (function_exists('mb_detect_encoding') && false !== $encoding = mb_detect_encoding($value)) { $strlen = function ($string) use ($encoding) { return mb_strlen($string, $encoding); }; $substr = function ($string, $offset, $length) use ($encoding) { return mb_substr($string, $offset, $length, $encoding); }; } else { $strlen = 'strlen'; $substr = 'substr'; } $length = $strlen($value); // After parsing, position holds the index of the character where the // parsing stopped if ($position < $length) { // Check if there are unrecognized characters at the end of the // number (excluding whitespace characters) $remainder = trim($substr($value, $position, $length), " \t\n\r\0\x0b\xc2\xa0"); if ('' !== $remainder) { throw new TransformationFailedException( sprintf('The number contains unrecognized characters: "%s"', $remainder) ); } } // NumberFormatter::parse() does not round return $this->round($result); } /** * Returns a preconfigured \NumberFormatter instance * * @return \NumberFormatter */ protected function getNumberFormatter() { $formatter = new \NumberFormatter(\Locale::getDefault(), \NumberFormatter::DECIMAL); if (null !== $this->precision) { $formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->precision); $formatter->setAttribute(\NumberFormatter::ROUNDING_MODE, $this->roundingMode); } $formatter->setAttribute(\NumberFormatter::GROUPING_USED, $this->grouping); return $formatter; } /** * Rounds a number according to the configured precision and rounding mode. * * @param integer|float $number A number. * * @return integer|float The rounded number. */ private function round($number) { if (null !== $this->precision && null !== $this->roundingMode) { // shift number to maintain the correct precision during rounding $roundingCoef = pow(10, $this->precision); $number *= $roundingCoef; switch ($this->roundingMode) { case self::ROUND_CEILING: $number = ceil($number); break; case self::ROUND_FLOOR: $number = floor($number); break; case self::ROUND_UP: $number = $number > 0 ? ceil($number) : floor($number); break; case self::ROUND_DOWN: $number = $number > 0 ? floor($number) : ceil($number); break; case self::ROUND_HALF_EVEN: $number = round($number, 0, PHP_ROUND_HALF_EVEN); break; case self::ROUND_HALF_UP: $number = round($number, 0, PHP_ROUND_HALF_UP); break; case self::ROUND_HALF_DOWN: $number = round($number, 0, PHP_ROUND_HALF_DOWN); break; } $number /= $roundingCoef; } return $number; } } Core/DataTransformer/ChoiceToValueTransformer.php000064400000003255152405076560016175 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface; /** * @author Bernhard Schussek */ class ChoiceToValueTransformer implements DataTransformerInterface { private $choiceList; /** * Constructor. * * @param ChoiceListInterface $choiceList */ public function __construct(ChoiceListInterface $choiceList) { $this->choiceList = $choiceList; } public function transform($choice) { return (string) current($this->choiceList->getValuesForChoices(array($choice))); } public function reverseTransform($value) { if (null !== $value && !is_scalar($value)) { throw new TransformationFailedException('Expected a scalar.'); } // These are now valid ChoiceList values, so we can return null // right away if ('' === $value || null === $value) { return null; } $choices = $this->choiceList->getChoicesForValues(array($value)); if (1 !== count($choices)) { throw new TransformationFailedException(sprintf('The choice "%s" does not exist or is not unique', $value)); } $choice = current($choices); return '' === $choice ? null : $choice; } } Core/DataTransformer/DateTimeToTimestampTransformer.php000064400000005236152405076560017367 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\Exception\TransformationFailedException; /** * Transforms between a timestamp and a DateTime object * * @author Bernhard Schussek * @author Florian Eckerstorfer */ class DateTimeToTimestampTransformer extends BaseDateTimeTransformer { /** * Transforms a DateTime object into a timestamp in the configured timezone. * * @param \DateTime $value A \DateTime object * * @return integer A timestamp * * @throws TransformationFailedException If the given value is not an instance * of \DateTime or if the output * timezone is not supported. */ public function transform($value) { if (null === $value) { return null; } if (!$value instanceof \DateTime) { throw new TransformationFailedException('Expected a \DateTime.'); } $value = clone $value; try { $value->setTimezone(new \DateTimeZone($this->outputTimezone)); } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } return (int) $value->format('U'); } /** * Transforms a timestamp in the configured timezone into a DateTime object * * @param string $value A timestamp * * @return \DateTime A \DateTime object * * @throws TransformationFailedException If the given value is not a timestamp * or if the given timestamp is invalid. */ public function reverseTransform($value) { if (null === $value) { return null; } if (!is_numeric($value)) { throw new TransformationFailedException('Expected a numeric.'); } try { $dateTime = new \DateTime(); $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); $dateTime->setTimestamp($value); if ($this->inputTimezone !== $this->outputTimezone) { $dateTime->setTimezone(new \DateTimeZone($this->inputTimezone)); } } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } return $dateTime; } } Core/ChoiceList/ChoiceList.php000064400000037626152405076560012251 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\ChoiceList; use Symfony\Component\Form\FormConfigBuilder; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\Form\Exception\InvalidConfigurationException; use Symfony\Component\Form\Exception\InvalidArgumentException; use Symfony\Component\Form\Extension\Core\View\ChoiceView; /** * A choice list for choices of arbitrary data types. * * Choices and labels are passed in two arrays. The indices of the choices * and the labels should match. Choices may also be given as hierarchy of * unlimited depth by creating nested arrays. The title of the sub-hierarchy * can be stored in the array key pointing to the nested array. The topmost * level of the hierarchy may also be a \Traversable. * * * $choices = array(true, false); * $labels = array('Agree', 'Disagree'); * $choiceList = new ChoiceList($choices, $labels); * * * @author Bernhard Schussek */ class ChoiceList implements ChoiceListInterface { /** * The choices with their indices as keys. * * @var array */ private $choices = array(); /** * The choice values with the indices of the matching choices as keys. * * @var array */ private $values = array(); /** * The preferred view objects as hierarchy containing also the choice groups * with the indices of the matching choices as bottom-level keys. * * @var array */ private $preferredViews = array(); /** * The non-preferred view objects as hierarchy containing also the choice * groups with the indices of the matching choices as bottom-level keys. * * @var array */ private $remainingViews = array(); /** * Creates a new choice list. * * @param array|\Traversable $choices The array of choices. Choices may also be given * as hierarchy of unlimited depth. Hierarchies are * created by creating nested arrays. The title of * the sub-hierarchy can be stored in the array * key pointing to the nested array. The topmost * level of the hierarchy may also be a \Traversable. * @param array $labels The array of labels. The structure of this array * should match the structure of $choices. * @param array $preferredChoices A flat array of choices that should be * presented to the user with priority. * * @throws UnexpectedTypeException If the choices are not an array or \Traversable. */ public function __construct($choices, array $labels, array $preferredChoices = array()) { if (!is_array($choices) && !$choices instanceof \Traversable) { throw new UnexpectedTypeException($choices, 'array or \Traversable'); } $this->initialize($choices, $labels, $preferredChoices); } /** * Initializes the list with choices. * * Safe to be called multiple times. The list is cleared on every call. * * @param array|\Traversable $choices The choices to write into the list. * @param array $labels The labels belonging to the choices. * @param array $preferredChoices The choices to display with priority. */ protected function initialize($choices, array $labels, array $preferredChoices) { $this->choices = array(); $this->values = array(); $this->preferredViews = array(); $this->remainingViews = array(); $this->addChoices( $this->preferredViews, $this->remainingViews, $choices, $labels, $preferredChoices ); } /** * {@inheritdoc} */ public function getChoices() { return $this->choices; } /** * {@inheritdoc} */ public function getValues() { return $this->values; } /** * {@inheritdoc} */ public function getPreferredViews() { return $this->preferredViews; } /** * {@inheritdoc} */ public function getRemainingViews() { return $this->remainingViews; } /** * {@inheritdoc} */ public function getChoicesForValues(array $values) { $values = $this->fixValues($values); $choices = array(); foreach ($values as $i => $givenValue) { foreach ($this->values as $j => $value) { if ($value === $givenValue) { $choices[$i] = $this->choices[$j]; unset($values[$i]); if (0 === count($values)) { break 2; } } } } return $choices; } /** * {@inheritdoc} */ public function getValuesForChoices(array $choices) { $choices = $this->fixChoices($choices); $values = array(); foreach ($choices as $i => $givenChoice) { foreach ($this->choices as $j => $choice) { if ($choice === $givenChoice) { $values[$i] = $this->values[$j]; unset($choices[$i]); if (0 === count($choices)) { break 2; } } } } return $values; } /** * {@inheritdoc} * * @deprecated Deprecated since version 2.4, to be removed in 3.0. */ public function getIndicesForChoices(array $choices) { $choices = $this->fixChoices($choices); $indices = array(); foreach ($choices as $i => $givenChoice) { foreach ($this->choices as $j => $choice) { if ($choice === $givenChoice) { $indices[$i] = $j; unset($choices[$i]); if (0 === count($choices)) { break 2; } } } } return $indices; } /** * {@inheritdoc} * * @deprecated Deprecated since version 2.4, to be removed in 3.0. */ public function getIndicesForValues(array $values) { $values = $this->fixValues($values); $indices = array(); foreach ($values as $i => $givenValue) { foreach ($this->values as $j => $value) { if ($value === $givenValue) { $indices[$i] = $j; unset($values[$i]); if (0 === count($values)) { break 2; } } } } return $indices; } /** * Recursively adds the given choices to the list. * * @param array $bucketForPreferred The bucket where to store the preferred * view objects. * @param array $bucketForRemaining The bucket where to store the * non-preferred view objects. * @param array|\Traversable $choices The list of choices. * @param array $labels The labels corresponding to the choices. * @param array $preferredChoices The preferred choices. * * @throws InvalidArgumentException If the structures of the choices and labels array do not match. * @throws InvalidConfigurationException If no valid value or index could be created for a choice. */ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemaining, $choices, array $labels, array $preferredChoices) { // Add choices to the nested buckets foreach ($choices as $group => $choice) { if (!array_key_exists($group, $labels)) { throw new InvalidArgumentException('The structures of the choices and labels array do not match.'); } if (is_array($choice)) { // Don't do the work if the array is empty if (count($choice) > 0) { $this->addChoiceGroup( $group, $bucketForPreferred, $bucketForRemaining, $choice, $labels[$group], $preferredChoices ); } } else { $this->addChoice( $bucketForPreferred, $bucketForRemaining, $choice, $labels[$group], $preferredChoices ); } } } /** * Recursively adds a choice group. * * @param string $group The name of the group. * @param array $bucketForPreferred The bucket where to store the preferred * view objects. * @param array $bucketForRemaining The bucket where to store the * non-preferred view objects. * @param array $choices The list of choices in the group. * @param array $labels The labels corresponding to the choices in the group. * @param array $preferredChoices The preferred choices. * * @throws InvalidConfigurationException If no valid value or index could be created for a choice. */ protected function addChoiceGroup($group, array &$bucketForPreferred, array &$bucketForRemaining, array $choices, array $labels, array $preferredChoices) { // If this is a choice group, create a new level in the choice // key hierarchy $bucketForPreferred[$group] = array(); $bucketForRemaining[$group] = array(); $this->addChoices( $bucketForPreferred[$group], $bucketForRemaining[$group], $choices, $labels, $preferredChoices ); // Remove child levels if empty if (empty($bucketForPreferred[$group])) { unset($bucketForPreferred[$group]); } if (empty($bucketForRemaining[$group])) { unset($bucketForRemaining[$group]); } } /** * Adds a new choice. * * @param array $bucketForPreferred The bucket where to store the preferred * view objects. * @param array $bucketForRemaining The bucket where to store the * non-preferred view objects. * @param mixed $choice The choice to add. * @param string $label The label for the choice. * @param array $preferredChoices The preferred choices. * * @throws InvalidConfigurationException If no valid value or index could be created. */ protected function addChoice(array &$bucketForPreferred, array &$bucketForRemaining, $choice, $label, array $preferredChoices) { $index = $this->createIndex($choice); if ('' === $index || null === $index || !FormConfigBuilder::isValidName((string) $index)) { throw new InvalidConfigurationException(sprintf('The index "%s" created by the choice list is invalid. It should be a valid, non-empty Form name.', $index)); } $value = $this->createValue($choice); if (!is_string($value)) { throw new InvalidConfigurationException(sprintf('The value created by the choice list is of type "%s", but should be a string.', gettype($value))); } $view = new ChoiceView($choice, $value, $label); $this->choices[$index] = $this->fixChoice($choice); $this->values[$index] = $value; if ($this->isPreferred($choice, $preferredChoices)) { $bucketForPreferred[$index] = $view; } else { $bucketForRemaining[$index] = $view; } } /** * Returns whether the given choice should be preferred judging by the * given array of preferred choices. * * Extension point to optimize performance by changing the structure of the * $preferredChoices array. * * @param mixed $choice The choice to test. * @param array $preferredChoices An array of preferred choices. * * @return Boolean Whether the choice is preferred. */ protected function isPreferred($choice, array $preferredChoices) { return false !== array_search($choice, $preferredChoices, true); } /** * Creates a new unique index for this choice. * * Extension point to change the indexing strategy. * * @param mixed $choice The choice to create an index for * * @return integer|string A unique index containing only ASCII letters, * digits and underscores. */ protected function createIndex($choice) { return count($this->choices); } /** * Creates a new unique value for this choice. * * By default, an integer is generated since it cannot be guaranteed that * all values in the list are convertible to (unique) strings. Subclasses * can override this behaviour if they can guarantee this property. * * @param mixed $choice The choice to create a value for * * @return string A unique string. */ protected function createValue($choice) { return (string) count($this->values); } /** * Fixes the data type of the given choice value to avoid comparison * problems. * * @param mixed $value The choice value. * * @return string The value as string. */ protected function fixValue($value) { return (string) $value; } /** * Fixes the data types of the given choice values to avoid comparison * problems. * * @param array $values The choice values. * * @return array The values as strings. */ protected function fixValues(array $values) { foreach ($values as $i => $value) { $values[$i] = $this->fixValue($value); } return $values; } /** * Fixes the data type of the given choice index to avoid comparison * problems. * * @param mixed $index The choice index. * * @return integer|string The index as PHP array key. */ protected function fixIndex($index) { if (is_bool($index) || (string) (int) $index === (string) $index) { return (int) $index; } return (string) $index; } /** * Fixes the data types of the given choice indices to avoid comparison * problems. * * @param array $indices The choice indices. * * @return array The indices as strings. */ protected function fixIndices(array $indices) { foreach ($indices as $i => $index) { $indices[$i] = $this->fixIndex($index); } return $indices; } /** * Fixes the data type of the given choice to avoid comparison problems. * * Extension point. In this implementation, choices are guaranteed to * always maintain their type and thus can be typesafely compared. * * @param mixed $choice The choice * * @return mixed The fixed choice */ protected function fixChoice($choice) { return $choice; } /** * Fixes the data type of the given choices to avoid comparison problems. * * @param array $choices The choices. * * @return array The fixed choices. * * @see fixChoice */ protected function fixChoices(array $choices) { return $choices; } } Core/ChoiceList/ObjectChoiceList.php000064400000017043152405076560013367 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\ChoiceList; use Symfony\Component\Form\Exception\StringCastException; use Symfony\Component\Form\Exception\InvalidArgumentException; use Symfony\Component\PropertyAccess\PropertyPath; use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessorInterface; /** * A choice list for object choices. * * Supports generation of choice labels, choice groups and choice values * by calling getters of the object (or associated objects). * * * $choices = array($user1, $user2); * * // call getName() to determine the choice labels * $choiceList = new ObjectChoiceList($choices, 'name'); * * * @author Bernhard Schussek */ class ObjectChoiceList extends ChoiceList { /** * @var PropertyAccessorInterface */ private $propertyAccessor; /** * The property path used to obtain the choice label. * * @var PropertyPath */ private $labelPath; /** * The property path used for object grouping. * * @var PropertyPath */ private $groupPath; /** * The property path used to obtain the choice value. * * @var PropertyPath */ private $valuePath; /** * Creates a new object choice list. * * @param array|\Traversable $choices The array of choices. Choices may also be given * as hierarchy of unlimited depth by creating nested * arrays. The title of the sub-hierarchy can be * stored in the array key pointing to the nested * array. The topmost level of the hierarchy may also * be a \Traversable. * @param string $labelPath A property path pointing to the property used * for the choice labels. The value is obtained * by calling the getter on the object. If the * path is NULL, the object's __toString() method * is used instead. * @param array $preferredChoices A flat array of choices that should be * presented to the user with priority. * @param string $groupPath A property path pointing to the property used * to group the choices. Only allowed if * the choices are given as flat array. * @param string $valuePath A property path pointing to the property used * for the choice values. If not given, integers * are generated instead. * @param PropertyAccessorInterface $propertyAccessor The reflection graph for reading property paths. */ public function __construct($choices, $labelPath = null, array $preferredChoices = array(), $groupPath = null, $valuePath = null, PropertyAccessorInterface $propertyAccessor = null) { $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); $this->labelPath = null !== $labelPath ? new PropertyPath($labelPath) : null; $this->groupPath = null !== $groupPath ? new PropertyPath($groupPath) : null; $this->valuePath = null !== $valuePath ? new PropertyPath($valuePath) : null; parent::__construct($choices, array(), $preferredChoices); } /** * Initializes the list with choices. * * Safe to be called multiple times. The list is cleared on every call. * * @param array|\Traversable $choices The choices to write into the list. * @param array $labels Ignored. * @param array $preferredChoices The choices to display with priority. * * @throws InvalidArgumentException When passing a hierarchy of choices and using * the "groupPath" option at the same time. */ protected function initialize($choices, array $labels, array $preferredChoices) { if (null !== $this->groupPath) { $groupedChoices = array(); foreach ($choices as $i => $choice) { if (is_array($choice)) { throw new InvalidArgumentException('You should pass a plain object array (without groups) when using the "groupPath" option.'); } try { $group = $this->propertyAccessor->getValue($choice, $this->groupPath); } catch (NoSuchPropertyException $e) { // Don't group items whose group property does not exist // see https://github.com/symfony/symfony/commit/d9b7abb7c7a0f28e0ce970afc5e305dce5dccddf $group = null; } if (null === $group) { $groupedChoices[$i] = $choice; } else { $groupName = (string) $group; if (!isset($groupedChoices[$groupName])) { $groupedChoices[$groupName] = array(); } $groupedChoices[$groupName][$i] = $choice; } } $choices = $groupedChoices; } $labels = array(); $this->extractLabels($choices, $labels); parent::initialize($choices, $labels, $preferredChoices); } /** * Creates a new unique value for this choice. * * If a property path for the value was given at object creation, * the getter behind that path is now called to obtain a new value. * Otherwise a new integer is generated. * * @param mixed $choice The choice to create a value for * * @return integer|string A unique value without character limitations. */ protected function createValue($choice) { if ($this->valuePath) { return (string) $this->propertyAccessor->getValue($choice, $this->valuePath); } return parent::createValue($choice); } private function extractLabels($choices, array &$labels) { foreach ($choices as $i => $choice) { if (is_array($choice)) { $labels[$i] = array(); $this->extractLabels($choice, $labels[$i]); } elseif ($this->labelPath) { $labels[$i] = $this->propertyAccessor->getValue($choice, $this->labelPath); } elseif (method_exists($choice, '__toString')) { $labels[$i] = (string) $choice; } else { throw new StringCastException(sprintf('A "__toString()" method was not found on the objects of type "%s" passed to the choice field. To read a custom getter instead, set the argument $labelPath to the desired property path.', get_class($choice))); } } } } Core/ChoiceList/LazyChoiceList.php000064400000006464152405076560013105 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\ChoiceList; use Symfony\Component\Form\Exception\InvalidArgumentException; /** * A choice list that is loaded lazily * * This list loads itself as soon as any of the getters is accessed for the * first time. You should implement loadChoiceList() in your child classes, * which should return a ChoiceListInterface instance. * * @author Bernhard Schussek */ abstract class LazyChoiceList implements ChoiceListInterface { /** * The loaded choice list * * @var ChoiceListInterface */ private $choiceList; /** * {@inheritdoc} */ public function getChoices() { if (!$this->choiceList) { $this->load(); } return $this->choiceList->getChoices(); } /** * {@inheritdoc} */ public function getValues() { if (!$this->choiceList) { $this->load(); } return $this->choiceList->getValues(); } /** * {@inheritdoc} */ public function getPreferredViews() { if (!$this->choiceList) { $this->load(); } return $this->choiceList->getPreferredViews(); } /** * {@inheritdoc} */ public function getRemainingViews() { if (!$this->choiceList) { $this->load(); } return $this->choiceList->getRemainingViews(); } /** * {@inheritdoc} */ public function getChoicesForValues(array $values) { if (!$this->choiceList) { $this->load(); } return $this->choiceList->getChoicesForValues($values); } /** * {@inheritdoc} */ public function getValuesForChoices(array $choices) { if (!$this->choiceList) { $this->load(); } return $this->choiceList->getValuesForChoices($choices); } /** * {@inheritdoc} * * @deprecated Deprecated since version 2.4, to be removed in 3.0. */ public function getIndicesForChoices(array $choices) { if (!$this->choiceList) { $this->load(); } return $this->choiceList->getIndicesForChoices($choices); } /** * {@inheritdoc} * * @deprecated Deprecated since version 2.4, to be removed in 3.0. */ public function getIndicesForValues(array $values) { if (!$this->choiceList) { $this->load(); } return $this->choiceList->getIndicesForValues($values); } /** * Loads the choice list * * Should be implemented by child classes. * * @return ChoiceListInterface The loaded choice list */ abstract protected function loadChoiceList(); private function load() { $choiceList = $this->loadChoiceList(); if (!$choiceList instanceof ChoiceListInterface) { throw new InvalidArgumentException(sprintf('loadChoiceList() should return a ChoiceListInterface instance. Got %s', gettype($choiceList))); } $this->choiceList = $choiceList; } } Core/ChoiceList/SimpleChoiceList.php000064400000012634152405076560013413 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\ChoiceList; /** * A choice list for choices of type string or integer. * * Choices and their associated labels can be passed in a single array. Since * choices are passed as array keys, only strings or integer choices are * allowed. Choices may also be given as hierarchy of unlimited depth by * creating nested arrays. The title of the sub-hierarchy can be stored in the * array key pointing to the nested array. * * * $choiceList = new SimpleChoiceList(array( * 'creditcard' => 'Credit card payment', * 'cash' => 'Cash payment', * )); * * * @author Bernhard Schussek */ class SimpleChoiceList extends ChoiceList { /** * Creates a new simple choice list. * * @param array $choices The array of choices with the choices as keys and * the labels as values. Choices may also be given * as hierarchy of unlimited depth by creating nested * arrays. The title of the sub-hierarchy is stored * in the array key pointing to the nested array. * @param array $preferredChoices A flat array of choices that should be * presented to the user with priority. */ public function __construct(array $choices, array $preferredChoices = array()) { // Flip preferred choices to speed up lookup parent::__construct($choices, $choices, array_flip($preferredChoices)); } /** * {@inheritdoc} */ public function getChoicesForValues(array $values) { $values = $this->fixValues($values); // The values are identical to the choices, so we can just return them // to improve performance a little bit return $this->fixChoices(array_intersect($values, $this->getValues())); } /** * {@inheritdoc} */ public function getValuesForChoices(array $choices) { $choices = $this->fixChoices($choices); // The choices are identical to the values, so we can just return them // to improve performance a little bit return $this->fixValues(array_intersect($choices, $this->getValues())); } /** * Recursively adds the given choices to the list. * * Takes care of splitting the single $choices array passed in the * constructor into choices and labels. * * @param array $bucketForPreferred The bucket where to store the preferred * view objects. * @param array $bucketForRemaining The bucket where to store the * non-preferred view objects. * @param array|\Traversable $choices The list of choices. * @param array $labels Ignored. * @param array $preferredChoices The preferred choices. */ protected function addChoices(array &$bucketForPreferred, array &$bucketForRemaining, $choices, array $labels, array $preferredChoices) { // Add choices to the nested buckets foreach ($choices as $choice => $label) { if (is_array($label)) { // Don't do the work if the array is empty if (count($label) > 0) { $this->addChoiceGroup( $choice, $bucketForPreferred, $bucketForRemaining, $label, $label, $preferredChoices ); } } else { $this->addChoice( $bucketForPreferred, $bucketForRemaining, $choice, $label, $preferredChoices ); } } } /** * Returns whether the given choice should be preferred judging by the * given array of preferred choices. * * Optimized for performance by treating the preferred choices as array * where choices are stored in the keys. * * @param mixed $choice The choice to test. * @param array $preferredChoices An array of preferred choices. * * @return Boolean Whether the choice is preferred. */ protected function isPreferred($choice, array $preferredChoices) { // Optimize performance over the default implementation return isset($preferredChoices[$choice]); } /** * Converts the choice to a valid PHP array key. * * @param mixed $choice The choice * * @return string|integer A valid PHP array key */ protected function fixChoice($choice) { return $this->fixIndex($choice); } /** * {@inheritdoc} */ protected function fixChoices(array $choices) { return $this->fixIndices($choices); } /** * {@inheritdoc} */ protected function createValue($choice) { // Choices are guaranteed to be unique and scalar, so we can simply // convert them to strings return (string) $choice; } } Core/ChoiceList/ChoiceListInterface.php000064400000012406152405076560014057 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\ChoiceList; /** * Contains choices that can be selected in a form field. * * Each choice has three different properties: * * - Choice: The choice that should be returned to the application by the * choice field. Can be any scalar value or an object, but no * array. * - Label: A text representing the choice that is displayed to the user. * - Value: A uniquely identifying value that can contain arbitrary * characters, but no arrays or objects. This value is displayed * in the HTML "value" attribute. * * @author Bernhard Schussek */ interface ChoiceListInterface { /** * Returns the list of choices * * @return array The choices with their indices as keys */ public function getChoices(); /** * Returns the values for the choices * * @return array The values with the corresponding choice indices as keys */ public function getValues(); /** * Returns the choice views of the preferred choices as nested array with * the choice groups as top-level keys. * * Example: * * * array( * 'Group 1' => array( * 10 => ChoiceView object, * 20 => ChoiceView object, * ), * 'Group 2' => array( * 30 => ChoiceView object, * ), * ) * * * @return array A nested array containing the views with the corresponding * choice indices as keys on the lowest levels and the choice * group names in the keys of the higher levels */ public function getPreferredViews(); /** * Returns the choice views of the choices that are not preferred as nested * array with the choice groups as top-level keys. * * Example: * * * array( * 'Group 1' => array( * 10 => ChoiceView object, * 20 => ChoiceView object, * ), * 'Group 2' => array( * 30 => ChoiceView object, * ), * ) * * * @return array A nested array containing the views with the corresponding * choice indices as keys on the lowest levels and the choice * group names in the keys of the higher levels * * @see getPreferredValues */ public function getRemainingViews(); /** * Returns the choices corresponding to the given values. * * The choices can have any data type. * * The choices must be returned with the same keys and in the same order * as the corresponding values in the given array. * * @param array $values An array of choice values. Not existing values in * this array are ignored * * @return array An array of choices with ascending, 0-based numeric keys */ public function getChoicesForValues(array $values); /** * Returns the values corresponding to the given choices. * * The values must be strings. * * The values must be returned with the same keys and in the same order * as the corresponding choices in the given array. * * @param array $choices An array of choices. Not existing choices in this * array are ignored * * @return array An array of choice values with ascending, 0-based numeric * keys */ public function getValuesForChoices(array $choices); /** * Returns the indices corresponding to the given choices. * * The indices must be positive integers or strings accepted by * {@link FormConfigBuilder::validateName()}. * * The index "placeholder" is internally reserved. * * The indices must be returned with the same keys and in the same order * as the corresponding choices in the given array. * * @param array $choices An array of choices. Not existing choices in this * array are ignored * * @return array An array of indices with ascending, 0-based numeric keys * * @deprecated Deprecated since version 2.4, to be removed in 3.0. */ public function getIndicesForChoices(array $choices); /** * Returns the indices corresponding to the given values. * * The indices must be positive integers or strings accepted by * {@link FormConfigBuilder::validateName()}. * * The index "placeholder" is internally reserved. * * The indices must be returned with the same keys and in the same order * as the corresponding values in the given array. * * @param array $values An array of choice values. Not existing values in * this array are ignored * * @return array An array of indices with ascending, 0-based numeric keys * * @deprecated Deprecated since version 2.4, to be removed in 3.0. */ public function getIndicesForValues(array $values); } Core/EventListener/FixUrlProtocolListener.php000064400000002627152405076560015416 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\EventListener; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Adds a protocol to a URL if it doesn't already have one. * * @author Bernhard Schussek */ class FixUrlProtocolListener implements EventSubscriberInterface { private $defaultProtocol; public function __construct($defaultProtocol = 'http') { $this->defaultProtocol = $defaultProtocol; } public function onSubmit(FormEvent $event) { $data = $event->getData(); if ($this->defaultProtocol && $data && !preg_match('~^\w+://~', $data)) { $event->setData($this->defaultProtocol.'://'.$data); } } /** * Alias of {@link onSubmit()}. * * @deprecated Deprecated since version 2.3, to be removed in 3.0. Use * {@link onSubmit()} instead. */ public function onBind(FormEvent $event) { $this->onSubmit($event); } public static function getSubscribedEvents() { return array(FormEvents::SUBMIT => 'onSubmit'); } } Core/EventListener/FixRadioInputListener.php000064400000004332152405076560015203 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\EventListener; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface; /** * Takes care of converting the input from a single radio button * to an array. * * @author Bernhard Schussek */ class FixRadioInputListener implements EventSubscriberInterface { private $choiceList; private $placeholderPresent; /** * Constructor. * * @param ChoiceListInterface $choiceList * @param Boolean $placeholderPresent */ public function __construct(ChoiceListInterface $choiceList, $placeholderPresent) { $this->choiceList = $choiceList; $this->placeholderPresent = $placeholderPresent; } public function preSubmit(FormEvent $event) { $data = $event->getData(); // Since expanded choice fields are completely loaded anyway, we // can just as well get the values again without losing performance. $existingValues = $this->choiceList->getValues(); if (false !== ($index = array_search($data, $existingValues, true))) { $data = array($index => $data); } elseif ('' === $data || null === $data) { // Empty values are always accepted. $data = $this->placeholderPresent ? array('placeholder' => '') : array(); } // Else leave the data unchanged to provoke an error during submission $event->setData($data); } /** * Alias of {@link preSubmit()}. * * @deprecated Deprecated since version 2.3, to be removed in 3.0. Use * {@link preSubmit()} instead. */ public function preBind(FormEvent $event) { $this->preSubmit($event); } public static function getSubscribedEvents() { return array(FormEvents::PRE_SUBMIT => 'preSubmit'); } } Core/EventListener/MergeCollectionListener.php000064400000010222152405076560015524 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\Exception\UnexpectedTypeException; /** * @author Bernhard Schussek */ class MergeCollectionListener implements EventSubscriberInterface { /** * Whether elements may be added to the collection * @var Boolean */ private $allowAdd; /** * Whether elements may be removed from the collection * @var Boolean */ private $allowDelete; /** * Creates a new listener. * * @param Boolean $allowAdd Whether values might be added to the * collection. * @param Boolean $allowDelete Whether values might be removed from the * collection. */ public function __construct($allowAdd = false, $allowDelete = false) { $this->allowAdd = $allowAdd; $this->allowDelete = $allowDelete; } public static function getSubscribedEvents() { return array( FormEvents::SUBMIT => 'onSubmit', ); } public function onSubmit(FormEvent $event) { $dataToMergeInto = $event->getForm()->getNormData(); $data = $event->getData(); if (null === $data) { $data = array(); } if (!is_array($data) && !($data instanceof \Traversable && $data instanceof \ArrayAccess)) { throw new UnexpectedTypeException($data, 'array or (\Traversable and \ArrayAccess)'); } if (null !== $dataToMergeInto && !is_array($dataToMergeInto) && !($dataToMergeInto instanceof \Traversable && $dataToMergeInto instanceof \ArrayAccess)) { throw new UnexpectedTypeException($dataToMergeInto, 'array or (\Traversable and \ArrayAccess)'); } // If we are not allowed to change anything, return immediately if ((!$this->allowAdd && !$this->allowDelete) || $data === $dataToMergeInto) { $event->setData($dataToMergeInto); return; } if (!$dataToMergeInto) { // No original data was set. Set it if allowed if ($this->allowAdd) { $dataToMergeInto = $data; } } else { // Calculate delta $itemsToAdd = is_object($data) ? clone $data : $data; $itemsToDelete = array(); foreach ($dataToMergeInto as $beforeKey => $beforeItem) { foreach ($data as $afterKey => $afterItem) { if ($afterItem === $beforeItem) { // Item found, next original item unset($itemsToAdd[$afterKey]); continue 2; } } // Item not found, remember for deletion $itemsToDelete[] = $beforeKey; } // Remove deleted items before adding to free keys that are to be // replaced if ($this->allowDelete) { foreach ($itemsToDelete as $key) { unset($dataToMergeInto[$key]); } } // Add remaining items if ($this->allowAdd) { foreach ($itemsToAdd as $key => $item) { if (!isset($dataToMergeInto[$key])) { $dataToMergeInto[$key] = $item; } else { $dataToMergeInto[] = $item; } } } } $event->setData($dataToMergeInto); } /** * Alias of {@link onSubmit()}. * * @deprecated Deprecated since version 2.3, to be removed in 3.0. Use * {@link onSubmit()} instead. */ public function onBind(FormEvent $event) { $this->onSubmit($event); } } Core/EventListener/TrimListener.php000064400000002501152405076560013365 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\EventListener; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Trims string data * * @author Bernhard Schussek */ class TrimListener implements EventSubscriberInterface { public function preSubmit(FormEvent $event) { $data = $event->getData(); if (!is_string($data)) { return; } if (null !== $result = @preg_replace('/^[\pZ\p{Cc}]+|[\pZ\p{Cc}]+$/u', '', $data)) { $event->setData($result); } else { $event->setData(trim($data)); } } /** * Alias of {@link preSubmit()}. * * @deprecated Deprecated since version 2.3, to be removed in 3.0. Use * {@link preSubmit()} instead. */ public function preBind(FormEvent $event) { $this->preSubmit($event); } public static function getSubscribedEvents() { return array(FormEvents::PRE_SUBMIT => 'preSubmit'); } } Core/EventListener/FixCheckboxInputListener.php000064400000005612152405076560015675 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\EventListener; use Symfony\Component\Form\Exception\TransformationFailedException; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface; /** * Takes care of converting the input from a list of checkboxes to a correctly * indexed array. * * @author Bernhard Schussek */ class FixCheckboxInputListener implements EventSubscriberInterface { private $choiceList; /** * Constructor. * * @param ChoiceListInterface $choiceList */ public function __construct(ChoiceListInterface $choiceList) { $this->choiceList = $choiceList; } public function preSubmit(FormEvent $event) { $data = $event->getData(); if (is_array($data)) { // Flip the submitted values for faster lookup // It's better to flip this array than $existingValues because // $submittedValues is generally smaller. $submittedValues = array_flip($data); // Since expanded choice fields are completely loaded anyway, we // can just as well get the values again without losing performance. $existingValues = $this->choiceList->getValues(); // Clear the data array and fill it with correct indices $data = array(); foreach ($existingValues as $index => $value) { if (isset($submittedValues[$value])) { // Value was submitted $data[$index] = $value; unset($submittedValues[$value]); } } if (count($submittedValues) > 0) { throw new TransformationFailedException(sprintf( 'The following choices were not found: "%s"', implode('", "', array_keys($submittedValues)) )); } } elseif ('' === $data || null === $data) { // Empty values are always accepted. $data = array(); } // Else leave the data unchanged to provoke an error during submission $event->setData($data); } /** * Alias of {@link preSubmit()}. * * @deprecated Deprecated since version 2.3, to be removed in 3.0. Use * {@link preSubmit()} instead. */ public function preBind(FormEvent $event) { $this->preSubmit($event); } public static function getSubscribedEvents() { return array(FormEvents::PRE_SUBMIT => 'preSubmit'); } } Core/EventListener/ResizeFormListener.php000064400000011457152405076560014551 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\EventListener; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\Exception\UnexpectedTypeException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Resize a collection form element based on the data sent from the client. * * @author Bernhard Schussek */ class ResizeFormListener implements EventSubscriberInterface { /** * @var string */ protected $type; /** * @var array */ protected $options; /** * Whether children could be added to the group * @var Boolean */ protected $allowAdd; /** * Whether children could be removed from the group * @var Boolean */ protected $allowDelete; public function __construct($type, array $options = array(), $allowAdd = false, $allowDelete = false) { $this->type = $type; $this->allowAdd = $allowAdd; $this->allowDelete = $allowDelete; $this->options = $options; } public static function getSubscribedEvents() { return array( FormEvents::PRE_SET_DATA => 'preSetData', FormEvents::PRE_SUBMIT => 'preSubmit', // (MergeCollectionListener, MergeDoctrineCollectionListener) FormEvents::SUBMIT => array('onSubmit', 50), ); } public function preSetData(FormEvent $event) { $form = $event->getForm(); $data = $event->getData(); if (null === $data) { $data = array(); } if (!is_array($data) && !($data instanceof \Traversable && $data instanceof \ArrayAccess)) { throw new UnexpectedTypeException($data, 'array or (\Traversable and \ArrayAccess)'); } // First remove all rows foreach ($form as $name => $child) { $form->remove($name); } // Then add all rows again in the correct order foreach ($data as $name => $value) { $form->add($name, $this->type, array_replace(array( 'property_path' => '['.$name.']', ), $this->options)); } } public function preSubmit(FormEvent $event) { $form = $event->getForm(); $data = $event->getData(); if (null === $data || '' === $data) { $data = array(); } if (!is_array($data) && !($data instanceof \Traversable && $data instanceof \ArrayAccess)) { throw new UnexpectedTypeException($data, 'array or (\Traversable and \ArrayAccess)'); } // Remove all empty rows if ($this->allowDelete) { foreach ($form as $name => $child) { if (!isset($data[$name])) { $form->remove($name); } } } // Add all additional rows if ($this->allowAdd) { foreach ($data as $name => $value) { if (!$form->has($name)) { $form->add($name, $this->type, array_replace(array( 'property_path' => '['.$name.']', ), $this->options)); } } } } public function onSubmit(FormEvent $event) { $form = $event->getForm(); $data = $event->getData(); if (null === $data) { $data = array(); } if (!is_array($data) && !($data instanceof \Traversable && $data instanceof \ArrayAccess)) { throw new UnexpectedTypeException($data, 'array or (\Traversable and \ArrayAccess)'); } // The data mapper only adds, but does not remove items, so do this // here if ($this->allowDelete) { $toDelete = array(); foreach ($data as $name => $child) { if (!$form->has($name)) { $toDelete[] = $name; } } foreach ($toDelete as $name) { unset($data[$name]); } } $event->setData($data); } /** * Alias of {@link preSubmit()}. * * @deprecated Deprecated since version 2.3, to be removed in 3.0. Use * {@link preSubmit()} instead. */ public function preBind(FormEvent $event) { $this->preSubmit($event); } /** * Alias of {@link onSubmit()}. * * @deprecated Deprecated since version 2.3, to be removed in 3.0. Use * {@link onSubmit()} instead. */ public function onBind(FormEvent $event) { $this->onSubmit($event); } } Core/CoreExtension.php000064400000003331152405076560010744 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core; use Symfony\Component\Form\AbstractExtension; use Symfony\Component\PropertyAccess\PropertyAccess; /** * Represents the main form extension, which loads the core functionality. * * @author Bernhard Schussek */ class CoreExtension extends AbstractExtension { protected function loadTypes() { return array( new Type\FormType(PropertyAccess::createPropertyAccessor()), new Type\BirthdayType(), new Type\CheckboxType(), new Type\ChoiceType(), new Type\CollectionType(), new Type\CountryType(), new Type\DateType(), new Type\DateTimeType(), new Type\EmailType(), new Type\HiddenType(), new Type\IntegerType(), new Type\LanguageType(), new Type\LocaleType(), new Type\MoneyType(), new Type\NumberType(), new Type\PasswordType(), new Type\PercentType(), new Type\RadioType(), new Type\RepeatedType(), new Type\SearchType(), new Type\TextareaType(), new Type\TextType(), new Type\TimeType(), new Type\TimezoneType(), new Type\UrlType(), new Type\FileType(), new Type\ButtonType(), new Type\SubmitType(), new Type\ResetType(), new Type\CurrencyType(), ); } } Validator/Constraints/FormValidator.php000064400000015626152405076560014306 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\Constraints; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\Extension\Validator\Util\ServerParams; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; /** * @author Bernhard Schussek */ class FormValidator extends ConstraintValidator { /** * @var ServerParams */ private $serverParams; /** * Creates a validator with the given server parameters. * * @param ServerParams $params The server parameters. Default * parameters are created if null. */ public function __construct(ServerParams $params = null) { $this->serverParams = $params ?: new ServerParams(); } /** * {@inheritdoc} */ public function validate($form, Constraint $constraint) { if (!$form instanceof FormInterface) { return; } /* @var FormInterface $form */ $config = $form->getConfig(); if ($form->isSynchronized()) { // Validate the form data only if transformation succeeded $groups = self::getValidationGroups($form); // Validate the data against its own constraints if (self::allowDataWalking($form)) { foreach ($groups as $group) { $this->context->validate($form->getData(), 'data', $group, true); } } // Validate the data against the constraints defined // in the form $constraints = $config->getOption('constraints'); foreach ($constraints as $constraint) { foreach ($groups as $group) { if (in_array($group, $constraint->groups)) { $this->context->validateValue($form->getData(), $constraint, 'data', $group); // Prevent duplicate validation continue 2; } } } } else { $childrenSynchronized = true; foreach ($form as $child) { if (!$child->isSynchronized()) { $childrenSynchronized = false; break; } } // Mark the form with an error if it is not synchronized BUT all // of its children are synchronized. If any child is not // synchronized, an error is displayed there already and showing // a second error in its parent form is pointless, or worse, may // lead to duplicate errors if error bubbling is enabled on the // child. // See also https://github.com/symfony/symfony/issues/4359 if ($childrenSynchronized) { $clientDataAsString = is_scalar($form->getViewData()) ? (string) $form->getViewData() : gettype($form->getViewData()); $this->context->addViolation( $config->getOption('invalid_message'), array_replace(array('{{ value }}' => $clientDataAsString), $config->getOption('invalid_message_parameters')), $form->getViewData(), null, Form::ERR_INVALID ); } } // Mark the form with an error if it contains extra fields if (count($form->getExtraData()) > 0) { $this->context->addViolation( $config->getOption('extra_fields_message'), array('{{ extra_fields }}' => implode('", "', array_keys($form->getExtraData()))), $form->getExtraData() ); } // Mark the form with an error if the uploaded size was too large $length = $this->serverParams->getContentLength(); if ($form->isRoot() && null !== $length) { $max = $this->serverParams->getPostMaxSize(); if (!empty($max) && $length > $max) { $this->context->addViolation( $config->getOption('post_max_size_message'), array('{{ max }}' => $this->serverParams->getNormalizedIniPostMaxSize()), $length ); } } } /** * Returns whether the data of a form may be walked. * * @param FormInterface $form The form to test. * * @return Boolean Whether the graph walker may walk the data. */ private static function allowDataWalking(FormInterface $form) { $data = $form->getData(); // Scalar values cannot have mapped constraints if (!is_object($data) && !is_array($data)) { return false; } // Root forms are always validated if ($form->isRoot()) { return true; } // Non-root forms are validated if validation cascading // is enabled in all ancestor forms while (null !== ($form = $form->getParent())) { if (!$form->getConfig()->getOption('cascade_validation')) { return false; } } return true; } /** * Returns the validation groups of the given form. * * @param FormInterface $form The form. * * @return array The validation groups. */ private static function getValidationGroups(FormInterface $form) { // Determine the clicked button of the complete form tree $clickedButton = null; if (method_exists($form, 'getClickedButton')) { $clickedButton = $form->getClickedButton(); } if (null !== $clickedButton) { $groups = $clickedButton->getConfig()->getOption('validation_groups'); if (null !== $groups) { return self::resolveValidationGroups($groups, $form); } } do { $groups = $form->getConfig()->getOption('validation_groups'); if (null !== $groups) { return self::resolveValidationGroups($groups, $form); } $form = $form->getParent(); } while (null !== $form); return array(Constraint::DEFAULT_GROUP); } /** * Post-processes the validation groups option for a given form. * * @param array|callable $groups The validation groups. * @param FormInterface $form The validated form. * * @return array The validation groups. */ private static function resolveValidationGroups($groups, FormInterface $form) { if (!is_string($groups) && is_callable($groups)) { $groups = call_user_func($groups, $form); } return (array) $groups; } } Validator/Constraints/Form.php000064400000001217152405076560012427 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\Constraints; use Symfony\Component\Validator\Constraint; /** * @author Bernhard Schussek */ class Form extends Constraint { /** * Violation code marking an invalid form. */ const ERR_INVALID = 1; /** * {@inheritdoc} */ public function getTargets() { return self::CLASS_CONSTRAINT; } } Validator/Type/BaseValidatorExtension.php000064400000002721152405076560014554 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\Type; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; /** * Encapsulates common logic of {@link FormTypeValidatorExtension} and * {@link SubmitTypeValidatorExtension}. * * @author Bernhard Schussek */ abstract class BaseValidatorExtension extends AbstractTypeExtension { /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { // Make sure that validation groups end up as null, closure or array $validationGroupsNormalizer = function (Options $options, $groups) { if (false === $groups) { return array(); } if (empty($groups)) { return null; } if (is_callable($groups)) { return $groups; } return (array) $groups; }; $resolver->setDefaults(array( 'validation_groups' => null, )); $resolver->setNormalizers(array( 'validation_groups' => $validationGroupsNormalizer, )); } } Validator/Type/SubmitTypeValidatorExtension.php000064400000001033152405076560016002 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\Type; /** * @author Bernhard Schussek */ class SubmitTypeValidatorExtension extends BaseValidatorExtension { /** * {@inheritdoc} */ public function getExtendedType() { return 'submit'; } } Validator/Type/RepeatedTypeValidatorExtension.php000064400000002100152405076560016264 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\Type; use Symfony\Component\Form\AbstractTypeExtension; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; /** * @author Bernhard Schussek */ class RepeatedTypeValidatorExtension extends AbstractTypeExtension { /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { // Map errors to the first field $errorMapping = function (Options $options) { return array('.' => $options['first_name']); }; $resolver->setDefaults(array( 'error_mapping' => $errorMapping, )); } /** * {@inheritdoc} */ public function getExtendedType() { return 'repeated'; } } Validator/Type/FormTypeValidatorExtension.php000064400000004730152405076560015451 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\Type; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapper; use Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener; use Symfony\Component\Validator\ValidatorInterface; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; /** * @author Bernhard Schussek */ class FormTypeValidatorExtension extends BaseValidatorExtension { /** * @var ValidatorInterface */ private $validator; /** * @var ViolationMapper */ private $violationMapper; public function __construct(ValidatorInterface $validator) { $this->validator = $validator; $this->violationMapper = new ViolationMapper(); } /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addEventSubscriber(new ValidationListener($this->validator, $this->violationMapper)); } /** * {@inheritdoc} */ public function setDefaultOptions(OptionsResolverInterface $resolver) { parent::setDefaultOptions($resolver); // Constraint should always be converted to an array $constraintsNormalizer = function (Options $options, $constraints) { return is_object($constraints) ? array($constraints) : (array) $constraints; }; $resolver->setDefaults(array( 'error_mapping' => array(), 'constraints' => array(), 'cascade_validation' => false, 'invalid_message' => 'This value is not valid.', 'invalid_message_parameters' => array(), 'extra_fields_message' => 'This form should not contain extra fields.', 'post_max_size_message' => 'The uploaded file was too large. Please try to upload a smaller file.', )); $resolver->setNormalizers(array( 'constraints' => $constraintsNormalizer, )); } /** * {@inheritdoc} */ public function getExtendedType() { return 'form'; } } Validator/ValidatorTypeGuesser.php000064400000025164152405076560013351 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator; use Symfony\Component\Form\FormTypeGuesserInterface; use Symfony\Component\Form\Guess\Guess; use Symfony\Component\Form\Guess\TypeGuess; use Symfony\Component\Form\Guess\ValueGuess; use Symfony\Component\Validator\MetadataFactoryInterface; use Symfony\Component\Validator\Constraint; class ValidatorTypeGuesser implements FormTypeGuesserInterface { private $metadataFactory; public function __construct(MetadataFactoryInterface $metadataFactory) { $this->metadataFactory = $metadataFactory; } /** * {@inheritDoc} */ public function guessType($class, $property) { $guesser = $this; return $this->guess($class, $property, function (Constraint $constraint) use ($guesser) { return $guesser->guessTypeForConstraint($constraint); }); } /** * {@inheritDoc} */ public function guessRequired($class, $property) { $guesser = $this; return $this->guess($class, $property, function (Constraint $constraint) use ($guesser) { return $guesser->guessRequiredForConstraint($constraint); // If we don't find any constraint telling otherwise, we can assume // that a field is not required (with LOW_CONFIDENCE) }, false); } /** * {@inheritDoc} */ public function guessMaxLength($class, $property) { $guesser = $this; return $this->guess($class, $property, function (Constraint $constraint) use ($guesser) { return $guesser->guessMaxLengthForConstraint($constraint); }); } /** * {@inheritDoc} */ public function guessPattern($class, $property) { $guesser = $this; return $this->guess($class, $property, function (Constraint $constraint) use ($guesser) { return $guesser->guessPatternForConstraint($constraint); }); } /** * Guesses a field class name for a given constraint * * @param Constraint $constraint The constraint to guess for * * @return TypeGuess|null The guessed field class and options */ public function guessTypeForConstraint(Constraint $constraint) { switch (get_class($constraint)) { case 'Symfony\Component\Validator\Constraints\Type': switch ($constraint->type) { case 'array': return new TypeGuess('collection', array(), Guess::MEDIUM_CONFIDENCE); case 'boolean': case 'bool': return new TypeGuess('checkbox', array(), Guess::MEDIUM_CONFIDENCE); case 'double': case 'float': case 'numeric': case 'real': return new TypeGuess('number', array(), Guess::MEDIUM_CONFIDENCE); case 'integer': case 'int': case 'long': return new TypeGuess('integer', array(), Guess::MEDIUM_CONFIDENCE); case '\DateTime': return new TypeGuess('date', array(), Guess::MEDIUM_CONFIDENCE); case 'string': return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE); } break; case 'Symfony\Component\Validator\Constraints\Country': return new TypeGuess('country', array(), Guess::HIGH_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\Date': return new TypeGuess('date', array('input' => 'string'), Guess::HIGH_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\DateTime': return new TypeGuess('datetime', array('input' => 'string'), Guess::HIGH_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\Email': return new TypeGuess('email', array(), Guess::HIGH_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\File': case 'Symfony\Component\Validator\Constraints\Image': return new TypeGuess('file', array(), Guess::HIGH_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\Language': return new TypeGuess('language', array(), Guess::HIGH_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\Locale': return new TypeGuess('locale', array(), Guess::HIGH_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\Time': return new TypeGuess('time', array('input' => 'string'), Guess::HIGH_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\Url': return new TypeGuess('url', array(), Guess::HIGH_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\Ip': return new TypeGuess('text', array(), Guess::MEDIUM_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\Length': case 'Symfony\Component\Validator\Constraints\Regex': return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\Range': return new TypeGuess('number', array(), Guess::LOW_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\Count': return new TypeGuess('collection', array(), Guess::LOW_CONFIDENCE); case 'Symfony\Component\Validator\Constraints\True': case 'Symfony\Component\Validator\Constraints\False': return new TypeGuess('checkbox', array(), Guess::MEDIUM_CONFIDENCE); } return null; } /** * Guesses whether a field is required based on the given constraint * * @param Constraint $constraint The constraint to guess for * * @return ValueGuess|null The guess whether the field is required */ public function guessRequiredForConstraint(Constraint $constraint) { switch (get_class($constraint)) { case 'Symfony\Component\Validator\Constraints\NotNull': case 'Symfony\Component\Validator\Constraints\NotBlank': case 'Symfony\Component\Validator\Constraints\True': return new ValueGuess(true, Guess::HIGH_CONFIDENCE); } return null; } /** * Guesses a field's maximum length based on the given constraint * * @param Constraint $constraint The constraint to guess for * * @return ValueGuess|null The guess for the maximum length */ public function guessMaxLengthForConstraint(Constraint $constraint) { switch (get_class($constraint)) { case 'Symfony\Component\Validator\Constraints\Length': if (is_numeric($constraint->max)) { return new ValueGuess($constraint->max, Guess::HIGH_CONFIDENCE); } break; case 'Symfony\Component\Validator\Constraints\Type': if (in_array($constraint->type, array('double', 'float', 'numeric', 'real'))) { return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); } break; case 'Symfony\Component\Validator\Constraints\Range': if (is_numeric($constraint->max)) { return new ValueGuess(strlen((string) $constraint->max), Guess::LOW_CONFIDENCE); } break; } return null; } /** * Guesses a field's pattern based on the given constraint * * @param Constraint $constraint The constraint to guess for * * @return ValueGuess|null The guess for the pattern */ public function guessPatternForConstraint(Constraint $constraint) { switch (get_class($constraint)) { case 'Symfony\Component\Validator\Constraints\Length': if (is_numeric($constraint->min)) { return new ValueGuess(sprintf('.{%s,}', (string) $constraint->min), Guess::LOW_CONFIDENCE); } break; case 'Symfony\Component\Validator\Constraints\Regex': $htmlPattern = $constraint->getHtmlPattern(); if (null !== $htmlPattern) { return new ValueGuess($htmlPattern, Guess::HIGH_CONFIDENCE); } break; case 'Symfony\Component\Validator\Constraints\Range': if (is_numeric($constraint->min)) { return new ValueGuess(sprintf('.{%s,}', strlen((string) $constraint->min)), Guess::LOW_CONFIDENCE); } break; case 'Symfony\Component\Validator\Constraints\Type': if (in_array($constraint->type, array('double', 'float', 'numeric', 'real'))) { return new ValueGuess(null, Guess::MEDIUM_CONFIDENCE); } break; } return null; } /** * Iterates over the constraints of a property, executes a constraints on * them and returns the best guess * * @param string $class The class to read the constraints from * @param string $property The property for which to find constraints * @param \Closure $closure The closure that returns a guess * for a given constraint * @param mixed $defaultValue The default value assumed if no other value * can be guessed. * * @return Guess|null The guessed value with the highest confidence */ protected function guess($class, $property, \Closure $closure, $defaultValue = null) { $guesses = array(); $classMetadata = $this->metadataFactory->getMetadataFor($class); if ($classMetadata->hasMemberMetadatas($property)) { $memberMetadatas = $classMetadata->getMemberMetadatas($property); foreach ($memberMetadatas as $memberMetadata) { $constraints = $memberMetadata->getConstraints(); foreach ($constraints as $constraint) { if ($guess = $closure($constraint)) { $guesses[] = $guess; } } } if (null !== $defaultValue) { $guesses[] = new ValueGuess($defaultValue, Guess::LOW_CONFIDENCE); } } return Guess::getBestGuess($guesses); } } Validator/Util/ServerParams.php000064400000003214152405076560012543 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\Util; /** * @author Bernhard Schussek */ class ServerParams { /** * Returns maximum post size in bytes. * * @return null|integer The maximum post size in bytes */ public function getPostMaxSize() { $iniMax = strtolower($this->getNormalizedIniPostMaxSize()); if ('' === $iniMax) { return null; } $max = ltrim($iniMax, '+'); if (0 === strpos($max, '0x')) { $max = intval($max, 16); } elseif (0 === strpos($max, '0')) { $max = intval($max, 8); } else { $max = intval($max); } switch (substr($iniMax, -1)) { case 't': $max *= 1024; case 'g': $max *= 1024; case 'm': $max *= 1024; case 'k': $max *= 1024; } return $max; } /** * Returns the normalized "post_max_size" ini setting. * * @return string */ public function getNormalizedIniPostMaxSize() { return strtoupper(trim(ini_get('post_max_size'))); } /** * Returns the content length of the request. * * @return mixed The request content length. */ public function getContentLength() { return isset($_SERVER['CONTENT_LENGTH']) ? (int) $_SERVER['CONTENT_LENGTH'] : null; } } Validator/EventListener/ValidationListener.php000064400000003617152405076560015612 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\Extension\Validator\ViolationMapper\ViolationMapperInterface; use Symfony\Component\Validator\ValidatorInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\Extension\Validator\Constraints\Form; /** * @author Bernhard Schussek */ class ValidationListener implements EventSubscriberInterface { private $validator; private $violationMapper; /** * {@inheritdoc} */ public static function getSubscribedEvents() { return array(FormEvents::POST_SUBMIT => 'validateForm'); } public function __construct(ValidatorInterface $validator, ViolationMapperInterface $violationMapper) { $this->validator = $validator; $this->violationMapper = $violationMapper; } /** * Validates the form and its domain object. * * @param FormEvent $event The event object */ public function validateForm(FormEvent $event) { $form = $event->getForm(); if ($form->isRoot()) { // Validate the form in group "Default" $violations = $this->validator->validate($form); foreach ($violations as $violation) { // Allow the "invalid" constraint to be put onto // non-synchronized forms $allowNonSynchronized = Form::ERR_INVALID === $violation->getCode(); $this->violationMapper->mapViolation($violation, $form, $allowNonSynchronized); } } } } Validator/ValidatorExtension.php000064400000003450152405076560013040 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator; use Symfony\Component\Form\Extension\Validator\Constraints\Form; use Symfony\Component\Form\AbstractExtension; use Symfony\Component\Validator\ValidatorInterface; use Symfony\Component\Validator\Constraints\Valid; /** * Extension supporting the Symfony2 Validator component in forms. * * @author Bernhard Schussek */ class ValidatorExtension extends AbstractExtension { private $validator; public function __construct(ValidatorInterface $validator) { $this->validator = $validator; // Register the form constraints in the validator programmatically. // This functionality is required when using the Form component without // the DIC, where the XML file is loaded automatically. Thus the following // code must be kept synchronized with validation.xml /** @var \Symfony\Component\Validator\Mapping\ClassMetadata $metadata */ $metadata = $this->validator->getMetadataFactory()->getMetadataFor('Symfony\Component\Form\Form'); $metadata->addConstraint(new Form()); $metadata->addPropertyConstraint('children', new Valid()); } public function loadTypeGuesser() { return new ValidatorTypeGuesser($this->validator->getMetadataFactory()); } protected function loadTypeExtensions() { return array( new Type\FormTypeValidatorExtension($this->validator), new Type\RepeatedTypeValidatorExtension(), new Type\SubmitTypeValidatorExtension(), ); } } Validator/ViolationMapper/ViolationPath.php000064400000014441152405076560015112 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; use Symfony\Component\Form\Exception\OutOfBoundsException; use Symfony\Component\PropertyAccess\PropertyPath; use Symfony\Component\PropertyAccess\PropertyPathInterface; /** * @author Bernhard Schussek */ class ViolationPath implements \IteratorAggregate, PropertyPathInterface { /** * @var array */ private $elements = array(); /** * @var array */ private $isIndex = array(); /** * @var array */ private $mapsForm = array(); /** * @var string */ private $pathAsString = ''; /** * @var integer */ private $length = 0; /** * Creates a new violation path from a string. * * @param string $violationPath The property path of a {@link ConstraintViolation} * object. */ public function __construct($violationPath) { $path = new PropertyPath($violationPath); $elements = $path->getElements(); $data = false; for ($i = 0, $l = count($elements); $i < $l; ++$i) { if (!$data) { // The element "data" has not yet been passed if ('children' === $elements[$i] && $path->isProperty($i)) { // Skip element "children" ++$i; // Next element must exist and must be an index // Otherwise consider this the end of the path if ($i >= $l || !$path->isIndex($i)) { break; } $this->elements[] = $elements[$i]; $this->isIndex[] = true; $this->mapsForm[] = true; } elseif ('data' === $elements[$i] && $path->isProperty($i)) { // Skip element "data" ++$i; // End of path if ($i >= $l) { break; } $this->elements[] = $elements[$i]; $this->isIndex[] = $path->isIndex($i); $this->mapsForm[] = false; $data = true; } else { // Neither "children" nor "data" property found // Consider this the end of the path break; } } else { // Already after the "data" element // Pick everything as is $this->elements[] = $elements[$i]; $this->isIndex[] = $path->isIndex($i); $this->mapsForm[] = false; } } $this->length = count($this->elements); $this->buildString(); } /** * {@inheritdoc} */ public function __toString() { return $this->pathAsString; } /** * {@inheritdoc} */ public function getLength() { return $this->length; } /** * {@inheritdoc} */ public function getParent() { if ($this->length <= 1) { return null; } $parent = clone $this; --$parent->length; array_pop($parent->elements); array_pop($parent->isIndex); array_pop($parent->mapsForm); $parent->buildString(); return $parent; } /** * {@inheritdoc} */ public function getElements() { return $this->elements; } /** * {@inheritdoc} */ public function getElement($index) { if (!isset($this->elements[$index])) { throw new OutOfBoundsException(sprintf('The index %s is not within the violation path', $index)); } return $this->elements[$index]; } /** * {@inheritdoc} */ public function isProperty($index) { if (!isset($this->isIndex[$index])) { throw new OutOfBoundsException(sprintf('The index %s is not within the violation path', $index)); } return !$this->isIndex[$index]; } /** * {@inheritdoc} */ public function isIndex($index) { if (!isset($this->isIndex[$index])) { throw new OutOfBoundsException(sprintf('The index %s is not within the violation path', $index)); } return $this->isIndex[$index]; } /** * Returns whether an element maps directly to a form. * * Consider the following violation path: * * * children[address].children[office].data.street * * * In this example, "address" and "office" map to forms, while * "street does not. * * @param integer $index The element index. * * @return Boolean Whether the element maps to a form. * * @throws OutOfBoundsException If the offset is invalid. */ public function mapsForm($index) { if (!isset($this->mapsForm[$index])) { throw new OutOfBoundsException(sprintf('The index %s is not within the violation path', $index)); } return $this->mapsForm[$index]; } /** * Returns a new iterator for this path * * @return ViolationPathIterator */ public function getIterator() { return new ViolationPathIterator($this); } /** * Builds the string representation from the elements. */ private function buildString() { $this->pathAsString = ''; $data = false; foreach ($this->elements as $index => $element) { if ($this->mapsForm[$index]) { $this->pathAsString .= ".children[$element]"; } elseif (!$data) { $this->pathAsString .= '.data'.($this->isIndex[$index] ? "[$element]" : ".$element"); $data = true; } else { $this->pathAsString .= $this->isIndex[$index] ? "[$element]" : ".$element"; } } if ('' !== $this->pathAsString) { // remove leading dot $this->pathAsString = substr($this->pathAsString, 1); } } } Validator/ViolationMapper/MappingRule.php000064400000005276152405076560014562 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\Exception\ErrorMappingException; /** * @author Bernhard Schussek */ class MappingRule { /** * @var FormInterface */ private $origin; /** * @var string */ private $propertyPath; /** * @var string */ private $targetPath; public function __construct(FormInterface $origin, $propertyPath, $targetPath) { $this->origin = $origin; $this->propertyPath = $propertyPath; $this->targetPath = $targetPath; } /** * @return FormInterface */ public function getOrigin() { return $this->origin; } /** * Matches a property path against the rule path. * * If the rule matches, the form mapped by the rule is returned. * Otherwise this method returns false. * * @param string $propertyPath The property path to match against the rule. * * @return null|FormInterface The mapped form or null. */ public function match($propertyPath) { if ($propertyPath === (string) $this->propertyPath) { return $this->getTarget(); } return null; } /** * Matches a property path against a prefix of the rule path. * * @param string $propertyPath The property path to match against the rule. * * @return Boolean Whether the property path is a prefix of the rule or not. */ public function isPrefix($propertyPath) { $length = strlen($propertyPath); $prefix = substr($this->propertyPath, 0, $length); $next = isset($this->propertyPath[$length]) ? $this->propertyPath[$length] : null; return $prefix === $propertyPath && ('[' === $next || '.' === $next); } /** * @return FormInterface * * @throws ErrorMappingException */ public function getTarget() { $childNames = explode('.', $this->targetPath); $target = $this->origin; foreach ($childNames as $childName) { if (!$target->has($childName)) { throw new ErrorMappingException(sprintf('The child "%s" of "%s" mapped by the rule "%s" in "%s" does not exist.', $childName, $target->getName(), $this->targetPath, $this->origin->getName())); } $target = $target->get($childName); } return $target; } } Validator/ViolationMapper/ViolationMapperInterface.php000064400000002075152405076560017263 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; use Symfony\Component\Form\FormInterface; use Symfony\Component\Validator\ConstraintViolation; /** * @author Bernhard Schussek */ interface ViolationMapperInterface { /** * Maps a constraint violation to a form in the form tree under * the given form. * * @param ConstraintViolation $violation The violation to map. * @param FormInterface $form The root form of the tree * to map it to. * @param Boolean $allowNonSynchronized Whether to allow * mapping to non-synchronized forms. */ public function mapViolation(ConstraintViolation $violation, FormInterface $form, $allowNonSynchronized = false); } Validator/ViolationMapper/RelativePath.php000064400000001642152405076560014720 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; use Symfony\Component\Form\FormInterface; use Symfony\Component\PropertyAccess\PropertyPath; /** * @author Bernhard Schussek */ class RelativePath extends PropertyPath { /** * @var FormInterface */ private $root; /** * @param FormInterface $root * @param string $propertyPath */ public function __construct(FormInterface $root, $propertyPath) { parent::__construct($propertyPath); $this->root = $root; } /** * @return FormInterface */ public function getRoot() { return $this->root; } } Validator/ViolationMapper/ViolationPathIterator.php000064400000001300152405076560016612 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; use Symfony\Component\PropertyAccess\PropertyPathIterator; /** * @author Bernhard Schussek */ class ViolationPathIterator extends PropertyPathIterator { public function __construct(ViolationPath $violationPath) { parent::__construct($violationPath); } public function mapsForm() { return $this->path->mapsForm($this->key()); } } Validator/ViolationMapper/ViolationMapper.php000064400000025525152405076560015447 0ustar00 * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Validator\ViolationMapper; use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\Util\InheritDataAwareIterator; use Symfony\Component\PropertyAccess\PropertyPathIterator; use Symfony\Component\PropertyAccess\PropertyPathBuilder; use Symfony\Component\PropertyAccess\PropertyPathIteratorInterface; use Symfony\Component\Form\FormError; use Symfony\Component\Validator\ConstraintViolation; /** * @author Bernhard Schussek */ class ViolationMapper implements ViolationMapperInterface { /** * @var Boolean */ private $allowNonSynchronized; /** * {@inheritdoc} */ public function mapViolation(ConstraintViolation $violation, FormInterface $form, $allowNonSynchronized = false) { $this->allowNonSynchronized = $allowNonSynchronized; // The scope is the currently found most specific form that // an error should be mapped to. After setting the scope, the // mapper will try to continue to find more specific matches in // the children of scope. If it cannot, the error will be // mapped to this scope. $scope = null; $violationPath = null; $relativePath = null; $match = false; // Don't create a ViolationPath instance for empty property paths if (strlen($violation->getPropertyPath()) > 0) { $violationPath = new ViolationPath($violation->getPropertyPath()); $relativePath = $this->reconstructPath($violationPath, $form); } // This case happens if the violation path is empty and thus // the violation should be mapped to the root form if (null === $violationPath) { $scope = $form; } // In general, mapping happens from the root form to the leaf forms // First, the rules of the root form are applied to determine // the subsequent descendant. The rules of this descendant are then // applied to find the next and so on, until we have found the // most specific form that matches the violation. // If any of the forms found in this process is not synchronized, // mapping is aborted. Non-synchronized forms could not reverse // transform the value entered by the user, thus any further violations // caused by the (invalid) reverse transformed value should be // ignored. if (null !== $relativePath) { // Set the scope to the root of the relative path // This root will usually be $form. If the path contains // an unmapped form though, the last unmapped form found // will be the root of the path. $scope = $relativePath->getRoot(); $it = new PropertyPathIterator($relativePath); while ($this->acceptsErrors($scope) && null !== ($child = $this->matchChild($scope, $it))) { $scope = $child; $it->next(); $match = true; } } // This case happens if an error happened in the data under a // form inheriting its parent data that does not match any of the // children of that form. if (null !== $violationPath && !$match) { // If we could not map the error to anything more specific // than the root element, map it to the innermost directly // mapped form of the violation path // e.g. "children[foo].children[bar].data.baz" // Here the innermost directly mapped child is "bar" $scope = $form; $it = new ViolationPathIterator($violationPath); // Note: acceptsErrors() will always return true for forms inheriting // their parent data, because these forms can never be non-synchronized // (they don't do any data transformation on their own) while ($this->acceptsErrors($scope) && $it->valid() && $it->mapsForm()) { if (!$scope->has($it->current())) { // Break if we find a reference to a non-existing child break; } $scope = $scope->get($it->current()); $it->next(); } } // Follow dot rules until we have the final target $mapping = $scope->getConfig()->getOption('error_mapping'); while ($this->acceptsErrors($scope) && isset($mapping['.'])) { $dotRule = new MappingRule($scope, '.', $mapping['.']); $scope = $dotRule->getTarget(); $mapping = $scope->getConfig()->getOption('error_mapping'); } // Only add the error if the form is synchronized if ($this->acceptsErrors($scope)) { $scope->addError(new FormError( $violation->getMessage(), $violation->getMessageTemplate(), $violation->getMessageParameters(), $violation->getMessagePluralization() )); } } /** * Tries to match the beginning of the property path at the * current position against the children of the scope. * * If a matching child is found, it is returned. Otherwise * null is returned. * * @param FormInterface $form The form to search. * @param PropertyPathIteratorInterface $it The iterator at its current position. * * @return null|FormInterface The found match or null. */ private function matchChild(FormInterface $form, PropertyPathIteratorInterface $it) { // Remember at what property path underneath "data" // we are looking. Check if there is a child with that // path, otherwise increase path by one more piece $chunk = ''; $foundChild = null; $foundAtIndex = 0; // Construct mapping rules for the given form $rules = array(); foreach ($form->getConfig()->getOption('error_mapping') as $propertyPath => $targetPath) { // Dot rules are considered at the very end if ('.' !== $propertyPath) { $rules[] = new MappingRule($form, $propertyPath, $targetPath); } } // Skip forms inheriting their parent data when iterating the children $childIterator = new \RecursiveIteratorIterator( new InheritDataAwareIterator($form) ); // Make the path longer until we find a matching child while (true) { if (!$it->valid()) { return null; } if ($it->isIndex()) { $chunk .= '['.$it->current().']'; } else { $chunk .= ('' === $chunk ? '' : '.').$it->current(); } // Test mapping rules as long as we have any foreach ($rules as $key => $rule) { /* @var MappingRule $rule */ // Mapping rule matches completely, terminate. if (null !== ($form = $rule->match($chunk))) { return $form; } // Keep only rules that have $chunk as prefix if (!$rule->isPrefix($chunk)) { unset($rules[$key]); } } // Test children unless we already found one if (null === $foundChild) { foreach ($childIterator as $child) { /* @var FormInterface $child */ $childPath = (string) $child->getPropertyPath(); // Child found, mark as return value if ($chunk === $childPath) { $foundChild = $child; $foundAtIndex = $it->key(); } } } // Add element to the chunk $it->next(); // If we reached the end of the path or if there are no // more matching mapping rules, return the found child if (null !== $foundChild && (!$it->valid() || count($rules) === 0)) { // Reset index in case we tried to find mapping // rules further down the path $it->seek($foundAtIndex); return $foundChild; } } return null; } /** * Reconstructs a property path from a violation path and a form tree. * * @param ViolationPath $violationPath The violation path. * @param FormInterface $origin The root form of the tree. * * @return RelativePath The reconstructed path. */ private function reconstructPath(ViolationPath $violationPath, FormInterface $origin) { $propertyPathBuilder = new PropertyPathBuilder($violationPath); $it = $violationPath->getIterator(); $scope = $origin; // Remember the current index in the builder $i = 0; // Expand elements that map to a form (like "children[address]") for ($it->rewind(); $it->valid() && $it->mapsForm(); $it->next()) { if (!$scope->has($it->current())) { // Scope relates to a form that does not exist // Bail out break; } // Process child form $scope = $scope->get($it->current()); if ($scope->getConfig()->getInheritData()) { // Form inherits its parent data // Cut the piece out of the property path and proceed $propertyPathBuilder->remove($i); } elseif (!$scope->getConfig()->getMapped()) { // Form is not mapped // Set the form as new origin and strip everything // we have so far in the path $origin = $scope; $propertyPathBuilder->remove(0, $i + 1); $i = 0; } else { /* @var \Symfony\Component\PropertyAccess\PropertyPathInterface $propertyPath */ $propertyPath = $scope->getPropertyPath(); if (null === $propertyPath) { // Property path of a mapped form is null // Should not happen, bail out break; } $propertyPathBuilder->replace($i, 1, $propertyPath); $i += $propertyPath->getLength(); } } $finalPath = $propertyPathBuilder->getPropertyPath(); return null !== $finalPath ? new RelativePath($origin, $finalPath) : null; } /** * @param FormInterface $form * * @return Boolean */ private function acceptsErrors(FormInterface $form) { return $this->allowNonSynchronized || $form->isSynchronized(); } }