This commit is contained in:
Xes
2025-08-14 22:41:49 +02:00
parent 2de81ccc46
commit 8ce45119b6
39774 changed files with 4309466 additions and 0 deletions

View File

@@ -0,0 +1,300 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\ChoiceList;
use Doctrine\Common\Util\ClassUtils;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Symfony\Component\Form\Exception\InvalidArgumentException;
use Symfony\Component\Form\Exception\RuntimeException;
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyAccess\PropertyPath;
@trigger_error(
'The '.__CLASS__.' class is deprecated since 3.24 and will be removed in 4.0.'
.' Use '.__NAMESPACE__.'\ModelChoiceLoader instead.',
E_USER_DEPRECATED
);
/**
* NEXT_MAJOR: Remove this class.
*
* @deprecated Since 3.24, to be removed in 4.0. Use Sonata\AdminBundle\ModelChoiceLoader instead
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class ModelChoiceList extends SimpleChoiceList
{
/**
* @var ModelManagerInterface
*/
private $modelManager;
/**
* @var string
*/
private $class;
/**
* The entities from which the user can choose.
*
* This array is either indexed by ID (if the ID is a single field)
* or by key in the choices array (if the ID consists of multiple fields)
*
* This property is initialized by initializeChoices(). It should only
* be accessed through getEntity() and getEntities().
*
* @var mixed
*/
private $entities = [];
/**
* Contains the query builder that builds the query for fetching the
* entities.
*
* This property should only be accessed through queryBuilder.
*
* @var \Doctrine\ORM\QueryBuilder
*/
private $query;
/**
* The fields of which the identifier of the underlying class consists.
*
* This property should only be accessed through identifier.
*
* @var array
*/
private $identifier = [];
/**
* A cache for \ReflectionProperty instances for the underlying class.
*
* This property should only be accessed through getReflProperty().
*
* @var array
*/
private $reflProperties = [];
/**
* @var PropertyPath
*/
private $propertyPath;
/**
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
/**
* @param ModelManagerInterface $modelManager
* @param string $class
* @param null $property
* @param null $query
* @param array $choices
*/
public function __construct(ModelManagerInterface $modelManager, $class, $property = null, $query = null, $choices = [], PropertyAccessorInterface $propertyAccessor = null)
{
$this->modelManager = $modelManager;
$this->class = $class;
$this->query = $query;
$this->identifier = $this->modelManager->getIdentifierFieldNames($this->class);
// The property option defines, which property (path) is used for
// displaying entities as strings
if ($property) {
$this->propertyPath = new PropertyPath($property);
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
}
parent::__construct($this->load($choices));
}
/**
* @return array
*/
public function getIdentifier()
{
return $this->identifier;
}
/**
* Returns the according entities for the choices.
*
* If the choices were not initialized, they are initialized now. This
* is an expensive operation, except if the entities were passed in the
* "choices" option.
*
* @return array An array of entities
*/
public function getEntities()
{
return $this->entities;
}
/**
* Returns the entity for the given key.
*
* If the underlying entities have composite identifiers, the choices
* are initialized. The key is expected to be the index in the choices
* array in this case.
*
* If they have single identifiers, they are either fetched from the
* internal entity cache (if filled) or loaded from the database.
*
* @param string $key The choice key (for entities with composite
* identifiers) or entity ID (for entities with single
* identifiers)
*
* @return object The matching entity
*/
public function getEntity($key)
{
if (count($this->identifier) > 1) {
// $key is a collection index
$entities = $this->getEntities();
return isset($entities[$key]) ? $entities[$key] : null;
} elseif ($this->entities) {
return isset($this->entities[$key]) ? $this->entities[$key] : null;
}
return $this->modelManager->find($this->class, $key);
}
/**
* Returns the values of the identifier fields of an entity.
*
* Doctrine must know about this entity, that is, the entity must already
* be persisted or added to the identity map before. Otherwise an
* exception is thrown.
*
* @param object $entity The entity for which to get the identifier
*
* @throws InvalidArgumentException If the entity does not exist in Doctrine's
* identity map
*
* @return array
*/
public function getIdentifierValues($entity)
{
try {
return $this->modelManager->getIdentifierValues($entity);
} catch (\Exception $e) {
throw new InvalidArgumentException(sprintf('Unable to retrieve the identifier values for entity %s', ClassUtils::getClass($entity)), 0, $e);
}
}
/**
* @return ModelManagerInterface
*/
public function getModelManager()
{
return $this->modelManager;
}
/**
* @return string
*/
public function getClass()
{
return $this->class;
}
/**
* Initializes the choices and returns them.
*
* The choices are generated from the entities. If the entities have a
* composite identifier, the choices are indexed using ascending integers.
* Otherwise the identifiers are used as indices.
*
* If the entities were passed in the "choices" option, this method
* does not have any significant overhead. Otherwise, if a query builder
* was passed in the "query" option, this builder is now used to construct
* a query which is executed. In the last case, all entities for the
* underlying class are fetched from the repository.
*
* If the option "property" was passed, the property path in that option
* is used as option values. Otherwise this method tries to convert
* objects to strings using __toString().
*
* @param $choices
*
* @return array An array of choices
*/
protected function load($choices)
{
if (is_array($choices) && count($choices) > 0) {
$entities = $choices;
} elseif ($this->query) {
$entities = $this->modelManager->executeQuery($this->query);
} else {
$entities = $this->modelManager->findBy($this->class);
}
if (null === $entities) {
return [];
}
$choices = [];
$this->entities = [];
foreach ($entities as $key => $entity) {
if ($this->propertyPath) {
// If the property option was given, use it
$value = $this->propertyAccessor->getValue($entity, $this->propertyPath);
} else {
// Otherwise expect a __toString() method in the entity
try {
$value = (string) $entity;
} catch (\Exception $e) {
throw new RuntimeException(sprintf('Unable to convert the entity "%s" to string, provide '
.'"property" option or implement "__toString()" method in your entity.', ClassUtils::getClass($entity)), 0, $e);
}
}
if (count($this->identifier) > 1) {
// When the identifier consists of multiple field, use
// naturally ordered keys to refer to the choices
$choices[$key] = $value;
$this->entities[$key] = $entity;
} else {
// When the identifier is a single field, index choices by
// entity ID for performance reasons
$id = current($this->getIdentifierValues($entity));
$choices[$id] = $value;
$this->entities[$id] = $entity;
}
}
return $choices;
}
/**
* Returns the \ReflectionProperty instance for a property of the
* underlying class.
*
* @param string $property The name of the property
*
* @return \ReflectionProperty The reflection instance
*/
private function getReflProperty($property)
{
if (!isset($this->reflProperties[$property])) {
$this->reflProperties[$property] = new \ReflectionProperty($this->class, $property);
$this->reflProperties[$property]->setAccessible(true);
}
return $this->reflProperties[$property];
}
}

View File

@@ -0,0 +1,168 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\ChoiceList;
use Doctrine\Common\Util\ClassUtils;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Sonata\CoreBundle\Model\Adapter\AdapterInterface;
use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\Exception\RuntimeException;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyAccess\PropertyPath;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class ModelChoiceLoader implements ChoiceLoaderInterface
{
public $identifier;
/**
* @var \Sonata\AdminBundle\Model\ModelManagerInterface
*/
private $modelManager;
/**
* @var string
*/
private $class;
private $property;
private $query;
private $choices;
/**
* @var PropertyPath
*/
private $propertyPath;
/**
* @var PropertyAccessorInterface
*/
private $propertyAccessor;
private $choiceList;
/**
* @param ModelManagerInterface $modelManager
* @param string $class
* @param null $property
* @param null $query
* @param array $choices
* @param PropertyAccessorInterface|null $propertyAccessor
*/
public function __construct(ModelManagerInterface $modelManager, $class, $property = null, $query = null, $choices = [], PropertyAccessorInterface $propertyAccessor = null)
{
$this->modelManager = $modelManager;
$this->class = $class;
$this->property = $property;
$this->query = $query;
$this->choices = $choices;
$this->identifier = $this->modelManager->getIdentifierFieldNames($this->class);
// The property option defines, which property (path) is used for
// displaying entities as strings
if ($property) {
$this->propertyPath = new PropertyPath($property);
$this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor();
}
}
/**
* {@inheritdoc}
*/
public function loadChoiceList($value = null)
{
if (!$this->choiceList) {
if ($this->query) {
$entities = $this->modelManager->executeQuery($this->query);
} elseif (is_array($this->choices) && count($this->choices) > 0) {
$entities = $this->choices;
} else {
$entities = $this->modelManager->findBy($this->class);
}
$choices = [];
foreach ($entities as $key => $entity) {
if ($this->propertyPath) {
// If the property option was given, use it
$valueObject = $this->propertyAccessor->getValue($entity, $this->propertyPath);
} else {
// Otherwise expect a __toString() method in the entity
try {
$valueObject = (string) $entity;
} catch (\Exception $e) {
throw new RuntimeException(sprintf('Unable to convert the entity "%s" to string, provide "property" option or implement "__toString()" method in your entity.', ClassUtils::getClass($entity)), 0, $e);
}
}
$id = implode(AdapterInterface::ID_SEPARATOR, $this->getIdentifierValues($entity));
if (!array_key_exists($valueObject, $choices)) {
$choices[$valueObject] = [];
}
$choices[$valueObject][] = $id;
}
$finalChoices = [];
foreach ($choices as $valueObject => $idx) {
if (count($idx) > 1) { // avoid issue with identical values ...
foreach ($idx as $id) {
$finalChoices[sprintf('%s (id: %s)', $valueObject, $id)] = $id;
}
} else {
$finalChoices[$valueObject] = current($idx);
}
}
$this->choiceList = new ArrayChoiceList($finalChoices, $value);
}
return $this->choiceList;
}
/**
* {@inheritdoc}
*/
public function loadChoicesForValues(array $values, $value = null)
{
return $this->loadChoiceList($value)->getChoicesForValues($values);
}
/**
* {@inheritdoc}
*/
public function loadValuesForChoices(array $choices, $value = null)
{
return $this->loadChoiceList($value)->getValuesForChoices($choices);
}
/**
* @param object $entity
*
* @return array
*/
private function getIdentifierValues($entity)
{
try {
return $this->modelManager->getIdentifierValues($entity);
} catch (\Exception $e) {
throw new \InvalidArgumentException(sprintf('Unable to retrieve the identifier values for entity %s', ClassUtils::getClass($entity)), 0, $e);
}
}
}

View File

@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\DataTransformer;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Symfony\Component\Form\DataTransformerInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class ArrayToModelTransformer implements DataTransformerInterface
{
/**
* @var ModelManagerInterface
*/
protected $modelManager;
/**
* @var string
*/
protected $className;
/**
* @param ModelManagerInterface $modelManager
* @param string $className
*/
public function __construct(ModelManagerInterface $modelManager, $className)
{
$this->modelManager = $modelManager;
$this->className = $className;
}
/**
* {@inheritdoc}
*/
public function reverseTransform($array)
{
// when the object is created the form return an array
// one the object is persisted, the edit $array is the user instance
if ($array instanceof $this->className) {
return $array;
}
$instance = new $this->className();
if (!is_array($array)) {
return $instance;
}
return $this->modelManager->modelReverseTransform($this->className, $array);
}
/**
* {@inheritdoc}
*/
public function transform($value)
{
return $value;
}
}

View File

@@ -0,0 +1,115 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\DataTransformer;
use Sonata\AdminBundle\Form\ChoiceList\ModelChoiceList;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
/**
* NEXT_MAJOR: remove this class when dropping Symfony < 2.7 support.
*
* This class should be used with Symfony <2.7 only and will be deprecated with 3.0.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class LegacyModelsToArrayTransformer implements DataTransformerInterface
{
/**
* @var ModelChoiceList
*/
protected $choiceList;
/**
* @param ModelChoiceList $choiceList
*/
public function __construct(ModelChoiceList $choiceList)
{
if (interface_exists('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface')) {
@trigger_error(
'The '.__CLASS__.' class is deprecated since 3.11, to be removed in 4.0. '.
'Use Sonata\AdminBundle\Form\DataTransformer\ModelsToArrayTransformer instead.',
E_USER_DEPRECATED
);
}
$this->choiceList = $choiceList;
}
/**
* {@inheritdoc}
*/
public function transform($collection)
{
if (null === $collection) {
return [];
}
$array = [];
if (count($this->choiceList->getIdentifier()) > 1) {
// load all choices
$availableEntities = $this->choiceList->getEntities();
foreach ($collection as $entity) {
// identify choices by their collection key
$key = array_search($entity, $availableEntities);
$array[] = $key;
}
} else {
foreach ($collection as $entity) {
$array[] = current($this->choiceList->getIdentifierValues($entity));
}
}
return $array;
}
/**
* {@inheritdoc}
*/
public function reverseTransform($keys)
{
$collection = $this->choiceList->getModelManager()->getModelCollectionInstance(
$this->choiceList->getClass()
);
if (!$collection instanceof \ArrayAccess) {
throw new UnexpectedTypeException($collection, '\ArrayAccess');
}
if ('' === $keys || null === $keys) {
return $collection;
}
if (!is_array($keys)) {
throw new UnexpectedTypeException($keys, 'array');
}
$notFound = [];
// optimize this into a SELECT WHERE IN query
foreach ($keys as $key) {
if ($entity = $this->choiceList->getEntity($key)) {
$collection[] = $entity;
} else {
$notFound[] = $key;
}
}
if (count($notFound) > 0) {
throw new TransformationFailedException(sprintf('The entities with keys "%s" could not be found', implode('", "', $notFound)));
}
return $collection;
}
}

View File

@@ -0,0 +1,157 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\DataTransformer;
use Doctrine\Common\Util\ClassUtils;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Symfony\Component\Form\DataTransformerInterface;
/**
* Transform object to ID and property label.
*
* @author Andrej Hudec <pulzarraider@gmail.com>
*/
class ModelToIdPropertyTransformer implements DataTransformerInterface
{
/**
* @var ModelManagerInterface
*/
protected $modelManager;
/**
* @var string
*/
protected $className;
/**
* @var string
*/
protected $property;
/**
* @var bool
*/
protected $multiple;
/**
* @var callable|null
*/
protected $toStringCallback;
/**
* @param ModelManagerInterface $modelManager
* @param string $className
* @param string $property
* @param bool $multiple
* @param null $toStringCallback
*/
public function __construct(ModelManagerInterface $modelManager, $className, $property, $multiple = false, $toStringCallback = null)
{
$this->modelManager = $modelManager;
$this->className = $className;
$this->property = $property;
$this->multiple = $multiple;
$this->toStringCallback = $toStringCallback;
}
/**
* {@inheritdoc}
*/
public function reverseTransform($value)
{
$collection = $this->modelManager->getModelCollectionInstance($this->className);
if (empty($value)) {
if ($this->multiple) {
return $collection;
}
return;
}
if (!$this->multiple) {
return $this->modelManager->find($this->className, $value);
}
if (!is_array($value)) {
throw new \UnexpectedValueException(sprintf('Value should be array, %s given.', gettype($value)));
}
foreach ($value as $key => $id) {
if ($key === '_labels') {
continue;
}
$collection->add($this->modelManager->find($this->className, $id));
}
return $collection;
}
/**
* {@inheritdoc}
*/
public function transform($entityOrCollection)
{
$result = [];
if (!$entityOrCollection) {
return $result;
}
if ($this->multiple) {
$isArray = is_array($entityOrCollection);
if (!$isArray && substr(get_class($entityOrCollection), -1 * strlen($this->className)) == $this->className) {
throw new \InvalidArgumentException('A multiple selection must be passed a collection not a single value. Make sure that form option "multiple=false" is set for many-to-one relation and "multiple=true" is set for many-to-many or one-to-many relations.');
} elseif ($isArray || ($entityOrCollection instanceof \ArrayAccess)) {
$collection = $entityOrCollection;
} else {
throw new \InvalidArgumentException('A multiple selection must be passed a collection not a single value. Make sure that form option "multiple=false" is set for many-to-one relation and "multiple=true" is set for many-to-many or one-to-many relations.');
}
} else {
if (substr(get_class($entityOrCollection), -1 * strlen($this->className)) == $this->className) {
$collection = [$entityOrCollection];
} elseif ($entityOrCollection instanceof \ArrayAccess) {
throw new \InvalidArgumentException('A single selection must be passed a single value not a collection. Make sure that form option "multiple=false" is set for many-to-one relation and "multiple=true" is set for many-to-many or one-to-many relations.');
} else {
$collection = [$entityOrCollection];
}
}
if (empty($this->property)) {
throw new \RuntimeException('Please define "property" parameter.');
}
foreach ($collection as $entity) {
$id = current($this->modelManager->getIdentifierValues($entity));
if ($this->toStringCallback !== null) {
if (!is_callable($this->toStringCallback)) {
throw new \RuntimeException('Callback in "to_string_callback" option doesn`t contain callable function.');
}
$label = call_user_func($this->toStringCallback, $entity, $this->property);
} else {
try {
$label = (string) $entity;
} catch (\Exception $e) {
throw new \RuntimeException(sprintf("Unable to convert the entity %s to String, entity must have a '__toString()' method defined", ClassUtils::getClass($entity)), 0, $e);
}
}
$result[] = $id;
$result['_labels'][] = $label;
}
return $result;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\DataTransformer;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Symfony\Component\Form\DataTransformerInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class ModelToIdTransformer implements DataTransformerInterface
{
/**
* @var ModelManagerInterface
*/
protected $modelManager;
/**
* @var string
*/
protected $className;
/**
* @param ModelManagerInterface $modelManager
* @param string $className
*/
public function __construct(ModelManagerInterface $modelManager, $className)
{
$this->modelManager = $modelManager;
$this->className = $className;
}
/**
* {@inheritdoc}
*/
public function reverseTransform($newId)
{
if (empty($newId) && !in_array($newId, ['0', 0], true)) {
return;
}
return $this->modelManager->find($this->className, $newId);
}
/**
* {@inheritdoc}
*/
public function transform($entity)
{
if (empty($entity)) {
return;
}
return $this->modelManager->getNormalizedIdentifier($entity);
}
}

View File

@@ -0,0 +1,217 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\DataTransformer;
use Doctrine\Common\Util\ClassUtils;
use Sonata\AdminBundle\Form\ChoiceList\ModelChoiceList;
use Sonata\AdminBundle\Form\ChoiceList\ModelChoiceLoader;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Sonata\CoreBundle\Model\Adapter\AdapterInterface;
use Symfony\Component\Form\ChoiceList\LazyChoiceList;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\RuntimeException;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class ModelsToArrayTransformer implements DataTransformerInterface
{
/**
* @var ModelManagerInterface
*/
protected $modelManager;
/**
* @var string
*/
protected $class;
/**
* @var ModelChoiceList
*
* @deprecated since 3.12, to be removed in 4.0
* NEXT_MAJOR: remove this property
*/
protected $choiceList;
/**
* ModelsToArrayTransformer constructor.
*
* @param ModelChoiceList|LazyChoiceList|ModelChoiceLoader $choiceList
* @param ModelManagerInterface $modelManager
* @param $class
*
* @throws RuntimeException
*/
public function __construct($choiceList, $modelManager, $class = null)
{
/*
NEXT_MAJOR: Remove condition , magic methods, legacyConstructor() method, $choiceList property and argument
__construct() signature should be : public function __construct(ModelManager $modelManager, $class)
*/
$args = func_get_args();
if (func_num_args() == 3) {
$this->legacyConstructor($args);
} else {
$this->modelManager = $args[0];
$this->class = $args[1];
}
}
/**
* @internal
*/
public function __get($name)
{
if ('choiceList' === $name) {
$this->triggerDeprecation();
}
return $this->$name;
}
/**
* @internal
*/
public function __set($name, $value)
{
if ('choiceList' === $name) {
$this->triggerDeprecation();
}
$this->$name = $value;
}
/**
* @internal
*/
public function __isset($name)
{
if ('choiceList' === $name) {
$this->triggerDeprecation();
}
return isset($this->$name);
}
/**
* @internal
*/
public function __unset($name)
{
if ('choiceList' === $name) {
$this->triggerDeprecation();
}
unset($this->$name);
}
/**
* {@inheritdoc}
*/
public function transform($collection)
{
if (null === $collection) {
return [];
}
$array = [];
foreach ($collection as $key => $entity) {
$id = implode(AdapterInterface::ID_SEPARATOR, $this->getIdentifierValues($entity));
$array[] = $id;
}
return $array;
}
/**
* {@inheritdoc}
*/
public function reverseTransform($keys)
{
if (!is_array($keys)) {
throw new UnexpectedTypeException($keys, 'array');
}
$collection = $this->modelManager->getModelCollectionInstance($this->class);
$notFound = [];
// optimize this into a SELECT WHERE IN query
foreach ($keys as $key) {
if ($entity = $this->modelManager->find($this->class, $key)) {
$collection[] = $entity;
} else {
$notFound[] = $key;
}
}
if (count($notFound) > 0) {
throw new TransformationFailedException(sprintf('The entities with keys "%s" could not be found', implode('", "', $notFound)));
}
return $collection;
}
/**
* Simulates the old constructor for BC.
*
* @param array $args
*
* @throws RuntimeException
*/
private function legacyConstructor($args)
{
$choiceList = $args[0];
if (!$choiceList instanceof ModelChoiceList
&& !$choiceList instanceof ModelChoiceLoader
&& !$choiceList instanceof LazyChoiceList) {
throw new RuntimeException('First param passed to ModelsToArrayTransformer should be instance of
ModelChoiceLoader or ModelChoiceList or LazyChoiceList');
}
$this->choiceList = $choiceList;
$this->modelManager = $args[1];
$this->class = $args[2];
}
/**
* @param object $entity
*
* @return array
*/
private function getIdentifierValues($entity)
{
try {
return $this->modelManager->getIdentifierValues($entity);
} catch (\Exception $e) {
throw new \InvalidArgumentException(sprintf('Unable to retrieve the identifier values for entity %s', ClassUtils::getClass($entity)), 0, $e);
}
}
/**
* @internal
*/
private function triggerDeprecation()
{
@trigger_error(sprintf(
'Using the "%s::$choiceList" property is deprecated since version 3.12 and will be removed in 4.0.',
__CLASS__),
E_USER_DEPRECATED)
;
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\EventListener;
use Sonata\AdminBundle\Model\ModelManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class MergeCollectionListener implements EventSubscriberInterface
{
/**
* @var ModelManagerInterface
*/
protected $modelManager;
/**
* @param ModelManagerInterface $modelManager
*/
public function __construct(ModelManagerInterface $modelManager)
{
$this->modelManager = $modelManager;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
FormEvents::SUBMIT => ['onBind', 10],
];
}
/**
* @param FormEvent $event
*/
public function onBind(FormEvent $event)
{
$collection = $event->getForm()->getData();
$data = $event->getData();
// looks like there is no way to remove other listeners
$event->stopPropagation();
if (!$collection) {
$collection = $data;
} elseif (count($data) === 0) {
$this->modelManager->collectionClear($collection);
} else {
// merge $data into $collection
foreach ($collection as $entity) {
if (!$this->modelManager->collectionHasElement($data, $entity)) {
$this->modelManager->collectionRemoveElement($collection, $entity);
} else {
$this->modelManager->collectionRemoveElement($data, $entity);
}
}
foreach ($data as $entity) {
$this->modelManager->collectionAddElement($collection, $entity);
}
}
$event->setData($collection);
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Extension;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* @author Amine Zaghdoudi <amine.zaghdoudi@ekino.com>
*/
class ChoiceTypeExtension extends AbstractTypeExtension
{
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$optionalOptions = ['sortable'];
if (method_exists($resolver, 'setDefined')) {
$resolver->setDefined($optionalOptions);
} else {
// To keep Symfony <2.6 support
$resolver->setOptional($optionalOptions);
}
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['sortable'] = array_key_exists('sortable', $options) && $options['sortable'];
}
/**
* {@inheritdoc}
*/
public function getExtendedType()
{
/*
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support. It should
* simply be return 'Symfony\Component\Form\Extension\Core\Type\ChoiceType';
*/
return method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\ChoiceType'
: 'choice';
}
}

View File

@@ -0,0 +1,263 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Extension\Field\Type;
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
use Sonata\AdminBundle\Exception\NoValueException;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class FormTypeFieldExtension extends AbstractTypeExtension
{
/**
* @var array
*/
protected $defaultClasses = [];
/**
* @var array
*/
protected $options;
/**
* @param array $defaultClasses
* @param array $options
*/
public function __construct(array $defaultClasses, array $options)
{
$this->defaultClasses = $defaultClasses;
$this->options = $options;
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$sonataAdmin = [
'name' => null,
'admin' => null,
'value' => null,
'edit' => 'standard',
'inline' => 'natural',
'field_description' => null,
'block_name' => false,
'options' => $this->options,
];
$builder->setAttribute('sonata_admin_enabled', false);
$builder->setAttribute('sonata_help', false);
if ($options['sonata_field_description'] instanceof FieldDescriptionInterface) {
$fieldDescription = $options['sonata_field_description'];
$sonataAdmin['admin'] = $fieldDescription->getAdmin();
$sonataAdmin['field_description'] = $fieldDescription;
$sonataAdmin['name'] = $fieldDescription->getName();
$sonataAdmin['edit'] = $fieldDescription->getOption('edit', 'standard');
$sonataAdmin['inline'] = $fieldDescription->getOption('inline', 'natural');
$sonataAdmin['block_name'] = $fieldDescription->getOption('block_name', false);
$sonataAdmin['class'] = $this->getClass($builder);
$builder->setAttribute('sonata_admin_enabled', true);
}
$builder->setAttribute('sonata_admin', $sonataAdmin);
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$sonataAdmin = $form->getConfig()->getAttribute('sonata_admin');
$sonataAdminHelp = isset($options['sonata_help']) ? $options['sonata_help'] : null;
/*
* We have a child, so we need to upgrade block prefix
*/
if ($view->parent && $view->parent->vars['sonata_admin_enabled'] && !$sonataAdmin['admin']) {
$blockPrefixes = $view->vars['block_prefixes'];
$baseName = str_replace('.', '_', $view->parent->vars['sonata_admin_code']);
$baseType = $blockPrefixes[count($blockPrefixes) - 2];
$blockSuffix = preg_replace('#^_([a-z0-9]{14})_(.++)$#', '$2', array_pop($blockPrefixes));
$blockPrefixes[] = sprintf('%s_%s', $baseName, $baseType);
$blockPrefixes[] = sprintf('%s_%s_%s_%s', $baseName, $baseType, $view->parent->vars['name'], $view->vars['name']);
$blockPrefixes[] = sprintf('%s_%s_%s_%s', $baseName, $baseType, $view->parent->vars['name'], $blockSuffix);
$view->vars['block_prefixes'] = $blockPrefixes;
$view->vars['sonata_admin_enabled'] = true;
$view->vars['sonata_admin'] = [
'admin' => false,
'field_description' => false,
'name' => false,
'edit' => 'standard',
'inline' => 'natural',
'block_name' => false,
'class' => false,
'options' => $this->options,
];
$view->vars['sonata_help'] = $sonataAdminHelp;
$view->vars['sonata_admin_code'] = $view->parent->vars['sonata_admin_code'];
return;
}
// avoid to add extra information not required by non admin field
if ($sonataAdmin && $form->getConfig()->getAttribute('sonata_admin_enabled', true)) {
$sonataAdmin['value'] = $form->getData();
// add a new block types, so the Admin Form element can be tweaked based on the admin code
$blockPrefixes = $view->vars['block_prefixes'];
$baseName = str_replace('.', '_', $sonataAdmin['admin']->getCode());
$baseType = $blockPrefixes[count($blockPrefixes) - 2];
$blockSuffix = preg_replace('#^_([a-z0-9]{14})_(.++)$#', '$2', array_pop($blockPrefixes));
$blockPrefixes[] = sprintf('%s_%s', $baseName, $baseType);
$blockPrefixes[] = sprintf('%s_%s_%s', $baseName, $sonataAdmin['name'], $baseType);
$blockPrefixes[] = sprintf('%s_%s_%s_%s', $baseName, $sonataAdmin['name'], $baseType, $blockSuffix);
if (isset($sonataAdmin['block_name']) && $sonataAdmin['block_name'] !== false) {
$blockPrefixes[] = $sonataAdmin['block_name'];
}
$view->vars['block_prefixes'] = $blockPrefixes;
$view->vars['sonata_admin_enabled'] = true;
$view->vars['sonata_admin'] = $sonataAdmin;
$view->vars['sonata_admin_code'] = $sonataAdmin['admin']->getCode();
$attr = $view->vars['attr'];
if (!isset($attr['class']) && isset($sonataAdmin['class'])) {
$attr['class'] = $sonataAdmin['class'];
}
$view->vars['attr'] = $attr;
} else {
$view->vars['sonata_admin_enabled'] = false;
}
$view->vars['sonata_help'] = $sonataAdminHelp;
$view->vars['sonata_admin'] = $sonataAdmin;
}
/**
* {@inheritdoc}
*/
public function getExtendedType()
{
return
method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix') ?
'Symfony\Component\Form\Extension\Core\Type\FormType' :
'form';
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'sonata_admin' => null,
'sonata_field_description' => null,
// be compatible with mopa if not installed, avoid generating an exception for invalid option
'label_render' => true,
'sonata_help' => null,
]);
}
/**
* return the value related to FieldDescription, if the associated object does no
* exists => a temporary one is created.
*
* @param object $object
* @param FieldDescriptionInterface $fieldDescription
*
* @return mixed
*/
public function getValueFromFieldDescription($object, FieldDescriptionInterface $fieldDescription)
{
$value = null;
if (!$object) {
return $value;
}
try {
$value = $fieldDescription->getValue($object);
} catch (NoValueException $e) {
if ($fieldDescription->getAssociationAdmin()) {
$value = $fieldDescription->getAssociationAdmin()->getNewInstance();
}
}
return $value;
}
/**
* @param FormBuilderInterface $formBuilder
*
* @return string
*/
protected function getClass(FormBuilderInterface $formBuilder)
{
foreach ($this->getTypes($formBuilder) as $type) {
if (!method_exists($type, 'getName')) { // SF3.0+
$name = get_class($type);
} else {
$name = $type->getName();
}
if (isset($this->defaultClasses[$name])) {
return $this->defaultClasses[$name];
}
}
return '';
}
/**
* @param FormBuilderInterface $formBuilder
*
* @return array
*/
protected function getTypes(FormBuilderInterface $formBuilder)
{
$types = [];
for ($type = $formBuilder->getType(); null !== $type; $type = $type->getParent()) {
array_unshift($types, $type->getInnerType());
}
return $types;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Extension\Field\Type;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* This class is built to allow AdminInterface to work properly
* if the MopaBootstrapBundle is not installed.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class MopaCompatibilityTypeFieldExtension extends AbstractTypeExtension
{
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'horizontal_label_class' => '',
'horizontal_label_offset_class' => '',
'horizontal_input_wrapper_class' => '',
]);
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['horizontal_label_class'] = $options['horizontal_label_class'];
$view->vars['horizontal_label_offset_class'] = $options['horizontal_label_offset_class'];
$view->vars['horizontal_input_wrapper_class'] = $options['horizontal_input_wrapper_class'];
}
/**
* {@inheritdoc}
*/
public function getExtendedType()
{
/*
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support. It should
* simply be return 'Symfony\Component\Form\Extension\Core\Type\FormType';
*/
return method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\FormType'
: 'form';
}
}

View File

@@ -0,0 +1,336 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Builder\FormContractorInterface;
use Sonata\AdminBundle\Mapper\BaseGroupedMapper;
use Symfony\Component\Form\FormBuilderInterface;
/**
* This class is use to simulate the Form API.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class FormMapper extends BaseGroupedMapper
{
/**
* @var FormBuilderInterface
*/
protected $formBuilder;
/**
* @param FormContractorInterface $formContractor
* @param FormBuilderInterface $formBuilder
* @param AdminInterface $admin
*/
public function __construct(FormContractorInterface $formContractor, FormBuilderInterface $formBuilder, AdminInterface $admin)
{
parent::__construct($formContractor, $admin);
$this->formBuilder = $formBuilder;
}
/**
* {@inheritdoc}
*/
public function reorder(array $keys)
{
$this->admin->reorderFormGroup($this->getCurrentGroupName(), $keys);
return $this;
}
/**
* @param string $name
* @param string $type
* @param array $options
* @param array $fieldDescriptionOptions
*
* @return $this
*/
public function add($name, $type = null, array $options = [], array $fieldDescriptionOptions = [])
{
if ($this->apply !== null && !$this->apply) {
return $this;
}
if ($name instanceof FormBuilderInterface) {
$fieldName = $name->getName();
} else {
$fieldName = $name;
}
// "Dot" notation is not allowed as form name, but can be used as property path to access nested data.
if (!$name instanceof FormBuilderInterface && !isset($options['property_path'])) {
$options['property_path'] = $fieldName;
// fix the form name
$fieldName = $this->sanitizeFieldName($fieldName);
}
// change `collection` to `sonata_type_native_collection` form type to
// avoid BC break problems
if ($type === 'collection' || $type === 'Symfony\Component\Form\Extension\Core\Type\CollectionType') {
// the field name is used to preserve Symfony <2.8 compatibility, the FQCN should be used instead
$type = 'sonata_type_native_collection';
}
$label = $fieldName;
$group = $this->addFieldToCurrentGroup($label);
// Try to autodetect type
if ($name instanceof FormBuilderInterface && null === $type) {
$fieldDescriptionOptions['type'] = get_class($name->getType()->getInnerType());
}
if (!isset($fieldDescriptionOptions['type']) && is_string($type)) {
$fieldDescriptionOptions['type'] = $type;
}
if ($group['translation_domain'] && !isset($fieldDescriptionOptions['translation_domain'])) {
$fieldDescriptionOptions['translation_domain'] = $group['translation_domain'];
}
$fieldDescription = $this->admin->getModelManager()->getNewFieldDescriptionInstance(
$this->admin->getClass(),
$name instanceof FormBuilderInterface ? $name->getName() : $name,
$fieldDescriptionOptions
);
// Note that the builder var is actually the formContractor:
$this->builder->fixFieldDescription($this->admin, $fieldDescription, $fieldDescriptionOptions);
if ($fieldName != $name) {
$fieldDescription->setName($fieldName);
}
$this->admin->addFormFieldDescription($fieldName, $fieldDescription);
if ($name instanceof FormBuilderInterface) {
$this->formBuilder->add($name);
} else {
// Note that the builder var is actually the formContractor:
$options = array_replace_recursive($this->builder->getDefaultOptions($type, $fieldDescription), $options);
// be compatible with mopa if not installed, avoid generating an exception for invalid option
// force the default to false ...
if (!isset($options['label_render'])) {
$options['label_render'] = false;
}
if (!isset($options['label'])) {
$options['label'] = $this->admin->getLabelTranslatorStrategy()->getLabel($fieldDescription->getName(), 'form', 'label');
}
$help = null;
if (isset($options['help'])) {
$help = $options['help'];
unset($options['help']);
}
$this->formBuilder->add($fieldDescription->getName(), $type, $options);
if (null !== $help) {
$this->admin->getFormFieldDescription($fieldDescription->getName())->setHelp($help);
}
}
return $this;
}
/**
* {@inheritdoc}
*/
public function get($name)
{
$name = $this->sanitizeFieldName($name);
return $this->formBuilder->get($name);
}
/**
* {@inheritdoc}
*/
public function has($key)
{
$key = $this->sanitizeFieldName($key);
return $this->formBuilder->has($key);
}
/**
* {@inheritdoc}
*/
final public function keys()
{
return array_keys($this->formBuilder->all());
}
/**
* {@inheritdoc}
*/
public function remove($key)
{
$key = $this->sanitizeFieldName($key);
$this->admin->removeFormFieldDescription($key);
$this->admin->removeFieldFromFormGroup($key);
$this->formBuilder->remove($key);
return $this;
}
/**
* Removes a group.
*
* @param string $group The group to delete
* @param string $tab The tab the group belongs to, defaults to 'default'
* @param bool $deleteEmptyTab Whether or not the Tab should be deleted, when the deleted group leaves the tab empty after deletion
*
* @return $this
*/
public function removeGroup($group, $tab = 'default', $deleteEmptyTab = false)
{
$groups = $this->getGroups();
// When the default tab is used, the tabname is not prepended to the index in the group array
if ($tab !== 'default') {
$group = $tab.'.'.$group;
}
if (isset($groups[$group])) {
foreach ($groups[$group]['fields'] as $field) {
$this->remove($field);
}
}
unset($groups[$group]);
$tabs = $this->getTabs();
$key = array_search($group, $tabs[$tab]['groups']);
if (false !== $key) {
unset($tabs[$tab]['groups'][$key]);
}
if ($deleteEmptyTab && count($tabs[$tab]['groups']) == 0) {
unset($tabs[$tab]);
}
$this->setTabs($tabs);
$this->setGroups($groups);
return $this;
}
/**
* @return FormBuilderInterface
*/
public function getFormBuilder()
{
return $this->formBuilder;
}
/**
* @param string $name
* @param mixed $type
* @param array $options
*
* @return FormBuilderInterface
*/
public function create($name, $type = null, array $options = [])
{
return $this->formBuilder->create($name, $type, $options);
}
/**
* @param array $helps
*
* @return FormMapper
*/
public function setHelps(array $helps = [])
{
foreach ($helps as $name => $help) {
$this->addHelp($name, $help);
}
return $this;
}
/**
* @param $name
* @param $help
*
* @return FormMapper
*/
public function addHelp($name, $help)
{
if ($this->admin->hasFormFieldDescription($name)) {
$this->admin->getFormFieldDescription($name)->setHelp($help);
}
return $this;
}
/**
* Symfony default form class sadly can't handle
* form element with dots in its name (when data
* get bound, the default dataMapper is a PropertyPathMapper).
* So use this trick to avoid any issue.
*
* @param string $fieldName
*
* @return string
*/
protected function sanitizeFieldName($fieldName)
{
return str_replace(['__', '.'], ['____', '__'], $fieldName);
}
/**
* {@inheritdoc}
*/
protected function getGroups()
{
return $this->admin->getFormGroups();
}
/**
* {@inheritdoc}
*/
protected function setGroups(array $groups)
{
$this->admin->setFormGroups($groups);
}
/**
* {@inheritdoc}
*/
protected function getTabs()
{
return $this->admin->getFormTabs();
}
/**
* {@inheritdoc}
*/
protected function setTabs(array $tabs)
{
$this->admin->setFormTabs($tabs);
}
/**
* {@inheritdoc}
*/
protected function getName()
{
return 'form';
}
}

View File

@@ -0,0 +1,105 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* This type define an ACL matrix.
*
* @author Samuel Roze <samuel@sroze.io>
* @author Baptiste Meyer <baptiste@les-tilleuls.coop>
*/
class AclMatrixType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$aclValueType = $options['acl_value'] instanceof UserInterface ? 'user' : 'role';
$aclValueData = $options['acl_value'] instanceof UserInterface ? $options['acl_value']->getUsername() : $options['acl_value'];
$builder->add(
$aclValueType,
// NEXT_MAJOR: remove when dropping Symfony <2.8 support
method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\HiddenType'
: 'hidden',
['data' => $aclValueData]
);
foreach ($options['permissions'] as $permission => $attributes) {
$builder->add(
$permission,
// NEXT_MAJOR: remove when dropping Symfony <2.8 support
method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\CheckboxType'
: 'checkbox',
$attributes
);
}
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setRequired([
'permissions',
'acl_value',
]);
if (method_exists($resolver, 'setDefined')) {
$resolver->setAllowedTypes('permissions', 'array');
$resolver->setAllowedTypes('acl_value', ['string', '\Symfony\Component\Security\Core\User\UserInterface']);
} else {
$resolver->setAllowedTypes([
'permissions' => 'array',
'acl_value' => ['string', '\Symfony\Component\Security\Core\User\UserInterface'],
]);
}
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_acl_matrix';
}
}

View File

@@ -0,0 +1,182 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type;
use Doctrine\Common\Collections\Collection;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
use Sonata\AdminBundle\Form\DataTransformer\ArrayToModelTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\PropertyAccess\Exception\NoSuchIndexException;
use Symfony\Component\PropertyAccess\PropertyAccessor;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class AdminType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$admin = clone $this->getAdmin($options);
if ($admin->hasParentFieldDescription()) {
$admin->getParentFieldDescription()->setAssociationAdmin($admin);
}
if ($options['delete'] && $admin->hasAccess('delete')) {
if (!array_key_exists('translation_domain', $options['delete_options']['type_options'])) {
$options['delete_options']['type_options']['translation_domain'] = $admin->getTranslationDomain();
}
$builder->add('_delete', $options['delete_options']['type'], $options['delete_options']['type_options']);
}
// hack to make sure the subject is correctly set
// https://github.com/sonata-project/SonataAdminBundle/pull/2076
if ($builder->getData() === null) {
$p = new PropertyAccessor(false, true);
try {
$parentSubject = $admin->getParentFieldDescription()->getAdmin()->getSubject();
if ($parentSubject !== null && $parentSubject !== false) {
// for PropertyAccessor < 2.5
// NEXT_MAJOR: remove this code for old PropertyAccessor after dropping support for Symfony 2.3
if (!method_exists($p, 'isReadable')) {
$subjectCollection = $p->getValue(
$parentSubject,
$this->getFieldDescription($options)->getFieldName()
);
if ($subjectCollection instanceof Collection) {
$subject = $subjectCollection->get(trim($options['property_path'], '[]'));
}
} else {
// for PropertyAccessor >= 2.5
$subject = $p->getValue(
$parentSubject,
$options['property_path']
);
}
$builder->setData($subject);
}
} catch (NoSuchIndexException $e) {
// no object here
}
}
$admin->setSubject($builder->getData());
$admin->defineFormBuilder($builder);
$builder->addModelTransformer(new ArrayToModelTransformer($admin->getModelManager(), $admin->getClass()));
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['btn_add'] = $options['btn_add'];
$view->vars['btn_list'] = $options['btn_list'];
$view->vars['btn_delete'] = $options['btn_delete'];
$view->vars['btn_catalogue'] = $options['btn_catalogue'];
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'delete' => function (Options $options) {
return $options['btn_delete'] !== false;
},
'delete_options' => [
// NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
'type' => method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\CheckboxType'
: 'checkbox',
'type_options' => [
'required' => false,
'mapped' => false,
],
],
'auto_initialize' => false,
'btn_add' => 'link_add',
'btn_list' => 'link_list',
'btn_delete' => 'link_delete',
'btn_catalogue' => 'SonataAdminBundle',
]);
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_admin';
}
/**
* @param array $options
*
* @return FieldDescriptionInterface
*
* @throws \RuntimeException
*/
protected function getFieldDescription(array $options)
{
if (!isset($options['sonata_field_description'])) {
throw new \RuntimeException('Please provide a valid `sonata_field_description` option');
}
return $options['sonata_field_description'];
}
/**
* @param array $options
*
* @return AdminInterface
*/
protected function getAdmin(array $options)
{
return $this->getFieldDescription($options)->getAssociationAdmin();
}
}

View File

@@ -0,0 +1,104 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class ChoiceFieldMaskType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$sanitizedMap = [];
foreach ($options['map'] as $value => $fieldNames) {
foreach ($fieldNames as $fieldName) {
$sanitizedMap[$value][] =
str_replace(['__', '.'], ['____', '__'], $fieldName);
}
}
$allFieldNames = call_user_func_array('array_merge', $sanitizedMap);
$allFieldNames = array_unique($allFieldNames);
$view->vars['all_fields'] = $allFieldNames;
$view->vars['map'] = $sanitizedMap;
$options['expanded'] = false;
parent::buildView($view, $form, $options);
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
// NEXT_MAJOR: Remove conditional parent call when bumping requirements to SF 2.7+
if (method_exists('Symfony\Component\Form\AbstractType', 'configureOptions')) {
parent::configureOptions($resolver);
} else {
parent::setDefaultOptions($resolver);
}
$resolver->setDefaults([
'map' => [],
]);
}
/**
* {@inheritdoc}
*/
public function getParent()
{
// NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
return method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\ChoiceType'
: 'choice';
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_choice_field_mask';
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
/**
* This type wrap native `collection` form type and render `add` and `delete`
* buttons in standard Symfony` collection form type.
*
* @author Andrej Hudec <pulzarraider@gmail.com>
*/
class CollectionType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function getParent()
{
return
method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix') ?
'Symfony\Component\Form\Extension\Core\Type\CollectionType' :
'collection';
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_native_collection';
}
}

View File

@@ -0,0 +1,128 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type\Filter;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class ChoiceType extends AbstractType
{
const TYPE_CONTAINS = 1;
const TYPE_NOT_CONTAINS = 2;
const TYPE_EQUAL = 3;
/**
* NEXT_MAJOR: remove this property.
*
* @deprecated since 3.5, to be removed with 4.0
*
* @var TranslatorInterface
*/
protected $translator;
/**
* @param TranslatorInterface $translator
*/
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_filter_choice';
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$choices = [
'label_type_contains' => self::TYPE_CONTAINS,
'label_type_not_contains' => self::TYPE_NOT_CONTAINS,
'label_type_equals' => self::TYPE_EQUAL,
];
$operatorChoices = [];
// NEXT_MAJOR: Remove first check (when requirement of Symfony is >= 2.8)
if ($options['operator_type'] !== 'hidden' && $options['operator_type'] !== 'Symfony\Component\Form\Extension\Core\Type\HiddenType') {
// NEXT_MAJOR: Remove (when requirement of Symfony is >= 2.7)
if (!method_exists('Symfony\Component\Form\AbstractType', 'configureOptions')) {
$choices = array_flip($choices);
foreach ($choices as $key => $value) {
$choices[$key] = $this->translator->trans($value, [], 'SonataAdminBundle');
}
} else {
$operatorChoices['choice_translation_domain'] = 'SonataAdminBundle';
// NEXT_MAJOR: Remove (when requirement of Symfony is >= 3.0)
if (method_exists('Symfony\Component\Form\FormTypeInterface', 'setDefaultOptions')) {
$operatorChoices['choices_as_values'] = true;
}
}
$operatorChoices['choices'] = $choices;
}
$builder
->add('type', $options['operator_type'], array_merge(['required' => false], $options['operator_options'], $operatorChoices))
->add('value', $options['field_type'], array_merge(['required' => false], $options['field_options']))
;
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'field_type' => 'choice',
'field_options' => [],
'operator_type' => method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\ChoiceType'
: 'choice', // NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
'operator_options' => [],
]);
}
}

View File

@@ -0,0 +1,122 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type\Filter;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class DateRangeType extends AbstractType
{
const TYPE_BETWEEN = 1;
const TYPE_NOT_BETWEEN = 2;
/**
* NEXT_MAJOR: remove this property.
*
* @deprecated since 3.5, to be removed with 4.0
*
* @var TranslatorInterface
*/
protected $translator;
/**
* @param TranslatorInterface $translator
*/
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_filter_date_range';
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$choices = [
'label_date_type_between' => self::TYPE_BETWEEN,
'label_date_type_not_between' => self::TYPE_NOT_BETWEEN,
];
$choiceOptions = [
'required' => false,
];
// NEXT_MAJOR: Remove (when requirement of Symfony is >= 2.7)
if (!method_exists('Symfony\Component\Form\AbstractType', 'configureOptions')) {
$choices = array_flip($choices);
foreach ($choices as $key => $value) {
$choices[$key] = $this->translator->trans($value, [], 'SonataAdminBundle');
}
} else {
$choiceOptions['choice_translation_domain'] = 'SonataAdminBundle';
// NEXT_MAJOR: Remove (when requirement of Symfony is >= 3.0)
if (method_exists('Symfony\Component\Form\FormTypeInterface', 'setDefaultOptions')) {
$choiceOptions['choices_as_values'] = true;
}
}
$choiceOptions['choices'] = $choices;
$builder
// NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
->add('type', method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\ChoiceType'
: 'choice', $choiceOptions)
->add('value', $options['field_type'], $options['field_options'])
;
}
// NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+
/**
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'field_type' => 'sonata_type_date_range',
'field_options' => ['format' => 'yyyy-MM-dd'],
]);
}
}

View File

@@ -0,0 +1,122 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type\Filter;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class DateTimeRangeType extends AbstractType
{
const TYPE_BETWEEN = 1;
const TYPE_NOT_BETWEEN = 2;
/**
* NEXT_MAJOR: remove this property.
*
* @deprecated since 3.5, to be removed with 4.0
*
* @var TranslatorInterface
*/
protected $translator;
/**
* @param TranslatorInterface $translator
*/
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_filter_datetime_range';
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$choices = [
'label_date_type_between' => self::TYPE_BETWEEN,
'label_date_type_not_between' => self::TYPE_NOT_BETWEEN,
];
$choiceOptions = [
'required' => false,
];
// NEXT_MAJOR: Remove (when requirement of Symfony is >= 2.7)
if (!method_exists('Symfony\Component\Form\AbstractType', 'configureOptions')) {
$choices = array_flip($choices);
foreach ($choices as $key => $value) {
$choices[$key] = $this->translator->trans($value, [], 'SonataAdminBundle');
}
} else {
$choiceOptions['choice_translation_domain'] = 'SonataAdminBundle';
// NEXT_MAJOR: Remove (when requirement of Symfony is >= 3.0)
if (method_exists('Symfony\Component\Form\FormTypeInterface', 'setDefaultOptions')) {
$choiceOptions['choices_as_values'] = true;
}
}
$choiceOptions['choices'] = $choices;
$builder
// NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
->add('type', method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\ChoiceType'
: 'choice', $choiceOptions)
->add('value', $options['field_type'], $options['field_options'])
;
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'field_type' => 'sonata_type_datetime_range',
'field_options' => ['date_format' => 'yyyy-MM-dd'],
]);
}
}

View File

@@ -0,0 +1,138 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type\Filter;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class DateTimeType extends AbstractType
{
const TYPE_GREATER_EQUAL = 1;
const TYPE_GREATER_THAN = 2;
const TYPE_EQUAL = 3;
const TYPE_LESS_EQUAL = 4;
const TYPE_LESS_THAN = 5;
const TYPE_NULL = 6;
const TYPE_NOT_NULL = 7;
/**
* NEXT_MAJOR: remove this property.
*
* @deprecated since 3.5, to be removed with 4.0
*
* @var TranslatorInterface
*/
protected $translator;
/**
* @param TranslatorInterface $translator
*/
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_filter_datetime';
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$choices = [
'label_date_type_equal' => self::TYPE_EQUAL,
'label_date_type_greater_equal' => self::TYPE_GREATER_EQUAL,
'label_date_type_greater_than' => self::TYPE_GREATER_THAN,
'label_date_type_less_equal' => self::TYPE_LESS_EQUAL,
'label_date_type_less_than' => self::TYPE_LESS_THAN,
'label_date_type_null' => self::TYPE_NULL,
'label_date_type_not_null' => self::TYPE_NOT_NULL,
];
$choiceOptions = [
'required' => false,
];
// NEXT_MAJOR: Remove (when requirement of Symfony is >= 2.7)
if (!method_exists('Symfony\Component\Form\AbstractType', 'configureOptions')) {
$choices = array_flip($choices);
foreach ($choices as $key => $value) {
$choices[$key] = $this->translator->trans($value, [], 'SonataAdminBundle');
}
} else {
$choiceOptions['choice_translation_domain'] = 'SonataAdminBundle';
// NEXT_MAJOR: Remove (when requirement of Symfony is >= 3.0)
if (method_exists('Symfony\Component\Form\FormTypeInterface', 'setDefaultOptions')) {
$choiceOptions['choices_as_values'] = true;
}
}
$choiceOptions['choices'] = $choices;
$builder
// NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
->add('type', method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\ChoiceType'
: 'choice', $choiceOptions)
->add('value', $options['field_type'], array_merge(['required' => false], $options['field_options']))
;
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'field_type' => 'datetime',
'field_options' => ['date_format' => 'yyyy-MM-dd'],
]);
}
}

View File

@@ -0,0 +1,138 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type\Filter;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Optionsresolver\OptionsResolverInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class DateType extends AbstractType
{
const TYPE_GREATER_EQUAL = 1;
const TYPE_GREATER_THAN = 2;
const TYPE_EQUAL = 3;
const TYPE_LESS_EQUAL = 4;
const TYPE_LESS_THAN = 5;
const TYPE_NULL = 6;
const TYPE_NOT_NULL = 7;
/**
* NEXT_MAJOR: remove this property.
*
* @deprecated since 3.5, to be removed with 4.0
*
* @var TranslatorInterface
*/
protected $translator;
/**
* @param TranslatorInterface $translator
*/
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_filter_date';
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$choices = [
'label_date_type_equal' => self::TYPE_EQUAL,
'label_date_type_greater_equal' => self::TYPE_GREATER_EQUAL,
'label_date_type_greater_than' => self::TYPE_GREATER_THAN,
'label_date_type_less_equal' => self::TYPE_LESS_EQUAL,
'label_date_type_less_than' => self::TYPE_LESS_THAN,
'label_date_type_null' => self::TYPE_NULL,
'label_date_type_not_null' => self::TYPE_NOT_NULL,
];
$choiceOptions = [
'required' => false,
];
// NEXT_MAJOR: Remove (when requirement of Symfony is >= 2.7)
if (!method_exists('Symfony\Component\Form\AbstractType', 'configureOptions')) {
$choices = array_flip($choices);
foreach ($choices as $key => $value) {
$choices[$key] = $this->translator->trans($value, [], 'SonataAdminBundle');
}
} else {
$choiceOptions['choice_translation_domain'] = 'SonataAdminBundle';
// NEXT_MAJOR: Remove (when requirement of Symfony is >= 3.0)
if (method_exists('Symfony\Component\Form\FormTypeInterface', 'setDefaultOptions')) {
$choiceOptions['choices_as_values'] = true;
}
}
$choiceOptions['choices'] = $choices;
$builder
// NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
->add('type', method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\ChoiceType'
: 'choice', $choiceOptions)
->add('value', $options['field_type'], array_merge(['required' => false], $options['field_options']))
;
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'field_type' => 'date',
'field_options' => ['date_format' => 'yyyy-MM-dd'],
]);
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type\Filter;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class DefaultType extends AbstractType
{
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_filter_default';
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('type', $options['operator_type'], array_merge(['required' => false], $options['operator_options']))
->add('value', $options['field_type'], array_merge(['required' => false], $options['field_options']))
;
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'operator_type' => method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\HiddenType'
: 'hidden', // NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
'operator_options' => [],
'field_type' => method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\TextType'
: 'text', // NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
'field_options' => [],
]);
}
}

View File

@@ -0,0 +1,133 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type\Filter;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Translation\TranslatorInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class NumberType extends AbstractType
{
const TYPE_GREATER_EQUAL = 1;
const TYPE_GREATER_THAN = 2;
const TYPE_EQUAL = 3;
const TYPE_LESS_EQUAL = 4;
const TYPE_LESS_THAN = 5;
/**
* NEXT_MAJOR: remove this property.
*
* @deprecated since 3.5, to be removed with 4.0
*
* @var TranslatorInterface
*/
protected $translator;
/**
* @param TranslatorInterface $translator
*/
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_filter_number';
}
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$choices = [
'label_type_equal' => self::TYPE_EQUAL,
'label_type_greater_equal' => self::TYPE_GREATER_EQUAL,
'label_type_greater_than' => self::TYPE_GREATER_THAN,
'label_type_less_equal' => self::TYPE_LESS_EQUAL,
'label_type_less_than' => self::TYPE_LESS_THAN,
];
$choiceOptions = [
'required' => false,
];
// NEXT_MAJOR: Remove (when requirement of Symfony is >= 2.7)
if (!method_exists('Symfony\Component\Form\AbstractType', 'configureOptions')) {
$choices = array_flip($choices);
foreach ($choices as $key => $value) {
$choices[$key] = $this->translator->trans($value, [], 'SonataAdminBundle');
}
} else {
$choiceOptions['choice_translation_domain'] = 'SonataAdminBundle';
// NEXT_MAJOR: Remove (when requirement of Symfony is >= 3.0)
if (method_exists('Symfony\Component\Form\FormTypeInterface', 'setDefaultOptions')) {
$choiceOptions['choices_as_values'] = true;
}
}
$choiceOptions['choices'] = $choices;
$builder
// NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
->add('type', method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\ChoiceType'
: 'choice', $choiceOptions)
->add('value', $options['field_type'], array_merge(['required' => false], $options['field_options']))
;
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'field_type' => 'number',
'field_options' => [],
]);
}
}

View File

@@ -0,0 +1,183 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type;
use Sonata\AdminBundle\Form\DataTransformer\ModelToIdPropertyTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\EventListener\ResizeFormListener;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* This type defines a standard text field with autocomplete feature.
*
* @author Andrej Hudec <pulzarraider@gmail.com>
* @author Florent Denis <dflorent.pokap@gmail.com>
*/
class ModelAutocompleteType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addViewTransformer(new ModelToIdPropertyTransformer($options['model_manager'], $options['class'], $options['property'], $options['multiple'], $options['to_string_callback']), true);
$builder->setAttribute('property', $options['property']);
$builder->setAttribute('callback', $options['callback']);
$builder->setAttribute('minimum_input_length', $options['minimum_input_length']);
$builder->setAttribute('items_per_page', $options['items_per_page']);
$builder->setAttribute('req_param_name_page_number', $options['req_param_name_page_number']);
$builder->setAttribute(
'disabled',
$options['disabled']
// NEXT_MAJOR: Remove this when bumping Symfony constraint to 2.8+
|| (array_key_exists('read_only', $options) && $options['read_only'])
);
$builder->setAttribute('to_string_callback', $options['to_string_callback']);
$builder->setAttribute('target_admin_access_action', $options['target_admin_access_action']);
if ($options['multiple']) {
$resizeListener = new ResizeFormListener(
// NEXT_MAJOR: Remove ternary and keep 'Symfony\Component\Form\Extension\Core\Type\HiddenType'
// (when requirement of Symfony is >= 2.8)
method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\HiddenType'
: 'hidden',
[],
true,
true,
true
);
$builder->addEventSubscriber($resizeListener);
}
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['admin_code'] = $options['admin_code'];
$view->vars['placeholder'] = $options['placeholder'];
$view->vars['multiple'] = $options['multiple'];
$view->vars['minimum_input_length'] = $options['minimum_input_length'];
$view->vars['items_per_page'] = $options['items_per_page'];
$view->vars['width'] = $options['width'];
// ajax parameters
$view->vars['url'] = $options['url'];
$view->vars['route'] = $options['route'];
$view->vars['req_params'] = $options['req_params'];
$view->vars['req_param_name_search'] = $options['req_param_name_search'];
$view->vars['req_param_name_page_number'] = $options['req_param_name_page_number'];
$view->vars['req_param_name_items_per_page'] = $options['req_param_name_items_per_page'];
$view->vars['quiet_millis'] = $options['quiet_millis'];
$view->vars['cache'] = $options['cache'];
// CSS classes
$view->vars['container_css_class'] = $options['container_css_class'];
$view->vars['dropdown_css_class'] = $options['dropdown_css_class'];
$view->vars['dropdown_item_css_class'] = $options['dropdown_item_css_class'];
$view->vars['dropdown_auto_width'] = $options['dropdown_auto_width'];
// template
$view->vars['template'] = $options['template'];
$view->vars['context'] = $options['context'];
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$compound = function (Options $options) {
return $options['multiple'];
};
$resolver->setDefaults([
'attr' => [],
'compound' => $compound,
'model_manager' => null,
'class' => null,
'admin_code' => null,
'callback' => null,
'multiple' => false,
'width' => '',
'context' => '',
'placeholder' => '',
'minimum_input_length' => 3, //minimum 3 chars should be typed to load ajax data
'items_per_page' => 10, //number of items per page
'quiet_millis' => 100,
'cache' => false,
'to_string_callback' => null,
// ajax parameters
'url' => '',
'route' => ['name' => 'sonata_admin_retrieve_autocomplete_items', 'parameters' => []],
'req_params' => [],
'req_param_name_search' => 'q',
'req_param_name_page_number' => '_page',
'req_param_name_items_per_page' => '_per_page',
// security
'target_admin_access_action' => 'list',
// CSS classes
'container_css_class' => '',
'dropdown_css_class' => '',
'dropdown_item_css_class' => '',
'dropdown_auto_width' => false,
'template' => 'SonataAdminBundle:Form/Type:sonata_type_model_autocomplete.html.twig',
]);
$resolver->setRequired(['property']);
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_model_autocomplete';
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
}

View File

@@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type;
use Sonata\AdminBundle\Form\DataTransformer\ModelToIdTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* This type define a standard hidden field, that stored id to a object.
*
* @author Andrej Hudec <pulzarraider@gmail.com>
*/
class ModelHiddenType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->addViewTransformer(new ModelToIdTransformer($options['model_manager'], $options['class']), true)
;
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'model_manager' => null,
'class' => null,
]);
}
/**
* {@inheritdoc}
*/
public function getParent()
{
// NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
return method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\HiddenType'
: 'hidden';
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_model_hidden';
}
}

View File

@@ -0,0 +1,122 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type;
use Sonata\AdminBundle\Form\DataTransformer\ModelToIdTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* This type can be used to select one associated model from a list.
*
* The associated model must be in a single-valued association relationship (e.g many-to-one)
* with the model currently edited in the parent form.
* The associated model must have an admin class registered.
*
* The selected model's identifier is rendered in an hidden input.
*
* When a model is selected, a short description is displayed by the widget.
* This description can be customized by overriding the associated admin's
* `short_object_description` template and/or overriding it's `toString` method.
*
* The widget also provides three action buttons:
* - a button to open the associated admin list view in a dialog,
* in order to select an associated model.
* - a button to open the associated admin create form in a dialog,
* in order to create and select an associated model.
* - a button to unlink the associated model, if any.
*/
class ModelListType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->resetViewTransformers()
->addViewTransformer(new ModelToIdTransformer($options['model_manager'], $options['class']));
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
if (isset($view->vars['sonata_admin'])) {
// set the correct edit mode
$view->vars['sonata_admin']['edit'] = 'list';
}
$view->vars['btn_add'] = $options['btn_add'];
$view->vars['btn_list'] = $options['btn_list'];
$view->vars['btn_delete'] = $options['btn_delete'];
$view->vars['btn_catalogue'] = $options['btn_catalogue'];
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'model_manager' => null,
'class' => null,
'btn_add' => 'link_add',
'btn_list' => 'link_list',
'btn_delete' => 'link_delete',
'btn_catalogue' => 'SonataAdminBundle',
]);
}
/**
* {@inheritdoc}
*/
public function getParent()
{
// NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
return method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\TextType'
: 'text';
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_model_list';
}
}

View File

@@ -0,0 +1,83 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type;
use Sonata\AdminBundle\Form\DataTransformer\ModelToIdTransformer;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class ModelReferenceType extends AbstractType
{
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new ModelToIdTransformer($options['model_manager'], $options['class']));
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'compound' => false,
'model_manager' => null,
'class' => null,
]);
}
/**
* {@inheritdoc}
*/
public function getParent()
{
// NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
return method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\TextType'
: 'text';
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_model_reference';
}
}

View File

@@ -0,0 +1,199 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type;
use Sonata\AdminBundle\Form\ChoiceList\ModelChoiceList;
use Sonata\AdminBundle\Form\ChoiceList\ModelChoiceLoader;
use Sonata\AdminBundle\Form\DataTransformer\LegacyModelsToArrayTransformer;
use Sonata\AdminBundle\Form\DataTransformer\ModelsToArrayTransformer;
use Sonata\AdminBundle\Form\DataTransformer\ModelToIdTransformer;
use Sonata\AdminBundle\Form\EventListener\MergeCollectionListener;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* This type define a standard select input with a + sign to add new associated object.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class ModelType extends AbstractType
{
/**
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
public function __construct(PropertyAccessorInterface $propertyAccessor)
{
$this->propertyAccessor = $propertyAccessor;
}
/**
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
if ($options['multiple']) {
if (array_key_exists('choice_loader', $options) && $options['choice_loader'] !== null) { // SF2.7+
$builder->addViewTransformer(new ModelsToArrayTransformer(
$options['model_manager'],
$options['class']), true);
} else {
$builder->addViewTransformer(new LegacyModelsToArrayTransformer($options['choice_list']), true);
}
$builder
->addEventSubscriber(new MergeCollectionListener($options['model_manager']))
;
} else {
$builder
->addViewTransformer(new ModelToIdTransformer($options['model_manager'], $options['class']), true)
;
}
}
/**
* {@inheritdoc}
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['btn_add'] = $options['btn_add'];
$view->vars['btn_list'] = $options['btn_list'];
$view->vars['btn_delete'] = $options['btn_delete'];
$view->vars['btn_catalogue'] = $options['btn_catalogue'];
}
/**
* NEXT_MAJOR: Remove method, when bumping requirements to SF 2.7+.
*
* {@inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$this->configureOptions($resolver);
}
/**
* {@inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$options = [];
$propertyAccessor = $this->propertyAccessor;
if (interface_exists('Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface')) { // SF2.7+
$options['choice_loader'] = function (Options $options, $previousValue) use ($propertyAccessor) {
if ($previousValue && count($choices = $previousValue->getChoices())) {
return $choices;
}
return new ModelChoiceLoader(
$options['model_manager'],
$options['class'],
$options['property'],
$options['query'],
$options['choices'],
$propertyAccessor
);
};
// NEXT_MAJOR: Remove this when dropping support for SF 2.8
if (method_exists('Symfony\Component\Form\FormTypeInterface', 'setDefaultOptions')) {
$options['choices_as_values'] = true;
}
} else {
$options['choice_list'] = function (Options $options, $previousValue) use ($propertyAccessor) {
if ($previousValue && count($choices = $previousValue->getChoices())) {
return $choices;
}
return new ModelChoiceList(
$options['model_manager'],
$options['class'],
$options['property'],
$options['query'],
$options['choices'],
$propertyAccessor
);
};
}
$resolver->setDefaults(array_merge($options, [
'compound' => function (Options $options) {
if (isset($options['multiple']) && $options['multiple']) {
if (isset($options['expanded']) && $options['expanded']) {
//checkboxes
return true;
}
//select tag (with multiple attribute)
return false;
}
if (isset($options['expanded']) && $options['expanded']) {
//radio buttons
return true;
}
//select tag
return false;
},
'template' => 'choice',
'multiple' => false,
'expanded' => false,
'model_manager' => null,
'class' => null,
'property' => null,
'query' => null,
'choices' => [],
'preferred_choices' => [],
'btn_add' => 'link_add',
'btn_list' => 'link_list',
'btn_delete' => 'link_delete',
'btn_catalogue' => 'SonataAdminBundle',
]));
}
/**
* {@inheritdoc}
*/
public function getParent()
{
// NEXT_MAJOR: Remove ternary (when requirement of Symfony is >= 2.8)
return method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\ChoiceType'
: 'choice';
}
/**
* NEXT_MAJOR: Remove when dropping Symfony <2.8 support.
*
* {@inheritdoc}
*/
public function getName()
{
return $this->getBlockPrefix();
}
/**
* {@inheritdoc}
*/
public function getBlockPrefix()
{
return 'sonata_type_model';
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Sonata Project package.
*
* (c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Sonata\AdminBundle\Form\Type;
@trigger_error(
'The '.__NAMESPACE__.'\ModelTypeList class is deprecated since version 3.5 and will be removed in 4.0.'
.' Use '.__NAMESPACE__.'\ModelListType instead.',
E_USER_DEPRECATED
);
/**
* This type is used to render an hidden input text and 3 links
* - an add form modal
* - a list modal to select the targeted entities
* - a clear selection link.
*
* NEXT_MAJOR: remove this class.
*
* @deprecated since version 3.5, to be removed in 4.0. Use ModelListType instead
*/
class ModelTypeList extends ModelListType
{
}