Skip to content

Commit

Permalink
Merge 3.x into master
Browse files Browse the repository at this point in the history
  • Loading branch information
SonataCI committed Sep 29, 2017
2 parents 78fd0d8 + 26d69b8 commit 2e2098a
Show file tree
Hide file tree
Showing 14 changed files with 650 additions and 151 deletions.
107 changes: 102 additions & 5 deletions Admin/AbstractAdmin.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
/**
* @author Thomas Rabaix <thomas.rabaix@sonata-project.org>
*/
abstract class AbstractAdmin implements AdminInterface, DomainObjectInterface
abstract class AbstractAdmin implements AdminInterface, DomainObjectInterface, AdminTreeInterface
{
const CONTEXT_MENU = 'menu';
const CONTEXT_DASHBOARD = 'dashboard';
Expand Down Expand Up @@ -227,6 +227,10 @@ abstract class AbstractAdmin implements AdminInterface, DomainObjectInterface
/**
* The base code route refer to the prefix used to generate the route name.
*
* NEXT_MAJOR: remove this attribute.
*
* @deprecated This attribute is deprecated since 3.x and will be removed in 4.0
*
* @var string
*/
protected $baseCodeRoute = '';
Expand Down Expand Up @@ -607,6 +611,7 @@ public function initialize()
$this->classnameLabel = substr($this->getClass(), strrpos($this->getClass(), '\\') + 1);
}

// NEXT_MAJOR: Remove this line.
$this->baseCodeRoute = $this->getCode();

$this->configure();
Expand Down Expand Up @@ -811,8 +816,9 @@ public function getBaseRoutePattern()
}
}

$this->cachedBaseRoutePattern = sprintf('%s/{id}/%s',
$this->cachedBaseRoutePattern = sprintf('%s/%s/%s',
$this->getParent()->getBaseRoutePattern(),
$this->getParent()->getRouterIdParameter(),
$this->baseRoutePattern ?: $this->urlize($matches[5], '-')
);
} elseif ($this->baseRoutePattern) {
Expand Down Expand Up @@ -1035,15 +1041,21 @@ public function getRoutes()
*/
public function getRouterIdParameter()
{
return $this->isChild() ? '{childId}' : '{id}';
return '{'.$this->getIdParameter().'}';
}

/**
* {@inheritdoc}
*/
public function getIdParameter()
{
return $this->isChild() ? 'childId' : 'id';
$parameter = 'id';

for ($i = 0; $i < $this->getChildDepth(); ++$i) {
$parameter = 'child'.ucfirst($parameter);
}

return $parameter;
}

/**
Expand Down Expand Up @@ -1779,9 +1791,19 @@ public function getFilterFieldDescriptions()
*/
public function addChild(AdminInterface $child)
{
for ($parentAdmin = $this; null !== $parentAdmin; $parentAdmin = $parentAdmin->getParent()) {
if ($parentAdmin->getCode() !== $child->getCode()) {
continue;
}

throw new \RuntimeException(sprintf(
'Circular reference detected! The child admin `%s` is already in the parent tree of the `%s` admin.',
$child->getCode(), $this->getCode()
));
}

$this->children[$child->getCode()] = $child;

$child->setBaseCodeRoute($this->getCode().'|'.$child->getCode());
$child->setParent($this);
}

Expand Down Expand Up @@ -1825,6 +1847,54 @@ public function getParent()
return $this->parent;
}

/**
* {@inheritdoc}
*/
final public function getRootAncestor()
{
$parent = $this;

while ($parent->isChild()) {
$parent = $parent->getParent();
}

return $parent;
}

/**
* {@inheritdoc}
*/
final public function getChildDepth()
{
$parent = $this;
$depth = 0;

while ($parent->isChild()) {
$parent = $parent->getParent();
++$depth;
}

return $depth;
}

/**
* {@inheritdoc}
*/
final public function getCurrentLeafChildAdmin()
{
$child = $this->getCurrentChildAdmin();

if (null === $child) {
return;
}

for ($c = $child; null !== $c; $c = $child->getCurrentChildAdmin()) {
$child = $c;
}

return $child;
}

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -2173,10 +2243,19 @@ public function getCode()
}

/**
* NEXT_MAJOR: Remove this function.
*
* @deprecated This method is deprecated since 3.x and will be removed in 4.0
*
* @param string $baseCodeRoute
*/
public function setBaseCodeRoute($baseCodeRoute)
{
@trigger_error(
'The '.__METHOD__.' is deprecated since 3.x and will be removed in 4.0.',
E_USER_DEPRECATED
);

$this->baseCodeRoute = $baseCodeRoute;
}

Expand All @@ -2185,6 +2264,24 @@ public function setBaseCodeRoute($baseCodeRoute)
*/
public function getBaseCodeRoute()
{
// NEXT_MAJOR: Uncomment the following lines.
// if ($this->isChild()) {
// return $this->getParent()->getBaseCodeRoute().'|'.$this->getCode();
// }
//
// return $this->getCode();

// NEXT_MAJOR: Remove all the code below.
if ($this->isChild()) {
$parentCode = $this->getParent()->getCode();

if ($this->getParent()->isChild()) {
$parentCode = $this->getParent()->getBaseCodeRoute();
}

return $parentCode.'|'.$this->getCode();
}

return $this->baseCodeRoute;
}

Expand Down
41 changes: 41 additions & 0 deletions Admin/AdminTreeInterface.php
Original file line number Diff line number Diff line change
@@ -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();
}
21 changes: 16 additions & 5 deletions Admin/Pool.php
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,24 @@ public function hasAdminByClass($class)
public function getAdminByAdminCode($adminCode)
{
$codes = explode('|', $adminCode);
$admin = false;

if (false === $codes) {
return false;
}

$admin = $this->getInstance($codes[0]);
array_shift($codes);

if (empty($codes)) {
return $admin;
}

foreach ($codes as $code) {
if ($admin == false) {
$admin = $this->getInstance($code);
} elseif ($admin->hasChild($code)) {
$admin = $admin->getChild($code);
if (!$admin->hasChild($code)) {
return false;
}

$admin = $admin->getChild($code);
}

return $admin;
Expand Down
4 changes: 2 additions & 2 deletions Controller/CRUDController.php
Original file line number Diff line number Diff line change
Expand Up @@ -1012,8 +1012,8 @@ protected function configure()

$rootAdmin = $this->admin;

if ($this->admin->isChild()) {
$this->admin->setCurrentChild(true);
while ($rootAdmin->isChild()) {
$rootAdmin->setCurrentChild(true);
$rootAdmin = $rootAdmin->getParent();
}

Expand Down
Binary file added Resources/doc/images/child_admin.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions Resources/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ The demo website can be found at http://demo.sonata-project.org.
reference/getting_started
reference/configuration
reference/architecture
reference/child_admin
reference/dashboard
reference/search
reference/action_list
Expand Down
116 changes: 0 additions & 116 deletions Resources/doc/reference/architecture.rst
Original file line number Diff line number Diff line change
Expand Up @@ -313,121 +313,5 @@ DIC, handles the ``Admin`` classes, lazy-loading them on demand (to reduce overh
and matching each of them to a group. It is also responsible for handling the top level
template files, administration panel title and logo.

Create child admins
-------------------

Let us say you have a ``PlaylistAdmin`` and a ``VideoAdmin``. You can optionally declare the ``VideoAdmin``
to be a child of the ``PlaylistAdmin``. This will create new routes like, for example, ``/playlist/{id}/video/list``,
where the videos will automatically be filtered by post.

To do this, you first need to call the ``addChild`` method in your ``PlaylistAdmin`` service configuration:

.. configuration-block::

.. code-block:: xml
<!-- app/config/config.xml -->
<service id="sonata.admin.playlist" class="AppBundle\Admin\PlaylistAdmin">
<!-- ... -->
<call method="addChild">
<argument type="service" id="sonata.admin.video" />
</call>
</service>
Then, you have to set the VideoAdmin ``parentAssociationMapping`` attribute to ``playlist`` :

.. code-block:: php
<?php
namespace AppBundle\Admin;
// ...
class VideoAdmin extends AbstractAdmin
{
protected $parentAssociationMapping = 'playlist';
// OR
public function getParentAssociationMapping()
{
return 'playlist';
}
}
To display the ``VideoAdmin`` extend the menu in your ``PlaylistAdmin`` class:

.. code-block:: php
<?php
namespace AppBundle\Admin;
use Knp\Menu\ItemInterface as MenuItemInterface;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Admin\AdminInterface;
class PlaylistAdmin extends AbstractAdmin
{
// ...
protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
{
if (!$childAdmin && !in_array($action, array('edit', 'show'))) {
return;
}
$admin = $this->isChild() ? $this->getParent() : $this;
$id = $admin->getRequest()->get('id');
$menu->addChild('View Playlist', array('uri' => $admin->generateUrl('show', array('id' => $id))));
if ($this->isGranted('EDIT')) {
$menu->addChild('Edit Playlist', array('uri' => $admin->generateUrl('edit', array('id' => $id))));
}
if ($this->isGranted('LIST')) {
$menu->addChild('Manage Videos', array(
'uri' => $admin->generateUrl('sonata.admin.video.list', array('id' => $id))
));
}
}
}
It also possible to set a dot-separated value, like ``post.author``, if your parent and child admins are not directly related.

Be wary that being a child admin is optional, which means that regular routes
will be created regardless of whether you actually need them or not. To get rid
of them, you may override the ``configureRoutes`` method::

<?php
namespace AppBundle\Admin;

use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Route\RouteCollection;

class VideoAdmin extends AbstractAdmin
{
protected $parentAssociationMapping = 'playlist';

protected function configureRoutes(RouteCollection $collection)
{
if ($this->isChild()) {

// This is the route configuration as a child
$collection->clearExcept(['show', 'edit']);

return;
}

// This is the route configuration as a parent
$collection->clear();

}
}

.. _`Django Project Website`: http://www.djangoproject.com/
.. _`CRUD`: http://en.wikipedia.org/wiki/CRUD

0 comments on commit 2e2098a

Please sign in to comment.