Przeglądaj źródła

Add audit prototype support (need to be tweaked to match others persistencies layers)

Thomas Rabaix 13 lat temu
rodzic
commit
baa6cd0ee9

+ 2 - 2
Admin/Admin.php

@@ -396,10 +396,10 @@ abstract class Admin implements AdminInterface, DomainObjectInterface
 
 
     /**
     /**
      * @deprecated Use configureShowFields instead.
      * @deprecated Use configureShowFields instead.
-     * @param \Sonata\AdminBundle\Show\ShowMapper $filter
+     * @param \Sonata\AdminBundle\Show\ShowMapper $show
      * @return void
      * @return void
      */
      */
-    protected function configureShowField(ShowMapper $filter)
+    protected function configureShowField(ShowMapper $show)
     {
     {
 
 
     }
     }

+ 83 - 6
Controller/CRUDController.php

@@ -416,9 +416,9 @@ class CRUDController extends Controller
         $this->get('twig')->getExtension('form')->setTheme($view, $this->admin->getFormTheme());
         $this->get('twig')->getExtension('form')->setTheme($view, $this->admin->getFormTheme());
 
 
         return $this->render($this->admin->getEditTemplate(), array(
         return $this->render($this->admin->getEditTemplate(), array(
-            'action'        => 'create',
-            'form'          => $view,
-            'object'        => $object,
+            'action' => 'create',
+            'form'   => $view,
+            'object' => $object,
         ));
         ));
     }
     }
 
 
@@ -447,9 +447,86 @@ class CRUDController extends Controller
         $elements = $this->admin->getShow();
         $elements = $this->admin->getShow();
 
 
         return $this->render($this->admin->getShowTemplate(), array(
         return $this->render($this->admin->getShowTemplate(), array(
-            'action'         => 'show',
-            'object'         => $object,
-            'elements'       => $this->admin->getShow(),
+            'action'   => 'show',
+            'object'   => $object,
+            'elements' => $this->admin->getShow(),
+        ));
+    }
+
+    /**
+     * @param null $id
+     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException|\Symfony\Component\Security\Core\Exception\AccessDeniedException
+     */
+    public function historyAction($id = null)
+    {
+        if (false === $this->admin->isGranted('EDIT')) {
+            throw new AccessDeniedException();
+        }
+
+        $id = $this->get('request')->get($this->admin->getIdParameter());
+
+        $object = $this->admin->getObject($id);
+
+        if (!$object) {
+            throw new NotFoundHttpException(sprintf('unable to find the object with id : %s', $id));
+        }
+
+        $manager = $this->get('sonata.admin.audit.manager');
+
+        if (!$manager->hasReader($this->admin->getClass())) {
+            throw new NotFoundHttpException(sprintf('unable to find the audit reader for class : %s', $this->admin->getClass()));
+        }
+
+        $reader = $manager->getReader($this->admin->getClass());
+
+        $revisions = $reader->findRevisions($this->admin->getClass(), $id);
+
+        return $this->render($this->admin->getTemplate('history'), array(
+            'action'   => 'history',
+            'object'   => $object,
+            'revisions' => $revisions,
+        ));
+    }
+
+    /**
+     * @param null $id
+     * @param $revision
+     */
+    public function historyViewRevisionAction($id = null, $revision = null)
+    {
+        if (false === $this->admin->isGranted('EDIT')) {
+            throw new AccessDeniedException();
+        }
+
+        $id = $this->get('request')->get($this->admin->getIdParameter());
+
+        $object = $this->admin->getObject($id);
+
+        if (!$object) {
+            throw new NotFoundHttpException(sprintf('unable to find the object with id : %s', $id));
+        }
+
+        $manager = $this->get('sonata.admin.audit.manager');
+
+        if (!$manager->hasReader($this->admin->getClass())) {
+            throw new NotFoundHttpException(sprintf('unable to find the audit reader for class : %s', $this->admin->getClass()));
+        }
+
+        $reader = $manager->getReader($this->admin->getClass());
+
+        // retrieve the revisioned object
+        $object = $reader->find($this->admin->getClass(), $id, $revision);
+
+        if (!$object) {
+            throw new NotFoundHttpException(sprintf('unable to find the targeted object `%s` from the revision `%s` with classname : `%s`', $id, $revision, $this->admin->getClass()));
+        }
+
+        $this->admin->setSubject($object);
+
+        return $this->render($this->admin->getShowTemplate(), array(
+            'action'   => 'show',
+            'object'   => $object,
+            'elements' => $this->admin->getShow(),
         ));
         ));
     }
     }
 }
 }

+ 3 - 0
DependencyInjection/Configuration.php

@@ -89,6 +89,9 @@ class Configuration implements ConfigurationInterface
                         ->scalarNode('list')->defaultValue('SonataAdminBundle:CRUD:list.html.twig')->cannotBeEmpty()->end()
                         ->scalarNode('list')->defaultValue('SonataAdminBundle:CRUD:list.html.twig')->cannotBeEmpty()->end()
                         ->scalarNode('show')->defaultValue('SonataAdminBundle:CRUD:show.html.twig')->cannotBeEmpty()->end()
                         ->scalarNode('show')->defaultValue('SonataAdminBundle:CRUD:show.html.twig')->cannotBeEmpty()->end()
                         ->scalarNode('edit')->defaultValue('SonataAdminBundle:CRUD:edit.html.twig')->cannotBeEmpty()->end()
                         ->scalarNode('edit')->defaultValue('SonataAdminBundle:CRUD:edit.html.twig')->cannotBeEmpty()->end()
+                        ->scalarNode('history')->defaultValue('SonataAdminBundle:CRUD:history.html.twig')->cannotBeEmpty()->end()
+                        ->scalarNode('history_revision')->defaultValue('SonataAdminBundle:CRUD:history_revision.html.twig')->cannotBeEmpty()->end()
+                        ->scalarNode('action')->defaultValue('SonataAdminBundle:CRUD:action.html.twig')->cannotBeEmpty()->end()
                     ->end()
                     ->end()
                 ->end()
                 ->end()
             ->end()
             ->end()

+ 71 - 0
Model/AuditManager.php

@@ -0,0 +1,71 @@
+<?php
+
+/*
+ * This file is part of the Sonata 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\Model;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+class AuditManager implements AuditManagerInterface
+{
+    protected $classes = array();
+
+    protected $readers = array();
+
+    protected $container;
+
+    /**
+     * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
+     */
+    public function __construct(ContainerInterface $container)
+    {
+        $this->container = $container;
+    }
+
+    /**
+     * @param $serviceId
+     * @param array $classes
+     */
+    public function setReader($serviceId, array $classes)
+    {
+        $this->readers[$serviceId] = $classes;
+    }
+
+    /**
+     * @param $class
+     * @return bool
+     */
+    public function hasReader($class)
+    {
+        foreach ($this->readers as $classes) {
+            if (in_array($class, $classes)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * @param $class
+     * @return \Sonata\AdminBundle\Model\AuditReaderInterface
+     * @throws \RuntimeException
+     */
+    public function getReader($class)
+    {
+        foreach ($this->readers as $readerId => $classes) {
+            if (in_array($class, $classes)) {
+                return $this->container->get($readerId);
+            }
+        }
+
+        throw new \RuntimeException(sprintf('The class %s does not have any reader manager', $class));
+    }
+}

+ 38 - 0
Model/AuditManagerInterface.php

@@ -0,0 +1,38 @@
+<?php
+
+/*
+ * This file is part of the Sonata 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\Model;
+
+use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
+use Sonata\AdminBundle\Datagrid\DatagridInterface;
+use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
+
+interface AuditManagerInterface
+{
+    /**
+      * @param $serviceId
+      * @param array $classes
+      */
+    function setReader($serviceId, array $classes);
+
+    /**
+      * @param $class
+      * @return bool
+      */
+    function hasReader($class);
+
+     /**
+      * @param $class
+      * @return \Sonata\AdminBundle\Model\AuditReaderInterface
+      * @throws \RuntimeException
+      */
+    function getReader($class);
+}

+ 49 - 0
Model/AuditReaderInterface.php

@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of the Sonata 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\Model;
+
+use Sonata\AdminBundle\Admin\FieldDescriptionInterface;
+use Sonata\AdminBundle\Datagrid\DatagridInterface;
+use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
+
+interface AuditReaderInterface
+{
+    /**
+     * @abstract
+     * @param $className
+     * @param $id
+     * @param $revision
+     */
+    function find($className, $id, $revision);
+
+    /**
+     * @abstract
+     * @param $className
+     * @param int $limit
+     * @param int $offset
+     */
+    function findRevisionHistory($className, $limit = 20, $offset = 0);
+
+    /**
+     * @abstract
+     * @param $classname
+     * @param $revision
+     */
+    function findRevision($classname, $revision);
+
+    /**
+     * @abstract
+     * @param $className
+     * @param $id
+     */
+    function findRevisions($className, $id);
+}

+ 5 - 0
Resources/config/core.xml

@@ -61,6 +61,11 @@
             <argument type="service" id="sonata.admin.pool" />
             <argument type="service" id="sonata.admin.pool" />
             <argument type="service" id="sonata.admin.helper" />
             <argument type="service" id="sonata.admin.helper" />
         </service>
         </service>
+
+        <!-- audit manager -->
+        <service id="sonata.admin.audit.manager" class="Sonata\AdminBundle\Model\AuditManager">
+            <argument type="service" id="service_container" />
+        </service>
     </services>
     </services>
 </container>
 </container>
 
 

+ 6 - 2
Resources/config/route.xml

@@ -5,8 +5,12 @@
            xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
            xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
 
 
     <services>
     <services>
-        <service id="sonata.admin.route.path_info" class="Sonata\AdminBundle\Route\PathInfoBuilder" />
+        <service id="sonata.admin.route.path_info" class="Sonata\AdminBundle\Route\PathInfoBuilder">
+            <argument type="service" id="sonata.admin.audit.manager" />
+        </service>
 
 
-        <service id="sonata.admin.route.query_string" class="Sonata\AdminBundle\Route\QueryStringBuilder" />
+        <service id="sonata.admin.route.query_string" class="Sonata\AdminBundle\Route\QueryStringBuilder">
+            <argument type="service" id="sonata.admin.audit.manager" />
+        </service>
     </services>
     </services>
 </container>
 </container>

+ 3 - 0
Resources/doc/reference/templates.rst

@@ -15,6 +15,7 @@ By default, an Admin class used a set of templates, it is possible to tweak the
             list:    SonataAdminBundle:CRUD:list.html.twig
             list:    SonataAdminBundle:CRUD:list.html.twig
             show:    SonataAdminBundle:CRUD:show.html.twig
             show:    SonataAdminBundle:CRUD:show.html.twig
             edit:    SonataAdminBundle:CRUD:edit.html.twig
             edit:    SonataAdminBundle:CRUD:edit.html.twig
+            history:  SonataAdminBundle:CRUD:history.html.twig
 
 
 
 
 Usage of each template :
 Usage of each template :
@@ -24,5 +25,7 @@ Usage of each template :
 * list : the template to use for the list action
 * list : the template to use for the list action
 * show : the template to use for the show action
 * show : the template to use for the show action
 * edit : the template to use for the edit and create action
 * edit : the template to use for the edit and create action
+* history : the template to use for the history / audit action
 
 
 The default values will be set only if the ``Admin::setTemplates`` is not called by the Container.
 The default values will be set only if the ``Admin::setTemplates`` is not called by the Container.
+

+ 24 - 0
Resources/translations/SonataAdminBundle.en.xliff

@@ -268,6 +268,30 @@
               <target>or</target>
               <target>or</target>
             </trans-unit>
             </trans-unit>
 
 
+            <trans-unit id="link_action_history">
+                <source>link_action_history</source>
+                <target>Revisions</target>
+            </trans-unit>
+            <trans-unit id="td_action">
+                <source>td_action</source>
+                <target>Action</target>
+            </trans-unit>
+            <trans-unit id="td_revision">
+                <source>td_revision</source>
+                <target>Revisions</target>
+            </trans-unit>
+            <trans-unit id="td_timestamp">
+                <source>td_timestamp</source>
+                <target>Date</target>
+            </trans-unit>
+            <trans-unit id="td_username">
+                <source>td_username</source>
+                <target>Author</target>
+            </trans-unit>
+            <trans-unit id="label_view_revision">
+                <source>label_view_revision</source>
+                <target>View Revision</target>
+            </trans-unit>
 
 
         </body>
         </body>
     </file>
     </file>

+ 21 - 0
Resources/translations/SonataAdminBundle.fr.xliff

@@ -230,6 +230,27 @@
               <source>delete_or</source>
               <source>delete_or</source>
               <target>ou</target>
               <target>ou</target>
             </trans-unit>
             </trans-unit>
+            <trans-unit id="link_action_history">
+                <source>link_action_history</source>
+                <target>Révisions</target>
+            </trans-unit>
+            <trans-unit id="td_revision">
+                <source>td_revision</source>
+                <target>Révisions</target>
+            </trans-unit>
+            <trans-unit id="td_timestamp">
+                <source>td_timestamp</source>
+                <target>Date</target>
+            </trans-unit>
+            <trans-unit id="td_username">
+                <source>td_username</source>
+                <target>Auteur</target>
+            </trans-unit>
+            <trans-unit id="label_view_revision">
+                <source>label_view_revision</source>
+                <target>Afficher la révision</target>
+            </trans-unit>
+
         </body>
         </body>
     </file>
     </file>
 </xliff>
 </xliff>

+ 3 - 0
Resources/views/CRUD/base_edit.html.twig

@@ -25,6 +25,9 @@ file that was distributed with this source code.
             {% if admin.hasroute('show') and admin.id(object) and admin.isGranted('VIEW') and admin.show|length > 0 %}
             {% if admin.hasroute('show') and admin.id(object) and admin.isGranted('VIEW') and admin.show|length > 0 %}
                 <li class="btn sonata-action-element"><a href="{{ admin.generateObjectUrl('show', object) }}">{% trans from 'SonataAdminBundle' %}link_action_show{% endtrans %}</a></li>
                 <li class="btn sonata-action-element"><a href="{{ admin.generateObjectUrl('show', object) }}">{% trans from 'SonataAdminBundle' %}link_action_show{% endtrans %}</a></li>
             {% endif %}
             {% endif %}
+            {% if admin.hasroute('history') and admin.id(object) and admin.isGranted('EDIT') %}
+                <li class="btn sonata-action-element"><a href="{{ admin.generateObjectUrl('history', object) }}">{% trans from 'SonataAdminBundle' %}link_action_history{% endtrans %}</a></li>
+            {% endif %}
             {% if admin.hasroute('create') and admin.isGranted('CREATE')%}
             {% if admin.hasroute('create') and admin.isGranted('CREATE')%}
                 <li class="btn sonata-action-element"><a href="{{ admin.generateUrl('create') }}">{% trans from 'SonataAdminBundle' %}link_action_create{% endtrans %}</a></li>
                 <li class="btn sonata-action-element"><a href="{{ admin.generateUrl('create') }}">{% trans from 'SonataAdminBundle' %}link_action_create{% endtrans %}</a></li>
             {% endif %}
             {% endif %}

+ 81 - 0
Resources/views/CRUD/base_history.html.twig

@@ -0,0 +1,81 @@
+{#
+
+This file is part of the Sonata 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.
+
+#}
+
+{% extends base_template %}
+
+{% block actions %}
+    <div class="sonata-actions">
+        <ul>
+            {% if admin.hasroute('edit') and admin.id(object) and admin.isGranted('EDIT') and admin.show|length > 0 %}
+                <li class="btn sonata-action-element"><a href="{{ admin.generateObjectUrl('edit', object) }}">{% trans from 'SonataAdminBundle' %}link_action_edit{% endtrans %}</a></li>
+            {% endif %}
+            {% if admin.hasroute('show') and admin.isGranted('SHOW')%}
+                <li class="btn sonata-action-element"><a href="{{ admin.generateObjectUrl('show', object) }}">{% trans from 'SonataAdminBundle' %}link_action_show{% endtrans %}</a></li>
+            {% endif %}
+            {% if admin.hasroute('list') and admin.isGranted('LIST')%}
+                <li class="btn sonata-action-element"><a href="{{ admin.generateUrl('list') }}">{% trans from 'SonataAdminBundle' %}link_action_list{% endtrans %}</a></li>
+            {% endif %}
+        </ul>
+    </div>
+{% endblock %}
+
+{% block content %}
+    <div class="row">
+        <div class="span9">
+            <table id="revisions">
+                <thead>
+                    <tr>
+                        <th>{{ "td_revision"|trans({}, 'SonataAdminBundle') }}</th>
+                        <th>{{ "td_timestamp"|trans({}, 'SonataAdminBundle') }}</th>
+                        <th>{{ "td_username"|trans({}, 'SonataAdminBundle') }}</th>
+                        <th>{{ "td_action"|trans({}, 'SonataAdminBundle') }}</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    {% for revision in revisions %}
+                        <tr>
+                            <td>{{ revision.rev}}</td>
+                            <td>{{ revision.timestamp | date}}</td>
+                            <td>{{ revision.username}}</td>
+                            <td><a href="{{ admin.generateObjectUrl('history_view_revision', object, {'revision': revision.rev }) }}" class="revision-link" rel="{{ revision.rev }}">{{ "label_view_revision"|trans({}, 'SonataAdminBundle') }}</a></td>
+                        </tr>
+                    {% endfor %}
+                </tbody>
+            </table>
+        </div>
+        <div id="revision-detail" class="span12 revision-detail">
+
+        </div>
+    </div>
+
+    <script type="text/javascript">
+        jQuery(document).ready(function() {
+
+            jQuery('a.revision-link').bind('click', function(event) {
+                event.stopPropagation();
+                event.preventDefault();
+
+                jQuery('#revision-detail').html('');
+                jQuery('table#revisions tbody tr').removeClass('current');
+                jQuery(this).parent('').removeClass('current');
+
+                jQuery.ajax({
+                    url: jQuery(this).attr('href'),
+                    success: function(data) {
+                        jQuery('#revision-detail').html(data);
+                    }
+                });
+
+                return false;
+            })
+        });
+    </script>
+{% endblock %}

+ 3 - 1
Resources/views/CRUD/base_show.html.twig

@@ -17,7 +17,9 @@ file that was distributed with this source code.
             {% if admin.hasRoute('edit') and admin.isGranted('EDIT')%}
             {% if admin.hasRoute('edit') and admin.isGranted('EDIT')%}
                 <li class="btn sonata-action-element"><a href="{{ admin.generateObjectUrl('edit', object) }}">{% trans from 'SonataAdminBundle' %}link_action_edit{% endtrans %}</a></li>
                 <li class="btn sonata-action-element"><a href="{{ admin.generateObjectUrl('edit', object) }}">{% trans from 'SonataAdminBundle' %}link_action_edit{% endtrans %}</a></li>
             {% endif %}
             {% endif %}
-
+            {% if admin.hasroute('history') and admin.id(object) and admin.isGranted('EDIT') %}
+                <li class="btn sonata-action-element"><a href="{{ admin.generateObjectUrl('history', object) }}">{% trans from 'SonataAdminBundle' %}link_action_history{% endtrans %}</a></li>
+            {% endif %}
             {% if admin.hasRoute('create') and admin.isGranted('CREATE')%}
             {% if admin.hasRoute('create') and admin.isGranted('CREATE')%}
                 <li class="btn sonata-action-element"><a href="{{ admin.generateUrl('create') }}">{% trans from 'SonataAdminBundle' %}link_action_create{% endtrans %}</a></li>
                 <li class="btn sonata-action-element"><a href="{{ admin.generateUrl('create') }}">{% trans from 'SonataAdminBundle' %}link_action_create{% endtrans %}</a></li>
             {% endif %}
             {% endif %}

+ 12 - 0
Resources/views/CRUD/history.html.twig

@@ -0,0 +1,12 @@
+{#
+
+This file is part of the Sonata 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.
+
+#}
+
+{% extends 'SonataAdminBundle:CRUD:base_history.html.twig' %}

+ 1 - 0
Resources/views/ajax_layout.html.twig

@@ -13,6 +13,7 @@ file that was distributed with this source code.
     {% block preview %}{% endblock %}
     {% block preview %}{% endblock %}
     {% block form %}{% endblock %}
     {% block form %}{% endblock %}
     {% block list %}{% endblock %}
     {% block list %}{% endblock %}
+    {% block show %}{% endblock %}
 
 
     <div class="sonata-ba-filter">
     <div class="sonata-ba-filter">
         {% block list_filters %}{% endblock %}
         {% block list_filters %}{% endblock %}

+ 17 - 1
Route/PathInfoBuilder.php

@@ -12,14 +12,25 @@ namespace Sonata\AdminBundle\Route;
 
 
 use Sonata\AdminBundle\Builder\RouteBuilderInterface;
 use Sonata\AdminBundle\Builder\RouteBuilderInterface;
 use Sonata\AdminBundle\Admin\AdminInterface;
 use Sonata\AdminBundle\Admin\AdminInterface;
+use Sonata\AdminBundle\Model\AuditManagerInterface;
 
 
 class PathInfoBuilder implements RouteBuilderInterface
 class PathInfoBuilder implements RouteBuilderInterface
 {
 {
+    protected $manager;
+
+    /**
+     * @param \Sonata\AdminBundle\Model\AuditManagerInterface $manager
+     */
+    public function __construct(AuditManagerInterface $manager)
+    {
+        $this->manager = $manager;
+    }
+
     /**
     /**
      * @param \Sonata\AdminBundle\Admin\AdminInterface $admin
      * @param \Sonata\AdminBundle\Admin\AdminInterface $admin
      * @param \Sonata\AdminBundle\Route\RouteCollection $collection
      * @param \Sonata\AdminBundle\Route\RouteCollection $collection
      */
      */
-    function build(AdminInterface $admin, RouteCollection $collection)
+    public function build(AdminInterface $admin, RouteCollection $collection)
     {
     {
         $collection->add('list');
         $collection->add('list');
         $collection->add('create');
         $collection->add('create');
@@ -28,6 +39,11 @@ class PathInfoBuilder implements RouteBuilderInterface
         $collection->add('delete', $admin->getRouterIdParameter().'/delete');
         $collection->add('delete', $admin->getRouterIdParameter().'/delete');
         $collection->add('show', $admin->getRouterIdParameter().'/show');
         $collection->add('show', $admin->getRouterIdParameter().'/show');
 
 
+        if ($this->manager->hasReader($admin->getClass())) {
+            $collection->add('history', $admin->getRouterIdParameter().'/history');
+            $collection->add('history_view_revision', $admin->getRouterIdParameter().'/history/{revision}/view');
+        }
+
         // add children urls
         // add children urls
         foreach ($admin->getChildren() as $children) {
         foreach ($admin->getChildren() as $children) {
             $collection->addCollection($children->getRoutes());
             $collection->addCollection($children->getRoutes());

+ 17 - 1
Route/QueryStringBuilder.php

@@ -12,14 +12,25 @@ namespace Sonata\AdminBundle\Route;
 
 
 use Sonata\AdminBundle\Builder\RouteBuilderInterface;
 use Sonata\AdminBundle\Builder\RouteBuilderInterface;
 use Sonata\AdminBundle\Admin\AdminInterface;
 use Sonata\AdminBundle\Admin\AdminInterface;
+use Sonata\AdminBundle\Model\AuditManagerInterface;
 
 
 class QueryStringBuilder implements RouteBuilderInterface
 class QueryStringBuilder implements RouteBuilderInterface
 {
 {
+    protected $manager;
+
+    /**
+     * @param \Sonata\AdminBundle\Model\AuditManagerInterface $manager
+     */
+    public function __construct(AuditManagerInterface $manager)
+    {
+        $this->manager = $manager;
+    }
+
     /**
     /**
      * @param \Sonata\AdminBundle\Admin\AdminInterface $admin
      * @param \Sonata\AdminBundle\Admin\AdminInterface $admin
      * @param \Sonata\AdminBundle\Route\RouteCollection $collection
      * @param \Sonata\AdminBundle\Route\RouteCollection $collection
      */
      */
-    function build(AdminInterface $admin, RouteCollection $collection)
+    public function build(AdminInterface $admin, RouteCollection $collection)
     {
     {
         $collection->add('list');
         $collection->add('list');
         $collection->add('create');
         $collection->add('create');
@@ -28,6 +39,11 @@ class QueryStringBuilder implements RouteBuilderInterface
         $collection->add('delete');
         $collection->add('delete');
         $collection->add('show');
         $collection->add('show');
 
 
+        if ($this->manager->hasReader($admin->getClass())) {
+            $collection->add('history', '/audit-history');
+            $collection->add('history_view_revision', '/audit-history-view');
+        }
+
         // add children urls
         // add children urls
         foreach ($admin->getChildren() as $children) {
         foreach ($admin->getChildren() as $children) {
             $collection->addCollection($children->getRoutes());
             $collection->addCollection($children->getRoutes());