From 4f5b44bd3bd490309eb2ba7b44df4769816ba729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20L=C5=93uillet?= Date: Sat, 3 Aug 2013 19:26:54 +0200 Subject: twig implementation --- .../Form/Extension/Core/ChoiceList/ChoiceList.php | 510 +++++++++++++++++++++ .../Core/ChoiceList/ChoiceListInterface.php | 149 ++++++ .../Extension/Core/ChoiceList/LazyChoiceList.php | 149 ++++++ .../Extension/Core/ChoiceList/ObjectChoiceList.php | 184 ++++++++ .../Extension/Core/ChoiceList/SimpleChoiceList.php | 164 +++++++ .../Form/Extension/Core/CoreExtension.php | 59 +++ .../Core/DataMapper/PropertyPathMapper.php | 92 ++++ .../DataTransformer/ArrayToPartsTransformer.php | 86 ++++ .../DataTransformer/BaseDateTimeTransformer.php | 52 +++ .../DataTransformer/BooleanToStringTransformer.php | 85 ++++ .../ChoiceToBooleanArrayTransformer.php | 118 +++++ .../DataTransformer/ChoiceToValueTransformer.php | 62 +++ .../ChoicesToBooleanArrayTransformer.php | 117 +++++ .../DataTransformer/ChoicesToValuesTransformer.php | 83 ++++ .../Core/DataTransformer/DataTransformerChain.php | 86 ++++ .../DataTransformer/DateTimeToArrayTransformer.php | 184 ++++++++ .../DateTimeToLocalizedStringTransformer.php | 169 +++++++ .../DateTimeToRfc3339Transformer.php | 82 ++++ .../DateTimeToStringTransformer.php | 231 ++++++++++ .../DateTimeToTimestampTransformer.php | 89 ++++ .../IntegerToLocalizedStringTransformer.php | 53 +++ .../MoneyToLocalizedStringTransformer.php | 90 ++++ .../NumberToLocalizedStringTransformer.php | 184 ++++++++ .../PercentToLocalizedStringTransformer.php | 149 ++++++ .../ValueToDuplicatesTransformer.php | 91 ++++ .../EventListener/FixCheckboxInputListener.php | 62 +++ .../Core/EventListener/FixRadioInputListener.php | 66 +++ .../Core/EventListener/FixUrlProtocolListener.php | 56 +++ .../Core/EventListener/MergeCollectionListener.php | 137 ++++++ .../Core/EventListener/ResizeFormListener.php | 173 +++++++ .../Extension/Core/EventListener/TrimListener.php | 55 +++ .../Form/Extension/Core/Type/BaseType.php | 121 +++++ .../Form/Extension/Core/Type/BirthdayType.php | 44 ++ .../Form/Extension/Core/Type/ButtonType.php | 38 ++ .../Form/Extension/Core/Type/CheckboxType.php | 67 +++ .../Form/Extension/Core/Type/ChoiceType.php | 274 +++++++++++ .../Form/Extension/Core/Type/CollectionType.php | 103 +++++ .../Form/Extension/Core/Type/CountryType.php | 45 ++ .../Form/Extension/Core/Type/CurrencyType.php | 45 ++ .../Form/Extension/Core/Type/DateTimeType.php | 281 ++++++++++++ .../Form/Extension/Core/Type/DateType.php | 309 +++++++++++++ .../Form/Extension/Core/Type/EmailType.php | 33 ++ .../Form/Extension/Core/Type/FileType.php | 61 +++ .../Form/Extension/Core/Type/FormType.php | 214 +++++++++ .../Form/Extension/Core/Type/HiddenType.php | 40 ++ .../Form/Extension/Core/Type/IntegerType.php | 68 +++ .../Form/Extension/Core/Type/LanguageType.php | 45 ++ .../Form/Extension/Core/Type/LocaleType.php | 46 ++ .../Form/Extension/Core/Type/MoneyType.php | 111 +++++ .../Form/Extension/Core/Type/NumberType.php | 66 +++ .../Form/Extension/Core/Type/PasswordType.php | 57 +++ .../Form/Extension/Core/Type/PercentType.php | 55 +++ .../Form/Extension/Core/Type/RadioType.php | 33 ++ .../Form/Extension/Core/Type/RepeatedType.php | 67 +++ .../Form/Extension/Core/Type/ResetType.php | 39 ++ .../Form/Extension/Core/Type/SearchType.php | 33 ++ .../Form/Extension/Core/Type/SubmitType.php | 46 ++ .../Form/Extension/Core/Type/TextType.php | 36 ++ .../Form/Extension/Core/Type/TextareaType.php | 43 ++ .../Form/Extension/Core/Type/TimeType.php | 225 +++++++++ .../Form/Extension/Core/Type/TimezoneType.php | 86 ++++ .../Component/Form/Extension/Core/Type/UrlType.php | 54 +++ .../Form/Extension/Core/View/ChoiceView.php | 55 +++ .../Form/Extension/Csrf/CsrfExtension.php | 64 +++ .../Csrf/CsrfProvider/CsrfProviderInterface.php | 49 ++ .../Csrf/CsrfProvider/DefaultCsrfProvider.php | 78 ++++ .../Csrf/CsrfProvider/SessionCsrfProvider.php | 57 +++ .../Csrf/EventListener/CsrfValidationListener.php | 115 +++++ .../Extension/Csrf/Type/FormTypeCsrfExtension.php | 129 ++++++ .../DependencyInjectionExtension.php | 101 ++++ .../EventListener/BindRequestListener.php | 91 ++++ .../HttpFoundation/HttpFoundationExtension.php | 29 ++ .../HttpFoundationRequestHandler.php | 79 ++++ .../Type/FormTypeHttpFoundationExtension.php | 56 +++ .../Extension/Templating/TemplatingExtension.php | 33 ++ .../Templating/TemplatingRendererEngine.php | 125 +++++ .../Form/Extension/Validator/Constraints/Form.php | 33 ++ .../Validator/Constraints/FormValidator.php | 236 ++++++++++ .../Validator/EventListener/ValidationListener.php | 68 +++ .../Validator/Type/BaseValidatorExtension.php | 56 +++ .../Validator/Type/FormTypeValidatorExtension.php | 84 ++++ .../Type/RepeatedTypeValidatorExtension.php | 45 ++ .../Type/SubmitTypeValidatorExtension.php | 28 ++ .../Form/Extension/Validator/Util/ServerParams.php | 64 +++ .../Extension/Validator/ValidatorExtension.php | 57 +++ .../Extension/Validator/ValidatorTypeGuesser.php | 286 ++++++++++++ .../Validator/ViolationMapper/MappingRule.php | 106 +++++ .../Validator/ViolationMapper/RelativePath.php | 45 ++ .../Validator/ViolationMapper/ViolationMapper.php | 299 ++++++++++++ .../ViolationMapper/ViolationMapperInterface.php | 33 ++ .../Validator/ViolationMapper/ViolationPath.php | 250 ++++++++++ .../ViolationMapper/ViolationPathIterator.php | 30 ++ 92 files changed, 9433 insertions(+) create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/LazyChoiceList.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/CoreExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToPartsTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/BaseDateTimeTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/BooleanToStringTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToBooleanArrayTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToBooleanArrayTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DataTransformerChain.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToTimestampTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/FixCheckboxInputListener.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/FixRadioInputListener.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/TrimListener.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/BaseType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ButtonType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CollectionType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CountryType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/DateType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/EmailType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/FileType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/FormType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/HiddenType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/IntegerType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/LanguageType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/LocaleType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/MoneyType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/NumberType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/PasswordType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/PercentType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/RadioType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ResetType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/SearchType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/SubmitType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TextType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TextareaType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TimeType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/UrlType.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Core/View/ChoiceView.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/EventListener/BindRequestListener.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Templating/TemplatingRendererEngine.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/Form.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Util/ServerParams.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/RelativePath.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php create mode 100644 vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php (limited to 'vendor/symfony/form/Symfony/Component/Form/Extension') diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php new file mode 100644 index 00000000..f9d381cd --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceList.php @@ -0,0 +1,510 @@ + + * + * 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 $j => $givenValue) { + foreach ($this->values as $i => $value) { + if ($value === $givenValue) { + $choices[] = $this->choices[$i]; + unset($values[$j]); + + if (0 === count($values)) { + break 2; + } + } + } + } + + return $choices; + } + + /** + * {@inheritdoc} + */ + public function getValuesForChoices(array $choices) + { + $choices = $this->fixChoices($choices); + $values = array(); + + foreach ($this->choices as $i => $choice) { + foreach ($choices as $j => $givenChoice) { + if ($choice === $givenChoice) { + $values[] = $this->values[$i]; + unset($choices[$j]); + + if (0 === count($choices)) { + break 2; + } + } + } + } + + return $values; + } + + /** + * {@inheritdoc} + */ + public function getIndicesForChoices(array $choices) + { + $choices = $this->fixChoices($choices); + $indices = array(); + + foreach ($this->choices as $i => $choice) { + foreach ($choices as $j => $givenChoice) { + if ($choice === $givenChoice) { + $indices[] = $i; + unset($choices[$j]); + + if (0 === count($choices)) { + break 2; + } + } + } + } + + return $indices; + } + + /** + * {@inheritdoc} + */ + public function getIndicesForValues(array $values) + { + $values = $this->fixValues($values); + $indices = array(); + + foreach ($this->values as $i => $value) { + foreach ($values as $j => $givenValue) { + if ($value === $givenValue) { + $indices[] = $i; + unset($values[$j]); + + 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php new file mode 100644 index 00000000..099ace82 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/ChoiceListInterface.php @@ -0,0 +1,149 @@ + + * + * 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. + * + * @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. + * + * @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. + * + * @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 + */ + 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. + * + * @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 + */ + public function getIndicesForValues(array $values); +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/LazyChoiceList.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/LazyChoiceList.php new file mode 100644 index 00000000..996f900c --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/LazyChoiceList.php @@ -0,0 +1,149 @@ + + * + * 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} + */ + public function getIndicesForChoices(array $choices) + { + if (!$this->choiceList) { + $this->load(); + } + + return $this->choiceList->getIndicesForChoices($choices); + } + + /** + * {@inheritdoc} + */ + 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php new file mode 100644 index 00000000..0a153883 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/ObjectChoiceList.php @@ -0,0 +1,184 @@ + +* +* 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::getPropertyAccessor(); + $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 { + if (!isset($groupedChoices[$group])) { + $groupedChoices[$group] = array(); + } + + $groupedChoices[$group][$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))); + } + } + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php new file mode 100644 index 00000000..914dbe5f --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/ChoiceList/SimpleChoiceList.php @@ -0,0 +1,164 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/CoreExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/CoreExtension.php new file mode 100644 index 00000000..bbcac4ba --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/CoreExtension.php @@ -0,0 +1,59 @@ + + * + * 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::getPropertyAccessor()), + 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(), + ); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php new file mode 100644 index 00000000..d8bd9c71 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php @@ -0,0 +1,92 @@ + + * + * 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::getPropertyAccessor(); + } + + /** + * {@inheritdoc} + */ + public function mapDataToForms($data, $forms) + { + if (null === $data || array() === $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(); + + if (null !== $propertyPath && $config->getMapped()) { + $form->setData($this->propertyAccessor->getValue($data, $propertyPath)); + } + } + } + + /** + * {@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) + // and if the form is disabled (modification not allowed) + if (null !== $propertyPath && $config->getMapped() && $form->isSynchronized() && !$form->isDisabled()) { + // 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()); + } + } + } + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToPartsTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToPartsTransformer.php new file mode 100644 index 00000000..fc080f25 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ArrayToPartsTransformer.php @@ -0,0 +1,86 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/BaseDateTimeTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/BaseDateTimeTransformer.php new file mode 100644 index 00000000..e4e8932e --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/BaseDateTimeTransformer.php @@ -0,0 +1,52 @@ + + * + * 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\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 + */ + 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(); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/BooleanToStringTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/BooleanToStringTransformer.php new file mode 100644 index 00000000..95e7332d --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/BooleanToStringTransformer.php @@ -0,0 +1,85 @@ + + * + * 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 true === $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; + } + +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToBooleanArrayTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToBooleanArrayTransformer.php new file mode 100644 index 00000000..79b3f7ac --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToBooleanArrayTransformer.php @@ -0,0 +1,118 @@ + + * + * 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); + } + + $index = current($this->choiceList->getIndicesForChoices(array($choice))); + + foreach ($values as $i => $value) { + $values[$i] = $i === $index; + } + + if ($this->placeholderPresent) { + $values['placeholder'] = false === $index; + } + + 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php new file mode 100644 index 00000000..5a818558 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php @@ -0,0 +1,62 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToBooleanArrayTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToBooleanArrayTransformer.php new file mode 100644 index 00000000..a13c0d4d --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToBooleanArrayTransformer.php @@ -0,0 +1,117 @@ + + * + * 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); + } + + $indexMap = array_flip($this->choiceList->getIndicesForChoices($array)); + + foreach ($values as $i => $value) { + $values[$i] = isset($indexMap[$i]); + } + + 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php new file mode 100644 index 00000000..4492865e --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ChoicesToValuesTransformer.php @@ -0,0 +1,83 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DataTransformerChain.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DataTransformerChain.php new file mode 100644 index 00000000..9cc185e1 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DataTransformerChain.php @@ -0,0 +1,86 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php new file mode 100644 index 00000000..34af2820 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToArrayTransformer.php @@ -0,0 +1,184 @@ + + * + * 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($value['month']) && !is_int($value['month'])) { + throw new TransformationFailedException('This month is invalid'); + } + + if (isset($value['day']) && !ctype_digit($value['day']) && !is_int($value['day'])) { + throw new TransformationFailedException('This day is invalid'); + } + + if (isset($value['year']) && !ctype_digit($value['year']) && !is_int($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'); + } + + 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php new file mode 100644 index 00000000..d755e485 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php @@ -0,0 +1,169 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php new file mode 100644 index 00000000..0eb07422 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToRfc3339Transformer.php @@ -0,0 +1,82 @@ + + * + * 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 !== $this->inputTimezone) { + 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php new file mode 100644 index 00000000..131f45cb --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToStringTransformer.php @@ -0,0 +1,231 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToTimestampTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToTimestampTransformer.php new file mode 100644 index 00000000..d2ca6604 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToTimestampTransformer.php @@ -0,0 +1,89 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php new file mode 100644 index 00000000..6bb48a9a --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php @@ -0,0 +1,53 @@ + + * + * 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 an integer and a localized number with grouping + * (each thousand) and comma separators. + * + * @author Bernhard Schussek + */ +class IntegerToLocalizedStringTransformer extends NumberToLocalizedStringTransformer +{ + /** + * {@inheritDoc} + */ + 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 integer'); + } + + $formatter = $this->getNumberFormatter(); + $value = $formatter->parse( + $value, + PHP_INT_SIZE == 8 ? $formatter::TYPE_INT64 : $formatter::TYPE_INT32 + ); + + if (intl_is_failure($formatter->getErrorCode())) { + throw new TransformationFailedException($formatter->getErrorMessage()); + } + + return $value; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php new file mode 100644 index 00000000..5b8e9d96 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php @@ -0,0 +1,90 @@ + + * + * 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 = null, $grouping = null, $roundingMode = null, $divisor = null) + { + 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; + } + +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php new file mode 100644 index 00000000..b0c59b3e --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php @@ -0,0 +1,184 @@ + + * + * 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 +{ + const ROUND_FLOOR = \NumberFormatter::ROUND_FLOOR; + const ROUND_DOWN = \NumberFormatter::ROUND_DOWN; + const ROUND_HALFDOWN = \NumberFormatter::ROUND_HALFDOWN; + const ROUND_HALFEVEN = \NumberFormatter::ROUND_HALFEVEN; + const ROUND_HALFUP = \NumberFormatter::ROUND_HALFUP; + const ROUND_UP = \NumberFormatter::ROUND_UP; + const ROUND_CEILING = \NumberFormatter::ROUND_CEILING; + + protected $precision; + + protected $grouping; + + protected $roundingMode; + + public function __construct($precision = null, $grouping = null, $roundingMode = null) + { + if (null === $grouping) { + $grouping = false; + } + + if (null === $roundingMode) { + $roundingMode = self::ROUND_HALFUP; + } + + $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) + ); + } + } + + return $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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php new file mode 100644 index 00000000..e099d436 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/PercentToLocalizedStringTransformer.php @@ -0,0 +1,149 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php new file mode 100644 index 00000000..c34a0139 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/DataTransformer/ValueToDuplicatesTransformer.php @@ -0,0 +1,91 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/FixCheckboxInputListener.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/FixCheckboxInputListener.php new file mode 100644 index 00000000..1f62e060 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/FixCheckboxInputListener.php @@ -0,0 +1,62 @@ + + * + * 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 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) + { + $values = (array) $event->getData(); + $indices = $this->choiceList->getIndicesForValues($values); + + $event->setData(count($indices) > 0 ? array_combine($indices, $values) : array()); + } + + /** + * 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'); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/FixRadioInputListener.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/FixRadioInputListener.php new file mode 100644 index 00000000..bf03792f --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/FixRadioInputListener.php @@ -0,0 +1,66 @@ + + * + * 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) + { + $value = $event->getData(); + $index = current($this->choiceList->getIndicesForValues(array($value))); + + $event->setData(false !== $index ? array($index => $value) : ($this->placeholderPresent ? array('placeholder' => '') : array())) ; + } + + /** + * 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'); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php new file mode 100644 index 00000000..e25dacf2 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/FixUrlProtocolListener.php @@ -0,0 +1,56 @@ + + * + * 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'); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php new file mode 100644 index 00000000..4d0bdfaa --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/MergeCollectionListener.php @@ -0,0 +1,137 @@ + + * + * 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); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php new file mode 100644 index 00000000..f1c39db2 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php @@ -0,0 +1,173 @@ + + * + * 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) { + foreach ($data as $name => $child) { + if (!$form->has($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); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/TrimListener.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/TrimListener.php new file mode 100644 index 00000000..cbe6e0ab --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/EventListener/TrimListener.php @@ -0,0 +1,55 @@ + + * + * 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'); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/BaseType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/BaseType.php new file mode 100644 index 00000000..79333a67 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/BaseType.php @@ -0,0 +1,121 @@ + + * + * 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']); + } + + /** + * {@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, + )); + + $resolver->setAllowedTypes(array( + 'attr' => 'array', + )); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php new file mode 100644 index 00000000..5314c140 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/BirthdayType.php @@ -0,0 +1,44 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ButtonType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ButtonType.php new file mode 100644 index 00000000..3569963b --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ButtonType.php @@ -0,0 +1,38 @@ + + * + * 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; + +/** + * A form button. + * + * @author Bernhard Schussek + */ +class ButtonType extends BaseType implements ButtonTypeInterface +{ + /** + * {@inheritdoc} + */ + public function getParent() + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'button'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php new file mode 100644 index 00000000..214e581a --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CheckboxType.php @@ -0,0 +1,67 @@ + + * + * 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) + { + $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, $clientData) { + return $clientData; + }; + + $resolver->setDefaults(array( + 'value' => '1', + 'empty_data' => $emptyData, + 'compound' => false, + )); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'checkbox'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php new file mode 100644 index 00000000..9a3fdef1 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -0,0 +1,274 @@ + + * + * 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']->getIndicesForValues(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 + // Only add the empty value option if this is not the case + if (null !== $options['empty_value'] && 0 === count($options['choice_list']->getIndicesForValues(array('')))) { + $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 = md5(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); + } + } + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CollectionType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CollectionType.php new file mode 100644 index 00000000..0cb3af1b --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CollectionType.php @@ -0,0 +1,103 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CountryType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CountryType.php new file mode 100644 index 00000000..3482ba66 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CountryType.php @@ -0,0 +1,45 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php new file mode 100644 index 00000000..3a925e3a --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php @@ -0,0 +1,45 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php new file mode 100644 index 00000000..a612b6fc --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/DateTimeType.php @@ -0,0 +1,281 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/DateType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/DateType.php new file mode 100644 index 00000000..93d3502e --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/DateType.php @@ -0,0 +1,309 @@ + + * + * 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 (version_compare(\PHP_VERSION, '5.5.0-dev', '>=')) { + $formatter->setTimeZone(\DateTimeZone::UTC); + } else { + $formatter->setTimeZoneId(\DateTimeZone::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 (version_compare(\PHP_VERSION, '5.5.0-dev', '>=')) { + $formatter->setTimeZone($timezone); + } else { + $formatter->setTimeZoneId($timezone); + } + + return $timestamps; + } + + private function listYears(array $years) + { + $result = array(); + + foreach ($years as $year) { + $result[$year] = gmmktime(0, 0, 0, 6, 15, $year); + } + + 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/EmailType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/EmailType.php new file mode 100644 index 00000000..26652ef6 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/EmailType.php @@ -0,0 +1,33 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/FileType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/FileType.php new file mode 100644 index 00000000..2c09da6f --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -0,0 +1,61 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/FormType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/FormType.php new file mode 100644 index 00000000..0c39d3eb --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -0,0 +1,214 @@ + + * + * 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::getPropertyAccessor(); + } + + /** + * {@inheritdoc} + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + parent::buildForm($builder, $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(isset($options['data']) ? $options['data'] : null) + ->setDataLocked(isset($options['data'])) + ->setDataMapper($options['compound'] ? new PropertyPathMapper($this->propertyAccessor) : null) + ->setMethod($options['method']) + ->setAction($options['action']) + ->setAutoInitialize($options['auto_initialize']) + ; + + 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(), + )); + } + + /** + * {@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' => '', + 'auto_initialize' => true, + )); + + $resolver->setAllowedTypes(array( + 'label_attr' => 'array', + )); + } + + /** + * {@inheritdoc} + */ + public function getParent() + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'form'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/HiddenType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/HiddenType.php new file mode 100644 index 00000000..bd4fa898 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/HiddenType.php @@ -0,0 +1,40 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/IntegerType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/IntegerType.php new file mode 100644 index 00000000..b224cac5 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/IntegerType.php @@ -0,0 +1,68 @@ + + * + * 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' => \NumberFormatter::ROUND_DOWN, + 'compound' => false, + )); + + $resolver->setAllowedValues(array( + 'rounding_mode' => array( + \NumberFormatter::ROUND_FLOOR, + \NumberFormatter::ROUND_DOWN, + \NumberFormatter::ROUND_HALFDOWN, + \NumberFormatter::ROUND_HALFEVEN, + \NumberFormatter::ROUND_HALFUP, + \NumberFormatter::ROUND_UP, + \NumberFormatter::ROUND_CEILING, + ), + )); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'integer'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/LanguageType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/LanguageType.php new file mode 100644 index 00000000..37b2bf33 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/LanguageType.php @@ -0,0 +1,45 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/LocaleType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/LocaleType.php new file mode 100644 index 00000000..c68c561a --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/LocaleType.php @@ -0,0 +1,46 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/MoneyType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/MoneyType.php new file mode 100644 index 00000000..9e36f9ce --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/MoneyType.php @@ -0,0 +1,111 @@ + + * + * 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]; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/NumberType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/NumberType.php new file mode 100644 index 00000000..beb3c89a --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/NumberType.php @@ -0,0 +1,66 @@ + + * + * 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' => \NumberFormatter::ROUND_HALFUP, + 'compound' => false, + )); + + $resolver->setAllowedValues(array( + 'rounding_mode' => array( + \NumberFormatter::ROUND_FLOOR, + \NumberFormatter::ROUND_DOWN, + \NumberFormatter::ROUND_HALFDOWN, + \NumberFormatter::ROUND_HALFEVEN, + \NumberFormatter::ROUND_HALFUP, + \NumberFormatter::ROUND_UP, + \NumberFormatter::ROUND_CEILING, + ), + )); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'number'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/PasswordType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/PasswordType.php new file mode 100644 index 00000000..5a5b1635 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/PasswordType.php @@ -0,0 +1,57 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/PercentType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/PercentType.php new file mode 100644 index 00000000..b1df9436 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/PercentType.php @@ -0,0 +1,55 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/RadioType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/RadioType.php new file mode 100644 index 00000000..dfa7c7d5 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/RadioType.php @@ -0,0 +1,33 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php new file mode 100644 index 00000000..9a3cd146 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/RepeatedType.php @@ -0,0 +1,67 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ResetType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ResetType.php new file mode 100644 index 00000000..cf55f7c5 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/ResetType.php @@ -0,0 +1,39 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/SearchType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/SearchType.php new file mode 100644 index 00000000..bf82972d --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/SearchType.php @@ -0,0 +1,33 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/SubmitType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/SubmitType.php new file mode 100644 index 00000000..6d160b96 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/SubmitType.php @@ -0,0 +1,46 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TextType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TextType.php new file mode 100644 index 00000000..11503261 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TextType.php @@ -0,0 +1,36 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TextareaType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TextareaType.php new file mode 100644 index 00000000..0e749b15 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TextareaType.php @@ -0,0 +1,43 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TimeType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TimeType.php new file mode 100644 index 00000000..d7a2a9ef --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TimeType.php @@ -0,0 +1,225 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php new file mode 100644 index 00000000..cd4a2ad3 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/TimezoneType.php @@ -0,0 +1,86 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/UrlType.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/UrlType.php new file mode 100644 index 00000000..27749b1a --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/Type/UrlType.php @@ -0,0 +1,54 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Core/View/ChoiceView.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/View/ChoiceView.php new file mode 100644 index 00000000..97cdd214 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Core/View/ChoiceView.php @@ -0,0 +1,55 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php new file mode 100644 index 00000000..f9d9e40a --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php @@ -0,0 +1,64 @@ + + * + * 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\Extension\Csrf\Type; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +use Symfony\Component\Form\AbstractExtension; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * This extension protects forms by using a CSRF token. + * + * @author Bernhard Schussek + */ +class CsrfExtension extends AbstractExtension +{ + /** + * @var CsrfProviderInterface + */ + private $csrfProvider; + + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * @var null|string + */ + private $translationDomain; + + /** + * Constructor. + * + * @param CsrfProviderInterface $csrfProvider The CSRF provider + * @param TranslatorInterface $translator The translator for translating error messages. + * @param null|string $translationDomain The translation domain for translating. + */ + public function __construct(CsrfProviderInterface $csrfProvider, TranslatorInterface $translator = null, $translationDomain = null) + { + $this->csrfProvider = $csrfProvider; + $this->translator = $translator; + $this->translationDomain = $translationDomain; + } + + /** + * {@inheritDoc} + */ + protected function loadTypeExtensions() + { + return array( + new Type\FormTypeCsrfExtension($this->csrfProvider, true, '_token', $this->translator, $this->translationDomain), + ); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.php new file mode 100644 index 00000000..7143b130 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.php @@ -0,0 +1,49 @@ + + * + * 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 + */ +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. + */ + 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); +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php new file mode 100644 index 00000000..5354886c --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php @@ -0,0 +1,78 @@ + + * + * 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 + */ +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(); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php new file mode 100644 index 00000000..ea1fa585 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php @@ -0,0 +1,57 @@ + + * + * 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 + */ +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(); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php new file mode 100644 index 00000000..547e9d75 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php @@ -0,0 +1,115 @@ + + * + * 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\FormEvents; +use Symfony\Component\Form\FormError; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * @author Bernhard Schussek + */ +class CsrfValidationListener implements EventSubscriberInterface +{ + /** + * The name of the CSRF field + * @var string + */ + private $fieldName; + + /** + * The provider for generating and validating CSRF tokens + * @var CsrfProviderInterface + */ + private $csrfProvider; + + /** + * A text mentioning the intention of the CSRF token + * + * Validation of the token will only succeed if it was generated in the + * same session and with the same intention. + * + * @var string + */ + private $intention; + + /** + * 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, CsrfProviderInterface $csrfProvider, $intention, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null) + { + $this->fieldName = $fieldName; + $this->csrfProvider = $csrfProvider; + $this->intention = $intention; + $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->csrfProvider->isCsrfTokenValid($this->intention, $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); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php new file mode 100644 index 00000000..336cf047 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -0,0 +1,129 @@ + + * + * 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\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +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\OptionsResolverInterface; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * @author Bernhard Schussek + */ +class FormTypeCsrfExtension extends AbstractTypeExtension +{ + /** + * @var CsrfProviderInterface + */ + private $defaultCsrfProvider; + + /** + * @var Boolean + */ + private $defaultEnabled; + + /** + * @var string + */ + private $defaultFieldName; + + /** + * @var TranslatorInterface + */ + private $translator; + + /** + * @var null|string + */ + private $translationDomain; + + public function __construct(CsrfProviderInterface $defaultCsrfProvider, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null) + { + $this->defaultCsrfProvider = $defaultCsrfProvider; + $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 + ->setAttribute('csrf_factory', $builder->getFormFactory()) + ->addEventSubscriber(new CsrfValidationListener( + $options['csrf_field_name'], + $options['csrf_provider'], + $options['intention'], + $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()->getAttribute('csrf_factory'); + $data = $options['csrf_provider']->generateCsrfToken($options['intention']); + + $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) + { + $resolver->setDefaults(array( + 'csrf_protection' => $this->defaultEnabled, + 'csrf_field_name' => $this->defaultFieldName, + 'csrf_provider' => $this->defaultCsrfProvider, + 'csrf_message' => 'The CSRF token is invalid. Please try to resubmit the form.', + 'intention' => 'unknown', + )); + } + + /** + * {@inheritDoc} + */ + public function getExtendedType() + { + return 'form'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php new file mode 100644 index 00000000..6637ac8c --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php @@ -0,0 +1,101 @@ + + * + * 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 $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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/EventListener/BindRequestListener.php b/vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/EventListener/BindRequestListener.php new file mode 100644 index 00000000..6205b98d --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/EventListener/BindRequestListener.php @@ -0,0 +1,91 @@ + + * + * 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::process()} 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); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationExtension.php new file mode 100644 index 00000000..08bd89c9 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationExtension.php @@ -0,0 +1,29 @@ + + * + * 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(), + ); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php b/vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php new file mode 100644 index 00000000..cc485156 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php @@ -0,0 +1,79 @@ + + * + * 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(); + } else { + $default = $form->getConfig()->getCompound() ? array() : null; + $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; + } + } + + // 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); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php new file mode 100644 index 00000000..9b09b05c --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/HttpFoundation/Type/FormTypeHttpFoundationExtension.php @@ -0,0 +1,56 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php new file mode 100644 index 00000000..573cb518 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php @@ -0,0 +1,33 @@ + + * + * 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\FormRenderer; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +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, CsrfProviderInterface $csrfProvider = null, array $defaultThemes = array()) + { + $engine->addHelpers(array( + new FormHelper(new FormRenderer(new TemplatingRendererEngine($engine, $defaultThemes), $csrfProvider)) + )); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Templating/TemplatingRendererEngine.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Templating/TemplatingRendererEngine.php new file mode 100644 index 00000000..c1dda60b --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Templating/TemplatingRendererEngine.php @@ -0,0 +1,125 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/Form.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/Form.php new file mode 100644 index 00000000..87e33297 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/Form.php @@ -0,0 +1,33 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php new file mode 100644 index 00000000..bad5a007 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Constraints/FormValidator.php @@ -0,0 +1,236 @@ + + * + * 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\ClickableInterface; +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) + { + $button = self::findClickedButton($form->getRoot()); + + if (null !== $button) { + $groups = $button->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); + } + + /** + * Extracts a clicked button from a form tree, if one exists. + * + * @param FormInterface $form The root form. + * + * @return ClickableInterface|null The clicked button or null. + */ + private static function findClickedButton(FormInterface $form) + { + if ($form instanceof ClickableInterface && $form->isClicked()) { + return $form; + } + + foreach ($form as $child) { + if (null !== ($button = self::findClickedButton($child))) { + return $button; + } + } + + return null; + } + + /** + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php new file mode 100644 index 00000000..14147531 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/EventListener/ValidationListener.php @@ -0,0 +1,68 @@ + + * + * 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); + + if (count($violations) > 0) { + 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); + } + } + } + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php new file mode 100644 index 00000000..7c5e6784 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/BaseValidatorExtension.php @@ -0,0 +1,56 @@ + + * + * 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, + )); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php new file mode 100644 index 00000000..344bddad --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/FormTypeValidatorExtension.php @@ -0,0 +1,84 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php new file mode 100644 index 00000000..858ff0fa --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/RepeatedTypeValidatorExtension.php @@ -0,0 +1,45 @@ + + * + * 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'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php new file mode 100644 index 00000000..5aad67fb --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Type/SubmitTypeValidatorExtension.php @@ -0,0 +1,28 @@ + + * + * 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; + +/** + * @author Bernhard Schussek + */ +class SubmitTypeValidatorExtension extends AbstractTypeExtension +{ + /** + * {@inheritdoc} + */ + public function getExtendedType() + { + return 'submit'; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Util/ServerParams.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Util/ServerParams.php new file mode 100644 index 00000000..fab6ac2a --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/Util/ServerParams.php @@ -0,0 +1,64 @@ + + * + * 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 = $this->getNormalizedIniPostMaxSize(); + + if ('' === $iniMax) { + return null; + } + + if (preg_match('#^\+?(0X?)?(.*?)([KMG]?)$#', $iniMax, $match)) { + $shifts = array('' => 0, 'K' => 10, 'M' => 20, 'G' => 30); + $bases = array('' => 10, '0' => 8, '0X' => 16); + + return intval($match[2], $bases[$match[1]]) << $shifts[$match[3]]; + } + + return 0; + } + + /** + * 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; + } + +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php new file mode 100644 index 00000000..9cff22a2 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorExtension.php @@ -0,0 +1,57 @@ + + * + * 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\Type; +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(), + ); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php new file mode 100644 index 00000000..dcd9cc55 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ValidatorTypeGuesser.php @@ -0,0 +1,286 @@ + + * + * 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 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\MaxLength': + case 'Symfony\Component\Validator\Constraints\MinLength': + case 'Symfony\Component\Validator\Constraints\Regex': + return new TypeGuess('text', array(), Guess::LOW_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\Min': + case 'Symfony\Component\Validator\Constraints\Max': + return new TypeGuess('number', array(), Guess::LOW_CONFIDENCE); + + case 'Symfony\Component\Validator\Constraints\MinCount': + case 'Symfony\Component\Validator\Constraints\MaxCount': + 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 Guess 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 Guess The guess for the maximum length + */ + public function guessMaxLengthForConstraint(Constraint $constraint) + { + switch (get_class($constraint)) { + case 'Symfony\Component\Validator\Constraints\MaxLength': + return new ValueGuess($constraint->limit, Guess::HIGH_CONFIDENCE); + + 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\Max': + return new ValueGuess(strlen((string) $constraint->limit), Guess::LOW_CONFIDENCE); + } + + return null; + } + + /** + * Guesses a field's pattern based on the given constraint + * + * @param Constraint $constraint The constraint to guess for + * + * @return Guess The guess for the pattern + */ + public function guessPatternForConstraint(Constraint $constraint) + { + switch (get_class($constraint)) { + case 'Symfony\Component\Validator\Constraints\MinLength': + return new ValueGuess(sprintf('.{%s,}', (string) $constraint->limit), Guess::LOW_CONFIDENCE); + + 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\Min': + return new ValueGuess(sprintf('.{%s,}', strlen((string) $constraint->limit)), Guess::LOW_CONFIDENCE); + + 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 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); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php new file mode 100644 index 00000000..7b96efb4 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/MappingRule.php @@ -0,0 +1,106 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/RelativePath.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/RelativePath.php new file mode 100644 index 00000000..ef5c9fad --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/RelativePath.php @@ -0,0 +1,45 @@ + + * + * 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; + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php new file mode 100644 index 00000000..8a7636c7 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapper.php @@ -0,0 +1,299 @@ + + * + * 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\Extension\Validator\ViolationMapper\ViolationPathIterator; +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->all()) + ); + + // 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(); + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php new file mode 100644 index 00000000..eb8907f1 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationMapperInterface.php @@ -0,0 +1,33 @@ + + * + * 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); +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php new file mode 100644 index 00000000..06d09195 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPath.php @@ -0,0 +1,250 @@ + + * + * 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); + } + } +} diff --git a/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php new file mode 100644 index 00000000..50baa453 --- /dev/null +++ b/vendor/symfony/form/Symfony/Component/Form/Extension/Validator/ViolationMapper/ViolationPathIterator.php @@ -0,0 +1,30 @@ + + * + * 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()); + } +} -- cgit v1.2.3