<?php

/**
 * Questo file è parte di Pongho.
 *
 * @author    Daniele De Nobili
 * @author    Daniele Termini
 * @copyright Web Agency Meta Line S.r.l.
 * @package   Application\Admin
 */

namespace Application\Admin\Controller;

use ActiveRecord\Column;
use Application\Admin\Form\FilterSubject;
use Application\Admin\Form\FormBuilder;
use Application\Admin\Form\FormConfig;
use Application\Admin\Form\Generator\AdminGenerator;
use Application\Admin\Form\Repeater\RowMain;
use Application\Admin\Model\ArchiveLastView;
use Application\Admin\Model\Filter;
use Application\Admin\Model\Manager\ArchiveLastViewManager;
use Application\Admin\Utilities\ArchiveManager;
use Application\Cms\Model\CustomFieldValue;
use Application\Core\Model\Account;
use Application\Core\Model\File;
use Application\Core\Utilities\AssociativeArrayObject;
use Application\Core\Utilities\Pagination;
use Pongho\Form\Field\DateField;
use Pongho\Form\Field\DateTimeField;
use Pongho\Form\Field\HiddenField;
use Pongho\Form\Field\SelectField;
use Pongho\Form\Field\TextField;
use Pongho\Form\Field\TimeField;
use Pongho\Form\Fieldset;
use Pongho\Form\Form;
use Pongho\Form\Repeater\RepeaterRow;
use Pongho\Form\Repeater\SerializableRepeater;
use Pongho\Form\Subject\ModelSubject;
use Pongho\Http\Exception\HttpException;
use Pongho\Http\Exception\HttpNotFoundException;
use Pongho\Http\Exception\HttpUnauthorizedException;
use Pongho\Http\JsonResponse;
use Pongho\Http\RedirectResponse;
use Pongho\Http\Response;
use Pongho\Template\View;
use Pongho\Template\ViewClosure;
use Pongho\Utilities\Inflector;

abstract class Crud2Controller extends AdminController
{
    /**
     * @var ArchiveManager
     */
    protected $am;

    /**
     * @var int
     */
    protected $page = 1;

    /**
     * Restituisce il namespace per il salvataggio dei filtri
     *
     * @return string
     *
     * @todo Modificare la visibilità su protected
     */
    abstract public function getNamespace();

    /**
     * Restituisce il namespace usato negli eventi
     *
     * @param string $eventName
     * @return string
     *
     * @todo Modificare la visibilità su protected
     */
    public function getEventNamespace($eventName)
    {
        return 'admin.' . $this->getNamespace() . '.' . $eventName;
    }

    /**
     * Restituisce le opzioni per il modello dell'elenco
     *
     * @return array
     */
    protected function getModelOptions()
    {
        return [];
    }

    /**
     * Restituisce i campi del modello filtrabili tramite ricerca libera
     *
     * @return array
     */
    protected function getSearchFields()
    {
        return [];
    }

    /**
     * Restituisce la configurazione dei campi filtrabili tramite i filtri
     *
     * @return array
     *
     * @todo: Rinominare con getFiltersConfig(). Attenzione ai controller di Qjob che hanno già un metodo con questo nome.
     */
    protected function getModelFieldsConfig()
    {
        return [];
    }

    /**
     * Restituisce la stringa del titolo dell'archivio.
     *
     * @return string
     */
    protected function getArchiveTitle()
    {
        return $this->getHelper()->getTranslator()->trans('List');
    }

    /**
     * Restituisce il testo del pulsante "add_new".
     *
     * @return string
     */
    protected function getArchiveAddButtonText()
    {
        return $this->getHelper()->getTranslator()->trans('Add');
    }

    /**
     * Restituisce il testo per il link "add_first".
     *
     * @return string
     */
    protected function getArchiveAddFirstText()
    {
        return $this->getArchiveAddButtonText();
    }

    /**
     * Restituisce il testo per la tabella quando non contiene record e non sono in ricerca
     *
     * @return string
     */
    protected function getArchiveEmptyRecordsetText()
    {
        return $this->getHelper()->getTranslator()->trans('No records to show yet');
    }

    /**
     * Indica se si ha il permesso di utilizzare l'azione "add"
     *
     * @return bool
     */
    protected function canAdd()
    {
        return false;
    }

    /**
     * Indica al template se visualizzare il tasto "Aggiungi"
     *
     * @return bool
     */
    protected function hasAddButton()
    {
        return $this->canAdd();
    }

    /**
     * Restituisce il testo per il link "search_no_results"
     *
     * @return string
     */
    protected function getArchiveSearchNoResultsText()
    {
        return $this->getHelper()->getTranslator()->trans('The search did not return any result');
    }

    /**
     * Restituisce i valori per i campi enum dei filtri
     *
     * @return array
     *
     * @example
     * $fields = array(
     *   'fieldname' => array(
     *     'value_1' => 'label_1',
     *     'value_2' => 'label_2',
     *   ),
     * );
     */
    protected function getFilterEnumValues()
    {
        return [];
    }

    /**
     * Restituisce i valori per i campi select dei filtri (un array di funzioni anonime)
     *
     * @return array
     *
     * @example
     * $fields = array(
     *   'fieldname' => function() {
     *     $values = array();
     *     foreach ( Model::all() as $model )
     *     {
     *       $values[$model->id] = $model->label;
     *     }
     *     return $values;
     *   },
     * );
     */
    protected function getFilterSelectValues()
    {
        return [];
    }

    /**
     * Restituisce i valori per i filtri rapidi
     *
     * @return array
     */
    protected function getFastFilters()
    {
        return [];
    }

    /**
     * Restituisce il nome della classe del modello.
     *
     * @return string
     */
    abstract public function getModelClass();

    /**
     * Restituisce la lista dei campi da elaborare.
     *
     * @param \ActiveRecord\Base $model
     * @return string
     *
     * @todo Modificare la visibilità su protected
     */
    public function getAddEditTitle($model)
    {
        if ($this->getAction() === 'add') {
            return $this->getHelper()->getTranslator()->trans('Add');
        }

        return $this->getHelper()->getTranslator()->trans('Edit');
    }

    /**
     * @return Response
     */
    abstract protected function addEdit();

    /**
     * Controlla quali CustomField applicare al modello corrente e li aggiunge alla Form
     *
     * @return FormConfig
     */
    protected function addCustomFieldsToFormFields(FormConfig $config)
    {
        return $config;
    }

    /**
     * Restituisce le opzioni di compatibilità delle regole
     *
     * @return array
     */
    protected function getCustomFieldRulesOptions(ModelSubject $subject)
    {
        return [];
    }

    /**
     * Azione per eliminare un file caricato tramite custom fields.
     *
     * @return Response
     */
    public function deletecustomfileAction()
    {
        $custom_field_id = $this->getRequest()->query->get('field');
        $custom_field = CustomFieldValue::find($custom_field_id);

        if ($custom_field) {
            $file = File::find($custom_field->field_value);

            if ($file) {
                $file->delete();
            }

            $custom_field->delete();
        }

        if ($this->getRequest()->isAjax()) {
            return $this->getHelper()->displayJsonMessage(
                $this->getHelper()->getTranslator()->trans('The file has been deleted')
            );
        }

        return new RedirectResponse(
            $this->url(sprintf('/%s/edit/%d/', $this->getParameter('path'), $this->getParameter('id')))
        );
    }

    /**
     * @return array
     */
    protected function getTableColumns()
    {
        return [];
    }

    /**
     * Restituisce il percorso del template per l'archivio del modello
     *
     * @return string
     */
    public function getArchiveTemplatePath()
    {
        return __DIR__ . '/../Resources/views/list.php';
    }

    /**
     * Restituisce il percorso del template per la tabella dell'archivio
     *
     * @return string
     */
    protected function getArchiveTableTemplatePath()
    {
        return __DIR__ . '/../Resources/views/list_table.php';
    }

    /**
     * Azione `index`.
     *
     * @return Response
     */
    public function indexAction()
    {
        $response = $this->getHelper()->notifyUntil($this, $this->getEventNamespace('notify_index'));

        if ($response instanceof Response) {
            return $response;
        }

        return $this->displayArchive();
    }

    /**
     * Azione `page`.
     *
     * @return Response
     */
    public function pageAction()
    {
        $this->page = $this->getParameter('page', 1, true);

        return $this->displayArchive();
    }

    /**
     * Visualizza l’archivio dei record.
     *
     * @return Response
     */
    public function displayArchive()
    {
        $request = $this->getRequest();
        $filterId = intval($request->query->get('filter'));

        $am = $this->getArchiveManager();

        $this->getHelper()->notify(
            $this,
            $this->getEventNamespace('notify_archive_before_filter'),
            ['archive_manager' => $am]
        );

        /** @var Filter $filter */
        $filter = null;
        if ($filterId) {
            $filter = Filter::find($filterId);
        }

        if ($filter) {
            $am->setFilters($filter->getFilters());
        } else {
            $filter = new Filter();
            $f = $request->query->get('f', []);

            if ($f) {
                $am->setFilters($f);
            }
        }

        $form = $this->getFiltersForm($filter);

        // Se sono in POST, salvo il filtro
        if ($request->getMethod() === 'POST') {
            // Controllo che ci sia tutto quello che mi serve per salvare il filtro
            $filters = $request->post->get('filters', []);

            if (!isset($filters['name']) || $filters['name'] === '') {
                $form->addError(
                    'name',
                    $this->getHelper()->getTranslator()->get('To save a filter the name is required')
                );
            }

            /** @var \Application\Core\User $user */
            $user = $this->getContainer()->get('current_user');

            $form
                ->setValue('namespace', $this->getNamespace())
                ->setValue('user_id', $user->getAccount()->getId())
                ->handleRequest($request);

            // Ricarico la form dei filtri e l'elenco dei filtri
            return new JsonResponse([
                'filters' => $this->getFiltersListHtml(),
                'form'    => $form->render(),
            ]);
        }

        $this->getHelper()->notify(
            $this,
            $this->getEventNamespace('notify_archive_after_filter'),
            [
                'archive_manager' => $am,
            ]
        );

        // Se sono in AJAX restituisco solo le parti che cambiano
        if ($request->isAjax()) {
            $list = new View($this->getArchiveTableTemplatePath());

            $list
                ->assignVars($this->getHelper()->getViewGlobalVariables())
                ->assignVars([
                    'mass_action_url'     => $this->getMassActionUrl(),
                    'mass_action_buttons' => $this->getMassActionButtons(),
                    'pagination'          => $am->getPagination(),
                    'table'               => $am->getTable(),
                ])
            ;

            $this->beforeRenderArchive();

            return new JsonResponse([
                'list'         => $list->render(),
                'form'         => $form->render(),
                'fast_filters' => $am->isFastFiltersEnabled() ? $am->getFastFilters()->render() : '',
                'search_form'  => $this->getSearchFormCode(),
            ]);
        }

        // In tutti gli altri casi...

        /** @var \Application\Admin\Utilities\FilterOperators $filterOperators */
        $filterOperators = $this->getContainer()->get('filter_operators');

        $operators = $this->getHelper()->filter(
            $this,
            $this->getEventNamespace('filter_filters_operators'),
            $filterOperators->getOperators()
        );

        $operators = json_encode($operators);

        $enums = json_encode($this->getFilterEnumValuesWrapper());

        $this->getHelper()
            ->addJavascript(pongho_url(
                '/Application/Admin/Resources/vendor/jqueryui-nestedsortable/jquery.ui.nestedSortable.js?v='
                . filemtime(PONGHO_PATH . '/Application/Admin/Resources/vendor/jqueryui-nestedsortable/jquery.ui.nestedSortable.js')
            ))
            ->addJavascript(pongho_url(
                '/Application/Admin/Resources/views/js/plugins.js?v='
                . filemtime(PONGHO_PATH . '/Application/Admin/Resources/views/js/plugins.js')
            ))
            ->addJavascript(pongho_url(
                '/Application/Admin/Resources/vendor/jquery-urlparser/purl.js?v='
                . filemtime(PONGHO_PATH . '/Application/Admin/Resources/vendor/jquery-urlparser/purl.js')
            ))
            ->addJavascript(pongho_url(
                '/Application/Admin/Resources/views/js/jquery.filters.js?v='
                . filemtime(PONGHO_PATH . '/Application/Admin/Resources/views/js/jquery.filters.js')
            ))
            ->addJavaScriptInline("$('#filters').data('operators', {$operators}).data('enums', {$enums});")
        ;

        $this->getHelper()->getBodyView()
            ->setTemplatePath($this->getArchiveTemplatePath())
            ->assignVars([
                'title'             => $this->getArchiveTitle(),
                'left_header_view'  => $this->getLeftHeaderCode(),
                'right_header_view' => $this->getRightHeaderCode(),
                'filters_view'      => $this->getFiltersCode($form),
                'table_view'        => $this->getTableCode(),
            ])
        ;

        $this->beforeRenderArchive();
        $this->getHelper()->notify($this, $this->getEventNamespace('notify_before_render_archive'));

        return null;
    }

    /**
     * Controlla il permesso per utilizzare l'azione.
     *
     * @throws HttpUnauthorizedException
     */
    protected function checkAddPermit()
    {
    }

    /**
     * Controlla il permesso per utilizzare l'azione.
     *
     * @throws HttpUnauthorizedException
     */
    protected function checkEditPermit()
    {
    }

    /**
     * Controlla il permesso per utilizzare l'azione.
     *
     * @throws HttpUnauthorizedException
     */
    protected function checkDeletePermit()
    {
    }

    /**
     * Controlla il permesso per utilizzare l'azione.
     *
     * @throws HttpUnauthorizedException
     */
    protected function checkClonePermit()
    {
    }

    /**
     * Controlla il permesso per utilizzare l'azione.
     *
     * @throws HttpUnauthorizedException
     */
    protected function checkMassPermit()
    {
    }

    /**
     * Azione `add`.
     *
     * @return Response
     */
    public function addAction()
    {
        $this->checkAddPermit();

        $response = $this->getHelper()->notifyUntil($this, $this->getEventNamespace('notify_add'));

        if ($response instanceof Response) {
            return $response;
        }

        return $this->addEdit();
    }

    /**
     * Azione `edit`.
     *
     * @return Response
     */
    public function editAction()
    {
        $this->checkEditPermit();

        $response = $this->getHelper()->notifyUntil($this, $this->getEventNamespace('notify_edit'));

        if ($response instanceof Response) {
            return $response;
        }

        return $this->addEdit();
    }

    /**
     * Azione `clone`.
     *
     * @return Response
     */
    public function cloneAction()
    {
        $this->checkClonePermit();

        return $this->addEdit();
    }

    /**
     * Azione `delete`.
     *
     * @return Response
     * @throws \Exception
     */
    public function deleteAction()
    {
        $this->checkDeletePermit();

        if (($model = $this->getModel()) === null) {
            throw new HttpNotFoundException('Record not found!');
        }

        try {
            $this->invokeCallBack('before_delete');

            $delete = $model->transaction(
                function () use ($model) {
                    return $model->delete();
                }
            );

            if ($delete) {
                $this->invokeCallBack('after_delete');

                if ($this->getRequest()->isAjax()) {
                    return $this->getHelper()->displayJsonMessage('Riga eliminata!');
                } else {
                    return new RedirectResponse($this->url('/' . $this->getParameter('path') . '/'));
                }
            }

            throw new \RuntimeException('Could not delete the record!');
        } catch (\Exception $exception) {
            if ($this->getRequest()->isAjax()) {
                return $this->getHelper()->displayJsonError($exception->getMessage());
            } else {
                throw $exception;
            }
        }
    }

    /**
     * Azione `mass`.
     *
     * @return RedirectResponse
     * @throws \ActiveRecord\Exceptions\ActiveRecordException
     * @throws \Exception
     */
    public function massAction()
    {
        if ($this->getRequest()->getMethod() !== 'POST') {
            throw new HttpException(405);
        }

        $this->checkMassPermit();

        $ids = $_POST['ids'] ?? [];
        $action = $_POST['mass_action'] ?? null;

        if (!empty($ids)) {
            /** @var \ActiveRecord\Base $model_class */
            $model_class = $this->getModelClass();

            if (method_exists($model_class, $action)) {
                // Le azioni di massa devono essere eseguite caricando un modello alla volta e non tutti assieme,
                // perché quando un modello viene modificato potrebbe modificare anche gli altri senza che questi
                // vengano aggiornati. È il caso, ad esempio, delle azioni "Sposta nel cestino" e "Archivia" del CMS,
                // dove le posizioni left e right dei nodi vengono spostate.
                foreach (array_keys($ids) as $id) {
                    if (($model = $model_class::find($id)) === null) {
                        continue;
                    }

                    $model->transaction(
                        function () use ($model, $action) {
                            return $model->$action();
                        }
                    );
                }
            }
        }

        return new RedirectResponse($this->getMassActionRedirectUrl());
    }

    /**
     * Restituisce il percorso del template da utilizzare per le action add e edit.
     *
     * @throws \UnexpectedValueException
     * @return string
     *
     * @todo Quando passeremo alla gestione delle form di amministrazione con le
     *       Pongho\Form, potremo usare un template generico per le pagine add/edit.
     */
    protected function getAddEditTemplatePath()
    {
        $ref = new \ReflectionClass($this);
        $class_dir = dirname($ref->getFileName());
        $views_dir = $class_dir . '/../../Resources/views';

        if (!file_exists($views_dir) || !is_dir($views_dir)) {
            throw new \UnexpectedValueException(sprintf('Directory "%s" not exists!', $views_dir));
        }

        $class_name = Inflector::demodulize($ref->getName());
        $filepath = $views_dir . '/' . Inflector::underscore(substr($class_name, 0, -10)) . '_edit.php';

        if (!file_exists($filepath)) {
            throw new \UnexpectedValueException(sprintf('File "%s" not exists!', $filepath));
        }

        return $filepath;
    }

    /**
     * Richiama una callback.
     *
     * @throws \InvalidArgumentException
     */
    protected function invokeCallBack(...$args)
    {
        if (!isset($args[0])) {
            throw new \InvalidArgumentException(
                sprintf('Method "%s()" expects at least 1 argument, 0 given!', __METHOD__)
            );
        }

        $callback_method = Inflector::camelize(array_shift($args), false) . 'Callback';

        if (method_exists($this, $callback_method)) {
            call_user_func_array([$this, $callback_method], $args);
        }
    }

    /**
     * Salva il modello.
     *
     * @param mixed $model
     * @return bool
     */
    protected function saveModel($model)
    {
        return $model->save();
    }

    /**
     * Indica se è stato eseguito l’invio del modulo.
     *
     * @return bool
     */
    protected function isSubmit()
    {
        return $this->getRequest()->getMethod() === 'POST';
    }

    /**
     * Restituisce il modello in base all’azione.
     *
     * @return mixed
     * @throws HttpNotFoundException
     */
    protected function getModel()
    {
        /** @var \ActiveRecord\Base $model_class */
        $model_class = $this->getModelClass();

        if ($this->action == 'add') {
            return new $model_class();
        } else {
            $model = $model_class::find($this->getParameter('id', null, true), ['cachable' => false]);

            if (!$model) {
                throw new HttpNotFoundException();
            }

            if ($this->action == 'duplicate') {
                return clone $model;
            } else {
                return $model;
            }
        }
    }

    /**
     * Restituisce la risposta dopo il salvatggio del modello.
     *
     * @param mixed $model
     * @return RedirectResponse
     */
    protected function getAddEditResponse($model)
    {
        return new RedirectResponse($this->url(sprintf('/%s/edit/%d/', $this->getParameter('path'), $model->id)));
    }

    /**
     * Restituisce la action per le azioni di massa.
     *
     * @return string
     */
    protected function getMassActionUrl()
    {
        return $this->url('/' . $this->getParameter('path') . '/mass/');
    }

    /**
     * Restituisce il link di redirect per le azioni di massa.
     *
     * @return string
     */
    protected function getMassActionRedirectUrl()
    {
        return $this->url('/' . $this->getParameter('path') . '/');
    }

    /**
     * Restituisce l’elenco delle azioni di massa.
     *
     * @return array
     */
    protected function getMassActions()
    {
        return ['delete' => $this->getHelper()->getTranslator()->trans('Delete')];
    }

    /**
     * Restituisce il codice per visualizzare le azioni di massa.
     *
     * @return string
     */
    protected function getMassActionButtons()
    {
        $translator = $this->getHelper()->getTranslator();

        $mass_actions = $this->getMassActions();

        switch (count($mass_actions)) {
            case 0:
                return '';

            case 1:
                $label = reset($mass_actions);
                $action = key($mass_actions);

                return '<input type="hidden" name="mass_action" value="' . $action . '" class="hide" /><input type="submit" class="pongho-button" name="submit" value="' . $label . '" />';

            default:
                $options = '<option>' . $translator->trans('Mass actions') . '</option>';

                foreach ($mass_actions as $action => $label) {
                    $options .= '<option value="' . $action . '">' . $label . '</option>';
                }

                return '<select name="mass_action">' . $options . '</select> <input type="submit" class="pongho-button" value="' . $translator->trans('Submit') . '" name="submit" />';
        }
    }

    /**
     * Visualizza la paginazione
     *
     * @param int   $page          Il numero della pagina corrente.
     * @param int   $total_records Numero totale dei records.
     * @param int   $rows_per_page Numero di righe per pagina da visualizzare.
     * @param array $query         Elenco dei parametri da aggiungere in query string nei collegamenti.
     */
    protected function displayPagination($page, $total_records, $rows_per_page = null, array $query = [])
    {
        if ($rows_per_page === null) {
            $rows_per_page = $this->records_per_page;
        }

        $url = preg_replace('#(page\/(\d+)\/)$#', '', $this->getRequest()->getPathInfo());

        $this->getHelper()->getBodyView()
            ->assignVar('pagination', new Pagination($url, $total_records, $page, $rows_per_page, $query));
    }

    /**
     * Genera la lista delle azioni.
     *
     * L’array da passare al metodo deve essere di questo tipo:
     *
     * <code>
     * $options = array(
     *     array('Edit', 'href' => $this->_url(""), 'title' => 'Edit_message', 'class' => 'edit'),
     *     array('Delete', 'href' => $this->_url(""), 'class' => 'delete'),
     * );
     * </code>
     *
     * @return string
     */
    protected function parseActions(array $actions)
    {
        /** @var \Pongho\Template\Html $html */
        $html = $this->getContainer()->get('template_html');

        $code = [];
        foreach ($actions as $action) {
            $label = array_shift($action);
            $attributes = $action;

            $code[] = $html->generateTagBlock('a', $label, $attributes);
        }

        return implode(' | ', $code);
    }

    /**
     * Abilita l'editor wysiwyg (TinyMCE).
     *
     * @deprecated
     */
    protected function enableWysiwyg(array $options = [])
    {
        trigger_error('Method ' . __METHOD__ . ' is deprecated.', E_USER_DEPRECATED);

        $this->getHelper()->loadEditorWysiwyg($options, $this);
    }

    /**
     * Abilita il supporto per il campo Mappa.
     *
     * @todo: spostare su helper, rimettere la visibilità a protected, deprecare e modificare HDC
     */
    public function enableGeoField()
    {
        /** @var \Pongho\Template\Helper\JavaScript $js */
        $js = $this->container->get('admin.footer_js');

        $site = $this->getHelper()->getSite();

        $js->addInline('Pongho.GoogleMaps = {apiKey: "' . $site->getOption('google_maps_api_key') . '"};');
        $js->add(pongho_url('/Application/Core/Resources/views/js/geo-localization.js'));
    }

    /**
     * @return array
     */
    public function convertTableColumns()
    {
        $columns = $this->getTableColumns();

        // Questa parte la fa per retro compatibilità
        if (!is_hash($columns)) {
            $cols = [];
            foreach ($columns as $column) {
                $cols[$column['name']] = $column;
                unset($column['name']);
            }

            $columns = $cols;
        }

        $columns = $this->getHelper()->filter(
            $this,
            $this->getEventNamespace('filter_table_columns'),
            new AssociativeArrayObject($columns)
        );

        return $columns->getArrayCopy();
    }

    /**
     * @return string
     */
    protected function getTableEmptyRecordsetText()
    {
        $in_search = $this->getRequest()->query->get('q', '') !== ''
            || $this->getRequest()->query->get('f')
            || $this->getRequest()->query->get('filter');

        if ($in_search) {
            return $this->getArchiveSearchNoResultsText();
        }

        if ($this->canAdd()) {
            $path = '/' . $this->getParameter('path') . '/';

            return '<a href="' . $this->url($path . 'add/') . '">' . $this->getArchiveAddFirstText() . '</a>';
        }

        return $this->getArchiveEmptyRecordsetText();
    }

    /**
     * Restituisce il gestore degli archivi
     *
     * @param array $config
     * @return ArchiveManager
     */
    protected function getArchiveManager(?array $config = null)
    {
        if ($this->am === null) {
            $path = '/' . $this->getParameter('path') . '/';

            if ($config === null) {
                $modelOptions = $this->getHelper()->filter(
                    $this,
                    $this->getEventNamespace('filter_model_options'),
                    $this->getModelOptions()
                );

                $config = [
                    'model_class'   => $this->getModelClass(),
                    'model_options' => $modelOptions,
                    'model_fields'  => $this->getModelFieldsConfigWrapper(),
                    'parse_row'     => $this->parseArchiveRow(...),
                    'search_fields' => $this->getSearchFieldsWrapper(),
                    'fast_filters'  => $this->getFastFilters(),
                    'form'          => $this->getFiltersForm(...),
                    'table'         => [
                        'columns'         => $this->convertTableColumns(...),
                        'empty_recordset' => $this->getTableEmptyRecordsetText(),
                        'row_attributes'  => $this->setRowAttributes(...),
                    ],
                ];
            }

            $this->am = new ArchiveManager(
                $this->getRequest(),
                $this->getHelper()->getTranslator(),
                $this->getHelper()->filter($this, $this->getEventNamespace('filter_archive_config'), $config),
                pongho_url($path),
                $this->page
            );
        }

        return $this->am;
    }

    /**
     * Aggiunge le azioni alle righe dell'elenco dei modelli
     *
     * @param mixed $row
     */
    public function parseArchiveRow($row)
    {
        $path = $this->getParameter('path');

        $translator = $this->getHelper()->getTranslator();

        $actions = [
            [$translator->trans('Edit'), 'href' => $this->url("/{$path}/edit/{$row->id}/"), 'class' => 'edit'],
            [$translator->trans('Delete'), 'href' => $this->url("/{$path}/delete/{$row->id}/"), 'class' => 'delete'],
        ];

        $row->actions = $this->parseActions($actions);
    }

    /**
     * @param mixed $row
     * @return array
     */
    public function setRowAttributes($row)
    {
        /** @var ArchiveLastViewManager $last_view_manager */
        $last_view_manager = $this->getContainer()->get('archive_last_view_manager');

        $last_view = $last_view_manager->findByEntity($this->getHelper()->getUserId(), $this->getNamespace());

        $attributes = [];

        if ($last_view && $last_view->getEntityId() === $row->id) {
            $attributes['class'] = 'last-view';
        }

        return $attributes;
    }

    /**
     * @param $row
     */
    protected function updateLastViewedRow($row)
    {
        // Se sto aggiungendo un nuovo record non ho ancora il suo ID, non faccio niente, se ne occuperà l'edit
        if ($this->getAction() === 'add') {
            return;
        }

        /** @var ArchiveLastViewManager $last_view_manager */
        $last_view_manager = $this->getContainer()->get('archive_last_view_manager');

        $last_view = $last_view_manager->findByEntity($this->getHelper()->getUserId(), $this->getNamespace());

        if ($last_view === null) {
            $last_view = new ArchiveLastView([
                'user_id'     => $this->getHelper()->getUserId(),
                'entity_type' => $this->getNamespace(),
                'entity_id'   => $row->id,
            ]);
        } else {
            $last_view->setEntityId($row->id);
        }

        $last_view_manager->save($last_view);
    }

    /**
     * Aggiunge le azioni alle righe dell'elenco dei filtri salvati
     *
     * @param mixed $row
     */
    public function parseFilterRow($row)
    {
        $path = $this->getParameter('path');
        $translator = $this->getHelper()->getTranslator();

        $actions = [
            [
                $translator->trans('Execute'),
                'href'  => $this->url("/{$path}/?filter={$row->id}"),
                'class' => 'run',
            ],
            [
                $translator->trans('Delete'),
                'href'  => $this->url("/{$path}/filterdelete/{$row->id}/"),
                'class' => 'delete',
            ],
        ];

        $row->actions = $this->parseActions($actions);
    }

    /**
     * @return array
     */
    protected function getSearchFieldsWrapper()
    {
        return $this->getHelper()->filter(
            $this,
            $this->getEventNamespace('filter_search_fields'),
            $this->getSearchFields()
        );
    }

    /**
     * @return array
     */
    protected function getFilterEnumValuesWrapper()
    {
        return $this->getHelper()->filter(
            $this,
            $this->getEventNamespace('filters.filter_enums_values'),
            $this->getFilterEnumValues()
        );
    }

    /**
     * @return array
     */
    protected function getFilterSelectValuesWrapper()
    {
        return $this->getHelper()->filter(
            $this,
            $this->getEventNamespace('filters.filter_select_values'),
            $this->getFilterSelectValues()
        );
    }

    /**
     * @return array
     */
    protected function getModelFieldsConfigWrapper()
    {
        return $this->getHelper()->filter(
            $this,
            $this->getEventNamespace('filters.filter_model_fields'),
            $this->getModelFieldsConfig()
        );
    }

    /**
     * Restituisce la form dei filtri.
     *
     * @return \Pongho\Form\Form
     */
    public function getFiltersForm(Filter $filter)
    {
        $translator = $this->getHelper()->getTranslator();
        $path = $this->getParameter('path');

        $subject = new FilterSubject($filter);

        $actions_view = new View(__DIR__ . '/../Resources/views/filter-actions.php');

        $actions_view->assignVar('translator', $translator);

        $config = new FormConfig('filters', $subject, $translator);

        $config->addFields(
            '',
            [
                'explain' => [
                    'class'       => Fieldset::class,
                    'description' => $translator->trans('The filters will meet all the conditions. If a condition will be selected multiple times, it will be taken into account only the last occurrence'),
                    'attributes'  => [
                        'class' => 'filters-description',
                    ],
                    'settings'    => [
                        'hide_label' => true,
                    ],
                ],
                'filter'  => [
                    'class'      => SerializableRepeater::class,
                    'subject'    => $subject,
                    'attributes' => ['class' => 'panel'],
                    'label'      => $translator->trans('Filters'),
                    'settings'   => [
                        'check_valid_row_callback' => $this->filterCheckValidRowCallback(...),
                        'filter_fields_config'     => $this->filterFiltersFieldsCallback(...),
                        'row_settings'             => [
                            'title'        => 'Cerca per:',
                            'open-label'   => $translator->trans('Advanced'),
                            'close-label'  => $translator->trans('Close'),
                            'delete-label' => $translator->trans('Delete'),
                            'hide-open'    => true,
                            'show-delete'  => true,
                            'show-content' => false,
                            'hide-options' => true,
                        ],
                        'columns'                  => $this->getModelFields(),
                    ],
                ],
                'actions' => [
                    'class'      => Fieldset::class,
                    'label'      => $translator->trans('Save filter'),
                    'attributes' => ['class' => 'filters-actions'],
                    'settings'   => [
                        'hide_label' => true,
                        'view'       => $actions_view,
                    ],
                ],
            ]
        );

        $config->addField(
            'filter/main',
            [
                'class' => RowMain::class,
            ]
        );

        $config->addFields(
            'filter/main',
            [
                'field'           => [
                    'class'      => SelectField::class,
                    'options'    => $this->getModelFieldsSelectOptions(),
                    'attributes' => [
                        'class'    => 'filter-field chosen',
                        'data-ref' => pongho_url('/' . $path . '/filtervalue/'),
                    ],
                ],
                'operator'        => [
                    'class'      => SelectField::class,
                    'options'    => [],
                    'attributes' => [
                        'class' => 'filter-operator',
                    ],
                ],
                'type'            => [
                    'class'      => HiddenField::class,
                    'attributes' => [
                        'class' => 'filter-type',
                    ],
                ],
                'valuefield'      => [
                    'class'      => HiddenField::class,
                    'attributes' => [
                        'class' => 'filter-valuefield',
                    ],
                ],
                'value_text'      => [
                    'class'      => TextField::class,
                    'attributes' => [
                        'placeholder' => $translator->trans('Empty'),
                        'class'       => 'input_text filter-value filter-value-text',
                    ],
                ],
                'value_select'    => [
                    'class'      => SelectField::class,
                    'options'    => [],
                    'attributes' => [
                        'class' => 'filter-value filter-value-select',
                        'style' => 'display: none;',
                    ],
                ],
                'value_multiple'  => [
                    'class'      => SelectField::class,
                    'options'    => [],
                    'attributes' => [
                        'class'    => 'filter-value filter-value-multiple',
                        'multiple' => 'multiple',
                        'style'    => 'display: none;',
                        'size'     => 10,
                    ],
                ],
                'datewrapper'     => [
                    'class'      => Fieldset::class,
                    'attributes' => [
                        'class' => 'picker-wrapper date-wrapper',
                    ],
                    'settings'   => [
                        'view' => new ViewClosure($this->getWrapperView(...)),
                    ],
                ],
                'datetimewrapper' => [
                    'class'      => Fieldset::class,
                    'attributes' => [
                        'class' => 'picker-wrapper datetime-wrapper',
                    ],
                    'settings'   => [
                        'view' => new ViewClosure($this->getWrapperView(...)),
                    ],
                ],
                'timewrapper'     => [
                    'class'      => Fieldset::class,
                    'attributes' => [
                        'class' => 'picker-wrapper time-wrapper',
                    ],
                    'settings'   => [
                        'view' => new ViewClosure($this->getWrapperView(...)),
                    ],
                ],
            ]
        );

        $config->addFields(
            'filter/main/datewrapper',
            [
                'value_date'    => [
                    'class'      => DateField::class,
                    'attributes' => [
                        'class'       => 'filter-value filter-value-date filter-value-date-between date-between-from',
                        'placeholder' => $translator->trans('Empty'),
                        'style'       => 'display: none;',
                    ],
                ],
                'value_date_to' => [
                    'class'      => DateField::class,
                    'attributes' => [
                        'class'       => 'filter-value filter-value-date-between date-between-to',
                        'placeholder' => $translator->trans('Empty'),
                        'style'       => 'display: none;',
                    ],
                ],
            ]
        );

        $config->addFields(
            'filter/main/datetimewrapper',
            [
                'value_datetime'    => [
                    'class'      => DateTimeField::class,
                    'attributes' => [
                        'class'        => 'filter-value filter-value-datetime filter-value-datetime-between datetime-between-from',
                        'placeholder'  => $translator->trans('Empty'),
                        'style'        => 'display: none;',
                        'data-options' => htmlspecialchars(
                            json_encode(
                                [
                                    'stepMinute' => 1,
                                ]
                            )
                        ),
                    ],
                ],
                'value_datetime_to' => [
                    'class'      => DateTimeField::class,
                    'attributes' => [
                        'class'        => 'filter-value filter-value-datetime-between datetime-between-to',
                        'placeholder'  => $translator->trans('Empty'),
                        'style'        => 'display: none;',
                        'data-options' => htmlspecialchars(
                            json_encode(
                                [
                                    'stepMinute' => 1,
                                ]
                            )
                        ),
                    ],
                ],
            ]
        );

        $config->addFields(
            'filter/main/timewrapper',
            [
                'value_time'    => [
                    'class'      => TimeField::class,
                    'attributes' => [
                        'class'        => 'filter-value filter-value-time filter-value-time-between time-between-from',
                        'placeholder'  => $translator->trans('Empty'),
                        'style'        => 'display: none;',
                        'data-options' => htmlspecialchars(
                            json_encode(
                                [
                                    'showSecond' => 1,
                                    'stepMinute' => 1,
                                    'timeFormat' => 'HH:mm:ss',
                                ]
                            )
                        ),
                    ],
                ],
                'value_time_to' => [
                    'class'      => TimeField::class,
                    'attributes' => [
                        'class'        => 'filter-value filter-value-time-between time-between-to',
                        'placeholder'  => $translator->trans('Empty'),
                        'style'        => 'display: none;',
                        'data-options' => htmlspecialchars(
                            json_encode(
                                [
                                    'showSecond' => 1,
                                    'stepMinute' => 1,
                                    'timeFormat' => 'HH:mm:ss',
                                ]
                            )
                        ),
                    ],
                ],
            ]
        );

        $config->addFields(
            'actions',
            [
                'name' => [
                    'class'      => TextField::class,
                    'attributes' => ['class' => 'input_text', 'placeholder' => $translator->trans('Name')],
                    'settings'   => [
                        'hide_label' => true,
                    ],
                ],
            ]
        );

        // Aggiungo il campo ID per sovrascrivere un filtro salvato
        if (!$filter->isNewRecord()) {
            $config->addField(
                'actions/id',
                [
                    'class' => HiddenField::class,
                ]
            );
        }

        $config = $this->getHelper()->filter($this, $this->getEventNamespace('filter_filters'), $config);

        $form = FormBuilder::buildConfig($config)
            ->setGenerator(new AdminGenerator($this->getHelper()->getLocalization()))
            ->setAttribute('action', $this->url('/' . $path . '/'))
            ->setAttribute('data-ref', $this->url('/' . $path . '/filterslist/'))
            ->setAttribute('data-save', $this->url('/' . $path . '/' . ($filter->isNewRecord() ? '' : '?filter=' . $filter->id)));
        $form->afterAddFieldToParent();

        return $form;
    }

    /**
     * @return string
     */
    public function getWrapperView(Fieldset $field)
    {
        $tags = '';

        /** @var \Pongho\Form\BaseField $f */
        foreach ($field->getFields() as $f) {
            $tags .= $field->getGenerator()->renderTag($f);
        }

        return '<div style="display: inline-block;" class="' . implode(' ', $field->getCssClasses()) . '">' . $tags . '</div>';
    }

    /**
     * Controlla la validità dei singoli filtri prima di salvarli
     *
     * @param int   $id
     * @return bool
     */
    public function filterCheckValidRowCallback($id, array $row_request)
    {
        return isset($row_request['field']) && $row_request['field'] !== '';
    }

    /**
     * Gestisce il contenuto delle righe dei singoli filtri
     *
     * @return array
     */
    public function filterFiltersFieldsCallback(RepeaterRow $row, array $rowFields)
    {
        if ($row->getKey() !== 'new') {
            /** @var \Pongho\Form\Subject\RowSubjectInterface $rowSubject */
            $rowSubject = $row->getSubject();

            $rowConfig = FormConfig::fromArray($rowFields);

            $operators = $this->getHelper()->filter(
                $this,
                $this->getEventNamespace('filter_filters_operators'),
                $this->getContainer()->get('filter_operators')->getOperators()
            );

            $filterType = $rowSubject->get('type');

            $operatorSelect = $rowConfig->getField('main/operator');
            $operatorSelect['options'] = $operators[$filterType] ?? [];

            if ($filterType === 'foreign' || $filterType === 'enum') {
                $fieldName = $rowSubject->get('field'); // TODO: Rinominare 'field' con 'field_name'
                $fieldType = $rowSubject->get('valuefield'); // TODO: Rinominare 'valuefield' con 'field_type'
                $fieldValue = $rowSubject->get($fieldType);

                if ($filterType === 'enum') {
                    $options = $this->getFilterRowEnumOptionsCallback($fieldName);
                } else {
                    $options = $this->getFilterRowSelectOptionsCallback($fieldName, $fieldValue);
                }

                $fieldConfig = $rowConfig->getField('main/' . $fieldType);
                $fieldConfig['options'] = $options;
            }

            return $rowConfig->getFormFields();
        }

        return $rowFields;
    }

    /**
     * Restituisce i campi del modello tipizzati per gestire gli operatori dei filtri in base al tipo delle colonne del modello
     *
     * @return array
     * @throws \LogicException
     */
    protected function getModelFields()
    {
        $fields = $this->getModelFieldsConfigWrapper();
        $translator = $this->getHelper()->getTranslator();

        $model_fields = [null => $translator->trans('Select a field')];

        /** @var \ActiveRecord\Base $model_class */
        $model_class = $this->getModelClass();
        $model_columns = $model_class::columns();

        foreach ($fields as $field => $options) {
            if (isset($options['data-type'])) {
                $filter_type = $options['data-type'];
            } elseif (isset($options['type'])) {
                $filter_type = $options['type'];
            } else {
                $filter_type = '';
                if (array_key_exists($field, $model_columns)) {
                    switch ($model_columns[$field]->type) {
                        case Column::STRING:
                            $filter_type = 'string';
                            break;

                        case Column::BOOLEAN:
                            $filter_type = 'enum';
                            break;

                        case Column::INTEGER:
                        case Column::DECIMAL:
                            $filter_type = 'numeric';
                            break;

                        case Column::DATE:
                        case Column::DATETIME:
                            $filter_type = 'date';
                            break;
                    }
                } else {
                    throw new \LogicException(
                        sprintf(
                            'The column "%s" is not defined in the table "%s" (maybe is it a join?), therefore you must manually define the "data-type"',
                            $field,
                            $model_class::tableName()
                        )
                    );
                }
            }

            $model_fields[$field] = [
                $options['label'] ?? $field,
                'data-type' => $filter_type,
            ];

            if (isset($options['attributes'])) {
                unset($options['attributes']['data-type']);

                foreach ($options['attributes'] as $attribute => $value) {
                    $model_fields[$field][$attribute] = $value;
                }
            }

            if (isset($options['optgroup'])) {
                $model_fields[$field]['optgroup'] = $options['optgroup'];
            }
        }

        return $model_fields;
    }

    /**
     * @return array
     */
    protected function getModelFieldsSelectOptions()
    {
        $options = [];

        foreach ($this->getModelFields() as $field => $config) {
            if (is_array($config) && isset($config['optgroup'])) {
                $optgroup = $config['optgroup'];
                unset($config['optgroup']);
                $options[$optgroup]['optgroup'][$field] = $config;
            } else {
                $options[$field] = $config;
            }
        }

        return $options;
    }

    /**
     * Restituisce le opzioni per i campi enum dei filtri.
     *
     * @param string $field_name
     * @return array
     */
    public function getFilterRowEnumOptionsCallback($field_name)
    {
        $enums = $this->getFilterEnumValuesWrapper();

        $options = [];
        if (array_key_exists($field_name, $enums)) {
            $options = $enums[$field_name];
        }

        return $options;
    }

    /**
     * Gestisce la generazione delle opzioni della select per i dati del campo del modello.
     *
     * @param string $fieldName
     * @param mixed  $fieldValue
     * @return array
     */
    public function getFilterRowSelectOptionsCallback($fieldName, $fieldValue = null)
    {
        /** @var \ActiveRecord\Base $modelClass */
        $modelClass = $this->getModelClass();

        $fields = [
            'default' => function ($fieldValue, $fieldName) use ($modelClass) {
                $options = [
                    'select' => $fieldName,
                    'group'  => $fieldName,
                    'order'  => $fieldName . ' ASC',
                ];

                $o = [];

                foreach ($modelClass::all($options) as $row) {
                    $o[$row->$fieldName] = $row->$fieldName;
                }

                return $o;
            },
        ];

        $fields = array_merge($fields, $this->getFilterSelectValuesWrapper());

        if (array_key_exists($fieldName, $fields)) {
            $options = call_user_func($fields[$fieldName], $fieldValue, $fieldName);
        } else {
            $options = call_user_func($fields['default'], $fieldValue, $fieldName);
        }

        return $options;
    }

    /**
     * Restituisce le opzioni per la select dei valori del filtro.
     *
     * @return JsonResponse
     */
    public function filtervalueAction()
    {
        $field = $this->getRequest()->query->get('f');

        return new JsonResponse($this->getFilterRowSelectOptionsCallback($field));
    }

    /**
     * Esegue le azioni legate al filtro.
     *
     * @deprecated
     *
     * @example
     * -> GET
     * /pongho/users/filter/        => form vuota
     * /pongho/users/filter/123/    => carico il filtro 123 da db
     * -> POST
     * /pongho/users/filter/        => aggiungo un nuovo filtro con i dati provenienti da POST
     * /pongho/users/filter/123/    => modifico il filtro 123 con i dati provenienti da POST
     */
    public function filterAction()
    {
        throw new HttpNotFoundException('This action has been deprecated, use "/pongho/path/?filter=123" instead');
    }

    /**
     * Restituisce l'elenco dei filtri salvati.
     *
     * @return Response
     */
    public function filtersAction()
    {
        return new Response($this->getFiltersListHtml());
    }

    /**
     * Restituisce i filtri per l'elenco corrente
     *
     * @return Filter[]
     */
    protected function getFiltersList()
    {
        /** @var Filter[] $filters */
        $filters = Filter::all([
            'conditions' => [
                'namespace = :namespace AND (user_id = :user OR user_id = :anon)',
                'namespace' => $this->getNamespace(),
                'user'      => $this->getContainer()->get('current_user')->id,
                'anon'      => Account::ANONYMOUS,
            ],
        ]);

        foreach ($filters as $filter) {
            $this->parseFilterRow($filter);
        }

        return $filters;
    }

    /**
     * Restituisce il template dell'elenco dei filtri salvati.
     *
     * @return string
     */
    protected function getFiltersListHtml()
    {
        $this->getHelper()->getBodyView()
            ->setTemplatePath(__DIR__ . '/../Resources/views/list_filters_table.php')
            ->assignVars([
                'filters_list' => $this->getFiltersList(),
            ]);

        return $this->getHelper()->getBodyView()->render();
    }

    /**
     * Elimina un filtro salvato.
     *
     * @return JsonResponse
     */
    public function filterdeleteAction()
    {
        $id = intval($this->getParameter('id'));

        $translator = $this->getHelper()->getTranslator();

        $filter = Filter::find($id);

        if ($filter && !$filter->delete()) {
            return new JsonResponse([
                'error'   => true,
                'message' => $translator->trans('An error occurred when trying to delete the filter'),
            ]);
        }

        return new JsonResponse(['message' => $translator->trans('The filter has been deleted')]);
    }

    /**
     * Gestisce le azioni di massa dei filtri.
     *
     * @return Response
     */
    public function filtermassAction()
    {
    }

    /**
     * Metodo da sovrascrivere per aggiungere alla vista variabili richieste
     * dai template personalizzati per le singole applicazioni.
     */
    protected function beforeRenderArchive()
    {
    }

    /**
     * Restituisce il codice per la parte sinistra dell'header di amministrazione.
     *
     * @return string
     */
    protected function getLeftHeaderCode()
    {
        return $this->getAddButtonCode() . $this->getFastFiltersCode();
    }

    /**
     * Restituisce il codice per la parte destra dell'header di amministrazione.
     *
     * @return string
     */
    protected function getRightHeaderCode()
    {
        return '<div class="pongho-search-form-wrapper">' . $this->getSearchFormCode() . '</div>'
            . $this->getFiltersButtonCode()
            . $this->getExportButtonCode();
    }

    /**
     * Restituisce il codice per il pulsante di aggiunta di un nuovo record.
     *
     * @return string
     */
    protected function getAddButtonCode()
    {
        if (!$this->hasAddButton()) {
            return '';
        }

        $path = $this->getParameter('path');

        return '<div class="actions"><a href="' . $this->url('/' . $path . '/add/') . '" class="primary pongho-button">' . $this->getArchiveAddButtonText() . '</a></div>';
    }

    /**
     * Restituisce il codice per i filtri rapidi.
     *
     * @return string
     */
    protected function getFastFiltersCode()
    {
        if (!$this->getArchiveManager()->isFastFiltersEnabled()) {
            return '';
        }

        return '<div class="filter-status"><ul>' . $this->getArchiveManager()->getFastFilters() . '</ul></div>';
    }

    /**
     * Restituisce il codice per la Form di ricerca.
     *
     * @return string
     */
    protected function getSearchFormCode()
    {
        if (!$this->getArchiveManager()->isSearchEnabled()) {
            return '';
        }

        $formAction = $this->url('/' . $this->getParameter('path') . '/');
        $query = $this->getRequest()->query->get('q', '');
        $siteId = $this->getHelper()->getSiteId();
        $languageId = $this->getHelper()->getLanguageId();

        $fastFilterFields = $this->buildFastFiltersFieldsForSearchForm(
            $this->getRequest()->query->get('f[status]', '', true)
        );

        $translator = $this->getHelper()->getTranslator();

        return <<<HTML
    <form method="get" class="pongho-form pongho-search-form" action="{$formAction}">
        <input type="hidden" name="site" value="{$siteId}">
        <input type="hidden" name="language" value="{$languageId}">
        {$fastFilterFields}
        <input type="text" id="search-query" name="q" class="pongho-search-form__query" value="{$query}">
        <input type="submit" id="search-button" class="pongho-search-form__btn" value="{$translator->trans('Search')}">
    </form>
HTML;
    }

    /**
     * @param array|string $value
     * @param string       $name
     * @return string
     */
    private function buildFastFiltersFieldsForSearchForm($value, $name = 'f[status]')
    {
        if (empty($value)) {
            return '';
        }

        if (is_array($value)) {
            $result = '';
            foreach ($value as $subKey => $subValue) {
                $subName = $name . '[' . $subKey . ']';
                $result .= $this->buildFastFiltersFieldsForSearchForm($subValue, $subName);
            }

            return $result;
        }

        return sprintf('<input type="hidden" name="%s" value="%s">', $name, $value);
    }

    /**
     * Restituisce il codice per il bottone di apertura dei filtri.
     *
     * @return string
     */
    protected function getFiltersButtonCode()
    {
        if (!$this->getArchiveManager()->isFiltersEnabled()) {
            return '';
        }

        $translator = $this->getHelper()->getTranslator();

        return '<button id="filter-toggle" class="pongho-button">' . $translator->trans('Filters') . '</button>';
    }

    /**
     * Form dei filtri.
     *
     * @return string
     */
    protected function getFiltersCode(Form $form)
    {
        $view = new View(__DIR__ . '/../Resources/views/list_filters.php');

        $view->assignVars([
            'filters_list' => $this->getFiltersList(),
            'filters'      => $form->render(),
        ]);

        return $view->render();
    }

    /**
     * Form della tabella.
     *
     * @return string
     */
    protected function getTableCode()
    {
        $view = new View($this->getArchiveTableTemplatePath());

        $view->assignVars([
            'table'               => $this->getArchiveManager()->getTable(),
            'mass_action_url'     => $this->getMassActionUrl(),
            'mass_action_buttons' => $this->getMassActionButtons(),
            'pagination'          => $this->getArchiveManager()->getPagination(),
        ]);

        return $view->render();
    }

    /**
     * @return bool
     */
    protected function isExportEnabled()
    {
        return false;
    }

    /**
     * @return string
     */
    protected function getExportButtonCode()
    {
        if (!$this->isExportEnabled()) {
            return '';
        }

        $label = $this->getHelper()->getTranslator()->trans('Export');
        $url = $this->url('/' . $this->getParameter('path') . '/export/');

        return '<a href="' . $url . '" class="pongho-button">' . $label . '</a>';
    }

    /**
     * @return Response
     * @throws HttpNotFoundException If the export feature is not enabled.
     */
    protected function exportAction()
    {
        if (!$this->isExportEnabled()) {
            throw new HttpNotFoundException();
        }

        $exporter = $this->getExporter();
        $serializer = $this->getExportSerializer();

        $response = $serializer->serialize($exporter);

        $response->getHeaders()
            ->makeDownloadable($this->getNamespace() . '.' . $serializer->getFileExtension())
            ->makeNoCacheable();

        return $response;
    }

    /**
     * @return null|\Application\Core\Export\DataExporterInterface
     */
    protected function getExporter()
    {
        if (!$this->isExportEnabled()) {
            return null;
        }

        $serviceName = 'admin.' . $this->getNamespace() . '.exporter';

        if ($this->getContainer()->has($serviceName)) {
            return $this->getContainer()->get($serviceName);
        }

        throw new \RuntimeException(
            sprintf('The exporter service "%s" does not exist.', $serviceName)
        );
    }

    /**
     * @return null|\Application\Core\Export\SerializerInterface
     */
    private function getExportSerializer()
    {
        if (!$this->isExportEnabled()) {
            return null;
        }

        return $this->getContainer()->get('admin.export_serializer');
    }
}
