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

File diff suppressed because it is too large Load Diff

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\Admin;
use Knp\Menu\ItemInterface as MenuItemInterface;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Route\RouteCollection;
use Sonata\AdminBundle\Show\ShowMapper;
use Sonata\CoreBundle\Validator\ErrorElement;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
abstract class AbstractAdminExtension implements AdminExtensionInterface
{
/**
* {@inheritdoc}
*/
public function configureFormFields(FormMapper $formMapper)
{
}
/**
* {@inheritdoc}
*/
public function configureListFields(ListMapper $listMapper)
{
}
/**
* {@inheritdoc}
*/
public function configureDatagridFilters(DatagridMapper $datagridMapper)
{
}
/**
* {@inheritdoc}
*/
public function configureShowFields(ShowMapper $showMapper)
{
}
/**
* {@inheritdoc}
*/
public function configureRoutes(AdminInterface $admin, RouteCollection $collection)
{
}
/**
* {@inheritdoc}
*/
public function configureSideMenu(AdminInterface $admin, MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
{
}
/**
* {@inheritdoc}
*/
public function configureTabMenu(AdminInterface $admin, MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
{
// Use configureSideMenu not to mess with previous overrides
// TODO remove once deprecation period is over
$this->configureSideMenu($admin, $menu, $action, $childAdmin);
}
/**
* {@inheritdoc}
*/
public function validate(AdminInterface $admin, ErrorElement $errorElement, $object)
{
}
/**
* {@inheritdoc}
*/
public function configureQuery(AdminInterface $admin, ProxyQueryInterface $query, $context = 'list')
{
}
/**
* {@inheritdoc}
*/
public function alterNewInstance(AdminInterface $admin, $object)
{
}
/**
* {@inheritdoc}
*/
public function alterObject(AdminInterface $admin, $object)
{
}
/**
* {@inheritdoc}
*/
public function getPersistentParameters(AdminInterface $admin)
{
return [];
}
/**
* {@inheritdoc}
*/
public function getAccessMapping(AdminInterface $admin)
{
return [];
}
/**
* {@inheritdoc}
*/
public function configureBatchActions(AdminInterface $admin, array $actions)
{
return $actions;
}
/**
* {@inheritdoc}
*/
public function configureExportFields(AdminInterface $admin, array $fields)
{
return $fields;
}
/**
* {@inheritdoc}
*/
public function preUpdate(AdminInterface $admin, $object)
{
}
/**
* {@inheritdoc}
*/
public function postUpdate(AdminInterface $admin, $object)
{
}
/**
* {@inheritdoc}
*/
public function prePersist(AdminInterface $admin, $object)
{
}
/**
* {@inheritdoc}
*/
public function postPersist(AdminInterface $admin, $object)
{
}
/**
* {@inheritdoc}
*/
public function preRemove(AdminInterface $admin, $object)
{
}
/**
* {@inheritdoc}
*/
public function postRemove(AdminInterface $admin, $object)
{
}
/**
* {@inheritdoc}
*/
public function configureActionButtons(AdminInterface $admin, $list, $action, $object)
{
return $list;
}
/**
* Returns a list of default filters.
*
* @param AdminInterface $admin
* @param array $filterValues
*/
public function configureDefaultFilterValues(AdminInterface $admin, array &$filterValues)
{
}
}

View File

@@ -0,0 +1,46 @@
<?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\Admin;
/**
* Tells if the current user has access to a given action.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
interface AccessRegistryInterface
{
/**
* Return the controller access mapping.
*
* @return array
*/
public function getAccessMapping();
/**
* Hook to handle access authorization.
*
* @param string $action
* @param object $object
*/
public function checkAccess($action, $object = null);
/*
* Hook to handle access authorization, without throwing an exception.
*
* @param string $action
* @param object $object
*
* @return bool
* TODO: uncomment this method for next major release
*/
// public function hasAccess($action, $object = null);
}

View File

@@ -0,0 +1,29 @@
<?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\Admin;
@trigger_error(
'The '.__NAMESPACE__.'\Admin class is deprecated since version 3.1 and will be removed in 4.0.'
.' Use '.__NAMESPACE__.'\AbstractAdmin instead.',
E_USER_DEPRECATED
);
/**
* NEXT_MAJOR: remove this class.
*
* @deprecated since version 3.1, to be removed in 4.0. Use Sonata\AdminBundle\AbstractAdmin instead
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
abstract class Admin extends AbstractAdmin
{
}

View File

@@ -0,0 +1,29 @@
<?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\Admin;
@trigger_error(
'The '.__NAMESPACE__.'\AdminExtension class is deprecated since version 3.1 and will be removed in 4.0.'
.' Use '.__NAMESPACE__.'\AbstractAdminExtension instead.',
E_USER_DEPRECATED
);
/**
* NEXT_MAJOR: remove this class.
*
* @deprecated since version 3.1, to be removed in 4.0. Use Sonata\AdminBundle\AbstractAdminExtension instead
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
abstract class AdminExtension extends AbstractAdminExtension
{
}

View File

@@ -0,0 +1,207 @@
<?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\Admin;
use Knp\Menu\ItemInterface as MenuItemInterface;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Route\RouteCollection;
use Sonata\AdminBundle\Show\ShowMapper;
use Sonata\CoreBundle\Validator\ErrorElement;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
interface AdminExtensionInterface
{
/**
* @param FormMapper $formMapper
*/
public function configureFormFields(FormMapper $formMapper);
/**
* @param ListMapper $listMapper
*/
public function configureListFields(ListMapper $listMapper);
/**
* @param DatagridMapper $datagridMapper
*/
public function configureDatagridFilters(DatagridMapper $datagridMapper);
/**
* @param ShowMapper $showMapper
*/
public function configureShowFields(ShowMapper $showMapper);
/**
* @param AdminInterface $admin
* @param RouteCollection $collection
*/
public function configureRoutes(AdminInterface $admin, RouteCollection $collection);
/**
* DEPRECATED: Use configureTabMenu instead.
*
* NEXT_MAJOR: remove this method.
*
* @param AdminInterface $admin
* @param MenuItemInterface $menu
* @param string $action
* @param AdminInterface $childAdmin
*
* @deprecated
*/
public function configureSideMenu(AdminInterface $admin, MenuItemInterface $menu, $action, AdminInterface $childAdmin = null);
/**
* Builds the tab menu.
*
* @param AdminInterface $admin
* @param MenuItemInterface $menu
* @param string $action
* @param AdminInterface $childAdmin
*/
public function configureTabMenu(AdminInterface $admin, MenuItemInterface $menu, $action, AdminInterface $childAdmin = null);
/**
* @param AdminInterface $admin
* @param ErrorElement $errorElement
* @param mixed $object
*/
public function validate(AdminInterface $admin, ErrorElement $errorElement, $object);
/**
* @param AdminInterface $admin
* @param ProxyQueryInterface $query
* @param string $context
*/
public function configureQuery(AdminInterface $admin, ProxyQueryInterface $query, $context = 'list');
/**
* Get a chance to modify a newly created instance.
*
* @param AdminInterface $admin
* @param mixed $object
*/
public function alterNewInstance(AdminInterface $admin, $object);
/**
* Get a chance to modify object instance.
*
* @param AdminInterface $admin
* @param mixed $object
*/
public function alterObject(AdminInterface $admin, $object);
/**
* Get a chance to add persistent parameters.
*
* @param AdminInterface $admin
*
* @return array
*/
public function getPersistentParameters(AdminInterface $admin);
/**
* Return the controller access mapping.
*
* @param AdminInterface $admin
*
* @return array
*/
// TODO: Uncomment in next major release
// public function getAccessMapping(AdminInterface $admin);
/**
* Returns the list of batch actions.
*
* @param AdminInterface $admin
* @param array $actions
*
* @return array
*/
// TODO: Uncomment in next major release
// public function configureBatchActions(AdminInterface $admin, array $actions);
/**
* Get a chance to modify export fields.
*
* @param AdminInterface $admin
* @param string[] $fields
*
* @return string[]
*/
// TODO: Uncomment in next major release
// public function configureExportFields(AdminInterface $admin, array $fields);
/**
* @param AdminInterface $admin
* @param mixed $object
*/
public function preUpdate(AdminInterface $admin, $object);
/**
* @param AdminInterface $admin
* @param mixed $object
*/
public function postUpdate(AdminInterface $admin, $object);
/**
* @param AdminInterface $admin
* @param mixed $object
*/
public function prePersist(AdminInterface $admin, $object);
/**
* @param AdminInterface $admin
* @param mixed $object
*/
public function postPersist(AdminInterface $admin, $object);
/**
* @param AdminInterface $admin
* @param mixed $object
*/
public function preRemove(AdminInterface $admin, $object);
/**
* @param AdminInterface $admin
* @param mixed $object
*/
public function postRemove(AdminInterface $admin, $object);
/*
* Get all action buttons for an action
*
* @param AdminInterface $admin
* @param array $list
* @param string $action
* @param mixed $object
*
* @return array
*/
// TODO: Uncomment in next major release
// public function configureActionButtons(AdminInterface $admin, $list, $action, $object);
/*
* NEXT_MAJOR: Uncomment in next major release
*
* Returns a list of default filters
*
* @param AdminInterface $admin
* @param array $filterValues
*/
// public function configureDefaultFilterValues(AdminInterface $admin, array &$filterValues);
}

View File

@@ -0,0 +1,338 @@
<?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\Admin;
use Doctrine\Common\Inflector\Inflector;
use Doctrine\Common\Util\ClassUtils;
use Sonata\AdminBundle\Exception\NoValueException;
use Sonata\AdminBundle\Util\FormBuilderIterator;
use Sonata\AdminBundle\Util\FormViewIterator;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView;
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
use Symfony\Component\PropertyAccess\Exception\UnexpectedTypeException;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class AdminHelper
{
/**
* @var Pool
*/
protected $pool;
/**
* @param Pool $pool
*/
public function __construct(Pool $pool)
{
$this->pool = $pool;
}
/**
* @throws \RuntimeException
*
* @param FormBuilderInterface $formBuilder
* @param string $elementId
*
* @return FormBuilderInterface|null
*/
public function getChildFormBuilder(FormBuilderInterface $formBuilder, $elementId)
{
foreach (new FormBuilderIterator($formBuilder) as $name => $formBuilder) {
if ($name == $elementId) {
return $formBuilder;
}
}
return;
}
/**
* @param FormView $formView
* @param string $elementId
*
* @return null|FormView
*/
public function getChildFormView(FormView $formView, $elementId)
{
foreach (new \RecursiveIteratorIterator(new FormViewIterator($formView), \RecursiveIteratorIterator::SELF_FIRST) as $name => $formView) {
if ($name === $elementId) {
return $formView;
}
}
return;
}
/**
* NEXT_MAJOR: remove this method.
*
* @deprecated
*
* @param string $code
*
* @return AdminInterface
*/
public function getAdmin($code)
{
return $this->pool->getInstance($code);
}
/**
* Note:
* This code is ugly, but there is no better way of doing it.
* For now the append form element action used to add a new row works
* only for direct FieldDescription (not nested one).
*
* @throws \RuntimeException
*
* @param AdminInterface $admin
* @param object $subject
* @param string $elementId
*
* @return array
*
* @throws \Exception
*/
public function appendFormFieldElement(AdminInterface $admin, $subject, $elementId)
{
// retrieve the subject
$formBuilder = $admin->getFormBuilder();
$form = $formBuilder->getForm();
$form->setData($subject);
$form->handleRequest($admin->getRequest());
// get the field element
$childFormBuilder = $this->getChildFormBuilder($formBuilder, $elementId);
//Child form not found (probably nested one)
//if childFormBuilder was not found resulted in fatal error getName() method call on non object
if (!$childFormBuilder) {
$propertyAccessor = $this->pool->getPropertyAccessor();
$entity = $admin->getSubject();
$path = $this->getElementAccessPath($elementId, $entity);
$collection = $propertyAccessor->getValue($entity, $path);
if ($collection instanceof \Doctrine\ORM\PersistentCollection || $collection instanceof \Doctrine\ODM\MongoDB\PersistentCollection) {
//since doctrine 2.4
$entityClassName = $collection->getTypeClass()->getName();
} elseif ($collection instanceof \Doctrine\Common\Collections\Collection) {
$entityClassName = $this->getEntityClassName($admin, explode('.', preg_replace('#\[\d*?\]#', '', $path)));
} else {
throw new \Exception('unknown collection class');
}
$collection->add(new $entityClassName());
$propertyAccessor->setValue($entity, $path, $collection);
$fieldDescription = null;
} else {
// retrieve the FieldDescription
$fieldDescription = $admin->getFormFieldDescription($childFormBuilder->getName());
try {
$value = $fieldDescription->getValue($form->getData());
} catch (NoValueException $e) {
$value = null;
}
// retrieve the posted data
$data = $admin->getRequest()->get($formBuilder->getName());
if (!isset($data[$childFormBuilder->getName()])) {
$data[$childFormBuilder->getName()] = [];
}
$objectCount = count($value);
$postCount = count($data[$childFormBuilder->getName()]);
$fields = array_keys($fieldDescription->getAssociationAdmin()->getFormFieldDescriptions());
// for now, not sure how to do that
$value = [];
foreach ($fields as $name) {
$value[$name] = '';
}
// add new elements to the subject
while ($objectCount < $postCount) {
// append a new instance into the object
$this->addNewInstance($form->getData(), $fieldDescription);
++$objectCount;
}
$this->addNewInstance($form->getData(), $fieldDescription);
}
$finalForm = $admin->getFormBuilder()->getForm();
$finalForm->setData($subject);
// bind the data
$finalForm->setData($form->getData());
return [$fieldDescription, $finalForm];
}
/**
* Add a new instance to the related FieldDescriptionInterface value.
*
* @param object $object
* @param FieldDescriptionInterface $fieldDescription
*
* @throws \RuntimeException
*/
public function addNewInstance($object, FieldDescriptionInterface $fieldDescription)
{
$instance = $fieldDescription->getAssociationAdmin()->getNewInstance();
$mapping = $fieldDescription->getAssociationMapping();
$method = sprintf('add%s', Inflector::classify($mapping['fieldName']));
if (!method_exists($object, $method)) {
$method = rtrim($method, 's');
if (!method_exists($object, $method)) {
$method = sprintf('add%s', Inflector::classify(Inflector::singularize($mapping['fieldName'])));
if (!method_exists($object, $method)) {
throw new \RuntimeException(
sprintf('Please add a method %s in the %s class!', $method, ClassUtils::getClass($object))
);
}
}
}
$object->$method($instance);
}
/**
* Camelize a string.
*
* NEXT_MAJOR: remove this method.
*
* @static
*
* @param string $property
*
* @return string
*
* @deprecated Deprecated since version 3.1. Use \Doctrine\Common\Inflector\Inflector::classify() instead
*/
public function camelize($property)
{
@trigger_error(
sprintf(
'The %s method is deprecated since 3.1 and will be removed in 4.0. '.
'Use \Doctrine\Common\Inflector\Inflector::classify() instead.',
__METHOD__
),
E_USER_DEPRECATED
);
return Inflector::classify($property);
}
/**
* Get access path to element which works with PropertyAccessor.
*
* @param string $elementId expects string in format used in form id field.
* (uniqueIdentifier_model_sub_model or uniqueIdentifier_model_1_sub_model etc.)
* @param mixed $entity
*
* @return string
*
* @throws \Exception
*/
public function getElementAccessPath($elementId, $entity)
{
$propertyAccessor = $this->pool->getPropertyAccessor();
$idWithoutIdentifier = preg_replace('/^[^_]*_/', '', $elementId);
$initialPath = preg_replace('#(_(\d+)_)#', '[$2]_', $idWithoutIdentifier);
$parts = explode('_', $initialPath);
$totalPath = '';
$currentPath = '';
foreach ($parts as $part) {
$currentPath .= empty($currentPath) ? $part : '_'.$part;
$separator = empty($totalPath) ? '' : '.';
if ($this->pathExists($propertyAccessor, $entity, $totalPath.$separator.$currentPath)) {
$totalPath .= $separator.$currentPath;
$currentPath = '';
}
}
if (!empty($currentPath)) {
throw new \Exception(
sprintf('Could not get element id from %s Failing part: %s', $elementId, $currentPath)
);
}
return $totalPath;
}
/**
* Recursively find the class name of the admin responsible for the element at the end of an association chain.
*
* @param AdminInterface $admin
* @param array $elements
*
* @return string
*/
protected function getEntityClassName(AdminInterface $admin, $elements)
{
$element = array_shift($elements);
$associationAdmin = $admin->getFormFieldDescription($element)->getAssociationAdmin();
if (count($elements) == 0) {
return $associationAdmin->getClass();
}
return $this->getEntityClassName($associationAdmin, $elements);
}
/**
* Check if given path exists in $entity.
*
* @param PropertyAccessorInterface $propertyAccessor
* @param mixed $entity
* @param string $path
*
* @return bool
*
* @throws \RuntimeException
*/
private function pathExists(PropertyAccessorInterface $propertyAccessor, $entity, $path)
{
// Symfony <= 2.3 did not have isReadable method for PropertyAccessor
if (method_exists($propertyAccessor, 'isReadable')) {
return $propertyAccessor->isReadable($entity, $path);
}
try {
$propertyAccessor->getValue($entity, $path);
return true;
} catch (NoSuchPropertyException $e) {
return false;
} catch (UnexpectedTypeException $e) {
return false;
}
}
}

View File

@@ -0,0 +1,775 @@
<?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\Admin;
use Knp\Menu\FactoryInterface as MenuFactoryInterface;
use Sonata\AdminBundle\Builder\DatagridBuilderInterface;
use Sonata\AdminBundle\Builder\FormContractorInterface;
use Sonata\AdminBundle\Builder\ListBuilderInterface;
use Sonata\AdminBundle\Builder\RouteBuilderInterface;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Sonata\AdminBundle\Security\Handler\SecurityHandlerInterface;
use Sonata\AdminBundle\Translator\LabelTranslatorStrategyInterface;
use Sonata\CoreBundle\Model\Metadata;
use Sonata\CoreBundle\Validator\ErrorElement;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Validator\ValidatorInterface as LegacyValidatorInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
interface AdminInterface extends AccessRegistryInterface, FieldDescriptionRegistryInterface, LifecycleHookProviderInterface, MenuBuilderInterface, ParentAdminInterface, UrlGeneratorInterface
{
/**
* @param MenuFactoryInterface $menuFactory
*/
public function setMenuFactory(MenuFactoryInterface $menuFactory);
/**
* @return MenuFactoryInterface
*/
public function getMenuFactory();
/**
* @param FormContractorInterface $formContractor
*/
public function setFormContractor(FormContractorInterface $formContractor);
/**
* Set ListBuilder.
*
* @param ListBuilderInterface $listBuilder
*/
public function setListBuilder(ListBuilderInterface $listBuilder);
/**
* Get ListBuilder.
*
* @return ListBuilderInterface
*/
public function getListBuilder();
/**
* Set DatagridBuilder.
*
* @param DatagridBuilderInterface $datagridBuilder
*/
public function setDatagridBuilder(DatagridBuilderInterface $datagridBuilder);
/**
* Get DatagridBuilder.
*
* @return DatagridBuilderInterface
*/
public function getDatagridBuilder();
/**
* Set translator.
*
* @param TranslatorInterface $translator
*/
public function setTranslator(TranslatorInterface $translator);
/**
* Get translator.
*
* @return TranslatorInterface
*/
public function getTranslator();
/**
* @param Request $request
*/
public function setRequest(Request $request);
/**
* @param Pool $pool
*/
public function setConfigurationPool(Pool $pool);
/**
* Returns subjectClass/class/subclass name managed
* - subclass name if subclass parameter is defined
* - subject class name if subject is defined
* - class name if not.
*
* @return string
*/
public function getClass();
/**
* @param \Sonata\AdminBundle\Admin\FieldDescriptionInterface $fieldDescription
*/
public function attachAdminClass(FieldDescriptionInterface $fieldDescription);
/**
* @return \Sonata\AdminBundle\Datagrid\DatagridInterface
*/
public function getDatagrid();
/**
* Set base controller name.
*
* @param string $baseControllerName
*/
public function setBaseControllerName($baseControllerName);
/**
* Get base controller name.
*
* @return string
*/
public function getBaseControllerName();
/**
* @return \Sonata\AdminBundle\Model\ModelManagerInterface
*/
public function getModelManager();
/**
* @return string the manager type of the admin
*/
public function getManagerType();
/**
* @param string $context NEXT_MAJOR: remove this argument
*
* @return ProxyQueryInterface
*/
public function createQuery($context = 'list');
/**
* @return FormBuilderInterface the form builder
*/
public function getFormBuilder();
/**
* Returns a form depend on the given $object.
*
* @return Form
*/
public function getForm();
/**
* @return Request
*
* @throws \RuntimeException if no request is set
*/
public function getRequest();
/**
* @return bool true if a request object is linked to this Admin, false
* otherwise
*/
public function hasRequest();
/**
* @return string
*/
public function getCode();
/**
* @return string
*/
public function getBaseCodeRoute();
/**
* Return the roles and permissions per role
* - different permissions per role for the acl handler
* - one permission that has the same name as the role for the role handler
* This should be used by experimented users.
*
* @return array [role] => array([permission], [permission])
*/
public function getSecurityInformation();
/**
* @param FieldDescriptionInterface $parentFieldDescription
*/
public function setParentFieldDescription(FieldDescriptionInterface $parentFieldDescription);
/**
* Get parent field description.
*
* @return FieldDescriptionInterface The parent field description
*/
public function getParentFieldDescription();
/**
* Returns true if the Admin is linked to a parent FieldDescription.
*
* @return bool
*/
public function hasParentFieldDescription();
/**
* translate a message id.
*
* NEXT_MAJOR: remove this method
*
* @param string $id
* @param array $parameters
* @param null $domain
* @param null $locale
*
* @return string the translated string
*
* @deprecated since 3.9, to be removed in 4.0
*/
public function trans($id, array $parameters = [], $domain = null, $locale = null);
/**
* Returns the parameter representing request id, ie: id or childId.
*
* @return string
*/
public function getIdParameter();
/**
* Returns true if the route $name is available.
*
* @param string $name
*
* @return bool
*/
public function hasRoute($name);
/**
* Check the current request is given route or not.
*
* TODO: uncomment this method before releasing 4.0
*
* ```
* $this->isCurrentRoute('create'); // is create page?
* $this->isCurrentRoute('edit', 'some.admin.code'); // is some.admin.code admin's edit page?
* ```
*
* @param string $name
* @param string $adminCode
*
* @return bool
*/
// public function isCurrentRoute($name, $adminCode = null);
/**
* @param SecurityHandlerInterface $securityHandler
*/
public function setSecurityHandler(SecurityHandlerInterface $securityHandler);
/**
* @return SecurityHandlerInterface|null
*/
public function getSecurityHandler();
/**
* @param string $name
* @param object|null $object
*
* @return bool
*/
public function isGranted($name, $object = null);
/**
* @param mixed $entity
*
* @return string a string representation of the identifiers for this instance
*/
public function getNormalizedIdentifier($entity);
/**
* Shorthand method for templating.
*
* @param object $entity
*
* @return mixed
*/
public function id($entity);
/**
* @param ValidatorInterface|LegacyValidatorInterface $validator
*/
public function setValidator($validator);
/**
* @return ValidatorInterface|LegacyValidatorInterface
*/
public function getValidator();
/**
* @return array
*/
public function getShow();
/**
* @param array $formTheme
*/
public function setFormTheme(array $formTheme);
/**
* @return array
*/
public function getFormTheme();
/**
* @param array $filterTheme
*/
public function setFilterTheme(array $filterTheme);
/**
* @return array
*/
public function getFilterTheme();
/**
* @param AdminExtensionInterface $extension
*/
public function addExtension(AdminExtensionInterface $extension);
/**
* Returns an array of extension related to the current Admin.
*
* @return AdminExtensionInterface[]
*/
public function getExtensions();
/**
* @param RouteBuilderInterface $routeBuilder
*/
public function setRouteBuilder(RouteBuilderInterface $routeBuilder);
/**
* @return RouteBuilderInterface
*/
public function getRouteBuilder();
/**
* @param mixed $object
*
* @return string
*/
public function toString($object);
/**
* @param LabelTranslatorStrategyInterface $labelTranslatorStrategy
*/
public function setLabelTranslatorStrategy(LabelTranslatorStrategyInterface $labelTranslatorStrategy);
/**
* @return LabelTranslatorStrategyInterface
*/
public function getLabelTranslatorStrategy();
/**
* Returning true will enable preview mode for
* the target entity and show a preview button
* when editing/creating an entity.
*
* @return bool
*/
public function supportsPreviewMode();
/**
* @return mixed a new object instance
*/
public function getNewInstance();
/**
* @param string $uniqId
*/
public function setUniqid($uniqId);
/**
* Returns the uniqid.
*
* @return int
*/
public function getUniqid();
/**
* @param mixed $id
*
* @return mixed
*/
public function getObject($id);
/**
* @param object $subject
*/
public function setSubject($subject);
/**
* @return mixed
*/
public function getSubject();
/**
* Returns a list FieldDescription.
*
* @param string $name
*
* @return FieldDescriptionInterface
*/
public function getListFieldDescription($name);
/**
* Returns true if the list FieldDescription exists.
*
* @param string $name
*
* @return bool
*/
public function hasListFieldDescription($name);
/**
* Returns the collection of list FieldDescriptions.
*
* @return array
*/
public function getListFieldDescriptions();
/**
* Returns the array of allowed export formats.
*
* @return array
*/
public function getExportFormats();
/**
* Returns SourceIterator.
*
* @return \Exporter\Source\SourceIteratorInterface
*/
public function getDataSourceIterator();
public function configure();
/**
* Call before the batch action, allow you to alter the query and the idx.
*
* @param string $actionName
* @param ProxyQueryInterface $query
* @param array $idx
* @param bool $allElements
*/
public function preBatchAction($actionName, ProxyQueryInterface $query, array &$idx, $allElements);
/**
* Return array of filter parameters.
*
* @return array
*/
public function getFilterParameters();
/**
* Return true if the Admin is related to a subject.
*
* @return bool
*/
public function hasSubject();
/**
* NEXT_MAJOR: remove this method.
*
* @param ErrorElement $errorElement
* @param mixed $object
*
* @deprecated this feature cannot be stable, use a custom validator,
* the feature will be removed with Symfony 2.2
*/
public function validate(ErrorElement $errorElement, $object);
/**
* @param string $context
*
* @return bool
*/
public function showIn($context);
/**
* Add object security, fe. make the current user owner of the object.
*
* @param mixed $object
*/
public function createObjectSecurity($object);
/**
* @return AdminInterface
*/
public function getParent();
/**
* @param AdminInterface $admin
*/
public function setParent(AdminInterface $admin);
/**
* Returns true if the Admin class has an Parent Admin defined.
*
* @return bool
*/
public function isChild();
/**
* Returns template.
*
* @param string $name
*
* @return null|string
*/
public function getTemplate($name);
/**
* Set the translation domain.
*
* @param string $translationDomain the translation domain
*/
public function setTranslationDomain($translationDomain);
/**
* Returns the translation domain.
*
* @return string the translation domain
*/
public function getTranslationDomain();
/**
* Return the form groups.
*
* @return array
*/
public function getFormGroups();
/**
* Set the form groups.
*
* @param array $formGroups
*/
public function setFormGroups(array $formGroups);
public function getFormTabs();
public function setFormTabs(array $formTabs);
public function getShowTabs();
public function setShowTabs(array $showTabs);
/**
* Remove a form group field.
*
* @param string $key
*/
public function removeFieldFromFormGroup($key);
/**
* Returns the show groups.
*
* @return array
*/
public function getShowGroups();
/**
* Set the show groups.
*
* @param array $showGroups
*/
public function setShowGroups(array $showGroups);
/**
* Reorder items in showGroup.
*
* @param string $group
* @param array $keys
*/
public function reorderShowGroup($group, array $keys);
/**
* add a FieldDescription.
*
* @param string $name
* @param FieldDescriptionInterface $fieldDescription
*/
public function addFormFieldDescription($name, FieldDescriptionInterface $fieldDescription);
/**
* Remove a FieldDescription.
*
* @param string $name
*/
public function removeFormFieldDescription($name);
/**
* Returns true if this admin uses ACL.
*
* @return bool
*/
public function isAclEnabled();
/**
* Sets the list of supported sub classes.
*
* @param array $subClasses the list of sub classes
*/
public function setSubClasses(array $subClasses);
/**
* Returns true if the admin has the sub classes.
*
* @param string $name The name of the sub class
*
* @return bool
*/
public function hasSubClass($name);
/**
* Returns true if a subclass is currently active.
*
* @return bool
*/
public function hasActiveSubClass();
/**
* Returns the currently active sub class.
*
* @return string the active sub class
*/
public function getActiveSubClass();
/**
* Returns the currently active sub class code.
*
* @return string the code for active sub class
*/
public function getActiveSubclassCode();
/**
* Returns the list of batchs actions.
*
* @return array the list of batchs actions
*/
public function getBatchActions();
/**
* Returns Admin`s label.
*
* @return string
*/
public function getLabel();
/**
* Returns an array of persistent parameters.
*
* @return array
*/
public function getPersistentParameters();
/**
* NEXT_MAJOR: remove this signature
* Get breadcrumbs for $action.
*
* @param string $action
*
* @return mixed array|Traversable
*/
public function getBreadcrumbs($action);
/**
* Set the current child status.
*
* @param bool $currentChild
*/
public function setCurrentChild($currentChild);
/**
* Returns the current child status.
*
* @return bool
*/
public function getCurrentChild();
/**
* Get translation label using the current TranslationStrategy.
*
* @param string $label
* @param string $context
* @param string $type
*
* @return string
*/
public function getTranslationLabel($label, $context = '', $type = '');
/**
* @param $object
*
* @return Metadata
*/
public function getObjectMetadata($object);
/**
* @return array
*/
public function getListModes();
/**
* @param string $mode
*/
public function setListMode($mode);
/**
* return the list mode.
*
* @return string
*/
public function getListMode();
/*
* Configure buttons for an action
*
* @param string $action
* @param object $object
*
*/
// public function configureActionButtons($action, $object = null);
//TODO: uncomment this method for 4.0
/*
* Returns the result link for an object.
*
* @param mixed $object
*
* @return string|null
*/
//public function getSearchResultLink($object)
// TODO: uncomment this method in 4.0
// /**
// * Setting to true will enable mosaic button for the admin screen.
// * Setting to false will hide mosaic button for the admin screen.
// *
// * @param bool $isShown
// */
// public function showMosaicButton($isShown);
/*
* Checks if a filter type is set to a default value
*
* @param string $name
*
* @return bool
*/
// NEXT_MAJOR: uncomment this method in 4.0
// public function isDefaultFilter($name);
}

View File

@@ -0,0 +1,41 @@
<?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\Admin;
/**
* @author Jules Lamur <contact@juleslamur.fr>
*/
interface AdminTreeInterface
{
/**
* Returns the root ancestor or itself if not a child.
*
* @return AdminInterface
*/
public function getRootAncestor();
/**
* Returns the depth of the admin.
* e.g. 0 if not a child; 2 if child of a child; etc...
*
* @return int
*/
public function getChildDepth();
/**
* Returns the current leaf child admin instance,
* or null if there's no current child.
*
* @return AdminInterface|null
*/
public function getCurrentLeafChildAdmin();
}

View File

@@ -0,0 +1,512 @@
<?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\Admin;
use Doctrine\Common\Inflector\Inflector;
use Sonata\AdminBundle\Exception\NoValueException;
/**
* A FieldDescription hold the information about a field. A typical
* admin instance contains different collections of fields.
*
* - form: used by the form
* - list: used by the list
* - filter: used by the list filter
*
* Some options are global across the different contexts, other are
* context specifics.
*
* Global options :
* - type (m): define the field type (use to tweak the form or the list)
* - template (o) : the template used to render the field
* - name (o) : the name used (label in the form, title in the list)
* - link_parameters (o) : add link parameter to the related Admin class when
* the Admin.generateUrl is called
* - code : the method name to retrieve the related value
* - associated_tostring : (deprecated, use associated_property option)
* the method to retrieve the "string" representation
* of the collection element.
* - associated_property : property path to retrieve the "string" representation
* of the collection element.
*
* Form Field options :
* - field_type (o): the widget class to use to render the field
* - field_options (o): the options to give to the widget
* - edit (o) : list|inline|standard (only used for associated admin)
* - list : open a popup where the user can search, filter and click on one field
* to select one item
* - inline : the associated form admin is embedded into the current form
* - standard : the associated admin is created through a popup
*
* List Field options :
* - identifier (o): if set to true a link appear on to edit the element
*
* Filter Field options :
* - options (o): options given to the Filter object
* - field_type (o): the widget class to use to render the field
* - field_options (o): the options to give to the widget
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
abstract class BaseFieldDescription implements FieldDescriptionInterface
{
/**
* @var string the field name
*/
protected $name;
/**
* @var string|int the type
*/
protected $type;
/**
* @var string|int the original mapping type
*/
protected $mappingType;
/**
* @var string the field name (of the form)
*/
protected $fieldName;
/**
* @var array the ORM association mapping
*/
protected $associationMapping;
/**
* @var array the ORM field information
*/
protected $fieldMapping;
/**
* @var array the ORM parent mapping association
*/
protected $parentAssociationMappings;
/**
* @var string the template name
*/
protected $template;
/**
* @var array the option collection
*/
protected $options = [];
/**
* @var AdminInterface|null the parent Admin instance
*/
protected $parent = null;
/**
* @var AdminInterface the related admin instance
*/
protected $admin;
/**
* @var AdminInterface the associated admin class if the object is associated to another entity
*/
protected $associationAdmin;
/**
* @var string the help message to display
*/
protected $help;
/**
* {@inheritdoc}
*/
public function setFieldName($fieldName)
{
$this->fieldName = $fieldName;
}
/**
* {@inheritdoc}
*/
public function getFieldName()
{
return $this->fieldName;
}
/**
* {@inheritdoc}
*/
public function setName($name)
{
$this->name = $name;
if (!$this->getFieldName()) {
$this->setFieldName(substr(strrchr('.'.$name, '.'), 1));
}
}
/**
* {@inheritdoc}
*/
public function getName()
{
return $this->name;
}
/**
* {@inheritdoc}
*/
public function getOption($name, $default = null)
{
return isset($this->options[$name]) ? $this->options[$name] : $default;
}
/**
* {@inheritdoc}
*/
public function setOption($name, $value)
{
$this->options[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function setOptions(array $options)
{
// set the type if provided
if (isset($options['type'])) {
$this->setType($options['type']);
unset($options['type']);
}
// remove property value
if (isset($options['template'])) {
$this->setTemplate($options['template']);
unset($options['template']);
}
// set help if provided
if (isset($options['help'])) {
$this->setHelp($options['help']);
unset($options['help']);
}
// set default placeholder
if (!isset($options['placeholder'])) {
$options['placeholder'] = 'short_object_description_placeholder';
}
if (!isset($options['link_parameters'])) {
$options['link_parameters'] = [];
}
$this->options = $options;
}
/**
* {@inheritdoc}
*/
public function getOptions()
{
return $this->options;
}
/**
* {@inheritdoc}
*/
public function setTemplate($template)
{
$this->template = $template;
}
/**
* {@inheritdoc}
*/
public function getTemplate()
{
return $this->template;
}
/**
* {@inheritdoc}
*/
public function setType($type)
{
$this->type = $type;
}
/**
* {@inheritdoc}
*/
public function getType()
{
return $this->type;
}
/**
* {@inheritdoc}
*/
public function setParent(AdminInterface $parent)
{
$this->parent = $parent;
}
/**
* {@inheritdoc}
*/
public function getParent()
{
return $this->parent;
}
/**
* {@inheritdoc}
*/
public function getAssociationMapping()
{
return $this->associationMapping;
}
/**
* {@inheritdoc}
*/
public function getFieldMapping()
{
return $this->fieldMapping;
}
/**
* {@inheritdoc}
*/
public function getParentAssociationMappings()
{
return $this->parentAssociationMappings;
}
/**
* {@inheritdoc}
*/
public function setAssociationAdmin(AdminInterface $associationAdmin)
{
$this->associationAdmin = $associationAdmin;
$this->associationAdmin->setParentFieldDescription($this);
}
/**
* {@inheritdoc}
*/
public function getAssociationAdmin()
{
return $this->associationAdmin;
}
/**
* {@inheritdoc}
*/
public function hasAssociationAdmin()
{
return $this->associationAdmin !== null;
}
/**
* {@inheritdoc}
*/
public function getFieldValue($object, $fieldName)
{
if ($this->isVirtual()) {
return;
}
$camelizedFieldName = Inflector::classify($fieldName);
$getters = [];
$parameters = [];
// prefer method name given in the code option
if ($this->getOption('code')) {
$getters[] = $this->getOption('code');
}
// parameters for the method given in the code option
if ($this->getOption('parameters')) {
$parameters = $this->getOption('parameters');
}
$getters[] = 'get'.$camelizedFieldName;
$getters[] = 'is'.$camelizedFieldName;
$getters[] = 'has'.$camelizedFieldName;
foreach ($getters as $getter) {
if (method_exists($object, $getter)) {
return call_user_func_array([$object, $getter], $parameters);
}
}
if (method_exists($object, '__call')) {
return call_user_func_array([$object, '__call'], [$fieldName, $parameters]);
}
if (isset($object->{$fieldName})) {
return $object->{$fieldName};
}
throw new NoValueException(sprintf('Unable to retrieve the value of `%s`', $this->getName()));
}
/**
* {@inheritdoc}
*/
public function setAdmin(AdminInterface $admin)
{
$this->admin = $admin;
}
/**
* {@inheritdoc}
*/
public function getAdmin()
{
return $this->admin;
}
/**
* {@inheritdoc}
*/
public function mergeOption($name, array $options = [])
{
if (!isset($this->options[$name])) {
$this->options[$name] = [];
}
if (!is_array($this->options[$name])) {
throw new \RuntimeException(sprintf('The key `%s` does not point to an array value', $name));
}
$this->options[$name] = array_merge($this->options[$name], $options);
}
/**
* {@inheritdoc}
*/
public function mergeOptions(array $options = [])
{
$this->setOptions(array_merge_recursive($this->options, $options));
}
/**
* {@inheritdoc}
*/
public function setMappingType($mappingType)
{
$this->mappingType = $mappingType;
}
/**
* {@inheritdoc}
*/
public function getMappingType()
{
return $this->mappingType;
}
/**
* Camelize a string.
*
* NEXT_MAJOR: remove this method.
*
* @static
*
* @param string $property
*
* @return string
*
* @deprecated Deprecated since version 3.1. Use \Doctrine\Common\Inflector\Inflector::classify() instead
*/
public static function camelize($property)
{
@trigger_error(
sprintf(
'The %s method is deprecated since 3.1 and will be removed in 4.0. '.
'Use \Doctrine\Common\Inflector\Inflector::classify() instead.',
__METHOD__
),
E_USER_DEPRECATED
);
return Inflector::classify($property);
}
/**
* Defines the help message.
*
* @param string $help
*/
public function setHelp($help)
{
$this->help = $help;
}
/**
* {@inheritdoc}
*/
public function getHelp()
{
return $this->help;
}
/**
* {@inheritdoc}
*/
public function getLabel()
{
return $this->getOption('label');
}
/**
* {@inheritdoc}
*/
public function isSortable()
{
return false !== $this->getOption('sortable', false);
}
/**
* {@inheritdoc}
*/
public function getSortFieldMapping()
{
return $this->getOption('sort_field_mapping');
}
/**
* {@inheritdoc}
*/
public function getSortParentAssociationMapping()
{
return $this->getOption('sort_parent_association_mappings');
}
/**
* {@inheritdoc}
*/
public function getTranslationDomain()
{
return $this->getOption('translation_domain') ?: $this->getAdmin()->getTranslationDomain();
}
/**
* Return true if field is virtual.
*
* @return bool
*/
public function isVirtual()
{
return false !== $this->getOption('virtual_field', false);
}
}

View File

@@ -0,0 +1,178 @@
<?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\Admin;
use Knp\Menu\ItemInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Stateless breadcrumbs builder (each method needs an Admin object).
*
* @author Grégoire Paris <postmaster@greg0ire.fr>
*/
final class BreadcrumbsBuilder implements BreadcrumbsBuilderInterface
{
/**
* @var string[]
*/
protected $config = [];
/**
* @param string[] $config
*/
public function __construct(array $config = [])
{
$resolver = new OptionsResolver();
$this->configureOptions($resolver);
$this->config = $resolver->resolve($config);
}
/**
* @param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'child_admin_route' => 'edit',
]);
}
/**
* {@inheritdoc}
*/
public function getBreadcrumbs(AdminInterface $admin, $action)
{
$breadcrumbs = [];
if ($admin->isChild()) {
return $this->getBreadcrumbs($admin->getParent(), $action);
}
$menu = $this->buildBreadcrumbs($admin, $action);
do {
$breadcrumbs[] = $menu;
} while ($menu = $menu->getParent());
$breadcrumbs = array_reverse($breadcrumbs);
array_shift($breadcrumbs);
return $breadcrumbs;
}
/**
* {@inheritdoc}
* NEXT_MAJOR : make this method private.
*/
public function buildBreadcrumbs(AdminInterface $admin, $action, ItemInterface $menu = null)
{
if (!$menu) {
$menu = $admin->getMenuFactory()->createItem('root');
$menu = $menu->addChild(
'link_breadcrumb_dashboard',
[
'uri' => $admin->getRouteGenerator()->generate('sonata_admin_dashboard'),
'extras' => ['translation_domain' => 'SonataAdminBundle'],
]
);
}
$menu = $this->createMenuItem(
$admin,
$menu,
sprintf('%s_list', $admin->getClassnameLabel()),
$admin->getTranslationDomain(),
[
'uri' => $admin->hasRoute('list') && $admin->hasAccess('list') ?
$admin->generateUrl('list') :
null,
]
);
$childAdmin = $admin->getCurrentChildAdmin();
if ($childAdmin) {
$id = $admin->getRequest()->get($admin->getIdParameter());
$menu = $menu->addChild(
$admin->toString($admin->getSubject()),
[
'uri' => $admin->hasRoute($this->config['child_admin_route']) && $admin->hasAccess($this->config['child_admin_route'], $admin->getSubject()) ?
$admin->generateUrl($this->config['child_admin_route'], ['id' => $id]) :
null,
'extras' => [
'translation_domain' => false,
],
]
);
$menu->setExtra('safe_label', false);
return $this->buildBreadcrumbs($childAdmin, $action, $menu);
}
if ('list' === $action) {
$menu->setUri(false);
} elseif ('create' !== $action && $admin->hasSubject()) {
$menu = $menu->addChild($admin->toString($admin->getSubject()), [
'extras' => [
'translation_domain' => false,
],
]);
} else {
$menu = $this->createMenuItem(
$admin,
$menu,
sprintf('%s_%s', $admin->getClassnameLabel(), $action),
$admin->getTranslationDomain()
);
}
return $menu;
}
/**
* Creates a new menu item from a simple name. The name is normalized and
* translated with the specified translation domain.
*
* @param AdminInterface $admin used for translation
* @param ItemInterface $menu will be modified and returned
* @param string $name the source of the final label
* @param string $translationDomain for label translation
* @param array $options menu item options
*
* @return ItemInterface
*/
private function createMenuItem(
AdminInterface $admin,
ItemInterface $menu,
$name,
$translationDomain = null,
$options = []
) {
$options = array_merge([
'extras' => [
'translation_domain' => $translationDomain,
],
], $options);
return $menu->addChild(
$admin->getLabelTranslatorStrategy()->getLabel(
$name,
'breadcrumb',
'link'
),
$options
);
}
}

View File

@@ -0,0 +1,51 @@
<?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\Admin;
use Knp\Menu\ItemInterface;
/**
* Builds a breacrumbs. There is a dependency on the AdminInterface because
* this object holds useful object to deal with this task, but there is
* probably a better design.
*
* @author Grégoire Paris <postmaster@greg0ire.fr>
*/
interface BreadcrumbsBuilderInterface
{
/**
* Get breadcrumbs for $action.
*
* @param AdminInterface $admin
* @param string $action the name of the action we want to get a
* breadcrumbs for
*
* @return mixed array|Traversable the breadcrumbs
*/
public function getBreadcrumbs(AdminInterface $admin, $action);
/**
* Builds breadcrumbs for $action, starting from $menu.
*
* Note: the method will be called by the top admin instance (parent => child)
* NEXT_MAJOR : remove this method from the public interface.
*
* @param AdminInterface $admin
* @param string $action
* @param ItemInterface|null $menu
*/
public function buildBreadcrumbs(
AdminInterface $admin,
$action,
ItemInterface $menu = null
);
}

View File

@@ -0,0 +1,95 @@
<?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\Admin\Extension;
use Sonata\AdminBundle\Admin\AbstractAdminExtension;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Model\LockInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
/**
* @author Emmanuel Vella <vella.emmanuel@gmail.com>
*/
class LockExtension extends AbstractAdminExtension
{
/**
* @var string
*/
protected $fieldName = '_lock_version';
/**
* {@inheritdoc}
*/
public function configureFormFields(FormMapper $form)
{
$admin = $form->getAdmin();
$formBuilder = $form->getFormBuilder();
// PHP 5.3 BC
$fieldName = $this->fieldName;
$formBuilder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($admin, $fieldName) {
$data = $event->getData();
$form = $event->getForm();
if (null === $data || $form->getParent()) {
return;
}
$modelManager = $admin->getModelManager();
if (!$modelManager instanceof LockInterface) {
return;
}
if (null === $lockVersion = $modelManager->getLockVersion($data)) {
return;
}
$form->add(
$fieldName,
// NEXT_MAJOR: remove the check and add the FQCN
method_exists('Symfony\Component\Form\AbstractType', 'getBlockPrefix')
? 'Symfony\Component\Form\Extension\Core\Type\HiddenType'
: 'hidden',
[
'mapped' => false,
'data' => $lockVersion,
]
);
});
}
/**
* {@inheritdoc}
*/
public function preUpdate(AdminInterface $admin, $object)
{
if (!$admin->hasRequest() || !$data = $admin->getRequest()->get($admin->getUniqid())) {
return;
}
if (!isset($data[$this->fieldName])) {
return;
}
$modelManager = $admin->getModelManager();
if (!$modelManager instanceof LockInterface) {
return;
}
$modelManager->lock($object, $data[$this->fieldName]);
}
}

View File

@@ -0,0 +1,127 @@
<?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\Admin;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class FieldDescriptionCollection implements \ArrayAccess, \Countable
{
/**
* @var FieldDescriptionInterface[]
*/
protected $elements = [];
/**
* @param FieldDescriptionInterface $fieldDescription
*/
public function add(FieldDescriptionInterface $fieldDescription)
{
$this->elements[$fieldDescription->getName()] = $fieldDescription;
}
/**
* @return array
*/
public function getElements()
{
return $this->elements;
}
/**
* @param string $name
*
* @return bool
*/
public function has($name)
{
return array_key_exists($name, $this->elements);
}
/**
* @throws \InvalidArgumentException
*
* @param string $name
*
* @return FieldDescriptionInterface
*/
public function get($name)
{
if ($this->has($name)) {
return $this->elements[$name];
}
throw new \InvalidArgumentException(sprintf('Element "%s" does not exist.', $name));
}
/**
* @param string $name
*/
public function remove($name)
{
if ($this->has($name)) {
unset($this->elements[$name]);
}
}
/**
* {@inheritdoc}
*/
public function offsetExists($offset)
{
return $this->has($offset);
}
/**
* {@inheritdoc}
*/
public function offsetGet($offset)
{
return $this->get($offset);
}
/**
* {@inheritdoc}
*/
public function offsetSet($offset, $value)
{
throw new \RuntimeException('Cannot set value, use add');
}
/**
* {@inheritdoc}
*/
public function offsetUnset($offset)
{
$this->remove($offset);
}
/**
* {@inheritdoc}
*/
public function count()
{
return count($this->elements);
}
/**
* @param array $keys
*/
public function reorder(array $keys)
{
if ($this->has('batch')) {
array_unshift($keys, 'batch');
}
$this->elements = array_merge(array_flip($keys), $this->elements);
}
}

View File

@@ -0,0 +1,290 @@
<?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\Admin;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
interface FieldDescriptionInterface
{
/**
* set the field name.
*
* @param string $fieldName
*/
public function setFieldName($fieldName);
/**
* return the field name.
*
* @return string the field name
*/
public function getFieldName();
/**
* Set the name.
*
* @param string $name
*/
public function setName($name);
/**
* Return the name, the name can be used as a form label or table header.
*
* @return string the name
*/
public function getName();
/**
* Return the value represented by the provided name.
*
* @param string $name
* @param null $default
*
* @return array|null the value represented by the provided name
*/
public function getOption($name, $default = null);
/**
* Define an option, an option is has a name and a value.
*
* @param string $name
* @param mixed $value
*/
public function setOption($name, $value);
/**
* Define the options value, if the options array contains the reserved keywords
* - type
* - template.
*
* Then the value are copied across to the related property value
*
* @param array $options
*/
public function setOptions(array $options);
/**
* return options.
*
* @return array options
*/
public function getOptions();
/**
* return the template used to render the field.
*
* @param string $template
*/
public function setTemplate($template);
/**
* return the template name.
*
* @return string the template name
*/
public function getTemplate();
/**
* return the field type, the type is a mandatory field as it used to select the correct template
* or the logic associated to the current FieldDescription object.
*
* @param string $type
*/
public function setType($type);
/**
* return the type.
*
* @return int|string
*/
public function getType();
/**
* set the parent Admin (only used in nested admin).
*
* @param AdminInterface $parent
*/
public function setParent(AdminInterface $parent);
/**
* return the parent Admin (only used in nested admin).
*
* @return AdminInterface
*/
public function getParent();
/**
* Define the association mapping definition.
*
* @param array $associationMapping
*/
public function setAssociationMapping($associationMapping);
/**
* return the association mapping definition.
*
* @return array
*/
public function getAssociationMapping();
/**
* return the related Target Entity.
*
* @return string|null
*/
public function getTargetEntity();
/**
* set the field mapping information.
*
* @param array $fieldMapping
*/
public function setFieldMapping($fieldMapping);
/**
* return the field mapping definition.
*
* @return array the field mapping definition
*/
public function getFieldMapping();
/**
* set the parent association mappings information.
*
* @param array $parentAssociationMappings
*/
public function setParentAssociationMappings(array $parentAssociationMappings);
/**
* return the parent association mapping definitions.
*
* @return array the parent association mapping definitions
*/
public function getParentAssociationMappings();
/**
* set the association admin instance (only used if the field is linked to an Admin).
*
* @param AdminInterface $associationAdmin the associated admin
*/
public function setAssociationAdmin(AdminInterface $associationAdmin);
/**
* return the associated Admin instance (only used if the field is linked to an Admin).
*
* @return AdminInterface
*/
public function getAssociationAdmin();
/**
* return true if the FieldDescription is linked to an identifier field.
*
* @return bool
*/
public function isIdentifier();
/**
* return the value linked to the description.
*
* @param mixed $object
*
* @return bool|mixed
*/
public function getValue($object);
/**
* set the admin class linked to this FieldDescription.
*
* @param AdminInterface $admin
*/
public function setAdmin(AdminInterface $admin);
/**
* @return AdminInterface the admin class linked to this FieldDescription
*/
public function getAdmin();
/**
* merge option values related to the provided option name.
*
* @throws \RuntimeException
*
* @param string $name
* @param array $options
*/
public function mergeOption($name, array $options = []);
/**
* merge options values.
*
* @param array $options
*/
public function mergeOptions(array $options = []);
/**
* set the original mapping type (only used if the field is linked to an entity).
*
* @param string|int $mappingType
*/
public function setMappingType($mappingType);
/**
* return the mapping type.
*
* @return int|string
*/
public function getMappingType();
/**
* return the label to use for the current field.
*
* @return string
*/
public function getLabel();
/**
* Return the translation domain to use for the current field.
*
* @return string
*/
public function getTranslationDomain();
/**
* Return true if field is sortable.
*
* @return bool
*/
public function isSortable();
/**
* return the field mapping definition used when sorting.
*
* @return array the field mapping definition
*/
public function getSortFieldMapping();
/**
* return the parent association mapping definitions used when sorting.
*
* @return array the parent association mapping definitions
*/
public function getSortParentAssociationMapping();
/**
* @param object $object
* @param string $fieldName
*
* @return mixed
*/
public function getFieldValue($object, $fieldName);
}

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\Admin;
/**
* Implementations should provide arrays of FieldDescriptionInterface instances.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
interface FieldDescriptionRegistryInterface
{
/**
* Return FormFieldDescription.
*
* @param string $name
*
* @return FieldDescriptionInterface
*/
public function getFormFieldDescription($name);
/**
* Build and return the collection of form FieldDescription.
*
* @return FieldDescriptionInterface[] collection of form FieldDescription
*/
public function getFormFieldDescriptions();
/**
* Returns true if the admin has a FieldDescription with the given $name.
*
* @param string $name
*
* @return bool
*/
public function hasShowFieldDescription($name);
/**
* Adds a FieldDescription.
*
* @param string $name
* @param FieldDescriptionInterface $fieldDescription
*/
public function addShowFieldDescription($name, FieldDescriptionInterface $fieldDescription);
/**
* Removes a ShowFieldDescription.
*
* @param string $name
*/
public function removeShowFieldDescription($name);
/**
* Adds a list FieldDescription.
*
* @param string $name
* @param FieldDescriptionInterface $fieldDescription
*/
public function addListFieldDescription($name, FieldDescriptionInterface $fieldDescription);
/**
* Removes a list FieldDescription.
*
* @param string $name
*/
public function removeListFieldDescription($name);
/**
* Returns a list depend on the given $object.
*
* @return FieldDescriptionCollection
*/
public function getList();
/**
* Returns true if the filter FieldDescription exists.
*
* @param string $name
*
* @return bool
*/
public function hasFilterFieldDescription($name);
/**
* Adds a filter FieldDescription.
*
* @param string $name
* @param FieldDescriptionInterface $fieldDescription
*/
public function addFilterFieldDescription($name, FieldDescriptionInterface $fieldDescription);
/**
* Removes a filter FieldDescription.
*
* @param string $name
*/
public function removeFilterFieldDescription($name);
/**
* Returns the filter FieldDescription collection.
*
* @return FieldDescriptionInterface[]
*/
public function getFilterFieldDescriptions();
/**
* Returns a filter FieldDescription.
*
* @param string $name
*
* @return FieldDescriptionInterface|null
*/
public function getFilterFieldDescription($name);
}

View File

@@ -0,0 +1,82 @@
<?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\Admin;
/**
* This interface can be implemented to provide hooks that will be called
* during the lifecycle of the object.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
interface LifecycleHookProviderInterface
{
/**
* This method should call preUpdate, do the update, and call postUpdate.
*
* @param object $object
*
* @return object
*/
public function update($object);
/**
* This method should call prePersist, do the creation, and call postPersist.
*
* @param object $object
*
* @return object
*/
public function create($object);
/**
* This method should call preRemove, do the removal, and call postRemove.
*
* @param object $object
*/
public function delete($object);
//NEXT_MAJOR: uncomment this method for 4.0
// /**
// * @param object $object
// */
// public function preValidate($object);
/**
* @param object $object
*/
public function preUpdate($object);
/**
* @param object $object
*/
public function postUpdate($object);
/**
* @param object $object
*/
public function prePersist($object);
/**
* @param object $object
*/
public function postPersist($object);
/**
* @param object $object
*/
public function preRemove($object);
/**
* @param object $object
*/
public function postRemove($object);
}

View File

@@ -0,0 +1,44 @@
<?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\Admin;
use Knp\Menu\ItemInterface;
/**
* This interface can be implemented by admins that need to build menus.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
interface MenuBuilderInterface
{
/**
* NEXT_MAJOR: remove this method.
*
* @param string $action
* @param AdminInterface $childAdmin
*
* @return ItemInterface|bool
*
* @deprecated Use buildTabMenu instead
*/
public function buildSideMenu($action, AdminInterface $childAdmin = null);
/**
* Build the tab menu related to the current action.
*
* @param string $action
* @param AdminInterface $childAdmin
*
* @return ItemInterface|bool
*/
public function buildTabMenu($action, AdminInterface $childAdmin = null);
}

View File

@@ -0,0 +1,54 @@
<?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\Admin;
/**
* This interface can be used to implement an admin that can have children
* admins, meaning admin that correspond to objects with a relationship with
* the object managed by this admin.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
interface ParentAdminInterface
{
/**
* add an Admin child to the current one.
*
* @param AdminInterface $child
*/
public function addChild(AdminInterface $child);
/**
* Returns true or false if an Admin child exists for the given $code.
*
* @param string $code Admin code
*
* @return bool True if child exist, false otherwise
*/
public function hasChild($code);
/**
* Returns an collection of admin children.
*
* @return array list of Admin children
*/
public function getChildren();
/**
* Returns an admin child with the given $code.
*
* @param string $code
*
* @return AdminInterface|null
*/
public function getChild($code);
}

View File

@@ -0,0 +1,390 @@
<?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\Admin;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\PropertyAccess\PropertyAccess;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
class Pool
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var string[]
*/
protected $adminServiceIds = [];
/**
* @var array
*/
protected $adminGroups = [];
/**
* @var array
*/
protected $adminClasses = [];
/**
* @var string[]
*/
protected $templates = [];
/**
* @var array
*/
protected $assets = [];
/**
* @var string
*/
protected $title;
/**
* @var string
*/
protected $titleLogo;
/**
* @var array
*/
protected $options;
/**
* @var PropertyAccessorInterface
*/
protected $propertyAccessor;
/**
* @param ContainerInterface $container
* @param string $title
* @param string $logoTitle
* @param array $options
*/
public function __construct(ContainerInterface $container, $title, $logoTitle, $options = [], PropertyAccessorInterface $propertyAccessor = null)
{
$this->container = $container;
$this->title = $title;
$this->titleLogo = $logoTitle;
$this->options = $options;
$this->propertyAccessor = $propertyAccessor;
}
/**
* @return array
*/
public function getGroups()
{
$groups = $this->adminGroups;
foreach ($this->adminGroups as $name => $adminGroup) {
foreach ($adminGroup as $id => $options) {
$groups[$name][$id] = $this->getInstance($id);
}
}
return $groups;
}
/**
* Returns whether an admin group exists or not.
*
* @param string $group
*
* @return bool
*/
public function hasGroup($group)
{
return isset($this->adminGroups[$group]);
}
/**
* @return array
*/
public function getDashboardGroups()
{
$groups = $this->adminGroups;
foreach ($this->adminGroups as $name => $adminGroup) {
if (isset($adminGroup['items'])) {
foreach ($adminGroup['items'] as $key => $item) {
// Only Admin Group should be returned
if ('' != $item['admin']) {
$admin = $this->getInstance($item['admin']);
if ($admin->showIn(AbstractAdmin::CONTEXT_DASHBOARD)) {
$groups[$name]['items'][$key] = $admin;
} else {
unset($groups[$name]['items'][$key]);
}
} else {
unset($groups[$name]['items'][$key]);
}
}
}
if (empty($groups[$name]['items'])) {
unset($groups[$name]);
}
}
return $groups;
}
/**
* Returns all admins related to the given $group.
*
* @param string $group
*
* @return array
*
* @throws \InvalidArgumentException
*/
public function getAdminsByGroup($group)
{
if (!isset($this->adminGroups[$group])) {
throw new \InvalidArgumentException(sprintf('Group "%s" not found in admin pool.', $group));
}
$admins = [];
if (!isset($this->adminGroups[$group]['items'])) {
return $admins;
}
foreach ($this->adminGroups[$group]['items'] as $item) {
$admins[] = $this->getInstance($item['admin']);
}
return $admins;
}
/**
* Return the admin related to the given $class.
*
* @param string $class
*
* @return \Sonata\AdminBundle\Admin\AdminInterface|null
*/
public function getAdminByClass($class)
{
if (!$this->hasAdminByClass($class)) {
return;
}
if (!is_array($this->adminClasses[$class])) {
throw new \RuntimeException('Invalid format for the Pool::adminClass property');
}
if (count($this->adminClasses[$class]) > 1) {
throw new \RuntimeException(sprintf(
'Unable to find a valid admin for the class: %s, there are too many registered: %s',
$class,
implode(', ', $this->adminClasses[$class])
));
}
return $this->getInstance($this->adminClasses[$class][0]);
}
/**
* @param string $class
*
* @return bool
*/
public function hasAdminByClass($class)
{
return isset($this->adminClasses[$class]);
}
/**
* Returns an admin class by its Admin code
* ie : sonata.news.admin.post|sonata.news.admin.comment => return the child class of post.
*
* @param string $adminCode
*
* @return \Sonata\AdminBundle\Admin\AdminInterface|false|null
*/
public function getAdminByAdminCode($adminCode)
{
$codes = explode('|', $adminCode);
if (false === $codes) {
return false;
}
$admin = $this->getInstance($codes[0]);
array_shift($codes);
if (empty($codes)) {
return $admin;
}
foreach ($codes as $code) {
if (!$admin->hasChild($code)) {
return false;
}
$admin = $admin->getChild($code);
}
return $admin;
}
/**
* Returns a new admin instance depends on the given code.
*
* @param string $id
*
* @return AdminInterface
*
* @throws \InvalidArgumentException
*/
public function getInstance($id)
{
if (!in_array($id, $this->adminServiceIds)) {
throw new \InvalidArgumentException(sprintf('Admin service "%s" not found in admin pool.', $id));
}
return $this->container->get($id);
}
/**
* @return ContainerInterface|null
*/
public function getContainer()
{
return $this->container;
}
/**
* @param array $adminGroups
*/
public function setAdminGroups(array $adminGroups)
{
$this->adminGroups = $adminGroups;
}
/**
* @return array
*/
public function getAdminGroups()
{
return $this->adminGroups;
}
/**
* @param array $adminServiceIds
*/
public function setAdminServiceIds(array $adminServiceIds)
{
$this->adminServiceIds = $adminServiceIds;
}
/**
* @return array
*/
public function getAdminServiceIds()
{
return $this->adminServiceIds;
}
/**
* @param array $adminClasses
*/
public function setAdminClasses(array $adminClasses)
{
$this->adminClasses = $adminClasses;
}
/**
* @return array
*/
public function getAdminClasses()
{
return $this->adminClasses;
}
/**
* @param array $templates
*/
public function setTemplates(array $templates)
{
$this->templates = $templates;
}
/**
* @return array
*/
public function getTemplates()
{
return $this->templates;
}
/**
* @param string $name
*
* @return null|string
*/
public function getTemplate($name)
{
if (isset($this->templates[$name])) {
return $this->templates[$name];
}
}
/**
* @return string
*/
public function getTitleLogo()
{
return $this->titleLogo;
}
/**
* @return string
*/
public function getTitle()
{
return $this->title;
}
/**
* @param string $name
* @param mixed $default
*
* @return mixed
*/
public function getOption($name, $default = null)
{
if (isset($this->options[$name])) {
return $this->options[$name];
}
return $default;
}
public function getPropertyAccessor()
{
if (null === $this->propertyAccessor) {
$this->propertyAccessor = PropertyAccess::createPropertyAccessor();
}
return $this->propertyAccessor;
}
}

View File

@@ -0,0 +1,82 @@
<?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\Admin;
use Sonata\AdminBundle\Route\RouteGeneratorInterface;
/**
* Contains url generation logic related to an admin.
*
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
interface UrlGeneratorInterface
{
/**
* Returns the list of available urls.
*
* @return RouteCollection the list of available urls
*/
public function getRoutes();
/**
* Return the parameter name used to represent the id in the url.
*
* @return string
*/
public function getRouterIdParameter();
/**
* @param RouteGeneratorInterface $routeGenerator
*/
public function setRouteGenerator(RouteGeneratorInterface $routeGenerator);
/**
* Generates the object url with the given $name.
*
* @param string $name
* @param mixed $object
* @param array $parameters
* @param bool $absolute
*
* @return string return a complete url
*/
public function generateObjectUrl($name, $object, array $parameters = [], $absolute = false);
/**
* Generates a url for the given parameters.
*
* @param string $name
* @param array $parameters
* @param bool $absolute
*
* @return string return a complete url
*/
public function generateUrl($name, array $parameters = [], $absolute = false);
/**
* Generates a url for the given parameters.
*
* @param string $name
* @param array $parameters
* @param bool $absolute
*
* @return array return url parts: 'route', 'routeParameters', 'routeAbsolute'
*/
public function generateMenuUrl($name, array $parameters = [], $absolute = false);
/**
* @param mixed $entity
*
* @return string a string representation of the id that is safe to use in a url
*/
public function getUrlsafeIdentifier($entity);
}