Prechádzať zdrojové kódy

Filtro auditoria. Show y compare revisions

Guillermo Espinoza 8 rokov pred
rodič
commit
3304c825fb

+ 158 - 0
Controller/AuditController.php

@@ -0,0 +1,158 @@
+<?php
+
+namespace AuditBundle\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Component\HttpFoundation\Request;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+use AuditBundle\Form\Type\AuditFilterType;
+use AuditBundle\Services\AuditDataService;
+use SimpleThings\EntityAudit\Exception\NoRevisionFoundException;
+
+class AuditController extends Controller
+{
+
+    /**
+     * 
+     * @param Request $request
+     * 
+     * @return string
+     * 
+     * @Route("/admin/audit", name="entity_audit")
+     */
+    public function revsAction(Request $request)
+    {
+        /* @var $auditDataService AuditDataService */
+        $auditDataService = $this->get('audit.audit_data.service');
+        $entities = $auditDataService->getEntities();
+        $columns = $auditDataService->getColumns();
+
+        $entity = null;
+        $count = null;
+        $result = null;
+        $page = null;
+        $pages = 1;
+        $is_post = false;
+
+        /* @var $filterForm AuditFilterType */
+        $filterForm = $this->createForm(AuditFilterType::class);
+        $filterForm->handleRequest($request);
+        if ($filterForm->isSubmitted()) {
+            $data = $filterForm->getData();
+            $results = $auditDataService->getResults($data);
+
+            $is_post = true;
+            extract($data);
+            extract($results);
+
+            $entity = array_search($entity, $entities);
+        }
+
+        return $this->render('AuditBundle:Audit:revs.html.twig', array(
+                    'base_template' => $this->getBaseTemplate($request),
+                    'admin_pool' => $this->get('sonata.admin.pool'),
+                    'entities' => $entities,
+                    'columns' => $columns,
+                    'form' => $filterForm->createView(),
+                    'is_post' => $is_post,
+                    'data' => $result,
+                    'entity' => $entity,
+                    'total' => $count,
+                    'params' => array(
+                        'label' => $this->get('translator')->trans('Auditoría de Entidades', array(), 'AuditBundle'),
+                        'regs' => $count,
+                        'page' => $page,
+                        'pages' => $pages
+                    ),
+        ));
+    }
+
+    /**
+     * @param Request $request
+     * @param string $className
+     * @param id $id
+     * @param Revision $rev
+     * 
+     * @return string
+     * 
+     * @Route("/admin/audit/view/{className}/{id}/{rev}", name="entity_audit_view_details")
+     */
+    public function viewAction(Request $request, $className, $id, $rev)
+    {
+        /* @var $auditDataService AuditDataService */
+        $auditDataService = $this->get('audit.audit_data.service');
+        $result = $auditDataService->viewRevision($className, $id, $rev);
+
+        $data = array(
+            'label' => $this->get('translator')->trans('Detalles de la revisión: %rev%', array('%rev%' => $rev), 'AuditBundle'),
+            'className' => $className,
+            'id' => $id,
+            'rev' => $rev,
+            'revdata' => $result['revdata'],
+            'details' => $result['rev_details'],
+            'revisions' => $result['rev_revisions']
+        );
+
+        return $this->render('AuditBundle:Audit:view.html.twig', array(
+                    'base_template' => $this->getBaseTemplate($request),
+                    'data' => $data,
+                    'admin_pool' => $this->get('sonata.admin.pool'),
+        ));
+    }
+
+    /**
+     * @param Request $request
+     * @param string $className
+     * @param int $id
+     * @param Revision $oldRev
+     * @param Revision $newRev
+     * 
+     * @return string
+     * 
+     * @Route("/admin/flowdat2/entity/audit/compare/{className}/{id}", name="entity_audit_compare_revs")
+     */
+    public function compareAction(Request $request, $className, $id, $oldRev = null, $newRev = null)
+    {
+        try {
+            $oldRev = $request->query->get('oldRev');
+            $newRev = $request->query->get('newRev');
+
+            /* @var $auditDataService AuditDataService */
+            $auditDataService = $this->get('audit.audit_data.service');
+            $diff = $auditDataService->diff($className, $id, $oldRev, $newRev);
+
+            $data = array(
+                'label' => $this->get('translator')->trans('Comparativa entre las revisiones %oldRev% y %newRev%', array('%oldRev%' => $oldRev, '%newRev%' => $newRev), 'AuditBundle'),
+                'className' => $className,
+                'id' => $id,
+                'oldRev' => $oldRev,
+                'newRev' => $newRev,
+                'diff' => $diff,
+            );
+
+            return $this->render('AuditBundle:Audit:compare.html.twig', array(
+                        'base_template' => $this->getBaseTemplate($request),
+                        'data' => $data,
+                        'admin_pool' => $this->get('sonata.admin.pool'),
+            ));
+        } catch (NoRevisionFoundException $ex) {
+            $this->get('session')->getFlashBag()->add('sonata_error', $ex->getMessage());
+            
+            return $this->redirect($this->generateUrl('entity_audit'));
+        }
+    }
+
+    /**
+     * @return string
+     */
+    private function getBaseTemplate(Request $request)
+    {
+        $adminPool = $this->get('sonata.admin.pool');
+        if ($request->isXmlHttpRequest()) {
+            return $adminPool->getTemplate('ajax');
+        }
+
+        return $adminPool->getTemplate('layout');
+    }
+
+}

+ 105 - 0
Form/Type/AuditFilterType.php

@@ -0,0 +1,105 @@
+<?php
+
+namespace AuditBundle\Form\Type;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\Form\Extension\Core\Type\DateType;
+use Symfony\Component\Form\Extension\Core\Type\HiddenType;
+use AuditBundle\Services\AuditDataService;
+
+class AuditFilterType extends AbstractType
+{
+
+    /**
+     * @var AuditDataService
+     */
+    private $auditDataService;
+
+    /**
+     * @param AuditDataService $auditDataService
+     */
+    public function __construct(AuditDataService $auditDataService)
+    {
+        $this->auditDataService = $auditDataService;
+    }
+
+    /**
+     * @param FormBuilderInterface $builder
+     * @param array $options
+     */
+    public function buildForm(FormBuilderInterface $builder, array $options)
+    {
+        $builder
+                ->add('entity', ChoiceType::class, array(
+                    'choices' => $this->auditDataService->getEntities(),
+                    'required' => true,
+                ))
+                ->add('users', ChoiceType::class, array(
+                    'choices' => $this->auditDataService->getRevisionUsers(),
+                    'empty_data' => '',
+                    'required' => false,
+                    'multiple' => true,
+                ))
+                ->add('types', ChoiceType::class, array(
+                    'choices' => array(
+                        'INS' => 'INS',
+                        'UPD' => 'UPD',
+                        'DEL' => 'DEL',
+                    ),
+                    'empty_data' => '',
+                    'required' => false,
+                    'multiple' => true,
+                ))
+                ->add('idx', null, array(
+                    'required' => false,
+                ))
+                ->add('dateFrom', DateType::class, array(
+                    'widget' => 'single_text',
+                    'format' => 'dd/MM/yyyy',
+                    'empty_data' => '',
+                    'required' => false,
+                    'attr' => array(
+                        'class' => 'datepicker',
+                    ),
+                ))
+                ->add('dateTo', DateType::class, array(
+                    'widget' => 'single_text',
+                    'format' => 'dd/MM/yyyy',
+                    'empty_data' => '',
+                    'required' => false,
+                    'attr' => array(
+                        'class' => 'datepicker',
+                    ),
+                ))
+                ->add('searchValue', null, array(
+                    'required' => false,
+                ))
+                ->add('resxpage', ChoiceType::class, array(
+                    'choices' => array(
+                        "10" => 10,
+                        "25" => 25,
+                        "50" => 50,
+                        "100" => 100,
+                        "200" => 200,
+                        "500" => 500,
+                        "inf" => "∞",
+                    ),
+                    'required' => true,
+                ))
+                ->add('sort_by', HiddenType::class, array(
+                    'data' => 'date',
+                ))
+                ->add('sort_order', HiddenType::class, array(
+                    'data' => 'DESC',
+                ))
+                ->add('page', HiddenType::class, array(
+                    'attr' => array(
+                        'value' => 1,
+                    ),
+                ))
+        ;
+    }
+
+}

+ 17 - 3
Resources/config/services.yml

@@ -1,4 +1,18 @@
+#imports:
+#    - { resource: sonata-project/doctrine-orm-admin-bundle/config.yml }
+
 services:
-#    audit.example:
-#        class: AuditBundle\Example
-#        arguments: ["@service_id", "plain_value", "%parameter%"]
+    audit.audit_data.service:
+        class: AuditBundle\Services\AuditDataService
+        arguments: 
+            - "@doctrine.dbal.default_connection"
+            - "@simplethings_entityaudit.reader"
+            - "@simplethings_entityaudit.manager"
+            - "@doctrine.orm.entity_manager"
+
+    audit.form.type.audit_filter:
+        class: AuditBundle\Form\Type\AuditFilterType
+        arguments:
+            - '@audit.audit_data.service'
+        tags:
+            - { name: form.type }

+ 3 - 0
Resources/config/sonata-project/doctrine-orm-admin-bundle/config.yml

@@ -0,0 +1,3 @@
+sonata_doctrine_orm_admin:
+    audit:
+        force: false

+ 33 - 0
Resources/translations/AuditBundle.es.yml

@@ -0,0 +1,33 @@
+Auditoría de Entidades: Auditoría de Entidades
+FILTROS: FILTROS
+Entidad: Entidad
+Tipo de Revisión: Tipo de Revisión
+ID del Elemento: ID del Elemento
+Usuario: Usuario
+Fecha: Fecha
+Fecha Desde: Fecha Desde
+Fecha Hasta: Fecha Hasta
+Datos: Datos
+Página: Página
+Resultados por Página: Resultados por Página
+Buscar: Buscar
+Limpiar: Limpiar
+resultado(s): resultado(s)
+nothingfound: No se han encontrado resultados que coincidan con los parámetros indicados.
+readysearch: Ajuste los filtros y presione "Buscar" para realizar una consulta.
+'Detalles de la revisión: %rev%': 'Detalles de la revisión: %rev%'
+Resumen: Resumen
+Revisión: Revisión
+Id: Id
+Datos actuales de la entidad: Datos actuales de la entidad
+Campo: Campo
+Valor: Valor
+Revisiones efectuadas: Revisiones efectuadas
+Anterior: Anterior
+Actual: Actual
+Comparar: Comparar
+Regresar: Regresar
+Auditoría de Entidades: Auditoría de Entidades
+Comparativa entre las revisiones %oldRev% y %newRev%: Comparativa entre las revisiones %oldRev% y %newRev%
+Detalle de campos: Detalle de campos
+Sin cambios: Sin cambios

+ 89 - 0
Resources/views/Audit/compare.html.twig

@@ -0,0 +1,89 @@
+{% extends base_template %}
+
+{% block title %}
+    - {{ data.label }}
+{% endblock %}
+
+{% block breadcrumb %}
+    {% set links = [
+        {label: 'Auditoría de Entidades'|trans({}, 'AuditBundle'), class: '', link: url('entity_audit')},
+        {label: data.label, class: 'active', link: ''}
+    ] %}
+    {% include "BaseAdminBundle:Utils:breadcrumb.html.twig" with links %}
+{% endblock %}
+
+{% block list_filters %}
+    <fieldset class="filter_legend">
+        <div class="col-xs-12 col-md-12">   
+            <div class="box box-primary">
+                <table class="table table-bordered table-striped sonata-ba-list">
+                    <tbody>
+                        <tr class="filter inactive">
+                            <td class="filter-title" width="180"><b>{{ 'Entidad'|trans({}, 'AuditBundle') }}</b>:</td>
+                            <td class="filter-value">{{ data.className }}</td>
+                        </tr>
+                        <tr class="filter inactive">
+                            <td class="filter-title"><b>Id</b>:</td>
+                            <td class="filter-value">{{ data.id }}</td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+        </div>
+        <hr>
+        <div class="col-xs-12 col-md-12">   
+            <div class="box box-primary">
+                <div class="box-header">
+                    <h4 class="box-title"> {{ 'Detalle de campos'|trans({}, 'AuditBundle') }}: </h4>
+                </div>
+                <table class="table table-bordered table-striped sonata-ba-list">
+                    <tbody>
+                        <tr class="filter inactive">
+                            <td class="filter-title" width="180"><b>{{ 'Campo'|trans({}, 'AuditBundle') }}</b></td>
+                            <td class="filter-title"><b>{{ 'Sin cambios'|trans({}, 'AuditBundle') }}</b></td>
+                            <td class="filter-title"><b>{{ 'Anterior'|trans({}, 'AuditBundle') }} (Rev: <a href="{{ path('entity_audit_view_details', { 'className': data.className, 'id': data.id, 'rev': data.oldRev }) }}">{{ data.oldRev }}</a>)</b></td>
+                            <td class="filter-title"><b>{{ 'Actual'|trans({}, 'AuditBundle') }} (Rev: <a href="{{ path('entity_audit_view_details', { 'className': data.className, 'id': data.id, 'rev': data.newRev }) }}">{{ data.newRev }}</a>)</b></td>
+                        </tr>
+                    </tbody>
+                    <tbody>
+                        {% for field, value in data.diff %}
+                            <tr class="filter inactive">
+                                <td class="filter-value">{{ field }}</td>
+                                <td class="filter-value">
+                                    {% if value.same.timestamp is defined %}
+                                        {{ value.same | date("d/m/Y \\a \\l\\a\\s h:m:s") }}
+                                    {% elseif value.same is iterable %}
+                                        {{ dump(value.same) }}
+                                    {% else %}
+                                        {{ value.same }}
+                                    {% endif %}
+                                </td>
+                                <td class="filter-value">
+                                    {% if value.old.timestamp is defined %}
+                                        {{ value.old | date("d/m/Y \\a \\l\\a\\s h:m:s") }}
+                                    {% elseif value.old is iterable %}
+                                        {{ dump(value.old) }}
+                                    {% else %}
+                                        {{ value.old }}
+                                    {% endif %}
+                                </td>
+                                <td class="filter-value">
+                                    {% if value.new.timestamp is defined %}
+                                        {{ value.new | date("d/m/Y \\a \\l\\a\\s h:m:s") }}
+                                    {% elseif value.new is iterable %}
+                                        {{ dump(value.new) }}
+                                    {% else %}
+                                        {{ value.new }}
+                                    {% endif %}
+                                </td>
+                            </tr>
+                        {% endfor %}
+                    </tbody>
+                </table>
+                <a class="btn btn-small btn-primary" href="{{ path('entity_audit') }}">{{ 'Regresar'|trans({}, 'AuditBundle') }}</a>
+            </div>
+        </div>
+    </fieldset>
+{% endblock %}
+
+{% block content %}{% endblock %}

+ 220 - 0
Resources/views/Audit/revs.html.twig

@@ -0,0 +1,220 @@
+{% extends base_template %}
+
+{% if params.page is empty %}
+    {% set page = 1 %}
+{% else %}
+    {% set page = params.page %}
+{% endif %}
+
+{% set page_ini = 1 %}
+{% set page_end = params.pages %}
+
+{% if params.pages > 1 %}
+    {% if page == 1 %}
+        {% set page_prev = page_end %}
+        {% set page_next = page + 1 %}
+    {% elseif page == page_end %}
+        {% set page_prev = page - 1 %}
+        {% set page_next = 1 %}
+    {% else %}
+        {% set page_prev = page - 1 %}
+        {% set page_next = page + 1 %}
+    {% endif %}
+{% else %}
+    {% set page_prev = 1 %}
+    {% set page_next = 1 %}
+{% endif %}
+
+{% block title %}
+    - {{ params.label }}
+{% endblock %}
+
+{% block breadcrumb %}
+    {% set links = [{label: params.label, class: 'active', link: ''}] %}
+    {% include "BaseAdminBundle:Utils:breadcrumb.html.twig" with links %}
+{% endblock %}
+
+{% block list_filters %}
+    <style type="text/css">
+        .filter {
+            margin: 5px;
+            display: block;
+        }
+        .filter-title {
+            width: 200px;
+        }
+        .filter-value {
+            width: 400px;
+        }
+        input[type='text'] {
+            width: inherit;
+            padding: 4px;
+            -webkit-border-radius: 0px;
+            -moz-border-radius: 0px;
+            border-radius: 0px;
+            direction: ltr;
+            border: 1px solid #cccccc;
+            color: #555;
+            font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+            background: #fff none repeat scroll 0 0;
+        }
+    </style>    
+    <div class="col-xs-12 col-md-12">   
+        <div class="box">   
+            <form id="filter_form" action="{{ url('entity_audit') }}" method="POST">
+                <fieldset class="filter_legend">
+                    <legend class="filter_legend inactive">{{ 'FILTROS'|trans({}, 'AuditBundle') }}</legend>
+                    <div class="filter_container {%if data %}inactive{% endif %}" style="display:block; padding: 0 15px 15px 15px;">
+                        <table class="bordered-table">
+                            <tbody>
+                                <tr class="filter inactive">
+                                    <td class="filter-title" width="200">{{ 'Entidad'|trans({}, 'AuditBundle') }}:</td>
+                                    <td class="filter-value form-inline">
+                                        {{ form_widget(form.entity) }}
+                                    </td>
+                                </tr>
+                                <tr class="filter inactive">
+                                    <td class="filter-title" width="200">{{ 'Tipo de Revisión'|trans({}, 'AuditBundle') }}:</td>
+                                    <td class="filter-value form-inline">
+                                        {{ form_widget(form.types) }}
+                                    </td>
+                                </tr>
+                                <tr class="filter inactive">
+                                    <td class="filter-title" width="200">{{ 'ID del Elemento'|trans({}, 'AuditBundle') }}:</td>
+                                    <td class="filter-value form-inline">
+                                        {{ form_widget(form.idx) }}
+                                    </td>
+                                </tr>
+                                <tr class="filter inactive">
+                                    <td class="filter-title" width="200">{{ 'Usuario'|trans({}, 'AuditBundle') }}:</td>
+                                    <td class="filter-value form-inline">
+                                        {{ form_widget(form.users) }}
+                                    </td>
+                                </tr>
+                                <tr class="filter inactive">
+                                    <td class="filter-title">{{ 'Fecha Desde'|trans({}, 'AuditBundle') }}:</td>							<td class="filter-value form-inline">
+                                        {{ form_widget(form.dateFrom) }}
+                                    </td>
+                                </tr>
+                                <tr class="filter inactive">
+                                    <td class="filter-title">{{ 'Fecha Hasta'|trans({}, 'AuditBundle') }}:</td>
+                                    <td class="filter-value">
+                                        {{ form_widget(form.dateTo) }}
+                                    </td>
+                                </tr>
+                                <tr class="filter inactive">
+                                    <td class="filter-title" width="200">{{ 'Datos'|trans({}, 'AuditBundle') }}:</td>
+                                    <td class="filter-value form-inline">
+                                        {{ form_widget(form.searchValue) }}
+                                    </td>
+                                </tr>
+                                <tr class="filter inactive">
+                                    <td class="filter-title" width="200">{{ 'Resultados por Página'|trans({}, 'AuditBundle') }}:</td>
+                                    <td class="filter-value form-inline">
+                                        {{ form_widget(form.resxpage) }}
+                                    </td>
+                                </tr>
+                            </tbody>
+                        </table>
+
+                        {{ form_widget(form.page) }}
+                        {{ form_widget(form.sort_by) }}
+                        {{ form_widget(form.sort_order) }}
+                        {{ form_row(form._token) }}
+                        <input class="btn btn-small btn-primary" type="submit" value="{{ 'Buscar'|trans({}, 'AuditBundle') }}">
+                        <a class="btn btn-small btn-default" href="{{ url('entity_audit') }}">{{ 'Limpiar'|trans({}, 'AuditBundle') }}</a>
+                    </div>
+                </fieldset>
+            </form>
+        </div>
+    </div>
+{% endblock %}
+
+{% block list_table %}
+    <div class="col-xs-12 col-md-12">
+        <div class="box box-primary" style="margin-bottom: 100px; min-height: ">
+            {% if data and is_post %}
+                <div class="box-body no-padding table-responsive">
+                    <p style="float: right">{{ total }} {{ 'resultado(s)'|trans({}, 'AuditBundle') }}</p>
+                    <table class="table table-bordered table-striped sonata-ba-list">
+                        <thead>
+                            <tr>
+                                <th>{{ 'Entidad'|trans({}, 'AuditBundle') }}</th>
+                                <th>{{ 'ID del Elemento'|trans({}, 'AuditBundle') }}</th>
+                                <th>{{ 'Tipo de Revisión'|trans({}, 'AuditBundle') }}</th>
+                                <th>{{ 'Usuario'|trans({}, 'AuditBundle') }}</th>
+                                <th>{{ 'Fecha'|trans({}, 'AuditBundle') }}</th>
+                                <th>{{ 'Datos'|trans({}, 'AuditBundle') }}</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            {% for item in data %}
+                                <tr>
+                                    <td><a href="{{ path('entity_audit_view_details', { 'className': entity, 'id':item['id'], 'rev':item['rev'] }) }}">{{ item['rev'] }}</a></td>
+                                    <td>{{ item['id'] }}</td>
+                                    <td>{{ item['revtype'] }}</td>
+                                    <td>{{ item['username'] }}</td>
+                                    <td>{{ item['timestamp'] }}</td>
+                                    <td style="font-size: 14px">
+                                        <ul>
+                                            {% for column in columns[entity] %}
+                                                <li style="float: left; width: 23%; margin-right: 2%; line-height: 1 !important"><strong>{{ column }}:</strong> {{ item[column] }}</li>
+                                                {% endfor %}
+                                        </ul>
+                                    </td>
+                                </tr>
+                            {% endfor %}
+                        </tbody>
+                        <tfoot>
+                            <tr>
+                                <td colspan="6" class="pager">
+                                    <div class="pagination">
+                                        <ul style="float: right; text-align: center">
+                                            <p>{{ 'Página'|trans({}, 'AuditBundle') }} {{ page }} {{ 'de'|trans({}, 'AuditBundle') }} {{ params.pages }}</p>
+                                            {% if params.pages > 1 %}
+                                                <li>
+                                                    <a href="javascript:void(0)" onclick="SetPage('{{ page_ini }}');">«</a>
+                                                </li>
+                                                <li>
+                                                    <a href="javascript:void(0)" onclick="SetPage('{{ page_prev }}');">‹</a>
+                                                </li>
+                                                <li>
+                                                    <a href="javascript:void(0)" onclick="SetPage('{{ page_next }}');">›</a>
+                                                </li>
+                                                <li>
+                                                    <a href="javascript:void(0)" onclick="SetPage('{{ page_end }}');">»</a>
+                                                </li>
+                                            {% endif %} 
+                                        </ul>
+                                    </div>
+                                </td>
+                            </tr>	 
+                        </tfoot>
+                    </table>
+                </div>
+            {% else %}
+                <div style="padding: 15px;">
+                    {% if is_post %}
+                        {{ 'nothingfound' |trans({}, 'AuditBundle') }}
+                    {% else %}
+                        {{ 'readysearch' |trans({}, 'AuditBundle') }}
+                    {% endif %}
+                </div>
+            {% endif %}
+        </div>
+    </div>
+
+    <script type="text/javascript" src="{{ asset('bundles/sonataadmin/vendor/jqueryui/ui/i18n/jquery.ui.datepicker-es.js') }}"></script>
+    <script type="text/javascript">
+        $(document).ready(function () {
+            $('.datepicker').datepicker({
+                dateFormat: 'dd/mm/yy'
+            });
+        });
+
+        function SetPage(page) {
+            $('[name$="[page]"]').val(page);
+            $('#filter_form').submit();
+        }
+    </script> 
+{% endblock %}

+ 120 - 0
Resources/views/Audit/view.html.twig

@@ -0,0 +1,120 @@
+{% extends base_template %}
+
+{% block title %}
+    - {{ data.label }}
+{% endblock %}
+
+{% block breadcrumb %}
+    {% set links = [
+        {label: 'Auditoría de Entidades'|trans({}, 'AuditBundle'), class: '', link: url('entity_audit')},
+        {label: data.label, class: 'active', link: ''}
+    ] %}
+    {% include "BaseAdminBundle:Utils:breadcrumb.html.twig" with links %}
+{% endblock %}
+
+{% block list_filters %}
+    <fieldset class="filter_legend">
+        <div class="col-xs-12 col-md-12">   
+            <div class="box box-primary">
+                <div class="box-header">
+                    <h4 class="box-title"> {{ 'Resumen'|trans({}, 'AuditBundle') }}: </h4>
+                </div>
+                <table class="table table-bordered table-striped sonata-ba-list">
+                    <tbody>
+                        <tr class="filter inactive">
+                            <td class="filter-title" width="180"><b>{{ 'Revisión'|trans({}, 'AuditBundle') }}</b>:</td>
+                            <td class="filter-value">{{ data.rev }}</td>
+                        </tr>
+                        <tr class="filter inactive">
+                            <td class="filter-title"><b>{{ 'Entidad'|trans({}, 'AuditBundle') }}</b>:</td>
+                            <td class="filter-value">{{ data.className }}</td>
+                        </tr>
+                        <tr class="filter inactive">
+                            <td class="filter-title"><b>{{ 'Id'|trans({}, 'AuditBundle') }}</b>:</td>
+                            <td class="filter-value">{{ data.id }}</td>
+                        </tr>
+                        <tr class="filter inactive">
+                            <td class="filter-title"><b>{{ 'Usuario'|trans({}, 'AuditBundle') }}</b>:</td>
+                            <td class="filter-value">{{ data.revdata.username | default('(anonymous)')}}</td>
+                        </tr>
+                        <tr class="filter inactive">
+                            <td class="filter-title"><b>{{ 'Fecha'|trans({}, 'AuditBundle') }}</b>:</td>
+                            <td class="filter-value">{{ data.revdata.timestamp | date("d/m/Y \\a \\l\\a\\s h:m:s") }} hs</td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+        </div>
+        <hr>
+        <div class="col-xs-12 col-md-12">   
+            <div class="box box-primary">
+                <div class="box-header">
+                    <h4 class="box-title"> {{ 'Datos actuales de la entidad'|trans({}, 'AuditBundle') }}: </h4>
+                </div>
+                <table class="table table-bordered table-striped sonata-ba-list">
+                    <tbody>
+                        <tr class="filter inactive">
+                            <td class="filter-title" width="180"><b>{{ 'Campo'|trans({}, 'AuditBundle') }}</b></td>
+                            <td class="filter-title"><b>{{ 'Valor'|trans({}, 'AuditBundle') }}</b></td>
+                        </tr>
+                    </tbody>
+                    <tbody>
+                        {% for field, value in data.details %}
+                            <tr class="filter inactive">
+                                <td class="filter-value">{{ field }}</td>
+                                <td class="filter-value">
+                                    {% if value.timestamp is defined %}
+                                        {{ value | date("d/m/Y \\a \\l\\a\\s h:m:s") }}
+                                    {% elseif value is iterable %}
+                                        {{ dump(value) }}
+                                    {% else %}
+                                        {{ value }}
+                                    {% endif %}
+                                </td>
+                            </tr>
+                        {% endfor %}
+                    </tbody>
+                </table>
+            </div>
+        </div>
+        <hr>
+        <div class="col-xs-12 col-md-12">   
+            <div class="box box-primary">
+                <div class="box-header">
+                    <h4 class="box-title"> {{ 'Revisiones efectuadas'|trans({}, 'AuditBundle') }} ({{ data.revisions|length }}): </h4>
+                </div>
+                <form action="{{ path('entity_audit_compare_revs', { 'className': data.className, 'id': data.id }) }}" method="get">
+                    <div style="display:block; height:200px; overflow:auto; margin-bottom:10px">
+                        <table class="table table-bordered table-striped sonata-ba-list" style="width:100%;margin-top:0px;margin-bottom:0px">
+                            <tbody>
+                                <tr class="filter inactive">
+                                    <td class="filter-title" style="width:90px;"><b>{{ 'Revisión'|trans({}, 'AuditBundle') }}</b></td>
+                                    <td class="filter-title" style="width:250px;"><b>{{ 'Fecha'|trans({}, 'AuditBundle') }}</b></td>
+                                    <td class="filter-title" style="width:150px;"><b>{{ 'Usuario'|trans({}, 'AuditBundle') }}</b></td>
+                                    <td class="filter-title" style="width:50px;"><b>{{ 'Anterior'|trans({}, 'AuditBundle') }}</b></td>
+                                    <td class="filter-title" style="width:50px;"><b>{{ 'Actual'|trans({}, 'AuditBundle') }}</b></td>
+                                </tr>
+                            </tbody>
+                            <tbody>
+                                {% for revision in data.revisions %}
+                                    <tr class="filter inactive">
+                                        <td class="filter-value" style="text-align:center;"><a href="{{ path('entity_audit_view_details', { 'className': data.className, 'id': data.id, 'rev': revision.rev }) }}">{{ revision.rev }}</a></td>
+                                        <td class="filter-value">{{ revision.timestamp | date("d/m/Y \\a \\l\\a\\s h:m:s") }}</td>
+                                        <td class="filter-value">{{ revision.username|default('(anonymous)') }}</td>
+
+                                        <td class="filter-value" style="text-align:center"><input type="radio" name="oldRev" value="{{ revision.rev }}"{% if loop.index == 2 %} checked="checked"{% endif %} /></td>
+                                        <td class="filter-value" style="text-align:center"><input type="radio" name="newRev" value="{{ revision.rev }}"{% if loop.index == 1 %} checked="checked"{% endif %} /></td>
+                                    </tr>
+                                {% endfor %}
+                            </tbody>
+                        </table>
+                    </div>
+                    <input class="btn btn-small btn-primary" type="submit" value="{{ 'Comparar'|trans({}, 'AuditBundle') }}" />
+                    <a class="btn btn-small btn-default" href="{{ path('entity_audit') }}">{{ 'Regresar'|trans({}, 'AuditBundle') }}</a>
+            </div>
+        </div>
+    </form>	    
+</fieldset>
+{% endblock %}
+
+{% block content %}{% endblock %}

+ 388 - 0
Services/AuditDataService.php

@@ -0,0 +1,388 @@
+<?php
+
+namespace AuditBundle\Services;
+
+use Doctrine\DBAL\Connection;
+use Doctrine\ORM\EntityManagerInterface;
+use SimpleThings\EntityAudit\AuditManager;
+use SimpleThings\EntityAudit\AuditReader;
+use SimpleThings\EntityAudit\Revision;
+
+class AuditDataService
+{
+
+    /**
+     * @var Connection
+     */
+    private $connection;
+
+    /**
+     * @var AuditReader
+     */
+    private $reader;
+    
+    /**
+     * @var AuditManager 
+     */
+    private $auditManager;
+    
+    /**
+     * @var EntityManagerInterface 
+     */
+    private $entityManager;
+    
+    
+    /**
+     * @param Connection $connection
+     * @param AuditReader $reader
+     * @param AuditManager $auditManager
+     * @param EntityManagerInterface $entityManager
+     */
+    public function __construct(Connection $connection, AuditReader $reader, AuditManager $auditManager, EntityManagerInterface $entityManager)
+    {
+        $this->connection = $connection;
+        $this->reader = $reader;
+        $this->auditManager = $auditManager;
+        $this->entityManager = $entityManager;
+    }
+
+    /**
+     * Get all revisions filtered for user and timestamp
+     * 
+     * @param string $user
+     * @param DateTime $dateFrom
+     * @param DateTime $dateTo
+     * @param int $page
+     * 
+     * @return type
+     */
+    public function getRevisionsData($user, $dateFrom, $dateTo, $page)
+    {
+        //Set default page
+        $show = 25;
+
+        if (!$page) {
+            $page = 1;
+        }
+
+        $sqlct = "SELECT Count(id) AS Total FROM revisions WHERE (id <> 0 AND username <> '') ";
+        foreach ($user as $key => $usr) {
+            if ($key == 0) {
+                $sqlct .= "AND username LIKE '%{$usr}%' ";
+            } else {
+                $sqlct .= "OR username LIKE '%{$usr}%' ";
+            }
+        }
+        if ($dateFrom != '') {
+            $sqlct .= "AND (timestamp >= '{$dateFrom} 00:00:00') ";
+        }
+        if ($dateTo != '') {
+            $sqlct .= "AND (timestamp <= '{$dateTo} 23:59:59') ";
+        }
+
+        // Sql for get data
+        $sqlrev = "SELECT timestamp,id AS rev FROM revisions WHERE (id <> 0 AND username <> '') ";
+        foreach ($user as $key => $usr) {
+            if ($key == 0) {
+                $sqlrev .= "AND username LIKE '%{$usr}%' ";
+            } else {
+                $sqlrev .= "OR username LIKE '%{$usr}%' ";
+            }
+        }
+        if ($dateFrom != '') {
+            $sqlrev .= "AND (timestamp >= '{$dateFrom} 00:00:00') ";
+        }
+        if ($dateTo != '') {
+            $sqlrev .= "AND (timestamp <= '{$dateTo} 23:59:59') ";
+        }
+
+        // Count results
+        $regs = $this->connection->fetchColumn($sqlct, array(1), 0);
+
+        // Set pagination
+        $from = ($page - 1) * $show;
+        $To = ($page) * $show;
+
+        if ($To > $regs) {
+            $To = $regs;
+        }
+
+        $sqlrev .= "ORDER BY timestamp DESC LIMIT " . $from . ", " . $To;
+
+        $query_result = $this->connection->fetchAll($sqlrev);
+        $results_processed_changes = $this->results_with_processed_changes($query_result);
+
+        $tdata = array(
+            'regs' => $regs,
+            'revisions' => $results_processed_changes,
+        );
+        
+        return ($tdata);
+    }
+
+    /**
+     * @return array
+     */
+    public function getRevisionUsers()
+    {
+        $query = "SELECT DISTINCT(SUBSTRING_INDEX(username, '(', 1)) as username FROM revisions ORDER BY username ASC;";
+
+        $usernames = array();
+        foreach ($this->connection->fetchAll($query) as $username) {
+            $usernames[$username['username']] = $username['username'];
+        }
+
+        return $usernames;
+    }
+
+    /**
+     * Get all revisions with the changes processed
+     * 
+     * @param array $query_result
+     * 
+     * @return array
+     */
+    public function results_with_processed_changes($query_result)
+    {
+        $param_query_result = $query_result;
+        foreach ($param_query_result as $key => &$data) {
+            $entity_results = array();
+            $data['rev_data'] = $this->reader->findRevision($data['rev']);
+            $data['rev_entities_data'] = $this->reader->findEntitiesChangedAtRevision($data['rev']);
+            #Get entity of the revision
+            $entity_revs = $data['rev_entities_data'];
+
+            foreach ($entity_revs as $result) {
+                $changes_all = array();
+                #Get all revisions for each class in table revision
+                $history_for_class = $this->reader->findRevisions($result->getClassName(), $result->getId());
+
+                foreach ($history_for_class as $rev_history) {
+                    if ($rev_history->getRev() < $data['rev']) {
+                        $diff = $this->reader->diff($result->getClassName(), $result->getId(), $rev_history->getRev(), $data['rev']);
+                        foreach ($diff as $field => $value) {
+                            if ($value['old'] != "") {
+                                if (!is_a($value['old'], 'DateTime')) {
+                                    $changes = array();
+                                    $changes['field'] = $field;
+                                    $changes['old'] = $value['old'];
+                                    $changes['new'] = $value['new'];
+                                    array_push($changes_all, $changes);
+                                }
+                            }
+                        }
+                        break;
+                    }
+                }
+            }
+            if (!isset($changes_all)) {
+                $changes_all = array();
+            }
+            $data['changed_field'] = $changes_all;
+        }
+
+        return $param_query_result;
+    }
+
+    /**
+     * Applied diff between revisions
+     * 
+     * @param string $className
+     * @param int $id
+     * @param Revision $oldRev
+     * @param Revision $newRev
+     * 
+     * @return array
+     */
+    public function diff($className, $id, $oldRev, $newRev)
+    {
+        return $this->reader->diff($className, $id, $oldRev, $newRev);
+    }
+    
+    /**
+     * Get data of a revision
+     * 
+     * @param string $className
+     * @param int $id
+     * @param Revision $rev
+     * 
+     * @return array
+     */
+    public function viewRevision($className, $id, $rev)
+    {
+        $entity = $this->reader->find($className, $id, $rev);
+        
+        return array(
+            'rev_details' => $this->reader->getEntityValues($className, $entity),
+            'rev_revisions' => $this->reader->findRevisions($className, $id),
+            'revdata' => $this->reader->findRevision($rev),
+        );
+    }
+    
+    /**
+     * @return array
+     */
+    public function getEntities()
+    {
+        $mdFactory = $this->auditManager->getMetadataFactory();
+        $meta = $this->entityManager->getMetadataFactory()->getAllMetadata();
+        $entities = array();
+        foreach ($meta as $m) {
+            $entities[$m->getName()] = $m->table['name'];
+        }
+        ksort($entities);
+        foreach ($entities as $key => $entityName) {
+            if (!$mdFactory->isAudited($key)) {
+                unset($entities[$key]);
+            }
+        }
+        
+        return $entities;
+    }
+    
+    /**
+     * @return array
+     */
+    public function getColumns()
+    {
+        $columns = array();
+        $meta = $this->entityManager->getMetadataFactory()->getAllMetadata();
+        foreach ($meta as $m) {
+            $columns[$m->getName()] = $m->getColumnNames();
+        }
+        
+        return $columns;
+    }
+    
+    /**
+     * @param array $data
+     * 
+     * @return array
+     */
+    public function getResults($data)
+    {
+        $entities = $this->getEntities();
+        $pages = 1;
+        $users = array();
+        $types = array();
+        
+        extract($data); // retorna la data del filter form
+//        var_dump($page);
+//        die;
+        $entity = array_search($entity, $entities);
+        
+        $from = $this->buildFrom($entity);
+        $where = $this->buildWhere($entity, $users, $types, $idx, $dateFrom, $dateTo, $searchValue);
+
+        $sql_cnt = $this->buildSelect($entity, true) . $from . $where;
+        $count = $this->connection->query($sql_cnt)->fetchAll()[0]['total'];
+
+        $sql = $this->buildSelect($entity) . $from . $where;
+        $sql .= "ORDER BY R.timestamp DESC ";
+
+        if ($resxpage != "inf") {
+            $pages = ceil(($count / floatval($resxpage)));
+            $from = ($page - 1) * $resxpage;
+            $to = ($page) * $resxpage;
+            $sql .= "LIMIT {$from},{$to}";
+        }
+        
+        return array(
+            'result' => $this->connection->query($sql)->fetchAll(),
+            'count' => $count,
+            'page' => $page,
+            'pages' => $pages,
+        );
+    }
+    
+    /**
+     * @param string $entity
+     * @param bool $count
+     * 
+     * @return string
+     */
+    private function buildSelect($entity, $count = false)
+    {
+        $select = 'SELECT C.rev, C.id, C.revtype, R.username, R.timestamp ';
+        if ($count) {
+            $select = 'SELECT COUNT(*) as total ';
+        }
+        $columns = $this->getColumns();
+        foreach ($columns[$entity] as $column) {
+            $select .= ", C." . $column . " ";
+        }
+        
+        return $select;
+    }
+    
+    /**
+     * @param string $entity
+     * 
+     * @return string
+     */
+    private function buildFrom($entity)
+    {
+        $entities = $this->getEntities();
+        
+        return "FROM `{$entities[$entity]}_audit` AS C INNER JOIN `revisions` AS R ON C.rev = R.id ";
+    }
+    
+    /**
+     * @param string $entity
+     * @param array $users
+     * @param array $types
+     * @param int $idx
+     * @param string $dateFrom
+     * @param string $dateTo
+     * @param string $searchValue
+     * 
+     * @return string
+     */
+    private function buildWhere($entity, $users = null, $types = null, $idx = null, $dateFrom = null, $dateTo = null, $searchValue = null)
+    {
+        $where = '';
+        $columns = $this->getColumns();
+        if ($users || $types || $idx || $dateFrom || $dateTo || $searchValue) {
+            $where .= 'WHERE C.id > 0 ';
+            if ($users) {
+                foreach ($users as $key => $user) {
+                    if ($key == 0) {
+                        $where .= "AND (R.username = '{$user}' ";
+                    } else {
+                        $where .= "OR R.username = '{$user}' ";
+                    }
+                }
+                $where .= ') ';
+            }
+            if ($types) {
+                foreach ($types as $key => $type) {
+                    if ($key == 0) {
+                        $where .= "AND (C.revtype = '{$type}' ";
+                    } else {
+                        $where .= "OR C.revtype = '{$type}' ";
+                    }
+                }
+                $where .= ') ';
+            }
+            if ($idx) {
+                $where .= "AND (C.id = '{$idx}') ";
+            }
+            if ($dateFrom && $dateTo) {
+                $where .= " AND R.timestamp BETWEEN '{$dateFrom->format('Y-m-d')} 00:00:00' AND '{$dateTo->format('Y-m-d')} 23:59:59' ";
+            }
+            if ($searchValue) {
+                foreach ($columns[$entity] as $key => $col) {
+                    if ($key == 0) {
+                        $where .= " AND (C.{$col} LIKE '{$searchValue}' ";
+                    } else {
+                        $where .= " OR C.{$col} LIKE '{$searchValue}' ";
+                    }
+                }
+                $where .= ') ';
+            }
+        }
+        
+        return $where;
+    }
+    
+}